Skip to content

[Bug]: C# nested type identity is truncated to one level, merging distinct methods into one node #934

Description

@merlinhu1

code-review-graph version

2.3.8

Operating system

macOS

Python version

3.12.4

AI platform

claude-code

Output of code-review-graph status

Method nodes inside a nested class are qualified with only the immediate enclosing class, not the full containing-type path. When two nested classes share a name — the standard MediatR-on-ASP.NET feature layout used by gothinkster/aspnetcore-realworld-example-app — their methods collapse into a single node. One method is silently lost. No uncertainty caveat covers this.

Steps to reproduce

mkdir -p /tmp/crg-nested && cd /tmp/crg-nested && git init -q .
cat > Features.cs <<'EOF'
using MediatR;

namespace App.Articles;

public class Details
{
public record Query(string Slug) : IRequest;

public class QueryHandler : IRequestHandler<Query, string>
{
    public Task<string> Handle(Query request, CancellationToken ct) => Task.FromResult("details");
}

}

public class Edit
{
public record Query(string Slug) : IRequest;

public class QueryHandler : IRequestHandler<Query, string>
{
    public Task<string> Handle(Query request, CancellationToken ct) => Task.FromResult("edit");
}

}
EOF
code-review-graph build
python3 - <<'EOF'
import sqlite3
c = sqlite3.connect('.code-review-graph/graph.db')
for r in c.execute("select kind, qualified_name from nodes where kind!='File' order by qualified_name"):
print(f"{r[0]:9} {r[1]}")
EOF

Expected vs actual behavior

Expected
Features.cs::Details.QueryHandler.Handle and Features.cs::Edit.QueryHandler.Handle as separate nodes.

Actual:
Class Features.cs::Details
Class Features.cs::Details.Query
Class Features.cs::Details.QueryHandler
Class Features.cs::Edit
Class Features.cs::Edit.Query
Class Features.cs::Edit.QueryHandler
Function Features.cs::QueryHandler.Handle ← one node for two distinct methods

Additional context

This issue seems to affect more than C#

Cause
parser.py:10298, when recursing into a class body

Julia is the only language that accumulates nested scope; every other language passes the bare simple name to _qualify (parser.py:14378). _julia_scope_join (parser.py:7428) already implements the needed join, including its repeated-prefix guard.

Impact

Silent node loss with no caveat. Any tool that addresses a method — callers_of, tests_for, impact radius, review context, refactor/rename — targets a node standing for two unrelated methods. This is the failure mode CRG's uncertainty machinery exists to prevent, and it is not covered.

Second symptom: only one level is ever retained

mkdir -p /tmp/crg-deep && cd /tmp/crg-deep && git init -q .
cat > Deep.cs <<'EOF'
namespace App;
public class A { public class B { public class C { public void M() { } } } }
EOF
code-review-graph build

Nodes: Deep.cs::A, Deep.cs::A.B, Deep.cs::B.C (should be A.B.C), Deep.cs::C.M (should be A.B.C.M).
The containment tree is broken as a result: A.B.C's CONTAINS edge comes from the File rather than from A.B, and the edge Deep.cs::C -> Deep.cs::C.M has a source node that does not exist.
Separately, querying by the correctly-qualified nested name does not resolve — code-review-graph query children_of Details.QueryHandler returns target not indexed: no node matching 'Details.QueryHandler' even though that node exists.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions