https://github.com/dmaclach updated https://github.com/llvm/llvm-project/pull/213030
>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 1/2] [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]"); +} >From 10ad68cb625949ae5cf16903e1d70b14c1dc06f4 Mon Sep 17 00:00:00 2001 From: Dave MacLachlan <[email protected]> Date: Thu, 30 Jul 2026 07:52:12 -0700 Subject: [PATCH 2/2] Clean up some tests with new diagnostics format. --- .../WebKit/unretained-call-args-member.mm | 6 +++--- .../Checkers/WebKit/unretained-call-args.mm | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-call-args-member.mm b/clang/test/Analysis/Checkers/WebKit/unretained-call-args-member.mm index fc9e9b421ae0e..d680b2e348670 100644 --- a/clang/test/Analysis/Checkers/WebKit/unretained-call-args-member.mm +++ b/clang/test/Analysis/Checkers/WebKit/unretained-call-args-member.mm @@ -20,7 +20,7 @@ void Foo::bar() { [m_constObj doWork]; // no-warning - [m_obj doWork]; // expected-warning{{Receiver 'this->m_obj' (to 'SomeObj::doWork') is a raw pointer to RetainPtr-capable type 'SomeObj'}} + [m_obj doWork]; // expected-warning{{Receiver 'this->m_obj' (to '-[SomeObj doWork]') is a raw pointer to RetainPtr-capable type 'SomeObj'}} } } // namespace call_args_const_retainptr_member @@ -56,7 +56,7 @@ void Bar::baz() { [m_constObj doWork]; // no-warning - [m_obj doWork]; // expected-warning{{Receiver 'this->m_obj' (to 'SomeObj::doWork') is a raw pointer to RetainPtr-capable type 'SomeObj'}} + [m_obj doWork]; // expected-warning{{Receiver 'this->m_obj' (to '-[SomeObj doWork]') is a raw pointer to RetainPtr-capable type 'SomeObj'}} } } // namespace call_args_const_retainptr_struct_member @@ -149,7 +149,7 @@ void Foo::bar() { [m_constObj doWork]; // no-warning - [m_obj doWork]; // expected-warning{{Receiver 'this->m_obj' (to 'SomeObj::doWork') is a raw pointer to RetainPtr-capable type 'SomeObj'}} + [m_obj doWork]; // expected-warning{{Receiver 'this->m_obj' (to '-[SomeObj doWork]') is a raw pointer to RetainPtr-capable type 'SomeObj'}} } } // namespace call_args_const_osobjectptr_receiver diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm b/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm index 007cf5571f9f6..037bc1571bbef 100644 --- a/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm +++ b/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm @@ -216,7 +216,7 @@ void foo(CFMutableArrayRef param) { consume_more_cf(param); } } - + namespace os_obj { void foo(dispatch_queue_t param) { consume_more_dispatch(param); @@ -281,7 +281,7 @@ void foo() { void foo() { [provide() doWork]; - // expected-warning@-1{{Receiver 'provide()' (to 'SomeObj::doWork') is a raw pointer to RetainPtr-capable type 'SomeObj'}} + // expected-warning@-1{{Receiver 'provide()' (to '-[SomeObj doWork]') is a raw pointer to RetainPtr-capable type 'SomeObj'}} [protectedProvide().get() doWork]; CFArrayAppendValue(provide_cf(), nullptr); @@ -291,7 +291,7 @@ void foo() { void bar() { [downcast<OtherObj>(protectedProvide().get()) doMoreWork:downcast<OtherObj>(provide())]; - // expected-warning@-1{{Function argument 'downcast<OtherObj>(provide())' (parameter 'other' to 'OtherObj::doMoreWork:') is a raw pointer to RetainPtr-capable type 'OtherObj'}} + // expected-warning@-1{{Function argument 'downcast<OtherObj>(provide())' (parameter 'other' to '-[OtherObj doMoreWork:]') is a raw pointer to RetainPtr-capable type 'OtherObj'}} [protectedProvide().get() doWork]; }; @@ -541,7 +541,7 @@ bool foo(NSString *obj) { bool bar(NSObject *obj, Bar *bar, SomeObjectSingleton someObjSingleton) { return [obj isKindOfClass:(bar->*someObjSingleton)()]; - // expected-warning@-1{{Function argument '(bar ->* someObjSingleton)()' (parameter 'aClass' to 'NSObject::isKindOfClass:') is a RetainPtr-capable type 'Class'}} + // expected-warning@-1{{Function argument '(bar ->* someObjSingleton)()' (parameter 'aClass' to '-[NSObject isKindOfClass:]') is a RetainPtr-capable type 'Class'}} } bool baz(NSObject *obj) { @@ -656,9 +656,9 @@ - (void)doWork:(NSString *)msg, ... { - (void)doWorkOnSelf { [self doWork:nil]; [self doWork:@"hello", provide(), provide_cf(), provide_dispatch()]; - // expected-warning@-1{{Function argument 'provide()' (to 'TestObject::doWork:') is a raw pointer to RetainPtr-capable type 'SomeObj'}} - // expected-warning@-2{{Function argument 'provide_cf()' (to 'TestObject::doWork:') is a RetainPtr-capable type 'CFMutableArrayRef'}} - // expected-warning@-3{{Function argument 'provide_dispatch()' (to 'TestObject::doWork:') is a RetainPtr-capable type 'dispatch_queue_t'}} + // expected-warning@-1{{Function argument 'provide()' (to '-[TestObject doWork:]') is a raw pointer to RetainPtr-capable type 'SomeObj'}} + // expected-warning@-2{{Function argument 'provide_cf()' (to '-[TestObject doWork:]') is a RetainPtr-capable type 'CFMutableArrayRef'}} + // expected-warning@-3{{Function argument 'provide_dispatch()' (to '-[TestObject doWork:]') is a RetainPtr-capable type 'dispatch_queue_t'}} [self doWork:@"hello", RetainPtr<SomeObj> { provide() }.get(), RetainPtr<CFMutableArrayRef> { provide_cf() }.get(), OSObjectPtr { provide_dispatch() }.get()]; [self doWork:__null]; [self doWork:nil]; @@ -681,7 +681,7 @@ + (SomeObj *)sharedObj - (void)doWorkOnSomeObj { [[self getSomeObj] doWork]; - // expected-warning@-1{{Receiver '[self getSomeObj]' (to 'SomeObj::doWork') is a raw pointer to RetainPtr-capable type 'SomeObj'}} + // expected-warning@-1{{Receiver '[self getSomeObj]' (to '-[SomeObj doWork]') is a raw pointer to RetainPtr-capable type 'SomeObj'}} [[TestObject sharedObj] doWork]; } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
