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@-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];
 }
 

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

Reply via email to