Repository navigation
fix(csharp): extract delegate declarations so references don't mint sourceless stubs - #4556
rajatnagda45 wants to merge 1 commit into
Conversation
…ourceless stubs A C# `delegate` is a named type, but `delegate_declaration` was in neither `class_types` nor `function_types`, so the declaration was dropped from the graph. A field or parameter typed as the delegate (`public BinOp Op;`, `int Apply(BinOp f, ...)`) still resolved the name `BinOp` through `ensure_named_node`, which minted a *sourceless* stub and pointed the `references` edge at it — even though `BinOp` is declared in the same file. That violates the provenance invariant: a real source-backed declaration must not lose to a sourceless god node (and the bare-id stub becomes a cross-file hub shared by every same-named reference in the corpus). `delegate_declaration` has a `name` field and no body, so adding it to the C# `class_types` emits a clean empty type node (no phantom members) that the in-file references now bind to. Namespaced delegates are scoped and contained exactly like the sibling class/enum/struct declarations. Delegate parameter/return type references and delegate-instance invocation resolution are unchanged (out of scope).
|
Thanks for the pull request, @rajatnagda45. A maintainer will review it soon. Want to talk it through while it is in review? Come join us on our Discord server. For longer-form discussion there is also GitHub Discussions. A couple of things that speed up review: make sure the test suite passes on Python 3.10 and 3.13, and that the change keeps extraction deterministic. |
There was a problem hiding this comment.
Graphify reviewed this change.
No issues found by static checks — no coupling regressions or blocking issues in the code graph. That is not the same as safe to merge: see what was not checked.
Not checked: tests were not run; no formal proof of the changed code.
Graphify review — findings
Extracts C# delegate declarations as named type nodes by adding delegate_declaration to the C# class_types. Before, the declaration was dropped, so in-file references to the delegate (e.g. a BinOp field or parameter) went through ensure_named_node and minted a sourceless stub node. Those references now bind to a real, source-backed node, and the new tests cover both top-level and namespaced delegates.
No blocking issues surfaced.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 3115 functions depend on the 330 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
extract()— 976 callers, 59 callees
Verification — 3115 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: 2877 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
199 of 433 test file(s) selected (46%) via static blast radius.
tests/test_affected_overrides.py— impacttests/test_astro_extraction.py— impacttests/test_astro_import_ids.py— impacttests/test_blade_extractor.py— impacttests/test_build.py— impacttests/test_builtin_global_type_refs.py— impacttests/test_c_include_resolution.py— impacttests/test_cache.py— impacttests/test_case_sensitive_resolution.py— impacttests/test_cjs_module_extension.py— impacttests/test_cobol_extractor.py— impacttests/test_cpp_method_declarations.py— impacttests/test_cpp_nested_and_cli.py— impacttests/test_cpp_objc_cross_file_calls.py— impacttests/test_cpp_template_type_params.py— impacttests/test_cross_extension_reexport_self_cycle.py— impacttests/test_cross_language_call_resolution.py— impacttests/test_cross_repo_external_call_guards.py— impacttests/test_cross_repo_member_calls.py— impacttests/test_csharp_call_site_generic_args.py— impacttests/test_csharp_delegate_declarations.py— impact, changed-testtests/test_csharp_enum_members.py— impacttests/test_csharp_field_generic_args.py— impacttests/test_csharp_foreach_loop_variable.py— impacttests/test_csharp_generic_callsites.py— impacttests/test_csharp_interface_dispatch.py— impacttests/test_csharp_member_calls.py— impacttests/test_csharp_member_chain_calls.py— impacttests/test_csharp_member_nodes.py— impacttests/test_csharp_object_creation.py— impacttests/test_csharp_partial_classes.py— impacttests/test_csharp_tuple_type_refs.py— impacttests/test_csharp_type_resolution.py— impacttests/test_csharp_underscore_collisions.py— impacttests/test_csharp_var_call_return_type.py— impacttests/test_csharp_verbatim_interpolation.py— impacttests/test_definition_file_portability.py— impacttests/test_detect.py— impacttests/test_dotnet.py— impacttests/test_duplicate_annotation_edges.py— impacttests/test_ejs_extractor.py— impacttests/test_elixir_defdelegate.py— impacttests/test_elixir_import_resolution.py— impacttests/test_elixir_keyword_def_calls.py— impacttests/test_elixir_qualified_calls.py— impacttests/test_elixir_unqualified_call_scope.py— impacttests/test_erlang_extractor.py— impacttests/test_ext_tagged_id_clash.py— impacttests/test_extract.py— impacttests/test_extract_cache_location.py— impact- … and 149 more
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.
· 1 more finding(s) on lines outside this diff (see the check run).
Closes #4552.
Problem
A C#
delegateis a named type, butdelegate_declarationwas in neitherclass_typesnorfunction_types, so the declaration was dropped from the graph. A field or parameter typed as the delegate (public BinOp Op;,int Apply(BinOp f, ...)) still resolved the nameBinOpthroughensure_named_node, which minted a sourceless stub and pointed thereferencesedge at it — even thoughBinOpis declared in the same file.That violates the provenance invariant: a real source-backed declaration must not lose to a sourceless god node (and the bare-id stub becomes a cross-file hub shared by every same-named reference in the corpus).
Implementation
delegate_declarationhas anamefield and no body, so adding it to the C#class_typesemits a clean empty type node (no phantom members) that the in-file references now bind to. Namespaced delegates are scoped and contained exactly like the sibling class/enum/struct declarations.Verification
tests/test_csharp_delegate_declarations.py(non-namespaced + namespaced: delegate is a sourced node, references bind to it, no sourceless leftover). Both fail on pre-fix v8, pass after.python -m pytest tests/ -q --ignore=tests/test_security.py→ 8331 passed, 15 skipped (deselecting the live-DNS security tests and the built-wheel test).python -m pytest tests/ -k csharp→ 312 passed (no regressions, incl. the delegate-field/enum-collision test).ruff check .→ clean.pyright graphify/extract.py→ unchanged from baseline (0 added).Limitations
Delegate parameter/return type references and delegate-instance invocation resolution are unchanged (out of scope).