Associated types in protocol

I think that protocols with associated types (PAT) should be allowed to be declared as ordinary types in the upcoming swift language version.

So, you would have the compiler generate a version of each function for each set of associated types? In other words:

myFunc(x: SomeProtocol) -> SomeReturn

would translate to:

myFunc<ConformingType, AssociatedType1, AssociatedType2, etc.>(x: ConformingType) -> SomeReturn

and be broken down with Swift’s generic-function parser/AST/SIL system further.

But what happens with:

myFunc2(x: SomeProtocol, y: SomeProtocol) -> SomeReturn

Remember that the actual conforming types don’t have to be the same!

myFunc2<ConformingType1, ConformingType2, AssociatedType1-1, AssociatedType1-2, etc. AssociatedType2-1, AssociatedType2-2, etc.>(x: ConformingType1, y: ConformingType2) -> SomeReturn

Yikes.

(And imagine more parameters. And/or the return type has parts dependent on SomeProtocol.)

And the way we currently have it, where all the parameters have to have the same conforming type, that means we know that all the corresponding associated types are the same, and we can do various combinations with them. How’s that supposed to work when corresponding associated types could differ? Hope they don’t? Use Any? Just crash at run-time?

(Oh yeah, if the return type does depend on SomeProtocol, how do we determine which version to use? And how to write that out in code?)

Or do we just crash at runtime whenever all mentions of SomeProtocol don’t resolve to the same conforming type?

@Nicole_Jacque This should be in pitches

Agreed, moving to Pitches.

A very simple example why this is senseless:

protocol P {
    associatedtype A
    
    var value: Int { get }

    func foo(_ arg: A)
}

class Foo: P {
    typealias A = Int
    
    func foo(_ arg: Int) {}
}

let p: P = Foo()

print(p.value) // OK

p.foo(???)

Functions will have similar problems. If the return type depends on an associated type, you will have to parametrize the function. Suppose it is not dependent. If the associated types don’t have constraints, you will encounter type uncertainty and will have to parametrize to resolve it. If they do have constraints, the compiler still won’t let you use any instances of satisfying types for an obvious reason (again, you will have to parametrize):

protocol P1 {}

class Foo1: P1 {}
class Foo2: P1 {}

protocol P {
    associatedtype A: P1
}

class Foo: P {
    typealias A = Foo2
}

func foo(_ arg: P) {
    
    arg.foo(Foo1()) // What is A is not Foo1?
}

As you see, properties in this case are senseless.
Functions, constructs (enum, class, struct), protocols, however, can be parametrized or constrained to flexibly use protocols with associated types, and the way they are used is for the consumer to express, not the compiler:

protocol Protocol {
    associatedtype Associatedtype
}

func foo<T: Protocol, U>(_ arg: T) -> ... where T.Associatedtype == U { ... }

struct Foo<T>: Protocol { typealias Associatedtype = T }

struct Foo1<T: Protocol, U> where T.Associatedtype == U { ... }

protocol Protocol1 {
    associatedtype B: Protocol where B.A: StringProtocol
}