/**
* 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
};
/**
case NullabilityKind::Nullable:
return attr::TypeNullable;
+ case NullabilityKind::NullableResult:
+ return attr::TypeNullableResult;
+
case NullabilityKind::Unspecified:
return attr::TypeNullUnspecified;
}
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];
}];
}
+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 = [{
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))
/// 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
// 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
/// 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;
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);
case attr::ObjCInertUnsafeUnretained:
case attr::TypeNonNull:
case attr::TypeNullable:
+ case attr::TypeNullableResult:
case attr::TypeNullUnspecified:
case attr::LifetimeBound:
case attr::AddressSpace:
return NullabilityKind::Nullable;
if (getAttrKind() == attr::TypeNullUnspecified)
return NullabilityKind::Unspecified;
+ if (getAttrKind() == attr::TypeNullableResult)
+ return NullabilityKind::NullableResult;
return None;
}
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);
case attr::LifetimeBound:
case attr::TypeNonNull:
case attr::TypeNullable:
+ case attr::TypeNullableResult:
case attr::TypeNullUnspecified:
case attr::ObjCGC:
case attr::ObjCInertUnsafeUnretained:
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);
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";
}
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();
// 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;
case tok::kw__Nonnull:
case tok::kw__Nullable:
+ case tok::kw__Nullable_result:
case tok::kw__Null_unspecified:
case tok::kw___kindof:
case tok::kw__Nonnull:
case tok::kw__Nullable:
+ case tok::kw__Nullable_result:
case tok::kw__Null_unspecified:
case tok::kw___kindof:
// 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;
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;
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;
QualType SrcType,
SourceLocation Loc) {
Optional<NullabilityKind> ExprNullability = SrcType->getNullability(Context);
- if (!ExprNullability || *ExprNullability != NullabilityKind::Nullable)
+ if (!ExprNullability || (*ExprNullability != NullabilityKind::Nullable &&
+ *ExprNullability != NullabilityKind::NullableResult))
return;
Optional<NullabilityKind> TypeNullability = DstType->getNullability(Context);
case NullabilityKind::Unspecified:
Result += "null_unspecified ";
break;
+
+ case NullabilityKind::NullableResult:
+ llvm_unreachable("Not supported as a context-sensitive keyword!");
+ break;
}
}
}
auto GetNullability = [&Ctx](QualType Ty) {
Optional<NullabilityKind> 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;
};
// Map the nullability of the result into a table index.
unsigned receiverNullabilityIdx = 0;
- if (auto nullability = ReceiverType->getNullability(Context))
+ if (Optional<NullabilityKind> nullability =
+ ReceiverType->getNullability(Context)) {
+ if (*nullability == NullabilityKind::NullableResult)
+ nullability = NullabilityKind::Nullable;
receiverNullabilityIdx = 1 + static_cast<unsigned>(*nullability);
+ }
unsigned resultNullabilityIdx = 0;
- if (auto nullability = resultType->getNullability(Context))
+ if (Optional<NullabilityKind> nullability =
+ resultType->getNullability(Context)) {
+ if (*nullability == NullabilityKind::NullableResult)
+ nullability = NullabilityKind::Nullable;
resultNullabilityIdx = 1 + static_cast<unsigned>(*nullability);
+ }
// The table of nullability mappings, indexed by the receiver's nullability
// and then the result type's nullability.
#define NULLABILITY_TYPE_ATTRS_CASELIST \
case ParsedAttr::AT_TypeNonNull: \
case ParsedAttr::AT_TypeNullable: \
+ case ParsedAttr::AT_TypeNullableResult: \
case ParsedAttr::AT_TypeNullUnspecified
namespace {
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");
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;
}
case NullabilityKind::Nullable:
return createSimpleAttr<TypeNullableAttr>(Ctx, Attr);
+ case NullabilityKind::NullableResult:
+ return createSimpleAttr<TypeNullableResultAttr>(Ctx, Attr);
+
case NullabilityKind::Unspecified:
return createSimpleAttr<TypeNullUnspecifiedAttr>(Ctx, Attr);
}
case ParsedAttr::AT_TypeNullable:
return NullabilityKind::Nullable;
+ case ParsedAttr::AT_TypeNullableResult:
+ return NullabilityKind::NullableResult;
+
case ParsedAttr::AT_TypeNullUnspecified:
return NullabilityKind::Unspecified;
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]
- (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;
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'}}
NSFoo * _Nonnull nonnullP;
NSFoo * _Nullable nullableP;
NSFoo * _Null_unspecified unspecifiedP;
+ NSFoo * _Nullable_result nullableResultP;
NSFoo *noneP;
p = c ? nonnullP : nonnullP;
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];
--- /dev/null
+// 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
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) {
return CXTypeNullability_NonNull;
case NullabilityKind::Nullable:
return CXTypeNullability_Nullable;
+ case NullabilityKind::NullableResult:
+ return CXTypeNullability_NullableResult;
case NullabilityKind::Unspecified:
return CXTypeNullability_Unspecified;
}