https://github.com/dmaclach created https://github.com/llvm/llvm-project/pull/213030
This overrides getNameForDiagnostic to provide a qualified name representation for Objective-C methods and properties in diagnostics. When qualified is true, it formats them using the standard Objective-C syntax, such as -[Class selector] or +[Class property]. Otherwise, it falls back to printName. Note that I avoided modifying NamedDecl::getQualifiedNameAsString() or printQualifiedName() which will continue to (unfortunately) return `Class::method`, but this is intentional to attempt to avoid any breakage downstream due to output changing. My understanding is that improving diagnostic messages is encouraged, and maintenance of any downstream tooling depending on them for regex parsing is up to the developer of such tooling :) >From ea23565c8b2ccf6d1e4df86cdbda970376242055 Mon Sep 17 00:00:00 2001 From: Dave MacLachlan <[email protected]> Date: Thu, 30 Jul 2026 07:17:19 -0700 Subject: [PATCH] [clang][ast][objc] Implement getNameForDiagnostic for ObjCMethodDecl and ObjCPropertyDecl. This overrides getNameForDiagnostic to provide a qualified name representation for Objective-C methods and properties in diagnostics. When qualified is true, it formats them using the standard Objective-C syntax, such as -[Class selector] or +[Class property]. Otherwise, it falls back to printName. --- clang/include/clang/AST/DeclObjC.h | 6 + clang/lib/AST/DeclObjC.cpp | 45 +++++++ clang/unittests/AST/DeclTest.cpp | 186 ++++++++++++++++++++++++++++- 3 files changed, 236 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/AST/DeclObjC.h b/clang/include/clang/AST/DeclObjC.h index 2541edba83855..6b9aac57e6a50 100644 --- a/clang/include/clang/AST/DeclObjC.h +++ b/clang/include/clang/AST/DeclObjC.h @@ -247,6 +247,9 @@ class ObjCMethodDecl : public NamedDecl, public DeclContext { return static_cast<ObjCDeclQualifier>(ObjCMethodDeclBits.objcDeclQualifier); } + void getNameForDiagnostic(raw_ostream &OS, const PrintingPolicy &Policy, + bool Qualified) const override; + void setObjCDeclQualifier(ObjCDeclQualifier QV) { ObjCMethodDeclBits.objcDeclQualifier = QV; } @@ -793,6 +796,9 @@ class ObjCPropertyDecl : public NamedDecl { static ObjCPropertyDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID); + void getNameForDiagnostic(raw_ostream &OS, const PrintingPolicy &Policy, + bool Qualified) const override; + SourceLocation getAtLoc() const { return AtLoc; } void setAtLoc(SourceLocation L) { AtLoc = L; } diff --git a/clang/lib/AST/DeclObjC.cpp b/clang/lib/AST/DeclObjC.cpp index 5f80bf7aa29d8..795f97ec12f66 100644 --- a/clang/lib/AST/DeclObjC.cpp +++ b/clang/lib/AST/DeclObjC.cpp @@ -865,6 +865,24 @@ ObjCMethodDecl *ObjCMethodDecl::CreateDeserialized(ASTContext &C, Selector(), QualType(), nullptr, nullptr); } +void ObjCMethodDecl::getNameForDiagnostic(raw_ostream &OS, + const PrintingPolicy &Policy, + bool Qualified) const { + if (Qualified) { + OS << (isInstanceMethod() ? '-' : '+'); + OS << '['; + if (const auto *ID = getClassInterface()) + OS << ID->getName(); + else if (const auto *PD = dyn_cast<ObjCProtocolDecl>(getDeclContext())) + OS << PD->getName(); + else + OS << "<Unknown>"; + OS << ' ' << getSelector() << ']'; + } else { + printName(OS, Policy); + } +} + bool ObjCMethodDecl::isDirectMethod() const { return hasAttr<ObjCDirectAttr>() && !getASTContext().getLangOpts().ObjCDisableDirectMethodsForTesting; @@ -2365,6 +2383,33 @@ ObjCPropertyDecl *ObjCPropertyDecl::CreateDeserialized(ASTContext &C, QualType(), nullptr, None); } +void ObjCPropertyDecl::getNameForDiagnostic(raw_ostream &OS, + const PrintingPolicy &Policy, + bool Qualified) const { + if (Qualified) { + OS << (isInstanceProperty() ? '-' : '+'); + OS << '['; + const ObjCContainerDecl *Parent = nullptr; + if (const auto *MD = getGetterMethodDecl()) { + Parent = MD->getClassInterface(); + if (!Parent) + Parent = dyn_cast<ObjCProtocolDecl>(MD->getDeclContext()); + } + if (!Parent) { + Parent = dyn_cast<ObjCContainerDecl>(getDeclContext()); + } + + if (Parent) + OS << Parent->getName(); + else + OS << "<Unknown>"; + + OS << ' ' << getName() << ']'; + } else { + printName(OS, Policy); + } +} + QualType ObjCPropertyDecl::getUsageType(QualType objectType) const { return DeclType.substObjCMemberType(objectType, getDeclContext(), ObjCSubstitutionContext::Property); diff --git a/clang/unittests/AST/DeclTest.cpp b/clang/unittests/AST/DeclTest.cpp index b95d361896e21..4919977300fb8 100644 --- a/clang/unittests/AST/DeclTest.cpp +++ b/clang/unittests/AST/DeclTest.cpp @@ -13,19 +13,25 @@ #include "clang/AST/Decl.h" #include "clang/AST/ASTContext.h" #include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclObjC.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/Mangle.h" +#include "clang/AST/TypeBase.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Basic/ABI.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/LLVM.h" +#include "clang/Basic/Linkage.h" #include "clang/Basic/TargetInfo.h" #include "clang/Lex/Lexer.h" #include "clang/Tooling/Tooling.h" -#include "llvm/IR/DataLayout.h" +#include "llvm/Support/raw_ostream.h" #include "llvm/Testing/Annotations/Annotations.h" #include "gtest/gtest.h" +#include <cassert> +#include <memory> +#include <string> using namespace clang::ast_matchers; using namespace clang::tooling; @@ -725,3 +731,181 @@ TEST(Decl, NoWrittenArgsInImplicitlyInstantiatedVarSpec) { ASSERT_NE(VTSD, nullptr); EXPECT_EQ(VTSD->getTemplateArgsAsWritten(), nullptr); } + +TEST(Decl, ObjCMethodDeclNameForDiagnostic) { + const char *Code = R"objc( + @protocol MyProtocol + - (void)myProtocolMethod; + @end + + @interface MyClass + - (void)myMethod:(int)x; + + (void)myClassMethod; + @end + + @interface MyClass (MyCategory) + - (void)myCategoryMethod; + @end + + @interface MyClass () + - (void)myExtensionMethod; + @end + )objc"; + + auto AST = tooling::buildASTFromCodeWithArgs(Code, {"-x", "objective-c"}); + ASTContext &Ctx = AST->getASTContext(); + + auto const *IM = selectFirst<ObjCMethodDecl>( + "im", match(objcMethodDecl(hasName("myMethod:")).bind("im"), Ctx)); + ASSERT_NE(IM, nullptr); + + std::string IMQualifiedName; + llvm::raw_string_ostream IMQualifiedOS(IMQualifiedName); + IM->getNameForDiagnostic(IMQualifiedOS, Ctx.getPrintingPolicy(), + /*Qualified=*/true); + EXPECT_EQ(IMQualifiedOS.str(), "-[MyClass myMethod:]"); + + std::string IMUnqualifiedName; + llvm::raw_string_ostream IMUnqualifiedOS(IMUnqualifiedName); + IM->getNameForDiagnostic(IMUnqualifiedOS, Ctx.getPrintingPolicy(), + /*Qualified=*/false); + EXPECT_EQ(IMUnqualifiedOS.str(), "myMethod:"); + + auto const *CM = selectFirst<ObjCMethodDecl>( + "cm", match(objcMethodDecl(hasName("myClassMethod")).bind("cm"), Ctx)); + ASSERT_NE(CM, nullptr); + + std::string CMQualifiedName; + llvm::raw_string_ostream CMQualifiedOS(CMQualifiedName); + CM->getNameForDiagnostic(CMQualifiedOS, Ctx.getPrintingPolicy(), + /*Qualified=*/true); + EXPECT_EQ(CMQualifiedOS.str(), "+[MyClass myClassMethod]"); + + std::string CMUnqualifiedName; + llvm::raw_string_ostream CMUnqualifiedOS(CMUnqualifiedName); + CM->getNameForDiagnostic(CMUnqualifiedOS, Ctx.getPrintingPolicy(), + /*Qualified=*/false); + EXPECT_EQ(CMUnqualifiedOS.str(), "myClassMethod"); + + auto const *PM = selectFirst<ObjCMethodDecl>( + "pm", match(objcMethodDecl(hasName("myProtocolMethod")).bind("pm"), Ctx)); + ASSERT_NE(PM, nullptr); + + std::string PMQualifiedName; + llvm::raw_string_ostream PMQualifiedOS(PMQualifiedName); + PM->getNameForDiagnostic(PMQualifiedOS, Ctx.getPrintingPolicy(), + /*Qualified=*/true); + EXPECT_EQ(PMQualifiedOS.str(), "-[MyProtocol myProtocolMethod]"); + + auto const *CatM = selectFirst<ObjCMethodDecl>( + "catm", + match(objcMethodDecl(hasName("myCategoryMethod")).bind("catm"), Ctx)); + ASSERT_NE(CatM, nullptr); + + std::string CatMQualifiedName; + llvm::raw_string_ostream CatMQualifiedOS(CatMQualifiedName); + CatM->getNameForDiagnostic(CatMQualifiedOS, Ctx.getPrintingPolicy(), + /*Qualified=*/true); + EXPECT_EQ(CatMQualifiedOS.str(), "-[MyClass myCategoryMethod]"); + + auto const *ExtM = selectFirst<ObjCMethodDecl>( + "extm", + match(objcMethodDecl(hasName("myExtensionMethod")).bind("extm"), Ctx)); + ASSERT_NE(ExtM, nullptr); + + std::string ExtMQualifiedName; + llvm::raw_string_ostream ExtMQualifiedOS(ExtMQualifiedName); + ExtM->getNameForDiagnostic(ExtMQualifiedOS, Ctx.getPrintingPolicy(), + /*Qualified=*/true); + EXPECT_EQ(ExtMQualifiedOS.str(), "-[MyClass myExtensionMethod]"); +} + +TEST(Decl, ObjCPropertyDeclNameForDiagnostic) { + const char *Code = R"objc( + @protocol MyProtocol + @property int myProtocolProp; + @end + + @interface MyClass + @property int myProp; + @property(class) int myClassProp; + @end + + @interface MyClass (MyCategory) + @property int myCategoryProp; + @end + + @interface MyClass () + @property int extensionProp; + @end + )objc"; + + auto AST = tooling::buildASTFromCodeWithArgs(Code, {"-x", "objective-c"}); + ASTContext &Ctx = AST->getASTContext(); + + auto const *P = selectFirst<ObjCPropertyDecl>( + "p", match(objcPropertyDecl(hasName("myProp")).bind("p"), Ctx)); + ASSERT_NE(P, nullptr); + + std::string PQualifiedName; + llvm::raw_string_ostream PQualifiedOS(PQualifiedName); + P->getNameForDiagnostic(PQualifiedOS, Ctx.getPrintingPolicy(), + /*Qualified=*/true); + EXPECT_EQ(PQualifiedOS.str(), "-[MyClass myProp]"); + + std::string PUnqualifiedName; + llvm::raw_string_ostream PUnqualifiedOS(PUnqualifiedName); + P->getNameForDiagnostic(PUnqualifiedOS, Ctx.getPrintingPolicy(), + /*Qualified=*/false); + EXPECT_EQ(PUnqualifiedOS.str(), "myProp"); + + auto const *CP = selectFirst<ObjCPropertyDecl>( + "cp", match(objcPropertyDecl(hasName("myClassProp")).bind("cp"), Ctx)); + ASSERT_NE(CP, nullptr); + + std::string CPQualifiedName; + llvm::raw_string_ostream CPQualifiedOS(CPQualifiedName); + CP->getNameForDiagnostic(CPQualifiedOS, Ctx.getPrintingPolicy(), + /*Qualified=*/true); + EXPECT_EQ(CPQualifiedOS.str(), "+[MyClass myClassProp]"); + + std::string CPUnqualifiedName; + llvm::raw_string_ostream CPUnqualifiedOS(CPUnqualifiedName); + CP->getNameForDiagnostic(CPUnqualifiedOS, Ctx.getPrintingPolicy(), + /*Qualified=*/false); + EXPECT_EQ(CPUnqualifiedOS.str(), "myClassProp"); + + auto const *PP = selectFirst<ObjCPropertyDecl>( + "pp", match(objcPropertyDecl(hasName("myProtocolProp")).bind("pp"), Ctx)); + ASSERT_NE(PP, nullptr); + + std::string PPQualifiedName; + llvm::raw_string_ostream PPQualifiedOS(PPQualifiedName); + PP->getNameForDiagnostic(PPQualifiedOS, Ctx.getPrintingPolicy(), + /*Qualified=*/true); + EXPECT_EQ(PPQualifiedOS.str(), "-[MyProtocol myProtocolProp]"); + + auto const *CatP = selectFirst<ObjCPropertyDecl>( + "catp", + match(objcPropertyDecl(hasName("myCategoryProp")).bind("catp"), Ctx)); + ASSERT_NE(CatP, nullptr); + + std::string CatPQualifiedName; + llvm::raw_string_ostream CatPQualifiedOS(CatPQualifiedName); + CatP->getNameForDiagnostic(CatPQualifiedOS, Ctx.getPrintingPolicy(), + /*Qualified=*/true); + // We expect MyClass if getter is available, or if fallback looks through + // categories. Let's see what happens. + EXPECT_EQ(CatPQualifiedOS.str(), "-[MyClass myCategoryProp]"); + + auto const *ExtP = selectFirst<ObjCPropertyDecl>( + "extp", + match(objcPropertyDecl(hasName("extensionProp")).bind("extp"), Ctx)); + ASSERT_NE(ExtP, nullptr); + + std::string ExtPQualifiedName; + llvm::raw_string_ostream ExtPQualifiedOS(ExtPQualifiedName); + ExtP->getNameForDiagnostic(ExtPQualifiedOS, Ctx.getPrintingPolicy(), + /*Qualified=*/true); + EXPECT_EQ(ExtPQualifiedOS.str(), "-[MyClass extensionProp]"); +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
