antlr/antlr4 · error · ArgumentException

replace op boundaries of {rop} overlap with previous {prevRo

Error message

replace op boundaries of {rop} overlap with previous {prevRop}

What it means

Thrown by TokenStreamRewriter.reduceToSingleOperationPerIndex when two InsertAfter/InsertBefore/Replace operations produce overlapping ReplaceOp instructions that cannot be merged (merging only happens for pure overlapping deletes). ANTLR's rewriter refuses to guess which replacement text should win when two replace regions overlap, so it fails fast instead of silently producing corrupt output.

Source

Thrown at runtime/CSharp/src/TokenStreamRewriter.cs:630

                    }
                    // throw exception unless disjoint or identical
                    bool disjoint = prevRop.lastIndex < rop.index || prevRop.index > rop.lastIndex;
                    // Delete special case of replace (text==null):
                    // D.i-j.u D.x-y.v	| boundaries overlap	combine to max(min)..max(right)
                    if (prevRop.text == null && rop.text == null && !disjoint)
                    {
                        //System.out.println("overlapping deletes: "+prevRop+", "+rop);
                        rewrites[prevRop.instructionIndex] = null;
                        // kill first delete
                        rop.index = Math.Min(prevRop.index, rop.index);
                        rop.lastIndex = Math.Max(prevRop.lastIndex, rop.lastIndex);
                        System.Console.Out.WriteLine("new rop " + rop);
                    }
                    else
                    {
                        if (!disjoint)
                        {
                            throw new ArgumentException("replace op boundaries of " + rop + " overlap with previous " + prevRop);
                        }
                    }
                }
            }
            // WALK INSERTS
            for (int i_1 = 0; i_1 < rewrites.Count; i_1++)
            {
                TokenStreamRewriter.RewriteOperation op = rewrites[i_1];
                if (op == null)
                {
                    continue;
                }
                if (!(op is TokenStreamRewriter.InsertBeforeOp))
                {
                    continue;
                }
                TokenStreamRewriter.InsertBeforeOp iop = (TokenStreamRewriter.InsertBeforeOp)rewrites[i_1];
                // combine current insert with prior if any at same index

View on GitHub (pinned to 7d5770395b)

Solutions

  1. Rework the rewrite logic so token ranges passed to Replace/InsertAfter never overlap: track the last processed index and start the next range after it.
  2. Merge adjacent edits into one Replace call covering the union range with the combined text instead of issuing several overlapping replaces.
  3. Use separate TokenStreamRewriter instances (or re-parse the output) for each independent rewrite pass rather than stacking overlapping edits on one rewriter.
  4. Catch ArgumentException in GetText()/GetTextWithDiff() only to surface a clear diagnostic, then fix the offending edit sequence.

Example fix

// before
rewriter.Replace(0, 5, "a");
rewriter.Replace(3, 8, "b"); // overlaps 0..5
string out1 = rewriter.GetText();

// after
rewriter.Replace(0, 8, "a" + originalText(6, 8) + "b"); // single non-overlapping op
string out2 = rewriter.GetText();
Defensive patterns

Strategy: validation

Validate before calling

// Before issuing a Replace, ensure the range does not overlap a previous one
var issued = new List<(int from, int to)>();
void SafeReplace(TokenStreamRewriter r, int from, int to, string text)
{
    if (issued.Any(r2 => from <= r2.to && to >= r2.from))
        throw new InvalidOperationException($"Replace({from},{to}) overlaps a previous replace");
    issued.Add((from, to));
    r.Replace(from, to, text);
}

Try / catch

try { var text = rewriter.GetText(); } catch (ArgumentException ex) when (ex.Message.Contains("replace op boundaries")) { /* log the edit program and fix the overlapping ranges */ }

Prevention

When it happens

Trigger: Calling tokens.Replace(from, to, text) or InsertAfter/InsertBefore followed by Replace such that two ReplaceOps share token indexes and at least one is a real replace (not both deletes). Example: rewriter.Replace(0, 5, "a") then rewriter.Replace(3, 8, "b") before calling GetText().

Common situations: Buildng source-to-source translators or instrumentation tools that rewrite several neighboring token ranges in a loop; applying a second rewrite pass over ranges produced by a first pass; using delete via Replace(from,to,null) mixed with replaces over the same span.

Related errors


AI-assisted analysis of antlr/antlr4@7d5770395b (2026-08-14). Data as JSON: /api/errors/e1572d9ba2487a66. Report an issue: GitHub.