What's the intended behavior here (associated type with default type, conditional conformances)?

This program compiles successfully with Xcode 9.3 default toolchain and recent development snapshots.

There are clearly some compiler bug(s) at work here, but I would like to know what the intended behavior is.

protocol P {
    associatedtype A = Int
}
protocol Q {
    associatedtype A = Bool
}

struct S1: P {
    var v: A // A is Int, as expected.
}

struct S2: P, Q {
    var v: A // A is Bool, but should be ambiguous?
}

struct S3: Q, P {
    var v: A // A is Int, but should be ambiguous?
}

struct S4<T> {
    var v: A // A is Int (Bool with recent snapshots), but should be undeclared?
}
extension S4: P where T == Double {}
extension S4: Q where T == Double {}

struct S5<T> {
    var v: A // A is Bool (Int with recent snapshots), but should be undeclared?
}
extension S5: Q where T == Double {}
extension S5: P where T == Double {}

struct S6<T>: P {
    var v: A // A is Bool, but should be ambiguos or: Bool only if T == Float, otherwise Int?
}
extension S6: Q where T == Float {}
1 Like

Oof, that is some weird stuff. I'm guessing from the displayed behavior, when you explicitly declare the constraint in the struct definition, it takes the last protocol?

Ahhh..conditional conformance bugs. But the bug itself is much older. The intended behavior is to raise an error of course. Here's how to resolve them.

struct S2: P, Q {
    // A is ambiguous, require to disambiguate by explicitly declaring A
}

struct S4<T> {
    // A is ambiguous, require to disambiguate by explicitly declaring A
    // in either of the extensions or in the main declaration depending on 
    // where occurrences of A exist.  
}
extension S4: P where T == Double {}
extension S4: Q where T == Double {}

struct S6<T>: P {
    // A is ambiguous when T == Float, require to disambiguate by explicitly declaring
    // A in the extension or in the main declaration.
}
extension S6: Q where T == Float {}

// forAll: var v: A // A is ambiguous for type lookup in this context

Will you file it?

Filed SR-7516.


Are you sure that the last example

struct S6<T>: P {
    var v: A // A is Bool, but should be ambiguos or: Bool only if T == Float, otherwise Int?
}
extension S6: Q where T == Float {}

shouldn't be allowed, even though the type for A can't be known in the context of the struct (similar to how T can't be known in that context)?

cc @huon @Douglas_Gregor

1 Like

The problem is A will be ambiguous if T == Float. That last example is unambiguous as long as A is declared explicitly somewhere. T can't be known in the context because S6 is universally quantified over it (the consumer gets to choose T, the implementer doesn't know the value of T).
A is a bit different. Since it is an associated type witness, it has to be either known at compile time or bound to another type in a generic manner, since it is the implementer who gets to choose it.

1 Like

I suspect that these cases are also in part related to SR-7217, which shows similar behavior but with protocol composition. (I related SR-7516 and SR-7217 in JIRA)

1 Like

Thanks, that made me understand why S6 is intended to be ambiguous. I'm still wondering about the following though.

Let's look at this example program, demonstrating a simplified version of S4 and S5 in the original program:

protocol P {
    associatedtype A = Int
}
struct S<T> {
    var v: A // Compiler says A is String here, but shouldn't the error be
    // "A is undeclared" rather than "A is ambiguous" for this particular
    // example?
    // Note that S is not to conforming to any protocol at all, unless
    // T == Double (see extension below), and thus A should only be declared
    // in contexts like the extension.
}
extension S: P where T == Double {
    typealias A = String
}

I just realized that a similar issue (maybe the same?) can be demonstrated without any protocols using this program:

struct S<T> {
    var v: A // Compiler says A is String here too
}
extension S where T == Double {
    typealias A = String
}

That's right. A defect that was most likely introduced together with conditional conformances. This is simply use of undeclared type A where v is.

Turns out I'd already reported that particular issue 10 months ago: SR-5440

Shouldn't a (value) type's binary layout be solely determined by the primary definition, including any protocols declared directly in that primary definition? Shouldn't an error occurr if an extension is needed to complete the binary layout of a type, or would change the layout?

I'd like to know the answer to that too.

It might be the intended behavior, but as shown by my above example and the following one, it is currently not the case:

struct S {
    let v: A
}
extension S {
    typealias A = Int
}

This program will compile with Xcode 9.3 default toolchain and recent dev snapshots.
The extension is needed to complete the binary layout of S, but I'm not sure whether it is meant to be invalid.

But it's clear that my previous example program should be invalid (even though the current compiler accepts it), I'm repeating it here for completeness:

struct S<T> {
    let v: A // Compiler happily concludes that A is String
}
extension S where T == Double {
    typealias A = String
}

The answer to this came up in another thread, so I'm quoting it here: