From 813a066f16df52783708fdc2ef708bb76a9ff521 Mon Sep 17 00:00:00 2001 From: Douglas Gregor Date: Fri, 19 Jun 2015 18:14:38 +0000 Subject: [PATCH] Extend type nullability qualifiers for Objective-C. Introduce context-sensitive, non-underscored nullability specifiers (nonnull, nullable, null_unspecified) for Objective-C method return types, method parameter types, and properties. Introduce Objective-C-specific semantics, including computation of the nullability of the result of a message send, merging of nullability information from the @interface of a class into its @implementation, etc . This is the Objective-C part of rdar://problem/18868820. llvm-svn: 240154 --- clang/include/clang/AST/ASTContext.h | 30 ++++ clang/include/clang/AST/DeclBase.h | 7 +- clang/include/clang/AST/DeclObjC.h | 33 ++-- clang/include/clang/Basic/DiagnosticCommonKinds.td | 31 +++- clang/include/clang/Basic/DiagnosticSemaKinds.td | 29 ++-- clang/include/clang/Basic/Specifiers.h | 4 + clang/include/clang/Parse/Parser.h | 10 ++ clang/include/clang/Sema/AttributeList.h | 31 +++- clang/include/clang/Sema/DeclSpec.h | 45 +++++- clang/include/clang/Sema/Sema.h | 26 ++- clang/lib/AST/ASTImporter.cpp | 7 +- clang/lib/AST/DeclObjC.cpp | 8 +- clang/lib/AST/DeclPrinter.cpp | 74 ++++++++- clang/lib/Basic/IdentifierTable.cpp | 14 ++ clang/lib/Parse/ParseObjc.cpp | 177 ++++++++++++++++++++- clang/lib/Parse/Parser.cpp | 4 + clang/lib/Sema/SemaDecl.cpp | 8 +- clang/lib/Sema/SemaDeclObjC.cpp | 165 ++++++++++++++++--- clang/lib/Sema/SemaExprObjC.cpp | 133 ++++++++++++++-- clang/lib/Sema/SemaObjCProperty.cpp | 40 ++++- clang/lib/Sema/SemaType.cpp | 127 ++++++++------- clang/lib/Sema/TreeTransform.h | 2 +- clang/lib/Serialization/ASTReaderDecl.cpp | 4 +- clang/lib/Serialization/ASTWriterDecl.cpp | 3 +- clang/test/Index/comment-objc-decls.m | 8 +- clang/test/SemaObjC/arc-property-decl-attrs.m | 8 + clang/test/SemaObjC/arc-unavailable-for-weakref.m | 6 +- clang/test/SemaObjC/nullability.m | 151 +++++++++++++++++- clang/test/SemaObjC/nullable-weak-property.m | 18 +++ clang/test/SemaObjC/override-nullability.m | 15 ++ 30 files changed, 1035 insertions(+), 183 deletions(-) create mode 100644 clang/test/SemaObjC/nullable-weak-property.m create mode 100644 clang/test/SemaObjC/override-nullability.m diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 049221a..c06c5b4 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -1854,6 +1854,36 @@ public: getCanonicalType(T2).getTypePtr(); } + bool hasSameNullabilityTypeQualifier(QualType SubT, QualType SuperT, + bool IsParam) const { + auto SubTnullability = SubT->getNullability(*this); + auto SuperTnullability = SuperT->getNullability(*this); + if (SubTnullability.hasValue() == SuperTnullability.hasValue()) { + // Neither has nullability; return true + if (!SubTnullability) + return true; + // Both have nullability qualifier. + if (*SubTnullability == *SuperTnullability || + *SubTnullability == NullabilityKind::Unspecified || + *SuperTnullability == NullabilityKind::Unspecified) + return true; + + if (IsParam) { + // Ok for the superclass method parameter to be “nonnull” and the subclass + // method parameter to be “nullable” + return (*SuperTnullability == NullabilityKind::NonNull && + *SubTnullability == NullabilityKind::Nullable); + } + else { + // For the return type, it’s okay for the superclass method to specify + // “nullable” and the subclass method specify “nonnull” + return (*SuperTnullability == NullabilityKind::Nullable && + *SubTnullability == NullabilityKind::NonNull); + } + } + return true; + } + bool ObjCMethodsAreEqual(const ObjCMethodDecl *MethodDecl, const ObjCMethodDecl *MethodImp); diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h index f176e54..6b6ac3f 100644 --- a/clang/include/clang/AST/DeclBase.h +++ b/clang/include/clang/AST/DeclBase.h @@ -178,7 +178,12 @@ public: OBJC_TQ_Out = 0x4, OBJC_TQ_Bycopy = 0x8, OBJC_TQ_Byref = 0x10, - OBJC_TQ_Oneway = 0x20 + OBJC_TQ_Oneway = 0x20, + + /// The nullability qualifier is set when the nullability of the + /// result or parameter was expressed via a context-sensitive + /// keyword. + OBJC_TQ_CSNullability = 0x40 }; protected: diff --git a/clang/include/clang/AST/DeclObjC.h b/clang/include/clang/AST/DeclObjC.h index 4a5b4f3..86f1af1 100644 --- a/clang/include/clang/AST/DeclObjC.h +++ b/clang/include/clang/AST/DeclObjC.h @@ -141,7 +141,7 @@ private: // NOTE: VC++ treats enums as signed, avoid using the ObjCDeclQualifier enum /// in, inout, etc. - unsigned objcDeclQualifier : 6; + unsigned objcDeclQualifier : 7; /// \brief Indicates whether this method has a related result type. unsigned RelatedResultType : 1; @@ -2203,13 +2203,16 @@ public: OBJC_PR_atomic = 0x100, OBJC_PR_weak = 0x200, OBJC_PR_strong = 0x400, - OBJC_PR_unsafe_unretained = 0x800 + OBJC_PR_unsafe_unretained = 0x800, + /// Indicates that the nullability of the type was spelled with a + /// property attribute rather than a type qualifier. + OBJC_PR_nullability = 0x1000 // Adding a property should change NumPropertyAttrsBits }; enum { /// \brief Number of bits fitting all the property attributes. - NumPropertyAttrsBits = 12 + NumPropertyAttrsBits = 13 }; enum SetterKind { Assign, Retain, Copy, Weak }; @@ -2217,7 +2220,8 @@ public: private: SourceLocation AtLoc; // location of \@property SourceLocation LParenLoc; // location of '(' starting attribute list or null. - TypeSourceInfo *DeclType; + QualType DeclType; + TypeSourceInfo *DeclTypeSourceInfo; unsigned PropertyAttributes : NumPropertyAttrsBits; unsigned PropertyAttributesAsWritten : NumPropertyAttrsBits; // \@required/\@optional @@ -2232,12 +2236,13 @@ private: ObjCPropertyDecl(DeclContext *DC, SourceLocation L, IdentifierInfo *Id, SourceLocation AtLocation, SourceLocation LParenLocation, - TypeSourceInfo *T) + QualType T, TypeSourceInfo *TSI, + PropertyControl propControl) : NamedDecl(ObjCProperty, DC, L, Id), AtLoc(AtLocation), - LParenLoc(LParenLocation), DeclType(T), + LParenLoc(LParenLocation), DeclType(T), DeclTypeSourceInfo(TSI), PropertyAttributes(OBJC_PR_noattr), PropertyAttributesAsWritten(OBJC_PR_noattr), - PropertyImplementation(None), + PropertyImplementation(propControl), GetterName(Selector()), SetterName(Selector()), GetterMethodDecl(nullptr), SetterMethodDecl(nullptr), @@ -2248,7 +2253,8 @@ public: SourceLocation L, IdentifierInfo *Id, SourceLocation AtLocation, SourceLocation LParenLocation, - TypeSourceInfo *T, + QualType T, + TypeSourceInfo *TSI, PropertyControl propControl = None); static ObjCPropertyDecl *CreateDeserialized(ASTContext &C, unsigned ID); @@ -2259,9 +2265,14 @@ public: SourceLocation getLParenLoc() const { return LParenLoc; } void setLParenLoc(SourceLocation L) { LParenLoc = L; } - TypeSourceInfo *getTypeSourceInfo() const { return DeclType; } - QualType getType() const { return DeclType->getType(); } - void setType(TypeSourceInfo *T) { DeclType = T; } + TypeSourceInfo *getTypeSourceInfo() const { return DeclTypeSourceInfo; } + + QualType getType() const { return DeclType; } + + void setType(QualType T, TypeSourceInfo *TSI) { + DeclType = T; + DeclTypeSourceInfo = TSI; + } PropertyAttributeKind getPropertyAttributes() const { return PropertyAttributeKind(PropertyAttributes); diff --git a/clang/include/clang/Basic/DiagnosticCommonKinds.td b/clang/include/clang/Basic/DiagnosticCommonKinds.td index 8532594..82718d9 100644 --- a/clang/include/clang/Basic/DiagnosticCommonKinds.td +++ b/clang/include/clang/Basic/DiagnosticCommonKinds.td @@ -101,15 +101,32 @@ def err_enum_template : Error<"enumeration cannot be a template">; let CategoryName = "Nullability Issue" in { -def warn_mismatched_nullability_attr : Warning< - "nullability specifier " - "'__%select{nonnull|nullable|null_unspecified}0' " - "conflicts with existing specifier " - "'__%select{nonnull|nullable|null_unspecified}1'">, +def warn_nullability_duplicate : Warning< + "duplicate nullability specifier " + "'%select{__|}1%select{nonnull|nullable|null_unspecified}0'">, + InGroup; + +def warn_conflicting_nullability_attr_overriding_ret_types : Warning< + "conflicting nullability specifier on return types, " + "'%select{%select{__|}1nonnull|" + "%select{__|}1nullable|%select{__|}1null_unspecified}0' " + "conflicts with existing specifier '%select{%select{__|}3nonnull|" + "%select{__|}3nullable|%select{__|}3null_unspecified}2'">, InGroup; -def note_nullability_here : Note< - "'%select{__nonnull|__nullable|__null_unspecified}0' specified here">; +def warn_conflicting_nullability_attr_overriding_param_types : Warning< + "conflicting nullability specifier on parameter types, " + "'%select{%select{__|}1nonnull|" + "%select{__|}1nullable|%select{__|}1null_unspecified}0' " + "conflicts with existing specifier '%select{%select{__|}3nonnull|" + "%select{__|}3nullable|%select{__|}3null_unspecified}2'">, + InGroup; + +def err_nullability_conflicting : Error< + "nullability specifier " + "'%select{__|}1%select{nonnull|nullable|null_unspecified}0' conflicts with " + "existing specifier '%select{__|}3%select{nonnull|nullable|" + "null_unspecified}2'">; } diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index a4c6f4d..cc1a80e 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -7676,9 +7676,11 @@ def warn_profile_data_unprofiled : Warning< let CategoryName = "Nullability Issue" in { -def warn_duplicate_nullability : Warning< - "duplicate nullability specifier " - "'%select{__nonnull|__nullable|__null_unspecified}0'">, +def warn_mismatched_nullability_attr : Warning< + "nullability specifier " + "'%select{__|}1%select{nonnull|nullable|null_unspecified}0' " + "conflicts with existing specifier " + "'%select{__|}3%select{nonnull|nullable|null_unspecified}2'">, InGroup; def warn_nullability_declspec : Warning< @@ -7690,21 +7692,28 @@ def warn_nullability_declspec : Warning< InGroup, DefaultError; -def err_nullability_nonpointer : Error< - "nullability specifier " - "'%select{__nonnull|__nullable|__null_unspecified}0' cannot be applied to " - "non-pointer type %1">; +def note_nullability_here : Note< + "'%select{__nonnull|__nullable|__null_unspecified}0' specified here">; -def err_nullability_conflicting : Error< +def err_nullability_nonpointer : Error< "nullability specifier " - "'%select{__nonnull|__nullable|__null_unspecified}0' conflicts with existing " - "specifier '%select{__nonnull|__nullable|__null_unspecified}1'">; + "'%select{__|}1%select{nonnull|nullable|null_unspecified}0' cannot be applied " + "to non-pointer type %2">; def warn_nullability_lost : Warning< "implicit conversion from nullable pointer %0 to non-nullable pointer " "type %1">, InGroup, DefaultIgnore; +def err_nullability_cs_multilevel : Error< + "nullability keyword " + "'%select{nonnull|nullable|null_unspecified}0' cannot be applied to " + "multi-level pointer type %1">; +def note_nullability_type_specifier : Note< + "use nullability type specifier " + "'%select{__nonnull|__nullable|__null_unspecified}0' to affect the innermost " + "pointer type of %1">; + } } // end of sema component. diff --git a/clang/include/clang/Basic/Specifiers.h b/clang/include/clang/Basic/Specifiers.h index 2a872b2..5ce56c0 100644 --- a/clang/include/clang/Basic/Specifiers.h +++ b/clang/include/clang/Basic/Specifiers.h @@ -16,6 +16,7 @@ #ifndef LLVM_CLANG_BASIC_SPECIFIERS_H #define LLVM_CLANG_BASIC_SPECIFIERS_H +#include "llvm/ADT/StringRef.h" #include "llvm/Support/DataTypes.h" namespace clang { @@ -254,6 +255,9 @@ namespace clang { /// though it has been considered. Unspecified }; + + /// Retrieve the spelling of the given nullability kind. + llvm::StringRef getNullabilitySpelling(NullabilityKind kind); } // end namespace clang #endif // LLVM_CLANG_BASIC_SPECIFIERS_H diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 3d48f0a..c85160f 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -139,6 +139,11 @@ class Parser : public CodeCompletionHandler { // used as type traits. llvm::SmallDenseMap RevertibleTypeTraits; + /// Nullability type specifiers. + IdentifierInfo *Ident___nonnull = nullptr; + IdentifierInfo *Ident___nullable = nullptr; + IdentifierInfo *Ident___null_unspecified = nullptr; + std::unique_ptr AlignHandler; std::unique_ptr GCCVisibilityHandler; std::unique_ptr OptionsHandler; @@ -303,6 +308,10 @@ public: return true; } + /// Retrieve the underscored keyword (__nonnull, __nullable, + /// __null_unspecified) that corresponds to the given nullability kind. + IdentifierInfo *getNullabilityKeyword(NullabilityKind nullability); + private: //===--------------------------------------------------------------------===// // Low-Level token peeking and consumption methods. @@ -1282,6 +1291,7 @@ private: // Definitions for Objective-c context sensitive keywords recognition. enum ObjCTypeQual { objc_in=0, objc_out, objc_inout, objc_oneway, objc_bycopy, objc_byref, + objc_nonnull, objc_nullable, objc_null_unspecified, objc_NumQuals }; IdentifierInfo *ObjCTypeQuals[objc_NumQuals]; diff --git a/clang/include/clang/Sema/AttributeList.h b/clang/include/clang/Sema/AttributeList.h index 58b1b9e..b55f38a 100644 --- a/clang/include/clang/Sema/AttributeList.h +++ b/clang/include/clang/Sema/AttributeList.h @@ -120,6 +120,9 @@ private: /// True if this has a ParsedType unsigned HasParsedType : 1; + /// True when this keyword attribute is a context-sensitive keyword. + unsigned IsContextSensitiveKeyword : 1; + unsigned AttrKind : 8; /// \brief The location of the 'unavailable' keyword in an @@ -220,7 +223,8 @@ private: ScopeLoc(scopeLoc), EllipsisLoc(ellipsisLoc), NumArgs(numArgs), SyntaxUsed(syntaxUsed), Invalid(false), UsedAsTypeAttr(false), IsAvailability(false), IsTypeTagForDatatype(false), IsProperty(false), - HasParsedType(false), NextInPosition(nullptr), NextInPool(nullptr) { + HasParsedType(false), IsContextSensitiveKeyword(false), + NextInPosition(nullptr), NextInPool(nullptr) { if (numArgs) memcpy(getArgsBuffer(), args, numArgs * sizeof(ArgsUnion)); AttrKind = getKind(getName(), getScopeName(), syntaxUsed); } @@ -238,8 +242,8 @@ private: ScopeLoc(scopeLoc), EllipsisLoc(), NumArgs(1), SyntaxUsed(syntaxUsed), Invalid(false), UsedAsTypeAttr(false), IsAvailability(true), IsTypeTagForDatatype(false), IsProperty(false), HasParsedType(false), - UnavailableLoc(unavailable), MessageExpr(messageExpr), - NextInPosition(nullptr), NextInPool(nullptr) { + IsContextSensitiveKeyword(false), UnavailableLoc(unavailable), + MessageExpr(messageExpr), NextInPosition(nullptr), NextInPool(nullptr) { ArgsUnion PVal(Parm); memcpy(getArgsBuffer(), &PVal, sizeof(ArgsUnion)); new (&getAvailabilitySlot(IntroducedSlot)) AvailabilityChange(introduced); @@ -259,7 +263,8 @@ private: ScopeLoc(scopeLoc), EllipsisLoc(), NumArgs(3), SyntaxUsed(syntaxUsed), Invalid(false), UsedAsTypeAttr(false), IsAvailability(false), IsTypeTagForDatatype(false), IsProperty(false), HasParsedType(false), - NextInPosition(nullptr), NextInPool(nullptr) { + IsContextSensitiveKeyword(false), NextInPosition(nullptr), + NextInPool(nullptr) { ArgsVector Args; Args.push_back(Parm1); Args.push_back(Parm2); @@ -277,7 +282,8 @@ private: ScopeLoc(scopeLoc), EllipsisLoc(), NumArgs(1), SyntaxUsed(syntaxUsed), Invalid(false), UsedAsTypeAttr(false), IsAvailability(false), IsTypeTagForDatatype(true), IsProperty(false), HasParsedType(false), - NextInPosition(nullptr), NextInPool(nullptr) { + IsContextSensitiveKeyword(false), NextInPosition(nullptr), + NextInPool(nullptr) { ArgsUnion PVal(ArgKind); memcpy(getArgsBuffer(), &PVal, sizeof(ArgsUnion)); TypeTagForDatatypeData &ExtraData = getTypeTagForDatatypeDataSlot(); @@ -295,7 +301,8 @@ private: ScopeLoc(scopeLoc), EllipsisLoc(), NumArgs(0), SyntaxUsed(syntaxUsed), Invalid(false), UsedAsTypeAttr(false), IsAvailability(false), IsTypeTagForDatatype(false), IsProperty(false), HasParsedType(true), - NextInPosition(nullptr), NextInPool(nullptr) { + IsContextSensitiveKeyword(false), NextInPosition(nullptr), + NextInPool(nullptr) { new (&getTypeBuffer()) ParsedType(typeArg); AttrKind = getKind(getName(), getScopeName(), syntaxUsed); } @@ -309,7 +316,8 @@ private: ScopeLoc(scopeLoc), EllipsisLoc(), NumArgs(0), SyntaxUsed(syntaxUsed), Invalid(false), UsedAsTypeAttr(false), IsAvailability(false), IsTypeTagForDatatype(false), IsProperty(true), HasParsedType(false), - NextInPosition(nullptr), NextInPool(nullptr) { + IsContextSensitiveKeyword(false), NextInPosition(nullptr), + NextInPool(nullptr) { new (&getPropertyDataBuffer()) PropertyData(getterId, setterId); AttrKind = getKind(getName(), getScopeName(), syntaxUsed); } @@ -352,6 +360,15 @@ public: } bool isKeywordAttribute() const { return SyntaxUsed == AS_Keyword; } + bool isContextSensitiveKeywordAttribute() const { + return IsContextSensitiveKeyword; + } + + void setContextSensitiveKeywordAttribute() { + assert(SyntaxUsed == AS_Keyword); + IsContextSensitiveKeyword = true; + } + bool isInvalid() const { return Invalid; } void setInvalid(bool b = true) const { Invalid = b; } diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h index de39d83..9fe0cc7 100644 --- a/clang/include/clang/Sema/DeclSpec.h +++ b/clang/include/clang/Sema/DeclSpec.h @@ -31,6 +31,7 @@ #include "clang/Lex/Token.h" #include "clang/Sema/AttributeList.h" #include "clang/Sema/Ownership.h" +#include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/ErrorHandling.h" @@ -785,7 +786,8 @@ public: DQ_Out = 0x4, DQ_Bycopy = 0x8, DQ_Byref = 0x10, - DQ_Oneway = 0x20 + DQ_Oneway = 0x20, + DQ_CSNullability = 0x40 }; /// PropertyAttributeKind - list of property attributes. @@ -802,17 +804,21 @@ public: DQ_PR_atomic = 0x100, DQ_PR_weak = 0x200, DQ_PR_strong = 0x400, - DQ_PR_unsafe_unretained = 0x800 + DQ_PR_unsafe_unretained = 0x800, + DQ_PR_nullability = 0x1000 }; - ObjCDeclSpec() : objcDeclQualifier(DQ_None), PropertyAttributes(DQ_PR_noattr), - GetterName(nullptr), SetterName(nullptr) { } + Nullability(0), GetterName(nullptr), SetterName(nullptr) { } + ObjCDeclQualifier getObjCDeclQualifier() const { return objcDeclQualifier; } void setObjCDeclQualifier(ObjCDeclQualifier DQVal) { objcDeclQualifier = (ObjCDeclQualifier) (objcDeclQualifier | DQVal); } + void clearObjCDeclQualifier(ObjCDeclQualifier DQVal) { + objcDeclQualifier = (ObjCDeclQualifier) (objcDeclQualifier & ~DQVal); + } ObjCPropertyAttributeKind getPropertyAttributes() const { return ObjCPropertyAttributeKind(PropertyAttributes); @@ -822,6 +828,28 @@ public: (ObjCPropertyAttributeKind)(PropertyAttributes | PRVal); } + NullabilityKind getNullability() const { + assert(((getObjCDeclQualifier() & DQ_CSNullability) || + (getPropertyAttributes() & DQ_PR_nullability)) && + "Objective-C declspec doesn't have nullability"); + return static_cast(Nullability); + } + + SourceLocation getNullabilityLoc() const { + assert(((getObjCDeclQualifier() & DQ_CSNullability) || + (getPropertyAttributes() & DQ_PR_nullability)) && + "Objective-C declspec doesn't have nullability"); + return NullabilityLoc; + } + + void setNullability(SourceLocation loc, NullabilityKind kind) { + assert(((getObjCDeclQualifier() & DQ_CSNullability) || + (getPropertyAttributes() & DQ_PR_nullability)) && + "Set the nullability declspec or property attribute first"); + Nullability = static_cast(kind); + NullabilityLoc = loc; + } + const IdentifierInfo *getGetterName() const { return GetterName; } IdentifierInfo *getGetterName() { return GetterName; } void setGetterName(IdentifierInfo *name) { GetterName = name; } @@ -834,10 +862,15 @@ private: // FIXME: These two are unrelated and mutually exclusive. So perhaps // we can put them in a union to reflect their mutual exclusivity // (space saving is negligible). - ObjCDeclQualifier objcDeclQualifier : 6; + ObjCDeclQualifier objcDeclQualifier : 7; // NOTE: VC++ treats enums as signed, avoid using ObjCPropertyAttributeKind - unsigned PropertyAttributes : 12; + unsigned PropertyAttributes : 13; + + unsigned Nullability : 2; + + SourceLocation NullabilityLoc; + IdentifierInfo *GetterName; // getter name or NULL if no getter IdentifierInfo *SetterName; // setter name or NULL if no setter }; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index ec47a26..11933ec 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -2859,6 +2859,26 @@ public: /// Valid types should not have multiple attributes with different CCs. const AttributedType *getCallingConvAttributedType(QualType T) const; + /// Check whether a nullability type specifier can be added to the given + /// type. + /// + /// \param type The type to which the nullability specifier will be + /// added. On success, this type will be updated appropriately. + /// + /// \param nullability The nullability specifier to add. + /// + /// \param nullabilityLoc The location of the nullability specifier. + /// + /// \param isContextSensitive Whether this nullability specifier was + /// written as a context-sensitive keyword (in an Objective-C + /// method) or an Objective-C property attribute, rather than as an + /// underscored type specifier. + /// + /// \returns true if nullability cannot be applied, false otherwise. + bool checkNullabilityTypeSpecifier(QualType &type, NullabilityKind nullability, + SourceLocation nullabilityLoc, + bool isContextSensitive); + /// \brief Stmt attributes - this routine is the top level dispatcher. StmtResult ProcessStmtAttributes(Stmt *Stmt, AttributeList *Attrs, SourceRange Range); @@ -2934,7 +2954,8 @@ public: const unsigned Attributes, const unsigned AttributesAsWritten, bool *isOverridingProperty, - TypeSourceInfo *T, + QualType T, + TypeSourceInfo *TSI, tok::ObjCKeywordKind MethodImplKind); /// Called by ActOnProperty and HandlePropertyInClassExtension to @@ -2950,7 +2971,8 @@ public: const bool isReadWrite, const unsigned Attributes, const unsigned AttributesAsWritten, - TypeSourceInfo *T, + QualType T, + TypeSourceInfo *TSI, tok::ObjCKeywordKind MethodImplKind, DeclContext *lexicalDC = nullptr); diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp index 911f168..76e4e11 100644 --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -3922,8 +3922,8 @@ Decl *ASTNodeImporter::VisitObjCPropertyDecl(ObjCPropertyDecl *D) { } // Import the type. - TypeSourceInfo *T = Importer.Import(D->getTypeSourceInfo()); - if (!T) + TypeSourceInfo *TSI = Importer.Import(D->getTypeSourceInfo()); + if (!TSI) return nullptr; // Create the new property. @@ -3932,7 +3932,8 @@ Decl *ASTNodeImporter::VisitObjCPropertyDecl(ObjCPropertyDecl *D) { Name.getAsIdentifierInfo(), Importer.Import(D->getAtLoc()), Importer.Import(D->getLParenLoc()), - T, + Importer.Import(D->getType()), + TSI, D->getPropertyImplementation()); Importer.Imported(D, ToProperty); ToProperty->setLexicalDeclContext(LexicalDC); diff --git a/clang/lib/AST/DeclObjC.cpp b/clang/lib/AST/DeclObjC.cpp index a63ba7e..a1600cb 100644 --- a/clang/lib/AST/DeclObjC.cpp +++ b/clang/lib/AST/DeclObjC.cpp @@ -1862,16 +1862,18 @@ ObjCPropertyDecl *ObjCPropertyDecl::Create(ASTContext &C, DeclContext *DC, IdentifierInfo *Id, SourceLocation AtLoc, SourceLocation LParenLoc, - TypeSourceInfo *T, + QualType T, + TypeSourceInfo *TSI, PropertyControl propControl) { - return new (C, DC) ObjCPropertyDecl(DC, L, Id, AtLoc, LParenLoc, T); + return new (C, DC) ObjCPropertyDecl(DC, L, Id, AtLoc, LParenLoc, T, TSI, + propControl); } ObjCPropertyDecl *ObjCPropertyDecl::CreateDeserialized(ASTContext &C, unsigned ID) { return new (C, ID) ObjCPropertyDecl(nullptr, SourceLocation(), nullptr, SourceLocation(), SourceLocation(), - nullptr); + QualType(), nullptr, None); } //===----------------------------------------------------------------------===// diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp index d8cd40e..609cc08 100644 --- a/clang/lib/AST/DeclPrinter.cpp +++ b/clang/lib/AST/DeclPrinter.cpp @@ -37,6 +37,13 @@ namespace { void Print(AccessSpecifier AS); + /// Print an Objective-C method type in parentheses. + /// + /// \param Quals The Objective-C declaration qualifiers. + /// \param T The type to print. + void PrintObjCMethodType(ASTContext &Ctx, Decl::ObjCDeclQualifier Quals, + QualType T); + public: DeclPrinter(raw_ostream &Out, const PrintingPolicy &Policy, unsigned Indentation = 0, bool PrintInstantiation = false) @@ -930,24 +937,64 @@ void DeclPrinter::VisitClassTemplateDecl(ClassTemplateDecl *D) { // Objective-C declarations //---------------------------------------------------------------------------- +/// Strip off the top-level nullability annotation, if it's there. +static Optional stripOuterNullability(QualType &T) { + if (auto attributed = dyn_cast(T.getTypePtr())) { + if (auto nullability = attributed->getImmediateNullability()) { + T = attributed->getModifiedType(); + return nullability; + } + } + + return None; + } + +void DeclPrinter::PrintObjCMethodType(ASTContext &Ctx, + Decl::ObjCDeclQualifier Quals, + QualType T) { + Out << '('; + if (Quals & Decl::ObjCDeclQualifier::OBJC_TQ_In) + Out << "in "; + if (Quals & Decl::ObjCDeclQualifier::OBJC_TQ_Inout) + Out << "inout "; + if (Quals & Decl::ObjCDeclQualifier::OBJC_TQ_Out) + Out << "out "; + if (Quals & Decl::ObjCDeclQualifier::OBJC_TQ_Bycopy) + Out << "bycopy "; + if (Quals & Decl::ObjCDeclQualifier::OBJC_TQ_Byref) + Out << "byref "; + if (Quals & Decl::ObjCDeclQualifier::OBJC_TQ_Oneway) + Out << "oneway "; + if (Quals & Decl::ObjCDeclQualifier::OBJC_TQ_CSNullability) { + if (auto nullability = stripOuterNullability(T)) { + Out << getNullabilitySpelling(*nullability).substr(2) << ' '; + } + } + + Out << Ctx.getUnqualifiedObjCPointerType(T).getAsString(Policy); + Out << ')'; +} + void DeclPrinter::VisitObjCMethodDecl(ObjCMethodDecl *OMD) { if (OMD->isInstanceMethod()) Out << "- "; else Out << "+ "; - if (!OMD->getReturnType().isNull()) - Out << '(' << OMD->getASTContext() - .getUnqualifiedObjCPointerType(OMD->getReturnType()) - .getAsString(Policy) << ")"; + if (!OMD->getReturnType().isNull()) { + PrintObjCMethodType(OMD->getASTContext(), OMD->getObjCDeclQualifier(), + OMD->getReturnType()); + } std::string name = OMD->getSelector().getAsString(); std::string::size_type pos, lastPos = 0; for (const auto *PI : OMD->params()) { // FIXME: selector is missing here! pos = name.find_first_of(':', lastPos); - Out << " " << name.substr(lastPos, pos - lastPos); - Out << ":(" << PI->getASTContext().getUnqualifiedObjCPointerType(PI->getType()). - getAsString(Policy) << ')' << *PI; + Out << " " << name.substr(lastPos, pos - lastPos) << ':'; + PrintObjCMethodType(OMD->getASTContext(), + PI->getObjCDeclQualifier(), + PI->getType()); + Out << *PI; lastPos = pos + 1; } @@ -1103,6 +1150,8 @@ void DeclPrinter::VisitObjCPropertyDecl(ObjCPropertyDecl *PDecl) { else if (PDecl->getPropertyImplementation() == ObjCPropertyDecl::Optional) Out << "@optional\n"; + QualType T = PDecl->getType(); + Out << "@property"; if (PDecl->getPropertyAttributes() != ObjCPropertyDecl::OBJC_PR_noattr) { bool first = true; @@ -1161,10 +1210,19 @@ void DeclPrinter::VisitObjCPropertyDecl(ObjCPropertyDecl *PDecl) { first = false; } + if (PDecl->getPropertyAttributes() & + ObjCPropertyDecl::OBJC_PR_nullability) { + if (auto nullability = stripOuterNullability(T)) { + Out << (first ? ' ' : ',') + << getNullabilitySpelling(*nullability).substr(2); + first = false; + } + } + (void) first; // Silence dead store warning due to idiomatic code. Out << " )"; } - Out << ' ' << PDecl->getASTContext().getUnqualifiedObjCPointerType(PDecl->getType()). + Out << ' ' << PDecl->getASTContext().getUnqualifiedObjCPointerType(T). getAsString(Policy) << ' ' << *PDecl; if (Policy.PolishForDeclaration) Out << ';'; diff --git a/clang/lib/Basic/IdentifierTable.cpp b/clang/lib/Basic/IdentifierTable.cpp index 4e06352..0349ef4 100644 --- a/clang/lib/Basic/IdentifierTable.cpp +++ b/clang/lib/Basic/IdentifierTable.cpp @@ -16,6 +16,7 @@ #include "clang/Basic/IdentifierTable.h" #include "clang/Basic/LangOptions.h" #include "clang/Basic/OperatorKinds.h" +#include "clang/Basic/Specifiers.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/FoldingSet.h" #include "llvm/ADT/SmallString.h" @@ -645,3 +646,16 @@ const char *clang::getOperatorSpelling(OverloadedOperatorKind Operator) { llvm_unreachable("Invalid OverloadedOperatorKind!"); } + +StringRef clang::getNullabilitySpelling(NullabilityKind kind) { + switch (kind) { + case NullabilityKind::NonNull: + return "__nonnull"; + + case NullabilityKind::Nullable: + return "__nullable"; + + case NullabilityKind::Unspecified: + return "__null_unspecified"; + } +} diff --git a/clang/lib/Parse/ParseObjc.cpp b/clang/lib/Parse/ParseObjc.cpp index 8baeecb..e0f716e 100644 --- a/clang/lib/Parse/ParseObjc.cpp +++ b/clang/lib/Parse/ParseObjc.cpp @@ -13,6 +13,7 @@ #include "clang/Parse/Parser.h" #include "RAIIObjectsForParser.h" +#include "clang/AST/ASTContext.h" #include "clang/Basic/CharInfo.h" #include "clang/Parse/ParseDiagnostic.h" #include "clang/Sema/DeclSpec.h" @@ -307,6 +308,58 @@ Decl *Parser::ParseObjCAtInterfaceDeclaration(SourceLocation AtLoc, return ClsType; } +IdentifierInfo *Parser::getNullabilityKeyword(NullabilityKind nullability) { + switch (nullability) { + case NullabilityKind::NonNull: + if (!Ident___nonnull) + Ident___nonnull = PP.getIdentifierInfo("__nonnull"); + return Ident___nonnull; + + case NullabilityKind::Nullable: + if (!Ident___nullable) + Ident___nullable = PP.getIdentifierInfo("__nullable"); + return Ident___nullable; + + case NullabilityKind::Unspecified: + if (!Ident___null_unspecified) + Ident___null_unspecified = PP.getIdentifierInfo("__null_unspecified"); + return Ident___null_unspecified; + } +} + +/// Add an attribute for a context-sensitive type nullability to the given +/// declarator. +static void addContextSensitiveTypeNullability(Parser &P, + Declarator &D, + NullabilityKind nullability, + SourceLocation nullabilityLoc, + bool &addedToDeclSpec) { + // Create the attribute. + auto getNullabilityAttr = [&]() -> AttributeList * { + auto attr = D.getAttributePool().create( + P.getNullabilityKeyword(nullability), + SourceRange(nullabilityLoc), + nullptr, SourceLocation(), + nullptr, 0, + AttributeList::AS_Keyword); + attr->setContextSensitiveKeywordAttribute(); + return attr; + }; + + if (D.getNumTypeObjects() > 0) { + // Add the attribute to the declarator chunk nearest the declarator. + auto nullabilityAttr = getNullabilityAttr(); + DeclaratorChunk &chunk = D.getTypeObject(0); + nullabilityAttr->setNext(chunk.getAttrListRef()); + chunk.getAttrListRef() = nullabilityAttr; + } else if (!addedToDeclSpec) { + // Otherwise, just put it on the declaration specifiers (if one + // isn't there already). + D.getMutableDeclSpec().addAttributes(getNullabilityAttr()); + addedToDeclSpec = true; + } +} + /// objc-interface-decl-list: /// empty /// objc-interface-decl-list objc-property-decl [OBJC2] @@ -445,6 +498,7 @@ void Parser::ParseObjCInterfaceDeclList(tok::ObjCKeywordKind contextKey, ParseObjCPropertyAttribute(OCDS); } + bool addedToDeclSpec = false; auto ObjCPropertyCallback = [&](ParsingFieldDeclarator &FD) { if (FD.D.getIdentifier() == nullptr) { Diag(AtLoc, diag::err_objc_property_requires_field_name) @@ -457,6 +511,13 @@ void Parser::ParseObjCInterfaceDeclList(tok::ObjCKeywordKind contextKey, return; } + // Map a nullability property attribute to a context-sensitive keyword + // attribute. + if (OCDS.getPropertyAttributes() & ObjCDeclSpec::DQ_PR_nullability) + addContextSensitiveTypeNullability(*this, FD.D, OCDS.getNullability(), + OCDS.getNullabilityLoc(), + addedToDeclSpec); + // Install the property declarator into interfaceDecl. IdentifierInfo *SelName = OCDS.getGetterName() ? OCDS.getGetterName() : FD.D.getIdentifier(); @@ -510,6 +571,24 @@ void Parser::ParseObjCInterfaceDeclList(tok::ObjCKeywordKind contextKey, Actions.ActOnAtEnd(getCurScope(), AtEnd, allMethods, allTUVariables); } +/// Diagnose redundant or conflicting nullability information. +static void diagnoseRedundantPropertyNullability(Parser &P, + ObjCDeclSpec &DS, + NullabilityKind nullability, + SourceLocation nullabilityLoc){ + if (DS.getNullability() == nullability) { + P.Diag(nullabilityLoc, diag::warn_nullability_duplicate) + << static_cast(nullability) << true + << SourceRange(DS.getNullabilityLoc()); + return; + } + + P.Diag(nullabilityLoc, diag::err_nullability_conflicting) + << static_cast(nullability) << true + << static_cast(DS.getNullability()) << true + << SourceRange(DS.getNullabilityLoc()); +} + /// Parse property attribute declarations. /// /// property-attr-decl: '(' property-attrlist ')' @@ -529,6 +608,9 @@ void Parser::ParseObjCInterfaceDeclList(tok::ObjCKeywordKind contextKey, /// strong /// weak /// unsafe_unretained +/// nonnull +/// nullable +/// null_unspecified /// void Parser::ParseObjCPropertyAttribute(ObjCDeclSpec &DS) { assert(Tok.getKind() == tok::l_paren); @@ -614,6 +696,27 @@ void Parser::ParseObjCPropertyAttribute(ObjCDeclSpec &DS) { DS.setPropertyAttributes(ObjCDeclSpec::DQ_PR_getter); DS.setGetterName(SelIdent); } + } else if (II->isStr("nonnull")) { + if (DS.getPropertyAttributes() & ObjCDeclSpec::DQ_PR_nullability) + diagnoseRedundantPropertyNullability(*this, DS, + NullabilityKind::NonNull, + Tok.getLocation()); + DS.setPropertyAttributes(ObjCDeclSpec::DQ_PR_nullability); + DS.setNullability(Tok.getLocation(), NullabilityKind::NonNull); + } else if (II->isStr("nullable")) { + if (DS.getPropertyAttributes() & ObjCDeclSpec::DQ_PR_nullability) + diagnoseRedundantPropertyNullability(*this, DS, + NullabilityKind::Nullable, + Tok.getLocation()); + DS.setPropertyAttributes(ObjCDeclSpec::DQ_PR_nullability); + DS.setNullability(Tok.getLocation(), NullabilityKind::Nullable); + } else if (II->isStr("null_unspecified")) { + if (DS.getPropertyAttributes() & ObjCDeclSpec::DQ_PR_nullability) + diagnoseRedundantPropertyNullability(*this, DS, + NullabilityKind::Unspecified, + Tok.getLocation()); + DS.setPropertyAttributes(ObjCDeclSpec::DQ_PR_nullability); + DS.setNullability(Tok.getLocation(), NullabilityKind::Unspecified); } else { Diag(AttrName, diag::err_objc_expected_property_attr) << II; SkipUntil(tok::r_paren, StopAtSemi); @@ -779,6 +882,17 @@ bool Parser::isTokIdentifier_in() const { /// objc-type-qualifier /// objc-type-qualifiers objc-type-qualifier /// +/// objc-type-qualifier: +/// 'in' +/// 'out' +/// 'inout' +/// 'oneway' +/// 'bycopy' +/// 'byref' +/// 'nonnull' +/// 'nullable' +/// 'null_unspecified' +/// void Parser::ParseObjCTypeQualifierList(ObjCDeclSpec &DS, Declarator::TheContext Context) { assert(Context == Declarator::ObjCParameterContext || @@ -796,10 +910,13 @@ void Parser::ParseObjCTypeQualifierList(ObjCDeclSpec &DS, const IdentifierInfo *II = Tok.getIdentifierInfo(); for (unsigned i = 0; i != objc_NumQuals; ++i) { - if (II != ObjCTypeQuals[i]) + if (II != ObjCTypeQuals[i] || + NextToken().is(tok::less) || + NextToken().is(tok::coloncolon)) continue; ObjCDeclSpec::ObjCDeclQualifier Qual; + NullabilityKind Nullability; switch (i) { default: llvm_unreachable("Unknown decl qualifier"); case objc_in: Qual = ObjCDeclSpec::DQ_In; break; @@ -808,8 +925,28 @@ void Parser::ParseObjCTypeQualifierList(ObjCDeclSpec &DS, case objc_oneway: Qual = ObjCDeclSpec::DQ_Oneway; break; case objc_bycopy: Qual = ObjCDeclSpec::DQ_Bycopy; break; case objc_byref: Qual = ObjCDeclSpec::DQ_Byref; break; + + case objc_nonnull: + Qual = ObjCDeclSpec::DQ_CSNullability; + Nullability = NullabilityKind::NonNull; + break; + + case objc_nullable: + Qual = ObjCDeclSpec::DQ_CSNullability; + Nullability = NullabilityKind::Nullable; + break; + + case objc_null_unspecified: + Qual = ObjCDeclSpec::DQ_CSNullability; + Nullability = NullabilityKind::Unspecified; + break; } + + // FIXME: Diagnose redundant specifiers. DS.setObjCDeclQualifier(Qual); + if (Qual == ObjCDeclSpec::DQ_CSNullability) + DS.setNullability(Tok.getLocation(), Nullability); + ConsumeToken(); II = nullptr; break; @@ -889,6 +1026,14 @@ ParsedType Parser::ParseObjCTypeName(ObjCDeclSpec &DS, // If that's not invalid, extract a type. if (!declarator.isInvalidType()) { + // Map a nullability specifier to a context-sensitive keyword attribute. + bool addedToDeclSpec = false; + if (DS.getObjCDeclQualifier() & ObjCDeclSpec::DQ_CSNullability) + addContextSensitiveTypeNullability(*this, declarator, + DS.getNullability(), + DS.getNullabilityLoc(), + addedToDeclSpec); + TypeResult type = Actions.ActOnTypeName(getCurScope(), declarator); if (!type.isInvalid()) Ty = type.get(); @@ -904,8 +1049,34 @@ ParsedType Parser::ParseObjCTypeName(ObjCDeclSpec &DS, Ident_instancetype = PP.getIdentifierInfo("instancetype"); if (Tok.getIdentifierInfo() == Ident_instancetype) { - Ty = Actions.ActOnObjCInstanceType(Tok.getLocation()); - ConsumeToken(); + SourceLocation loc = ConsumeToken(); + Ty = Actions.ActOnObjCInstanceType(loc); + + // Map a nullability specifier to a context-sensitive keyword attribute. + if (DS.getObjCDeclQualifier() & ObjCDeclSpec::DQ_CSNullability) { + // Synthesize an abstract declarator so we can use Sema::ActOnTypeName. + bool addedToDeclSpec = false; + const char *prevSpec; + unsigned diagID; + DeclSpec declSpec(AttrFactory); + declSpec.setObjCQualifiers(&DS); + declSpec.SetTypeSpecType(DeclSpec::TST_typename, loc, prevSpec, diagID, + Ty, + Actions.getASTContext().getPrintingPolicy()); + declSpec.SetRangeEnd(loc); + Declarator declarator(declSpec, context); + + // Add the context-sensitive keyword attribute. + addContextSensitiveTypeNullability(*this, declarator, + DS.getNullability(), + DS.getNullabilityLoc(), + addedToDeclSpec); + + + TypeResult type = Actions.ActOnTypeName(getCurScope(), declarator); + if (!type.isInvalid()) + Ty = type.get(); + } } } diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index dea7a699..e32df95 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -463,6 +463,10 @@ void Parser::Initialize() { ObjCTypeQuals[objc_oneway] = &PP.getIdentifierTable().get("oneway"); ObjCTypeQuals[objc_bycopy] = &PP.getIdentifierTable().get("bycopy"); ObjCTypeQuals[objc_byref] = &PP.getIdentifierTable().get("byref"); + ObjCTypeQuals[objc_nonnull] = &PP.getIdentifierTable().get("nonnull"); + ObjCTypeQuals[objc_nullable] = &PP.getIdentifierTable().get("nullable"); + ObjCTypeQuals[objc_null_unspecified] + = &PP.getIdentifierTable().get("null_unspecified"); } Ident_instancetype = nullptr; diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index f4af3f3..aa006b3 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -2471,9 +2471,13 @@ static void mergeParamDeclTypes(ParmVarDecl *NewParam, if (auto Oldnullability = OldParam->getType()->getNullability(S.Context)) { if (auto Newnullability = NewParam->getType()->getNullability(S.Context)) { if (*Oldnullability != *Newnullability) { + unsigned unsNewnullability = static_cast(*Newnullability); + unsigned unsOldnullability = static_cast(*Oldnullability); S.Diag(NewParam->getLocation(), diag::warn_mismatched_nullability_attr) - << static_cast(*Newnullability) - << static_cast(*Oldnullability); + << unsNewnullability + << ((NewParam->getObjCDeclQualifier() & Decl::OBJC_TQ_CSNullability) != 0) + << unsOldnullability + << ((OldParam->getObjCDeclQualifier() & Decl::OBJC_TQ_CSNullability) != 0); S.Diag(OldParam->getLocation(), diag::note_previous_declaration); } } diff --git a/clang/lib/Sema/SemaDeclObjC.cpp b/clang/lib/Sema/SemaDeclObjC.cpp index 3831879..78c76ab 100644 --- a/clang/lib/Sema/SemaDeclObjC.cpp +++ b/clang/lib/Sema/SemaDeclObjC.cpp @@ -1366,6 +1366,13 @@ static SourceRange getTypeRange(TypeSourceInfo *TSI) { return (TSI ? TSI->getTypeLoc().getSourceRange() : SourceRange()); } +/// Determine whether two set of Objective-C declaration qualifiers conflict. +static bool objcModifiersConflict(Decl::ObjCDeclQualifier x, + Decl::ObjCDeclQualifier y) { + return (x & ~Decl::OBJC_TQ_CSNullability) != + (y & ~Decl::OBJC_TQ_CSNullability); +} + static bool CheckMethodOverrideReturn(Sema &S, ObjCMethodDecl *MethodImpl, ObjCMethodDecl *MethodDecl, @@ -1373,8 +1380,8 @@ static bool CheckMethodOverrideReturn(Sema &S, bool IsOverridingMode, bool Warn) { if (IsProtocolMethodDecl && - (MethodDecl->getObjCDeclQualifier() != - MethodImpl->getObjCDeclQualifier())) { + objcModifiersConflict(MethodDecl->getObjCDeclQualifier(), + MethodImpl->getObjCDeclQualifier())) { if (Warn) { S.Diag(MethodImpl->getLocation(), (IsOverridingMode @@ -1388,7 +1395,24 @@ static bool CheckMethodOverrideReturn(Sema &S, else return false; } - + if (Warn && IsOverridingMode && + !isa(MethodImpl->getDeclContext()) && + !S.Context.hasSameNullabilityTypeQualifier(MethodImpl->getReturnType(), + MethodDecl->getReturnType(), + false)) { + unsigned unsNullabilityMethodImpl = + static_cast(*MethodImpl->getReturnType()->getNullability(S.Context)); + unsigned unsNullabilityMethodDecl = + static_cast(*MethodDecl->getReturnType()->getNullability(S.Context)); + S.Diag(MethodImpl->getLocation(), + diag::warn_conflicting_nullability_attr_overriding_ret_types) + << unsNullabilityMethodImpl + << ((MethodImpl->getObjCDeclQualifier() & Decl::OBJC_TQ_CSNullability) != 0) + << unsNullabilityMethodDecl + << ((MethodDecl->getObjCDeclQualifier() & Decl::OBJC_TQ_CSNullability) != 0); + S.Diag(MethodDecl->getLocation(), diag::note_previous_declaration); + } + if (S.Context.hasSameUnqualifiedType(MethodImpl->getReturnType(), MethodDecl->getReturnType())) return true; @@ -1438,8 +1462,8 @@ static bool CheckMethodOverrideParam(Sema &S, bool IsOverridingMode, bool Warn) { if (IsProtocolMethodDecl && - (ImplVar->getObjCDeclQualifier() != - IfaceVar->getObjCDeclQualifier())) { + objcModifiersConflict(ImplVar->getObjCDeclQualifier(), + IfaceVar->getObjCDeclQualifier())) { if (Warn) { if (IsOverridingMode) S.Diag(ImplVar->getLocation(), @@ -1459,7 +1483,19 @@ static bool CheckMethodOverrideParam(Sema &S, QualType ImplTy = ImplVar->getType(); QualType IfaceTy = IfaceVar->getType(); - + if (Warn && IsOverridingMode && + !isa(MethodImpl->getDeclContext()) && + !S.Context.hasSameNullabilityTypeQualifier(ImplTy, IfaceTy, true)) { + unsigned unsImplTy = static_cast(*ImplTy->getNullability(S.Context)); + unsigned unsIfaceTy = static_cast(*IfaceTy->getNullability(S.Context)); + S.Diag(ImplVar->getLocation(), + diag::warn_conflicting_nullability_attr_overriding_param_types) + << unsImplTy + << ((ImplVar->getObjCDeclQualifier() & Decl::OBJC_TQ_CSNullability) != 0) + << unsIfaceTy + << ((IfaceVar->getObjCDeclQualifier() & Decl::OBJC_TQ_CSNullability) != 0); + S.Diag(IfaceVar->getLocation(), diag::note_previous_declaration); + } if (S.Context.hasSameUnqualifiedType(ImplTy, IfaceTy)) return true; @@ -3121,6 +3157,89 @@ void Sema::CheckObjCMethodOverrides(ObjCMethodDecl *ObjCMethod, ObjCMethod->setOverriding(hasOverriddenMethodsInBaseOrProtocol); } +/// Merge type nullability from for a redeclaration of the same entity, +/// producing the updated type of the redeclared entity. +static QualType mergeTypeNullabilityForRedecl(Sema &S, SourceLocation loc, + QualType type, + bool usesCSKeyword, + SourceLocation prevLoc, + QualType prevType, + bool prevUsesCSKeyword) { + // Determine the nullability of both types. + auto nullability = type->getNullability(S.Context); + auto prevNullability = prevType->getNullability(S.Context); + + // Easy case: both have nullability. + if (nullability.hasValue() == prevNullability.hasValue()) { + // Neither has nullability; continue. + if (!nullability) + return type; + + // The nullabilities are equivalent; do nothing. + if (*nullability == *prevNullability) + return type; + + // Complain about mismatched nullability. + S.Diag(loc, diag::err_nullability_conflicting) + << static_cast(*nullability) << usesCSKeyword + << static_cast(*prevNullability) << prevUsesCSKeyword; + return type; + } + + // If it's the redeclaration that has nullability, don't change anything. + if (nullability) + return type; + + // Otherwise, provide the result with the same nullability. + return S.Context.getAttributedType( + AttributedType::getNullabilityAttrKind(*prevNullability), + type, type); +} + +/// Merge information from the declaration of a method in the @interface +/// (or a category/extension) into the corresponding method in the +/// @implementation (for a class or category). +static void mergeInterfaceMethodToImpl(Sema &S, + ObjCMethodDecl *method, + ObjCMethodDecl *prevMethod) { + // Merge the objc_requires_super attribute. + if (prevMethod->hasAttr() && + !method->hasAttr()) { + // merge the attribute into implementation. + method->addAttr( + ObjCRequiresSuperAttr::CreateImplicit(S.Context, + method->getLocation())); + } + + // Merge nullability of the result type. + QualType newReturnType + = mergeTypeNullabilityForRedecl( + S, method->getReturnTypeSourceRange().getBegin(), + method->getReturnType(), + method->getObjCDeclQualifier() & Decl::OBJC_TQ_CSNullability, + prevMethod->getReturnTypeSourceRange().getBegin(), + prevMethod->getReturnType(), + prevMethod->getObjCDeclQualifier() & Decl::OBJC_TQ_CSNullability); + method->setReturnType(newReturnType); + + // Handle each of the parameters. + unsigned numParams = method->param_size(); + unsigned numPrevParams = prevMethod->param_size(); + for (unsigned i = 0, n = std::min(numParams, numPrevParams); i != n; ++i) { + ParmVarDecl *param = method->param_begin()[i]; + ParmVarDecl *prevParam = prevMethod->param_begin()[i]; + + // Merge nullability. + QualType newParamType + = mergeTypeNullabilityForRedecl( + S, param->getLocation(), param->getType(), + param->getObjCDeclQualifier() & Decl::OBJC_TQ_CSNullability, + prevParam->getLocation(), prevParam->getType(), + prevParam->getObjCDeclQualifier() & Decl::OBJC_TQ_CSNullability); + param->setType(newParamType); + } +} + Decl *Sema::ActOnMethodDeclaration( Scope *S, SourceLocation MethodLoc, SourceLocation EndLoc, @@ -3151,7 +3270,9 @@ Decl *Sema::ActOnMethodDeclaration( if (CheckFunctionReturnType(resultDeclType, MethodLoc)) return nullptr; - HasRelatedResultType = (resultDeclType == Context.getObjCInstanceType()); + QualType bareResultType = resultDeclType; + (void)AttributedType::stripOuterNullability(bareResultType); + HasRelatedResultType = (bareResultType == Context.getObjCInstanceType()); } else { // get the type for "id". resultDeclType = Context.getObjCIdType(); Diag(MethodLoc, diag::warn_missing_method_return_type) @@ -3252,22 +3373,20 @@ Decl *Sema::ActOnMethodDeclaration( ImpDecl->addClassMethod(ObjCMethod); } - ObjCMethodDecl *IMD = nullptr; - if (ObjCInterfaceDecl *IDecl = ImpDecl->getClassInterface()) - IMD = IDecl->lookupMethod(ObjCMethod->getSelector(), - ObjCMethod->isInstanceMethod()); - if (IMD && IMD->hasAttr() && - !ObjCMethod->hasAttr()) { - // merge the attribute into implementation. - ObjCMethod->addAttr(ObjCRequiresSuperAttr::CreateImplicit(Context, - ObjCMethod->getLocation())); - } - if (isa(ImpDecl)) { - ObjCMethodFamily family = - ObjCMethod->getSelector().getMethodFamily(); - if (family == OMF_dealloc && IMD && IMD->isOverriding()) - Diag(ObjCMethod->getLocation(), diag::warn_dealloc_in_category) - << ObjCMethod->getDeclName(); + // Merge information from the @interface declaration into the + // @implementation. + if (ObjCInterfaceDecl *IDecl = ImpDecl->getClassInterface()) { + if (auto *IMD = IDecl->lookupMethod(ObjCMethod->getSelector(), + ObjCMethod->isInstanceMethod())) { + mergeInterfaceMethodToImpl(*this, ObjCMethod, IMD); + + // Warn about defining -dealloc in a category. + if (isa(ImpDecl) && IMD->isOverriding() && + ObjCMethod->getSelector().getMethodFamily() == OMF_dealloc) { + Diag(ObjCMethod->getLocation(), diag::warn_dealloc_in_category) + << ObjCMethod->getDeclName(); + } + } } } else { cast(ClassDecl)->addDecl(ObjCMethod); diff --git a/clang/lib/Sema/SemaExprObjC.cpp b/clang/lib/Sema/SemaExprObjC.cpp index 63b7485..72fc47f 100644 --- a/clang/lib/Sema/SemaExprObjC.cpp +++ b/clang/lib/Sema/SemaExprObjC.cpp @@ -1135,49 +1135,150 @@ ObjCMethodDecl *Sema::tryCaptureObjCSelf(SourceLocation Loc) { } static QualType stripObjCInstanceType(ASTContext &Context, QualType T) { + QualType origType = T; + if (auto nullability = AttributedType::stripOuterNullability(T)) { + if (T == Context.getObjCInstanceType()) { + return Context.getAttributedType( + AttributedType::getNullabilityAttrKind(*nullability), + Context.getObjCIdType(), + Context.getObjCIdType()); + } + + return origType; + } + if (T == Context.getObjCInstanceType()) return Context.getObjCIdType(); - return T; + return origType; } -QualType Sema::getMessageSendResultType(QualType ReceiverType, - ObjCMethodDecl *Method, - bool isClassMessage, bool isSuperMessage) { +/// Determine the result type of a message send based on the receiver type, +/// method, and the kind of message send. +/// +/// This is the "base" result type, which will still need to be adjusted +/// to account for nullability. +static QualType getBaseMessageSendResultType(Sema &S, + QualType ReceiverType, + ObjCMethodDecl *Method, + bool isClassMessage, + bool isSuperMessage) { assert(Method && "Must have a method"); if (!Method->hasRelatedResultType()) return Method->getSendResultType(); - + + ASTContext &Context = S.Context; + + // Local function that transfers the nullability of the method's + // result type to the returned result. + auto transferNullability = [&](QualType type) -> QualType { + // If the method's result type has nullability, extract it. + if (auto nullability = Method->getSendResultType()->getNullability(Context)){ + // Strip off any outer nullability sugar from the provided type. + (void)AttributedType::stripOuterNullability(type); + + // Form a new attributed type using the method result type's nullability. + return Context.getAttributedType( + AttributedType::getNullabilityAttrKind(*nullability), + type, + type); + } + + return type; + }; + // If a method has a related return type: // - if the method found is an instance method, but the message send // was a class message send, T is the declared return type of the method // found if (Method->isInstanceMethod() && isClassMessage) return stripObjCInstanceType(Context, Method->getSendResultType()); - - // - if the receiver is super, T is a pointer to the class of the + + // - if the receiver is super, T is a pointer to the class of the // enclosing method definition if (isSuperMessage) { - if (ObjCMethodDecl *CurMethod = getCurMethodDecl()) - if (ObjCInterfaceDecl *Class = CurMethod->getClassInterface()) - return Context.getObjCObjectPointerType( - Context.getObjCInterfaceType(Class)); + if (ObjCMethodDecl *CurMethod = S.getCurMethodDecl()) + if (ObjCInterfaceDecl *Class = CurMethod->getClassInterface()) { + return transferNullability( + Context.getObjCObjectPointerType( + Context.getObjCInterfaceType(Class))); + } } - + // - if the receiver is the name of a class U, T is a pointer to U if (ReceiverType->getAs() || ReceiverType->isObjCQualifiedInterfaceType()) - return Context.getObjCObjectPointerType(ReceiverType); - // - if the receiver is of type Class or qualified Class type, + return transferNullability(Context.getObjCObjectPointerType(ReceiverType)); + // - if the receiver is of type Class or qualified Class type, // T is the declared return type of the method. if (ReceiverType->isObjCClassType() || ReceiverType->isObjCQualifiedClassType()) return stripObjCInstanceType(Context, Method->getSendResultType()); - + // - if the receiver is id, qualified id, Class, or qualified Class, T // is the receiver type, otherwise // - T is the type of the receiver expression. - return ReceiverType; + return transferNullability(ReceiverType); +} + +QualType Sema::getMessageSendResultType(QualType ReceiverType, + ObjCMethodDecl *Method, + bool isClassMessage, + bool isSuperMessage) { + // Produce the result type. + QualType resultType = getBaseMessageSendResultType(*this, ReceiverType, + Method, + isClassMessage, + isSuperMessage); + + // Map the nullability of the result into a table index. + unsigned receiverNullabilityIdx = 0; + if (auto nullability = ReceiverType->getNullability(Context)) + receiverNullabilityIdx = 1 + static_cast(*nullability); + + unsigned resultNullabilityIdx = 0; + if (auto nullability = resultType->getNullability(Context)) + resultNullabilityIdx = 1 + static_cast(*nullability); + + // The table of nullability mappings, indexed by the receiver's nullability + // and then the result type's nullability. + static const uint8_t None = 0; + static const uint8_t NonNull = 1; + static const uint8_t Nullable = 2; + static const uint8_t Unspecified = 3; + static const uint8_t nullabilityMap[4][4] = { + // None NonNull Nullable Unspecified + /* None */ { None, None, Nullable, None }, + /* NonNull */ { None, NonNull, Nullable, Unspecified }, + /* Nullable */ { Nullable, Nullable, Nullable, Nullable }, + /* Unspecified */ { None, Unspecified, Nullable, Unspecified } + }; + + unsigned newResultNullabilityIdx + = nullabilityMap[receiverNullabilityIdx][resultNullabilityIdx]; + if (newResultNullabilityIdx == resultNullabilityIdx) + return resultType; + + // Strip off the existing nullability. This removes as little type sugar as + // possible. + do { + if (auto attributed = dyn_cast(resultType.getTypePtr())) { + resultType = attributed->getModifiedType(); + } else { + resultType = resultType.getDesugaredType(Context); + } + } while (resultType->getNullability(Context)); + + // Add nullability back if needed. + if (newResultNullabilityIdx > 0) { + auto newNullability + = static_cast(newResultNullabilityIdx-1); + return Context.getAttributedType( + AttributedType::getNullabilityAttrKind(newNullability), + resultType, resultType); + } + + return resultType; } /// Look for an ObjC method whose result type exactly matches the given type. diff --git a/clang/lib/Sema/SemaObjCProperty.cpp b/clang/lib/Sema/SemaObjCProperty.cpp index 5e7b4b8..f62af8e 100644 --- a/clang/lib/Sema/SemaObjCProperty.cpp +++ b/clang/lib/Sema/SemaObjCProperty.cpp @@ -149,7 +149,6 @@ Decl *Sema::ActOnProperty(Scope *S, SourceLocation AtLoc, TypeSourceInfo *TSI = GetTypeForDeclarator(FD.D, S); QualType T = TSI->getType(); Attributes |= deduceWeakPropertyFromType(*this, T); - bool isReadWrite = ((Attributes & ObjCDeclSpec::DQ_PR_readwrite) || // default is readwrite! !(Attributes & ObjCDeclSpec::DQ_PR_readonly)); @@ -173,7 +172,7 @@ Decl *Sema::ActOnProperty(Scope *S, SourceLocation AtLoc, isAssign, isReadWrite, Attributes, ODS.getPropertyAttributes(), - isOverridingProperty, TSI, + isOverridingProperty, T, TSI, MethodImplKind); if (!Res) return nullptr; @@ -184,7 +183,7 @@ Decl *Sema::ActOnProperty(Scope *S, SourceLocation AtLoc, Res = CreatePropertyDecl(S, ClassDecl, AtLoc, LParenLoc, FD, GetterSel, SetterSel, isAssign, isReadWrite, Attributes, ODS.getPropertyAttributes(), - TSI, MethodImplKind); + T, TSI, MethodImplKind); if (lexicalDC) Res->setLexicalDeclContext(lexicalDC); } @@ -322,7 +321,8 @@ Sema::HandlePropertyInClassExtension(Scope *S, const unsigned Attributes, const unsigned AttributesAsWritten, bool *isOverridingProperty, - TypeSourceInfo *T, + QualType T, + TypeSourceInfo *TSI, tok::ObjCKeywordKind MethodImplKind) { ObjCCategoryDecl *CDecl = cast(CurContext); // Diagnose if this property is already in continuation class. @@ -348,7 +348,7 @@ Sema::HandlePropertyInClassExtension(Scope *S, // FIXME. We should really be using CreatePropertyDecl for this. ObjCPropertyDecl *PDecl = ObjCPropertyDecl::Create(Context, DC, FD.D.getIdentifierLoc(), - PropertyId, AtLoc, LParenLoc, T); + PropertyId, AtLoc, LParenLoc, T, TSI); PDecl->setPropertyAttributesAsWritten( makePropertyAttributesAsWritten(AttributesAsWritten)); if (Attributes & ObjCDeclSpec::DQ_PR_readonly) @@ -359,6 +359,8 @@ Sema::HandlePropertyInClassExtension(Scope *S, PDecl->setPropertyAttributes(ObjCPropertyDecl::OBJC_PR_nonatomic); if (Attributes & ObjCDeclSpec::DQ_PR_atomic) PDecl->setPropertyAttributes(ObjCPropertyDecl::OBJC_PR_atomic); + if (Attributes & ObjCDeclSpec::DQ_PR_nullability) + PDecl->setPropertyAttributes(ObjCPropertyDecl::OBJC_PR_nullability); // Set setter/getter selector name. Needed later. PDecl->setGetterName(GetterSel); PDecl->setSetterName(SetterSel); @@ -383,7 +385,8 @@ Sema::HandlePropertyInClassExtension(Scope *S, ObjCPropertyDecl *PrimaryPDecl = CreatePropertyDecl(S, CCPrimary, AtLoc, LParenLoc, FD, GetterSel, SetterSel, isAssign, isReadWrite, - Attributes,AttributesAsWritten, T, MethodImplKind, DC); + Attributes,AttributesAsWritten, T, TSI, MethodImplKind, + DC); // A case of continuation class adding a new property in the class. This // is not what it was meant for. However, gcc supports it and so should we. @@ -531,11 +534,11 @@ ObjCPropertyDecl *Sema::CreatePropertyDecl(Scope *S, const bool isReadWrite, const unsigned Attributes, const unsigned AttributesAsWritten, + QualType T, TypeSourceInfo *TInfo, tok::ObjCKeywordKind MethodImplKind, DeclContext *lexicalDC){ IdentifierInfo *PropertyId = FD.D.getIdentifier(); - QualType T = TInfo->getType(); // Issue a warning if property is 'assign' as default and its object, which is // gc'able conforms to NSCopying protocol @@ -564,7 +567,8 @@ ObjCPropertyDecl *Sema::CreatePropertyDecl(Scope *S, DeclContext *DC = cast(CDecl); ObjCPropertyDecl *PDecl = ObjCPropertyDecl::Create(Context, DC, FD.D.getIdentifierLoc(), - PropertyId, AtLoc, LParenLoc, TInfo); + PropertyId, AtLoc, + LParenLoc, T, TInfo); if (ObjCPropertyDecl *prevDecl = ObjCPropertyDecl::findPropertyDecl(DC, PropertyId)) { @@ -639,6 +643,9 @@ ObjCPropertyDecl *Sema::CreatePropertyDecl(Scope *S, else if (MethodImplKind == tok::objc_optional) PDecl->setPropertyImplementation(ObjCPropertyDecl::Optional); + if (Attributes & ObjCDeclSpec::DQ_PR_nullability) + PDecl->setPropertyAttributes(ObjCPropertyDecl::OBJC_PR_nullability); + return PDecl; } @@ -2228,6 +2235,23 @@ void Sema::CheckObjCPropertyAttributes(Decl *PDecl, Attributes &= ~ObjCDeclSpec::DQ_PR_weak; } + if ((Attributes & ObjCDeclSpec::DQ_PR_weak) && + !(Attributes & ObjCDeclSpec::DQ_PR_readonly)) { + // 'weak' and 'nonnull' are mutually exclusive. + if (auto nullability = PropertyTy->getNullability(Context)) { + if (*nullability == NullabilityKind::NonNull) + Diag(Loc, diag::err_objc_property_attr_mutually_exclusive) + << "nonnull" << "weak"; + } else { + PropertyTy = + Context.getAttributedType( + AttributedType::getNullabilityAttrKind(NullabilityKind::Nullable), + PropertyTy, PropertyTy); + TypeSourceInfo *TSInfo = PropertyDecl->getTypeSourceInfo(); + PropertyDecl->setType(PropertyTy, TSInfo); + } + } + if ((Attributes & ObjCDeclSpec::DQ_PR_atomic) && (Attributes & ObjCDeclSpec::DQ_PR_nonatomic)) { Diag(Loc, diag::err_objc_property_attr_mutually_exclusive) diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp index ea749a4..750172c 100644 --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -4522,34 +4522,10 @@ static bool handleMSPointerTypeQualifierAttr(TypeProcessingState &State, return false; } -/// Map a nullability attribute kind to a nullability kind. -static NullabilityKind mapNullabilityAttrKind(AttributeList::Kind kind) { - switch (kind) { - case AttributeList::AT_TypeNonNull: - return NullabilityKind::NonNull; - - case AttributeList::AT_TypeNullable: - return NullabilityKind::Nullable; - - case AttributeList::AT_TypeNullUnspecified: - return NullabilityKind::Unspecified; - - default: - llvm_unreachable("not a nullability attribute kind"); - } -} - -/// Handle a nullability type attribute. -static bool handleNullabilityTypeAttr(TypeProcessingState &state, - AttributeList &attr, - QualType &type) { - Sema &S = state.getSema(); - ASTContext &Context = S.Context; - - // Determine the nullability. - AttributeList::Kind kind = attr.getKind(); - NullabilityKind nullability = mapNullabilityAttrKind(kind); - +bool Sema::checkNullabilityTypeSpecifier(QualType &type, + NullabilityKind nullability, + SourceLocation nullabilityLoc, + bool isContextSensitive) { // Check for existing nullability attributes on the type. QualType desugared = type; while (auto attributed = dyn_cast(desugared.getTypePtr())) { @@ -4557,19 +4533,24 @@ static bool handleNullabilityTypeAttr(TypeProcessingState &state, if (auto existingNullability = attributed->getImmediateNullability()) { // Duplicated nullability. if (nullability == *existingNullability) { - S.Diag(attr.getLoc(), diag::warn_duplicate_nullability) - << static_cast(nullability); - return true; - } + Diag(nullabilityLoc, diag::warn_nullability_duplicate) + << static_cast(nullability) + << isContextSensitive + << FixItHint::CreateRemoval(nullabilityLoc); + + break; + } // Conflicting nullability. - S.Diag(attr.getLoc(), diag::err_nullability_conflicting) - << static_cast(nullability) - << static_cast(*existingNullability); + Diag(nullabilityLoc, diag::err_nullability_conflicting) + << static_cast(nullability) + << isContextSensitive + << static_cast(*existingNullability) + << false; return true; } - desugared = attributed->getEquivalentType(); + desugared = attributed->getModifiedType(); } // If there is already a different nullability specifier, complain. @@ -4578,8 +4559,9 @@ static bool handleNullabilityTypeAttr(TypeProcessingState &state, // provide a useful Fix-It. if (auto existingNullability = desugared->getNullability(Context)) { if (nullability != *existingNullability) { - S.Diag(attr.getLoc(), diag::err_nullability_conflicting) + Diag(nullabilityLoc, diag::err_nullability_conflicting) << static_cast(nullability) + << isContextSensitive << static_cast(*existingNullability); // Try to find the typedef with the existing nullability specifier. @@ -4589,7 +4571,7 @@ static bool handleNullabilityTypeAttr(TypeProcessingState &state, if (auto typedefNullability = AttributedType::stripOuterNullability(underlyingType)) { if (*typedefNullability == *existingNullability) { - S.Diag(typedefDecl->getLocation(), diag::note_nullability_here) + Diag(typedefDecl->getLocation(), diag::note_nullability_here) << static_cast(*existingNullability); } } @@ -4600,31 +4582,35 @@ static bool handleNullabilityTypeAttr(TypeProcessingState &state, } // If this definitely isn't a pointer type, reject the specifier. - if (!type->canHaveNullability()) { - S.Diag(attr.getLoc(), diag::err_nullability_nonpointer) - << static_cast(nullability) << type; + if (!desugared->canHaveNullability()) { + Diag(nullabilityLoc, diag::err_nullability_nonpointer) + << static_cast(nullability) << isContextSensitive << type; return true; } + + // For the context-sensitive keywords/Objective-C property + // attributes, require that the type be a single-level pointer. + if (isContextSensitive) { + // Make sure that the pointee isn't itself a pointer type. + QualType pointeeType = desugared->getPointeeType(); + if (pointeeType->isAnyPointerType() || + pointeeType->isObjCObjectPointerType() || + pointeeType->isMemberPointerType()) { + Diag(nullabilityLoc, diag::err_nullability_cs_multilevel) + << static_cast(nullability) + << type; + Diag(nullabilityLoc, diag::note_nullability_type_specifier) + << static_cast(nullability) + << type + << FixItHint::CreateReplacement(nullabilityLoc, + getNullabilitySpelling(nullability)); + return true; + } + } // Form the attributed type. - AttributedType::Kind typeAttrKind; - switch (kind) { - case AttributeList::AT_TypeNonNull: - typeAttrKind = AttributedType::attr_nonnull; - break; - - case AttributeList::AT_TypeNullable: - typeAttrKind = AttributedType::attr_nullable; - break; - - case AttributeList::AT_TypeNullUnspecified: - typeAttrKind = AttributedType::attr_null_unspecified; - break; - - default: - llvm_unreachable("Not a nullability specifier"); - } - type = S.Context.getAttributedType(typeAttrKind, type, type); + type = Context.getAttributedType( + AttributedType::getNullabilityAttrKind(nullability), type, type); return false; } @@ -4642,6 +4628,23 @@ static bool hasNullabilityAttr(const AttributeList *attrs) { return false; } +/// Map a nullability attribute kind to a nullability kind. +static NullabilityKind mapNullabilityAttrKind(AttributeList::Kind kind) { + switch (kind) { + case AttributeList::AT_TypeNonNull: + return NullabilityKind::NonNull; + + case AttributeList::AT_TypeNullable: + return NullabilityKind::Nullable; + + case AttributeList::AT_TypeNullUnspecified: + return NullabilityKind::Unspecified; + + default: + llvm_unreachable("not a nullability attribute kind"); + } +} + /// Distribute a nullability type attribute that cannot be applied to /// the type specifier to a pointer, block pointer, or member pointer /// declarator, complaining if necessary. @@ -5233,7 +5236,11 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type, // dependent type, because that complicates the user model. if (type->canHaveNullability() || type->isDependentType() || !distributeNullabilityTypeAttr(state, type, attr)) { - if (handleNullabilityTypeAttr(state, attr, type)) { + if (state.getSema().checkNullabilityTypeSpecifier( + type, + mapNullabilityAttrKind(attr.getKind()), + attr.getLoc(), + attr.isContextSensitiveKeywordAttribute())) { attr.setInvalid(); } diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index c4cd226..73dde7c 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -5392,7 +5392,7 @@ QualType TreeTransform::TransformAttributedType( if (auto nullability = oldType->getImmediateNullability()) { if (!modifiedType->canHaveNullability()) { SemaRef.Diag(TL.getAttrNameLoc(), diag::err_nullability_nonpointer) - << static_cast(*nullability) << modifiedType; + << static_cast(*nullability) << false << modifiedType; return QualType(); } } diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp index a3cbce0..7cff979 100644 --- a/clang/lib/Serialization/ASTReaderDecl.cpp +++ b/clang/lib/Serialization/ASTReaderDecl.cpp @@ -1042,7 +1042,9 @@ void ASTDeclReader::VisitObjCPropertyDecl(ObjCPropertyDecl *D) { VisitNamedDecl(D); D->setAtLoc(ReadSourceLocation(Record, Idx)); D->setLParenLoc(ReadSourceLocation(Record, Idx)); - D->setType(GetTypeSourceInfo(Record, Idx)); + QualType T = Reader.readType(F, Record, Idx); + TypeSourceInfo *TSI = GetTypeSourceInfo(Record, Idx); + D->setType(T, TSI); // FIXME: stable encoding D->setPropertyAttributes( (ObjCPropertyDecl::PropertyAttributeKind)Record[Idx++]); diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp index f69367f..6c5bc5b 100644 --- a/clang/lib/Serialization/ASTWriterDecl.cpp +++ b/clang/lib/Serialization/ASTWriterDecl.cpp @@ -542,7 +542,7 @@ void ASTDeclWriter::VisitObjCMethodDecl(ObjCMethodDecl *D) { // FIXME: stable encoding for @required/@optional Record.push_back(D->getImplementationControl()); - // FIXME: stable encoding for in/out/inout/bycopy/byref/oneway + // FIXME: stable encoding for in/out/inout/bycopy/byref/oneway/nullability Record.push_back(D->getObjCDeclQualifier()); Record.push_back(D->hasRelatedResultType()); Writer.AddTypeRef(D->getReturnType(), Record); @@ -678,6 +678,7 @@ void ASTDeclWriter::VisitObjCPropertyDecl(ObjCPropertyDecl *D) { VisitNamedDecl(D); Writer.AddSourceLocation(D->getAtLoc(), Record); Writer.AddSourceLocation(D->getLParenLoc(), Record); + Writer.AddTypeRef(D->getType(), Record); Writer.AddTypeSourceInfo(D->getTypeSourceInfo(), Record); // FIXME: stable encoding Record.push_back((unsigned)D->getPropertyAttributes()); diff --git a/clang/test/Index/comment-objc-decls.m b/clang/test/Index/comment-objc-decls.m index ae3b0bb..d53757c 100644 --- a/clang/test/Index/comment-objc-decls.m +++ b/clang/test/Index/comment-objc-decls.m @@ -20,19 +20,19 @@ * \param[in] range output value is unsigned int * \result return index */ -- (unsigned int)MethodMyProto:(id)anObject inRange:(unsigned int)range; +- (unsigned int)MethodMyProto:(nullable id)anObject inRange:(unsigned int)range; /** * \brief PropertyMyProto - This is protocol's property. */ -@property (copy) id PropertyMyProto; +@property (copy, nonnull) id PropertyMyProto; /** * \brief ClassMethodMyProto */ + ClassMethodMyProto; @end // CHECK: @protocol MyProto\n@end -// CHECK: - (unsigned int)MethodMyProto:(id)anObject inRange:(unsigned int)range; -// CHECK: @optional\n@property(readwrite, copy, atomic) id PropertyMyProto; +// CHECK: - (unsigned int)MethodMyProto:(nullable id)anObject inRange:(unsigned int)range; +// CHECK: @optional\n@property(readwrite, copy, atomic, nonnull) id PropertyMyProto; // CHECK: + (id)ClassMethodMyProto; /** diff --git a/clang/test/SemaObjC/arc-property-decl-attrs.m b/clang/test/SemaObjC/arc-property-decl-attrs.m index 283772c..e5b8f28 100644 --- a/clang/test/SemaObjC/arc-property-decl-attrs.m +++ b/clang/test/SemaObjC/arc-property-decl-attrs.m @@ -79,3 +79,11 @@ @property (readwrite) id frr; @end +// rdar://20152386 +@interface NSObject @end + +@interface rdar20152386_2: NSObject +@property(nonatomic, weak, nonnull) id delegate; // expected-error {{property attributes 'nonnull' and 'weak' are mutually exclusive}} +@property(nonatomic, weak, nonnull, readonly) id ReadDelegate; // no warning +@end + diff --git a/clang/test/SemaObjC/arc-unavailable-for-weakref.m b/clang/test/SemaObjC/arc-unavailable-for-weakref.m index 8274802..53ceaa1 100644 --- a/clang/test/SemaObjC/arc-unavailable-for-weakref.m +++ b/clang/test/SemaObjC/arc-unavailable-for-weakref.m @@ -56,7 +56,7 @@ __attribute__((objc_arc_weak_reference_unavailable)) @interface I { } -@property (weak) NSFont *font; // expected-error {{synthesizing __weak instance variable of type 'NSFont *', which does not support weak references}} +@property (weak) NSFont *font; // expected-error {{synthesizing __weak instance variable of type 'NSFont * __nullable', which does not support weak references}} @end @implementation I // expected-note {{when implemented by class I}} @@ -65,7 +65,7 @@ __attribute__((objc_arc_weak_reference_unavailable)) // rdar://13676793 @protocol MyProtocol -@property (weak) NSFont *font; // expected-error {{synthesizing __weak instance variable of type 'NSFont *', which does not support weak references}} +@property (weak) NSFont *font; // expected-error {{synthesizing __weak instance variable of type 'NSFont * __nullable', which does not support weak references}} @end @interface I1 @@ -76,7 +76,7 @@ __attribute__((objc_arc_weak_reference_unavailable)) @end @interface Super -@property (weak) NSFont *font; // expected-error {{synthesizing __weak instance variable of type 'NSFont *', which does not support weak references}} +@property (weak) NSFont *font; // expected-error {{synthesizing __weak instance variable of type 'NSFont * __nullable', which does not support weak references}} @end diff --git a/clang/test/SemaObjC/nullability.m b/clang/test/SemaObjC/nullability.m index 0bcc0cb..b852584 100644 --- a/clang/test/SemaObjC/nullability.m +++ b/clang/test/SemaObjC/nullability.m @@ -20,9 +20,6 @@ typedef __nonnull NSFoo * __nullable conflict_NSFoo_ptr_2; // expected-error{{'_ void testBlocksPrinting(NSFoo * __nullable (^bp)(int)) { int *ip = bp; // expected-error{{'NSFoo * __nullable (^)(int)'}} } -void test_accepts_nonnull_null_pointer_literal(NSFoo *foo) { - [foo methodTakingIntPtr: 0]; // expected-warning{{null passed to a callee that requires a non-null argument}} -} // Check returning nil from a __nonnull-returning method. @implementation NSFoo @@ -31,3 +28,151 @@ void test_accepts_nonnull_null_pointer_literal(NSFoo *foo) { return 0; // no warning } @end + +// Context-sensitive keywords and property attributes for nullability. +__attribute__((objc_root_class)) +@interface NSBar +- (nonnull NSFoo *)methodWithFoo:(nonnull NSFoo *)foo; + +- (nonnull NSFoo **)invalidMethod1; // expected-error{{nullability keyword 'nonnull' cannot be applied to multi-level pointer type 'NSFoo **'}} +// expected-note@-1{{use nullability type specifier '__nonnull' to affect the innermost pointer type of 'NSFoo **'}} +- (nonnull NSFoo * __nullable)conflictingMethod1; // expected-error{{nullability specifier '__nullable' conflicts with existing specifier '__nonnull'}} +- (nonnull NSFoo * __nonnull)redundantMethod1; // expected-warning{{duplicate nullability specifier '__nonnull'}} + +@property(nonnull,retain) NSFoo *property1; +@property(nullable,assign) NSFoo ** invalidProperty1; // expected-error{{nullability keyword 'nullable' cannot be applied to multi-level pointer type 'NSFoo **'}} +// expected-note@-1{{use nullability type specifier '__nullable' to affect the innermost pointer type of 'NSFoo **'}} +@property(null_unspecified,retain) NSFoo * __nullable conflictingProperty1; // expected-error{{nullability specifier '__nullable' conflicts with existing specifier '__null_unspecified'}} +@property(retain,nonnull) NSFoo * __nonnull redundantProperty1; // expected-warning{{duplicate nullability specifier '__nonnull'}} + +@property(null_unspecified,retain,nullable) NSFoo *conflictingProperty3; // expected-error{{nullability specifier 'nullable' conflicts with existing specifier 'null_unspecified'}} +@property(nullable,retain,nullable) NSFoo *redundantProperty3; // expected-warning{{duplicate nullability specifier 'nullable'}} +@end + +@interface NSBar () +@property(nonnull,retain) NSFoo *property2; +@property(nullable,assign) NSFoo ** invalidProperty2; // expected-error{{nullability keyword 'nullable' cannot be applied to multi-level pointer type 'NSFoo **'}} +// expected-note@-1{{use nullability type specifier '__nullable' to affect the innermost pointer type of 'NSFoo **'}} +@property(null_unspecified,retain) NSFoo * __nullable conflictingProperty2; // expected-error{{nullability specifier '__nullable' conflicts with existing specifier '__null_unspecified'}} +@property(retain,nonnull) NSFoo * __nonnull redundantProperty2; // expected-warning{{duplicate nullability specifier '__nonnull'}} +@end + +void test_accepts_nonnull_null_pointer_literal(NSFoo *foo, __nonnull NSBar *bar) { + [foo methodTakingIntPtr: 0]; // expected-warning{{null passed to a callee that requires a non-null argument}} + [bar methodWithFoo: 0]; // expected-warning{{null passed to a callee that requires a non-null argument}} + bar.property1 = 0; // expected-warning{{null passed to a callee that requires a non-null argument}} + bar.property2 = 0; // expected-warning{{null passed to a callee that requires a non-null argument}} + [bar setProperty1: 0]; // expected-warning{{null passed to a callee that requires a non-null argument}} + [bar setProperty2: 0]; // expected-warning{{null passed to a callee that requires a non-null argument}} + int *ptr = bar.property1; // expected-warning{{incompatible pointer types initializing 'int *' with an expression of type 'NSFoo * __nonnull'}} +} + +// Check returning nil from a nonnull-returning method. +@implementation NSBar +- (nonnull NSFoo *)methodWithFoo:(nonnull NSFoo *)foo { + return 0; // no warning +} + +- (NSFoo **)invalidMethod1 { + return 0; +} + +- (NSFoo *)conflictingMethod1 { + return 0; // no warning +} +- (NSFoo *)redundantMethod1 { + int *ip = 0; + return ip; // expected-warning{{result type 'NSFoo * __nonnull'}} +} +@end + +__attribute__((objc_root_class)) +@interface NSMerge +- (nonnull NSFoo *)methodA:(nonnull NSFoo*)foo; +- (nonnull NSFoo *)methodB:(nonnull NSFoo*)foo; +- (NSFoo *)methodC:(NSFoo*)foo; +@end + +@implementation NSMerge +- (NSFoo *)methodA:(NSFoo*)foo { + int *ptr = foo; // expected-warning{{incompatible pointer types initializing 'int *' with an expression of type 'NSFoo * __nonnull'}} + return ptr; // expected-warning{{result type 'NSFoo * __nonnull'}} +} + +- (nullable NSFoo *)methodB:(null_unspecified NSFoo*)foo { // expected-error{{nullability specifier 'nullable' conflicts with existing specifier 'nonnull'}} \ + // expected-error{{nullability specifier 'null_unspecified' conflicts with existing specifier 'nonnull'}} + return 0; +} + +- (nonnull NSFoo *)methodC:(nullable NSFoo*)foo { + int *ip = 0; + return ip; // expected-warning{{result type 'NSFoo * __nonnull'}} +} +@end + +// Checking merging of nullability when sending a message. +@interface NSMergeReceiver +- (id)returnsNone; +- (nonnull id)returnsNonNull; +- (nullable id)returnsNullable; +- (null_unspecified id)returnsNullUnspecified; +@end + +void test_receiver_merge(NSMergeReceiver *none, + __nonnull NSMergeReceiver *nonnull, + __nullable NSMergeReceiver *nullable, + __null_unspecified NSMergeReceiver *null_unspecified) { + int *ptr; + + ptr = [nullable returnsNullable]; // expected-warning{{'id __nullable'}} + ptr = [nullable returnsNullUnspecified]; // expected-warning{{'id __nullable'}} + ptr = [nullable returnsNonNull]; // expected-warning{{'id __nullable'}} + ptr = [nullable returnsNone]; // expected-warning{{'id __nullable'}} + + ptr = [null_unspecified returnsNullable]; // expected-warning{{'id __nullable'}} + ptr = [null_unspecified returnsNullUnspecified]; // expected-warning{{'id __null_unspecified'}} + ptr = [null_unspecified returnsNonNull]; // expected-warning{{'id __null_unspecified'}} + ptr = [null_unspecified returnsNone]; // expected-warning{{'id'}} + + ptr = [nonnull returnsNullable]; // expected-warning{{'id __nullable'}} + ptr = [nonnull returnsNullUnspecified]; // expected-warning{{'id __null_unspecified'}} + ptr = [nonnull returnsNonNull]; // expected-warning{{'id __nonnull'}} + ptr = [nonnull returnsNone]; // expected-warning{{'id'}} + + ptr = [none returnsNullable]; // expected-warning{{'id __nullable'}} + ptr = [none returnsNullUnspecified]; // expected-warning{{'id'}} + ptr = [none returnsNonNull]; // expected-warning{{'id'}} + ptr = [none returnsNone]; // expected-warning{{'id'}} + +} + +// instancetype +@protocol Initializable +- (instancetype)initWithBlah:(id)blah; +@end + +__attribute__((objc_root_class)) +@interface InitializableClass +- (nonnull instancetype)initWithBlah:(nonnull id)blah; +- (nullable instancetype)returnMe; ++ (nullable instancetype)returnInstanceOfMe; +@end + +void test_instancetype(InitializableClass * __nonnull ic, id __nonnull object) { + int *ip = [ic returnMe]; // expected-warning{{incompatible pointer types initializing 'int *' with an expression of type 'InitializableClass * __nullable'}} + ip = [InitializableClass returnMe]; // expected-warning{{incompatible pointer types assigning to 'int *' from 'id __nullable'}} + ip = [InitializableClass returnInstanceOfMe]; // expected-warning{{incompatible pointer types assigning to 'int *' from 'InitializableClass * __nullable'}} + ip = [object returnMe]; // expected-warning{{incompatible pointer types assigning to 'int *' from 'id __nullable'}} +} +// rdar://problem/19814852 +@interface MultiProp +@property (nullable, copy) id a, b, c; +@property (nullable, copy) MultiProp *d, *(^e)(int); +@end + +void testMultiProp(MultiProp *foo) { + int *ip; + ip = foo.a; // expected-warning{{from 'id __nullable'}} + ip = foo.d; // expected-warning{{from 'MultiProp * __nullable'}} + ip = foo.e; // expected-error{{incompatible type 'MultiProp *(^ __nullable)(int)'}} +} diff --git a/clang/test/SemaObjC/nullable-weak-property.m b/clang/test/SemaObjC/nullable-weak-property.m new file mode 100644 index 0000000..617ff4e --- /dev/null +++ b/clang/test/SemaObjC/nullable-weak-property.m @@ -0,0 +1,18 @@ +// RUN: %clang_cc1 -fobjc-arc -fobjc-runtime-has-weak -Wnullable-to-nonnull-conversion %s -verify + + +// rdar://19985330 +@interface NSObject @end + +@class NSFoo; +void foo (NSFoo * __nonnull); + +@interface NSBar : NSObject +@property(weak) NSFoo *property1; +@end + +@implementation NSBar +- (void) Meth { + foo (self.property1); // expected-warning {{implicit conversion from nullable pointer 'NSFoo * __nullable' to non-nullable pointer type 'NSFoo * __nonnull'}} +} +@end diff --git a/clang/test/SemaObjC/override-nullability.m b/clang/test/SemaObjC/override-nullability.m new file mode 100644 index 0000000..8e29f91 --- /dev/null +++ b/clang/test/SemaObjC/override-nullability.m @@ -0,0 +1,15 @@ +// RUN: %clang_cc1 -fobjc-arc -fobjc-runtime-has-weak -Wnonnull %s -verify +//rdar://19211059 + +@interface NSObject @end + +@interface Base : NSObject +- (nonnull id)bad:(nullable id)obj; // expected-note 2 {{previous declaration is here}} +- (nullable id)notAsBad:(nonnull id)obj; +@end + +@interface Sub : Base +- (nullable id)bad:(nonnull id)obj; // expected-warning {{conflicting nullability specifier on return types, 'nullable' conflicts with existing specifier 'nonnull'}} \ + // expected-warning {{conflicting nullability specifier on parameter types, 'nonnull' conflicts with existing specifier 'nullable'}} +- (nonnull id)notAsBad:(nullable id)obj; +@end -- 2.7.4