apple / swift-xcode-playground-support

Logging and communication to allow Swift toolchains to communicate with Xcode.
Apache License 2.0
280 stars 64 forks source link

[SR-13896] Confusing error message when assigning an optional UIImage as a state property #57

Open swift-ci opened 3 years ago

swift-ci commented 3 years ago
Previous ID SR-13896
Radar rdar://60600911
Original Reporter frostra1n (JIRA User)
Type Bug

Attachment: Download

Environment macOS Catalina 10.15.7 Apple Swift version 5.3 (swiftlang-1200.0.29.2 clang-1200.0.30.1) Xcode 12.1
Additional Detail from JIRA | | | |------------------|-----------------| |Votes | 0 | |Component/s | Compiler, Xcode Playground Support | |Labels | Bug, PropertyWrappers | |Assignee | None | |Priority | Medium | md5: f20a4f1b3d227c0ef91b2fdd21421924

Issue Description:

I'm working on project that uses both UIKit and SwiftUI and I found a scenario that shows a confusing error when assigning a `UIImage?` in a struct initializer. The error is:

Variable 'self.asPreview' used before being initialized

And this is the code that produces the error:

import SwiftUI
import UIKit

struct BookView: View {
    let asPreview: Bool
    @State var image: UIImage?

    init(image: UIImage?, asPreview: Bool = false) {
        self.image = image         
        self.asPreview = asPreview
    }

    var body: some View {
         Group {}
     }
 }

The error is pointed in line 9 right under the equal sign ("self.image = image"), but actually that line assigns a different struct property, so it's a little bit confusing.

What it could be happening is that a different type of value is being assigned to the image variable, so the error message that should be shown instead could be something related of assigning an `UIImage?` to a `State\<UIImage?>` variable.

The code above can be used in Xcode playground to reproduce the issue. I've attached a screenshot reproducing the bug.

LucianoPAlmeida commented 3 years ago

Here is a reduced reproducer

@propertyWrapper
class A {
  var wrappedValue: Int?
}

struct S {
    let b: Bool
    @A var a: Int?

    init(a: Int?, b: Bool = false) {
        self.a = a // 'self' used before all stored properties are initialized
        self.b = b
    }
}

As far as I could see the problem is related to the fact that in this case @A is a class and the way self is implicit passed to the property wrapper in the assignment that is required to all properties being initialized.
To me, the diagnostics should be improved to make this more clear.
But as a workaround, it should be able to make it work by making sure initialized all the other members before the property wrapper
e.g.

init(image: UIImage?, asPreview: Bool = false) {
   self.asPreview = asPreview // Make sure this is initialized before call property wrapper
   self.image = image 
}

cc @hborla

hborla commented 3 years ago

This is a known issue where out-of-line initialization via wrapped value isn't supported for class property wrappers and struct property wrappers with nonmutating setters (due to limitations of the assign_by_wrapper instruction in SIL). Here's a reproducer without SwiftUI:

@propertyWrapper
struct State<Value> {
  private var _wrappedValue: Value

  var wrappedValue: Value {
    get { _wrappedValue }
    nonmutating set {}
  }

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

struct BookView {
    let flag: Bool
    @State var str: String

    init(str: String, flag: Bool = false) {
        self.str = str
        self.flag = flag
    }
 }

@LucianoPAlmeida you're right that the error message should make it clear how to fix the issue. The right fix is to initialize the backing property wrapper directly, e.g.:

    init(str: String, flag: Bool = false) {
        self._str = State(wrappedValue: str)
        self.flag = flag
    }