Property wrapper, default values and memberwise initializer weirdness

I'm noticing a weird issue with a property wrapper where the designated initializer of a type using the property wrapper changes depending on how an optional value is initialised.

For example, given the following property wrapper:

@propertyWrapper
struct ExampleProperty<T> {
    var wrappedValue: T?
}

And this type using it:

struct SomeType {
    @ExampleProperty var foo: Int? = nil
}

The generated memberwise initializer for SomeType is init(foo: Int?). That's fine, but if I don't explicitly initialize foo to nil:

struct SomeType {
    @ExampleProperty var foo: Int?
}

The memberwise initializer is now init(foo: ExampleValue<Int>) which isn't what I'd expect or want. I spotted this bug because we are using SwiftLint and one of its default rules is to strip redundant optional initialisation.

It gets weirder if you have a property wrapper that takes an additional parameter. This time, I've given the property wrapper an explicit public initializer, as I would if it was in a library:

@propertyWrapper
struct ExampleProperty<T> {
    var wrappedValue: T?
    var other: String

    public init(wrappedValue: T?, other: String) {
        self.wrappedValue = wrappedValue
        self.other = other
    }
}

This time, @ExampleProperty var foo: Int? won't even compile, complaining about missing an argument for property wrappedValue - again, I can fix this in one of two ways - 1) explicitly doing var foo:Int? = nil or giving the wrappedValue parameter in the property wrapper's init a default value of nil - this also works but again causes the problem of changing the memberwise initializer in an unexpected way.

For now it looks like my only solution is to disable the SwiftLint default rule and explicitly add = nil at the property declaration but I'm confused about why this is necessary and why the other solutions affect the memberwise initializer.

Will it help for now if you change from var foo: T? to var foo: Optional<T> (with or without = nil) ?

It doesn't seem to make much difference in the case of the property wrapper with a second parameter: it requires me to specify = nil either at the property declaration or in the property wrapper's init, with the latter changing the init of the enclosing type in an undesirable way.

How are you "generating" the initializers? Xcode 14 beta 3 does this for both:

internal init(foo: Int? = nil) {
  self.foo = foo
}

I just wrote it by hand. The example that Xcode generates is the one causing the problem.

Its worth noting that the issue still exists without an explicit initializer, where switching between @ExampleProperty var foo: Int? and @ExampleProperty var foo: Int? = nil produces different memberwise initialisers for SomeType.

I'm saying that we're getting different results. I don't know why that would be possible. I'm talking about Show Code Actions->Generate Memberwise Initializer.

The other one is

internal init(wrappedValue: T? = nil, other: String) {
  self.wrappedValue = wrappedValue
  self.other = other
}

Sorry, I think I misunderstood your last post - you're talking about the memberwise initializer for SomeType? I'm not explicitly defining them, I'm just using the ones that the compiler implicitly provides. I'm using Xcode 13.3, so its perhaps a bug in Swift 5.6?

Seems so.

Just to be really clear, this is what I'm seeing in an Xcode playground:

And without the explicit = nil:

Okay, now I get you. "Generate" meant something different to us.

Same result here. Is always using the command an option?

I certainly could generate a memberwise initializer in the code as a workaround, but its less than ideal because it means I now need to maintain something that I otherwise wouldn't need to and I'm only really using the memberwise initializer in tests. For now I've gone with an explicit = nil on the property declaration and disabling the SwiftLint rule.

1 Like

cc @hborla

Quite sure this question has come up before but the reference isn’t coming up on a quick search.

There was this thread that discussed some of the same issues (though not for optionals specifically, I don't think), which led to a pitch from @filip-sakel, @amritpan, and me. But AFAIK there hasn't been any additional motion on the issue.

1 Like