Skip to content

fix[parser]: fix source-map end positions - #5209

Open
Sporarum wants to merge 8 commits into
vyperlang:masterfrom
Sporarum:fix-source-map
Open

fix[parser]: fix source-map end positions#5209
Sporarum wants to merge 8 commits into
vyperlang:masterfrom
Sporarum:fix-source-map

Conversation

@Sporarum

@Sporarum Sporarum commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

What I did

  1. Fix Invalid source map: negative length emitted for inline a ** b (runtime exponent) used as a call argument #5027 (token end positions are not offset properly)
  2. Make every get_contract-based test sanity-check the produced source maps

How I did it

  1. Change how we keep track of pre-parser adjustments from a dict of exact position -> offset, to a set of positions where the offset changes.
    This fixes the issue since now end positions are updated correctly (previously not recorded in the dict)
  2. Add a validation method which is called every time get_contract is called

How to verify it

pytest (new tests in file tests/unit/compiler/test_source_map.py)

Also can check that test_source_map_no_negative_length_after_keyword_rewrite fails on master

Commit message

the pre-parser rewrites keywords like extcall and staticcall into
shorter Python stand-ins, and shifts subsequent columns to compensate.
end columns went unadjusted because shifts were only recorded at the
exact column of each rewrite start, producing negative-length spans in
the compressed source map.

this commit replaces the exact-position dict with a per-line sorted list
of (threshold, shift) points looked up via binary search, so shifts
apply to every column past a rewritten keyword.

this commit also makes the get_contract-based tests sanity-check
produced source maps.

Description for the changelog

Fix exported source map sometimes being invalid

Cute Animal Picture

Put a link to a cute animal picture inside the parenthesis-->

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

Gas Changes

No changes detected.

Summary

  • Total tests measured: 560
  • Changed: 0
  • Regressions (gas up): 0
  • Improvements (gas down): 0
  • New tests: 0
  • Deleted tests: 0
  • Newly failing: 0
  • Newly passing: 0

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

📊 Bytecode Size Changes (venom)

No changes detected.

Full bytecode sizes

Contract legacy-O2 legacy-Os -O2 -O3 -Os
curvefi/amm/stableswap/meta_implementation/meta_implementation_v_700.vy 23610 22805 20535 19298 19074
curvefi/legacy/CurveStableSwapMetaNG.vy 24952 23578 20329 19575 19022
curvefi/amm/stableswap/implementation/implementation_v_700.vy 24962 23769 19946 18929 18536
curvefi/legacy/CurveStableSwapNG.vy 24473 23298 19496 18525 18142
curvefi/amm/tricryptoswap/implementation/implementation_v_200.vy 20724 19959 17817 17171 16771
yearnfi/VaultV3.vy 19972 19063 16860 14866 14338
curvefi/amm/twocryptoswap/implementation/implementation_v_210.vy 17634 16894 15471 14774 14450
curvefi/legacy/CurveCryptoSwap2.vy 18947 18382 15365 14589 14460
yearnfi/VaultV2.vy 16676 15763 13497 12645 12228
curvefi/amm/stableswap/factory/factory_v_100.vy 14558 13978 12878 11174 11366
curvefi/gauge/child_gauge/implementation/implementation_v_110.vy 12338 11561 10128 9441 9052
curvefi/gauge/child_gauge/implementation/implementation_v_100.vy 12017 11249 9859 9179 8793
curvefi/amm/stableswap/views/views_v_120.vy 12784 12368 9798 9145 9356
curvefi/amm/tricryptoswap/math/math_v_200.vy 11189 11126 9465 8075 8366
curvefi/legacy/CurveCryptoMathOptimized3.vy 11188 11125 9464 8075 8366
curvefi/gauge/child_gauge/implementation/implementation_v_020.vy 10665 9947 8902 8316 7930
curvefi/helpers/router/router_v_110.vy 6717 6717 6824 6080 6428
curvefi/registries/metaregistry/metaregistry_v_110.vy 7590 6732 6576 5765 5658
curvefi/amm/tricryptoswap/views/views_v_200.vy 7821 7776 6512 6172 6326
curvefi/helpers/stable_swap_meta_zap/stable_swap_meta_zap_v_100.vy 7302 7067 6205 5718 5938
curvefi/amm/twocryptoswap/views/views_v_200.vy 6991 6946 6104 5778 5918
curvefi/registries/metaregistry/registry_handlers/stableswap/handler_v_110.vy 6633 6259 5764 4919 5469
curvefi/amm/twocryptoswap/factory/factory_v_200.vy 5540 5252 5678 4287 4542
curvefi/amm/twocryptoswap/math/math_v_210.vy 6800 6800 5584 5054 5087
curvefi/amm/tricryptoswap/factory/factory_v_200.vy 5246 5021 5272 4161 4389
curvefi/gauge/child_gauge/factory/factory_v_201.vy 4844 4547 4208 3892 3728
yearnfi/VaultFactory.vy 3765 3617 3924 2414 2909
curvefi/registries/metaregistry/registry_handlers/tricryptoswap/handler_v_110.vy 4241 3939 3795 3444 3506
curvefi/registries/metaregistry/registry_handlers/twocryptoswap/handler_v_110.vy 4186 3884 3735 3315 3393
curvefi/gauge/child_gauge/factory/factory_v_100.vy 4183 3914 3643 3319 3146
curvefi/helpers/rate_provider/rate_provider_v_101.vy 3260 3260 2733 2371 2404
curvefi/registries/address_provider/address_provider_v_201.vy 2973 2782 2631 2427 2339
curvefi/amm/stableswap/math/math_v_100.vy 3067 3046 2495 2260 2316
curvefi/helpers/rate_provider/rate_provider_v_100.vy 2847 2841 2326 2007 2027
curvefi/helpers/deposit_and_stake_zap/deposit_and_stake_zap_v_100.vy 2322 2316 2042 1781 1840
curvefi/governance/relayer/taiko/relayer_v_001.vy 2068 2064 1766 1545 1593
curvefi/governance/relayer/polygon_cdk/relayer_v_101.vy 1556 1523 1554 1348 1371
curvefi/governance/relayer/arb_orbit/relayer_v_101.vy 1266 1262 1253 1077 1126
curvefi/governance/relayer/op_stack/relayer_v_101.vy 1186 1182 1194 1025 1067
curvefi/governance/relayer/not_rollup/relayer_v_100.vy 1168 1153 1185 1022 1048
curvefi/governance/vault/vault_v_100.vy 964 941 873 834 850
curvefi/governance/relayer/relayer_v_100.vy 496 496 593 490 503
curvefi/governance/agent/agent_v_100.vy 541 541 425 397 401
curvefi/governance/agent/agent_v_101.vy 541 541 425 397 401

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: de2e6d1f59

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread vyper/ast/parse.py Outdated
adj = adjustments.get((node.end_lineno, node.end_col_offset), 0)
node.end_col_offset += adj
node.col_offset += self._pre_parser.shift_for(node.lineno, node.col_offset)
node.end_col_offset += self._pre_parser.shift_for(node.end_lineno, node.end_col_offset)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not shift already-original module EOF offsets

