When compiling with -Wnullable-to-nonnull-conversion
, we get a proper warning with the following code:
NSString * _Nullable maybeFoo = @"foo";
^(NSString * _Nonnull bar) {
}(maybeFoo);
Tests.m:32:7: error: implicit conversion from nullable pointer 'NSString * _Nullable' to non-nullable pointer type 'NSString * _Nonnull' [-Werror,-Wnullable-to-nonnull-conversion]
}(maybeFoo);
^
1 error generated.
How do I safely cast foo
from an NSString * _Nullable
to an NSString * _Nonnull
?
The best solution I have so far
The best I've come up with is this macro:
#define ForceUnwrap(type, nullableExpression) ^type _Nonnull () { \
type _Nullable maybeValue___ = nullableExpression; \
if (maybeValue___) { \
return (type _Nonnull) maybeValue___; \
} else { \
NSLog(@"Attempted to force unwrap a null: " #nullableExpression); \
abort(); \
} \
}()
Which is used like:
NSString * _Nullable maybeFoo = @"foo";
if (maybeFoo) {
NSString * _Nonnull foo = ForceUnwrap(NSString *, maybeFoo);
^(NSString * _Nonnull bar) {
}(foo);
}
And which produces an error if assigned to a wrongly-typed variable:
NSString * _Nullable maybeFoo = @"foo";
if (maybeFoo) {
NSNumber * _Nonnull foo = ForceUnwrap(NSString *, maybeFoo);
^(NSNumber * _Nonnull bar) {
}(foo);
}
Tests.m:40:29: error: incompatible pointer types initializing 'NSNumber * _Nonnull' with an expression of type 'NSString * _Nonnull' [-Werror,-Wincompatible-pointer-types]
NSNumber * _Nonnull foo = ForceUnwrap(NSString *, maybeFoo);
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
And which produces an error if cast to the wrong type:
NSString * _Nullable maybeFoo = @"foo";
if (maybeFoo) {
NSNumber * _Nonnull foo = ForceUnwrap(NSNumber *, maybeFoo);
^(NSNumber * _Nonnull bar) {
}(foo);
}
Tests.m:40:35: error: incompatible pointer types initializing 'NSNumber * _Nullable' with an expression of type 'NSString * _Nullable' [-Werror,-Wincompatible-pointer-types]
NSNumber * _Nonnull foo = ForceUnwrap(NSNumber *, maybeFoo);
^ ~~~~~~~~
Tests.m:27:16: note: expanded from macro 'ForceUnwrap'
type _Nullable maybeValue___ = nullableExpression; \
^ ~~~~~~~~~~~~~~~~~~
1 error generated.
Unfortunately, if you need to cast to a generic type with multiple arguments, you have to resort to preprocessor hacks:
NSDictionary<NSString *, NSString *> * _Nullable maybeFoo =
[NSDictionary<NSString *, NSString *> new];
if (maybeFoo) {
NSDictionary<NSString *, NSString *> * _Nonnull foo =
#define COMMA ,
ForceUnwrap(NSDictionary<NSString * COMMMA NSString *>, maybeFoo);
#undef COMMA
^(NSDictionary<NSString *, NSString *> * _Nonnull bar) {
}(foo);
}
Things I've tried that don't work
Assigning maybeFoo
directly to an NSString * _Nonnull
doesn't work. It produces the same error as before:
NSString * _Nullable maybeFoo = @"foo";
if (maybeFoo) {
NSString * _Nonnull foo = maybeFoo;
^(NSString * _Nonnull bar) {
}(foo);
}
Tests.m:30:35: error: implicit conversion from nullable pointer 'NSString * _Nullable' to non-nullable pointer type 'NSString * _Nonnull' [-Werror,-Wnullable-to-nonnull-conversion]
NSString * _Nonnull foo = maybeFoo;
^
1 error generated.
And casting to maybeFoo
to NSString * _Nonnull
isn't safe because if maybeFoo
's type changes, the compiler won't break:
NSNumber * _Nullable maybeFoo = @"foo";
if (maybeFoo) {
NSString * _Nonnull foo = (NSString * _Nonnull) maybeFoo;
^(NSString * _Nonnull bar) {
}(foo);
}
// no errors!
I also tried using __typeof__
, when casting, but __typeof__
carries the nullability specifier, so when you try to cast to __typeof__(maybeFoo) _Nonnull
you get a nullability conflict:
NSString * _Nullable maybeFoo = @"foo";
if (maybeFoo) {
NSString * _Nonnull foo = (__typeof__(maybeFoo) _Nonnull) maybeFoo;
^(NSString * _Nonnull bar) {
}(foo);
}
Tests.m:30:57: error: nullability specifier '_Nonnull' conflicts with existing specifier '_Nullable'
NSString * _Nonnull foo = (__typeof__(maybeFoo) _Nonnull) maybeFoo;
^
Tests.m:30:35: error: implicit conversion from nullable pointer 'NSString * _Nullable' to non-nullable pointer type 'NSString * _Nonnull' [-Werror,-Wnullable-to-nonnull-conversion]
NSString * _Nonnull foo = (__typeof__(maybeFoo) _Nonnull) maybeFoo;
^
2 errors generated.
Everything was run with the deep static analyzer and compiled with Xcode 8.2.1
with the following flags:
-Wnon-modular-include-in-framework-module
-Werror=non-modular-include-in-framework-module
-Wno-trigraphs
-Werror
-Wno-missing-field-initializers
-Wno-missing-prototypes
-Wunreachable-code
-Wno-implicit-atomic-properties
-Wno-arc-repeated-use-of-weak
-Wduplicate-method-match
-Wno-missing-braces
-Wparentheses
-Wswitch
-Wunused-function
-Wno-unused-label
-Wno-unused-parameter
-Wunused-variable
-Wunused-value
-Wempty-body
-Wuninitialized
-Wno-unknown-pragmas
-Wno-shadow
-Wno-four-char-constants
-Wno-conversion
-Wconstant-conversion
-Wint-conversion
-Wbool-conversion
-Wenum-conversion
-Wshorten-64-to-32
-Wpointer-sign
-Wno-newline-eof
-Wno-selector
-Wno-strict-selector-match
-Wundeclared-selector
-Wno-deprecated-implementations
-Wno-sign-conversion
-Wno-infinite-recursion
-Weverything
-Wno-auto-import
-Wno-objc-missing-property-synthesis
-Wno-cstring-format-directive
-Wno-direct-ivar-access
-Wno-double-promotion
The best I found so far is a trick with generics.
Essentially you define an interface that uses generics and has a method that returns the generic type as nonnull
. Then in your macro you use typeof
but on the generic type, and this gives you the correct type.
Note that the generic class is never instantiated, it's just used to get the correct type.
@interface RBBBox<__covariant Type>
- (nonnull Type)asNonNull;
@end
#define RBBNotNil(V) \
({ \
NSCAssert(V, @"Expected '%@' not to be nil.", @#V); \
RBBBox<__typeof(V)> *type; \
(__typeof(type.asNonNull))V; \
})
This is not my idea, though. Source: https://gist.github.com/robb/d55b72d62d32deaee5fa