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

Incorrect optional inference on trunk #80494

Closed
Steelskin opened this issue Apr 3, 2025 · 6 comments
Closed

Incorrect optional inference on trunk #80494

Steelskin opened this issue Apr 3, 2025 · 6 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. triage needed This issue needs more specific labels

Comments

@Steelskin
Copy link
Contributor

Description

Code that used to compile with older compiler versions fails to compile with a compiler built from trunk. This seems to be due to some incorrect type inferrence.

Reproduction

extension AsyncStream {
    public static var finished: Self {
        Self { $0.finish() }
    }
}

public class Sthg {
    public func stream() -> AsyncStream<Int> {
        let stream = AsyncStream { continuation in
            for i in 1...9 {
                continuation.yield(i)
            }
            continuation.finish()
        }
        return stream
    }
    func fetch() async throws {
        await { [weak self] in
            for await i in self?.stream() ?? .finished {
                print(i)
            }
        }()
    }
}

Expected behavior

The above code compiles. It compiles fine with Swift 6.0.3.

Environment

Apple Swift version 6.2-dev (LLVM 86709865028dc89, Swift 2e6d7f71608bbad)
Target: arm64-apple-macosx15.0
Build config: +assertions

Additional information

Error message:

$ ~/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-04-02-a.xctoolchain/usr/bin/swiftc main.swift
main.swift:19:43: error: for-in loop requires 'AsyncStream<Int>?' to conform to 'AsyncSequence'; did you mean to unwrap optional?
17 |     func fetch() async throws {
18 |         await { [weak self] in
19 |             for await i in self?.stream() ?? .finished {
   |                                           `- error: for-in loop requires 'AsyncStream<Int>?' to conform to 'AsyncSequence'; did you mean to unwrap optional?
20 |                 print(i)
21 |             }
@Steelskin Steelskin added bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. triage needed This issue needs more specific labels labels Apr 3, 2025
@Steelskin
Copy link
Contributor Author

Steelskin commented Apr 3, 2025

Smaller reproducer:

extension AsyncStream {
    public static var finished: Self { fatalError() }
}

extension Int {
    public func stream() -> AsyncStream<Int> { fatalError() }
}

extension Optional<Int> {
    func fetch() async throws {
        for await i in self?.stream() ?? .finished {
            print(i)
        }
    }
}

EDIT: Even smaller:

extension AsyncStream {
    public static var finished: Self { fatalError() }
}

nonisolated var defaultStream: AsyncStream<Int> { fatalError() }

func fetch() async throws {
    for await i in (defaultStream ?? .finished) {}
}

Godbolt link: https://godbolt.org/z/7dGnhE9dj

@jumhyn-browser
Copy link

Problem occurs even with a custom operator:

extension AsyncStream {
    public static var finished: Self { fatalError() }
}

nonisolated var defaultStream: AsyncStream<Int> { fatalError() }

infix operator ???

func ???<T>(_ lhs: T?, rhs: T) -> T { fatalError() }

func fetch() async throws {
    for await _ in (defaultStream ??? .finished) {}
}

with this case, the error is:

error: type 'AsyncSequence' has no member 'finished'
10 | 
11 | func fetch() async throws {
12 |     for await _ in (defaultStream ??? .finished) {}
   |                                        `- error: type 'AsyncSequence' has no member 'finished'
13 | }

So it appears something changed with how the types are getting inferred for the generic args here. cc @xedin

@jumhyn-browser
Copy link

For some reason on main we are not discovering the binding for $T1 := AsyncStream<Int> until we already bind $T3 := AsyncSequence.

---Constraint solving at [<source>:12:5 - line:12:48]---
(increasing 'sync-in-asynchronous' score by 1 @ locator@0x632803988190 [DeclRef@<source>:12:35])
  (overload set choice binding $T0 := ($T1?, $T1) -> $T1 [T := $T1])
  (overload set choice binding $T2 := AsyncStream<Int>)
(increasing 'value to optional promotion' score by 1 @ locator@0x6328039887c8 [Binary@<source>:12:35 → apply argument] → comparing call argument #0 to parameter #0)

---Initial constraints for the given expression---
(call_expr implicit type="$T8" location=<source>:12:48 range=[<source>:12:20 - line:12:48] isolation_crossing="none"
  (unresolved_dot_expr implicit type="$T7" location=<source>:12:48 range=[<source>:12:20 - line:12:48] field="makeAsyncIterator()" function_ref=single apply
    (paren_expr type="$T1" location=<source>:12:35 range=[<source>:12:20 - line:12:48]
      (binary_expr type="$T1" location=<source>:12:35 range=[<source>:12:21 - line:12:40] isolation_crossing="none"
        (declref_expr type="($T1?, $T1) -> $T1" location=<source>:12:35 range=[<source>:12:35 - line:12:35] decl="output.(file).???@<source>:9:6" function_ref=single apply)
        (argument_list implicit
          (argument
            (declref_expr type="AsyncStream<Int>" location=<source>:12:21 range=[<source>:12:21 - line:12:21] decl="output.(file).defaultStream@<source>:5:17" function_ref=unapplied))
          (argument
            (unresolved_member_chain_expr implicit type="$T5" location=<source>:12:40 range=[<source>:12:39 - line:12:40]
              (unresolved_member_expr type="$T4" location=<source>:12:40 range=[<source>:12:39 - line:12:40] name="finished" function_ref=unapplied)))))))
  (argument_list implicit))

Score: [sync-in-asynchronous(s), weight: 17, impact: 1] [value to optional promotion(s), weight: 7, impact: 1]
Contextual Type: AsyncSequence at Paren@<source>:12:35
Type Variables:
  $T0 as ($T1?, $T1) -> $T1 @ locator@0x632803988190 [DeclRef@<source>:12:35]
  $T1 [can bind to: hole] [attributes: potentially_incomplete] [adjacent to: $T5] [potential bindings: (supertypes of) AsyncStream<Int>] @ locator@0x6328039881e0 [DeclRef@<source>:12:35 → generic parameter 'T']
  $T2 as AsyncStream<Int> @ locator@0x632803988480 [DeclRef@<source>:12:21]
  $T3 [can bind to: noescape, hole] [attributes: hole, delayed] [defaultable bindings: 1] [potential bindings: <none>] @ locator@0x6328039884d0 [UnresolvedMember@<source>:12:40 → member reference base]
  $T4 [can bind to: lvalue, noescape] [attributes: delayed] [adjacent to: $T5] [potential bindings: <none>] @ locator@0x6328039884f8 [UnresolvedMember@<source>:12:40 → unresolved member]
  $T5 [can bind to: lvalue, noescape, hole] [attributes: hole] [adjacent to: $T1, $T4] [defaultable bindings: 1] [potential bindings: <none>] @ locator@0x6328039885e8 [UnresolvedMemberChainResult@<source>:12:40 → unresolved chain result]
  $T6 equivalent to $T1 @ locator@0x6328039886e8 [Binary@<source>:12:35 → function result]
  $T7 [can bind to: lvalue, noescape] [attributes: delayed] [potential bindings: <none>] @ locator@0x6328039889b8 [UnresolvedDot@<source>:12:48 → member]
  $T8 [can bind to: noescape] [attributes: delayed] [potential bindings: <none>] @ locator@0x632803988a90 [Call@<source>:12:48 → function result]
Active Constraints:
  $T1 conforms to Copyable @ locator@0x632803988268 [DeclRef@<source>:12:35 → opened generic → type parameter requirement #0 (conformance)]
  $T1 conforms to Escapable @ locator@0x6328039882e0 [DeclRef@<source>:12:35 → opened generic → type parameter requirement #1 (conformance)]
  AsyncStream<Int> operator arg conv $T1 @ locator@0x632803988828 [Binary@<source>:12:35 → apply argument → comparing call argument #0 to parameter #0 → optional injection]
  $T5 operator arg conv $T1 @ locator@0x6328039888b0 [Binary@<source>:12:35 → apply argument → comparing call argument #1 to parameter #1]
Inactive Constraints:
  $T3.Type[(implicit) .finished: value] == $T4 @ locator@0x6328039884f8 [UnresolvedMember@<source>:12:40 → unresolved member]
  $T4 conv $T5 @ locator@0x6328039885e8 [UnresolvedMemberChainResult@<source>:12:40 → unresolved chain result]
  $T5 unresolved member chain base $T3 @ locator@0x6328039885e8 [UnresolvedMemberChainResult@<source>:12:40 → unresolved chain result]
  $T1[.makeAsyncIterator(): value] == $T7 @ locator@0x6328039889b8 [UnresolvedDot@<source>:12:48 → member]
  () -> $T8 applicable fn $T7 @ locator@0x632803988b10 [Call@<source>:12:48 → apply function]
Resolved overloads:
  selected overload set choice defaultStream: $T2 == AsyncStream<Int> for locator@0x632803988480 [DeclRef@<source>:12:21]
  selected overload set choice ???: $T0 == ($T1?, $T1) -> $T1 for locator@0x632803988190 [DeclRef@<source>:12:35]
Opened types:
  locator@0x632803988190 [DeclRef@<source>:12:35] opens 'T' (T) -> $T1

  (overload set choice binding $T10 := @lvalue $T8)

---Initial constraints for the given expression---
(await_expr implicit type="$T12" location=<source>:12:5 range=[<source>:12:5 - line:12:5]
  (call_expr implicit type="$T12" location=<source>:12:5 range=[<source>:12:5 - line:12:5] isolation_crossing="none"
    (unresolved_dot_expr implicit type="$T11" location=<source>:12:5 range=[<source>:12:5 - line:12:5] field="next(isolation:)" function_ref=single apply
      (declref_expr implicit type="@lvalue $T8" location=<source>:12:5 range=[<source>:12:5 - line:12:5] decl="output.(file).fetch().$generator@<source>:12:20" function_ref=unapplied))
    (argument_list implicit labels="isolation:"
      (argument label="isolation"
        (current_context_isolation_expr implicit type="(any Actor)?" location=<source>:12:5 range=[<source>:12:5 - line:12:5])))))

Score: [sync-in-asynchronous(s), weight: 17, impact: 1] [value to optional promotion(s), weight: 7, impact: 1]
Contextual Type: AsyncIteratorProtocol at DeclRef@<source>:12:5
Contextual Type: AsyncSequence at Paren@<source>:12:35
Type Variables:
  $T0 as ($T1?, $T1) -> $T1 @ locator@0x632803988190 [DeclRef@<source>:12:35]
  $T1 [can bind to: hole] [attributes: potentially_incomplete] [adjacent to: $T5] [potential bindings: (supertypes of) AsyncStream<Int>] @ locator@0x6328039881e0 [DeclRef@<source>:12:35 → generic parameter 'T']
  $T2 as AsyncStream<Int> @ locator@0x632803988480 [DeclRef@<source>:12:21]
  $T3 [can bind to: noescape, hole] [attributes: hole, delayed] [defaultable bindings: 1] [potential bindings: <none>] @ locator@0x6328039884d0 [UnresolvedMember@<source>:12:40 → member reference base]
  $T4 [can bind to: lvalue, noescape] [attributes: delayed] [adjacent to: $T5] [potential bindings: <none>] @ locator@0x6328039884f8 [UnresolvedMember@<source>:12:40 → unresolved member]
  $T5 [can bind to: lvalue, noescape, hole] [attributes: hole] [adjacent to: $T1, $T4] [defaultable bindings: 1] [potential bindings: <none>] @ locator@0x6328039885e8 [UnresolvedMemberChainResult@<source>:12:40 → unresolved chain result]
  $T6 equivalent to $T1 @ locator@0x6328039886e8 [Binary@<source>:12:35 → function result]
  $T7 [can bind to: lvalue, noescape] [attributes: delayed] [potential bindings: <none>] @ locator@0x6328039889b8 [UnresolvedDot@<source>:12:48 → member]
  $T8 [can bind to: noescape] [attributes: delayed] [potential bindings: <none>] @ locator@0x632803988a90 [Call@<source>:12:48 → function result]
  $T9 equivalent to $T1 @ locator@0x632803988bb0 [Paren@<source>:12:35]
  $T10 as @lvalue $T8 @ locator@0x632803988c40 [DeclRef@<source>:12:5]
  $T11 [can bind to: lvalue, noescape] [attributes: delayed] [potential bindings: <none>] @ locator@0x632803988cd0 [UnresolvedDot@<source>:12:5 → member]
  $T12 [can bind to: noescape] [attributes: delayed] [potential bindings: <none>] @ locator@0x632803988db0 [Call@<source>:12:5 → function result]
Active Constraints:
  $T1 conforms to Copyable @ locator@0x632803988268 [DeclRef@<source>:12:35 → opened generic → type parameter requirement #0 (conformance)]
  $T1 conforms to Escapable @ locator@0x6328039882e0 [DeclRef@<source>:12:35 → opened generic → type parameter requirement #1 (conformance)]
  AsyncStream<Int> operator arg conv $T1 @ locator@0x632803988828 [Binary@<source>:12:35 → apply argument → comparing call argument #0 to parameter #0 → optional injection]
  $T5 operator arg conv $T1 @ locator@0x6328039888b0 [Binary@<source>:12:35 → apply argument → comparing call argument #1 to parameter #1]
  $T1[.makeAsyncIterator(): value] == $T7 @ locator@0x6328039889b8 [UnresolvedDot@<source>:12:48 → member]
  () -> $T8 applicable fn $T7 @ locator@0x632803988b10 [Call@<source>:12:48 → apply function]
Inactive Constraints:
  $T3.Type[(implicit) .finished: value] == $T4 @ locator@0x6328039884f8 [UnresolvedMember@<source>:12:40 → unresolved member]
  $T4 conv $T5 @ locator@0x6328039885e8 [UnresolvedMemberChainResult@<source>:12:40 → unresolved chain result]
  $T5 unresolved member chain base $T3 @ locator@0x6328039885e8 [UnresolvedMemberChainResult@<source>:12:40 → unresolved chain result]
  $T1 conforms to AsyncSequence @ locator@0x632803988168 [Paren@<source>:12:35 → contextual type]
  @lvalue $T8[.next(isolation:): value] == $T11 @ locator@0x632803988cd0 [UnresolvedDot@<source>:12:5 → member]
  ((any Actor)?) -> $T12 applicable fn $T11 @ locator@0x632803988e50 [Call@<source>:12:5 → apply function]
Resolved overloads:
  selected overload set choice $generator: $T10 == @lvalue $T8 for locator@0x632803988c40 [DeclRef@<source>:12:5]
  selected overload set choice defaultStream: $T2 == AsyncStream<Int> for locator@0x632803988480 [DeclRef@<source>:12:21]
  selected overload set choice ???: $T0 == ($T1?, $T1) -> $T1 for locator@0x632803988190 [DeclRef@<source>:12:35]
Opened types:
  locator@0x632803988190 [DeclRef@<source>:12:35] opens 'T' (T) -> $T1

  (considering: $T1 conforms to Copyable @ locator@0x632803988268 [DeclRef@<source>:12:35 → opened generic → type parameter requirement #0 (conformance)]
    (simplification result:
    )
    (outcome: unsolved)
  )
  (considering: $T1 conforms to Escapable @ locator@0x6328039882e0 [DeclRef@<source>:12:35 → opened generic → type parameter requirement #1 (conformance)]
    (simplification result:
    )
    (outcome: unsolved)
  )
  (considering: AsyncStream<Int> operator arg conv $T1 @ locator@0x632803988828 [Binary@<source>:12:35 → apply argument → comparing call argument #0 to parameter #0 → optional injection]
    (simplification result:
    )
    (outcome: unsolved)
  )
  (considering: $T5 operator arg conv $T1 @ locator@0x6328039888b0 [Binary@<source>:12:35 → apply argument → comparing call argument #1 to parameter #1]
    (simplification result:
    )
    (outcome: unsolved)
  )
  (considering: $T1[.makeAsyncIterator(): value] == $T7 @ locator@0x6328039889b8 [UnresolvedDot@<source>:12:48 → member]
    (simplification result:
    )
    (outcome: unsolved)
  )
  (considering: () -> $T8 applicable fn $T7 @ locator@0x632803988b10 [Call@<source>:12:48 → apply function]
    (simplification result:
    )
    (outcome: unsolved)
  )
  (Potential Binding(s): 
    ($T1 [can bind to: hole] [attributes: potentially_incomplete] [adjacent to: $T5] [potential bindings: (supertypes of) AsyncStream<Int>])
    ($T3 [can bind to: noescape, hole] [attributes: potentially_incomplete] [potential bindings: AsyncSequence])
    ($T12 [can bind to: noescape] [attributes: delayed] [adjacent to: $T14] [potential bindings: (subtypes of) $T14?])
  )
  (attempting type variable binding $T3 := AsyncSequence
    (considering: $T3.Type[(implicit) .finished: value] == $T4 @ locator@0x6328039884f8 [UnresolvedMember@<source>:12:40 → unresolved member]
      (simplification result:
        (removed constraint: $T3.Type[(implicit) .finished: value] == $T4 @ locator@0x6328039884f8 [UnresolvedMember@<source>:12:40 → unresolved member])
        (failed constraint $T3.Type[(implicit) .finished: value] == $T4 @ locator@0x6328039884f8 [UnresolvedMember@<source>:12:40 → unresolved member])
      )
      (outcome: error)
    )
  )

---Solver statistics---
Total number of scopes explored: 2
Total number of trail steps: 8
Maximum depth reached while exploring solutions: 2
---Attempting to salvage and emit diagnostics---
...

@jumhyn-browser
Copy link

Ah, potentially already fixed by #80443 !

@Steelskin
Copy link
Contributor Author

I would like to leave this open to confirm that #80443 fixes the issue for us.

@Steelskin
Copy link
Contributor Author

I can confirm that #80443 fixed the issue. Closing.

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. triage needed This issue needs more specific labels
Projects
None yet
Development

No branches or pull requests

2 participants