[objc] diagnose protocol conformance in categories with direct members
authorAlex Lorenz <arphaman@gmail.com>
Fri, 4 Dec 2020 23:06:13 +0000 (15:06 -0800)
committerAlex Lorenz <arphaman@gmail.com>
Fri, 4 Dec 2020 23:55:34 +0000 (15:55 -0800)
in their corresponding class interfaces

Categories that add protocol conformances to classes with direct members should prohibit protocol
conformances when the methods/properties that the protocol expects are actually declared as 'direct' in the class.

Differential Revision: https://reviews.llvm.org/D92602

clang/include/clang/Basic/DiagnosticSemaKinds.td
clang/lib/Sema/SemaDeclObjC.cpp
clang/test/SemaObjC/category-direct-members-protocol-conformance.m [new file with mode: 0644]

index 2166096..01a521f 100644 (file)
@@ -1062,6 +1062,10 @@ def warn_objc_direct_property_ignored : Warning<
   InGroup<IgnoredAttributes>;
 def err_objc_direct_dynamic_property : Error<
   "direct property cannot be @dynamic">;
+def err_objc_direct_protocol_conformance : Error<
+  "%select{category %1|class extension}0 cannot conform to protocol %2 because "
+  "of direct members declared in interface %3">;
+def note_direct_member_here : Note<"direct member declared here">;
 
 def warn_conflicting_overriding_ret_types : Warning<
   "conflicting return type in "
index a489196..60253a8 100644 (file)
@@ -3912,6 +3912,55 @@ static void DiagnoseVariableSizedIvars(Sema &S, ObjCContainerDecl *OCD) {
   }
 }
 
