Compile-time generic specialization

Hey. Really old topic, but it looks like the best place to turn to.

I just built a hobby framework based on the assumption that specialization can eliminate deeply composed type based if-else trees, but I found out today that this is not the case.

What I tried to do:

public protocol ActionProtocol {}


public protocol ErasedReducer {
    
    associatedtype State
    
    func apply<Action : ActionProtocol>(_ action: Action,
                                        to state: inout State)
    
}

public protocol Reducer : ErasedReducer {
    
    associatedtype Action : ActionProtocol
    func apply(_ action: Action,
               to state: inout State)
    
}

public extension Reducer {
    
    @inlinable
    func apply<Action>(_ action: Action,
                              to state: inout State)
    where Action : ActionProtocol {
        guard let action = action as? Self.Action else {
            return
        }
        apply(action, to: &state)
    }
    
}

public struct ClosureReducer<State, Action : ActionProtocol> : Reducer {
    
    @usableFromInline
    let closure : (Action, inout State) -> Void
    
    public init(_ closure: @escaping (Action, inout State) -> Void) {
        self.closure = closure
    }
    
    @inlinable
    public func apply(_ action: Action, to state: inout State) {
        closure(action, &state)
    }
    
}

public extension ErasedReducer {
    
    func compose<Next : ErasedReducer>(with next: Next) -> ComposedReducer<Self, Next> {
        ComposedReducer(c1: self, c2: next)
    }
    
}

public struct ComposedReducer<C1 : ErasedReducer, C2 : ErasedReducer> : ErasedReducer where C1.State == C2.State {
    
    @usableFromInline
    let c1 : C1
    @usableFromInline
    let c2 : C2
    
    @inlinable
    public func apply<Action>(_ action: Action,
                              to state: inout C1.State) where Action : ActionProtocol {
        c1.apply(action, to: &state)
        c2.apply(action, to: &state)
    }
    
}

This is the core of what I'm doing. I build a large "registry" of handlers for actions that extract their required info using guard let downcasts - the only difference to what has been discussed in the middle of this thread, I compose this registry in a bottom-up way.

The guard statement can fully be resolved ahead of time by specialization. And this actually works for trivial compositions of, e.g., two primitive reducers. However, it does not work for the app reducer in this example project when compiled with optimizations. The disassembly still shows the dynamic casts and the debugger still stops when I set breakpoints at the return statements in the else block of the guards. I even tested this on a local branch in a test suite where I isolated the app reducer from the store.

Is generic specialization somehow sensitive to deep nesting and the full length of the composed functions? It would surprise me quite a bit, as it is often claimed that SwiftUI makes heavy use of this to optimize deeply nested conditional view hierarchies.

Edit: originally, I asked about this here, but there I'm really talking to myself at the moment.