fix(cpp): a nested class defined out of line is indexed under its name, and a construction makes the class C++ finds (#2542)

A class, struct or union whose declaration writes its name in a scope —
leveldb's `struct SkipList<Key, Comparator>::Node { … }`, rocksdb's
`struct BlockBasedTable::Rep { … }` — was indexed as `<anonymous>`, so its
members belonged to no class anyone names and a call on a `Node*` or `Rep*`
was guessed by the method's name. Such a class now takes its own name,
qualified under the written scope the way an out-of-line method is, and its
members are qualified under it, in the TS extractor and the kernel alike. A
specialization (`struct fmt::formatter<Answer>`) stays unnamed: named after
its template, it stood in for the template and every other specialization.

Constructions are recorded by the type's last name, so with `DBImpl::Writer`
named, leveldb's `new log::Writer(…)` in `DBImpl` methods went to it. A C++
construction now makes the class C++ lookup finds for the type as written at
the construction (matchCppConstruction): read with its template arguments
(`new Outer<int>::Inner(…)` makes an `Outer::Inner`), at 0.9, or 0.7 when it
is only the class of that name the file can see; an enum lookup finds; through
an alias the calling function declares, the class it names; and nothing for a
template parameter, a member of a type from outside the project or a local
alias of one. Of several classes or aliases the file declares under one
qualified name (the local classes of separate gtest TEST_F bodies, the members
of unnamed specializations), the one declared around the construction. A `<`
or `>` in parentheses inside template arguments is now read as a comparison.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-10-13 17:47:02 +00:00
committed by GitHub
parent a6ceb0472c
commit 34dd3d2fb4
9 changed files with 814 additions and 12 deletions
+74 -9
View File
@@ -301,6 +301,10 @@ struct Scope {
row: u32,
kind: &'static str,
name: String,
/// The qualified name its members are qualified under in place of the
/// scopes around it: a class written in a scope (objectPathOwners in
/// tree-sitter.ts, written_scope_qualified_name).
qualified: Option<String>,
}
#[derive(Default)]
@@ -464,7 +468,7 @@ pub fn extract(file_path: &str, source: &str, language: &str) -> Result<EmitOut,
});
w.nodes_meta.push(NodeMeta { kind: "file", name: base_name.to_string() });
w.node_ids.push(ids::file_node_id(file_path));
w.stack.push(Scope { row: 0, kind: "file", name: base_name.to_string() });
w.stack.push(Scope { row: 0, kind: "file", name: base_name.to_string(), qualified: None });
w.visit_node(tree.root_node());
w.flush_fn_ref_candidates();
@@ -541,7 +545,10 @@ impl<'t> Walker<'t> {
let qualified = extra.qualified_name.unwrap_or_else(|| {
let mut parts: Vec<&str> = self.namespace_prefix.iter().map(|s| s.as_str()).collect();
for s in &self.stack {
if s.kind != "file" {
if let Some(q) = &s.qualified {
parts.clear();
parts.push(q);
} else if s.kind != "file" {
parts.push(&s.name);
}
}
@@ -648,6 +655,11 @@ impl<'t> Walker<'t> {
if let Some(name) = self.unnamed_type_variable_name(node) {
return name;
}
if self.variant == Variant::Cpp {
if let Some(mut parts) = self.cpp_written_class_parts(node) {
return parts.pop().unwrap_or_default();
}
}
if let Some(name_node) = node.child_by_field_name("declarator") {
let mut resolved = name_node;
// Unwrap pointer/reference declarators (`int* f()`, `T& f()`).
@@ -688,6 +700,55 @@ impl<'t> Walker<'t> {
"<anonymous>".to_string()
}
/// cppWrittenClassParts (languages/c-cpp.ts): the `::` segments of a class,
/// struct or union name written with a scope, the scope's template
/// arguments dropped (`["SkipList", "Node"]` for `struct SkipList<Key,
/// Comparator>::Node`). None for a plain name and for a specialization,
/// whose innermost name has template arguments.
fn cpp_written_class_parts(&self, node: Node) -> Option<Vec<String>> {
if !matches!(node.kind(), "class_specifier" | "struct_specifier" | "union_specifier") {
return None;
}
let written = node.child_by_field_name("name")?;
if written.kind() != "qualified_identifier" {
return None;
}
let mut innermost = Some(written);
while let Some(n) = innermost.filter(|n| n.kind() == "qualified_identifier") {
innermost = n.child_by_field_name("name");
}
if innermost.map(|n| n.kind()) != Some("type_identifier") {
return None;
}
let parts: Vec<String> = strip_cpp_template_args(self.text(written))
.split("::")
.map(|p| p.trim().to_string())
.filter(|p| !p.is_empty())
.collect();
let identifier = |p: &String| {
let mut chars = p.chars();
chars.next().is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
&& chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
};
if parts.is_empty() || !parts.iter().all(identifier) {
return None;
}
Some(parts)
}
/// writtenScopeQualifiedName (tree-sitter.ts): a class whose name is
/// written in a scope is qualified like an out-of-line method under it.
fn written_scope_qualified_name(&self, node: Node, name: &str) -> Option<String> {
if self.variant != Variant::Cpp {
return None;
}
let parts = self.cpp_written_class_parts(node)?;
if parts.len() < 2 {
return None;
}
Some(self.compose_receiver_qualified_name(&parts[..parts.len() - 1].join("::"), name))
}
/// 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`).
@@ -1172,7 +1233,7 @@ impl<'t> Walker<'t> {
// for c/cpp: not in TYPE_ANNOTATION_LANGUAGES, and the decorator node
// kinds never appear as direct children/preceding siblings in these
// grammars — `attribute` only occurs under attribute_declaration.)
self.stack.push(Scope { row, kind: "function", name });
self.stack.push(Scope { row, kind: "function", name, qualified: None });
if let Some(body) = node.child_by_field_name("body") {
self.visit_function_body(body);
}
@@ -1242,7 +1303,7 @@ impl<'t> Walker<'t> {
}
}
self.stack.push(Scope { row, kind: "method", name });
self.stack.push(Scope { row, kind: "method", name, qualified: None });
if let Some(body) = node.child_by_field_name("body") {
self.visit_function_body(body);
}
@@ -1260,14 +1321,16 @@ impl<'t> Walker<'t> {
stack_guard!();
let Some(body) = node.child_by_field_name("body") else { return };
let name = self.extract_name(node);
let scoped = self.written_scope_qualified_name(node, &name);
let extra = Extra {
docstring: self.class_like_docstring(node),
visibility: self.visibility_of(node),
end: self.brace_body_end(body),
qualified_name: scoped.clone(),
..Extra::default()
};
let Some(row) = self.create_node("class", &name, node, extra) else { return };
let scope = Scope { row, kind: "class", name };
let scope = Scope { row, kind: "class", name, qualified: scoped };
self.open_class_scope(&scope, body);
self.extract_inheritance(node, row);
self.stack.push(scope);
@@ -1284,14 +1347,16 @@ impl<'t> Walker<'t> {
stack_guard!();
let Some(body) = node.child_by_field_name("body") else { return };
let name = self.extract_name(node);
let scoped = self.written_scope_qualified_name(node, &name);
let extra = Extra {
docstring: self.class_like_docstring(node),
visibility: if self.variant == Variant::Cpp { self.visibility_of(node) } else { None },
end: self.brace_body_end(body),
qualified_name: scoped.clone(),
..Extra::default()
};
let Some(row) = self.create_node(kind, &name, node, extra) else { return };
let scope = Scope { row, kind, name };
let scope = Scope { row, kind, name, qualified: scoped };
self.open_class_scope(&scope, body);
self.extract_inheritance(node, row);
self.stack.push(scope);
@@ -1314,7 +1379,7 @@ impl<'t> Walker<'t> {
};
let Some(row) = self.create_node("enum", &name, node, extra) else { return };
self.extract_inheritance(node, row);
self.stack.push(Scope { row, kind: "enum", name });
self.stack.push(Scope { row, kind: "enum", name, qualified: None });
for i in 0..body.named_child_count() {
let Some(child) = body.named_child(i) else { continue };
if child.kind() == "enumerator" {
@@ -1375,7 +1440,7 @@ impl<'t> Walker<'t> {
) else {
return true;
};
self.stack.push(Scope { row, kind, name });
self.stack.push(Scope { row, kind, name, qualified: None });
let type_child = node
.child_by_field_name("type")
.or_else(|| self.find_child_by_kind(node, "struct_specifier"))
@@ -1402,7 +1467,7 @@ impl<'t> Walker<'t> {
) else {
return true;
};
self.stack.push(Scope { row, kind: "enum", name });
self.stack.push(Scope { row, kind: "enum", name, qualified: None });
if let Some(inner) = self.find_child_by_kind(node, "enum_specifier") {
self.extract_inheritance(inner, row);
if let Some(body) = inner.child_by_field_name("body") {