Repository navigation
fix(csharp): recover C# 14 extension blocks — bind the receiver, keep members in the class (#3510) - #3523
fix(csharp): recover C# 14 extension blocks — bind the receiver, keep members in the class (#3510)#3523Ayushraj06-bit wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Adds C# 14 extension(...) block support to the generic extractor across three parse shapes the pinned tree-sitter-c-sharp 0.23.5 produces: a real extension_declaration, a clean constructor_declaration named extension, and a generic-form ERROR node holding a variable_declaration. Extension members are flattened into the enclosing static class (a local_function_statement under a class parent is retreated as a method_declaration, and _csharp_extra_walk walks the block body with the class as parent), and the receiver parameter is bound like a primary-constructor parameter so members can resolve against its type. The receiver binding runs after the member walk and refuses to guess: a same-named field/property of a different type, or two blocks binding one name to different receiver types, drops the name rather than emitting an edge.
Worth a look
- Alias-qualified generic extension receivers are dropped —
graphify/extractors/engine.py:2820· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 706 functions depend on the 262 functions this change touches.
Health — this change adds coupling hotspots:
- new:
_extract_generic()— 18 callers, 26 callees - new:
extract_js()— 85 callers, 4 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
extract_objc()— 27 callers, 9 callees - new:
extract_julia()— 17 callers, 7 callees - new:
extract_cpp()— 29 callers, 3 callees - new:
extract_vue()— 10 callers, 7 callees - new:
walk()— 1 callers, 61 callees - …and 9 more — each is listed as a finding
Verification — 706 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 646 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
23 of 276 test file(s) selected (8%) via static blast radius.
tests/test_astro_extraction.py— impacttests/test_build.py— impacttests/test_cjs_module_extension.py— impacttests/test_cpp_nested_and_cli.py— impacttests/test_csharp_extension_blocks.py— impact, changed-testtests/test_dotnet.py— impacttests/test_extract.py— impacttests/test_import_extension_resolution.py— impacttests/test_indirect_dispatch.py— impacttests/test_indirect_dispatch_assign_return.py— impacttests/test_indirect_dispatch_getattr.py— impacttests/test_js_exported_scalar_bindings.py— impacttests/test_languages.py— impacttests/test_multilang.py— impacttests/test_python_underscore_resolution.py— impacttests/test_rationale.py— impacttests/test_ruby_resolution.py— impacttests/test_scala_self_type.py— impacttests/test_swift_computed_properties.py— impacttests/test_trailing_newline_not_a_syntax_error.py— impacttests/test_ts_new_expression_calls.py— impacttests/test_typescript_module_extensions.py— impacttests/test_vue_extraction.py— impact
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
Could not verify: Could not verify \_csharp\_extra\_walk.
The verifier did not have enough to check \_csharp\_extra\_walk, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)
Could not verify: Could not verify \_extract\_generic.
The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
· 1 grounded finding(s) anchored inline below; 16 more finding(s) on lines outside this diff (see the check run).
…as their dotted form (Graphify-Labs#3510) Review finding on Graphify-Labs#3523: an alias-qualified extension receiver was dropped on the ERROR-shape text path (`::` was not a qualifier separator). The shared node helpers had the same gap, pre-existing and wider: with no `alias_qualified_name` branch, `global::IFoo` in any C# type position fabricated a `global` type node and bound the receiver to it, and the dotted `global::Acme.IFoo` carried `global::Acme` as its qualifier, which no namespace matches, so the reference repointed to a dangling stub. Fold the qualifier once in `_csharp_unalias`: `global::` is the root namespace (already in every scope list) and a using/extern alias `X::B` names what `X.B` does. Applied in `_read_csharp_type_name`, `_csharp_collect_type_refs`, and the receiver text path, so extension receivers, primary-constructor parameters, fields, and base lists all read the same way. Identity on text without `::`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…as their dotted form (Graphify-Labs#3510) Review finding on Graphify-Labs#3523: an alias-qualified extension receiver was dropped on the ERROR-shape text path (`::` was not a qualifier separator). The shared node helpers had the same gap, pre-existing and wider: with no `alias_qualified_name` branch, `global::IFoo` in any C# type position fabricated a `global` type node and bound the receiver to it, and the dotted `global::Acme.IFoo` carried `global::Acme` as its qualifier, which no namespace matches, so the reference repointed to a dangling stub. Fold the qualifier once in `_csharp_unalias`: `global::` is the root namespace (already in every scope list) and a using/extern alias `X::B` names what `X.B` does. Applied in `_read_csharp_type_name`, `_csharp_collect_type_refs`, and the receiver text path, so extension receivers, primary-constructor parameters, fields, and base lists all read the same way. Identity on text without `::`.
0be509d to
dbe0936
Compare
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 3 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Adds recovery for C# 14 extension(...) { } blocks so members inside are attached to the enclosing static class and the receiver parameter is bound and referenced like a primary-constructor parameter. Handles three parse shapes — a real extension_declaration on newer grammars, and under the bundled 0.23.5 either a constructor_declaration named extension or an ERROR node holding a variable_declaration for the generic form — resolving receiver names only for Pascal-case types and dropping the binding when two blocks disagree or a same-named field conflicts rather than guessing. Also folds C# alias qualifiers (global::, X::) into dotted form via _csharp_unalias and fixes qualified-name refs to mark themselves qualified only when a prefix actually exists.
Worth a look
- Alias-qualified generic names split on dots inside generic arguments —
graphify/extractors/engine.py:222· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- qualified_name refs now emit qualified=False for global:: names, changing resolver contract —
graphify/extractors/engine.py:223· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Alias-qualified receiver/type names split on dots inside generic arguments —
graphify/extractors/engine.py:3104· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 714 functions depend on the 270 functions this change touches.
Health — this change adds coupling hotspots:
- new:
_extract_generic()— 18 callers, 26 callees - new:
extract_js()— 85 callers, 4 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
extract_objc()— 27 callers, 9 callees - new:
extract_julia()— 17 callers, 7 callees - new:
extract_cpp()— 29 callers, 3 callees - new:
extract_vue()— 10 callers, 7 callees - new:
walk()— 1 callers, 61 callees - …and 11 more — each is listed as a finding
Verification — 714 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 654 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
23 of 276 test file(s) selected (8%) via static blast radius.
tests/test_astro_extraction.py— impacttests/test_build.py— impacttests/test_cjs_module_extension.py— impacttests/test_cpp_nested_and_cli.py— impacttests/test_csharp_extension_blocks.py— impact, changed-testtests/test_dotnet.py— impacttests/test_extract.py— impacttests/test_import_extension_resolution.py— impacttests/test_indirect_dispatch.py— impacttests/test_indirect_dispatch_assign_return.py— impacttests/test_indirect_dispatch_getattr.py— impacttests/test_js_exported_scalar_bindings.py— impacttests/test_languages.py— impacttests/test_multilang.py— impacttests/test_python_underscore_resolution.py— impacttests/test_rationale.py— impacttests/test_ruby_resolution.py— impacttests/test_scala_self_type.py— impacttests/test_swift_computed_properties.py— impacttests/test_trailing_newline_not_a_syntax_error.py— impacttests/test_ts_new_expression_calls.py— impacttests/test_typescript_module_extensions.py— impacttests/test_vue_extraction.py— impact
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
Could not verify: Could not verify \_csharp\_collect\_type\_refs.
The verifier did not have enough to check \_csharp\_collect\_type\_refs, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `skip` is annotated `frozenset[str] | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_csharp\_extra\_walk.
The verifier did not have enough to check \_csharp\_extra\_walk, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)
Could not verify: Could not verify \_extract\_generic.
The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_read\_csharp\_type\_name.
The verifier did not have enough to check \_read\_csharp\_type\_name, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)
· 3 grounded finding(s) anchored inline below; 16 more finding(s) on lines outside this diff (see the check run).
…ide a type argument (Graphify-Labs#3510) Review finding on Graphify-Labs#3523, pre-existing in the shared helpers: the qualified branch of `_read_csharp_type_name` / `_csharp_collect_type_refs` ran `rpartition(".")` over the whole node text, so `Acme.IFoo<System.String>` (a field, a base list, a receiver) became a phantom `String>` type qualified by `Acme.IFoo<System`, and `Outer<int>.Inner` carried `Outer<int>` as its qualifier. Every `<...>` list is now dropped, nesting included, before the split — folded into the same normaliser as the `::` alias handling (`_csharp_dotted_name`), which the ERROR-shape receiver text path uses too, so the three sites read a name identically.
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 2 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Adds C# 14 extension(...) { } block support to the extractor: _csharp_extension_blocks recognizes the three shapes the pinned tree-sitter-c-sharp 0.23.5 produces (unnamed constructor, generic ERROR-recovery, and the real extension_declaration from newer grammars), treats the block as a container so its members bind to the enclosing static class, and binds the receiver parameter like a primary-constructor parameter — resolved after the body walk so a conflicting same-named field drops the name rather than emitting a wrong edge. Introduces _csharp_dotted_name to strip nested <...> type arguments and fold global::/alias qualifiers into dotted paths, and routes qualified_name and alias_qualified_name through it in both _csharp_collect_type_refs and _read_csharp_type_name so alias-qualified names resolve as their dotted equivalents instead of being read as standalone types. Adds tests covering nested generics keeping their own name, properties inside plain blocks, and receivers shadowed by a field of another type being dropped.
Worth a look
- qualified_name refs now emit qualified=False for unqualified alias-stripped names, changing prior always-True contract —
graphify/extractors/engine.py:239· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Alias-qualified generic arguments are discarded —
graphify/extractors/engine.py:241· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 720 functions depend on the 276 functions this change touches.
Health — this change adds coupling hotspots:
- new:
_extract_generic()— 18 callers, 26 callees - new:
extract_js()— 85 callers, 4 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
extract_objc()— 27 callers, 9 callees - new:
extract_julia()— 17 callers, 7 callees - new:
extract_cpp()— 29 callers, 3 callees - new:
extract_vue()— 10 callers, 7 callees - new:
walk()— 1 callers, 61 callees - …and 11 more — each is listed as a finding
Verification — 720 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 660 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
23 of 276 test file(s) selected (8%) via static blast radius.
tests/test_astro_extraction.py— impacttests/test_build.py— impacttests/test_cjs_module_extension.py— impacttests/test_cpp_nested_and_cli.py— impacttests/test_csharp_extension_blocks.py— impact, changed-testtests/test_dotnet.py— impacttests/test_extract.py— impacttests/test_import_extension_resolution.py— impacttests/test_indirect_dispatch.py— impacttests/test_indirect_dispatch_assign_return.py— impacttests/test_indirect_dispatch_getattr.py— impacttests/test_js_exported_scalar_bindings.py— impacttests/test_languages.py— impacttests/test_multilang.py— impacttests/test_python_underscore_resolution.py— impacttests/test_rationale.py— impacttests/test_ruby_resolution.py— impacttests/test_scala_self_type.py— impacttests/test_swift_computed_properties.py— impacttests/test_trailing_newline_not_a_syntax_error.py— impacttests/test_ts_new_expression_calls.py— impacttests/test_typescript_module_extensions.py— impacttests/test_vue_extraction.py— impact
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
Could not verify: Could not verify \_csharp\_collect\_type\_refs.
The verifier did not have enough to check \_csharp\_collect\_type\_refs, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `skip` is annotated `frozenset[str] | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_csharp\_extra\_walk.
The verifier did not have enough to check \_csharp\_extra\_walk, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)
Could not verify: Could not verify \_extract\_generic.
The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_read\_csharp\_type\_name.
The verifier did not have enough to check \_read\_csharp\_type\_name, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)
· 3 grounded finding(s) anchored inline below; 16 more finding(s) on lines outside this diff (see the check run).
|
Both review passes checked by execution; here is what each finding turned out to be and what changed. Round 1 — "Alias-qualified generic extension receivers are dropped" (engine.py:2820) — real; fixed in dbe0936. Round 2 — #1 and #3, "split on dots inside generic arguments" (engine.py:222, :3104) — real, and pre-existing in Fix for all of the above — one normaliser, Round 2 — #2, " Tests — 10 added across the two commits (both block shapes × Coupling notes — answered inline: CI on this exact head, via the same |
…Graphify-Labs#3510) Review finding on Graphify-Labs#3523: `global::IFoo<Bar>` used to reach `Bar` only by accident — the unhandled alias_qualified_name fell into child recursion, which emitted the phantom `global` and `Bar` alongside `IFoo` — so routing it through the qualified branch dropped `Bar`. That branch had never collected type arguments at all: a bare `IFoo<Bar>` references Bar, a qualified `Acme.IFoo<Bar>` did not. Collect every type_argument_list at any depth of a qualified or alias-qualified name, the way the generic_name branch reads a bare one, so both spellings yield the same generic_arg references.
There was a problem hiding this comment.
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Adds C# 14 extension(Receiver r) { … } block support to the extractor: _csharp_extension_blocks recognizes all three parse shapes (a real extension_declaration from newer grammars, a constructor_declaration named extension inside a static class under the bundled 0.23.5, and an ERROR-node recovery for the generic extension<T>(...) form) and yields the receiver type, its type refs, and receiver name, while _csharp_extra_walk treats such blocks as containers whose members belong to the enclosing static class rather than as their own scope. Introduces _csharp_dotted_name to strip type-argument lists at any nesting depth and fold global::/alias qualifiers into dotted form, and routes qualified-name and alias-qualified-name handling in both _csharp_collect_type_refs and _read_csharp_type_name through it so splits land on the name's own dots and nested type arguments (e.g. Widget in Acme.Box<Widget>) get referenced as generic args. Also adds extension_declaration to the type-parameter scope set.
No blocking issues surfaced. 7 lower-confidence candidates did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 725 functions depend on the 281 functions this change touches.
Health — this change adds coupling hotspots:
- new:
_extract_generic()— 18 callers, 26 callees - new:
extract_js()— 85 callers, 4 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
extract_objc()— 27 callers, 9 callees - new:
extract_julia()— 17 callers, 7 callees - new:
extract_cpp()— 29 callers, 3 callees - new:
extract_vue()— 10 callers, 7 callees - new:
walk()— 1 callers, 61 callees - …and 11 more — each is listed as a finding
Verification — 725 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 665 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
23 of 276 test file(s) selected (8%) via static blast radius.
tests/test_astro_extraction.py— impacttests/test_build.py— impacttests/test_cjs_module_extension.py— impacttests/test_cpp_nested_and_cli.py— impacttests/test_csharp_extension_blocks.py— impact, changed-testtests/test_dotnet.py— impacttests/test_extract.py— impacttests/test_import_extension_resolution.py— impacttests/test_indirect_dispatch.py— impacttests/test_indirect_dispatch_assign_return.py— impacttests/test_indirect_dispatch_getattr.py— impacttests/test_js_exported_scalar_bindings.py— impacttests/test_languages.py— impacttests/test_multilang.py— impacttests/test_python_underscore_resolution.py— impacttests/test_rationale.py— impacttests/test_ruby_resolution.py— impacttests/test_scala_self_type.py— impacttests/test_swift_computed_properties.py— impacttests/test_trailing_newline_not_a_syntax_error.py— impacttests/test_ts_new_expression_calls.py— impacttests/test_typescript_module_extensions.py— impacttests/test_vue_extraction.py— impact
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
Could not verify: Could not verify \_csharp\_collect\_type\_refs.
The verifier did not have enough to check \_csharp\_collect\_type\_refs, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `skip` is annotated `frozenset[str] | None` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_csharp\_extra\_walk.
The verifier did not have enough to check \_csharp\_extra\_walk, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)
Could not verify: Could not verify \_extract\_generic.
The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_read\_csharp\_type\_name.
The verifier did not have enough to check \_read\_csharp\_type\_name, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: not verifiable: all 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)
· 3 grounded finding(s) anchored inline below; 16 more finding(s) on lines outside this diff (see the check run).
|
Round 3 (on 3080deb), checked by execution: "Alias-qualified generic arguments are discarded" (engine.py:241) — real in the narrow case, fixed in 6a693c3. On " Inline coupling threads answered and resolved (unchanged from the previous heads). Fork CI on this head, all six jobs green: https://github.com/Ayushraj06-bit/graphify/actions/runs/34727548500 |
… members in the class (Graphify-Labs#3510) tree-sitter-c-sharp 0.23.5 predates `extension(Receiver r) { … }`. A generic header lands in an ERROR node (members kept, receiver lost); a non-generic one parses clean as a constructor named `extension` (members dropped, no warning); upstream's coming `extension_declaration` was an unknown wrapper (members demoted to file level). Recognise all three: the receiver binds and is referenced like a primary-constructor parameter, and the body is a transparent container like the preproc_ wrappers. A same-named field or a second block of another type drops the name rather than guessing.
…as their dotted form (Graphify-Labs#3510) Review finding on Graphify-Labs#3523: an alias-qualified extension receiver was dropped on the ERROR-shape text path (`::` was not a qualifier separator). The shared node helpers had the same gap, pre-existing and wider: with no `alias_qualified_name` branch, `global::IFoo` in any C# type position fabricated a `global` type node and bound the receiver to it, and the dotted `global::Acme.IFoo` carried `global::Acme` as its qualifier, which no namespace matches, so the reference repointed to a dangling stub. Fold the qualifier once in `_csharp_unalias`: `global::` is the root namespace (already in every scope list) and a using/extern alias `X::B` names what `X.B` does. Applied in `_read_csharp_type_name`, `_csharp_collect_type_refs`, and the receiver text path, so extension receivers, primary-constructor parameters, fields, and base lists all read the same way. Identity on text without `::`.
…ide a type argument (Graphify-Labs#3510) Review finding on Graphify-Labs#3523, pre-existing in the shared helpers: the qualified branch of `_read_csharp_type_name` / `_csharp_collect_type_refs` ran `rpartition(".")` over the whole node text, so `Acme.IFoo<System.String>` (a field, a base list, a receiver) became a phantom `String>` type qualified by `Acme.IFoo<System`, and `Outer<int>.Inner` carried `Outer<int>` as its qualifier. Every `<...>` list is now dropped, nesting included, before the split — folded into the same normaliser as the `::` alias handling (`_csharp_dotted_name`), which the ERROR-shape receiver text path uses too, so the three sites read a name identically.
…Graphify-Labs#3510) Review finding on Graphify-Labs#3523: `global::IFoo<Bar>` used to reach `Bar` only by accident — the unhandled alias_qualified_name fell into child recursion, which emitted the phantom `global` and `Bar` alongside `IFoo` — so routing it through the qualified branch dropped `Bar`. That branch had never collected type arguments at all: a bare `IFoo<Bar>` references Bar, a qualified `Acme.IFoo<Bar>` did not. Collect every type_argument_list at any depth of a qualified or alias-qualified name, the way the generic_name branch reads a bare one, so both spellings yield the same generic_arg references.
6a693c3 to
b187e85
Compare
|
Rebased onto current
Verified after the rebase (Windows 11, Python 3.11, |
Problem. A C# 14
extension(Receiver r) { … }block loses its receiver: noreferencesedge to the extended type andr.Method()inside the block cannot resolve, so call edges into these helpers go missing (#3510). Probing the bundled grammar shows it is wider than reported: with a type-parameter list (extension<T>(IFoo<T> spec)) the header lands in anERRORnode and the members survive as class methods; without one (extension(IFoo spec)) the block parses clean as a constructor namedextension, so the members vanish outright and the partial-parse warning never fires.Cause. tree-sitter-c-sharp 0.23.5 predates the syntax. Upstream merged C# 14 support (tree-sitter/tree-sitter-c-sharp#429) but has cut no release since v0.23.5 (PyPI latest), so a version bump is not available yet — and when it ships, the
<0.25pin will pick it up, and itsextension_declarationnode is an unknown wrapper to the walker, which demotes extension members to file level.Approach. Recognise all three shapes in the walker and apply the existing rules to each: the receiver binds and is referenced like a C# 12 primary-constructor parameter (
csharp_field_types, so receiver-typed member calls resolve), and the block body is a transparent container like thepreproc_wrappers, so members stay with the static class. Binding happens after the class body is walked; a same-named field/property, or a second block binding the same name to a different type, drops the name (no edge, never a guess). Everything is gated ontree_sitter_c_sharp+static class+ the literal nameextension; no existing edge changes.Alternatives considered.
ERRORshape, as the issue suggests — leaves the non-generic form silently dropping members, and leaves the grammar-bump regression in place.<T>/wherein the source before parsing (the Vuesource_overridetrick) so 0.23.5 sees one clean shape — loses the block's type-parameter names, has to be gated per grammar, and rewrites input, which Kotlin: bundled grammar rejects one-line type bodies (class C { val v = 1 }) — 21/541 files partially extracted, exit 0, no warning #2551 deliberately avoided.How to verify.
18 tests (the
extension_declarationone skips until a newer grammar is installed). Unpatched: 12 fail (members missing / references and calls absent). Also run against a local build of upstream master (extension_declarationpresent): 18/18 pass; unpatched 13 fail. The issue's exact snippet now yieldsE → IFoo (references)andE.Url() → IFoo.Next() (calls), the same edges as the classicthis IFoo specspelling. Full suite, ruff, and pyright are unchanged fromv8head.Note for C# 14 users. Anything after an
extension<T>block in the same class still falls out of the class under 0.23.5 (grammar error recovery); the existing partial-parse warning names those files. Full support arrives with the next tree-sitter-c-sharp release, at which point the 0.23.5 branches in_csharp_extension_body/_csharp_extension_blockscan be deleted.Open questions. The receiver
referencesedge is emitted from the class (primary-constructor precedent); per-member edges (E.Url → IFoo) would mirror the classic spelling exactly — easy to switch if preferred. Theextension_declarationhandling is ~6 lines and tested against upstream master; happy to split it out if you'd rather land it with the version bump.Fixes #3510