antlr/antlr4 · error · Error

replace op boundaries of ${rop} overlap with previous ${prev

Error message

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

What it means

Thrown by IntervalSet.remove(int) when the set has been marked read-only with setReadonly(true). Read-only sets are shared across ATN instances and parsers; removing an element would silently change lexer/parser behavior everywhere the set is shared, so removal is rejected.

Source

Thrown at runtime/JavaScript/src/antlr4/TokenStreamRewriter.js:259

            let prevReplaces = this.getKindOfOps(rewrites, ReplaceOp, i);
            for (let prevRop of prevReplaces) {
                if (prevRop.index >= rop.index && prevRop.lastIndex <= rop.lastIndex) {
                    // delete replace as it's a no-op.
                    rewrites[prevRop.instructionIndex] = undefined;
                    continue;
                }
                // throw exception unless disjoint or identical
                let 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) {
                    rewrites[prevRop.instructionIndex] = undefined; // kill first delete
                    rop.index = Math.min(prevRop.index, rop.index);
                    rop.lastIndex = Math.max(prevRop.lastIndex, rop.lastIndex);
                }
                else if (!disjoint) {
                    throw new Error(`replace op boundaries of ${rop} overlap with previous ${prevRop}`);
                }
            }
        }

        // WALK INSERTS
        for (let i = 0; i < rewrites.length; i++) {
            let op = rewrites[i];
            if (op == null) {
                continue;
            }
            if (!(op instanceof InsertBeforeOp)) {
                continue;
            }
            let iop = op;
            // combine current insert with prior if any at same index
            let prevInserts = this.getKindOfOps(rewrites, InsertBeforeOp, i);
            for (let prevIop of prevInserts) {
                if (prevIop.index === iop.index) {

View on GitHub (pinned to 7d5770395b)

Solutions

  1. Copy then remove: IntervalSet copy = new IntervalSet(shared); copy.remove(el);
  2. Check isReadonly() before removal and route to the copy path.
  3. Build a fresh set containing exactly the elements you want rather than subtracting from a shared one.

Example fix

// before
sharedSet.remove('
'); // IllegalStateException

// after
IntervalSet filtered = new IntervalSet(sharedSet);
filtered.remove('
');
Defensive patterns

Strategy: validation

Validate before calling

IntervalSet target = set.isReadonly() ? new IntervalSet(set) : set;
target.remove(el);

Type guard

boolean isMutable(IntervalSet s) { return !s.isReadonly(); }

Prevention

When it happens

Trigger: Calling remove(el) on a set obtained from ATN transitions, statically cached sets, or any set after setReadonly(true) was invoked.

Common situations: Tools that filter character classes or token types out of ATN-derived interval sets; refactoring code that used to own its set but now receives a shared one from the runtime.

Related errors


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