Skip to content

Commit 2d567f5

Browse files
josh11bzygoloid
andauthored
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 <[email protected]>
1 parent 94265be commit 2d567f5

File tree

4 files changed

+250
-62
lines changed

4 files changed

+250
-62
lines changed

docs/design/generics/details.md

Lines changed: 58 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
112112
- [Bridge for C++ customization points](#bridge-for-c-customization-points)
113113
- [Variadic arguments](#variadic-arguments)
114114
- [Range constraints on generic integers](#range-constraints-on-generic-integers)
115+
- [Separate declaration and definition of impl](#separate-declaration-and-definition-of-impl)
115116
- [References](#references)
116117

117118
<!-- tocstop -->
@@ -1900,30 +1901,38 @@ interface NSpacePoint {
19001901
}
19011902
```
19021903
1903-
Implementations of `NSpacePoint` for different types might have different values
1904-
for `N`:
1904+
An implementation of an interface specifies values for associated constants with
1905+
a [`where` clause](#where-constraints). For example, implementations of
1906+
`NSpacePoint` for different types might have different values for `N`:
19051907
19061908
```
19071909
class Point2D {
1908-
impl as NSpacePoint {
1909-
let N:! i32 = 2;
1910+
impl as NSpacePoint where .N = 2 {
19101911
fn Get[addr me: Self*](i: i32) -> f64 { ... }
19111912
fn Set[addr me: Self*](i: i32, value: f64) { ... }
19121913
fn SetAll[addr me: Self*](value: Array(f64, 2)) { ... }
19131914
}
19141915
}
19151916

19161917
class Point3D {
1917-
impl as NSpacePoint {
1918-
let N:! i32 = 3;
1918+
impl as NSpacePoint where .N = 3 {
19191919
fn Get[addr me: Self*](i: i32) -> f64 { ... }
19201920
fn Set[addr me: Self*](i: i32, value: f64) { ... }
19211921
fn SetAll[addr me: Self*](value: Array(f64, 3)) { ... }
19221922
}
19231923
}
19241924
```
19251925
1926-
And these values may be accessed as members of the type:
1926+
Multiple assignments to associated constants may be joined using the `and`
1927+
keyword. The list of assignments is subject to two restrictions:
1928+
1929+
- An implementation of an interface cannot specify a value for a
1930+
[`final`](#final-members) associated constant.
1931+
- If an associated constant doesn't have a
1932+
[default value](#interface-defaults), every implementation must specify its
1933+
value.
1934+
1935+
These values may be accessed as members of the type:
19271936
19281937
```
19291938
Assert(Point2D.N == 2);
@@ -2021,9 +2030,8 @@ class DynamicArray(T:! Type) {
20212030
fn Insert[addr me: Self*](pos: IteratorType, value: T);
20222031
fn Remove[addr me: Self*](pos: IteratorType);
20232032

2024-
impl as StackAssociatedType {
2025-
// Set the associated type `ElementType` to `T`.
2026-
let ElementType:! Type = T;
2033+
// Set the associated type `ElementType` to `T`.
2034+
impl as StackAssociatedType where .ElementType = T {
20272035
fn Push[addr me: Self*](value: ElementType) {
20282036
me->Insert(me->End(), value);
20292037
}
@@ -2442,6 +2450,10 @@ constraint Point2DInterface {
24422450
}
24432451
```
24442452
2453+
This syntax is also used to specify the values of
2454+
[associated constants](#associated-constants) when implementing an interface for
2455+
a type.
2456+
24452457
**Concern:** Using `=` for this use case is not consistent with other `where`
24462458
clauses that write a boolean expression that evaluates to `true` when the
24472459
constraint is satisfied.
@@ -2486,6 +2498,9 @@ constraint IntStack {
24862498
}
24872499
```
24882500
2501+
This syntax is also used to specify the values of
2502+
[associated types](#associated-types) when implementing an interface for a type.
2503+
24892504
##### Equal generic types
24902505
24912506
Alternatively, two generic types could be constrained to be equal to each other,
@@ -3534,8 +3549,7 @@ lexically in the class' scope:
35343549
35353550
```
35363551
class Vector(T:! Type) {
3537-
impl as Iterable {
3538-
let ElementType:! Type = T;
3552+
impl as Iterable where .ElementType = T {
35393553
...
35403554
}
35413555
}
@@ -3545,8 +3559,7 @@ This is equivalent to naming the type between `impl` and `as`:
35453559
35463560
```
35473561
class Vector(T:! Type) {
3548-
impl Vector(T) as Iterable {
3549-
let ElementType:! Type = T;
3562+
impl Vector(T) as Iterable where .ElementType = T {
35503563
...
35513564
}
35523565
}
@@ -3556,13 +3569,13 @@ An impl may be declared [external](#external-impl) by adding an `external`
35563569
keyword before `impl`. External impls may also be declared out-of-line:
35573570
35583571
```
3559-
external impl [T:! Type] Vector(T) as Iterable {
3560-
let ElementType:! Type = T;
3572+
external impl [T:! Type] Vector(T) as Iterable
3573+
where .ElementType = T {
35613574
...
35623575
}
35633576
// This syntax is also allowed:
3564-
external impl Vector(T:! Type) as Iterable {
3565-
let ElementType:! Type = T;
3577+
external impl Vector(T:! Type) as Iterable
3578+
where .ElementType = T {
35663579
...
35673580
}
35683581
```
@@ -3758,9 +3771,8 @@ where blanket impls arise:
37583771
- `T` implements `CommonType(T)` for all `T`
37593772
37603773
```
3761-
external impl [T:! Type] T as CommonType(T) {
3762-
let Result:! auto = T;
3763-
}
3774+
external impl [T:! Type] T as CommonType(T)
3775+
where .Result = T { }
37643776
```
37653777
37663778
This means that every type is the common type with itself.
@@ -3857,6 +3869,8 @@ parameters are replaced the declarations are normalized as follows:
38573869
between the `impl` and `as` keywords if the type is left out.
38583870
- Pointer types `T*` are replaced with `Ptr(T)`.
38593871
- The `external` keyword is removed, if present.
3872+
- Any `where` clauses that are setting associated constants or types are
3873+
removed.
38603874
38613875
The type structure will always contain a single interface name, which is the
38623876
name of the interface being implemented, and some number of type names. Type
@@ -3995,12 +4009,10 @@ interface True {}
39954009
impl Y as True {}
39964010
interface Z(T:! Type) { let Cond:! Type; }
39974011
match_first {
3998-
impl [T:! Type, U:! Z(T) where .Cond is True] T as Z(U) {
3999-
let Cond:! Type = N;
4000-
}
4001-
impl [T:! Type, U:! Type] T as Z(U) {
4002-
let Cond:! Type = Y;
4003-
}
4012+
impl [T:! Type, U:! Z(T) where .Cond is True] T as Z(U)
4013+
where .Cond = N { }
4014+
impl [T:! Type, U:! Type] T as Z(U)
4015+
where .Cond = Y { }
40044016
}
40054017
```
40064018
@@ -4026,15 +4038,12 @@ class B {}
40264038
class C {}
40274039
interface D(T:! Type) { let Cond:! Type; }
40284040
match_first {
4029-
impl [T:! Type, U:! D(T) where .Cond = B] T as D(U) {
4030-
let Cond:! Type = C;
4031-
}
4032-
impl [T:! Type, U:! D(T) where .Cond = A] T as D(U) {
4033-
let Cond:! Type = B;
4034-
}
4035-
impl [T:! Type, U:! Type] T as D(U) {
4036-
let Cond:! Type = A;
4037-
}
4041+
impl [T:! Type, U:! D(T) where .Cond = B] T as D(U)
4042+
where .Cond = C { }
4043+
impl [T:! Type, U:! D(T) where .Cond = A] T as D(U)
4044+
where .Cond = B { }
4045+
impl [T:! Type, U:! Type] T as D(U)
4046+
where .Cond = A { }
40384047
}
40394048
```
40404049
@@ -4121,15 +4130,13 @@ interface Deref {
41214130
// Types implementing `Deref`
41224131
class Ptr(T:! Type) {
41234132
...
4124-
external impl as Deref {
4125-
let Result:! Type = T;
4133+
external impl as Deref where .Result = T {
41264134
fn DoDeref[me: Self]() -> Result { ... }
41274135
}
41284136
}
41294137
class Optional(T:! Type) {
41304138
...
4131-
external impl as Deref {
4132-
let Result:! Type = T;
4139+
external impl as Deref where .Result = T {
41334140
fn DoDeref[me: Self]() -> Result { ... }
41344141
}
41354142
}
@@ -4159,16 +4166,14 @@ To mark an impl as not able to be specialized, prefix it with the keyword
41594166
class Ptr(T:! Type) {
41604167
...
41614168
// Note: added `final`
4162-
final external impl as Deref {
4163-
let Result:! Type = T;
4169+
final external impl as Deref where .Result = T {
41644170
fn DoDeref[me: Self]() -> Result { ... }
41654171
}
41664172
}
41674173
class Optional(T:! Type) {
41684174
...
41694175
// Note: added `final`
4170-
final external impl as Deref {
4171-
let Result:! Type = T;
4176+
final external impl as Deref where .Result = T {
41724177
fn DoDeref[me: Self]() -> Result { ... }
41734178
}
41744179
}
@@ -4539,6 +4544,14 @@ between multiple generic integer parameters. For example, if `J < K` and
45394544
secondary syntactic concern about how to write this kind of constraint on a
45404545
parameter, as opposed to an associated type, as in `N:! u32 where ___ >= 2`.
45414546
4547+
### Separate declaration and definition of impl
4548+
4549+
There is a desire to support a short declaration that a type implements an
4550+
interface without giving a full definition of that implementation for API files.
4551+
Everything needed for type checking is provided in the interface definition,
4552+
except for the assignments to associated constants and types, and so those must
4553+
be included in the declaration as well.
4554+
45424555
## References
45434556
45444557
- [#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`.
45494562
- [#950: Generic details 6: remove facets](https://github.com/carbon-language/carbon-lang/pull/950)
45504563
- [#983: Generic details 7: final impls](https://github.com/carbon-language/carbon-lang/pull/983)
45514564
- [#990: Generics details 8: interface default and final members](https://github.com/carbon-language/carbon-lang/pull/990)
4565+
- [#1013: Generics: Set associated constants using where constraints](https://github.com/carbon-language/carbon-lang/pull/1013)

docs/design/generics/overview.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,8 @@ class Song {
197197
// ...
198198
199199
// Implementing `Printable` for `Song` inside the definition of `Song`
200-
// means all names of `Printable`, such as `F`, are included as a part
201-
// of the `Song` API.
200+
// without the keyword `external` means all names of `Printable`, such
201+
// as `F`, are included as a part of the `Song` API.
202202
impl as Printable {
203203
// Could use `Self` in place of `Song` here.
204204
fn Print[me: Song]() { ... }
@@ -594,6 +594,15 @@ Constraints limit the types that the generic function can operate on, but
594594
increase the knowledge that may be used in the body of the function to operate
595595
on values of those types.
596596

597+
Constraints are also used when implementing an interface to specify the values
598+
of associated types (and other associated constants).
599+
600+
```
601+
class Vector(T:! Movable) {
602+
impl as Stack where .ElementType = T { ... }
603+
}
604+
```
605+
597606
### Parameterized impls
598607

599608
Implementations can be parameterized to apply to multiple types. Those
@@ -634,3 +643,4 @@ priority order in a prioritization block.
634643
- [#818: Constraints for generics (generics details 3)](https://github.com/carbon-language/carbon-lang/pull/818)
635644
- [#920: Generic parameterized impls (details 5)](https://github.com/carbon-language/carbon-lang/pull/920)
636645
- [#950: Generic details 6: remove facets](https://github.com/carbon-language/carbon-lang/pull/950)
646+
- [#1013: Generics: Set associated constants using `where` constraints](https://github.com/carbon-language/carbon-lang/pull/1013)

docs/design/generics/terminology.md

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -655,11 +655,11 @@ class ListIterator(ElementType:! Type) {
655655
}
656656
class List(ElementType:! Type) {
657657
// Iterator type is determined by the container type.
658-
let IteratorType:! Iterator = ListIterator(ElementType);
659-
fn Insert[addr me: Self*](position: IteratorType, value: ElementType) {
660-
...
658+
impl as Container where .IteratorType = ListIterator(ElementType) {
659+
fn Insert[addr me: Self*](position: IteratorType, value: ElementType) {
660+
...
661+
}
661662
}
662-
impl as Container;
663663
}
664664
```
665665

@@ -682,18 +682,9 @@ interface Addable(T:! Type) {
682682
An `i32` value might support addition with `i32`, `u16`, and `f64` values.
683683

684684
```
685-
impl i32 as Addable(i32) {
686-
let ResultType:! Type = i32;
687-
// ...
688-
}
689-
impl i32 as Addable(u16) {
690-
let ResultType:! Type = i32;
691-
// ...
692-
}
693-
impl i32 as Addable(f64) {
694-
let ResultType:! Type = f64;
695-
// ...
696-
}
685+
impl i32 as Addable(i32) where .ResultType = i32 { ... }
686+
impl i32 as Addable(u16) where .ResultType = i32 { ... }
687+
impl i32 as Addable(f64) where .ResultType = f64 { ... }
697688
```
698689

699690
To write a generic function requiring a parameter to be `Addable`, there needs
@@ -756,3 +747,4 @@ available in the body of the function.
756747
- [#447: Generics terminology](https://github.com/carbon-language/carbon-lang/pull/447)
757748
- [#731: Generics details 2: adapters, associated types, parameterized interfaces](https://github.com/carbon-language/carbon-lang/pull/731)
758749
- [#950: Generic details 6: remove facets](https://github.com/carbon-language/carbon-lang/pull/950)
750+
- [#1013: Generics: Set associated constants using where constraints](https://github.com/carbon-language/carbon-lang/pull/1013)

0 commit comments

Comments
 (0)