I think it would be worth mentioning how invertible types are handled, e.g:
func foo(_ x: borrowing some ~Copyable?) {}
Today a bare ~Copyable? is parsed as ~(Copyable?) which is unfortunate, if we change the behavior when prefixed with some/any should we also change the behavior for the bare case?
I've confirmed that f3 is accepted (it fell naturally out of the implementation). I've added tests for that case and updated the pitch write-up to mention that for the constraint to be followed immediately by ? or !, it must be a single "syntactic unit" for which there can be no ambiguity about what the operator is binding to.
I had considered a variation of thisâupdating the parser to "invert" the TypeRepr at the point of creationâbut it violated rule that TypeRepr represents the type as written in source.
Changing the actual precedence between some/any and ?/! in the parser is an interesting thought, but inverting the precedence rules for existing syntax is the sort of change that feels like it could have much wider-spread undesired knock-on effects elsewhere. It's worth investigating though.
some ~Copyable? does not fall out of the implementation I currently have, precisely for the reason you mentioned: it's parsed as Inverted(Optional("Copyable") and so the new some/any logic doesn't see the optional, being nested inside something that it doesn't walk through.
Given that ~P? is currently meaningless, we could hypothetically do something similar and invert it from ~(P?) to (~P)?. However, I again wonder whether it's feasible/wise to do that as a parser change. In Swift, when we consider how expressions are parsed, postfix operators always have precedence over prefix operators: prefix operand postfix is always (prefix (operand postfix)). These types can also appear in expression contexts (let array = [~P?]()) and the type checker converts them to the correct types, so it seems like there would be no escaping having to do some custom handling there anyway.
I did consider pack expansions and started writing something up earlier, but figured I'd wait to see if someone else brought it up
In that case, without parentheses, I think there's still room for ambiguous interpretation.
// Is this an optional pack of Ts or a pack of optional Ts?
func f<each T>(_: repeat each T?) {}
Given that there are two keywords involved in a pack expansion, I think the idea that a postfix ? would subsume the each but not the repeat in this situation feels like a bit of a strange interpretation.
See my reply to @hamishknightabove. I can look at this approach but I was concerned about unintended side effects from changing fundamental precedence rules in the type grammar. I'm happy to have these concerns be unfounded though.
Or you can write Array<some Any>, but that looks silly.
The specific rule here is that the type that follows some (or any) has to be a type that can appear on the right-hand side of a conformance requirement T: P in a where clause.
Thatâs another reason why some P? should parse as (some P)? and not some (P?).
My two cents: the current compiler behaviour should be considered a bug and therefore a fix does not require a Swift Evolution proposal. The meaning of some P? is self-evident.
any and some are not special. They along with each are the current examples, but the same rule should apply to any other future keyword that is similarly tied to the following type-word.
Note though, this idea and this proposal goes against the following:
The rationale for this needs to be part of this discussion, as it's the same thing. I.e. this is not just about ?.
The following compiles:
protocol P { typealias A = Int }
func f(_: (some P)?, _: (any P).A) { }
If the first parameter of this will, then the second should too:
Is it equivalent to Array<any Any> or Array<some Any>?
For me some Array seems ambiguous. If there are people who are interested in introducing some keyword for generic types, it is a separate discussion that is out of current topic.
The reason I brought it up was to explore the possibility of this proposal conflicting with future syntax, namely allowing some to be applied to generic types. I donât feel strongly that we need to allow some Array, and as I mentioned above it seems like such syntax would be compatible with this proposal anyway. But discussing the topic certainly seems in scope to me.
I don't see that hypothetical direction as particularly viable and thus not really a concern, for reasons similar to what @Joe_Groffoutlined here.
It wouldn't be a good idea for some/any X to be made to mean either that X is a constraint (specifically a protocol or protocol composition, [EDIT: or a superclass, thanks Slava!]) or the name of some concrete generic type for which potentially multiple generic parameters are being hoisted into the generic signature without any indication of this in the declaration. That's a case of optimizing for the code author instead of for future understanding of the code. There are better ways that we can simplify the latter case in the future without introducing confusion to every appearance of some/any X in the language that makes people question "is X a protocol or is it a type?".
Indeed, I almost forgot that the type following some can be a class, which declares a type parameter or opaque return type subject to a superclass requirement, eg:
class C {}
func f(_ c: some C) -> some C { return c }
We don't allow any C, because that doesn't give you any expressivity over just a plain old C. But perhaps in an alternate universe we could have modeled inheritance as a special case of existential erasure instead, so that C would be the type of instances that are exactly C, and any C would be an instance of C or some subclass.
Nice, thanks for a good regression test to add to my suite.
Examples like this make me a little concerned about doing this entirely in the parser, at least in terms of inverting the precedence between some and ? (and & comes into play here as well). I've been thinking about it and I'm imagining the following cases:
some P? should parse like (some P)?, so Prec(some) > Prec(?)
some P & Q should still parse like some (P & Q), so Prec(&) > Prec(some)
some P? & Q, for the sake of error recovery, could parse a couple ways and be rejected later by Sema:
...as some ((P?) & Q)
...or as (some P)? & Q
...meaning that Prec(?) > Prec(&)
For symmetry with the previous one, some P & Q? should probably parse as some P & (Q?) and let Sema reject it later, but we could also just diagnose it outright and say "you should add parentheses"
But this leaves us with the contradiction Prec(some) > Prec(?) > Prec(&) > Prec(some). Maybe we could incorporate more lookahead (e.g., if what follows ? is a &, treat what we have so far as a single component, but if it's another type continuation like ., keep going in the current type context), but that seems fragile/risky.
Another approach to keep it in the parser would be to keep the precedence as it is today but rewrite the type nodes on the way back up the tree; basically just doing the same thing as my current implementation does, but on the syntax nodes instead of the TypeReprs. I don't know if there's precedent (pun intended?) for that kind of thing in the parser though, outside of operator folding.
And just for fun, not really relevant to this thread, but the following does not work, and gives a delightfully misleading error:
typealias A = Optional<Any>
extension Optional {
typealias W = Wrapped
}
func f(_: some A.W) {}
// error: type 'some A.W' constrained to non-protocol, non-class type 'A.W' (aka 'Any')