diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h --- a/clang/include/clang-c/Index.h +++ b/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 }; /** diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h --- a/clang/include/clang/AST/Type.h +++ b/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; } diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1805,6 +1805,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]; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/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 = [{ diff --git a/clang/include/clang/Basic/Features.def b/clang/include/clang/Basic/Features.def --- a/clang/include/clang/Basic/Features.def +++ b/clang/include/clang/Basic/Features.def @@ -85,6 +85,7 @@ 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 --- a/clang/include/clang/Basic/Specifiers.h +++ b/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 diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def --- a/clang/include/clang/Basic/TokenKinds.def +++ b/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 diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -12448,6 +12448,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; diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/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); diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp --- a/clang/lib/AST/Type.cpp +++ b/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; } diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp --- a/clang/lib/AST/TypePrinter.cpp +++ b/clang/lib/AST/TypePrinter.cpp @@ -1579,6 +1579,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); @@ -1649,6 +1651,7 @@ 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 --- a/clang/lib/Basic/Diagnostic.cpp +++ b/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); diff --git a/clang/lib/Basic/IdentifierTable.cpp b/clang/lib/Basic/IdentifierTable.cpp --- a/clang/lib/Basic/IdentifierTable.cpp +++ b/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"; } diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp --- a/clang/lib/Parse/ParseDecl.cpp +++ b/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(); @@ -3536,6 +3537,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; @@ -5022,6 +5024,7 @@ 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 @@ 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 @@ // 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 --- a/clang/lib/Parse/ParseTentative.cpp +++ b/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; diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -503,7 +503,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); diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/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; } } } diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -8562,8 +8562,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; }; diff --git a/clang/lib/Sema/SemaExprObjC.cpp b/clang/lib/Sema/SemaExprObjC.cpp --- a/clang/lib/Sema/SemaExprObjC.cpp +++ b/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. diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp --- a/clang/lib/Sema/SemaType.cpp +++ b/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 { @@ -3893,6 +3894,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"); @@ -3915,6 +3921,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; } @@ -4333,6 +4340,9 @@ 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 @@ 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 --- a/clang/test/Index/nullability.c +++ b/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] diff --git a/clang/test/SemaObjC/nullability.m b/clang/test/SemaObjC/nullability.m --- a/clang/test/SemaObjC/nullability.m +++ b/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]; diff --git a/clang/test/SemaObjC/nullable-result.m b/clang/test/SemaObjC/nullable-result.m new file mode 100644 --- /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 --- a/clang/tools/c-index-test/c-index-test.c +++ b/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) { diff --git a/clang/tools/libclang/CXType.cpp b/clang/tools/libclang/CXType.cpp --- a/clang/tools/libclang/CXType.cpp +++ b/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; }