diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h index 1c91e059c32229..e305283bbaf197 100644 --- a/clang/include/clang-c/Index.h +++ b/clang/include/clang-c/Index.h @@ -3861,7 +3861,15 @@ enum CXTypeNullabilityKind { /** * Nullability is not applicable to this type. */ - CXTypeNullability_Invalid = 3 + CXTypeNullability_Invalid = 3, + + /** + * Generally behaves like Nullable, except when used in a block parameter that + * was imported into a swift async method. There, swift will assume that the + * parameter can get null even if no error occured. _Nullable parameters are + * assumed to only get null on error. + */ + CXTypeNullability_NullableResult = 4 }; /** diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index 6dedd097ff891d..60b8ee0f16149b 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -4716,6 +4716,9 @@ class AttributedType : public Type, public llvm::FoldingSetNode { case NullabilityKind::Nullable: return attr::TypeNullable; + case NullabilityKind::NullableResult: + return attr::TypeNullableResult; + case NullabilityKind::Unspecified: return attr::TypeNullUnspecified; } diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 123313079d2862..8e12aba5dad367 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1805,6 +1805,11 @@ def TypeNullable : TypeAttr { let Documentation = [TypeNullableDocs]; } +def TypeNullableResult : TypeAttr { + let Spellings = [Keyword<"_Nullable_result">]; + let Documentation = [TypeNullableResultDocs]; +} + def TypeNullUnspecified : TypeAttr { let Spellings = [Keyword<"_Null_unspecified">]; let Documentation = [TypeNullUnspecifiedDocs]; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index a6f31e7e7f463f..1cc8d9995b8574 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -3508,6 +3508,29 @@ a caller of ``fetch_or_zero`` can provide null. }]; } +def TypeNullableResultDocs : Documentation { + let Category = NullabilityDocs; + let Content = [{ +The ``_Nullable_result`` nullability qualifier means that a value of the +``_Nullable_result`` pointer can be ``nil``, just like ``_Nullable``. Where this +attribute differs from ``_Nullable`` is when it's used on a parameter to a +completion handler in a Swift async method. For instance, here: + + .. code-block:: objc + + -(void)fetchSomeDataWithID:(int)identifier + completionHandler:(void (^)(Data *_Nullable_result result, NSError *error))completionHandler; + +This method asynchronously calls ``completionHandler`` when the data is +available, or calls it with an error. ``_Nullable_result`` indicates to the +Swift importer that this is the uncommon case where ``result`` can get ``nil`` +even if no error has occured, and will therefore import it as a Swift optional +type. Otherwise, if ``result`` was annotated with ``_Nullable``, the Swift +importer will assume that ``result`` will always be non-nil unless an error +occured. +}]; +} + def TypeNullUnspecifiedDocs : Documentation { let Category = NullabilityDocs; let Content = [{ diff --git a/clang/include/clang/Basic/Features.def b/clang/include/clang/Basic/Features.def index 302c56763c39a9..5424da67b62daa 100644 --- a/clang/include/clang/Basic/Features.def +++ b/clang/include/clang/Basic/Features.def @@ -85,6 +85,7 @@ FEATURE(cxx_rtti, LangOpts.RTTI &&LangOpts.RTTIData) FEATURE(enumerator_attributes, true) FEATURE(nullability, true) FEATURE(nullability_on_arrays, true) +FEATURE(nullability_nullable_result, true) FEATURE(memory_sanitizer, LangOpts.Sanitize.hasOneOf(SanitizerKind::Memory | SanitizerKind::KernelMemory)) diff --git a/clang/include/clang/Basic/Specifiers.h b/clang/include/clang/Basic/Specifiers.h index 03de6ea6a434c1..07d8177b8ab2fb 100644 --- a/clang/include/clang/Basic/Specifiers.h +++ b/clang/include/clang/Basic/Specifiers.h @@ -309,7 +309,12 @@ namespace clang { /// unspecified. This captures a (fairly rare) case where we /// can't conclude anything about the nullability of the type even /// though it has been considered. - Unspecified + Unspecified, + // Generally behaves like Nullable, except when used in a block parameter + // that was imported into a swift async method. There, swift will assume + // that the parameter can get null even if no error occured. _Nullable + // parameters are assumed to only get null on error. + NullableResult, }; /// Return true if \p L has a weaker nullability annotation than \p R. The diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index d8a2016e9caa29..572ebae6618db1 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -650,6 +650,7 @@ ALIAS("__volatile__" , volatile , KEYALL) // Type nullability. KEYWORD(_Nonnull , KEYALL) KEYWORD(_Nullable , KEYALL) +KEYWORD(_Nullable_result , KEYALL) KEYWORD(_Null_unspecified , KEYALL) // Microsoft extensions which should be disabled in strict conformance mode diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 4eddd07f66d065..9c838e57428390 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -12448,6 +12448,7 @@ class Sema final { /// Nullability type specifiers. IdentifierInfo *Ident__Nonnull = nullptr; IdentifierInfo *Ident__Nullable = nullptr; + IdentifierInfo *Ident__Nullable_result = nullptr; IdentifierInfo *Ident__Null_unspecified = nullptr; IdentifierInfo *Ident_NSError = nullptr; diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index 997929a9bd2206..a4120120a01c49 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -89,6 +89,7 @@ template <> struct ScalarEnumerationTraits { IO.enumCase(NK, "Nonnull", NullabilityKind::NonNull); IO.enumCase(NK, "Optional", NullabilityKind::Nullable); IO.enumCase(NK, "Unspecified", NullabilityKind::Unspecified); + IO.enumCase(NK, "NullableResult", NullabilityKind::NullableResult); // TODO: Mapping this to it's own value would allow for better cross // checking. Also the default should be Unknown. IO.enumCase(NK, "Scalar", NullabilityKind::Unspecified); diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 1c29d3a385ae10..c07cab9a4006cc 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -3512,6 +3512,7 @@ bool AttributedType::isQualifier() const { case attr::ObjCInertUnsafeUnretained: case attr::TypeNonNull: case attr::TypeNullable: + case attr::TypeNullableResult: case attr::TypeNullUnspecified: case attr::LifetimeBound: case attr::AddressSpace: @@ -4159,6 +4160,8 @@ AttributedType::getImmediateNullability() const { return NullabilityKind::Nullable; if (getAttrKind() == attr::TypeNullUnspecified) return NullabilityKind::Unspecified; + if (getAttrKind() == attr::TypeNullableResult) + return NullabilityKind::NullableResult; return None; } diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp index b42ffde4a06962..54c451291a0777 100644 --- a/clang/lib/AST/TypePrinter.cpp +++ b/clang/lib/AST/TypePrinter.cpp @@ -1579,6 +1579,8 @@ void TypePrinter::printAttributedBefore(const AttributedType *T, OS << " _Nullable"; else if (T->getAttrKind() == attr::TypeNullUnspecified) OS << " _Null_unspecified"; + else if (T->getAttrKind() == attr::TypeNullableResult) + OS << " _Nullable_result"; else llvm_unreachable("unhandled nullability"); spaceBeforePlaceHolder(OS); @@ -1649,6 +1651,7 @@ void TypePrinter::printAttributedAfter(const AttributedType *T, case attr::LifetimeBound: case attr::TypeNonNull: case attr::TypeNullable: + case attr::TypeNullableResult: case attr::TypeNullUnspecified: case attr::ObjCGC: case attr::ObjCInertUnsafeUnretained: diff --git a/clang/lib/Basic/Diagnostic.cpp b/clang/lib/Basic/Diagnostic.cpp index 30a4c51b134bc6..d3b2122e9c59fb 100644 --- a/clang/lib/Basic/Diagnostic.cpp +++ b/clang/lib/Basic/Diagnostic.cpp @@ -55,6 +55,12 @@ const StreamingDiagnostic &clang::operator<<(const StreamingDiagnostic &DB, case NullabilityKind::Unspecified: string = nullability.second ? "'null_unspecified'" : "'_Null_unspecified'"; break; + + case NullabilityKind::NullableResult: + assert(!nullability.second && + "_Nullable_result isn't supported as context-sensitive keyword"); + string = "_Nullable_result"; + break; } DB.AddString(string); diff --git a/clang/lib/Basic/IdentifierTable.cpp b/clang/lib/Basic/IdentifierTable.cpp index 36b26d9b7c6876..51c6e02e2e2e0a 100644 --- a/clang/lib/Basic/IdentifierTable.cpp +++ b/clang/lib/Basic/IdentifierTable.cpp @@ -714,6 +714,11 @@ StringRef clang::getNullabilitySpelling(NullabilityKind kind, case NullabilityKind::Nullable: return isContextSensitive ? "nullable" : "_Nullable"; + case NullabilityKind::NullableResult: + assert(!isContextSensitive && + "_Nullable_result isn't supported as context-sensitive keyword"); + return "_Nullable_result"; + case NullabilityKind::Unspecified: return isContextSensitive ? "null_unspecified" : "_Null_unspecified"; } diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index c3c56ddccb6b58..7ebf06a04fa767 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -833,6 +833,7 @@ void Parser::ParseNullabilityTypeSpecifiers(ParsedAttributes &attrs) { switch (Tok.getKind()) { case tok::kw__Nonnull: case tok::kw__Nullable: + case tok::kw__Nullable_result: case tok::kw__Null_unspecified: { IdentifierInfo *AttrName = Tok.getIdentifierInfo(); SourceLocation AttrNameLoc = ConsumeToken(); @@ -3536,6 +3537,7 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS, // Nullability type specifiers. case tok::kw__Nonnull: case tok::kw__Nullable: + case tok::kw__Nullable_result: case tok::kw__Null_unspecified: ParseNullabilityTypeSpecifiers(DS.getAttributes()); continue; @@ -5022,6 +5024,7 @@ bool Parser::isTypeSpecifierQualifier() { case tok::kw__Nonnull: case tok::kw__Nullable: + case tok::kw__Nullable_result: case tok::kw__Null_unspecified: case tok::kw___kindof: @@ -5249,6 +5252,7 @@ bool Parser::isDeclarationSpecifier(bool DisambiguatingWithExpression) { case tok::kw__Nonnull: case tok::kw__Nullable: + case tok::kw__Nullable_result: case tok::kw__Null_unspecified: case tok::kw___kindof: @@ -5524,6 +5528,7 @@ void Parser::ParseTypeQualifierListOpt( // Nullability type specifiers. case tok::kw__Nonnull: case tok::kw__Nullable: + case tok::kw__Nullable_result: case tok::kw__Null_unspecified: ParseNullabilityTypeSpecifiers(DS.getAttributes()); continue; diff --git a/clang/lib/Parse/ParseTentative.cpp b/clang/lib/Parse/ParseTentative.cpp index d0f1d2e09a8724..3bf2bc455bfe84 100644 --- a/clang/lib/Parse/ParseTentative.cpp +++ b/clang/lib/Parse/ParseTentative.cpp @@ -842,7 +842,8 @@ Parser::TPResult Parser::TryParsePtrOperatorSeq() { while (Tok.isOneOf(tok::kw_const, tok::kw_volatile, tok::kw_restrict, tok::kw__Nonnull, tok::kw__Nullable, - tok::kw__Null_unspecified, tok::kw__Atomic)) + tok::kw__Nullable_result, tok::kw__Null_unspecified, + tok::kw__Atomic)) ConsumeToken(); } else { return TPResult::True; @@ -1437,6 +1438,7 @@ Parser::isCXXDeclarationSpecifier(Parser::TPResult BracedCastResult, case tok::kw___unaligned: case tok::kw__Nonnull: case tok::kw__Nullable: + case tok::kw__Nullable_result: case tok::kw__Null_unspecified: case tok::kw___kindof: return TPResult::True; diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index b99dc33d87480c..456daab7e03238 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -503,7 +503,8 @@ void Sema::diagnoseNullableToNonnullConversion(QualType DstType, QualType SrcType, SourceLocation Loc) { Optional ExprNullability = SrcType->getNullability(Context); - if (!ExprNullability || *ExprNullability != NullabilityKind::Nullable) + if (!ExprNullability || (*ExprNullability != NullabilityKind::Nullable && + *ExprNullability != NullabilityKind::NullableResult)) return; Optional TypeNullability = DstType->getNullability(Context); diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp index 10fd5522d1ad31..243f68d16462f2 100644 --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/clang/lib/Sema/SemaCodeComplete.cpp @@ -2700,6 +2700,10 @@ static std::string formatObjCParamQualifiers(unsigned ObjCQuals, case NullabilityKind::Unspecified: Result += "null_unspecified "; break; + + case NullabilityKind::NullableResult: + llvm_unreachable("Not supported as a context-sensitive keyword!"); + break; } } } diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 3183b09402caf0..738fe87d37b4fa 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -8562,8 +8562,12 @@ static QualType computeConditionalNullability(QualType ResTy, bool IsBin, auto GetNullability = [&Ctx](QualType Ty) { Optional Kind = Ty->getNullability(Ctx); - if (Kind) + if (Kind) { + // For our purposes, treat _Nullable_result as _Nullable. + if (*Kind == NullabilityKind::NullableResult) + return NullabilityKind::Nullable; return *Kind; + } return NullabilityKind::Unspecified; }; diff --git a/clang/lib/Sema/SemaExprObjC.cpp b/clang/lib/Sema/SemaExprObjC.cpp index ced0952f575342..f5456ee0711e5d 100644 --- a/clang/lib/Sema/SemaExprObjC.cpp +++ b/clang/lib/Sema/SemaExprObjC.cpp @@ -1563,12 +1563,20 @@ QualType Sema::getMessageSendResultType(const Expr *Receiver, // Map the nullability of the result into a table index. unsigned receiverNullabilityIdx = 0; - if (auto nullability = ReceiverType->getNullability(Context)) + if (Optional nullability = + ReceiverType->getNullability(Context)) { + if (*nullability == NullabilityKind::NullableResult) + nullability = NullabilityKind::Nullable; receiverNullabilityIdx = 1 + static_cast(*nullability); + } unsigned resultNullabilityIdx = 0; - if (auto nullability = resultType->getNullability(Context)) + if (Optional nullability = + resultType->getNullability(Context)) { + if (*nullability == NullabilityKind::NullableResult) + nullability = NullabilityKind::Nullable; resultNullabilityIdx = 1 + static_cast(*nullability); + } // The table of nullability mappings, indexed by the receiver's nullability // and then the result type's nullability. diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp index 0d80bce10ffdd2..df46aa3bfc1fc9 100644 --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -147,6 +147,7 @@ static void diagnoseBadTypeAttribute(Sema &S, const ParsedAttr &attr, #define NULLABILITY_TYPE_ATTRS_CASELIST \ case ParsedAttr::AT_TypeNonNull: \ case ParsedAttr::AT_TypeNullable: \ + case ParsedAttr::AT_TypeNullableResult: \ case ParsedAttr::AT_TypeNullUnspecified namespace { @@ -3893,6 +3894,11 @@ IdentifierInfo *Sema::getNullabilityKeyword(NullabilityKind nullability) { Ident__Nullable = PP.getIdentifierInfo("_Nullable"); return Ident__Nullable; + case NullabilityKind::NullableResult: + if (!Ident__Nullable_result) + Ident__Nullable_result = PP.getIdentifierInfo("_Nullable_result"); + return Ident__Nullable_result; + case NullabilityKind::Unspecified: if (!Ident__Null_unspecified) Ident__Null_unspecified = PP.getIdentifierInfo("_Null_unspecified"); @@ -3915,6 +3921,7 @@ static bool hasNullabilityAttr(const ParsedAttributesView &attrs) { for (const ParsedAttr &AL : attrs) { if (AL.getKind() == ParsedAttr::AT_TypeNonNull || AL.getKind() == ParsedAttr::AT_TypeNullable || + AL.getKind() == ParsedAttr::AT_TypeNullableResult || AL.getKind() == ParsedAttr::AT_TypeNullUnspecified) return true; } @@ -4333,6 +4340,9 @@ static Attr *createNullabilityAttr(ASTContext &Ctx, ParsedAttr &Attr, case NullabilityKind::Nullable: return createSimpleAttr(Ctx, Attr); + case NullabilityKind::NullableResult: + return createSimpleAttr(Ctx, Attr); + case NullabilityKind::Unspecified: return createSimpleAttr(Ctx, Attr); } @@ -7008,6 +7018,9 @@ static NullabilityKind mapNullabilityAttrKind(ParsedAttr::Kind kind) { case ParsedAttr::AT_TypeNullable: return NullabilityKind::Nullable; + case ParsedAttr::AT_TypeNullableResult: + return NullabilityKind::NullableResult; + case ParsedAttr::AT_TypeNullUnspecified: return NullabilityKind::Unspecified; diff --git a/clang/test/Index/nullability.c b/clang/test/Index/nullability.c index 26e55cb65f476d..2eeb685c6fcd04 100644 --- a/clang/test/Index/nullability.c +++ b/clang/test/Index/nullability.c @@ -2,9 +2,11 @@ int *a; int * _Nonnull b; int * _Nullable c; int * _Null_unspecified d; +int * _Nullable_result e; // RUN: env CINDEXTEST_INCLUDE_ATTRIBUTED_TYPES=1 c-index-test -test-print-type %s | FileCheck %s // CHECK: VarDecl=a:1:6 [type=int *] [typekind=Pointer] [isPOD=1] [pointeetype=int] [pointeekind=Int] // CHECK: VarDecl=b:2:16 [type=int * _Nonnull] [typekind=Attributed] [nullability=nonnull] [canonicaltype=int *] [canonicaltypekind=Pointer] [modifiedtype=int *] [modifiedtypekind=Pointer] [isPOD=1] // CHECK: VarDecl=c:3:17 [type=int * _Nullable] [typekind=Attributed] [nullability=nullable] [canonicaltype=int *] [canonicaltypekind=Pointer] [modifiedtype=int *] [modifiedtypekind=Pointer] [isPOD=1] // CHECK: VarDecl=d:4:25 [type=int * _Null_unspecified] [typekind=Attributed] [nullability=unspecified] [canonicaltype=int *] [canonicaltypekind=Pointer] [modifiedtype=int *] [modifiedtypekind=Pointer] [isPOD=1] +// CHECK: VarDecl=e:5:24 [type=int * _Nullable_result] [typekind=Attributed] [nullability=nullable_result] [canonicaltype=int *] [canonicaltypekind=Pointer] [modifiedtype=int *] [modifiedtypekind=Pointer] [isPOD=1] diff --git a/clang/test/SemaObjC/nullability.m b/clang/test/SemaObjC/nullability.m index 885ced692ebccc..93834942be799d 100644 --- a/clang/test/SemaObjC/nullability.m +++ b/clang/test/SemaObjC/nullability.m @@ -116,11 +116,13 @@ - (id)returnsNone; - (nonnull id)returnsNonNull; - (nullable id)returnsNullable; - (null_unspecified id)returnsNullUnspecified; +- (_Nullable_result id)returnsNullableResult; @end void test_receiver_merge(NSMergeReceiver *none, _Nonnull NSMergeReceiver *nonnull, _Nullable NSMergeReceiver *nullable, + _Nullable_result NSMergeReceiver *nullable_result, _Null_unspecified NSMergeReceiver *null_unspecified) { int *ptr; @@ -129,6 +131,12 @@ void test_receiver_merge(NSMergeReceiver *none, ptr = [nullable returnsNonNull]; // expected-warning{{'id _Nullable'}} ptr = [nullable returnsNone]; // expected-warning{{'id _Nullable'}} + ptr = [nullable_result returnsNullable]; // expected-warning{{'id _Nullable'}} + ptr = [nullable_result returnsNullUnspecified]; // expected-warning{{'id _Nullable'}} + ptr = [nullable_result returnsNonNull]; // expected-warning{{'id _Nullable'}} + ptr = [nullable_result returnsNone]; // expected-warning{{'id _Nullable'}} + ptr = [nullable_result returnsNullableResult]; // expected-warning{{'id _Nullable_result'}} + 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'}} @@ -237,6 +245,7 @@ void conditional_expr(int c) { NSFoo * _Nonnull nonnullP; NSFoo * _Nullable nullableP; NSFoo * _Null_unspecified unspecifiedP; + NSFoo * _Nullable_result nullableResultP; NSFoo *noneP; p = c ? nonnullP : nonnullP; @@ -255,6 +264,10 @@ void conditional_expr(int c) { p = c ? noneP : nullableP; // expected-warning{{implicit conversion from nullable pointer 'NSFoo * _Nullable' to non-nullable pointer type 'NSFoo * _Nonnull'}} p = c ? noneP : unspecifiedP; p = c ? noneP : noneP; + p = c ? noneP : nullableResultP; // expected-warning{{implicit conversion from nullable pointer 'NSFoo * _Nullable' to non-nullable pointer type 'NSFoo * _Nonnull'}} + p = c ? nonnullP : nullableResultP; // expected-warning{{implicit conversion from nullable pointer 'NSFoo * _Nullable' to non-nullable pointer type 'NSFoo * _Nonnull'}} + p = c ? nullableP : nullableResultP; // expected-warning{{implicit conversion from nullable pointer 'NSFoo * _Nullable' to non-nullable pointer type 'NSFoo * _Nonnull'}} + p = c ? nullableResultP : nullableResultP; // expected-warning{{implicit conversion from nullable pointer 'NSFoo * _Nullable_result' to non-nullable pointer type 'NSFoo * _Nonnull'}} } typedef int INTS[4]; diff --git a/clang/test/SemaObjC/nullable-result.m b/clang/test/SemaObjC/nullable-result.m new file mode 100644 index 00000000000000..9958531c2b7ae2 --- /dev/null +++ b/clang/test/SemaObjC/nullable-result.m @@ -0,0 +1,36 @@ +// RUN: %clang_cc1 -verify -fsyntax-only -fblocks -Wnullable-to-nonnull-conversion %s +// RUN: %clang_cc1 -xobjective-c++ -verify -fsyntax-only -fblocks -Wnullable-to-nonnull-conversion %s + +@class X; +@class NSError; + +_Static_assert(__has_feature(nullability_nullable_result), ""); + +@interface SomeClass +-(void)async_get:(void (^)(X *_Nullable_result rptr, NSError *err))completionHandler; +@end + +void call(SomeClass *sc) { + [sc async_get:^(X *_Nullable_result rptr, NSError *err) {}]; + [sc async_get:^(X *_Nullable rptr, NSError *err) {}]; +} + +void test_conversion() { + X *_Nullable_result nr; + X *_Nonnull l = nr; // expected-warning{{implicit conversion from nullable pointer 'X * _Nullable_result' to non-nullable pointer type 'X * _Nonnull'}} + + (void)^(X * _Nullable_result p) { + X *_Nonnull l = p; // expected-warning{{implicit conversion from nullable pointer 'X * _Nullable_result' to non-nullable pointer type 'X * _Nonnull'}} + }; +} + +void test_dup() { + id _Nullable_result _Nullable_result a; // expected-warning {{duplicate nullability specifier _Nullable_result}} + id _Nullable _Nullable_result b; // expected-error{{nullability specifier _Nullable_result conflicts with existing specifier '_Nullable'}} + id _Nullable_result _Nonnull c; // expected-error{{nullability specifier '_Nonnull' conflicts with existing specifier _Nullable_result}} +} + +@interface NoContextSensitive +-(nullable_result id)m; // expected-error {{expected a type}} +@property(assign, nullable_result) id p; // expected-error{{unknown property attribute 'nullable_result'}} +@end diff --git a/clang/tools/c-index-test/c-index-test.c b/clang/tools/c-index-test/c-index-test.c index 6e82bf9999f64a..3eb63492071f40 100644 --- a/clang/tools/c-index-test/c-index-test.c +++ b/clang/tools/c-index-test/c-index-test.c @@ -1539,10 +1539,20 @@ static void PrintNullabilityKind(CXType T, const char *Format) { const char *nullability = 0; switch (N) { - case CXTypeNullability_NonNull: nullability = "nonnull"; break; - case CXTypeNullability_Nullable: nullability = "nullable"; break; - case CXTypeNullability_Unspecified: nullability = "unspecified"; break; - case CXTypeNullability_Invalid: break; + case CXTypeNullability_NonNull: + nullability = "nonnull"; + break; + case CXTypeNullability_Nullable: + nullability = "nullable"; + break; + case CXTypeNullability_NullableResult: + nullability = "nullable_result"; + break; + case CXTypeNullability_Unspecified: + nullability = "unspecified"; + break; + case CXTypeNullability_Invalid: + break; } if (nullability) { diff --git a/clang/tools/libclang/CXType.cpp b/clang/tools/libclang/CXType.cpp index 2d19e13f161a8d..4b9620827002c9 100644 --- a/clang/tools/libclang/CXType.cpp +++ b/clang/tools/libclang/CXType.cpp @@ -1315,6 +1315,8 @@ enum CXTypeNullabilityKind clang_Type_getNullability(CXType CT) { return CXTypeNullability_NonNull; case NullabilityKind::Nullable: return CXTypeNullability_Nullable; + case NullabilityKind::NullableResult: + return CXTypeNullability_NullableResult; case NullabilityKind::Unspecified: return CXTypeNullability_Unspecified; }