A `class Foo;` forward declaration parses as a bodiless class_specifier. extractStruct (#831) and extractEnum already skip their bodiless forms, but extractClass did not — so every forward decl across dozens of headers minted a phantom bodiless `class` node that competed with, and could be picked as the blast-radius representative over, the single real definition. Add an opt-in `skipBodilessClass` extractor flag (set only on cppExtractor) and skip a bodiless class node when it's set, mirroring the struct/enum skip. The flag keeps this C/C++-scoped: languages where a bodiless class is a complete definition (Kotlin `class Empty`, Scala `case object`/`trait`) leave it unset and are unaffected. The body is now resolved once at the top of extractClass and reused for the member walk. Regression tests cover the collapse to a single definition, elaborated-type references creating no phantom, and Kotlin/Scala staying indexed. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ad03d24fb9
commit
f856f7ae49
@@ -2811,6 +2811,59 @@ class MYGAME_API UMyComponent : public UActorComponent { };
|
||||
});
|
||||
});
|
||||
|
||||
describe('C++ forward declarations do not mint phantom class nodes (#1093)', () => {
|
||||
// `class Foo;` parses as a bodiless class_specifier. Repeated across headers,
|
||||
// each forward decl minted a phantom bodiless `class` node that crowded out —
|
||||
// and could be picked as the blast-radius representative over — the single
|
||||
// real definition. Bodiless struct/enum specifiers were already skipped;
|
||||
// classes now are too, but ONLY for C/C++ (opt-in flag), never for languages
|
||||
// where a bodiless class is a complete definition.
|
||||
it('keeps only the real definition, dropping repeated forward decls', () => {
|
||||
const code = `
|
||||
class APXCharacter; // forward decl (header 1)
|
||||
class APXCharacter; // forward decl (header 2)
|
||||
friend class APXCharacter; // elaborated / friend forward reference
|
||||
|
||||
class APXCharacter { // the one real definition
|
||||
int hp;
|
||||
void takeDamage(int amount) { hp -= amount; }
|
||||
};
|
||||
`;
|
||||
const result = extractFromSource('character.cpp', code);
|
||||
const classes = result.nodes.filter(
|
||||
(n) => n.kind === 'class' && n.name === 'APXCharacter'
|
||||
);
|
||||
// Exactly one class node, and it's the definition (carries the member).
|
||||
expect(classes).toHaveLength(1);
|
||||
expect(classes[0].startLine).toBe(6);
|
||||
expect(
|
||||
result.nodes.some((n) => n.kind === 'method' && n.name === 'takeDamage')
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('elaborated type references in declarations create no phantom class', () => {
|
||||
// `class Foo obj;` is a variable declaration using an elaborated type, not
|
||||
// a class definition — it must not mint a `Foo` class node.
|
||||
const result = extractFromSource('use.cpp', 'class Foo;\nvoid f() { class Foo *p = nullptr; (void)p; }\n');
|
||||
expect(result.nodes.filter((n) => n.kind === 'class' && n.name === 'Foo')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('does NOT affect languages where a bodiless class is complete', () => {
|
||||
// Kotlin `class Empty` and Scala `trait`/`case object`/`class` with no body
|
||||
// are complete definitions — the C/C++-only skip must leave them indexed.
|
||||
const kt = extractFromSource('Empty.kt', 'class Empty\nclass Full { val x = 1 }\n');
|
||||
const ktClasses = kt.nodes.filter((n) => n.kind === 'class').map((n) => n.name);
|
||||
expect(ktClasses).toContain('Empty');
|
||||
expect(ktClasses).toContain('Full');
|
||||
|
||||
const scala = extractFromSource('M.scala', 'trait Marker\ncase object Red\nclass Foo\n');
|
||||
const scalaNames = scala.nodes
|
||||
.filter((n) => ['class', 'trait', 'interface'].includes(n.kind))
|
||||
.map((n) => n.name);
|
||||
expect(scalaNames).toEqual(expect.arrayContaining(['Marker', 'Red', 'Foo']));
|
||||
});
|
||||
});
|
||||
|
||||
describe('C++ templated base-class inheritance (#1043)', () => {
|
||||
// Inheriting from a template (`class D : public Base<int>`) recorded the base
|
||||
// ref as the full instantiation `Base<int>`, which never name-matched the
|
||||
|
||||
Reference in New Issue
Block a user