csharp: emit member-access read/write edges - #482
Merged
Conversation
zzet
approved these changes
Aug 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
C# property, field, and constant accesses emitted no edges of any kind —
find_usageson any C# property answered with its declaration and nothing else. On a production C# codebase, 6 of 28,773 field nodes had any non-structural in-edge; every property looked unused no matter how often the code read or wrote it.This brings C# to parity with the PHP and Go extractors through the existing pipeline: each access emits
EdgeReads(EdgeWritesin assignment position — simple and compound assignment,++/--, with element accesses climbed sop.Items[0] = vwritesItems) tounresolved::*.<member>, the shaperesolveFieldRefalready consumes. Receiver typing rides the same ladder as calls:this/basefrom the enclosing declaration, locals from the tenv, known types for static accesses, chains through the chain typer. The resolver needed zero changes.One rule PHP does not need: dotted namespace qualification parses as nested member accesses in this grammar, so
System.Threading.Tasks.Task.CompletedTaskwould shower the graph with reads ofThreading/Tasks. An access with no receiver evidence emits only at the outermost link of its chain — with one refinement: a receiver that names a declared parameter or local is a value, not a namespace, so its chained reads survive even untyped.Documented v1 boundaries: parameter and field receivers emit untyped edges (binding falls to the resolver cascade, same contract as calls); conditional accesses are read-only; object-initializer assignments and attribute-argument reads don't emit.
Tests: extractor spec covering read/write/compound/
this/static-const/conditional/chained shapes plus the call-position exclusion and the namespace-noise rule; resolver spec binding a typed read and write to the receiver type's property at ≥0.9 over a same-named decoy. Written first, watched fail. All suites at their pre-existing Windows baselines.Validated live on a production C# codebase: 108,696 read + 9,978 write edges where there were none. On the counted fixture repo, an authored ledger (exactly 3 reads + 1 write of one property) pins as counted battery cells, green twice.
Stacked on #481 (which takes
csharpextractor version 7); this PR bumps 7→8.