[clang] [clang][ast][objc] Implement getNameForDiagnostic for ObjCMethodDecl and ObjCPropertyDecl. (PR #213030)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Jul 30 07:52:48 PDT 2026
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 <dmaclach at gmail.com>
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 <dmaclach at gmail.com>
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 at -1{{Receiver 'provide()' (to 'SomeObj::doWork') is a raw pointer to RetainPtr-capable type 'SomeObj'}}
+ // expected-warning at -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 at -1{{Function argument 'downcast<OtherObj>(provide())' (parameter 'other' to 'OtherObj::doMoreWork:') is a raw pointer to RetainPtr-capable type 'OtherObj'}}
+ // expected-warning at -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 at -1{{Function argument '(bar ->* someObjSingleton)()' (parameter 'aClass' to 'NSObject::isKindOfClass:') is a RetainPtr-capable type 'Class'}}
+ // expected-warning at -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 at -1{{Function argument 'provide()' (to 'TestObject::doWork:') is a raw pointer to RetainPtr-capable type 'SomeObj'}}
- // expected-warning at -2{{Function argument 'provide_cf()' (to 'TestObject::doWork:') is a RetainPtr-capable type 'CFMutableArrayRef'}}
- // expected-warning at -3{{Function argument 'provide_dispatch()' (to 'TestObject::doWork:') is a RetainPtr-capable type 'dispatch_queue_t'}}
+ // expected-warning at -1{{Function argument 'provide()' (to '-[TestObject doWork:]') is a raw pointer to RetainPtr-capable type 'SomeObj'}}
+ // expected-warning at -2{{Function argument 'provide_cf()' (to '-[TestObject doWork:]') is a RetainPtr-capable type 'CFMutableArrayRef'}}
+ // expected-warning at -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 at -1{{Receiver '[self getSomeObj]' (to 'SomeObj::doWork') is a raw pointer to RetainPtr-capable type 'SomeObj'}}
+ // expected-warning at -1{{Receiver '[self getSomeObj]' (to '-[SomeObj doWork]') is a raw pointer to RetainPtr-capable type 'SomeObj'}}
[[TestObject sharedObj] doWork];
}
More information about the cfe-commits
mailing list