fix(ids): declaration merging mints one id per facet; both facets survive in types{} (#177) - #190
Merged
Merged
Conversation
…vive in types{} (#177)
Value facets keep the bare id; a colliding type facet is <id>#type; a later type facet of a
type/type merge is keyed Name#<kind> and gets <id>#<kind>. Signatures unchanged; heritage
resolves through the type facet; a split is not an L1 collision. Ids move only on collision.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #177. Spec:
docs/design/specs/declaration-merging-ids.md(D1–D5, mirrored in.claude/SCHEMA_DECISIONS.md). Analyzer 1.5.0; Neo4j contract stays 2.0.0.What was wrong
Every facet of one name has the same signature, and ids were minted from signatures alone, so a value and a type of one name (
const TableOption = () => …+interface TableOption) shared an id and--emit neo4jMERGEd them onto one node with both labels and the last writer'skind. Worse, a type/type merge (class C+interface C) was "later kind wins" intypes{}— the class vanished fromanalysis.jsonentirely.What changes
<id>#type. Callables anchor call edges, body-node ids,@formal_in:Nandparameters[i].id; a type's id is only referenced throughextends_ids/implements_ids.Name#<kind>intypes{}and gets<id>#<kind>(#interface,#namespace, …).-a 4output on this branch is byte-identical tomain).idBySigkeeps the value facet for call-graph re-identification, callee backfill and homing; a newtypeIdBySiggives heritage the type facet; a split is not an L1 collision.[0](which was the value), soimplements TableOptionresolves to the interface facet.kindand labels agree on every node.Tests
test/declaration-merging.test.ts: value+interface, alias+arrow, alias+const, class+interface, function+namespace — distinct ids per facet, both facets intypes{},implements→#typefacet,extends→ bare class, no L1 collision, member ids under the bare class id, and the projection has no node with two kind labels.bun test: 345 pass.No sibling precedent: python rebinds on a later
def, Java forbids the construct.