Clang nullability warnings and how to approach them

3.3k Views Asked by At

Recently I turned on CLANG_WARN_NULLABLE_TO_NONNULL_CONVERSION in Xcode and I am overwhelmed with nullability related warnings in my Objective-C code. The one warning type that is most prevalent is Implicit conversion from nullable pointer 'TypeA * _Nullable' to non-nullable pointer type 'TypeA * _Nonnull'.

I started my attempt to remove with these warnings by creating a local of the same type in a method as described here. https://www.mail-archive.com/xcode-users%40lists.apple.com/msg02260.html This article says by first using a local, that object is of attribute unspecified nullable, so it can be used as legit parameter to the methods expecting nonnull.

But I feel this is a cop out move and really not solving the issue in any beneficial way.

Has anyone gone through this exercise already? I would be grateful if you can share a strategy that you took.

2

There are 2 best solutions below

3
Alfred Zien On

Actually, I have messed around with that topic for a little bit. I wanted to improve nullability situation in a somewhat big project (make it more 'swiftier'). Here is what I found.

Firstly, you should turn on CLANG_WARN_NULLABLE_TO_NONNULL_CONVERSION (-Wnullable-to-nonnull-conversion)

Secondly, about

first using a local, that object is of attribute unspecified nullable, so it can be used as legit parameter to the methods expecting nonnull.

This smells bad, and I created a macro called NONNUL_CAST(). Here is example how to implement it:

#define NONNUL_CAST(__var) ({ NSCAssert(__var, @"Variable is nil");\
    (__typeof(*(__var))* _Nonnull)__var; })

Here you can see hacky __typeof(*(__var))* _Nonnull)__var, but it is not so bad. If __var is of type A* _Nullable we dereference __var, so it's type now just A, after we make reference again, but _Nonnull, and get nonnull __var as answer. Of course we assert to, in case something goes wrong.

Thirdly, you must specify nullabilty on every local variable, and you should put all your code in NS_ASSUME_NONNULL_BEGIN/END, like this:

NS_ASSUME_NONNULL_BEGIN 
<your_code_goes_here>
NS_ASSUME_NONNULL_END`

You put there EVERY LINE (except imports) of your code, in .h and .m files. That will assume that all your arguments of methods, return types and properties are nonnull. If you want to make it nullable, put nullable there.

So, all done, now what? Here is example of typical usage:

- (Atype*)makeAtypeWithBtype:(nullable BType*)btype {
  Atype* _Nullable a = [btype makeAtype];
  if (a) {
    // All good here.
    return NONNUL_CAST(a);
  } else {
    // a appeared as nil. Some fallback is needed.
    [self reportError];
    return [AtypeFactory makeDeafult];
  }
}

Now you have more robust nullability situation. May be it is not looking nicely, but it is objective-c, so nothing to complain about.

UPDATE

Macro that works with block types (also probably with function types):

@interface __NONNULL_CASTER <__covariant T>
+ (T _Nonnull)nonnullCaster;
@end

#define NONNUL_CAST(__var)                                      \
  ({                                                            \
    __typeof(__var) __local_var = __var;                        \
    NSCAssert(__local_var##__id != nil);                        \
    (__typeof([__NONNULL_CASTER<__typeof(__var)> nonnullCast])) \
        __local_var;                                            \
  })
0
Michael T On

Not every warning makes sense. Sometimes it's a shortcoming in the compiler. For instance, this code doesn't need a warning.

- (nullable id)transformedValue:(nullable id)value {
    id result = value != nil ? UIImageJPEGRepresentation(value, 1.0) : nil;
    return result;
}

We are checking to see if it's null! What more can we do? Why create an extra pointer?

So, we do this:

- (nullable id)transformedValue:(nullable id)value {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnullable-to-nonnull-conversion"
    id result = value != nil ? UIImageJPEGRepresentation(value, 1.0) : nil;
#pragma clang diagnostic pop
    return result;
}

Why is this the proper answer?

First, it's OK to be smarter than the compiler. You don't want to start trashing your code, just because of a bogus warning.

This solution specifies the exact warning message to suppress, and it suppresses it for just one line.