Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-13815] Confusing unwrapping diagnostic when using implicit member access on Optionals. #56212

Closed
swift-ci opened this issue Nov 3, 2020 · 15 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself diagnostics QoI Bug: Diagnostics Quality of Implementation type checker Area → compiler: Semantic analysis

Comments

@swift-ci
Copy link
Collaborator

swift-ci commented Nov 3, 2020

Previous ID SR-13815
Radar rdar://problem/71082108
Original Reporter iDevid (JIRA User)
Type Bug
Status Resolved
Resolution Done
Additional Detail from JIRA
Votes 1
Component/s Compiler
Labels Bug, DiagnosticsQoI, TypeChecker
Assignee iDevid (JIRA)
Priority Medium

md5: 790bfd0e832f8f87224b0af0f4e3cd77

is duplicated by:

  • SR-13842 Compiler fails to type check optional UIImage when SwiftUI is imported

Issue Description:

struct Object {
    init?() {
        return nil
    }
}

struct Test {
    let id: Int
    let object: Object?
}

Test(id: 1, object: nil)
Test(id: 1, object: Object())
Test(id: 1, object: .init()) // this will fail

As you can see, the Test Struct contains an id along with an optional Object property.

Passing the Object's .init() in the Test initializer will lead to the error:

Value of optional type 'Object?' must be unwrapped to a value of type 'Object'

This diagnostic doesn't make it clear what the issue is.

@typesanitizer
Copy link

Could you please add a short comment describing why this was closed as Invalid, in case someone else stumbles across the same problem and lands here?

@swift-ci
Copy link
Collaborator Author

swift-ci commented Nov 4, 2020

Comment by Davide Sibilio (JIRA)

Hello theindigamer (JIRA User),

the real problem here, is that the compiler is searching for the Optional<Object>.init() and not the Object.init?().

So it might seem like a bug, but thinking with some colleagues, maybe it's not.

@swift-ci
Copy link
Collaborator Author

swift-ci commented Nov 4, 2020

Comment by Davide Sibilio (JIRA)

Adding an Extension like this:

extension Optional where Wrapped == Object {
    init() {
        self = .none
    }
}

will make the above code work, is it intentional?

@typesanitizer
Copy link

I think yes, that makes sense. The way the leading dot syntax works is that the "return type" is used as the prefix for the lookup. In this case, from context, the type of .init() is supposed to be Object?, so we end up trying to do Optional<Object>.init(), which fails.

@swift-ci
Copy link
Collaborator Author

swift-ci commented Nov 5, 2020

Comment by Davide Sibilio (JIRA)

Despite this, I think that the given error is incorrect, it should be something like "Does not exist an initializer for Optional<Object>" and not "Value of optional type 'Object?' must be unwrapped to a value of type 'Object' ".

@swift-ci
Copy link
Collaborator Author

swift-ci commented Nov 5, 2020

Comment by Davide Sibilio (JIRA)

There is something strange with failable inits

This code doesn't work:

struct Object {

    let id: Int
 
    init?() {
        return nil
    }

    init(id: Int) {
        self.id = id
    }
}

let object: Object = .init() ?? Object(id: 1) // will fail: Value of optional type 'Object?' must be unwrapped to a value of type 'Object'
let object: Object = .init() ?? Object()! // will fail: Value of optional type 'Object?' must be unwrapped to a value of type 'Object'
let object: Object = .init()! // will fail: type of expression is ambiguous without more context

@typesanitizer
Copy link

I agree that the diagnostic could be more helpful.

That said, the other examples fail to compile for similar reasons. Based on the operators, the .init() is checked for Optional<Object>, not for Object (the LHS of a ?? and ! is expected to be an optional).

@typesanitizer
Copy link

(I have edited the title and description to reflect the problem with the diagnostic. Please feel free to change them in case you'd like to improve the wording.)

@typesanitizer
Copy link

@swift-ci create

@LucianoPAlmeida
Copy link
Collaborator

Just ran this on a near master branch and the

let object: Object = .init()! // will fail: type of expression is ambiguous without more context

Seems to typecheck correctly, the others still produce the same confusing diagnostic.

@swift-ci
Copy link
Collaborator Author

Comment by Frederick Kellison-Linn (JIRA)

A bit more context here, since I've just recently done some implementation work on "leading dot syntax" (AKA implicit member syntax, AKA unresolved member expressions):

This one is a bit tricky. The way that lookup is supposed to work for an unresolved member expression ".foo" is basically:

  1. Determine the contextual type T.
  2. Perform lookup of "foo" in T.
  3. If no results are found, and T == Optional<U> for some type U, perform lookup of "foo" in U.

It's (3) that causes the issue seen here: since lookup only considers the base name, and because Optional has at least one `init`, we find results on the first lookup pass in (2) on `Optional<Object>` and thus never look into `Object`. Importantly, (3) does not care whether the results returned by (2) will type check properly or not. If anything exists in `Optional` under the name in question, we will not even try to look into the wrapped type.

This causes even more issues when Optional is imbued with conditional conformances. SR-13842 shows an example where, because of Optional's conditional conformance to SwiftUI.View, lookup for `.background as UIImage?` finds `View.background(:alignment🙂` and skips lookup in `UIImage`, despite the fact that we should know at lookup time that `View.background(:alignment🙂` is not available on `UIImage`, and that `View.background(_:alignment🙂` is an instance method (not static).

cc @xedin, what do you think the correct behavior is here? It seems not sufficient to skip the lookup in `T` whenever `Optional<T>` has results, so there are a couple alternatives:

  1. Always look into both `T` and `Optional<T>`, but include results from `T` as disfavored overloads if there are results from `Optional<T>`.
  2. Only skip lookup in `T` if there are static members returned by the lookup on `Optional<T>`.
  3. Only skip lookup in `T` if there are available results from the lookup on `Optional<T>` (as determined by the type `T`).
  4. Only skip lookup in `T` if there are results on `Optional` itself, i.e., not results from a protocol/extension (like `View`).
  5. Some combination of the above, or another heuristic...

Note: AFAICT, only (1) above will address the issue here with `init`, since lookup will always find Optional's various initializers.

@LucianoPAlmeida
Copy link
Collaborator

jumhyn (JIRA User) Just curious in the 1. option why disfavor the overloads from T if there are results from Optional<T>?

@swift-ci
Copy link
Collaborator Author

Comment by Frederick Kellison-Linn (JIRA)

@LucianoPAlmeida The best reason is that we have to maintain source compatibility, since today we'll never attempt any results from `T` if there are results from `T?`, so we wouldn't want to start diagnosing those cases as ambiguous. You can see this today if you define a custom type with a static `.none` member (although in this specific case we emit a warning):

```
enum E { case none }

func takesE(_: E?) {}
takesE(.none) // warning: assuming you mean `Optional<E>.none`, did you mean `E.none` instead?
```

@swift-ci
Copy link
Collaborator Author

Comment by Frederick Kellison-Linn (JIRA)

Also, FWIW, we already prefer solutions where an overload choice has kind `Decl` over those where the overload has kind `DeclViaUnwrappedOptional`, so maybe it would "just work" to always look through optional types? 🤷

@xedin
Copy link
Member

xedin commented Dec 5, 2020

Fixed by #34715 Thanks again, jumhyn (JIRA User)! iDevid (JIRA User)Please validate using the next available toolchain from main branch (found at swift.org).

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself diagnostics QoI Bug: Diagnostics Quality of Implementation type checker Area → compiler: Semantic analysis
Projects
None yet
Development

No branches or pull requests

4 participants