Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions code_review_graph/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3978,6 +3978,45 @@ def _get_name(self, node, language: str, kind: str) -> Optional[str]:
result = self._get_name(child, language, kind)
if result:
return result
# C++: inside function_declarator, the name appears as
# qualified_identifier (Class::method), destructor_name (~Class),
# operator_name (operator==), or field_identifier. The generic
# loop below only recognizes 'identifier'/'type_identifier',
# so scoped method definitions would otherwise fall through and
# match the outer return-type type_identifier as the function name.
# Nested scopes (Outer::Inner::method) produce nested
# qualified_identifier nodes — peel until we find the leaf name.
if language == "cpp" and node.type == "function_declarator":
def _leaf_name(qi):
# Walk right-to-left: the rightmost identifier/
# destructor_name/operator_name is the method name.
# If the rightmost child is itself a qualified_identifier
# (nested scope), recurse into it.
for sub in reversed(qi.children):
if sub.type in (
"identifier",
"destructor_name",
"operator_name",
):
return sub.text.decode(
"utf-8", errors="replace")
if sub.type == "qualified_identifier":
inner = _leaf_name(sub)
if inner:
return inner
return None
for child in node.children:
if child.type == "qualified_identifier":
name = _leaf_name(child)
if name:
return name
if child.type in (
"field_identifier",
"destructor_name",
"operator_name",
):
return child.text.decode(
"utf-8", errors="replace")

# Objective-C method_definition: the method name is the first
# ``identifier`` child (first part of the selector). Multi-part
Expand Down
41 changes: 41 additions & 0 deletions tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1209,3 +1209,44 @@ def test_elixir_top_level_dotted_call_attributes_to_file(self):
and e.target.endswith("puts")
]
assert len(top_level) == 1

def test_cpp_scoped_method_names(self, tmp_path):
"""C++ scoped method definitions must extract the leaf method name,
not the return-type identifier.

Regression: previously ``Ret Class::method()`` indexed as ``Ret``
(return type) and ``void Class::method()`` was silently dropped
because _get_name() fell through to the generic identifier loop,
which did not recognise qualified_identifier, destructor_name, or
operator_name nodes inside function_declarator.
"""
src = b"""
void PlaybackExtension::resetStateForPool() {}
quint64 PlaybackExtension::startTimestamp() const { return 0; }
PlaybackExtension::~PlaybackExtension() {}
~PlaybackExtension() {}
bool operator==(const A& a, const B& b) { return true; }
bool MyClass::operator<(const MyClass& o) const { return true; }
void foo() {}
int SnapshotController::getHandleIndex() { return 0; }
bool PlaybackWidget::AllocateResourceStrategy::allocateExtensionResource(int i) { return true; }
void A::B::C::deep() {}
ExtensionID PlaybackExtension::ID() const { return {}; }
"""
p = tmp_path / "x.cpp"
p.write_bytes(src)
nodes, _ = self.parser.parse_file(p)
names = [n.name for n in nodes if n.kind == "Function"]
assert names == [
"resetStateForPool",
"startTimestamp",
"~PlaybackExtension",
"~PlaybackExtension",
"operator==",
"operator<",
"foo",
"getHandleIndex",
"allocateExtensionResource",
"deep",
"ID",
]