antlr/antlr4 · error · ValueError

insert op {} within boundaries of previous {}

Error message

insert op {} within boundaries of previous {}

What it means

In the reduction pass, an InsertBeforeOp/InsertAfterOp whose index falls strictly inside a previous ReplaceOp's range (not exactly at its start index, which is merged as prepend) raises this ValueError. Inserting text inside a region that is about to be replaced is meaningless — the replacement would erase it — so ANTLR refuses rather than silently dropping the insert.

Source

Thrown at runtime/Python3/src/antlr4/TokenStreamRewriter.py:189

            if any((iop is None, not isinstance(iop, TokenStreamRewriter.InsertBeforeOp))):
                continue
            prevInserts = [op for op in rewrites[:i] if isinstance(op, TokenStreamRewriter.InsertBeforeOp)]
            for prev_index, prevIop in enumerate(prevInserts):
                if prevIop.index == iop.index and type(prevIop) is TokenStreamRewriter.InsertBeforeOp:
                    iop.text += prevIop.text
                    rewrites[prev_index] = None
                elif prevIop.index == iop.index and type(prevIop) is TokenStreamRewriter.InsertAfterOp:
                    iop.text = prevIop.text + iop.text
                    rewrites[prev_index] = None
            # look for replaces where iop.index is in range; error
            prevReplaces = [op for op in rewrites[:i] if isinstance(op, TokenStreamRewriter.ReplaceOp)]
            for rop in prevReplaces:
                if iop.index == rop.index:
                    rop.text = iop.text + rop.text
                    rewrites[i] = None
                    continue
                if all((iop.index >= rop.index, iop.index <= rop.last_index)):
                    raise ValueError("insert op {} within boundaries of previous {}".format(iop, rop))

        reduced = {}
        for i, op in enumerate(rewrites):
            if op is None: continue
            if reduced.get(op.index): raise ValueError('should be only one op per index')
            reduced[op.index] = op

        return reduced

    class RewriteOperation(object):
        __slots__ = ('tokens', 'index', 'text', 'instructionIndex')

        def __init__(self, tokens, index, text=""):
            """
            :type tokens: CommonTokenStream
            :param tokens:
            :param index:
            :param text:

View on GitHub (pinned to 7d5770395b)

Solutions

  1. Move the insert outside the replaced range, or fold the inserted text into the replacement text itself.
  2. If the insert belongs at the start of the replaced span, use index == rop.index (that case is merged as a prepend, not an error).
  3. Put independent insert/replace passes in different program names and render them in separate getText() calls.

Example fix

// before
rewriter.replace(2, 5, 'new')
rewriter.insertBefore(3, 'x')  // 3 inside 2..5 -> ValueError

// after
rewriter.replace(2, 5, 'xnew')  # text folded into the replacement
Defensive patterns

Strategy: validation

Validate before calling

def insert_safe(replaces, index):
    # replaces: list of (start, end) already submitted to this program
    return all(not (start < index <= end) for start, end in replaces)

Try / catch

try:
    rewriter.insertBefore(i, text)
except ValueError as e:
    if 'within boundaries' in str(e):
        # fold text into the covering replacement instead
        pass
    else:
        raise

Prevention

When it happens

Trigger: rewriter.insertAfter(3, 'x') or insertBefore(3, 'x') after rewriter.replace(2, 5, 'y') in the same program: 3 is within 2..5 and != 2, so the insert is rejected.

Common situations: Automated refactoring tools that emit inserts and replaces from independent passes; listeners that insert tokens near operators while another visitor replaces whole expressions.

Related errors


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