antlr/antlr4 · error · ArgumentException

insert op {iop} within boundaries of previous {rop}

Error message

insert op {iop} within boundaries of previous {rop}

What it means

Thrown by TokenStreamRewriter when an insert operation's index falls strictly inside the boundaries of a previously issued replace operation (insert at exactly rop.index is legal and is prepended to the replacement text; anything between rop.index+1 and rop.lastIndex-1 is not). The rewriter cannot decide whether the inserted text belongs before, inside, or after the replacement, so it rejects the program.

Source

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

                        iop.text = CatOpText(iop.text, prevIop.text);
                        // delete redundant prior insert
                        rewrites[prevIop.instructionIndex] = null;
                    }
                }
                // look for replaces where iop.index is in range; error
                IList<TokenStreamRewriter.ReplaceOp> prevReplaces = GetKindOfOps<TokenStreamRewriter.ReplaceOp>(rewrites, i_1);
                foreach (TokenStreamRewriter.ReplaceOp rop in prevReplaces)
                {
                    if (iop.index == rop.index)
                    {
                        rop.text = CatOpText(iop.text, rop.text);
                        rewrites[i_1] = null;
                        // delete current insert
                        continue;
                    }
                    if (iop.index >= rop.index && iop.index <= rop.lastIndex)
                    {
                        throw new ArgumentException("insert op " + iop + " within boundaries of previous " + rop);
                    }
                }
            }
            // System.out.println("rewrites after="+rewrites);
            IDictionary<int, TokenStreamRewriter.RewriteOperation> m = new Dictionary<int, TokenStreamRewriter.RewriteOperation>();
            for (int i_2 = 0; i_2 < rewrites.Count; i_2++)
            {
                TokenStreamRewriter.RewriteOperation op = rewrites[i_2];
                if (op == null)
                {
                    continue;
                }
                // ignore deleted ops
                if (m.ContainsKey(op.index))
                {
                    throw new InvalidOperationException("should only be one op per index");
                }
                m[op.index] = op;

View on GitHub (pinned to 7d5770395b)

Solutions

  1. Move the insert outside the replaced range: insert before rop.index or after rop.lastIndex instead of inside it.
  2. Fold the inserted text into the replacement itself: rewriter.Replace(rop.index, rop.lastIndex, insertedText + replacementText).
  3. If the insert index equals the replace start, rely on the supported merge by inserting exactly at rop.index (InsertBefore at the replace start is merged automatically).
  4. Reorder edits so replaces are issued first and then validate each insert index against the replace ranges you recorded.

Example fix

// before
rewriter.Replace(2, 6, "newExpr");
rewriter.InsertAfter(4, ";"); // 4 is inside 2..6 -> throws

// after
rewriter.Replace(2, 6, "newExpr;"); // fold insertion into the replacement
Defensive patterns

Strategy: validation

Validate before calling

// Check an insert index is not strictly inside a previously replaced range
var replaces = new List<(int from, int to)>();
void SafeInsertBefore(TokenStreamRewriter r, int i, string text)
{
    if (replaces.Any(rp => i > rp.from && i < rp.to))
        throw new InvalidOperationException($"Insert at {i} falls inside replace {rp.from}..{rp.to}");
    r.InsertBefore(i, text);
}

Try / catch

try { rewriter.GetText(); } catch (ArgumentException ex) when (ex.Message.Contains("within boundaries")) { /* locate the insert inside a replace and fold its text into the replacement */ }

Prevention

When it happens

Trigger: Calling rewriter.InsertAfter(i, "x") or InsertBefore(i, "x") where a prior rewriter.Replace(i, j, text) (or delete) exists with i in (rop.index, rop.lastIndex) exclusive of the equal-index merge case. Detected during GetText() when the rewrite program is validated.

Common situations: Adding a semicolon or comma after a token that a previous edit already replaced; composing multiple independent fix-up rules (e.g. a formatter plus a refactoring) over the same token stream; off-by-one when computing the insert position relative to a replaced range.

Related errors


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