Partial type annotations

@Lantua's post on the recent property wrappers enhancement thread triggered the following idea for me: would it be desirable to allow users to specify partial type signatures, allowing some portions to be filled in by type inference?

We already sort of have this with generic parameter inference—this would basically be a more configurable version of the same idea.

Possible scenarios where this might be useful:

Specifying an attribute without specifying the entire type (as in the linked post).

ETA: I suppose that the example from the post would actually be spelled as:

@declAttribute var foo: @typeAttribute _ = { ... }

for consistency with the below examples...

Specifying a single generic argument without writing out the entire generic signature:

struct S<T, U> {
    init(u: U) {}
}

let s = S<Int, _>(u: someVariableWithComplexType)

Specifying a function argument type which couldn't be inferred:

func foo<T>(_ closure: (T, Int) -> ComplexTypeThatIDontWantToRepeat<Int>) { ... }
func bar(_: Int, _: Int) ->  ComplexTypeThatIDontWantToRepeat<Int> { ... }
func bar(_: String, _: Int) ->  ComplexTypeThatIDontWantToRepeat<Int> { ... }

foo(bar as (String, _) -> _)

I haven't thought too hard about concrete use cases (or whether the _ spelling would cause issues, but it seems at least plausibly useful and I wanted to hear if anyone had actually ever desired a feature like this!

15 Likes

IIRC, Rust has this feature, also using _ for the syntax.

4 Likes

This would be great. It's always seemed weird to me that we let you write let x: Generic = foo() to infer the generic arguments, but not let you be more selective about where you need inference.

8 Likes

My only reservation is if we’d want to use the syntax for anything by else. Otherwise, it’s a good idea and I’d love to have it.

I would kind of like this syntax for existential constraints (i.e. Array<_> would be an Array of unknown element type). I’d also be happy with Array<?> for that, though.

3 Likes

Yeah I'm definitely not married to _, it just seemed like the best placeholder syntax to illustrate the idea. Open to other suggestions as well! IIRC we already sometimes use _ in the same position in diagnostics to indicate type variables which couldn't be inferred, but I'm not sure if that's an argument for or against the usage of _ in this proposal. :sweat_smile:

I agree with Karl. I like this functionality, but would like to see _ used for existential constraints or type erasure.

How about using ? for this?

let s = S<Int,?>(u: someVariableWithComplexType)
1 Like

? makes sense to me as well, and I could also see an argument for *. Will probably try to bang out a prototype for this soon!

1 Like

Not sure how I feel about ? given that it already has optional meaning in Swift, and are allowed (with type) in that position:

Array<Int?> // ok
Array<?> // Is this Array<Optional<XXX>> ?
13 Likes

_ is Swift's wildcard pattern, so it's the most appropriate one in this regard:

If existentials types will require the any keyword as described in Improving the UI of generics (although it refers only to protocol existential types and not generic existential types), there won't be ambiguities for generic existentials, right?

let d: [_: Int] = ["one": 1, "two": 2]
// inferred type Dictionary<String, Int>

var e: any [_: Int] = ["one": 1, "two": 2]
// inferred type existential type of all dictionaries
// having Int values
e = [3: 3, 4: 4]

// opaque types too?
var o: some [_: Int] = ["one": 1, "two": 2]
// hidden type, you cannot interact with 'o' unless casted
o = ["three": 3, "four": 4]  // {error}:
// Cannot assign value of type 'Dictionary<String, Int>'
// to type 'some Dictionary<_, Int>'
3 Likes

The lone ? could also be taken as "non-nil value present", as in switch statements, which might be even more confusing in this context.

2 Likes

Another mark against ? as the "placeholder" punctuation is that it makes pretty strange to use a placeholder as the generic argument to a sugared optional, e.g.,

let optInt: _? = 0

vs.

let optInt: ?? = 0
1 Like

An any modifier for existentials might allow for any Array<_> to work as an existential type too.

7 Likes

Obviously the precise answer will vary by code base, but as a rough guess, do you think code with _ sprinkled throughout (like let foo: Array<_> = someFunc()) would compile faster? Seems like telling the compiler that you’re expecting something to be an array would cut down on its search space, but I don’t know how much of a factor that is.

Knowing that something is Array should be enough to imply that it has one type argument, so I wouldn't expect writing Array<_> to significantly reduce the type checking space.

1 Like