It is possible for nil to make it past a guard statement, here's how!

When I was working on property wrappers, I realized something that kind of blew my mind:

Any value of a non-optional generic type can be optional. That means a nil value can sneak past a guard statement that otherwise appears to prevent this.

See below, which compiles and runs in the Swift 5.2.4 playground:

struct Foo {
    var bar: Int?
    
    func process<V>(id: Int, dict: [Int:PartialKeyPath<Self>]) -> V {
        guard let keyPath = dict[id],
            let val = self[keyPath: keyPath] as? V 
            else {
            fatalError()
        }
        return val // if V is optional then val can still be nil here even though the return type is not optional... and we made it past a guard statement!
    }
}
let foo = Foo()
var dict: [Int: PartialKeyPath<Foo>] = [1:\Foo.bar]
let test: Int? = foo.process(id: 1, dict: dict)
print(test) // prints 'nil'

Isn’t that just the reality of multilevel optionals? Unwrapping only does one layer at a time.

2 Likes

What's problematic about it to me, is that you cannot do this:

struct Bar<Baz> {
    var baz: Baz = nil
}

So that means we have to do this:

struct Bar<Baz> {
   var baz: Baz? = nil
}

However now lets say this is actually a property wrapper:

@propertyWrapper
struct Bar<Val> {
    var override: Val?
    var wrappedValue: Val 
    init(override: Val?) {
        self.override = override
    }
}

struct Foo {
    @Bar var bar: Int?
}

let foo = Foo(bar: nil) // doesn't compile, because nil is not compatible with type `Int?` according to the compiler

In a property wrapper you might want to have an optional default value (like "override" above), which is nil if the caller didn't override it. But now what happens if the wrapped parameter is also optional? It can't be compiled!

That seems like an odd interaction between diagnostics and your property wrapper, which isn’t well formed.

What's not well-formed about it?

Bear in mind, this is a very simplified example of the situation, which I came across when implementing something more complex.

The problem is that Swift does not really provide any mechanism by which to check whether a given generic type is representable by "nil", in a way that satisfies the compiler. To wit:

    var wrappedValue: Val {
        if Val.self is ExpressibleByNilLiteral {
            return nil
        }
        else { /* code that returns a Val */ } 
    }

This does not compile. Why not?

It seems we've painted ourselves into a corner by assuming that a type that's not explicitly declared as optional can't be nil, when in fact, it totally can be.

There's also no way to specify that a given generic type must not be optional.

It doesn't compile.

It’s hard to tell. If you’re using Bar you just wrote, there’s so many things wrong already. You’re using unescaped keyword override as an identifier. You’re not using the right init(wrappedValue) signature, and still expect the synthesized Foo initializer to treat bar as type Int?, when in fact it is of type Bar<Int?> which it is, of course, not ExpressibleByNilLiteral.

Bonus: You’re checking if the metatype Value.Self conforms to ExpressibleByNilLiteral which it is not. You would instead want to check if Value is a subtype of ExpressibleByNilLiteral.

I think you’re trying to achieve this:

@propertyWrapper
struct Bar<Value> {
  var storage: Value
  var wrappedValue: Value {
    if let metatype = Value.self as? ExpressibleByNilLiteral.Type {
      return metatype.init(nilLiteral: ()) as! Value
    } else {
      // Dunno what to do here...
      return storage
    }
  }

  init(wrappedValue: Value) {
    storage = wrappedValue
  }
}

struct Foo {
  @Bar var bar: Int?
}

let foo = Foo(bar: nil)
foo.bar

Correct me if I missed anything. I assumed so many things already due to how fragmented the problem description is.

We need the metatype variable because we want to use static function (initializer) for the conforming type (Value). If you want to just check, you can just do:

if Value.self is ExpressibleByNilLiteral.Type {
  ...
}

I don’t know how useful it’d be, though. More often you’ll want to cleanly separate a conforming to A and otherwise implementation. I’d do it by having both Bar and OptionalBar, but there’s many ways to do it.