+static void DiagnoseCategoryDirectMembersProtocolConformance(
+    Sema &S, ObjCProtocolDecl *PDecl, ObjCCategoryDecl *CDecl);
+
+static void DiagnoseCategoryDirectMembersProtocolConformance(
+    Sema &S, ObjCCategoryDecl *CDecl,
+    const llvm::iterator_range<ObjCProtocolList::iterator> &Protocols) {
+  for (auto *PI : Protocols)
+    DiagnoseCategoryDirectMembersProtocolConformance(S, PI, CDecl);
+}
+
+static void DiagnoseCategoryDirectMembersProtocolConformance(
+    Sema &S, ObjCProtocolDecl *PDecl, ObjCCategoryDecl *CDecl) {
+  if (!PDecl->isThisDeclarationADefinition() && PDecl->getDefinition())
+    PDecl = PDecl->getDefinition();
+
+  llvm::SmallVector<const Decl *, 4> DirectMembers;
+  const auto *IDecl = CDecl->getClassInterface();
+  for (auto *MD : PDecl->methods()) {
+    if (!MD->isPropertyAccessor()) {
+      if (const auto *CMD =
+              IDecl->getMethod(MD->getSelector(), MD->isInstanceMethod())) {
+        if (CMD->isDirectMethod())
+          DirectMembers.push_back(CMD);
+      }
+    }
+  }
+  for (auto *PD : PDecl->properties()) {
+    if (const auto *CPD = IDecl->FindPropertyVisibleInPrimaryClass(
+            PD->getIdentifier(),
+            PD->isClassProperty()
+                ? ObjCPropertyQueryKind::OBJC_PR_query_class
+                : ObjCPropertyQueryKind::OBJC_PR_query_instance)) {
+      if (CPD->isDirectProperty())
+        DirectMembers.push_back(CPD);
+    }
+  }
+  if (!DirectMembers.empty()) {
+    S.Diag(CDecl->getLocation(), diag::err_objc_direct_protocol_conformance)
+        << CDecl->IsClassExtension() << CDecl << PDecl << IDecl;
+    for (const auto *MD : DirectMembers)
+      S.Diag(MD->getLocation(), diag::note_direct_member_here);
+    return;
+  }
+
+  // Check on this protocols's referenced protocols, recursively.
+  DiagnoseCategoryDirectMembersProtocolConformance(S, CDecl,
+                                                   PDecl->protocols());
+}
+
 // Note: For class/category implementations, allMethods is always null.
 Decl *Sema::ActOnAtEnd(Scope *S, SourceRange AtEnd, ArrayRef<Decl *> allMethods,
                        ArrayRef<DeclGroupPtrTy> allTUVars) {
@@ -4012,6 +4061,8 @@ Decl *Sema::ActOnAtEnd(Scope *S, SourceRange AtEnd, ArrayRef<Decl *> allMethods,
       ObjCInterfaceDecl *CCPrimary = C->getClassInterface();
       DiagnoseClassExtensionDupMethods(C, CCPrimary);
     }
+
+    DiagnoseCategoryDirectMembersProtocolConformance(*this, C, C->protocols());
   }
   if (ObjCContainerDecl *CDecl = dyn_cast<ObjCContainerDecl>(ClassDecl)) {
     if (CDecl->getIdentifier())
diff --git a/clang/test/SemaObjC/category-direct-members-protocol-conformance.m b/clang/test/SemaObjC/category-direct-members-protocol-conformance.m
new file mode 100644 (file)
index 0000000..dfee42f
--- /dev/null
@@ -0,0 +1,98 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+__attribute__((objc_root_class))
+@interface RootClass
+
+- (void)baseMethod;
+
+@end
+
+__attribute__((objc_direct_members))
+@interface I : RootClass
+
+- (void)direct; // expected-note {{direct member declared here}}
+
+@end
+
+@protocol P
+- (void)direct;
+@end
+
+@interface I (Cat1) <P> // expected-error {{category 'Cat1' cannot conform to protocol 'P' because of direct members declared in interface 'I'}}
+@end
+
+@protocol BaseP
+- (void)baseMethod;
+@end
+
+@interface I (CatBase) <BaseP> // OK
+@end
+
+@protocol P2
+- (void)indirect;
+@end
+
+@interface I (Cat2) <P2> // OK
+- (void)indirect;
+@end
+
+@protocol P3
+- (void)indirect3;
+@end
+
+@interface I (Cat3) <P3> // OK
+@end
+
+@interface ExpDirect : RootClass
+
+- (void)direct __attribute__((objc_direct)); // expected-note {{direct member declared here}}
+
+- (void)directRecursive __attribute__((objc_direct)); // expected-note {{direct member declared here}}
+
+@end
+
+@interface ExpDirect (CatExpDirect) <P> // expected-error {{category 'CatExpDirect' cannot conform to protocol 'P' because of direct members declared in interface 'ExpDirect'}}
+@end
+
+@protocol PRecursive1
+- (void)directRecursive;
+@end
+
+@protocol PRecursiveTop <PRecursive1>
+@end
+
+@interface ExpDirect () <PRecursiveTop> // expected-error {{class extension cannot conform to protocol 'PRecursive1' because of direct members declared in interface 'ExpDirect'}}
+@end
+
+
+@protocol PProp
+
+@property (nonatomic, readonly) I *name;
+
+@end
+
+__attribute__((objc_direct_members))
+@interface IProp1 : RootClass
+
+@property (nonatomic, readonly) I *name; // expected-note {{direct member declared here}}
+
+@end
+
+@interface IProp1 () <PProp> // expected-error {{class extension cannot conform to protocol 'PProp' because of direct members declared in interface 'IProp1'}}
+@end
+
+
+@protocol PProp2
+
+@property (nonatomic, readonly, class) I *name;
+
+@end
+
+@interface IProp2 : RootClass
+
+@property (nonatomic, readonly, class, direct) I *name; // expected-note {{direct member declared here}}
+
+@end
+
+@interface IProp2 () <PProp2> // expected-error {{class extension cannot conform to protocol 'PProp2' because of direct members declared in interface 'IProp2'}}
+@end