Skip to content

[Bug]: C# and Java. Inheritance through a generic base class is dropped, and inheritors_of reports it as a confirmed absence #935

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

INHERITS edges store the base type as raw source text. When the base is generic, the target is Base<Arg>, which never matches the class node Base, so the relationship is invisible. Worse than the omission: inheritors_of returns 0 with the confidence string "is indexed and no such edge is recorded" — an affirmative claim of absence, when the edge is present in the edges table and merely unresolved.

Steps to reproduce

mkdir -p /tmp/crg-generic && cd /tmp/crg-generic && git init -q .
cat > Bases.cs <<'EOF'
namespace App;

public abstract class PlainBase { }
public abstract class GenericBase { }

public class FromPlain : PlainBase { }
public class FromGeneric : GenericBase { }
EOF
code-review-graph build
code-review-graph query inheritors_of PlainBase
code-review-graph query inheritors_of GenericBase

Expected vs actual behavior

Actual
inheritors_of PlainBase → count=1 ['FromPlain'] confidence=None
inheritors_of GenericBase → count=0 []
confidence: "'…::GenericBase' is indexed and no such edge is recorded; graph currency unverified"
INHERITS Bases.cs::FromGeneric -> GenericBase

The target is the raw text GenericBase, which never matches node Bases.cs::GenericBase.

Expected

inheritors_of GenericBase returns FromGeneric. Failing that, a caveat that does not assert absence — no LanguageGap in uncertainty.py covers inheritors_of for C#, so this false negative is delivered unqualified.

For Java

package app;
abstract class PlainBase { }
abstract class GenericBase { }
class FromPlain extends PlainBase { }
class FromGeneric extends GenericBase { }

inheritors_of PlainBase = 1, inheritors_of GenericBase = 0.

Additional context

eShop project routes ordering commands through IdentifiedCommandHandler<T, R>. Every concrete handler inherits from a closed generic
public class CreateOrderIdentifiedCommandHandler : IdentifiedCommandHandler<CreateOrderCommand, bool> { }

So every one of them is invisible to inheritors_of, and CRG states positively that the base class has no inheritors. Since these handlers also declare no Handle of their own — it's inherited from the abstract base — there is no other edge connecting a concrete handler to its behavior.

Suggested fix

Resolve INHERITS targets by stripping type arguments before node matching, retaining the full constructed type in extra so the argument list is not lost (it is needed for any later type-substitution work). At minimum, stop emitting "no such edge is recorded" when an unresolved edge of that kind exists from the target.

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