mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
* fix(cpp): qualify types nested in a named union by their union scope (#1995) `union_specifier` was missing from cppClassConfig.ancestorScopeNodeTypes, so a struct nested in `union U1` and one in `union U2` both qualified to the bare `Inner` and merged onto one Struct:...:Inner node — from_u1/from_u2 cross-wired (invisible to findDanglingEdges). Adding `union_specifier` lets buildQualifiedName pick up the named union's `name` segment, materializing distinct `U1.Inner` / `U2.Inner` nodes. Anonymous unions have no `name` child and correctly contribute nothing (members inject into the enclosing scope); the separate C config is untouched. New fixture + positive-identity tests (sequential + worker, both legs). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cpp): distinct nodes for anonymous-namespace-nested same-tail types (#1995) An anonymous `namespace { }` is a namespace_definition with no `name` child, so the scope walker dropped it (empty segment) and two `namespace { struct Inner {} }` blocks in one TU collapsed onto a single `Inner` node — from_anon_a/from_anon_b cross-wired. A C++ `extractScopeSegments` override (the first consumer of the existing config hook) gives each anonymous namespace a deterministic per-block discriminator from its start byte, keeping the nested types distinct. Named scopes (incl. `inline namespace`) and anonymous unions are unaffected. Deterministic across the sequential and worker full-file parses. New fixture + tests assert node DISTINCTNESS (count==2 / distinct owners), not the non-portable discriminator value. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(cpp): regenerate cpp scope-capture bench baseline for #1995 fixtures Rebased onto main (which now carries #1992 + its rust baseline). #1995 adds the cpp-union-nested-tail-collision and cpp-anon-ns-tail-collision fixtures, growing the cpp-* corpus 270->272 and drifting the order-independent fingerprint (538e8be -> d63ded6). Pure fixture-corpus drift — no scope-extractor change; existing fixtures' captures byte-identical. (cpp has no captures-golden gate, so only the bench baseline needs regenerating.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
16014657c8
commit
e316222cd5
5 changed files with 204 additions and 3 deletions
|
|
@ -16,11 +16,11 @@
|
|||
"_added": "#1956: c added to the scope-capture bench (was UNBENCHED). C has no inheritance \u2014 flat scale source. Adding it exposed + fixed a pre-existing O(n^2) findNodeAtRange root-walk in c/captures.ts (threaded c.node, byte-identical over c-* fixtures); scaling 3.475 -> 0.96."
|
||||
},
|
||||
"cpp": {
|
||||
"fingerprint": "538e8beebf0a69f6170dff452da3f98046a08cbe8b098b3c9943c4a8a79d2e22",
|
||||
"fingerprint": "d63ded6251a89d42cc63941ac3fdb093bf5b59ae483b135786766f925cdc91c5",
|
||||
"scaling_budget": 1.5,
|
||||
"_added": "#1956: cpp added to the scope-capture bench (was UNBENCHED). Heritage-bearing scale source (: public Base, public Mixin) drives emitCppInheritanceCaptures at scale. Adding it exposed + fixed a pre-existing O(n^2) findNodeAtRange root-walk in cpp/captures.ts (~12 sites, threaded c.node, byte-identical over 263 cpp-* fixtures); scaling 2.30 -> 1.12.",
|
||||
"_rebaselined": "#1965 / #1923 F4: uninitialized non-leading multi-declarators now emit @declaration.variable captures; cpp-adl-inner-callable-outer-noncallable data::Pair a, b adds the legitimate fixture drift. Linear (~1.06).",
|
||||
"_note": "#1975: + cpp-out-of-line-class fixture, fixture_count 263->265. #1990: + cpp-adl-ns-plus-hidden-friend-same-name fixture (ADL hidden-friend + namespace-callable merge parity test). Pure fixture-corpus drift — no scope-extractor change; existing fixtures' captures byte-identical. fixture_count 265->267."
|
||||
"_note": "#1975: + cpp-out-of-line-class fixture, fixture_count 263->265. #1990: + cpp-adl-ns-plus-hidden-friend-same-name fixture (ADL hidden-friend + namespace-callable merge parity test). Pure fixture-corpus drift — no scope-extractor change; existing fixtures' captures byte-identical. fixture_count 265->267. #1995: + cpp-union-nested-tail-collision and cpp-anon-ns-tail-collision fixtures — pure fixture-corpus drift; fixture_count 270->272, fingerprint 538e8be->d63ded6."
|
||||
},
|
||||
"csharp": {
|
||||
"_rebaselined": "#1956 synth-widening: + csharp-qualified-base fixture; the synth now walks record_declaration + struct_declaration base_lists and handles alias_qualified_name (matching the #1940 legacy leg), so record/struct heritage now emits. csharp-record-base gains a record inherits capture. (record->record SAME-namespace EXTENDS is a separate registry resolution gap, tracked as follow-up.) Linear (~1.00). (Earlier #1956: heritage-bearing scale source.)",
|
||||
|
|
|
|||
|
|
@ -45,10 +45,33 @@ export const cClassConfig: ClassExtractionConfig = {
|
|||
export const cppClassConfig: ClassExtractionConfig = {
|
||||
language: SupportedLanguages.CPlusPlus,
|
||||
typeDeclarationNodes: ['class_specifier', 'struct_specifier', 'enum_specifier'],
|
||||
ancestorScopeNodeTypes: ['namespace_definition', 'class_specifier', 'struct_specifier'],
|
||||
// #1995: `union_specifier` is included so a type nested in a NAMED union
|
||||
// (`union U1 { struct Inner {...} }`) qualifies as `U1.Inner`. Anonymous unions
|
||||
// have no `name` child → extractScopeSegmentsFromNode returns [] → they correctly
|
||||
// contribute nothing (members inject into the enclosing scope). C uses the
|
||||
// separate cClassConfig (no qualifiedNodeId), so it is intentionally untouched.
|
||||
ancestorScopeNodeTypes: [
|
||||
'namespace_definition',
|
||||
'class_specifier',
|
||||
'struct_specifier',
|
||||
'union_specifier',
|
||||
],
|
||||
// #1978: key nested-type nodes by their fully-qualified path (Outer.Inner) so
|
||||
// same-tail nested types in one TU stay distinct instead of silently merging.
|
||||
qualifiedNodeId: true,
|
||||
// #1995: anonymous namespaces have no `name` child, so the generic scope walker
|
||||
// drops them (empty segment) and two `namespace { struct Inner {} }` blocks in one
|
||||
// TU collapse onto a single `Inner` node. Give each anonymous namespace_definition
|
||||
// a deterministic per-block discriminator (its start byte — stable across the
|
||||
// sequential and worker full-file parses) so the nested types stay distinct.
|
||||
// Returning `undefined` for every other scope — named namespaces (incl. `inline
|
||||
// namespace`), classes, structs, named unions — falls through to the default
|
||||
// name-based extraction, leaving them unchanged. Anonymous UNIONS are not matched
|
||||
// here (members inject into the enclosing scope), so they keep yielding [].
|
||||
extractScopeSegments: (node) =>
|
||||
node.type === 'namespace_definition' && !node.childForFieldName?.('name')
|
||||
? [`@anon${node.startIndex}`]
|
||||
: undefined,
|
||||
extractName: (node) => {
|
||||
const nameNode = node.childForFieldName?.('name');
|
||||
if (!nameNode) return undefined;
|
||||
|
|
|
|||
17
gitnexus/test/fixtures/lang-resolution/cpp-anon-ns-tail-collision/main.cpp
vendored
Normal file
17
gitnexus/test/fixtures/lang-resolution/cpp-anon-ns-tail-collision/main.cpp
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// Same-tail structs in sibling ANONYMOUS namespaces (#1995).
|
||||
//
|
||||
// An anonymous `namespace { }` is a namespace_definition with no `name` child, so
|
||||
// extractScopeSegmentsFromNode returns [] and both `Inner` structs qualified to the
|
||||
// bare `Inner` and merged onto one node — from_anon_a / from_anon_b cross-wired. A
|
||||
// deterministic per-block discriminator (derived from the namespace node's start
|
||||
// byte) keeps the two blocks' types distinct.
|
||||
namespace {
|
||||
struct Inner {
|
||||
void from_anon_a() {}
|
||||
};
|
||||
}
|
||||
namespace {
|
||||
struct Inner {
|
||||
void from_anon_b() {}
|
||||
};
|
||||
}
|
||||
17
gitnexus/test/fixtures/lang-resolution/cpp-union-nested-tail-collision/main.cpp
vendored
Normal file
17
gitnexus/test/fixtures/lang-resolution/cpp-union-nested-tail-collision/main.cpp
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// Same-tail structs nested in sibling NAMED unions (#1995).
|
||||
//
|
||||
// `union_specifier` was omitted from cppClassConfig.ancestorScopeNodeTypes, so a
|
||||
// struct nested in `union U1` and one nested in `union U2` both qualified to the
|
||||
// bare `Inner` and merged onto ONE Struct:...:Inner node — from_u1 / from_u2
|
||||
// cross-wired (dangling:0 but wrong). With the union scope qualified they must
|
||||
// materialize distinct `U1.Inner` / `U2.Inner` nodes.
|
||||
union U1 {
|
||||
struct Inner {
|
||||
void from_u1() {}
|
||||
};
|
||||
};
|
||||
union U2 {
|
||||
struct Inner {
|
||||
void from_u2() {}
|
||||
};
|
||||
};
|
||||
|
|
@ -3915,6 +3915,150 @@ describe('C++ inline nested same-tail collision — worker path parity (issue #1
|
|||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Named-union nested same-tail collision — distinct qualified nodes (issue #1995)
|
||||
//
|
||||
// `union U1 { struct Inner {...} }` + `union U2 { struct Inner {...} }` must
|
||||
// materialize TWO distinct Struct nodes (qn U1.Inner / U2.Inner). `union_specifier`
|
||||
// was missing from cppClassConfig.ancestorScopeNodeTypes, so both Inner structs
|
||||
// qualified to the bare `Inner` and merged (dangling:0 but wrong). Mirrors the
|
||||
// #1978 inline-collision template; positive owner-identity, not just dangle-free.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('C++ named-union nested same-tail collision — distinct qualified nodes (issue #1995)', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(
|
||||
path.join(FIXTURES, 'cpp-union-nested-tail-collision'),
|
||||
() => {},
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
it('materializes U1.Inner and U2.Inner as two distinct Struct nodes [#1995-union]', () => {
|
||||
const qns = getNodesByLabelFull(result, 'Struct')
|
||||
.map((n) => n.properties.qualifiedName)
|
||||
.filter((q) => q === 'U1.Inner' || q === 'U2.Inner')
|
||||
.sort();
|
||||
expect(qns).toEqual(['U1.Inner', 'U2.Inner']);
|
||||
});
|
||||
|
||||
it('owns from_u1 / from_u2 through their OWN distinct node (positive identity) [#1995-union]', () => {
|
||||
expect(findDanglingEdges(result, ['HAS_METHOD'])).toEqual([]);
|
||||
const hm = getRelationships(result, 'HAS_METHOD');
|
||||
const ownerQn = (target: string) => {
|
||||
const e = hm.find((x) => x.target === target);
|
||||
expect(e, `HAS_METHOD -> ${target}`).toBeDefined();
|
||||
return result.graph.getNode(e!.rel.sourceId)?.properties.qualifiedName;
|
||||
};
|
||||
expect(ownerQn('from_u1')).toBe('U1.Inner');
|
||||
expect(ownerQn('from_u2')).toBe('U2.Inner');
|
||||
});
|
||||
});
|
||||
|
||||
// Worker-path parity for the named-union collision (parse-worker.ts must qualify
|
||||
// the union scope byte-identically to the sequential parser).
|
||||
describe('C++ named-union nested same-tail collision — worker path parity (issue #1995)', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(
|
||||
path.join(FIXTURES, 'cpp-union-nested-tail-collision'),
|
||||
() => {},
|
||||
{ workerThresholdsForTest: { minFiles: 1, minBytes: 1 }, workerPoolSize: 2 },
|
||||
);
|
||||
}, 120000);
|
||||
|
||||
it('genuinely used the worker pool [#1995-union]', () => {
|
||||
expect(result.usedWorkerPool).toBe(true);
|
||||
});
|
||||
|
||||
it('materializes U1.Inner / U2.Inner and owns each method on the worker path [#1995-union]', () => {
|
||||
const qns = getNodesByLabelFull(result, 'Struct')
|
||||
.map((n) => n.properties.qualifiedName)
|
||||
.filter((q) => q === 'U1.Inner' || q === 'U2.Inner')
|
||||
.sort();
|
||||
expect(qns).toEqual(['U1.Inner', 'U2.Inner']);
|
||||
expect(findDanglingEdges(result, ['HAS_METHOD'])).toEqual([]);
|
||||
const hm = getRelationships(result, 'HAS_METHOD');
|
||||
const ownerQn = (target: string) =>
|
||||
result.graph.getNode(hm.find((x) => x.target === target)!.rel.sourceId)?.properties
|
||||
.qualifiedName;
|
||||
expect(ownerQn('from_u1')).toBe('U1.Inner');
|
||||
expect(ownerQn('from_u2')).toBe('U2.Inner');
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Anonymous-namespace nested same-tail collision — distinct nodes (issue #1995)
|
||||
//
|
||||
// Two `namespace { struct Inner {...} }` blocks must materialize TWO distinct
|
||||
// Struct nodes. An anonymous namespace_definition has no `name` child, so both
|
||||
// Inner structs qualified to the bare `Inner` and merged. A C++ extractScopeSegments
|
||||
// override gives each anon block a deterministic start-byte discriminator. The
|
||||
// discriminator value is not portable, so assert on node DISTINCTNESS (count==2 /
|
||||
// distinct owner ids), never a literal qualifiedName.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('C++ anonymous-namespace nested same-tail collision — distinct nodes (issue #1995)', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(path.join(FIXTURES, 'cpp-anon-ns-tail-collision'), () => {});
|
||||
}, 60000);
|
||||
|
||||
it('materializes two distinct Struct Inner nodes (one per anon namespace) [#1995-anon]', () => {
|
||||
const innerQns = getNodesByLabelFull(result, 'Struct')
|
||||
.map((n) => n.properties.qualifiedName)
|
||||
.filter((q): q is string => typeof q === 'string' && q.endsWith('Inner'));
|
||||
// Start-byte discriminator → assert DISTINCTNESS, not a literal value. Pre-fix
|
||||
// both Inner structs merge onto one bare `Inner` node (set size 1).
|
||||
expect(new Set(innerQns).size).toBe(2);
|
||||
});
|
||||
|
||||
it('owns from_anon_a / from_anon_b through DISTINCT nodes (no merge) [#1995-anon]', () => {
|
||||
expect(findDanglingEdges(result, ['HAS_METHOD'])).toEqual([]);
|
||||
const hm = getRelationships(result, 'HAS_METHOD');
|
||||
const a = hm.find((x) => x.target === 'from_anon_a');
|
||||
const b = hm.find((x) => x.target === 'from_anon_b');
|
||||
expect(a, 'HAS_METHOD -> from_anon_a').toBeDefined();
|
||||
expect(b, 'HAS_METHOD -> from_anon_b').toBeDefined();
|
||||
expect(a!.rel.sourceId).not.toBe(b!.rel.sourceId);
|
||||
});
|
||||
});
|
||||
|
||||
// Worker-path parity for the anonymous-namespace collision: the start-byte
|
||||
// discriminator must be deterministic across the worker's full-file parse.
|
||||
describe('C++ anonymous-namespace nested same-tail collision — worker path parity (issue #1995)', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(
|
||||
path.join(FIXTURES, 'cpp-anon-ns-tail-collision'),
|
||||
() => {},
|
||||
{ workerThresholdsForTest: { minFiles: 1, minBytes: 1 }, workerPoolSize: 2 },
|
||||
);
|
||||
}, 120000);
|
||||
|
||||
it('genuinely used the worker pool [#1995-anon]', () => {
|
||||
expect(result.usedWorkerPool).toBe(true);
|
||||
});
|
||||
|
||||
it('materializes two distinct anon Inner nodes and owns each method on the worker path [#1995-anon]', () => {
|
||||
const innerQns = getNodesByLabelFull(result, 'Struct')
|
||||
.map((n) => n.properties.qualifiedName)
|
||||
.filter((q): q is string => typeof q === 'string' && q.endsWith('Inner'));
|
||||
expect(new Set(innerQns).size).toBe(2);
|
||||
expect(findDanglingEdges(result, ['HAS_METHOD'])).toEqual([]);
|
||||
const hm = getRelationships(result, 'HAS_METHOD');
|
||||
const a = hm.find((x) => x.target === 'from_anon_a');
|
||||
const b = hm.find((x) => x.target === 'from_anon_b');
|
||||
expect(a, 'HAS_METHOD -> from_anon_a').toBeDefined();
|
||||
expect(b, 'HAS_METHOD -> from_anon_b').toBeDefined();
|
||||
expect(a!.rel.sourceId).not.toBe(b!.rel.sourceId);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Inline nested same-tail HERITAGE — qualified base resolution (issue #1982)
|
||||
//
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue