swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.59k stars 10.36k forks source link

Result builder type inference bug requiring `some` #60445

Open stephencelis opened 2 years ago

stephencelis commented 2 years ago

Describe the bug

There are cases where result builder type inference fails when using protocols with generics, but succeeds when using an opaque result type.

Steps To Reproduce

Try to build the following code:

// Given a protocol with a primary associated type
protocol P<A> {
  associatedtype A
}

// And a builder for this protocol that is generic over the same type
@resultBuilder
enum B<A> {
  // Use `buildExpression` to fix type inference over a builder block
  static func buildExpression<T: P>(_ expression: T) -> T where T.A == A {
    expression
  }

  // Accumulate types with `buildPartialBlock`
  static func buildPartialBlock<T: P>(first: T) -> T where T.A == A {
    first
  }
  static func buildPartialBlock<T1: P, T2: P>(accumulated: T1, next: T2) -> C<T1, T2>
  where T1.A == A {
    C(a1: accumulated, a2: next)
  }

  struct C<A1: P, A2: P>: P where A1.A == A2.A {
    typealias A = A1.A

    let a1: A1
    let a2: A2
  }
}

// A conformance with a concrete associated type.
struct A1: P {
  typealias A = Int
}

// A conformance with a generic associated type.
struct A2<A>: P {
}

// A conformance that wraps a builder (like SwiftUI.Group)
struct Build1<T: P>: P {
  typealias A = T.A

  let t: T

  init(@B<T.A> t: () -> T) {
    self.t = t()
  }
}

// Fails:
var x: some P<Int> {
  Build1 { // 🛑 Generic parameter 'T' could not be inferred
    A1()
    A2() // Will build with explicit `A2<Int>()` here instead
  }
}

// A simple function that uses `some` instead:
func Build2<A>(@B<A> t: () -> some P<A>) -> some P<A> {
  t()
}

// Builds:
var y: some P<Int> {
  Build2 { // ✅ Builds fine!
    A1()
    A2()
  }
}

Expected behavior

I expect the line that fails to build. Right now it seems impossible to write BuildN as a conformance (Build1) and instead we must write it as a function (Build2).

Environment (please fill out the following information)

stephencelis commented 2 years ago

@slavapestov You might have an idea of what's going on here...

li3zhen1 commented 10 months ago

I've occurred similar problems. For the example you mentioned above I've figured out a work around:

struct Build1<T: P>: P {
    typealias A = T.A

    let t: T

    // This signature compiles
    init<_A>(@B<_A> t: () -> T) where _A==T.A {
        self.t = t()
    }
}