When the final source line contains a rewritten keyword such as staticcall or extcall, the module node's end_col_offset is already set from the original source in visit_Module (len(self.source_lines[-1])) before generic_visit runs. Applying shift_for here treats that original EOF column as a reformatted Python column, so a file ending with return staticcall ... adds the rewrite delta again and exports an out-of-range module src span, which can be used as the pc 0 fallback in build_source_map_output. Skip this adjustment for coordinates that were synthesized from the original source, or set the module end after the generic shift path.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: c1c24b2

Comment thread vyper/ast/parse.py Outdated
Comment on lines +241 to +242
node.col_offset += self._pre_parser.shift_for(node.lineno, node.col_offset)
node.end_col_offset += self._pre_parser.shift_for(node.end_lineno, node.end_col_offset)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid re-shifting inherited AST coordinates

For AST nodes that do not carry their own coordinates, such as operator and context singleton nodes, generic_visit copies the location from self._parents[-1]; at that point the parent has already passed through these shift lines. With a keyword rewrite earlier on the same line, e.g. return staticcall Vault(v).convertToAssets(10 ** d), the Pow/Load children inherit original-source coordinates and then shift_for adds the same delta again, so annotated_ast_dict and any diagnostics or source-map entries that use those children point several columns too far right. Track whether a coordinate came from the raw Python parser before applying the shift, or avoid reapplying it to inherited fields.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: 6fcaaf8

Comment thread vyper/ast/pre_parser.py
return 0
# bisect_right on col: the last shift point with col_key <= col is at
# index i - 1 (0 means no shift point applies yet).
i = bisect.bisect_right(pts, (col, float("inf")))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we bisecting here?

@Sporarum Sporarum Aug 25, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're finding the element of pts which has the biggest col_key that is smaller than col
Equivalent to something like:

res: int
for (col_key, shift) in pts:
  if (col_key <= col):
    res = shift
  else:
    break

return res

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other words, we're looking to find which shift applies at this location

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I didn't read carefully enough but why wouldn't the line just be in the dict?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The most complete example is this:

x = extcall foo(extcall bar())

Which will be pre-parsed to this:

x = await foo(await bar())

And so:

  • x = should not be shifted
  • the first await should have its start not shifted, but its end shifted by 2
  • foo( should be shifted by 2
  • the second await should have it's start shifted by 2, and it's end shifted by 4
  • bar()) should be shifted by 4

So a single line has actually 3 different shifts

@Sporarum Sporarum Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(oh and in this case we could replace extcall by await and avoid having to shift entirely, but there are cases where the shift goes the other way around, so that wouldn't work)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invalid source map: negative length emitted for inline a ** b (runtime exponent) used as a call argument

2 participants