diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 4e5319c12e6c..78828a8ec1ee 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -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) diff --git a/proposals/p1327.md b/proposals/p1327.md new file mode 100644 index 000000000000..fac2c09e00e8 --- /dev/null +++ b/proposals/p1327.md @@ -0,0 +1,127 @@ +# Generics: `impl forall` + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/1327) + + + +## Table of contents + +- [Problem](#problem) +- [Background](#background) +- [Proposal](#proposal) +- [Rationale](#rationale) +- [Alternatives considered](#alternatives-considered) + + + +## 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 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.