Metatype casting and member access - type ambiguity error

I'm encountering an issue when working with metatype casting, associated types and would appreciate some clarification on how to resolve it.

Here's a simplified version of the code that demonstrates the issue:

protocol Container {
    
}

protocol SpecialisedContainer: Container {
    associatedtype BaseContainer: Container
}

func repro<Root: Container>(root: Root.Type) {
    if let specialised = root as? any SpecialisedContainer.Type {
        let _ = specialised.BaseContainer.self  # Type of expression is ambiguous without a type annotation
        # let _: any Container.Type = specialised.BaseContainer.self # Type of expression is ambiguous without a type annotation
    }
}

The error message Type of expression is ambiguous without a type annotation occurs when attempting to access specialised.BaseContainer.self.

I'm wondering how to properly access BaseContainer metatype in this context. Is there a way to resolve this ambiguity?

The compiler is right because you cannot access a member type as a member of an existential metatype like that, but the diagnostic is not helpful because no type annotation will make this work, as you already observed. Do you mind filing a bug?

What you need to do is open the existential metatype. One way of doing that is to declare a local generic function with a named generic parameter, say T, and then call the function with an existential as an argument. Inside the function, you can then refer to member types of T directly:

protocol Container {
    
}

protocol SpecialisedContainer: Container {
    associatedtype BaseContainer: Container
}

func repro<Root: Container>(root: Root.Type) {
    if let specialised = root as? any SpecialisedContainer.Type {
        func localFunc<T: SpecialisedContainer>(_: T.Type) {
          let _ = T.BaseContainer.self
        }
        localFunc(specialised) // T is bound to the concrete type inside the existential
    }
}

A possibly cleaner approach is to package up whatever it is you're doing with that member type into a protocol extension method; this is almost the same as the above, except here the generic parameter is the Self parameter of the protocol:

extension SpecialisedContainer {
  static func doStuff() {
    print(Self.BaseContainer.self)
  }
}

...
    if let specialised = root as? any SpecialisedContainer.Type {
        specialised.doStuff()
    }

The complete behavior is described in SE-0352.

1 Like

Thank you @Slava_Pestov for looking into it and sharing the workaround :pray:
For sure, created this issue - Enhancing Diagnostics for Accessing Member Types within Existential Metatypes · Issue #73420 · apple/swift · GitHub

1 Like