Skip to content

C#: property and enum member nodes enter the type-definition index, so a member can capture every reference to a same-named type #3212

Description

@durmazoguzhan

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions