-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Initial Semantics for Variadic Generics #40587
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
A pack type looks a lot like a tuple in the surface language, except there is no way for the user to spell a pack. Pack types are created by the solver when it encounters an apply of a variadic generic function, as in ``` func print<T...>(_ xs: T...) {} // Creates a pack type <String, Int, String> print("Macs say Hello in", 42, " different languages") ``` Pack types substituted into the variadic generic arguments of a PackExpansionType "trip" the pack expansion and cause it to produce a new pack type with the pack expansion pattern applied. ``` typealias Foo<T...> = (T?...) Foo<Int, String, Int> // Forces expansion to (Int?, String?, Int?) ```
A PackExpansionType is the interface type of the explicit expansion of a corresponding set of variadic generic parameters. Pack expansions are spelled as single-element tuples with a single variadic component in most contexts except functions where they are allowed to appear without parentheses to match normal variadic declaration syntax. ``` func expand<T...>(_ xs: T...) -> (T...) ~~~~ ~~~~~~ ``` A pack expansion type comes equipped with a pattern type spelled before the ellipses - `T` in the examples above. This pattern type is the subject of the expansion of the pack that is tripped when its variadic generic parameter is substituted for a `PackType`.
Pack expressions take a series of argument values and bundle them together as a pack - much like how a tuple expression bundles argument expressions into a tuple. Pack reification represents the operation that converts packs to tuples/scalar types in the AST. This is important since we want pack types in return positions to resolve to tuples contextually.
Behaves much like isTypeParameter but specifically checks for the type sequence bits. Also, add the type sequence bits as a recursive type property.
Also begin resolving (T...) as a pack expansion.
The algorithm is detailed below: Suppose we encounter a type T<..., U, V..., W, ...> for V... the _sole_ variadic generic parameter. When we encounter an argument list <A, B, C, D, E, F, ...> to T, we must work in three steps - Bind all generic parameters up to U in parallel with the argument list until we encounter V... - Measure the tail of parameters after V... and call it `t`. Assuming `m` type variables hav been bound already, we must bind `n - t - m` type arguments to V... - Finally, bind the remaining `t` arguments. This procedure is often called "Saturation" in the literature. Variadic generics have a special saturation property where unlike normal generic parameters it is possible to bind zero arguments to them. In such a case, the result is an empty pack type, which is semantically well-formed.
The heart of this patchset: An inference scheme for variadic generic functions and associated pack expansions. A traditional variadic function looks like func foo<T>(_ xs: T...) {} Along with the corresponding function type <T>(T [variadic]) -> Void which the constraint system only has to resolve one two for. Hence it opens <T> as a type variable and uses each argument to the function to try to solve for <T>. This approach cannot work for variadic generics as each argument would try to bind to the same <T> and the constraint system would lose coherency in the one case we need it: when the arguments all have different types. Instead, notice when we encounter expansion types: func print<T...>(_ xs: T...) print("Macs say Hello in", 42, "different languages") We open this type as print : ($t0...) -> ($t0...) Now for the brand new stuff: We need to create and bind a pack to $t0, which will trigger the expansion we need for CSApply to see a coherent view of the world. This means we need to examine the argument list and construct the pack type <$t1, $t2, $t3, ...> - one type variable per argument - and bind it to $t0. There's also the catch that each argument that references the opened type $t0 needs to have the same parallel structure, including its arity. The algorithm is thus: For input type F<... ($t0...), ..., ($tn..) ...> and an apply site F(a0, ..., an) we walk the type `F` and record an entry in a mapping for each opened variadic generic parameter. Now, for each argument ai in (a0, ..., an), we create a fresh type variable corresponding to the argument ai, and record an additional entry in the parameter pack type elements corresponding to that argument. Concretely, suppose we have func print2<T..., U...>(first: T..., second: U...) {} print2(first: "", 42, (), second: [42]) We open print2 as print2 : ($t0..., $t1...) -> Void And construct a mapping $t0 => <$t2, $t3, $t4> // eventually <String, Int, Void> $t1 => <$t5> // eventually [Int] We then yield the entries of this map back to the solver, which constructs bind constraints where each => exists in the diagram above. The pack type thus is immediately substituted into the corresponding pack expansion type which produces a fully-expanded pack type of the correct arity that the solver can actually use to make forward progress. Applying the solution is as simple as pulling out the pack type and coercing arguments element-wise into a pack expression.
@swift-ci test |
Build failed |
Build failed |
Insert an implicit conversion from pack types to tuples with equivalent parallel structure. That means 1) The tuple must have the same arity 2) The tuple may not have any argument labels 3) The tuple may not have any variadic or inout components 4) The tuple must have the same element types as the pack
72d5678
to
d44d8ec
Compare
@swift-ci test |
Build failed |
@swift-ci test macOS platform |
Build failed |
@swift-ci smoke test macOS |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Implements a variation of the semantics described in the initial pitch.
This patchset implements
Notably missing:
Foo<>
forFoo<T...>
func foo<T...>(_ xs: T...); foo()
The heart of this patchset is an inference scheme for variadic generic functions and associated pack expansions.
A traditional variadic function looks like
Along with the corresponding function type
which the constraint system only has to resolve one type for. Hence it opens
<T>
as a type variable and uses each argument to the function to try to solve for<T>
. This approach cannot work for variadic generics as each argument would try to bind to the same<T>
and the constraint system would lose coherency in the one case we need it: when the arguments all have different types.Instead, notice when we encounter expansion types:
We open this type as
Now for the brand new stuff: We need to create and bind a pack to
$t0
, which will trigger the expansion we need for CSApply to see a coherent view of the world. This means we need to examine the argument list and construct the pack type<$t1, $t2, $t3, ...>
- one type variable per argument - and bind it to$t0
. There's also the catch that each argument that references the opened type$t0
needs to have the same parallel structure, including its arity. The algorithm is thus:For input type
F : (... ($t0...), ..., ($tn..) ...) -> R
and an apply siteF(a0, ..., an)
we walk the parameters ofF
and record an entry in a mapping for each opened variadic generic parameter. Now, for each argument ai in(a0, ..., an)
, we create a fresh type variable corresponding to the argumentai
, and record an additional entry in the parameter pack type elements corresponding to that argument.Concretely, suppose we have
We open print2 as
And construct a mapping
We then yield the entries of this map back to the solver, which constructs bind constraints where each
=>
occurs in the diagram above. The pack type thus is immediately substituted into the corresponding pack expansion type which produces a fully-expanded pack type of the correct arity that the solver can actually use to make forward progress.Applying the solution is as simple as pulling out the pack type and coercing arguments element-wise into a pack expression.
You would think this would appear in the literature somewhere, but lo and behold I'm pretty sure nobody's thought to do this yet.