-
-
Save robb/d55b72d62d32deaee5fa to your computer and use it in GitHub Desktop.
NS_ASSUME_NONNULL_BEGIN | |
void Log(NSString *foo) { | |
NSLog(@"%@", foo); | |
} | |
int main(int argc, const char * argv[]) { | |
@autoreleasepool { | |
NSDictionary *stuff = @{ | |
@"a": @"Test" | |
}; | |
// This will trigger a warning in Xcode 7 if | |
// `-Wnullable-to-nonnull-conversion` is enabled: | |
// | |
// Implicit conversion from nullable pointer 'id _Nullable' to non- | |
// nullable pointer type 'NSString * _Nonnull'. | |
Log(stuff[@"a"]); | |
/// This will not trigger a warning. | |
Log(RBBNotNil(stuff[@"a"])); | |
/// This will trigger the assert. | |
Log(RBBNotNil(stuff[@"b"])); | |
} | |
return 0; | |
} | |
NS_ASSUME_NONNULL_END |
#if __has_feature(objc_generics) | |
/// An unimplemented class used to trick the compiler, since a cast along the | |
/// lines of | |
/// | |
/// (__nonnull __typeof(bla))bla; | |
/// | |
/// is not possible. | |
@interface RBBBox<__covariant Type> | |
- (nonnull Type)asNonNull; | |
@end | |
/// This macro allows us to cast a nullable reference to a non-nullable | |
/// reference that would otherwise trigger a warning if | |
/// `-Wnullable-to-nonnull-conversion` is enabled. | |
#define RBBNotNil(V) \ | |
({ \ | |
NSCAssert(V, @"Expected '%@' not to be nil.", @#V); \ | |
RBBBox<__typeof(V)> *type; \ | |
(__typeof(type.asNonNull))V; \ | |
}) | |
#else | |
/// If generics are unavailable, so is `-Wnullable-to-nonnull-conversion`. | |
#define RBBNotNil(V) \ | |
({ \ | |
NSCAssert(V, @"Expected '%@' not to be nil.", @#V); \ | |
V; \ | |
}) | |
#endif |
There is an issue with this macro in debug configuraiton. Argument V
is calling twice in assert and right after it. Here is a possible solution:
#define RBBNotNil(V) \
({ \
id rbb_nullable_object = V; \
NSCAssert(rbb_nullable_object, @"Expected '%@' not to be nil.", @#V); \
RBBBox<__typeof(V)> *type; \
(__typeof(type.asNonNull))rbb_nullable_object; \
})
#else
/// If generics are unavailable, so is `-Wnullable-to-nonnull-conversion`.
#define RBBNotNil(V) \
({ \
id rbb_nullable_object = V; \
NSCAssert(rbb_nullable_object, @"Expected '%@' not to be nil.", @#V); \
rbb_nullable_object; \
})
This is really great!
I ran into some static analyzer warnings with in when archiving. I outlined details in a stack overflow answer.
// We purposefully don't have a matching @implementation.
// We don't want +asNonnull to ever actually be called
// because that will add a lot of overhead to every RBBNotNil
// and we want RBBNotNil to be very cheap.
// If there is no @implementation, then if the +asNonnull is
// actually called, we'll get a linker error complaining about
// the lack of @implementation.
@interface RBBBox <__covariant Type>
// This as a class method so you don't need to
// declare an unused lvalue just for a __typeof
+ (Type _Nonnull)asNonnull;
@end
/*!
* @define RBBNotNil(V)
* Converts an Objective-C object expression from _Nullable to _Nonnull.
* Crashes if it receives a nil! We must crash or else we'll receive
* static analyzer warnings when archiving. I think in Release mode,
* the compiler ignores the _Nonnull cast.
* @param V a _Nullable Objective-C object expression
*/
#define RBBNotNil(V) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wgnu-statement-expression\"") \
({ \
__typeof__(V) __nullableV = V; \
NSCAssert(__nullableV, @"Expected '%@' not to be nil.", @#V); \
if (!__nullableV) { \
abort(); \
} \
(__typeof([RBBNotNil<__typeof(V)> asNonnull]))__nullableV; \
}) \
_Pragma("clang diagnostic pop")
#define assumeNotNull(_value) \
({ if (!_value) abort(); __auto_type const _temp = _value; _temp; })
use as
if (parameters) {
[obj processParameters:assumeNotNull(parameters)];
}
#define assumeNotNull(_value) \ ({ if (!_value) abort(); __auto_type const _temp = _value; _temp; })
Unfortunately broke with clang 15 update (Xcode 14.3 and later) but that's because the clang devs now inherit nullability as an attribute, which is quite annoying as they don't treat it as a standard qualifier. E.g. in clang 16 they don't drop it when you use typeof_unqual()
instead of typeof()
@CodingMarkus, can we use typeof_unqual
instead?
#define assumeNotNull(_value) \
({ if (!_value) abort(); typeof_unqual(_value) _Nonnull const _temp = _value; _temp; })
@CodingMarkus, can we use
typeof_unqual
instead?
Not unless they've changed the behavior in the meantime, just as I said:
which is quite annoying as they don't treat it as a standard qualifier.
E.g. in clang 16 they don't drop it when you usetypeof_unqual()
instead oftypeof()
Nice work. I'd like to borrow this code. If you're willing, could you add some kind of license declaration
RBBNotNil.h
?