mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 22:02:55 +01:00
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:
committed by
GitHub
co-authored by
Richard Smith
parent
94265befee
commit
2d567f5824
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user