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

I'd love to revisit this proposal now that Swift 3 has come and gone. @Joe_Groff: any interest in sending this through evolution again?

13 Likes

It would be great to implement this eventually. The core team's primary concern moving forward here was type checker performance, since distinguishing type references from collection literals, optional chains, and other common syntax forms would impose an overload resolution problem on all of those forms. If someone wanted to take a stab at implementing this and investigate the real-world compiler performance impact, so that we can assess the impact and problem points in the type checker to improve, that would be a good step toward moving this forward.

15 Likes

I was wondering just the other day why the .self "property" was necessary. Would like to see this requirement removed.

It disambiguates some forms that would otherwise be ambiguous.

Eg,

let m = [Int]
let e = ()
let ee = [()]
  • Is m Array<Int>.self or a single-element array value containing Int.self?
  • Is e ().self or an empty tuple value?
  • Is ee a single-element array of type Array<()> containing an empty tuple, a single-element array of type Array<().Type>, or a metatype value Array<()>.self?

As Joe mentioned there's the performance cost of overload resolution, but if no type is available from context you'll also have more ambiguity than today.

2 Likes

I think these two disambiguation rules would do:

  1. Prefer to interpret an empty literal syntax as an instance, not a type reference.
  2. Prefer to interpret the largest possible subexpression as a type reference.

Rule 1 is because type references are relatively rare compared to empty literals. Rule 2 is because collections of type literals are rarely useful unless there is context to specify a type besides the one that would be inferred—after all, [Int] would be interpreted as an Array<Int.Type>, but there is only one instance that could be added to an Array<Int.Type>, so it's basically just a really wasteful representation of an unsigned integer.

(I'm not necessarily saying that these rules are implementable or would be fast enough, mind you, just that if we can implement them, they will probably be correct.)

4 Likes

Some time ago, someone (I think Ian Partridge) suggested:

\T

instead of

T.self

what do you think about it?

2 Likes

I like it. As long as the documentation contains the word "backslash" in it somewhere, so Google can find it!

I would have to agree that .self just doesn't fit with other parts of the language. Additionally, I can't imagine that this would significantly increase compile time performance (which has become significantly faster since this proposal was initially deferred). I also enjoy the backslash idea.

1 Like

\T is still ambiguous—\X.Y?.Z could then refer to either the nested type Optional<X.Y>.Z or a key path (or eventually, an unapplied member reference) to the property Y optional-chained to the property Z off of the base type X. Furthermore, we have \ at least speculatively carved out for applicative things like key paths and method references, and a metatype doesn't really fit that mold to me.

10 Likes

what if instead of a backslash we prefix with a colon? it would be more consistent with how they appear as declarations, and swift doesn’t use double colon anywhere so this should be fine

foo.withMemoryRebound(to: :Int, capacity: 13, body)

Slava’s examples could be

let m = [:Int]
let e = :()

let ee0 = [()] // Array<Void>
let ee1 = [:()] // Array<Void.Type>
let ee2 = :[()] // Array<Void>.self
let ee2 = :[:()] // Array<Void.Type>.self

I don't know the grammar well enough to say for sure whether this is workable or not but I think I might like it if it is. I wonder if there would be a way to piggyback on something like this to also support disambiguation between a module and a top-level symbol with the same name as a module?

also can i ask why () is typealiased as both Void and an instance of Void?

1 Like

I have to say I fail to see the purpose of simply eliminating one spelling (.self)--and a very readable one at that--only to invent another one to do the exact same thing. The aim of SE-0090 was to remove any need for this altogether; either we still have need of it, in which case there's no reason for syntactic churn, or we don't.

16 Likes

I should clarify: if we are going to change the syntax I think this is a reasonable direction to explore.

I think you make a good point if it's just a change in spelling. But if we could leverage the new syntactic form for other things such as the disambiguation mentioned above it might still be worthwhile.

well, the problem is if “leading colon means type identifier”, then we can use it to disambiguate between a top-level variable and an imported type, but, it would be useless for disambiguating .self since there would be just as much reason for [:Int] to be an array of Ints instead of a 1-element array of Int.Type since the Int in the brackets is still a type identifier, just not a type object reference

also, nobody thinks it’s weird that () is both its own type and an instance of itself?

The convention matches Haskell's (). It'd have been nice to be a nominal type, though, like struct Void {}, so we could extend it.

5 Likes

I'm not sure I follow this. Can you elaborate?

This is such an important special case that I wonder if a proposal to do this might make sense. Something like typealias () = Void could exist as well with a little compiler magic. I think that would let us do it without a breaking change.

We already have plenty of colons in the language that don't mean types: argument labels and dictionary keys. The common thread is that colons can usually be read as "is", but even that falls down a little for full function names.

2 Likes

Yeah, I'm not a fan of this. It seems to be a cause of much confusion, and worse, a bunch of odd edge cases (being the only value that's both a type and an instance)