From 28eace65c09a51e24fb5651ee035e2c6c102da69 Mon Sep 17 00:00:00 2001 From: Ted Kremenek Date: Sat, 23 Nov 2013 01:01:34 +0000 Subject: [PATCH] Add back experimental attribute objc_suppress_protocol_methods (slightly renamed). This is still an experimental attribute, but I wanted it in tree for review. It may still get yanked. This attribute can only be applied to a class @interface, not a class extension or category. It does not change the type system rules for Objective-C, but rather the implementation checking for Objective-C classes that explicitly conform to a protocol. During protocol conformance checking, clang recursively searches up the class hierarchy for the set of methods that compose a protocol. This attribute will cause the compiler to not consider the methods contributed by a super class, its categories, and those from its ancestor classes. Thus this attribute is used to force subclasses to redeclare (and hopefully re-implement) methods if they decide to explicitly conform to a protocol where some of those methods may be provided by a super class. This attribute intentionally leaves out properties, which are associated with state. This attribute only considers methods (at least right now) that are non-property accessors. These represent methods that "do something" as dictated by the protocol. This may be further refined, and this should be considered a WIP until documentation gets written or this gets removed. llvm-svn: 195533 --- clang/include/clang/AST/DeclObjC.h | 3 +- clang/include/clang/Basic/Attr.td | 6 ++ clang/lib/AST/DeclObjC.cpp | 19 ++++- clang/lib/Sema/SemaDeclAttr.cpp | 27 ++++++- clang/lib/Sema/SemaDeclObjC.cpp | 8 +- .../SemaObjC/protocols-suppress-conformance.m | 79 +++++++++++++++++++ 6 files changed, 137 insertions(+), 5 deletions(-) create mode 100644 clang/test/SemaObjC/protocols-suppress-conformance.m diff --git a/clang/include/clang/AST/DeclObjC.h b/clang/include/clang/AST/DeclObjC.h index eebed07aa9ac..23778cb368a5 100644 --- a/clang/include/clang/AST/DeclObjC.h +++ b/clang/include/clang/AST/DeclObjC.h @@ -1143,7 +1143,8 @@ public: ObjCMethodDecl *lookupMethod(Selector Sel, bool isInstance, bool shallowCategoryLookup = false, bool followSuper = true, - const ObjCCategoryDecl *C = 0) const; + const ObjCCategoryDecl *C = 0, + const ObjCProtocolDecl *P = 0) const; /// Lookup an instance method for a given selector. ObjCMethodDecl *lookupInstanceMethod(Selector Sel) const { diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 49c04500924e..5902932be2de 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -642,6 +642,12 @@ def ObjCRootClass : InheritableAttr { let Subjects = [ObjCInterface]; } +def ObjCSuppressProtocol : InheritableAttr { + let Spellings = [GNU<"objc_suppress_protocol_methods">]; + let Subjects = [ObjCInterface]; + let Args = [IdentifierArgument<"Protocol", 1>]; +} + def Overloadable : Attr { let Spellings = [GNU<"overloadable">]; let Subjects = [Function]; diff --git a/clang/lib/AST/DeclObjC.cpp b/clang/lib/AST/DeclObjC.cpp index ca87bb8197f5..a4857b607315 100644 --- a/clang/lib/AST/DeclObjC.cpp +++ b/clang/lib/AST/DeclObjC.cpp @@ -460,7 +460,8 @@ ObjCMethodDecl *ObjCInterfaceDecl::lookupMethod(Selector Sel, bool isInstance, bool shallowCategoryLookup, bool followSuper, - const ObjCCategoryDecl *C) const + const ObjCCategoryDecl *C, + const ObjCProtocolDecl *P) const { // FIXME: Should make sure no callers ever do this. if (!hasDefinition()) @@ -473,6 +474,22 @@ ObjCMethodDecl *ObjCInterfaceDecl::lookupMethod(Selector Sel, LoadExternalDefinition(); while (ClassDecl) { + // If we are looking for a method that is part of protocol conformance, + // check if the superclass has been marked to suppress conformance + // of that protocol. + if (P && ClassDecl->hasAttrs()) { + const AttrVec &V = ClassDecl->getAttrs(); + const IdentifierInfo *PI = P->getIdentifier(); + for (AttrVec::const_iterator I = V.begin(), E = V.end(); I != E; ++I) { + if (const ObjCSuppressProtocolAttr *A = + dyn_cast(*I)){ + if (A->getProtocol() == PI) { + return 0; + } + } + } + } + if ((MethodDecl = ClassDecl->getMethod(Sel, isInstance))) return MethodDecl; diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 1dec334bc7e2..7ae873d7f9ea 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -2134,6 +2134,28 @@ static void handleObjCRootClassAttr(Sema &S, Decl *D, Attr.getAttributeSpellingListIndex())); } +static void handleObjCSuppresProtocolAttr(Sema &S, Decl *D, + const AttributeList &Attr) { + if (!isa(D)) { + S.Diag(Attr.getLoc(), diag::err_attribute_wrong_decl_type) + << Attr.getName() << ExpectedObjectiveCInterface; + return; + } + + IdentifierLoc *Parm = (Attr.getNumArgs() == 1 && Attr.isArgIdent(0)) + ? Attr.getArgAsIdent(0) : 0; + + if (!Parm) { + S.Diag(D->getLocStart(), diag::err_objc_attr_not_id) << Attr.getName() << 1; + return; + } + + D->addAttr(::new (S.Context) + ObjCSuppressProtocolAttr(Attr.getRange(), S.Context, Parm->Ident, + Attr.getAttributeSpellingListIndex())); +} + + static void handleObjCRequiresPropertyDefsAttr(Sema &S, Decl *D, const AttributeList &Attr) { if (!isa(D)) { @@ -4713,7 +4735,10 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, case AttributeList::AT_ObjCRootClass: handleObjCRootClassAttr(S, D, Attr); break; - case AttributeList::AT_ObjCRequiresPropertyDefs: + case AttributeList::AT_ObjCSuppressProtocol: + handleObjCSuppresProtocolAttr(S, D, Attr); + break; + case AttributeList::AT_ObjCRequiresPropertyDefs: handleObjCRequiresPropertyDefsAttr (S, D, Attr); break; case AttributeList::AT_Unused: handleUnusedAttr (S, D, Attr); break; diff --git a/clang/lib/Sema/SemaDeclObjC.cpp b/clang/lib/Sema/SemaDeclObjC.cpp index 95e3b4f4669f..6de4e5bb21a3 100644 --- a/clang/lib/Sema/SemaDeclObjC.cpp +++ b/clang/lib/Sema/SemaDeclObjC.cpp @@ -1667,7 +1667,9 @@ void Sema::CheckProtocolMethodDefs(SourceLocation ImpLoc, (!Super || !Super->lookupMethod(method->getSelector(), true /* instance */, false /* shallowCategory */, - true /* followsSuper */))) { + true /* followsSuper */, + NULL /* category */, + PDecl /* protocol */))) { // If a method is not implemented in the category implementation but // has been declared in its primary class, superclass, // or in one of their protocols, no need to issue the warning. @@ -1703,7 +1705,9 @@ void Sema::CheckProtocolMethodDefs(SourceLocation ImpLoc, (!Super || !Super->lookupMethod(method->getSelector(), false /* class method */, false /* shallowCategoryLookup */, - true /* followSuper */))) { + true /* followSuper */, + NULL /* category */, + PDecl /* protocol */))) { // See above comment for instance method lookups. if (C && IDecl->lookupMethod(method->getSelector(), false /* class */, diff --git a/clang/test/SemaObjC/protocols-suppress-conformance.m b/clang/test/SemaObjC/protocols-suppress-conformance.m new file mode 100644 index 000000000000..61c950a7b0a2 --- /dev/null +++ b/clang/test/SemaObjC/protocols-suppress-conformance.m @@ -0,0 +1,79 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin11 -fsyntax-only -verify %s -Wno-objc-root-class + +@protocol Protocol +- (void) theBestOfTimes; // expected-note {{method 'theBestOfTimes' declared here}} +@property (readonly) id theWorstOfTimes; // expected-note {{property declared here}} +@end + +// In this example, the root class provides all the methods for +// a protocol, and the immediate subclass adopts the attribute. +// +// The further subclasses should not have access to the root class's +// methods for checking protocol conformance. +// +// ClassC states protocol conformance, but does not redeclare the method. +// For this case we get a warning. +// +// ClassD states protocol conformance, but does redeclare the method. +// For this case we do not get a warning. +// + +@interface ClassA +- (void) theBestOfTimes; +//@property (readonly) id theWorstOfTimes; +@end + +__attribute__((objc_suppress_protocol_methods(Protocol))) @interface ClassB : ClassA @end + +@interface ClassC : ClassB @end // expected-note {{required for direct or indirect protocol 'Protocol'}} + +@interface ClassD : ClassB +- (void) theBestOfTimes; +@property (readonly) id theWorstOfTimes; +@end + +@implementation ClassA // expected-warning {{auto property synthesis will not synthesize property declared in a protocol}} +- (void) theBestOfTimes {} +@end + +@implementation ClassC @end // expected-warning {{method 'theBestOfTimes' in protocol not implemented}} + +@implementation ClassD // no-warning +- (void) theBestOfTimes {} +@end + +// In this example, the class both conforms to the protocl and adopts +// the attribute. This illustrates that the attribute does not +// interfere with the protocol conformance checking for the class +// itself. +__attribute__((objc_suppress_protocol_methods(Protocol))) +@interface AdoptsAndConforms +- (void) theBestOfTimes; +@property (readonly) id theWorstOfTimes; +@end + +@implementation AdoptsAndConforms // no-warning +- (void) theBestOfTimes {} +@end + +// This attribute cannot be added to a class extension or category. +@interface ClassE +-(void) theBestOfTimes; +@end + +__attribute__((objc_supress_protocol(Protocol))) +@interface ClassE () @end // expected-error {{attributes may not be specified on a category}} + +__attribute__((objc_supress_protocol(Protocol))) +@interface ClassE (MyCat) @end // expected-error {{attributes may not be specified on a category}} + +// The attribute requires one or more identifiers. +__attribute__((objc_suppress_protocol_methods())) +@interface ClassF @end // expected-error {{parameter of 'objc_suppress_protocol_methods' attribute must be a single name of an Objective-C protocol}} + +// The attribute requires one or more identifiers. +__attribute__((objc_suppress_protocol_methods(ProtoA, ProtoB))) // expected-error {{use of undeclared identifier 'ProtoB'}} +@interface ClassG @end +__attribute__((objc_suppress_protocol_methods(1+2))) +@interface ClassH @end // expected-error {{parameter of 'objc_suppress_protocol_methods' attribute must be a single name of an Objective-C protocol}} + \ No newline at end of file