Revisit SE-0090: Remove .self and freely allow type references in expressions

Hi @Slava_Pestov,

Please forgive me if my coffee hasn't kicked in yet, but how are metatypes of non-nominal types expected to be used again?

In the case of arrays, the problem is arguably is not .self but the syntactic sugar. If people need to write Array<Int> or Array<()> in an expression context instead of [Int].self and [()].self respectively, that seems like a reasonable tradeoff in order to get rid of .self entirely.

2 Likes

The [Int] in decoder.decode([Int], from: data) is completely unambiguous, and I'm not sure if I've ever used SomeType.self for anything else than passing a type to a function that already expects T.Type. Couldn't let m = [Int] just be ambiguous to the compiler? Or is the main issue with this that it will probably impact the type checker's performance too much?

I think for a first implementation the compiler should infer .self ONLY in places where a .Type is expected? That would prevent the ambiguity and compiler complexity mentioned by wholly removing the need for .self everywhere while still giving the benefit that originally sparked this discussion.

Of course, I'm no compiler engineer and I'm not sure how "minimal" this change would really be.

2 Likes

@Joe_Groff do you have a sense of the impact this could have on type-checker robustness with invalid code? I'm not sure how far to read into "favoring the type reference analysis if possible", but it seems like the opposite of what you'd want if you have ambiguity and need to choose a fallback in e.g. code-completion. The more we rely on type-context the harder it becomes for code-completion and other IDE tools to make sense of incomplete and invalid code.

2 Likes

That's a fair concern as well, and another reason it'd be useful to have an implementation to experiment with. My feeling is that by far the most common type references are to named types, where the <> grammatical disambiguation is far more important, and that the cases where you'll be trying to pass a semantically ambiguous type reference form will be relatively rare, but I may be mistaken.

"()" is not really its own "thing". It's just syntax. It's like asking if it's weird that 3 is both an Int and a Float. There isn't really a "3", just representations of different types that happen to have the same spelling.

7 Likes

I guess the ultimate goal here is to avoid ambiguity in different scenarios. Currently, if I have struct A with a static var someVar, I'm just writing A.someVar (instead of A.self.someVar) to access it. In contrast, if I want the type A itself, I have to use A.self instead of just A.

While this can be "solved" by dropping the .self requirement, the actual problem here seems to be with things like Array literals, where the actual type and instances of the type are confused (as others already pointed out). I shouldn't be able to write either [MyStruct]() or [MyStruct(someParam: 2)] and get an Array of MyStruct each time. So the first thing to handle here would to drop this literal syntax and require explicit declarations (Array<MyStruct>()). I don't have a thorough overview where such confusions occur, but before dropping the requirement to write .self, this is something one should first think about in my opinion.

Apart from that, it's still an interesting thing how metaclasses fit into the design of Swift. I don't know how it's handled internally, but I don't see Swift publicly exposing metaclasses like Python does e. g....

Allowing bare type references (i.e. not called or used as the base of a member reference) to be metatype references without .self is a reasonable proposal that we can discuss. I don't think that should be extended to any syntax that would actually be ambiguous, and I think it's much too late to be inventing completely new syntax for metatype references.

8 Likes

Actually, this behavior does make sense to me. Since init is essentially a static func, meaning it can be called on the type like a static variable (ie: String.init()), and adding the () to any Type is really just shorthand for calling .init(). That means that [MyStruct]() expands to [MyStruct].init() so you've got your Array<MyStruct> type. Then [MyStruct(someParam: 2)] means to initialize a MyStruct and store it in an array.

It is strange though that you need .self for the type of A, but not a static variable/function on the type of A.

Oh, it seems like there is some misunderstanding. I didn't mean any confusion with the () init syntax, but rather the fact, that the literal syntax to define an array of a type that then can be initialized is similar to an array instance created via a literal. Basically the array literal syntax allows both for initialising an array and defining the array type that can then be initialized...