Generics: Set associated constants using where constraints (#1013)

Change the syntax for setting the associated constants and types in an interface implementation for a type from using `let` declarations as in:
```
class Vector(T:! Type) {
  impl as Iterable {
    let ElementType:! Type = T;
    ...
  }
}
```
to using `where` clauses as in:
```
class Vector(T:! Type) {
  impl as Iterable where .ElementType = T {
    ...
  }
}
```

This is an attempt to simplify by removing redundancy, improve consistency by removing a use of `let` that was different than other examples, and better support forward declaration that a type implements an interface while retaining the information needed for type checking.

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
This commit is contained in:
josh11b
2022-01-27 11:18:20 -08:00
committed by GitHub
co-authored by Richard Smith
parent 94265befee
commit 2d567f5824
4 changed files with 250 additions and 62 deletions
+58 -44
View File
@@ -112,6 +112,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- [Bridge for C++ customization points](#bridge-for-c-customization-points)
- [Variadic arguments](#variadic-arguments)
- [Range constraints on generic integers](#range-constraints-on-generic-integers)
- [Separate declaration and definition of impl](#separate-declaration-and-definition-of-impl)
- [References](#references)
<!-- tocstop -->
@@ -1900,13 +1901,13 @@ interface NSpacePoint {
}
```
Implementations of `NSpacePoint` for different types might have different values
for `N`:
An implementation of an interface specifies values for associated constants with
a [`where` clause](#where-constraints). For example, implementations of
`NSpacePoint` for different types might have different values for `N`:
```
class Point2D {
impl as NSpacePoint {
let N:! i32 = 2;
impl as NSpacePoint where .N = 2 {
fn Get[addr me: Self*](i: i32) -> f64 { ... }
fn Set[addr me: Self*](i: i32, value: f64) { ... }
fn SetAll[addr me: Self*](value: Array(f64, 2)) { ... }
@@ -1914,8 +1915,7 @@ class Point2D {
}
class Point3D {
impl as NSpacePoint {
let N:! i32 = 3;
impl as NSpacePoint where .N = 3 {
fn Get[addr me: Self*](i: i32) -> f64 { ... }
fn Set[addr me: Self*](i: i32, value: f64) { ... }
fn SetAll[addr me: Self*](value: Array(f64, 3)) { ... }
@@ -1923,7 +1923,16 @@ class Point3D {
}
```
And these values may be accessed as members of the type:
Multiple assignments to associated constants may be joined using the `and`
keyword. The list of assignments is subject to two restrictions:
- An implementation of an interface cannot specify a value for a
[`final`](#final-members) associated constant.
- If an associated constant doesn't have a
[default value](#interface-defaults), every implementation must specify its
value.
These values may be accessed as members of the type:
```
Assert(Point2D.N == 2);
@@ -2021,9 +2030,8 @@ class DynamicArray(T:! Type) {
fn Insert[addr me: Self*](pos: IteratorType, value: T);
fn Remove[addr me: Self*](pos: IteratorType);
impl as StackAssociatedType {
// Set the associated type `ElementType` to `T`.
let ElementType:! Type = T;
// Set the associated type `ElementType` to `T`.
impl as StackAssociatedType where .ElementType = T {
fn Push[addr me: Self*](value: ElementType) {
me->Insert(me->End(), value);
}
@@ -2442,6 +2450,10 @@ constraint Point2DInterface {
}
```
This syntax is also used to specify the values of
[associated constants](#associated-constants) when implementing an interface for
a type.
**Concern:** Using `=` for this use case is not consistent with other `where`
clauses that write a boolean expression that evaluates to `true` when the
constraint is satisfied.
@@ -2486,6 +2498,9 @@ constraint IntStack {
}
```
This syntax is also used to specify the values of
[associated types](#associated-types) when implementing an interface for a type.
##### Equal generic types
Alternatively, two generic types could be constrained to be equal to each other,
@@ -3534,8 +3549,7 @@ lexically in the class' scope:
```
class Vector(T:! Type) {
impl as Iterable {
let ElementType:! Type = T;
impl as Iterable where .ElementType = T {
...
}
}
@@ -3545,8 +3559,7 @@ This is equivalent to naming the type between `impl` and `as`:
```
class Vector(T:! Type) {
impl Vector(T) as Iterable {
let ElementType:! Type = T;
impl Vector(T) as Iterable where .ElementType = T {
...
}
}
@@ -3556,13 +3569,13 @@ An impl may be declared [external](#external-impl) by adding an `external`
keyword before `impl`. External impls may also be declared out-of-line:
```
external impl [T:! Type] Vector(T) as Iterable {
let ElementType:! Type = T;
external impl [T:! Type] Vector(T) as Iterable
where .ElementType = T {
...
}
// This syntax is also allowed:
external impl Vector(T:! Type) as Iterable {
let ElementType:! Type = T;
external impl Vector(T:! Type) as Iterable
where .ElementType = T {
...
}
```
@@ -3758,9 +3771,8 @@ where blanket impls arise:
- `T` implements `CommonType(T)` for all `T`
```
external impl [T:! Type] T as CommonType(T) {
let Result:! auto = T;
}
external impl [T:! Type] T as CommonType(T)
where .Result = T { }
```
This means that every type is the common type with itself.
@@ -3857,6 +3869,8 @@ 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.
- Any `where` clauses that are setting associated constants or types are
removed.
The type structure will always contain a single interface name, which is the
name of the interface being implemented, and some number of type names. Type
@@ -3995,12 +4009,10 @@ 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) {
let Cond:! Type = N;
}
impl [T:! Type, U:! Type] T as Z(U) {
let Cond:! Type = Y;
}
impl [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)
where .Cond = Y { }
}
```
@@ -4026,15 +4038,12 @@ 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) {
let Cond:! Type = C;
}
impl [T:! Type, U:! D(T) where .Cond = A] T as D(U) {
let Cond:! Type = B;
}
impl [T:! Type, U:! Type] T as D(U) {
let Cond:! Type = A;
}
impl [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)
where .Cond = B { }
impl [T:! Type, U:! Type] T as D(U)
where .Cond = A { }
}
```
@@ -4121,15 +4130,13 @@ interface Deref {
// Types implementing `Deref`
class Ptr(T:! Type) {
...
external impl as Deref {
let Result:! Type = T;
external impl as Deref where .Result = T {
fn DoDeref[me: Self]() -> Result { ... }
}
}
class Optional(T:! Type) {
...
external impl as Deref {
let Result:! Type = T;
external impl as Deref where .Result = T {
fn DoDeref[me: Self]() -> Result { ... }
}
}
@@ -4159,16 +4166,14 @@ To mark an impl as not able to be specialized, prefix it with the keyword
class Ptr(T:! Type) {
...
// Note: added `final`
final external impl as Deref {
let Result:! Type = T;
final external impl as Deref where .Result = T {
fn DoDeref[me: Self]() -> Result { ... }
}
}
class Optional(T:! Type) {
...
// Note: added `final`
final external impl as Deref {
let Result:! Type = T;
final external impl as Deref where .Result = T {
fn DoDeref[me: Self]() -> Result { ... }
}
}
@@ -4539,6 +4544,14 @@ between multiple generic integer parameters. For example, if `J < K` and
secondary syntactic concern about how to write this kind of constraint on a
parameter, as opposed to an associated type, as in `N:! u32 where ___ >= 2`.
### Separate declaration and definition of impl
There is a desire to support a short declaration that a type implements an
interface without giving a full definition of that implementation for API files.
Everything needed for type checking is provided in the interface definition,
except for the assignments to associated constants and types, and so those must
be included in the declaration as well.
## References
- [#553: Generics details part 1](https://github.com/carbon-language/carbon-lang/pull/553)
@@ -4549,3 +4562,4 @@ parameter, as opposed to an associated type, as in `N:! u32 where ___ >= 2`.
- [#950: Generic details 6: remove facets](https://github.com/carbon-language/carbon-lang/pull/950)
- [#983: Generic details 7: final impls](https://github.com/carbon-language/carbon-lang/pull/983)
- [#990: Generics details 8: interface default and final members](https://github.com/carbon-language/carbon-lang/pull/990)
- [#1013: Generics: Set associated constants using where constraints](https://github.com/carbon-language/carbon-lang/pull/1013)
+12 -2
View File
@@ -197,8 +197,8 @@ class Song {
// ...
// Implementing `Printable` for `Song` inside the definition of `Song`
// means all names of `Printable`, such as `F`, are included as a part
// of the `Song` API.
// without the keyword `external` means all names of `Printable`, such
// as `F`, are included as a part of the `Song` API.
impl as Printable {
// Could use `Self` in place of `Song` here.
fn Print[me: Song]() { ... }
@@ -594,6 +594,15 @@ Constraints limit the types that the generic function can operate on, but
increase the knowledge that may be used in the body of the function to operate
on values of those types.
Constraints are also used when implementing an interface to specify the values
of associated types (and other associated constants).
```
class Vector(T:! Movable) {
impl as Stack where .ElementType = T { ... }
}
```
### Parameterized impls
Implementations can be parameterized to apply to multiple types. Those
@@ -634,3 +643,4 @@ priority order in a prioritization block.
- [#818: Constraints for generics (generics details 3)](https://github.com/carbon-language/carbon-lang/pull/818)
- [#920: Generic parameterized impls (details 5)](https://github.com/carbon-language/carbon-lang/pull/920)
- [#950: Generic details 6: remove facets](https://github.com/carbon-language/carbon-lang/pull/950)
- [#1013: Generics: Set associated constants using `where` constraints](https://github.com/carbon-language/carbon-lang/pull/1013)
+8 -16
View File
@@ -655,11 +655,11 @@ class ListIterator(ElementType:! Type) {
}
class List(ElementType:! Type) {
// Iterator type is determined by the container type.
let IteratorType:! Iterator = ListIterator(ElementType);
fn Insert[addr me: Self*](position: IteratorType, value: ElementType) {
...
impl as Container where .IteratorType = ListIterator(ElementType) {
fn Insert[addr me: Self*](position: IteratorType, value: ElementType) {
...
}
}
impl as Container;
}
```
@@ -682,18 +682,9 @@ interface Addable(T:! Type) {
An `i32` value might support addition with `i32`, `u16`, and `f64` values.
```
impl i32 as Addable(i32) {
let ResultType:! Type = i32;
// ...
}
impl i32 as Addable(u16) {
let ResultType:! Type = i32;
// ...
}
impl i32 as Addable(f64) {
let ResultType:! Type = f64;
// ...
}
impl i32 as Addable(i32) where .ResultType = i32 { ... }
impl i32 as Addable(u16) where .ResultType = i32 { ... }
impl i32 as Addable(f64) where .ResultType = f64 { ... }
```
To write a generic function requiring a parameter to be `Addable`, there needs
@@ -756,3 +747,4 @@ available in the body of the function.
- [#447: Generics terminology](https://github.com/carbon-language/carbon-lang/pull/447)
- [#731: Generics details 2: adapters, associated types, parameterized interfaces](https://github.com/carbon-language/carbon-lang/pull/731)
- [#950: Generic details 6: remove facets](https://github.com/carbon-language/carbon-lang/pull/950)
- [#1013: Generics: Set associated constants using where constraints](https://github.com/carbon-language/carbon-lang/pull/1013)