You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Raised out of #939, which is parked in favour of doing this deliberately. #935 is the motivating bug and should be a test case for whatever design lands.
Problem
INHERITS.target_qualified currently serves three incompatible roles at once:
role
example
raw source reference
Models.GenericBase<string>
canonical declaration reference
Models.GenericBase + arity
resolved graph target
/repo/GenericBase.cs::GenericBase
#935 is a symptom: a generic base is stored as GenericBase<string>, which never matches
the node GenericBase, so inheritors_of returns zero and asserts the absence.
#939 attempted to fix this by normalizing the stored target to the erased declaration
name. That makes a shared field lossy, and the field has more than one consumer. Two
new false positives resulted, both verified against main.
Verified regressions from the erase-in-place approach
These are the constraints any design must satisfy. Both were reproduced by building the
same source with main and with the #939 branch.
1. Spring resolution picks the wrong implementation.
spring_resolver.py builds its implementation map directly from INHERITS.target_qualified
and resolves when a key has exactly one implementor. INJECTS already reduces Store<Integer> to Store, so once INHERITS also erases to Store the two meet:
This rewrites a CALLS edge, so the error propagates into impact radius, flows and review
context. Generic parameters are deliberately autowiring qualifiers in Spring, so Store<String> and Store<Integer> are distinct candidates.
2. C# generic arity is part of declaration identity.
main : 0 results
#939 : ['Child'] (Child implements I<int>, not I)
inheritors_of falls back from the qualified node to node.name filtered only by
language, and extra.constructed_types is not consulted on that path. This is within one
namespace, so it is distinct from #940.
Requirements for a design
Keep the raw constructed spelling losslessly. A stored edge should not destroy
information because the current query layer cannot use it yet.
Give the canonical declaration reference enough identity to be correct for C#: at
minimum the name plus generic arity (I, I<>, I<,>, or metadata-style I`1).
Java and C# should not necessarily share one normalization rule.
Make resolution consult that canonical form, rather than having a lookup fallback do
bare-name matching that the stored format was reshaped to satisfy.
Audit every consumer of INHERITS.target_qualified before changing its meaning.
Known consumers today: tools/query.py (inheritors_of), spring_resolver.py
(implementation map). This list must be re-derived, not trusted.
CPP_IDENTITY_VERSION in incremental.py handles a changed persisted identity format
by rebuilding with the real parser. That is the right mechanism if a stored format
changes, in preference to a SQL data migration that reimplements parser logic (see the
v10 attempt in fix(parser): resolve generic inheritance targets #939's history).
Tree-sitter already exposes the structure needed to extract a base name without
scanning for angle brackets: C# generic_name → identifier + type_argument_list,
Java generic_type → type_identifier + type_arguments. A character scanner gets class C : I</* < */ string> {} wrong, because < also occurs in trivia.
Raised out of #939, which is parked in favour of doing this deliberately. #935 is the motivating bug and should be a test case for whatever design lands.
Problem
INHERITS.target_qualifiedcurrently serves three incompatible roles at once:Models.GenericBase<string>Models.GenericBase+ arity/repo/GenericBase.cs::GenericBase#935 is a symptom: a generic base is stored as
GenericBase<string>, which never matchesthe node
GenericBase, soinheritors_ofreturns zero and asserts the absence.#939 attempted to fix this by normalizing the stored target to the erased declaration
name. That makes a shared field lossy, and the field has more than one consumer. Two
new false positives resulted, both verified against
main.Verified regressions from the erase-in-place approach
These are the constraints any design must satisfy. Both were reproduced by building the
same source with
mainand with the #939 branch.1. Spring resolution picks the wrong implementation.
spring_resolver.pybuilds its implementation map directly fromINHERITS.target_qualifiedand resolves when a key has exactly one implementor. INJECTS already reduces
Store<Integer>toStore, so once INHERITS also erases toStorethe two meet:This rewrites a CALLS edge, so the error propagates into impact radius, flows and review
context. Generic parameters are deliberately autowiring qualifiers in Spring, so
Store<String>andStore<Integer>are distinct candidates.2. C# generic arity is part of declaration identity.
Querying the non-generic
I0.cs::I:inheritors_offalls back from the qualified node tonode.namefiltered only bylanguage, and
extra.constructed_typesis not consulted on that path. This is within onenamespace, so it is distinct from #940.
Requirements for a design
information because the current query layer cannot use it yet.
minimum the name plus generic arity (
I,I<>,I<,>, or metadata-styleI`1).Java and C# should not necessarily share one normalization rule.
bare-name matching that the stored format was reshaped to satisfy.
INHERITS.target_qualifiedbefore changing its meaning.Known consumers today:
tools/query.py(inheritors_of),spring_resolver.py(implementation map). This list must be re-derived, not trusted.
the graph asserting a relationship it had not established.
Prior art in this repo
CPP_IDENTITY_VERSIONinincremental.pyhandles a changed persisted identity formatby rebuilding with the real parser. That is the right mechanism if a stored format
changes, in preference to a SQL data migration that reimplements parser logic (see the
v10 attempt in fix(parser): resolve generic inheritance targets #939's history).
scanning for angle brackets: C#
generic_name→identifier+type_argument_list,Java
generic_type→type_identifier+type_arguments. A character scanner getsclass C : I</* < */ string> {}wrong, because<also occurs in trivia.