antlr/antlr4 · error · Error

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

Error message

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

What it means

Thrown by IntervalSet.setReadonly(false) when the set is currently read-only. Read-only status is deliberately one-way in the ANTLR runtime: it protects sets shared between threads and ATN caches, and allowing an un-flag would open a race where one consumer mutates while others read. Only transitioning false→true is legal.

Source

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

                    else if (prevIop instanceof InsertBeforeOp) { // combine objects
                        // convert to strings...we're in process of toString'ing
                        // whole token buffer so no lazy eval issue with any templates
                        iop.text = this.catOpText(iop.text, prevIop.text);
                        // delete redundant prior insert
                        rewrites[prevIop.instructionIndex] = undefined;
                    }
                }
            }
            // look for replaces where iop.index is in range; error
            let prevReplaces = this.getKindOfOps(rewrites, ReplaceOp, i);
            for (let rop of prevReplaces) {
                if (iop.index === rop.index) {
                    rop.text = this.catOpText(iop.text, rop.text);
                    rewrites[i] = undefined;	// delete current insert
                    continue;
                }
                if (iop.index >= rop.index && iop.index <= rop.lastIndex) {
                    throw new Error(`insert op ${iop} within boundaries of previous ${rop}`);
                }
            }
        }

        /** @type {Map<number, RewriteOperation>} */
        let m = new Map();
        for (let op of rewrites) {
            if (op == null) {
                // ignore deleted ops
                continue;
            }
            if (m.get(op.index) != null) {
                throw new Error("should only be one op per index");
            }
            m.set(op.index, op);
        }
        return m;
    }

View on GitHub (pinned to 7d5770395b)

Solutions

  1. Never un-flag: clone the set (new IntervalSet(set)) and mutate the clone.
  2. Design construction so setReadonly(true) is the last step, after all mutation is done.
  3. If you need freeze/thaw semantics, implement them in your own wrapper that swaps between mutable copies.

Example fix

// before
set.setReadonly(true);
// ... later
set.setReadonly(false); // IllegalStateException

// after
set.setReadonly(true);
// ... later, work on a copy
IntervalSet mutable = new IntervalSet(set);
Defensive patterns

Strategy: validation

Validate before calling

// setReadonly is one-way; to 'unlock', copy
IntervalSet fresh = new IntervalSet(readonlySet); // fresh is mutable
fresh.add(newInterval);

Prevention

When it happens

Trigger: Calling setReadonly(false) on a set that already had setReadonly(true) applied, e.g. an attempt to 'unlock' a cached or ATN-owned set for modification.

Common situations: Code that wants to temporarily freeze and then thaw a set; sets acquired from the runtime's shared state that the application tries to make mutable again.

Related errors


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