mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-18 13:53:01 +08:00
fix(c,cpp): a struct, class, union or enum defined in a variable declaration is indexed (#2446)
`struct Foo { … } foo;`, `class Bar { … } bar;` and `static struct { … } SPT;`
outside any function or class body went to the variable extractor, which
skipped the declaration's children, so the type, its methods and its
enumerators were never indexed in either extraction engine.
Both engines now walk such a type before the variables: in C always, and in
C++ when the tree has no errors or the file is walked in brace scopes. This
converges with #2426's walk for erroring C++ files; a C++ file whose braces
don't balance is still left alone, since error recovery can run a class past
its own `}` there. The declaration's function-as-value scan skips the walked
type, which captured its own. An unnamed struct, union or enum takes the name
of the first variable its declaration declares (also inside function bodies,
where it was `<anonymous>`), as `typedef struct { … } Name;` already takes the
typedef name, and the comment above the declaration is the type's docstring.
cDeclaratorIdentifier moves to languages/c-cpp.ts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,13 @@
|
||||
//! (loses uninit scalars by design); cpp declarations instead take the TS
|
||||
//! GENERIC fallback (direct identifier children only → `int x;` extracts,
|
||||
//! `int x = 5;` does not — bug-for-bug).
|
||||
//! - a class/struct/union/enum defined in a declaration's `type`
|
||||
//! (`struct Foo { … } foo;`) is walked before the variables — in C, and in
|
||||
//! C++ unless the tree has errors and no brace scopes (walkDeclaredTypes);
|
||||
//! an unnamed one takes the name of the first declarator identifier
|
||||
//! (`static struct { … } SPT;` → `SPT`, in function bodies too), and the
|
||||
//! comment above the declaration is its docstring (c/cpp
|
||||
//! getDeclarationWrapper).
|
||||
//! - static-member/value-read pass (cpp only): `field_expression` is in
|
||||
//! MEMBER_ACCESS_TYPES (listed for Scala, same node kind in cpp), so
|
||||
//! `Capitalized.member` / `Capitalized->member` VALUE reads emit
|
||||
@@ -369,6 +376,10 @@ pub struct Walker<'t> {
|
||||
brace_scopes: Option<BraceScopes>,
|
||||
class_scopes: NestedIntervals<Scope>,
|
||||
class_scope_rows: HashSet<u32>,
|
||||
/// walkDeclaredTypes (tree-sitter.ts): a class-like type defined in a
|
||||
/// declaration is walked in C, and in C++ unless the tree has errors and
|
||||
/// its braces don't balance (no brace scopes; only under the hatch).
|
||||
walk_declared_types: bool,
|
||||
}
|
||||
|
||||
pub fn extract(file_path: &str, source: &str, language: &str) -> Result<EmitOut, String> {
|
||||
@@ -394,6 +405,13 @@ pub fn extract(file_path: &str, source: &str, language: &str) -> Result<EmitOut,
|
||||
return Err("defer: parse tree contains errors — wasm recovery is canonical".to_string());
|
||||
}
|
||||
|
||||
let brace_scopes = if variant == Variant::Cpp && tree.root_node().has_error() {
|
||||
brace_scopes::scan(source)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let walk_declared_types =
|
||||
variant == Variant::C || !tree.root_node().has_error() || brace_scopes.is_some();
|
||||
let mut w = Walker {
|
||||
src: source,
|
||||
file_path,
|
||||
@@ -413,13 +431,10 @@ pub fn extract(file_path: &str, source: &str, language: &str) -> Result<EmitOut,
|
||||
fs_values: HashMap::new(),
|
||||
fs_value_counts: HashMap::new(),
|
||||
value_scopes: Vec::new(),
|
||||
brace_scopes: if variant == Variant::Cpp && tree.root_node().has_error() {
|
||||
brace_scopes::scan(source)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
brace_scopes,
|
||||
class_scopes: NestedIntervals::new(),
|
||||
class_scope_rows: HashSet::new(),
|
||||
walk_declared_types,
|
||||
};
|
||||
|
||||
let line_count = source.bytes().filter(|b| *b == b'\n').count() as u32 + 1;
|
||||
@@ -630,6 +645,9 @@ impl<'t> Walker<'t> {
|
||||
return hook;
|
||||
}
|
||||
}
|
||||
if let Some(name) = self.unnamed_type_variable_name(node) {
|
||||
return name;
|
||||
}
|
||||
if let Some(name_node) = node.child_by_field_name("declarator") {
|
||||
let mut resolved = name_node;
|
||||
// Unwrap pointer/reference declarators (`int* f()`, `T& f()`).
|
||||
@@ -670,6 +688,21 @@ impl<'t> Walker<'t> {
|
||||
"<anonymous>".to_string()
|
||||
}
|
||||
|
||||
/// unnamedTypeVariableName (languages/c-cpp.ts): an unnamed class, struct,
|
||||
/// union or enum defined in a declaration takes the name of the first
|
||||
/// variable the declaration declares (`static struct { … } SPT;` → `SPT`).
|
||||
fn unnamed_type_variable_name(&self, node: Node) -> Option<String> {
|
||||
if node.child_by_field_name("name").is_some() {
|
||||
return None;
|
||||
}
|
||||
let declaration = defining_declaration(node)?;
|
||||
let mut cursor = declaration.walk();
|
||||
let identifier = declaration
|
||||
.children_by_field_name("declarator", &mut cursor)
|
||||
.find_map(c_declarator_identifier)?;
|
||||
Some(self.text(identifier).to_string())
|
||||
}
|
||||
|
||||
/// extractCppQualifiedMethodName (languages/c-cpp.ts:75).
|
||||
fn extract_cpp_qualified_method_name(&self, node: Node) -> Option<String> {
|
||||
if let Some(n) = self.recover_cpp_macro_defined_name(node) {
|
||||
@@ -1043,21 +1076,33 @@ impl<'t> Walker<'t> {
|
||||
{
|
||||
skip_children = self.extract_type_alias(node);
|
||||
} else if kind == "declaration" && !self.inside_class_like() {
|
||||
// In brace scopes, a class the tree reads as a declaration's type
|
||||
// (glued to the tokens after it by error recovery) is still a class.
|
||||
if self.brace_scopes.is_some() {
|
||||
if let Some(t) = node.child_by_field_name("type") {
|
||||
let class_like = matches!(
|
||||
t.kind(),
|
||||
"class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
|
||||
);
|
||||
if class_like && t.child_by_field_name("body").is_some() {
|
||||
self.visit_node(t);
|
||||
}
|
||||
}
|
||||
// A class, struct, union or enum defined in the declaration's type
|
||||
// (`struct Foo { … } foo;`) is a definition like one written on its
|
||||
// own, and the variables keep their nodes beside it — so is, in
|
||||
// brace scopes, a class the tree reads as a declaration's type
|
||||
// (glued to the tokens after it by error recovery). The fn-ref
|
||||
// scan skips the walked type, which captured its own (the TS
|
||||
// walked set).
|
||||
let defined_type = node
|
||||
.child_by_field_name("type")
|
||||
.filter(|t| self.walk_declared_types && is_class_like_definition(*t));
|
||||
if let Some(t) = defined_type {
|
||||
self.visit_node(t);
|
||||
}
|
||||
self.extract_variable(node);
|
||||
self.scan_fn_ref_subtree(node, 0);
|
||||
match defined_type {
|
||||
Some(t) => {
|
||||
self.maybe_capture_fn_refs(node);
|
||||
for i in 0..node.named_child_count() {
|
||||
if let Some(c) = node.named_child(i) {
|
||||
if c.id() != t.id() {
|
||||
self.scan_fn_ref_subtree(c, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => self.scan_fn_ref_subtree(node, 0),
|
||||
}
|
||||
skip_children = true;
|
||||
} else if self.variant == Variant::Cpp
|
||||
&& kind == "field_declaration"
|
||||
@@ -1204,13 +1249,19 @@ impl<'t> Walker<'t> {
|
||||
self.stack.pop();
|
||||
}
|
||||
|
||||
/// docstringFor a class-like node: the comment above the declaration it is
|
||||
/// defined in (the c/cpp getDeclarationWrapper), else above the node.
|
||||
fn class_like_docstring(&self, node: Node<'t>) -> Option<String> {
|
||||
preceding_docstring(defining_declaration(node).unwrap_or(node), self.src)
|
||||
}
|
||||
|
||||
/// extractClass for cpp class_specifier (skipBodilessClass, #1093).
|
||||
fn extract_class(&mut self, node: Node<'t>) {
|
||||
stack_guard!();
|
||||
let Some(body) = node.child_by_field_name("body") else { return };
|
||||
let name = self.extract_name(node);
|
||||
let extra = Extra {
|
||||
docstring: preceding_docstring(node, self.src),
|
||||
docstring: self.class_like_docstring(node),
|
||||
visibility: self.visibility_of(node),
|
||||
end: self.brace_body_end(body),
|
||||
..Extra::default()
|
||||
@@ -1234,7 +1285,7 @@ impl<'t> Walker<'t> {
|
||||
let Some(body) = node.child_by_field_name("body") else { return };
|
||||
let name = self.extract_name(node);
|
||||
let extra = Extra {
|
||||
docstring: preceding_docstring(node, self.src),
|
||||
docstring: self.class_like_docstring(node),
|
||||
visibility: if self.variant == Variant::Cpp { self.visibility_of(node) } else { None },
|
||||
end: self.brace_body_end(body),
|
||||
..Extra::default()
|
||||
@@ -1257,7 +1308,7 @@ impl<'t> Walker<'t> {
|
||||
let Some(body) = node.child_by_field_name("body") else { return };
|
||||
let name = self.extract_name(node);
|
||||
let extra = Extra {
|
||||
docstring: preceding_docstring(node, self.src),
|
||||
docstring: self.class_like_docstring(node),
|
||||
visibility: if self.variant == Variant::Cpp { self.visibility_of(node) } else { None },
|
||||
..Extra::default()
|
||||
};
|
||||
@@ -2296,7 +2347,7 @@ fn find_declarator_qualified_id(declarator: Node) -> Option<Node> {
|
||||
None
|
||||
}
|
||||
|
||||
/// cDeclaratorIdentifier (tree-sitter.ts:234): resolve the declared identifier
|
||||
/// cDeclaratorIdentifier (languages/c-cpp.ts): resolve the declared identifier
|
||||
/// through init/pointer/array/parenthesized declarator wrappers; a
|
||||
/// function_declarator means prototype/fn-ptr — null. (The C grammar's
|
||||
/// parenthesized_declarator exposes no `declarator` field, so that arm always
|
||||
@@ -2322,6 +2373,26 @@ fn c_declarator_identifier(node: Node) -> Option<Node> {
|
||||
None
|
||||
}
|
||||
|
||||
/// isClassLikeDefinition (languages/c-cpp.ts): a class/struct/union/enum
|
||||
/// specifier that carries a body defines the type; `struct Foo *p` and the
|
||||
/// forward declaration `class Foo;` only name it.
|
||||
fn is_class_like_definition(node: Node) -> bool {
|
||||
matches!(node.kind(), "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier")
|
||||
&& node.child_by_field_name("body").is_some()
|
||||
}
|
||||
|
||||
/// definingDeclaration (languages/c-cpp.ts): the `declaration` a class-like
|
||||
/// definition is the type of (`struct Foo { … } foo;`). Its comment documents
|
||||
/// the type, and an unnamed type takes the name of its first variable.
|
||||
fn defining_declaration(node: Node) -> Option<Node> {
|
||||
if !is_class_like_definition(node) {
|
||||
return None;
|
||||
}
|
||||
let parent = node.parent()?;
|
||||
let is_type = parent.child_by_field_name("type").map(|t| t.id()) == Some(node.id());
|
||||
(parent.kind() == "declaration" && is_type).then_some(parent)
|
||||
}
|
||||
|
||||
/// isMacroMisparsedTypeDecl (languages/c-cpp.ts:261): `class MACRO Name {…}`
|
||||
/// misparse residue — bodyless class/struct specifier in `type` + a
|
||||
/// non-function_declarator declarator.
|
||||
|
||||
Reference in New Issue
Block a user