Pure-virtual declarations (`virtual int read(int key) = 0;`) parse as field_declaration, not function_definition, so they minted no method node — calls through an abstract base and cpp-override synthesis had nothing to attach to. Mirror Java interface methods: mint the node (TS + kernel), mark isAbstract, and cover with extraction/e2e/parity fixtures. Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com>
This commit is contained in:
co-authored by
Colby McHenry
parent
8df9ecac9d
commit
2f1a99d34c
@@ -166,6 +166,35 @@ export function stripCppTemplateArgs(name: string): string {
|
||||
return out.trim();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is this C++ `field_declaration` a pure-virtual method (`virtual int read(int key) = 0;`)?
|
||||
* tree-sitter-cpp shapes those as a field_declaration whose declarator unwraps to a
|
||||
* `function_declarator`, with the pure-virtual `= 0` as a DIRECT `number_literal` "0"
|
||||
* child of the field_declaration (default-arg `= 0` lives inside parameter_declaration
|
||||
* and must not match). Bodiless method prototypes (`int foo();`) and data members
|
||||
* (`int x = 0;`) are excluded — prototypes usually have an out-of-line definition that
|
||||
* already mints the method node; pure virtuals never do (#1727).
|
||||
*/
|
||||
export function isCppPureVirtualMethodDecl(node: SyntaxNode): boolean {
|
||||
if (node.type !== 'field_declaration') return false;
|
||||
let declarator: SyntaxNode | null = getChildByField(node, 'declarator');
|
||||
if (!declarator) return false;
|
||||
while (
|
||||
declarator.type === 'pointer_declarator' ||
|
||||
declarator.type === 'reference_declarator'
|
||||
) {
|
||||
const inner: SyntaxNode | null =
|
||||
getChildByField(declarator, 'declarator') || declarator.namedChild(0);
|
||||
if (!inner) return false;
|
||||
declarator = inner;
|
||||
}
|
||||
if (declarator.type !== 'function_declarator') return false;
|
||||
return node.namedChildren.some(
|
||||
(c: SyntaxNode) => c.type === 'number_literal' && c.text === '0'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A function/method's return type lives in the `function_definition`'s `type`
|
||||
* field (`Metrics& Metrics::instance()` → `Metrics`). Constructors, destructors,
|
||||
@@ -1607,7 +1636,17 @@ export const cppExtractor: LanguageExtractor = {
|
||||
// get picked as the blast-radius representative over — the single real
|
||||
// definition, exactly as bodiless struct/enum specifiers are already skipped. (#1093)
|
||||
skipBodilessClass: true,
|
||||
methodTypes: ['function_definition'],
|
||||
// `function_definition` covers inline / out-of-line bodies; `field_declaration`
|
||||
// covers pure-virtual methods (`virtual int read(int key) = 0;`), which have no
|
||||
// body and would otherwise mint no node — so calls through the abstract base and
|
||||
// cpp-override synthesis had nothing to attach to (#1727). classifyMethodNode
|
||||
// keeps ordinary data members / prototypes on the children-walk path.
|
||||
methodTypes: ['function_definition', 'field_declaration'],
|
||||
classifyMethodNode: (node) => {
|
||||
if (node.type !== 'field_declaration') return 'method';
|
||||
return isCppPureVirtualMethodDecl(node) ? 'method' : 'skip';
|
||||
},
|
||||
isAbstract: (node) => (isCppPureVirtualMethodDecl(node) ? true : undefined),
|
||||
interfaceTypes: [],
|
||||
structTypes: ['struct_specifier'],
|
||||
// C++ unions additionally carry member functions, which extract through the
|
||||
|
||||
@@ -160,6 +160,8 @@ export interface LanguageExtractor {
|
||||
isAsync?: (node: SyntaxNode) => boolean;
|
||||
/** Check if node is static */
|
||||
isStatic?: (node: SyntaxNode) => boolean;
|
||||
/** Check if a method/class is abstract (C++ pure virtual, Java abstract, …). Return true to set; undefined/false leaves the flag unset. */
|
||||
isAbstract?: (node: SyntaxNode) => boolean | undefined;
|
||||
/** Check if variable declaration is a constant (const vs let/var) */
|
||||
isConst?: (node: SyntaxNode) => boolean;
|
||||
/**
|
||||
@@ -221,9 +223,13 @@ export interface LanguageExtractor {
|
||||
* both callable and data members (#808): TS/JS class FIELDS
|
||||
* (`public_field_definition` / `field_definition`) are methods only when
|
||||
* their value is callable (`onClick = () => {}`); a plain field
|
||||
* (`public fonts: Fonts;`, `count = 0`) is a property. Default: 'method'.
|
||||
* (`public fonts: Fonts;`, `count = 0`) is a property. C++ also lists
|
||||
* `field_declaration` in methodTypes so pure-virtual methods (`= 0`) can
|
||||
* mint nodes (#1727); non-callable field_declarations return `'skip'` so
|
||||
* the walker still descends (data-member initializers keep their call
|
||||
* edges). Default: 'method'.
|
||||
*/
|
||||
classifyMethodNode?: (node: SyntaxNode) => 'method' | 'property';
|
||||
classifyMethodNode?: (node: SyntaxNode) => 'method' | 'property' | 'skip';
|
||||
|
||||
/**
|
||||
* Resolve the body node for a function/method/class when it's not a child field.
|
||||
|
||||
@@ -1041,8 +1041,14 @@ export class TreeSitterExtractor {
|
||||
else if (this.extractor.methodTypes.includes(nodeType)) {
|
||||
// TS/JS class fields parse as a methodTypes node; only function-valued
|
||||
// fields are methods — a plain field (`public fonts: Fonts;`) is a
|
||||
// property (#808). classifyMethodNode is absent for other languages.
|
||||
if (this.extractor.classifyMethodNode?.(node) === 'property') {
|
||||
// property (#808). C++ lists `field_declaration` so pure-virtual methods
|
||||
// mint nodes (#1727); non-callable ones return 'skip' and fall through to
|
||||
// the children walk. classifyMethodNode is absent for other languages.
|
||||
const methodClass = this.extractor.classifyMethodNode?.(node) ?? 'method';
|
||||
if (methodClass === 'skip') {
|
||||
// Not a method — leave skipChildren false so data-member initializers
|
||||
// still contribute call/instantiation edges under the enclosing class.
|
||||
} else if (methodClass === 'property') {
|
||||
const propNode = this.extractProperty(node);
|
||||
// Walk the initializer so its calls/instantiations attribute to the
|
||||
// property (`history = createHistory()` → history calls
|
||||
@@ -1798,6 +1804,9 @@ export class TreeSitterExtractor {
|
||||
const visibility = this.extractor.getVisibility?.(node);
|
||||
const isAsync = this.extractor.isAsync?.(node);
|
||||
const isStatic = this.extractor.isStatic?.(node);
|
||||
// Only persist abstract when true — a false return must not mint `isAbstract: false`
|
||||
// on every ordinary method (breaks kernel↔wasm parity JSON equality).
|
||||
const isAbstract = this.extractor.isAbstract?.(node) ? true : undefined;
|
||||
const returnType = this.extractor.getReturnType?.(node, this.source);
|
||||
const extraProps: Partial<Node> = {
|
||||
docstring,
|
||||
@@ -1805,6 +1814,7 @@ export class TreeSitterExtractor {
|
||||
visibility,
|
||||
isAsync,
|
||||
isStatic,
|
||||
isAbstract,
|
||||
returnType,
|
||||
};
|
||||
if (receiverType) {
|
||||
|
||||
Reference in New Issue
Block a user