Environment
- graphify 0.9.52 (
v8 @ 680e3ed), Linux, Python 3.12
graphify.extract.extract([...])
Summary
_build_csharp_type_def_index (graphify/extractors/csharp.py:19) treats every sourced .cs code node with a plain identifier label as a type declaration. Property nodes (#3006) and enum member nodes (#3063) are exactly that shape, so they enter the index and compete with real declarations for a (namespace, name) key.
The index does not require the key to be unique. It resolves a collision by sorting on (source_file, source_location, id) and taking the first entry, so the winner is decided by file name order. A member in an earlier-sorting file captures every reference to the type it shares a name with, across the whole corpus.
I filed both of the PRs that added those member nodes, so this is my regression.
Reproducer
// holder.cs
namespace App;
public class Holder { public Widget Widget { get; set; } }
// widget.cs
namespace App;
public class Widget {}
// other.cs
namespace App;
public class Other { public Widget Thing { get; set; } }
public Widget Widget { get; set; } is ordinary C#.
Current behavior (0.9.52)
Holder --references--> Widget @ holder.cs (the property)
Other --references--> Widget @ holder.cs (the property)
Both classes bind to the property, including Other, which has no colliding member of its own. Rename widget.cs to aaa.cs so it sorts first and both bind to the class instead. Nothing else changes.
The same shape with an enum:
// abc.cs
namespace App;
public enum ProductType { Standard, Bundle }
// zeta.cs
namespace App;
public class Standard {}
A reference to the type Standard resolves to ProductType.Standard.
And it can invent a binding that C# scoping forbids. With the class in NsA, the property in NsB, and no using, the reference should dangle:
Holder --references--> Widget @ b_holder.cs (the property, cross-namespace)
A sibling class in NsB without the colliding property correctly dangles on a sourceless stub, so the member is what makes the difference.
Measured
On one .NET service (1,497 .cs files):
|
0.9.52 |
| index entries |
4,197 |
| entries whose winner is a member |
2,777 (66%) |
| of those, ones that shadow a real declaration |
18 |
| edges arriving at member nodes |
75 references, 43 calls |
The 18 are the public ReviewInfo ReviewInfo { get; set; } idiom, where the property and the class it names sit in the same namespace.
The damage runs both ways: the member collects edges that are not about it, and the type loses them, so "what uses this type" under-reports.
Note on scope
_build_csharp_type_def_index is the only index of this shape; grep for type_def_index finds no equivalent in another language's extractor, and type_def_nids in extract.py is already guarded by contained, which member nodes are not in. TypeScript enum members (#3064) are unaffected because this index is .cs only.
The extraction cache directory is versioned (v{version}-s{schema}), so a release invalidates the stale unstamped nodes on its own.
I have a patch and will open it against this.
Environment
v8@ 680e3ed), Linux, Python 3.12graphify.extract.extract([...])Summary
_build_csharp_type_def_index(graphify/extractors/csharp.py:19) treats every sourced.cscode node with a plain identifier label as a type declaration. Property nodes (#3006) and enum member nodes (#3063) are exactly that shape, so they enter the index and compete with real declarations for a(namespace, name)key.The index does not require the key to be unique. It resolves a collision by sorting on
(source_file, source_location, id)and taking the first entry, so the winner is decided by file name order. A member in an earlier-sorting file captures every reference to the type it shares a name with, across the whole corpus.I filed both of the PRs that added those member nodes, so this is my regression.
Reproducer
public Widget Widget { get; set; }is ordinary C#.Current behavior (0.9.52)
Both classes bind to the property, including
Other, which has no colliding member of its own. Renamewidget.cstoaaa.csso it sorts first and both bind to the class instead. Nothing else changes.The same shape with an enum:
A reference to the type
Standardresolves toProductType.Standard.And it can invent a binding that C# scoping forbids. With the class in
NsA, the property inNsB, and nousing, the reference should dangle:A sibling class in
NsBwithout the colliding property correctly dangles on a sourceless stub, so the member is what makes the difference.Measured
On one .NET service (1,497
.csfiles):references, 43callsThe 18 are the
public ReviewInfo ReviewInfo { get; set; }idiom, where the property and the class it names sit in the same namespace.The damage runs both ways: the member collects edges that are not about it, and the type loses them, so "what uses this type" under-reports.
Note on scope
_build_csharp_type_def_indexis the only index of this shape;grepfortype_def_indexfinds no equivalent in another language's extractor, andtype_def_nidsinextract.pyis already guarded bycontained, which member nodes are not in. TypeScript enum members (#3064) are unaffected because this index is.csonly.The extraction cache directory is versioned (
v{version}-s{schema}), so a release invalidates the stale unstamped nodes on its own.I have a patch and will open it against this.