Am I hitting a compiler bug?

The following code snippet was written in playgrounds and it does compile. The code itself represents the code structure from one of my real code bases which emits a compile time error, which I cannot resolve without a boilerplate workaround.

The idea was to move several generic types into protocol typealiases so that every conforming type would automatically know a shortcut alias for that type (the original type is rather long).

struct Generic<A, B, C> /* constraints */ {}

protocol PathProto {
  associatedtype Container /* constraint */
  typealias Shortcut = Generic
}

protocol Proto {
  associatedtype Path : PathProto
}

struct SomeContainer : Proto {}
extension SomeContainer {
  struct Path : PathProto {
    typealias Container = SomeContainer
  }
}

extension SomeContainer.Path {
  var test: Shortcut<Container, Int, Int> {
    return Shortcut()
  }
}

Now by moving those types into own files will start emitting the error I see in my project

  • File 1: Generic
  • File 2: Proto
  • File 3: PathProto
  • File 4: SomeContainer + extensions

Error message:

public protocol PathProto {
  associatedtype Container /* constraint */
  typealias Shortcut = Generic // error: Reference to generic type 'Generic' requires arguments in <...>
}

It shouldn't be allowed to refer to a generic type without binding its arguments. The playground behavior is a bug. You could write typealias Shortcut<A,B,C> = Generic<A,B,C> to define a generic typealias.

Wait what? I thought that was always a feature similar to how we can sometimes omit the generic parameter list in some contexts like extension Array : Equatable { static func == (lhs: Array, rhs: Array) -> Bool { ... } }:

typealias List = Array // This is a bug, really?
var list = List<Int>()
list.append(1)

Btw. typealias Shortcut = Generic will automatically apply all constraints from the generic parameter list of Generic to Shortcut while typealias Shortcut<A, B, C> = Generic<A, B, C> won't.

The code of the OT copy pasted into a main.swift will compile successfully (I haven't tried with separate files, but I don't see why that should make a difference).

For example the following also compiles and runs:

protocol P {
    typealias List = Array
}

struct S : P {
    var a: List<Int>
    var b: List<Bool>
}

let s = S(a: [1, 2], b: [false, true])
print(s)

But for example:

protocol P {
    associatedtype List = Array // ERROR: Reference to generic type 'Array' requires arguments in <...>
}

As I mentioned above it does compile an run fine in the same file, but if you move the types into separate files the compiler starts complaining and I really don't know why. Joe said the way I'm using it is a bug, but that's kind of odd since it feels more like a feature since it allows coping all generic parameter type constraints to the type alias. So to me it feels like the bug is the other way around, but I would really want some more clarification on that.

1 Like

Sorry to nitpick but you said:

Which I interpreted as "It works in Playgrounds", and as Playgrounds is too buggy to be trusted for almost any kind of testing I thought it was interesting to know that it does compile as a command line app. : )


Did you try with whole module optimization to see if it made any difference?

1 Like

Nah it's totally fine ;) I use playground to quickly reproduce issues or sketch out short code snippets. Anyways the issue remains the same across different project setups. In a single file it works, across multiple files the compiler starts hallucinating.

:thinking: This did the trick, the error message is gone using WMO, but this means I have to use it in debug configuration which as per some WWDC 2018 session will slow down the compilation time.

So is it now a feature or an optimization bug?

I don't know, SE-0092 brought "the typealias keyword back into protocols for type aliasing".

It doesn't mention anything specifically regarding generic type aliases or aliases for generic type names without arguments in <...>.

If I remember correctly the typealias List = Array form existed before SE-0048, which added generic type aliases.

Seems most likely that it should work as you expect and that it's a bug that it doesn't work without whole module optimization.

2 Likes

We have tests that cover this case and it's supposed to work. It's a short-hand for "forwarding" all the arguments verbatim.

I agree it's kind of a funny feature but people do rely on it and we can't take it out now; we need to fix any bugs that it causes instead.

2 Likes

I reproduced the issue and its definitely a bug. The root cause is we have two different code paths for resolving the underlying type of a type alias; one supports this behavior where generic arguments are not bound, the other does not. Also as you can see there's some disagreement on the team about what the correct behavior is, which is the kind of thing that no doubt leads to two different code paths that do the same thing :-) In any case, it should be an easy fix.

5 Likes

So I guess this short-hand form should be documented in this section: https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#ID361

(Not sure if request for doc improvements should be reported like this but, here you go: SR-7931.)

And more generally, a lot of the behavior of type aliases, associated types and extensions is both undocumented and inconsistent/broken.

Properly documenting the intended behavior would certainly help not only us (new and seasoned) users but also the compiler devs.

4 Likes

It's definitely a bug that it behaves inconsistently.

I think it was an accidental feature, and I would love if we can remove it because I find it strange to introduce a new generic name without having the generic parameters explicitly written. However, @Slava_Pestov may very well be right that we're stuck with it.

Doug

1 Like