Subtyping in associated types

I'm trying to use a protocol's associated types to constrain what type of values can be passed to a function. Consider the following type:

struct Value<T> {}

I then have a number of functions that are constrained:

protocol A {}
protocol B {}

func useA(_ input: Value<A>) {}
func useB(_ input: Value<B>) {}

This mostly works as expected:

useA(Value<A>()) // Works ✅
useB(Value<B>()) // Works ✅

useB(Value<A>()) // Doesn't compile (expected) ✅

But is there a reason I can't do this?

useA(Value<A & B>()) // ❌ Cannot convert value of type 'Value<any A & B>' to expected argument type 'Value<any A>'

As far as I can tell, A & B is a subtype of A, and should therefore be usable in all places where A is required. Is there something missing?

You cannot imply covariance for generic parameters by default:

1 Like

Looks like the good old any vs some topic:

func useA(_ input: Value<A>) {} is actually not generic over the protocol, but expect the specific type Value<any A>, where any A is the box that contains something that implements A.

The solution is generally to go all-in on generics, and often using some is enough.

I am not entirely sure what you are after, but something like this compiles just fine:

// functions with generic arguments
func useA(_ input: Value<some A>) {}
func useB(_ input: Value<some B>) {}
func useAB(_ input: Value<some A & B>) {}

struct AB: A, B {}

func doSomething() {
    // AB is a concrete type, not an existential
    let value = Value<AB>() 
    useA(value)
    useB(value)
    useAB(value)
}
2 Likes

I'm trying to model GraphQL in a Swift result builder. Consider the following query:

query {
  repository(owner: "swiftlang", name: "swift") {
    id
    name
  }
  viewer {
    id
  }
}

To make this work in Swift, I was going to have a type that's constrained to its parent:

struct Field<Parent> { /.../ }

Then create values that are constrained, so only valid queries can be constructed:

// Constraint for all fields of "User" query
protocol User {}

// Constraint for all fields of "Repository" query
protocol Repository {}

// Can be used in both Repository and User builders
let id: Field<Repository & User>

// Can be only be used in the Repository builder
let name: Field<Repository>

But since the generic can't be covariant, this might be the wrong abstraction. The some A & B looks intriguing, though.