I don't know why would you want that, but with generalized existentials this and many other exotics things would be possible:
protocol Object {
associatedtype Id
var id: Id { get }
}
protocol Vehicle: Object {
}
struct Car: Vehicle {
struct Id { ... }
let id: Id
}
func registerVehicleId(_ id: any<T> T.Id where T: Vehicle) {
let <V> openedID: V.Id = id
print(V.self) // prints "Car"
print(type(of: id)) // prints "Car.Id"
}
let car: Car = ...
registerVehicleId(car.id)
Note that such type would have a layout that stores value of identifier in the value buffer, but does store its type directly. Instead it stores types that existential type abstracts over - type T and witness table of T: Vehicle. When opening such existential you would get the full type T.
And when passing id to type(of:) it is casted to Any which has a different layout - value buffer and metadata for the actual type of the id. So type(of:) works as usual, but instead at the call site of type(of:) compiler would emit code that takes type metadata for T from existential, uses it to lookup metadata for the associated type, and then constructs Any with that metadata.