swiftlang / swift

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

Serious type checking regression in Swift 5.8. #65765

Open mluisbrown opened 1 year ago

mluisbrown commented 1 year ago

Description There appears to be a serious regression in the type checker for Swift 5.8 (compared to Swift 5.7). In the repro project I created, successive chaining of calls to a result builder modifier cause an exponential increase in compile types, reaching a point where the compiler gives up with Unable to type check the expression in reasonable time. In Swift 5.7 there was no such issue and successive chaining of the modifier didn't cause any significant increase in compile time.

Steps to reproduce

  1. Clone the Swift Package at https://github.com/mluisbrown/swift5.8-typechecker-regression.
  2. Try to build the package (for an iOS destination) using Xcode 14.3

Expected behavior The project should build in reasonable time.

Using Xcode 14.3 the project does not build. Using Xcode 14.2 it builds without issue. There are further details in the README of the project and source code comments detailing how to demonstrate the exponential increase in compile time for each additional chaining of the .onChange modifier.

Environment

As mentioned above, the issue did not exist in Xcode 14.2 and Swift 5.7 and is new to Swift 5.8.

As mentioned in the demo project: the demo uses the Swift Composable Architecture in order to demonstrated the problem. This is merely as it was how I discovered the problem and therefore was the easiest way for me to provide a repro project. I am pretty sure that the issue is not related to TCA, it just happened to be the easiest way to demonstrate it.

xedin commented 1 year ago

This is an unfortunate downside of AST based transform because result builder bodies are now solved in isolation and single-expression ones aren't so such closures "drive" solving of the result builder ones. We have a few mechanisms to improve performance in cases like that i.e. when result builder produces a concrete or opaque type it's body could be solved early which reduces overall complexity (in fact if I change result type from Reducers to some ReducerProtocol in CombineReducers.init your example type-checks in less than 1 sec.). We might have overlooked something which can help in this case as well.

stephencelis commented 1 year ago

@xedin Do you mean in the library code here? https://github.com/pointfreeco/swift-composable-architecture/blob/04607631de001ada2ed5a49329e12763bbb2edd8/Sources/ComposableArchitecture/Reducer/Reducers/CombineReducers.swift#L28

How is it possible to use some here?

mluisbrown commented 1 year ago

when result builder produces a concrete or opaque type it's body could be solved early which reduces overall complexity

@xedin Yes. I forgot to mention that a workaround I'm using for the above problem is to extract out the CombineReducers into a variable and then chain on that so if I change the example like this, it type checks very quickly:

  var combined = CombineReducers {
    One(effect: { @MainActor in first = true })
    Two(effect: { @MainActor in second = true })
  }

  var body: some ReducerProtocol<State, Action> {
    combined.onChange(of: \.counter) { _, _, _ in
      .send(.increment)
    }.onChange(of: \.counter) { _, _, _ in ... etc etc }
xedin commented 1 year ago

@stephencelis it would have been great if that was possible to use some there but I don't think you can unless Reducers conforms to something that has init() requirement.