{"record":{"id":"44acb7d6ee7c0d53","repo":"antlr/antlr4","slug":"replace-op-boundaries-of-overlap-with-previous","errorCode":null,"errorMessage":"replace op boundaries of {} overlap with previous {}","messagePattern":"replace op boundaries of (.+?) overlap with previous (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"runtime/Python3/src/antlr4/TokenStreamRewriter.py","lineNumber":167,"sourceCode":"                    rewrites[iop.instructionIndex] = None\n                    rop.text = '{}{}'.format(iop.text, rop.text)\n                elif all((iop.index > rop.index, iop.index <= rop.last_index)):\n                    rewrites[iop.instructionIndex] = None\n\n            # Drop any prior replaces contained within\n            prevReplaces = [op for op in rewrites[:i] if isinstance(op, TokenStreamRewriter.ReplaceOp)]\n            for prevRop in prevReplaces:\n                if all((prevRop.index >= rop.index, prevRop.last_index <= rop.last_index)):\n                    rewrites[prevRop.instructionIndex] = None\n                    continue\n                isDisjoint = any((prevRop.last_index<rop.index, prevRop.index>rop.last_index))\n                if all((prevRop.text is None, rop.text is None, not isDisjoint)):\n                    rewrites[prevRop.instructionIndex] = None\n                    rop.index = min(prevRop.index, rop.index)\n                    rop.last_index = min(prevRop.last_index, rop.last_index)\n                    print('New rop {}'.format(rop))\n                elif (not(isDisjoint)):\n                    raise ValueError(\"replace op boundaries of {} overlap with previous {}\".format(rop, prevRop))\n\n        # Walk inserts\n        for i, iop in enumerate(rewrites):\n            if any((iop is None, not isinstance(iop, TokenStreamRewriter.InsertBeforeOp))):\n                continue\n            prevInserts = [op for op in rewrites[:i] if isinstance(op, TokenStreamRewriter.InsertBeforeOp)]\n            for prev_index, prevIop in enumerate(prevInserts):\n                if prevIop.index == iop.index and type(prevIop) is TokenStreamRewriter.InsertBeforeOp:\n                    iop.text += prevIop.text\n                    rewrites[prev_index] = None\n                elif prevIop.index == iop.index and type(prevIop) is TokenStreamRewriter.InsertAfterOp:\n                    iop.text = prevIop.text + iop.text\n                    rewrites[prev_index] = None\n            # look for replaces where iop.index is in range; error\n            prevReplaces = [op for op in rewrites[:i] if isinstance(op, TokenStreamRewriter.ReplaceOp)]\n            for rop in prevReplaces:\n                if iop.index == rop.index:\n                    rop.text = iop.text + rop.text","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/antlr/antlr4/blob/7d5770395bb7b02eb56e7c62662cb1d7c08f42a3/runtime/Python3/src/antlr4/TokenStreamRewriter.py#L149-L185","documentation":"During reduceProgramToSingleCommandPerIndex(), the rewriter folds overlapping rewrite operations into one command per token index. Two ReplaceOps whose ranges intersect (and are not strictly nested, and not both pure deletions) cannot be merged unambiguously, so ANTLR raises this ValueError instead of guessing the resulting text.","triggerScenarios":"Calling rewriter.replace() (or replaceRange/replaceRangeTokens) twice on the same program with ranges that partially overlap, e.g. replace(2,5,'a') then replace(4,7,'b'); nested ranges (prev fully inside new) and disjoint ranges are fine, partial overlap with non-None text is not.","commonSituations":"Applying many edits generated from different AST visits without sorting/merging; mixing inserts at the edge of a replaced range; using the default program name for edits intended for separate output passes.","solutions":["Use a separate program per independent edit batch: rewriter.replace('pass1', ...) / rewriter.replace('pass2', ...), or call rewriter.getText() between batches.","Merge overlapping replaces yourself before submitting: coalesce ranges and concatenate/decide text deterministically.","Sort edits by start index and rewrite them so each range is disjoint (extend ranges over gaps to eliminate partial overlaps)."],"exampleFix":"// before\nrewriter.replace(2, 5, 'a')\nrewriter.replace(4, 7, 'b')  // partial overlap -> ValueError on getText()\n\n// after\nrewriter.replace(2, 7, 'ab')  # single merged, deterministic edit","handlingStrategy":"validation","validationCode":"def assert_disjoint_replaces(edits):\n    # edits: list of (start, end, text) sorted by start\n    edits = sorted(edits, key=lambda e: e[0])\n    for (s1, e1, _), (s2, e2, _) in zip(edits, edits[1:]):\n        if s2 <= e1:\n            raise ValueError('edits %d..%d and %d..%d overlap; merge them first' % (s1, e1, s2, e2))\n    return edits\n\nfor start, stop, text in assert_disjoint_replaces(my_edits):\n    rewriter.replace(start, stop, text)","typeGuard":null,"tryCatchPattern":"try:\n    out = rewriter.getText()\nexcept ValueError as e:\n    if 'overlap' in str(e):\n        # fall back to per-pass programs so edits never co-reside\n        rewriter = TokenStreamRewriter(stream)\n    else:\n        raise","preventionTips":["Use a distinct program name per independent edit pass","Sort and merge overlapping replaces before submitting them","Call getText() between passes to roll the current program"],"tags":["antlr4","python","token-stream-rewriter","overlapping-edits","validation"],"backgroundTag":null,"analyzedSha":"7d5770395bb7b02eb56e7c62662cb1d7c08f42a3","analyzedAt":"2026-08-14T14:47:56.354Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}