[clang] 748828c - [clang][ast][objc] Implement getNameForDiagnostic for ObjC{Method,Property}Decl (#213030)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Aug 3 00:22:34 PDT 2026
Author: dmaclach
Date: 2026-08-03T08:22:30+01:00
New Revision: 748828c3dbeae182d50de46b8fb7cb9c1076aaa0
URL: https://github.com/llvm/llvm-project/commit/748828c3dbeae182d50de46b8fb7cb9c1076aaa0
DIFF: https://github.com/llvm/llvm-project/commit/748828c3dbeae182d50de46b8fb7cb9c1076aaa0.diff
LOG: [clang][ast][objc] Implement getNameForDiagnostic for ObjC{Method,Property}Decl (#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]`. Previously
these would be `Class::selector` or `Class::property`. If qualified is
false, 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.
Added:
Modified:
clang/include/clang/AST/DeclObjC.h
clang/lib/AST/DeclObjC.cpp
clang/test/Analysis/Checkers/WebKit/unretained-call-args-member.mm
clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm
clang/unittests/AST/DeclTest.cpp
Removed:
################################################################################
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..1adf6c9e048a9 100644
--- a/clang/lib/AST/DeclObjC.cpp
+++ b/clang/lib/AST/DeclObjC.cpp
@@ -865,6 +865,27 @@ ObjCMethodDecl *ObjCMethodDecl::CreateDeserialized(ASTContext &C,
Selector(), QualType(), nullptr, nullptr);
}
+void ObjCMethodDecl::getNameForDiagnostic(raw_ostream &OS,
+ const PrintingPolicy &Policy,
+ bool Qualified) const {
+ if (!Qualified) {
+ printName(OS, Policy);
+ return;
+ }
+
+ OS << (isInstanceMethod() ? '-' : '+');
+ OS << '[';
+ if (const auto *ID = getClassInterface()) {
+ OS << ID->getName();
+ } else if (const auto *PD = dyn_cast<ObjCProtocolDecl>(getDeclContext())) {
+ OS << PD->getName();
+ } else {
+ assert(false && "Context should be set for ObjCMethodDecl");
+ OS << "<Unknown>";
+ }
+ OS << ' ' << getSelector() << ']';
+}
+
bool ObjCMethodDecl::isDirectMethod() const {
return hasAttr<ObjCDirectAttr>() &&
!getASTContext().getLangOpts().ObjCDisableDirectMethodsForTesting;
@@ -2365,6 +2386,36 @@ ObjCPropertyDecl *ObjCPropertyDecl::CreateDeserialized(ASTContext &C,
QualType(), nullptr, None);
}
+void ObjCPropertyDecl::getNameForDiagnostic(raw_ostream &OS,
+ const PrintingPolicy &Policy,
+ bool Qualified) const {
+ if (!Qualified) {
+ printName(OS, Policy);
+ return;
+ }
+
+ 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 {
+ assert(false && "Parent should not be null");
+ OS << "<Unknown>";
+ }
+
+ OS << ' ' << getName() << ']';
+}
+
QualType ObjCPropertyDecl::getUsageType(QualType objectType) const {
return DeclType.substObjCMemberType(objectType, getDeclContext(),
ObjCSubstitutionContext::Property);
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];
}
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]");
+}
More information about the cfe-commits
mailing list