Index: clang/include/clang-c/Index.h =================================================================== --- clang/include/clang-c/Index.h +++ clang/include/clang-c/Index.h @@ -3861,7 +3861,15 @@ /** * 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 }; /** Index: clang/include/clang/AST/Type.h =================================================================== --- clang/include/clang/AST/Type.h +++ clang/include/clang/AST/Type.h @@ -4716,6 +4716,9 @@ case NullabilityKind::Nullable: return attr::TypeNullable; + case NullabilityKind::NullableResult: + return attr::TypeNullableResult; + case NullabilityKind::Unspecified: return attr::TypeNullUnspecified; } Index: clang/include/clang/Basic/Attr.td =================================================================== --- clang/include/clang/Basic/Attr.td +++ clang/include/clang/Basic/Attr.td @@ -1802,6 +1802,11 @@ 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]; Index: clang/include/clang/Basic/AttrDocs.td =================================================================== --- clang/include/clang/Basic/AttrDocs.td +++ clang/include/clang/Basic/AttrDocs.td @@ -3508,6 +3508,29 @@ }]; } +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 = [{ Index: clang/include/clang/Basic/Specifiers.h =================================================================== --- clang/include/clang/Basic/Specifiers.h +++ clang/include/clang/Basic/Specifiers.h @@ -309,7 +309,12 @@ /// 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 Index: clang/include/clang/Basic/TokenKinds.def =================================================================== --- clang/include/clang/Basic/TokenKinds.def +++ clang/include/clang/Basic/TokenKinds.def @@ -650,6 +650,7 @@ // 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 Index: clang/include/clang/Sema/Sema.h =================================================================== --- clang/include/clang/Sema/Sema.h +++ clang/include/clang/Sema/Sema.h @@ -12506,6 +12506,7 @@ /// 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; Index: clang/lib/APINotes/APINotesYAMLCompiler.cpp =================================================================== --- clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -89,6 +89,7 @@ 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); Index: clang/lib/AST/Type.cpp =================================================================== --- clang/lib/AST/Type.cpp +++ clang/lib/AST/Type.cpp @@ -3512,6 +3512,7 @@ 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 @@ return NullabilityKind::Nullable; if (getAttrKind() == attr::TypeNullUnspecified) return NullabilityKind::Unspecified; + if (getAttrKind() == attr::TypeNullableResult) + return NullabilityKind::NullableResult; return None; } Index: clang/lib/AST/TypePrinter.cpp =================================================================== --- clang/lib/AST/TypePrinter.cpp +++ clang/lib/AST/TypePrinter.cpp @@ -1570,6 +1570,8 @@ 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); @@ -1640,6 +1642,7 @@ case attr::LifetimeBound: case attr::TypeNonNull: case attr::TypeNullable: + case attr::TypeNullableResult: case attr::TypeNullUnspecified: case attr::ObjCGC: case attr::ObjCInertUnsafeUnretained: Index: clang/lib/Basic/Diagnostic.cpp =================================================================== --- clang/lib/Basic/Diagnostic.cpp +++ clang/lib/Basic/Diagnostic.cpp @@ -55,6 +55,12 @@ 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); Index: clang/lib/Basic/IdentifierTable.cpp =================================================================== --- clang/lib/Basic/IdentifierTable.cpp +++ clang/lib/Basic/IdentifierTable.cpp @@ -714,6 +714,11 @@ 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"; } Index: clang/lib/Parse/ParseDecl.cpp =================================================================== --- clang/lib/Parse/ParseDecl.cpp +++ clang/lib/Parse/ParseDecl.cpp @@ -833,6 +833,7 @@ 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(); @@ -3515,6 +3516,7 @@ // 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; @@ -5001,6 +5003,7 @@ case tok::kw__Nonnull: case tok::kw__Nullable: + case tok::kw__Nullable_result: case tok::kw__Null_unspecified: case tok::kw___kindof: @@ -5228,6 +5231,7 @@ case tok::kw__Nonnull: case tok::kw__Nullable: + case tok::kw__Nullable_result: case tok::kw__Null_unspecified: case tok::kw___kindof: @@ -5503,6 +5507,7 @@ // 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; Index: clang/lib/Parse/ParseTentative.cpp =================================================================== --- clang/lib/Parse/ParseTentative.cpp +++ clang/lib/Parse/ParseTentative.cpp @@ -842,7 +842,8 @@ 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 @@ 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; Index: clang/lib/Sema/Sema.cpp =================================================================== --- clang/lib/Sema/Sema.cpp +++ clang/lib/Sema/Sema.cpp @@ -501,7 +501,8 @@ 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); Index: clang/lib/Sema/SemaCodeComplete.cpp =================================================================== --- clang/lib/Sema/SemaCodeComplete.cpp +++ clang/lib/Sema/SemaCodeComplete.cpp @@ -2700,6 +2700,10 @@ case NullabilityKind::Unspecified: Result += "null_unspecified "; break; + + case NullabilityKind::NullableResult: + llvm_unreachable("Not supported as a context-sensitive keyword!"); + break; } } } Index: clang/lib/Sema/SemaExpr.cpp =================================================================== --- clang/lib/Sema/SemaExpr.cpp +++ clang/lib/Sema/SemaExpr.cpp @@ -8521,8 +8521,12 @@ 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; }; Index: clang/lib/Sema/SemaExprObjC.cpp =================================================================== --- clang/lib/Sema/SemaExprObjC.cpp +++ clang/lib/Sema/SemaExprObjC.cpp @@ -1563,12 +1563,20 @@ // 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. Index: clang/lib/Sema/SemaType.cpp =================================================================== --- clang/lib/Sema/SemaType.cpp +++ clang/lib/Sema/SemaType.cpp @@ -147,6 +147,7 @@ #define NULLABILITY_TYPE_ATTRS_CASELIST \ case ParsedAttr::AT_TypeNonNull: \ case ParsedAttr::AT_TypeNullable: \ + case ParsedAttr::AT_TypeNullableResult: \ case ParsedAttr::AT_TypeNullUnspecified namespace { @@ -3892,6 +3893,11 @@ 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"); @@ -3914,6 +3920,7 @@ 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; } @@ -4332,6 +4339,9 @@ case NullabilityKind::Nullable: return createSimpleAttr(Ctx, Attr); + case NullabilityKind::NullableResult: + return createSimpleAttr(Ctx, Attr); + case NullabilityKind::Unspecified: return createSimpleAttr(Ctx, Attr); } @@ -7007,6 +7017,9 @@ case ParsedAttr::AT_TypeNullable: return NullabilityKind::Nullable; + case ParsedAttr::AT_TypeNullableResult: + return NullabilityKind::NullableResult; + case ParsedAttr::AT_TypeNullUnspecified: return NullabilityKind::Unspecified; Index: clang/test/Index/nullability.c =================================================================== --- clang/test/Index/nullability.c +++ clang/test/Index/nullability.c @@ -2,9 +2,11 @@ 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] Index: clang/test/SemaObjC/nullability.m =================================================================== --- clang/test/SemaObjC/nullability.m +++ clang/test/SemaObjC/nullability.m @@ -116,11 +116,13 @@ - (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 @@ 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 @@ NSFoo * _Nonnull nonnullP; NSFoo * _Nullable nullableP; NSFoo * _Null_unspecified unspecifiedP; + NSFoo * _Nullable_result nullableResultP; NSFoo *noneP; p = c ? nonnullP : nonnullP; @@ -255,6 +264,10 @@ 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]; Index: clang/test/SemaObjC/nullable-result.m =================================================================== --- /dev/null +++ clang/test/SemaObjC/nullable-result.m @@ -0,0 +1,29 @@ +// 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; + +@interface SomeClass +-(void)async_get:(void (^)(R *_Nullable_result rptr, NSError *err))completionHandler; +@end + +void call(SomeClass *sc) { + [sc async_get:^(R *_Nullable_result rptr, NSError *err) {}]; + [sc async_get:^(R *_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}} +} Index: clang/tools/c-index-test/c-index-test.c =================================================================== --- clang/tools/c-index-test/c-index-test.c +++ clang/tools/c-index-test/c-index-test.c @@ -1539,10 +1539,20 @@ 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) { Index: clang/tools/libclang/CXType.cpp =================================================================== --- clang/tools/libclang/CXType.cpp +++ clang/tools/libclang/CXType.cpp @@ -1315,6 +1315,8 @@ return CXTypeNullability_NonNull; case NullabilityKind::Nullable: return CXTypeNullability_Nullable; + case NullabilityKind::NullableResult: + return CXTypeNullability_NullableResult; case NullabilityKind::Unspecified: return CXTypeNullability_Unspecified; }