Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generics: Set associated constants using where constraints #1013

Merged
merged 11 commits into from
Jan 27, 2022
102 changes: 58 additions & 44 deletions docs/design/generics/details.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 -->
Expand Down Expand Up @@ -1900,30 +1901,38 @@ 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)) { ... }
}
}

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)) { ... }
}
}
```

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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
...
}
}
Expand All @@ -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 {
...
}
}
Expand All @@ -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 {
...
}
```
Expand Down Expand Up @@ -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 { }
chandlerc marked this conversation as resolved.
Show resolved Hide resolved
```

This means that every type is the common type with itself.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 { }
}
```

Expand All @@ -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 { }
}
```

Expand Down Expand Up @@ -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 { ... }
}
}
Expand Down Expand Up @@ -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 { ... }
}
}
Expand Down Expand Up @@ -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)
Expand All @@ -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)
14 changes: 12 additions & 2 deletions docs/design/generics/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]() { ... }
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
24 changes: 8 additions & 16 deletions docs/design/generics/terminology.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
chandlerc marked this conversation as resolved.
Show resolved Hide resolved
}
```

Expand All @@ -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
Expand Down Expand Up @@ -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)
Loading