From b43f9a6d95f9fcbc0e1bc701598c4f7551fd7528 Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Tue, 12 May 2026 00:58:31 +0200 Subject: [PATCH] Look up operator overloads by CXXOperatorName in GetFunctionsUsingName GetFunctionsUsingName built its lookup key only as an identifier, so queries like "operator==" never matched: operator overloads live in the AST under CXXOperatorName, not under an Identifier. cppyy's namespace getattr fallback (Cppyy::GetMethodsFromName -> Cpp::GetFunctionsUsingName) therefore returned no candidates and getattr(ns, 'operator==') raised AttributeError. Detect names that start with "operator" followed by a non-identifier character and match the trimmed suffix against OperatorKinds.def. When it matches, build the DeclarationName via getCXXOperatorName(OO_...); otherwise fall back to the identifier lookup. Covers symbolic and multi-token overloads (==, +, (), [], new, delete, new[], delete[], ...). Names that merely start with "operator" (e.g. "operators_count") are left on the identifier path because the next character is an identifier-continue. Fixes https://github.com/compiler-research/cppyy/issues/219. --- lib/CppInterOp/CppInterOp.cpp | 23 ++++++++- .../CppInterOp/FunctionReflectionTest.cpp | 51 +++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/lib/CppInterOp/CppInterOp.cpp b/lib/CppInterOp/CppInterOp.cpp index 0c3fee7e4..6b9ec3fd5 100644 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -1484,9 +1484,28 @@ std::vector GetFunctionsUsingName(TCppScope_t scope, D = GetUnderlyingScope(D); std::vector funcs; - llvm::StringRef Name(name); auto& S = getSema(); - DeclarationName DName = &getASTContext().Idents.get(name); + auto& Ctx = getASTContext(); + DeclarationName DName; + + // Handle operator overloads (e.g. "operator==", "operator new"): these are + // stored under CXXOperatorName declaration names, not as identifiers. + static constexpr llvm::StringRef OperatorPrefix("operator"); + llvm::StringRef NameRef(name); + if (NameRef.consume_front(OperatorPrefix) && !NameRef.empty() && + !clang::isAsciiIdentifierContinue( + static_cast(NameRef.front()))) { + llvm::StringRef Spelling = NameRef.trim(); +#define OVERLOADED_OPERATOR(OpName, OpSpelling, Token, Unary, Binary, \ + MemberOnly) \ + if (Spelling == (OpSpelling)) \ + DName = Ctx.DeclarationNames.getCXXOperatorName(clang::OO_##OpName); +#include "clang/Basic/OperatorKinds.def" + } + + if (!DName) + DName = &Ctx.Idents.get(name); + clang::LookupResult R(S, DName, SourceLocation(), Sema::LookupOrdinaryName, RedeclarationKind::ForVisibleRedeclaration); diff --git a/unittests/CppInterOp/FunctionReflectionTest.cpp b/unittests/CppInterOp/FunctionReflectionTest.cpp index c67c4ee46..4f8aea5d1 100644 --- a/unittests/CppInterOp/FunctionReflectionTest.cpp +++ b/unittests/CppInterOp/FunctionReflectionTest.cpp @@ -323,6 +323,57 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionsUsingName) { EXPECT_EQ(get_number_of_funcs_using_name(Decls[2], ""), 0); } +TYPED_TEST(CPPINTEROP_TEST_MODE, + FunctionReflection_GetFunctionsUsingNameOperators) { + std::vector Decls; + std::string code = R"( + namespace ComparableSpace { + class NSComparable {}; + bool operator==(const NSComparable&, const NSComparable&) { return true; } + bool operator==(const NSComparable&, int) { return false; } + bool operator!=(const NSComparable&, const NSComparable&) { return false; } + int operators_count() { return 1; } + } + + struct WithOps { + bool operator==(const WithOps&) const { return true; } + WithOps& operator+=(int) { return *this; } + int operator[](int) const { return 0; } + int operator()(int, int) const { return 0; } + }; + )"; + + GetAllTopLevelDecls(code, Decls); + + auto count = [](Cpp::TCppScope_t scope, const std::string& name) { + return Cpp::GetFunctionsUsingName(scope, name).size(); + }; + + // Namespace-scope operator overloads are now findable by name. + EXPECT_EQ(count(Decls[0], "operator=="), 2); + EXPECT_EQ(count(Decls[0], "operator!="), 1); + + // Non-existent operator at this scope. + EXPECT_EQ(count(Decls[0], "operator+"), 0); + + // Identifiers that merely start with "operator" must still resolve via the + // identifier lookup path, not be misread as an operator. + EXPECT_EQ(count(Decls[0], "operators_count"), 1); + + // Class-scope operators, including multi-token "()" and "[]". + EXPECT_EQ(count(Decls[1], "operator=="), 1); + EXPECT_EQ(count(Decls[1], "operator+="), 1); + EXPECT_EQ(count(Decls[1], "operator[]"), 1); + EXPECT_EQ(count(Decls[1], "operator()"), 1); + EXPECT_EQ(count(Decls[1], "operator!="), 0); + + // Sanity-check that the returned decls are the operator overloads. + auto eqs = Cpp::GetFunctionsUsingName(Decls[0], "operator=="); + ASSERT_EQ(eqs.size(), 2u); + for (auto* f : eqs) + EXPECT_EQ(Cpp::GetName(f), "operator=="); +} + TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetClassDecls) { std::vector Decls, SubDecls; std::string code = R"(