Why is this crashing the compiler 5.6 & 5.7?

I'm trying to resolve UI from structured data. There is a "resolver" that maps content into a view, or falls back to the root resolver. However, I can't get around a Segmentation fault: 11 crash.

Does anybody know why would the following code crash the compiler, both Swift 5.6 and 5.7?

protocol Content {
    var id: String { get }
}

struct TestContent: Content {
    var id: String
}

protocol ViewResolver {
    associatedtype ContentView: View
    associatedtype RootResolver: ViewResolver where RootResolver.RootResolver == RootResolver

    @ViewBuilder
    func view(content: Content, rootResolver: RootResolver) -> ContentView
}

struct Resolver<RootResolver: ViewResolver>: ViewResolver where RootResolver.RootResolver == RootResolver {

    func view(content: Content, rootResolver: RootResolver) -> some View {
        switch content {
        case let test as TestContent:
            Text("Test")
        default:
            rootResolver.view(content: content, rootResolver: rootResolver)
        }
    }
}

struct RootResolver: ViewResolver {

    let resolver: Resolver<Self>

    func view(content: Content) -> some View {
        view(content: content, rootResolver: self)
    }

    func view(content: Content, rootResolver: Self) -> some View {
        resolver.view(content: content, rootResolver: self)
    }
}
1 Like
  • This is obviously a compiler bug
  • File a bug!
  • The crash is unrelated to SwiftUI (see the version below)
  • The code is complicated to understand (and not just for the compiler!)
  • So the compiler "fix" could be a "The code is too complicated to parse" error instead of a crash...
  • Try making it simpler, perhaps less "recursive"
  • Maybe the reasonable compromise here would be to use a type erased AnyView
Same issue without SwiftUI dependencies
protocol Proto {}

protocol FooProtocol {
    associatedtype P: Proto
    associatedtype Qux: FooProtocol where Qux.Qux == Qux
    func foo(bar: Qux) -> P
}

struct Baz<T: FooProtocol>: FooProtocol where T.Qux == T {
    func foo(bar: T) -> some Proto {
        bar.foo(bar: bar)
    }
}

struct Bar: FooProtocol {
    let baz: Baz<Self>
    func foo(bar: Self) -> some Proto {
        baz.foo(bar: self)
    }
}
1 Like

Thanks @tera, I have opened a bug report now. The reason I posted here is see if somebody would know a workaround. Type erasure is not an option, since it affects SwiftUI performance.

The code is not trivial, but classifying it as too complex would be an overstatement. There must be a concrete reason the code is crashing the compiler, and it's probably related to its recursive nature as you have implied.

Unfortunately, I could not find a way to make it less recursive, as the problem itself is recursive.

Alright, I was able to workaround the issue by introducing an intermediary concrete View so that the Resolver now returns a concrete type instead of some View, which I guess from the type system point of view is a type erasure, but from the SwiftUI point of view is not inefficient.

struct Resolver<RootResolver: ViewResolver>: ViewResolver where RootResolver.RootResolver == RootResolver {

    struct Resolved: View {
        let content: Content
        let rootResolver: RootResolver
        var body: some View {
            switch content {
            case let test as TestContent:
                Text("Test")
            default:
                rootResolver.view(content: content, rootResolver: rootResolver)
            }
        }
    }

    func view(content: Content, rootResolver: RootResolver) -> Resolved {
        Resolved(content: content, rootResolver: rootResolver)
    }
}