Getting rid of this “Typealias overriding associated type” warning?

This question is related to this and this thread.

This works but gives a Fix it-warning:

protocol IntegerStatusCode : RawRepresentable, ExpressibleByIntegerLiteral {
    // Typealias overriding associated type 'IntegerLiteralType' from protocol 'ExpressibleByIntegerLiteral' is better expressed as same-type constraint on the protocol
    typealias IntegerLiteralType = RawValue
    init(rawValue: RawValue)
}
extension IntegerStatusCode {
    init(integerLiteral value: IntegerLiteralType) { self.init(rawValue: value) }
}
    
struct StatusCode : IntegerStatusCode {
    let rawValue: Int
}

The goal is to not have to typealias in the conformer.
If I apply the fixit, the code looks better but does not compile:

protocol IntegerStatusCode : RawRepresentable, ExpressibleByIntegerLiteral where IntegerLiteralType == RawValue {
    init(rawValue: RawValue)
}
// Type 'StatusCode' does not conform to protocol 'ExpressibleByIntegerLiteral'

First of all, if I'm not mistaken these are not semantically identical. The fixit code ensures that the two associated types are always identical, while the original code does not.

The threads I linked to have me believe that typealias IntegerLiteralType = RawValue defines a default for the associated type, but does not impose any constraints upon the two associated types if the client wishes something else.

I also understood that the fixit doesn't compile because of inference limitations, and nothing else.

Since this code actually requires that the two associated types be identical, the right way to do it (that compiles) would be:

protocol IntegerStatusCode : RawRepresentable, ExpressibleByIntegerLiteral where Self.IntegerLiteralType == Self.RawValue {
    typealias IntegerLiteralType = RawValue
    init(rawValue: RawValue)
}
extension IntegerStatusCode {
    init(integerLiteral value: IntegerLiteralType) { self.init(rawValue: value) }
}

...where typealias IntegerLiteralType = RawValue is only necessary in order to assist inference. Is that right?

I'm wondering:

  • How should it be written to avoid the warning?
  • In the other threads I got the impression that using typealias in a protocol to provide default associated types was undesirable (it wasn't clear to me why), and that default values for associated types could be specified with a redeclaration using associatedtype in the sub-protocol to aid inference. When I do this in my example, it doesn't compile anymore (StatusCode does not conform to ExpressibleBy...). What did I misunderstand about that recommendation?

I'm using Xcode 11.3.1

Spot on, this is an associated type inference limitation. I'm afraid the best you can do for now to simulate a same-type constraint is to continue using the typealias and ignore the warning. I'll see if I can land a fix in the next couple weeks.

Thanks! As long as this is the recommended workaround and that I didn't misunderstand anything important the warning is not a problem.

It'd be nice if there were some way to tell when and where the inference engine has hit an artificial boundary. That way I wouldn't have to question my understanding of what I wrote, and could focus on helping it. Maybe that's not realistic though.

This particular one is just a bug, but we do have intentional limitations the compiler could indeed do a lot better in diagnosing (for example, the limitation of the scope of associated type inference to just the members of the protocol that introduces or restates the given associated type manifests itself as a plain conformance error).

Hope it isn't too late to bump this (I'm sorry if it is). Is there any advancement on fixing this bug? It seems that Xcode 12.5 has the same problem:

protocol Foo {
    associatedtype Fooing
}

protocol Bar {
    associatedtype Baring
}

protocol Baz: Foo where Fooing == Bazing.Baring {
    associatedtype Bazing: Bar
}

extension String: Bar {
    typealias Baring = Int
}

struct Something: Baz { // Error: Type 'Something' does not conform to protocol 'Baz'   
    typealias Bazing = String
}

This turned out to be a multi-faceted issue. I fixed one side, where a type parameter was mistakenly never considered to be a fixed (predefined) type witness,

protocol Foo {
  associatedtype Fooing
}
protocol Bar {
  associatedtype Baring
}

protocol Baz: Foo, Bar where Fooing == Baring {}

struct Something: Baz { // OK
  typealias Baring = Int 
}

and then discovered that swapping typealias Baring for typealias Fooing brings back the issue:

struct Something: Baz { // type 'Something' does not conform to protocol 'Baz'
  typealias /*Baring*/Fooing = Int
}

This time the culprit is a method called getCanonicalTypeInContext that we use to find these fixed type witnesses. In the first case, we attempt to infer an implementation for Fooing in the context of Baz, and everything works out because Self.Baring, which resides in the same equivalence class as Self.Fooing, is lexicographically the more canonical of the two. This implies that the opposite — attempting to infer an implementation for Baring (as in the second case) — will always fail, because getCanonicalTypeInContext always returns the most canonical result. The same happens to apply to your example (Self.Fooing is more canonical than Self.Bazing.Baring by depth) and the original example (IntegerLiteralType is more canonical than RawValue).

So far I only got to chase down the source of the problem, but still keen to give it a go once we are done with SE-309.

Building on you example, I'm trying different combinations of inheritance order and equivalence constraint, but I always get the same result:

protocol Foo {
  associatedtype Fooing
}

protocol Bar {
  associatedtype Baring
}

protocol Baz1: Foo, Bar where Fooing == Baring {}
protocol Baz2: Foo, Bar where Baring == Fooing {}
protocol Baz3: Bar, Foo where Fooing == Baring {}
protocol Baz4: Bar, Foo where Baring == Fooing {}

struct A: Baz1 { // OK
  typealias Baring = Int
}

struct B: Baz1 { // Type 'B' does not conform to protocol 'Bar'
  typealias Fooing = Int
}

struct C: Baz2 { // OK
  typealias Baring = Int
}

struct D: Baz2 { // Type 'D' does not conform to protocol 'Bar'
  typealias Fooing = Int
}

struct E: Baz3 { // OK
  typealias Baring = Int
}

struct F: Baz3 { // Type 'F' does not conform to protocol 'Bar'
  typealias Fooing = Int
}

struct G: Baz3 { // OK
  typealias Baring = Int
}

struct H: Baz3 { // Type 'H' does not conform to protocol 'Bar'
  typealias Fooing = Int
}

But if I rename Baring to ZBaring, the errors become inverted! It seems that Baring is considered the more canonical because it comes first in alphabetical order. I never though this could be relevant to the Swift compiler!

1 Like
Terms of Service

Privacy Policy

Cookie Policy