Generics: impl forall (#1327)

Implement the decision in #1192 to use this syntax for parameterized impls:

> `impl forall [`_generic parameters_`]` _type_ `as` _constraint_ ...
This commit is contained in:
josh11b
2022-06-14 19:35:53 -07:00
committed by GitHub
parent d37dfd2fb8
commit b83f5c3fc7
2 changed files with 181 additions and 69 deletions
+54 -69
View File
@@ -3606,15 +3606,11 @@ class Vector(T:! Type) {
```
An impl may be declared [external](#external-impl) by adding an `external`
keyword before `impl`. External impls may also be declared out-of-line:
keyword before `impl`. External impls may also be declared out-of-line, but all
parameters must be declared in a `forall` clause:
```
external impl [T:! Type] Vector(T) as Iterable
where .ElementType = T {
...
}
// This syntax is also allowed:
external impl Vector(T:! Type) as Iterable
external impl forall [T:! Type] Vector(T) as Iterable
where .ElementType = T {
...
}
@@ -3634,16 +3630,10 @@ or externally out-of-line:
```
class HashMap(Key:! Hashable, Value:! Type) { ... }
external impl [Key:! Hashable, Value:! Type]
external impl forall [Key:! Hashable, Value:! Type]
HashMap(Key, Value) as Has(Key) { ... }
external impl [Key:! Hashable, Value:! Type]
external impl forall [Key:! Hashable, Value:! Type]
HashMap(Key, Value) as Contains(HashSet(Key)) { ... }
// This syntax is also allowed:
external impl HashMap(Key:! Hashable, Value:! Type)
as Has(Key) { ... }
external impl HashMap(Key:! Hashable, Value:! Type)
as Contains(HashSet(Key)) { ... }
```
### Conditional conformance
@@ -3672,7 +3662,7 @@ class Vector(T:! Type) { ... }
// By saying "T:! Printable" instead of "T:! Type" here,
// we constrain T to be Printable for this impl.
external impl [T:! Printable] Vector(T) as Printable {
external impl forall [T:! Printable] Vector(T) as Printable {
fn Print[me: Self]() {
for (let a: T in me) {
// Can call `Print` on `a` since the constraint
@@ -3681,18 +3671,14 @@ external impl [T:! Printable] Vector(T) as Printable {
}
}
}
// This syntax is also allowed:
external impl Vector(T:! Printable) as Printable { ... }
```
To define these `impl`s inline in a `class` definition, include a more-specific
type between the `impl` and `as` keywords.
To define these `impl`s inline in a `class` definition, include a `forall`
clause with a more-specific type between the `impl` and `as` keywords.
```
class Array(T:! Type, template N:! Int) {
// These are both allowed:
impl [P:! Printable] Array(P, N) as Printable { ... }
impl Array(P:! Printable, N) as Printable { ... }
impl forall [P:! Printable] Array(P, N) as Printable { ... }
}
```
@@ -3712,7 +3698,7 @@ equal.
```
interface Foo(T:! Type) { ... }
class Pair(T:! Type, U:! Type) { ... }
external impl [T:! Type] Pair(T, T) as Foo(T) { ... }
external impl forall [T:! Type] Pair(T, T) as Foo(T) { ... }
```
You may also define the `impl` inline, in which case it can be internal:
@@ -3799,13 +3785,13 @@ where blanket impls arise:
`PartiallyOrdered`.
```
external impl [T:! Ordered] T as PartiallyOrdered { ... }
external impl forall [T:! Ordered] T as PartiallyOrdered { ... }
```
- `T` implements `CommonType(T)` for all `T`
```
external impl [T:! Type] T as CommonType(T)
external impl forall [T:! Type] T as CommonType(T)
where .Result = T { }
```
@@ -3836,14 +3822,10 @@ to `i32` and then add the `i32` to the `BigInt` value.
```
class BigInt {
extern impl [T:! ImplicitAs(i32)] as AddTo(T) { ... }
// Or:
extern impl as AddTo(T:! ImplicitAs(i32)) { ... }
external impl forall [T:! ImplicitAs(i32)] as AddTo(T) { ... }
}
// Or out-of-line:
extern impl [T:! ImplicitAs(i32)] BigInt as AddTo(T) { ... }
// Or:
extern impl BigInt as AddTo(T:! ImplicitAs(i32)) { ... }
external impl forall [T:! ImplicitAs(i32)] BigInt as AddTo(T) { ... }
```
Wildcard impls must always be [external](#external-impl), to avoid having the
@@ -3855,7 +3837,7 @@ The different kinds of parameters to impls may be combined. For example, if `T`
implements `As(U)`, then this implements `As(Optional(U))` for `Optional(T)`:
```
external impl [U:! Type, T:! As(U)]
external impl forall [U:! Type, T:! As(U)]
Optional(T) as As(Optional(U)) { ... }
```
@@ -3887,7 +3869,7 @@ parameters and replacing type parameters by a `?`. The type structure of this
declaration:
```
impl [T:! ..., U:! ...] Foo(T, i32) as Bar(String, U) { ... }
impl forall [T:! ..., U:! ...] Foo(T, i32) as Bar(String, U) { ... }
```
is:
@@ -3903,6 +3885,7 @@ parameters are replaced the declarations are normalized as follows:
between the `impl` and `as` keywords if the type is left out.
- Pointer types `T*` are replaced with `Ptr(T)`.
- The `external` keyword is removed, if present.
- The `forall` clause introducing type parameters is removed, if present.
- Any `where` clauses that are setting associated constants or types are
removed.
@@ -3974,8 +3957,8 @@ declarations inside matching curly braces `{` ... `}`.
```
match_first {
// If T is Foo prioritized ahead of T is Bar
impl [T:! Foo] T as Bar { ... }
impl [T:! Baz] T as Bar { ... }
impl forall [T:! Foo] T as Bar { ... }
impl forall [T:! Baz] T as Bar { ... }
}
```
@@ -4018,15 +4001,15 @@ erase too much information when considering this graph, that these impls are not
considered to form cycles with themselves:
```
impl [T:! Printable] Optional(T) as Printable;
impl [T:! Type, U:! ComparableTo(T)] U as ComparableTo(Optional(T));
impl forall [T:! Printable] Optional(T) as Printable;
impl forall [T:! Type, U:! ComparableTo(T)] U as ComparableTo(Optional(T));
```
**Example:** If `T` implements `ComparableWith(U)`, then `U` should implement
`ComparableWith(T)`.
```
external impl [U:! Type, T:! ComparableWith(U)]
external impl forall [U:! Type, T:! ComparableWith(U)]
U as ComparableWith(T);
```
@@ -4044,9 +4027,9 @@ interface True {}
impl Y as True {}
interface Z(T:! Type) { let Cond:! Type; }
match_first {
impl [T:! Type, U:! Z(T) where .Cond is True] T as Z(U)
impl forall [T:! Type, U:! Z(T) where .Cond is True] T as Z(U)
where .Cond = N { }
impl [T:! Type, U:! Type] T as Z(U)
impl forall [T:! Type, U:! Type] T as Z(U)
where .Cond = Y { }
}
```
@@ -4073,11 +4056,11 @@ class B {}
class C {}
interface D(T:! Type) { let Cond:! Type; }
match_first {
impl [T:! Type, U:! D(T) where .Cond = B] T as D(U)
impl forall [T:! Type, U:! D(T) where .Cond = B] T as D(U)
where .Cond = C { }
impl [T:! Type, U:! D(T) where .Cond = A] T as D(U)
impl forall [T:! Type, U:! D(T) where .Cond = A] T as D(U)
where .Cond = B { }
impl [T:! Type, U:! Type] T as D(U)
impl forall [T:! Type, U:! Type] T as D(U)
where .Cond = A { }
}
```
@@ -4127,7 +4110,7 @@ determining whether a type implements an interface could run forever.
the result of a single impl:
```
impl [A:! Type where Optional(.Self) is B] A as B { ... }
impl forall [A:! Type where Optional(.Self) is B] A as B { ... }
```
This problem can also result from a chain of impls, as in `A is B` if `A* is C`,
@@ -4742,7 +4725,7 @@ interface TotalOrder {
impl as PartialOrder;
}
external impl [T:! TotalOrder] T as PartialOrder {
external impl forall [T:! TotalOrder] T as PartialOrder {
fn PartialLess[me: Self](right: Self) -> bool {
return me.TotalLess(right);
}
@@ -4888,14 +4871,14 @@ fn ProcessVector(v: Vector(i32)) {
// Satisfies the requirement that `Vector(i32)` must
// implement `Equatable` since `i32` is `Equatable`.
external impl Vector(T:! Equatable) as Equatable { ... }
external impl forall [T:! Equatable] Vector(T) as Equatable { ... }
```
In some cases, the interface's requirement can be trivially satisfied by the
implementation itself, as in:
```
impl [T:! Type] T as CommonTypeWith(T) { ... }
impl forall [T:! Type] T as CommonTypeWith(T) { ... }
```
Here is an example where the requirement of interface `Iterable` that the type
@@ -4907,7 +4890,8 @@ class Foo(T:! Type) {}
// This is allowed because we know that an `impl Foo(T) as Equatable`
// will exist for all types `T` for which this impl is used, even
// though there's neither an imported impl nor an impl in this file.
external impl Foo(T:! Type where Foo(T) is Equatable) as Iterable {}
external impl forall [T:! Type where Foo(T) is Equatable]
Foo(T) as Iterable {}
```
This might be used to provide an implementation of `Equatable` for types that
@@ -5013,9 +4997,9 @@ interface B { }
interface C { }
interface D { }
impl [T:! A] T as B { }
impl [T:! B] T as C { }
impl [T:! C] T as D { }
impl forall [T:! A] T as B { }
impl forall [T:! B] T as C { }
impl forall [T:! C] T as D { }
fn RequiresD(T:! D)(x: T);
fn RequiresB(T:! B)(x: T);
@@ -5153,16 +5137,16 @@ external impl EvenInt as IntLike;
external impl EvenInt as ComparableWith(EvenInt);
// Allow `EvenInt` to be compared with anything that
// implements `IntLike`, in either order.
external impl [T:! IntLike] EvenInt as ComparableWith(T);
external impl [T:! IntLike] T as ComparableWith(EvenInt);
external impl forall [T:! IntLike] EvenInt as ComparableWith(T);
external impl forall [T:! IntLike] T as ComparableWith(EvenInt);
class PositiveInt { ... }
external impl PositiveInt as IntLike;
external impl PositiveInt as ComparableWith(PositiveInt);
// Allow `PositiveInt` to be compared with anything that
// implements `IntLike`, in either order.
external impl [T:! IntLike] PositiveInt as ComparableWith(T);
external impl [T:! IntLike] T as ComparableWith(PositiveInt);
external impl forall [T:! IntLike] PositiveInt as ComparableWith(T);
external impl forall [T:! IntLike] T as ComparableWith(PositiveInt);
```
Then it will favor selecting the implementation based on the type of the
@@ -5218,7 +5202,7 @@ conversion. The implementation is for types that implement the
```
// "Implementation Two"
external impl [T:! ImplicitAs(f64)]
external impl forall [T:! ImplicitAs(f64)]
Meters as MultipliableWith(T) where .Result = Meters {
fn Multiply[me: Self](other: T) -> Result {
// Carbon will implicitly convert `other` from type
@@ -5276,7 +5260,7 @@ external impl like Meters as MultipliableWith(like f64)
is equivalent to "implementation one", "implementation two", and:
```
external impl [T:! ImplicitAs(Meters)]
external impl forall [T:! ImplicitAs(Meters)]
T as MultipliableWith(f64) where .Result = Meters {
fn Multiply[me: Self](other: f64) -> Result {
// Will implicitly convert `me` to `Meters` in order to
@@ -5304,13 +5288,13 @@ external impl Meters as MultipliableWith(f64)
where .Result = Meters;
// First `like` replaced with a wildcard.
external impl [T:! ImplicitAs(Meters)]
external impl forall [T:! ImplicitAs(Meters)]
T as MultipliableWith(f64) where .Result = Meters;
// Second `like` replaced with a wildcard. Same as the
// declaration part of "implementation two", without the
// body of the definition.
external impl [T:! ImplicitAs(f64)]
external impl forall [T:! ImplicitAs(f64)]
Meters as MultipliableWith(T) where .Result = Meters;
```
@@ -5333,8 +5317,8 @@ Which will generate implementations with declarations:
```
external impl Vector(String) as Printable;
external impl [T:! ImplicitAs(Vector(String))] T as Printable;
external impl [T:! ImplicitAs(String)] Vector(T) as Printable;
external impl forall [T:! ImplicitAs(Vector(String))] T as Printable;
external impl forall [T:! ImplicitAs(String)] Vector(T) as Printable;
```
The generated implementations must be legal or the `like` is illegal. For
@@ -5365,11 +5349,11 @@ parameters must be able to be determined due to being repeated outside of the
external impl like Meters as Printable;
// ❌ Illegal: No other way to determine `T`
external impl [T:! IntLike] like T as Printable;
external impl forall [T:! IntLike] like T as Printable;
// ❌ Illegal: `T` being used in a `where` clause
// is insufficient.
external impl [T:! IntLike] like T
external impl forall [T:! IntLike] like T
as MultipliableWith(i64) where .Result = T;
// ❌ Illegal: `like` can't be used in a `where`
@@ -5379,14 +5363,14 @@ external impl Meters as MultipliableWith(f64)
// ✅ Allowed: `T` can be determined by another
// part of the query.
external impl [T:! IntLike] like T
external impl forall [T:! IntLike] like T
as MultipliableWith(T) where .Result = T;
external impl [T:! IntLike] T
external impl forall [T:! IntLike] T
as MultipliableWith(like T) where .Result = T;
// ✅ Allowed: Only one `like` used at a time, so this
// is equivalent to the above two examples.
external impl [T:! IntLike] like T
external impl forall [T:! IntLike] like T
as MultipliableWith(like T) where .Result = T;
```
@@ -5478,7 +5462,7 @@ The default implementation of this interface is provided by a
```
// Default blanket implementation
impl [T:! Movable] T as OptionalStorage
impl forall [T:! Movable] T as OptionalStorage
where .Storage = (bool, T) {
...
}
@@ -5490,7 +5474,7 @@ patterns:
```
// Specialization for pointers, using nullptr == None
final external impl [T:! Type] T* as OptionalStorage
final external impl forall [T:! Type] T* as OptionalStorage
where .Storage = Array(Byte, sizeof(T*)) {
...
}
@@ -5669,3 +5653,4 @@ parameter, as opposed to an associated type, as in `N:! u32 where ___ >= 2`.
- [#1088: Generic details 10: interface-implemented requirements](https://github.com/carbon-language/carbon-lang/pull/1088)
- [#1144: Generic details 11: operator overloading](https://github.com/carbon-language/carbon-lang/pull/1144)
- [#1146: Generic details 12: parameterized types](https://github.com/carbon-language/carbon-lang/pull/1146)
- [#1327: Generics: `impl forall`](https://github.com/carbon-language/carbon-lang/pull/1327)
+127
View File
@@ -0,0 +1,127 @@
# Generics: `impl forall`
<!--
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->
[Pull request](https://github.com/carbon-language/carbon-lang/pull/1327)
<!-- toc -->
## Table of contents
- [Problem](#problem)
- [Background](#background)
- [Proposal](#proposal)
- [Rationale](#rationale)
- [Alternatives considered](#alternatives-considered)
<!-- tocstop -->
## Problem
We have an ambiguity in the grammar between declaring a parameterized impl and
an impl for an array type.
```
// Parameterized impl
external impl [T:! Printable] Vector(T) as Printable { ... }
// Impl for an array type
external impl [i32; 5] as Printable { ... }
```
When the parser sees `impl [`, it doesn't know which kind of impl declaration it
is parsing without more lookahead than we plan to support. For the same reason,
these declarations are easy for humans to confuse.
## Background
Parameterized impls introduced in
[#920: Generic parameterized impls (details 5)](https://github.com/carbon-language/carbon-lang/pull/920).
Array syntax has not been finalized, but the leading contender is `[i32; 5]`,
similar to
[Rust](https://doc.rust-lang.org/rust-by-example/primitives/array.html). This is
what is currently provisionally implemented in Explorer. Some other contenders
also start with `[` and have the same problem.
This problem was discussed and resolved in question-for-leads issue
[#1192: Parameterized impl syntax](https://github.com/carbon-language/carbon-lang/issues/1192).
## Proposal
This proposal implements the decision in
[#1192](https://github.com/carbon-language/carbon-lang/issues/1192) to write
parameterized impls using this syntax:
> `impl forall [`_generic parameters_`]` _type_ `as` _constraint_ ...
and to remove the option to includes bindings in the _type_ `as` _constraint_
part of the declaration.
This PR includes the changes to
[the generics details design doc](/docs/design/generics/details.md).
## Rationale
This decision favored approaches that did not require more lookahead. This is to
simplify compiler and tool development and to make it easier for humans to read
the code, in support of these goals:
- [Language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem)
- [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write),
particularly "Excellent ergonomics", "Support tooling at every layer of the
development experience, including IDEs", and "Design features to be simple
to implement."
## Alternatives considered
@zygoloid listed some options for addressing this problem
[in the #syntax channel on Discord](https://discord.com/channels/655572317891461132/709488742942900284/963191891334168628):
> Summary of options for implicit parameters / arrays ambiguity discussed so
> far:
>
> 1. Just make it work as-is: `impl [a; b]` parses as an array type,
> `impl [a, b]` parses as an implicit parameter. Theoretically this is
> unambiguous given that a `;` is required inside the `[`...`]` in the former
> and disallowed in the latter. Concerns: it's likely to be visually
> ambiguous.
> 2. Add mandatory parentheses: `impl [T:! Type] (Vector(T) as Container)`.
> Concerns: it's hard to avoid requiring them in cases that don't start with
> a `[` if we want an unambiguous grammar. Requiring them always would impose
> a small ergonomic hit.
> 3. Add an introducer keyword for implicit parameters:
> `impl where [T:! Type] Vector(T) as Container`. Unambiguous. Concerns:
> still some visual ambiguity due to reuse of `[`...`]`, concern over whether
> we'd uniformly use this syntax (`fn F where [T:! Type](x: T)`) or have
> non-uniform syntax for implicit parameters.
> 4. Use a different syntax for array types in general:
> `impl Array(T) as Container` or `impl Array[N] as Container`. Concerns: may
> want a first-class syntax here, especially if (per @geoffromer 's variadics
> work, we want some special behavior for a deduced bound), and there's a
> strong convention to use `[`...`]` for this. The latter syntax is messy
> because of our types-as-expressions approach, but we could imagine
> providing a `impl Type as Indexable where .Result = Type` to construct
> array types. `T[]` might be a special case of some kind.
> 5. Use a different syntax for implicit parameters in general:
> `impl<T:! Type> Vector(T) as Container`. Concerns: we don't have many
> delimiter options unless we start using multi-character delimiters; `()`,
> `[]`, and `{}` are all used for types, leaving `<>` as the only remaining
> bracket. Use of `<>` as brackets as a long history but not a good one. ...
> 6. Remove the implicit parameter list from impls and force them to be
> introduced where they're first used: `impl Vector(T:! Type) as Container`.
> Concerns: harms readability in some cases, eg
> `impl Optional(T:! As(U:! Type)) as As(Optional(U))` versus
> `impl [U:! Type, T:! As(U)] Optional(T) as As(Optional(U))`.
> 7. Move the implicit parameter list before the impl keyword, perhaps with an
> introducer: `generic [T:! Type] impl Vector(T) as Container`. Concerns:
> increases verbosity; would be inconsistent if we put everything but me
> there, and surprising if we put me there. Also not clear what a good
> keyword is, given that the existence of deduced parameters isn't the same
> as an entity being generic.
Ultimately we adopted approach 3, but changed to the new keyword `forall` to
avoid overloading the meaning of a keyword used for something else.