13

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
Community
  • 1
  • 1
Heath Borders
  • 30,998
  • 16
  • 147
  • 256
  • 1
    You don't. Don't use that attributes for Objective-C. They are for Swift. Objective-C has a well-defined message to nil behavior. – Amin Negm-Awad Feb 25 '17 at 18:12
  • There are warnings in clang for when pass `_Nullable` rvalues to `_Nonnull` lvalues. Thus, they aren't just for Swift. This question is about managing that warning. – Heath Borders Feb 27 '17 at 21:35
  • 1
    They are just for Swift. The compiler does not change a bit of the generated code, depending on nullability of references. – Amin Negm-Awad Feb 28 '17 at 06:47
  • But `-Wnullable-to-nonnull-conversion` can prevent crashes when you pass `_Nullable` rvalues to lvalues that don't expect `nil`. A bunch of `Foundation` APIs will crash if you pass them `nil`. – Heath Borders Feb 28 '17 at 15:05
  • 1
    [Again](http://stackoverflow.com/questions/42424437/get-unichar-from-a-c-stdstring-to-create-a-nonnull-nsstring-in-objective#comment72002930_42424437), and as Amin is saying, "non-null" is not a thing in ObjC. Observe; compile the following with as many flags as you like. -Wall, -Weverything, -Wpedantic, -Werror. `NSString * __nonnull s = nil;` Compiles fine. ObjC is never going to enforce nullability. The behavior of a `nil` object pointer is fundamental and well-established. – jscs Mar 01 '17 at 02:07
  • @HeathBorders Yes, and your program can crash, if sou use an index out of bounds for an array. We dealt with this for decades. Maybe this was no problem, because the language wasn't polluted with nanny annotations. – Amin Negm-Awad Mar 01 '17 at 06:19
  • 2
    @Caswell, you're wrong. It you compile with `-Wnulllable-to-nonnull-conversion`, `NSString * _Nonnull s = nil;` will get a warning. – Heath Borders Mar 01 '17 at 14:35
  • @amin if Objective-C had an annotation to track indices, I'd use those annotations as well. It is possible for type checkers to track things like array indices. See the `Shen` and `ATS` languages. – Heath Borders Mar 01 '17 at 14:38
  • I know that such things exists and I know that nullability attributes exists in Objective-C. However, as I said: It is for Swift. (Maybe there is a reason, why `-Wnulllable-to-nonnull-conversion` is not default.) I do not know what you use to waste your time. On the other hand, this is irrelevant. What is more relevant: There is no meaning in continuing the discussion. – Amin Negm-Awad Mar 01 '17 at 17:38
  • 3
    Sorry, Amin, but that's just ignorant. Of course the compiler output doesn't change. If that's the only thing that matters, you can ignore most warnings. But if you want to write clean code that prevents you from a lot of issues before actually having them, of course nullability adds a lot of safety to your code. Not only in Swift. – Michael Ochs Aug 01 '17 at 19:44

3 Answers3

5

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

Michael Ochs
  • 2,846
  • 3
  • 27
  • 33
  • I updated the code from your answer with some additional notes about possible static analyzer warnings it caused in a new answer. https://stackoverflow.com/a/46123981/9636 – Heath Borders Sep 08 '17 at 20:30
3

I use this macro:

#define assumeNotNull(_value)         \
({                                    \
    if (!_value) abort();             \
    __auto_type const _temp = _value; \
    _temp;                            \
})

Of course, only after an appropriate test in code:

if (parameters) {
    [obj processParameters:assumeNotNull(parameters)];
}

Leaving out the macro the compiler would tell me that parameters might be NULL but processParameters requires a non-NULL argument. In my case that is even configured to be an error, not just a warning.

Leaving out the if check, the code will compile but if I ever feed in NULL, the application will crash on spot. So one should only use the macro after a test or if one is absolutely sure that the value cannot be NULL for some reason and you are so sure about that, that you are willing to bet your app stability on it.

If in doubt, always test and keep in mind, that if a test is clearly unnecessary (e.g. the condition was tested before and the code will never be reached if the value was NULL), the compiler will detect that during the optimization phase and remove the test for you. Unnecessary testing is hardly ever a performance problem, especially not with a test that cheap.

Update

Starting with Xcode 14.3 (LLVM 15) clang no longer understands that the if-statement ensures that _value is not NULL (after all abort() is a no-return function) and instead still throws an error. See also issue 63018.

As a workaround you can use this macro instead:

#define assumeNotNull(_value)         \
({                                    \
    if (!_value) abort();             \
    __auto_type const _temp = _value; \
    (typeof(*_temp) *_Nonnull)_temp;  \
})

Works okay for most cases but will not work with blocks as you cannot de-reference blocks. I still hope to find a better workaround.

The reason why this has changed could be an old radar: http://www.openradar.me/36877120

Also two month ago someone already complained about this change on the Apple developer forum: https://developer.apple.com/forums/thread/726000

Mecki
  • 125,244
  • 33
  • 244
  • 253
1

Michael Ochs' answer was basically correct, but I've since run into some static analyzer warnings because of lack of hard _Nonnull guarantees within. In short, we must abort if we receive a nil or else when we do an assignment like this:

@interface Foo : NSObject
+ (NSString * _Nullable)bar;
@end

int main(int argc, char * argv[]) {
  NSString * _Nonnull bar = RBBNotNil([Foo bar]);
}

In a Release configuration (in my case, when Archiving), the static analyzer will complain that you're attempting to assign a _Nullable value to a _Nonnull lvalue. I received warnings like this:

nil assigned to a pointer which is expected to have non-null value

This is my updated version:

// 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")
Heath Borders
  • 30,998
  • 16
  • 147
  • 256
  • 2
    Right, I usually have asserts enabled in release mode as well as otherwise you run different code in release than in debug. But I guess if you don't have that, this test is never done and thus clang emits a warning again. – Michael Ochs Sep 09 '17 at 12:38