From a27a55f59727d82d252247f2e10a774d16c29df6 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 7 Jul 2023 22:37:42 +0000 Subject: [PATCH 01/83] Checkpoint progress. --- docs/design/README.md | 2 +- docs/design/classes.md | 14 +- docs/design/expressions/if.md | 2 +- .../expressions/implicit_conversions.md | 10 +- docs/design/expressions/member_access.md | 31 ++- docs/design/generics/details.md | 198 +++++++++--------- docs/design/generics/goals.md | 4 +- docs/design/generics/overview.md | 49 +++-- docs/design/generics/terminology.md | 85 ++++---- docs/project/faq.md | 2 +- 10 files changed, 196 insertions(+), 201 deletions(-) diff --git a/docs/design/README.md b/docs/design/README.md index 22ae32f8c2019..9218614c05b81 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -2750,7 +2750,7 @@ fn DrawTies[T:! Renderable & GameResult](x: T) { > References: > -> - [Combining interfaces by anding type-of-types](generics/details.md#combining-interfaces-by-anding-type-of-types) +> - [Combining interfaces by anding facet types](generics/details.md#combining-interfaces-by-anding-facet-types) > - Question-for-leads issue > [#531: Combine interfaces with `+` or `&`](https://github.com/carbon-language/carbon-lang/issues/531) > - Proposal diff --git a/docs/design/classes.md b/docs/design/classes.md index 5fb04896c1c36..22d071d0f12ec 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -1595,7 +1595,7 @@ class MyDerivedClass { The properties of a type, whether type is abstract, base, or final, and whether the destructor is virtual or non-virtual, determines which -[type-of-types](/docs/design/generics/terminology.md#type-of-type) it satisfies. +[facet types](/docs/design/generics/terminology.md#facet-type) it satisfies. - Non-abstract classes are `Concrete`. This means you can create local and member variables of this type. `Concrete` types have destructors that are @@ -1623,7 +1623,7 @@ conform to the decision on | final | any | yes | yes | yes | The compiler automatically determines which of these -[type-of-types](/docs/design/generics/terminology.md#type-of-type) a given type +[facet types](/docs/design/generics/terminology.md#facet-type) a given type satisfies. It is illegal to directly implement `Concrete`, `Deletable`, or `Destructible` directly. For more about these constraints, see ["destructor constraints" in the detailed generics design](/docs/design/generics/details.md#destructor-constraints). @@ -1670,7 +1670,7 @@ and not implemented in the current class. Types satisfy the [`TrivialDestructor`](/docs/design/generics/details.md#destructor-constraints) -type-of-type if: +facet type if: - the class declaration does not define a destructor or the class defines the destructor with an empty body `{ }`, @@ -1949,11 +1949,11 @@ We want four things so that Carbon's object-safe interfaces may interoperate with C++ abstract base classes without data members, matching the [interface as base class use case](#interface-as-base-class): -- Ability to convert an object-safe interface (a type-of-type) into an +- Ability to convert an object-safe interface (a facet type) into an C++-compatible base class (a base type), maybe using `AsBaseClass(MyInterface)`. - Ability to convert a C++ base class without data members (a base type) into - an object-safe interface (a type-of-type), maybe using `AsInterface(MyIBC)`. + an object-safe interface (a facet type), maybe using `AsInterface(MyIBC)`. - Ability to convert a (thin) pointer to an abstract base class to a `DynPtr` of the corresponding interface. - Ability to convert `DynPtr(MyInterface)` values to a proxy type that extends @@ -2160,7 +2160,7 @@ implementations for [data classes](#data-classes) more generally. These implementations will typically subject to the criteria that all the data fields of the type must implement the interface. An example use case would be to say that a data class is serializable if all of its fields were. For this we will -need a type-of-type for capturing that criteria, maybe something like +need a facet type for capturing that criteria, maybe something like `DataFieldsImplement(MyInterface)`. The templated implementation will need some way of iterating through the fields so it can perform operations fieldwise. This feature should also implement the interfaces for any tuples whose fields satisfy @@ -2239,7 +2239,7 @@ the type of `U.x`." - [Allow functions to act as destructors](/proposals/p1154.md#allow-functions-to-act-as-destructors) - [Allow private destructors](/proposals/p1154.md#allow-private-destructors) - [Allow multiple conditional destructors](/proposals/p1154.md#allow-multiple-conditional-destructors) - - [Type-of-type naming](/proposals/p1154.md#type-of-type-naming) + - [Facet type naming](/proposals/p1154.md#type-of-type-naming) - [Other approaches to extensible classes without vtables](/proposals/p1154.md#other-approaches-to-extensible-classes-without-vtables) - [#2107: Clarify rules around `Self` and `.Self`](https://github.com/carbon-language/carbon-lang/pull/2107) diff --git a/docs/design/expressions/if.md b/docs/design/expressions/if.md index e13b2c021d9c1..f61e8ecf75cd9 100644 --- a/docs/design/expressions/if.md +++ b/docs/design/expressions/if.md @@ -176,7 +176,7 @@ rules in this document. Because this `impl` is declared `final`, `T.(CommonType(T)).Result` is always assumed to be `T`, even in contexts where `T` involves a generic parameter and -so the result would normally be an unknown type whose type-of-type is `type`. +so the result would normally be an unknown type whose facet type is `type`. ``` fn F[T:! Hashable](c: bool, x: T, y: T) -> HashCode { diff --git a/docs/design/expressions/implicit_conversions.md b/docs/design/expressions/implicit_conversions.md index 990dcc2c3e55d..ec8e1fdeed0f6 100644 --- a/docs/design/expressions/implicit_conversions.md +++ b/docs/design/expressions/implicit_conversions.md @@ -19,7 +19,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Data types](#data-types) - [Same type](#same-type) - [Pointer conversions](#pointer-conversions) - - [Type-of-types](#type-of-types) + - [Facet types](#facet-types) - [Consistency with `as`](#consistency-with-as) - [Extensibility](#extensibility) - [Alternatives considered](#alternatives-considered) @@ -182,11 +182,11 @@ var r: Base** = &p; *r = q; ``` -### Type-of-types +### Facet types -A type `T` with [type-of-type](../generics/terminology.md#type-of-type) `TT1` -can be implicitly converted to the type-of-type `TT2` if `T` -[satisfies the requirements](../generics/details.md#subtyping-between-type-of-types) +A type `T` with [facet type](../generics/terminology.md#facet-type) `TT1` can be +implicitly converted to the facet type `TT2` if `T` +[satisfies the requirements](../generics/details.md#subtyping-between-facet-types) of `TT2`. ## Consistency with `as` diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index d020035585e23..56a6edd01ee1b 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -129,16 +129,16 @@ remaining cases we wish to support: - The first operand is a type, and lookup should consider members of that type. For example, `i32.Least` should find the member constant `Least` of the type `i32`. -- The first operand is a type-of-type, and lookup should consider members of - that type-of-type. For example, `Addable.Add` should find the member - function `Add` of the interface `Addable`. Because a type-of-type is a type, - this is a special case of the previous bullet. +- The first operand is a facet type, and lookup should consider members of + that facet type. For example, `Addable.Add` should find the member function + `Add` of the interface `Addable`. Because a facet type is a type, this is a + special case of the previous bullet. -Note that because a type is a value, and a type-of-type is a type, these cases -are overlapping and not entirely separable. +Note that because a type is a value, and a facet type is a type, these cases are +overlapping and not entirely separable. If any of the above lookups ever looks for members of a type parameter, it -should consider members of the type-of-type, treating the type parameter as an +should consider members of the facet type, treating the type parameter as an archetype. **Note:** If lookup is performed into a type that involves a template parameter, @@ -151,7 +151,7 @@ For a simple member access, the word is looked up in the following types: - If the first operand can be evaluated and evaluates to a type, that type. - If the type of the first operand can be evaluated, that type. - If the type of the first operand is a generic type parameter, and the type - of that generic type parameter can be evaluated, that type-of-type. + of that generic type parameter can be evaluated, that facet type. The results of these lookups are [combined](#lookup-ambiguity). @@ -271,10 +271,10 @@ class SquareWidget { } fn DrawWidget(r: RoundWidget, s: SquareWidget) { - // ✅ OK, lookup in type and lookup in type-of-type find the same entity. + // ✅ OK, lookup in type and lookup in facet type find the same entity. DrawTemplate(r); - // ✅ OK, lookup in type and lookup in type-of-type find the same entity. + // ✅ OK, lookup in type and lookup in facet type find the same entity. DrawTemplate(s); // ✅ OK, found in type. @@ -294,11 +294,10 @@ the lookup results is the unique member that was found. ## `impl` lookup When the second operand of a member access expression resolves to a member of an -interface `I`, and the first operand is a value other than a type-of-type, -_`impl` lookup_ is performed to map the member of the interface to the -corresponding member of the relevant `impl`. The member of the `impl` replaces -the member of the interface in all further processing of the member access -expression. +interface `I`, and the first operand is a value other than a facet type, _`impl` +lookup_ is performed to map the member of the interface to the corresponding +member of the relevant `impl`. The member of the `impl` replaces the member of +the interface in all further processing of the member access expression. ```carbon interface Addable { @@ -378,7 +377,7 @@ class C { } } -// `V` is `I` and `M` is `I.F`. Because `V` is a type-of-type, +// `V` is `I` and `M` is `I.F`. Because `V` is a facet type, // `impl` lookup is not performed, and the alias binds to #1. alias A1 = I.F; diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 933e5ea8665a0..6c98cc2d185c1 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -21,10 +21,10 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Return type](#return-type) - [Implementation model](#implementation-model) - [Interfaces recap](#interfaces-recap) -- [Type-of-types](#type-of-types) +- [Facet types](#facet-types) - [Named constraints](#named-constraints) - - [Subtyping between type-of-types](#subtyping-between-type-of-types) -- [Combining interfaces by anding type-of-types](#combining-interfaces-by-anding-type-of-types) + - [Subtyping between facet types](#subtyping-between-facet-types) +- [Combining interfaces by anding facet types](#combining-interfaces-by-anding-facet-types) - [Interface requiring other interfaces](#interface-requiring-other-interfaces) - [Interface extension](#interface-extension) - [`extend` and `impl` with named constraints](#extend-and-impl-with-named-constraints) @@ -51,7 +51,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Same type constraints](#same-type-constraints) - [Set an associated type to a specific value](#set-an-associated-type-to-a-specific-value) - [Equal generic types](#equal-generic-types) - - [Satisfying both type-of-types](#satisfying-both-type-of-types) + - [Satisfying both facet types](#satisfying-both-facet-types) - [Type bound for associated type](#type-bound-for-associated-type) - [Type bounds on associated types in declarations](#type-bounds-on-associated-types-in-declarations) - [Type bounds on associated types in interfaces](#type-bounds-on-associated-types-in-interfaces) @@ -65,13 +65,13 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Referencing names in the interface being defined](#referencing-names-in-the-interface-being-defined) - [Manual type equality](#manual-type-equality) - [`observe` declarations](#observe-declarations) -- [Other constraints as type-of-types](#other-constraints-as-type-of-types) +- [Other constraints as facet types](#other-constraints-as-facet-types) - [Is a derived class](#is-a-derived-class) - [Type compatible with another type](#type-compatible-with-another-type) - [Same implementation restriction](#same-implementation-restriction) - [Example: Multiple implementations of the same interface](#example-multiple-implementations-of-the-same-interface) - [Example: Creating an impl out of other implementations](#example-creating-an-impl-out-of-other-implementations) - - [Sized types and type-of-types](#sized-types-and-type-of-types) + - [Sized types and facet types](#sized-types-and-facet-types) - [Implementation model](#implementation-model-2) - [`TypeId`](#typeid) - [Destructor constraints](#destructor-constraints) @@ -145,8 +145,8 @@ a string. To do this, we give the `PrintToStdout` function two parameters: one is the value to print, let's call that `val`, the other is the type of that value, let's call that `T`. The type of `val` is `T`, what is the type of `T`? Well, since we want to let `T` be any type implementing the -`ConvertibleToString` interface, we express that in the "interfaces are -type-of-types" model by saying the type of `T` is `ConvertibleToString`. +`ConvertibleToString` interface, we express that in the "interfaces are facet +types" model by saying the type of `T` is `ConvertibleToString`. Since we can figure out `T` from the type of `val`, we don't need the caller to pass in `T` explicitly, so it can be a @@ -164,10 +164,10 @@ the interface's functions to function pointers. For more on this, see In addition to function pointer members, interfaces can include any constants that belong to a type. For example, the -[type's size](#sized-types-and-type-of-types) (represented by an integer -constant member of the type) could be a member of an interface and its -implementation. There are a few cases why we would include another interface -implementation as a member: +[type's size](#sized-types-and-facet-types) (represented by an integer constant +member of the type) could be a member of an interface and its implementation. +There are a few cases why we would include another interface implementation as a +member: - [associated types](#associated-types) - [type parameters](#parameterized-interfaces) @@ -237,7 +237,7 @@ Each declaration in the interface defines an [associated entity](terminology.md#associated-entity). In this example, `Vector` has two associated methods, `Add` and `Scale`. -An interface defines a type-of-type, that is a type whose values are types. The +An interface defines a facet type, that is a type whose values are types. The values of an interface are any types implementing the interface, and so provide definitions for all the functions (and other members) declared in the interface. @@ -597,8 +597,8 @@ var v: Point = AddAndScaleGeneric(a, w, 2.5); ``` Here `T` is a type whose type is `Vector`. The `:!` syntax means that `T` is a -_[generic parameter](terminology.md#generic-versus-template-parameters)_. That -means it must be known to the caller, but we will only use the information +_[checked generic parameter](terminology.md#checked-versus-template-parameters)_. +That means it must be known to the caller, but we will only use the information present in the signature of the function to type check the body of `AddAndScaleGeneric`'s definition. In this case, we know that any value of type `T` implements the `Vector` interface and so has an `Add` and a `Scale` method. @@ -805,35 +805,35 @@ An interface's name may be used in a few different contexts: - to define [an `impl` for a type](#implementing-interfaces), - as a namespace name in [a qualified name](#qualified-member-names-and-compound-member-access), and -- as a [type-of-type](terminology.md#type-of-type) for +- as a [facet type](terminology.md#facet-type) for [a generic type parameter](#generics). -While interfaces are examples of type-of-types, type-of-types are a more general +While interfaces are examples of facet types, facet types are a more general concept, for which interfaces are a building block. -## Type-of-types +## Facet types -A [type-of-type](terminology.md#type-of-type) consists of a set of requirements -and a set of names. Requirements are typically a set of interfaces that a type -must satisfy, though other kinds of requirements are added below. The names are +A [facet type](terminology.md#facet-type) consists of a set of requirements and +a set of names. Requirements are typically a set of interfaces that a type must +satisfy, though other kinds of requirements are added below. The names are aliases for qualified names in those interfaces. -An interface is one particularly simple example of a type-of-type. For example, -`Vector` as a type-of-type has a set of requirements consisting of the single +An interface is one particularly simple example of a facet type. For example, +`Vector` as a facet type has a set of requirements consisting of the single interface `Vector`. Its set of names consists of `Add` and `Scale` which are aliases for the corresponding qualified names inside `Vector` as a namespace. -The requirements determine which types are values of a given type-of-type. The -set of names in a type-of-type determines the API of a generic type value and -define the result of [member access](/docs/design/expressions/member_access.md) -into the type-of-type. +The requirements determine which types are values of a given facet type. The set +of names in a facet type determines the API of a generic type value and define +the result of [member access](/docs/design/expressions/member_access.md) into +the facet type. -This general structure of type-of-types holds not just for interfaces, but -others described in the rest of this document. +This general structure of facet types holds not just for interfaces, but others +described in the rest of this document. ## Named constraints -If the interfaces discussed above are the building blocks for type-of-types, +If the interfaces discussed above are the building blocks for facet types, [generic named constraints](terminology.md#named-constraints) describe how they may be composed together. Unlike interfaces which are nominal, the name of a named constraint is not a part of its value. Two different named constraints @@ -843,7 +843,7 @@ implement, types automatically implement any named constraints they can satisfy. A named constraint definition can contain interface requirements using `impl` declarations and names using `alias` declarations. Note that this allows us to -declare the aspects of a type-of-type directly. +declare the aspects of a facet type directly. ``` constraint VectorLegoFish { @@ -882,9 +882,8 @@ whenever an interface may be. This includes all of these - A named constraint may be used as a namespace name in [a qualified name](#qualified-member-names-and-compound-member-access). For example, `VectorLegoFish.VAdd` refers to the same name as `Vector.Add`. -- A named constraint may be used as a - [type-of-type](terminology.md#type-of-type) for - [a generic type parameter](#generics). +- A named constraint may be used as a [facet type](terminology.md#facet-type) + for [a generic type parameter](#generics). We don't expect developers to directly define many named constraints, but other constructs we do expect them to use will be defined in terms of them. For @@ -895,8 +894,8 @@ as: constraint type { } ``` -That is, `type` is the type-of-type with no requirements (so matches every -type), and defines no names. +That is, `type` is the facet type with no requirements (so matches every type), +and defines no names. ``` fn Identity[T:! type](x: T) -> T { @@ -979,15 +978,15 @@ class ImplementsS { **TODO:** Move the `template constraint` and `auto` content to the template design document, once it exists. -### Subtyping between type-of-types +### Subtyping between facet types -There is a subtyping relationship between type-of-types that allows calls of one +There is a subtyping relationship between facet types that allows calls of one generic function from another as long as it has a subset of the requirements. -Given a generic type variable `T` with type-of-type `I1`, it satisfies a -type-of-type `I2` as long as the requirements of `I1` are a superset of the -requirements of `I2`. This means a value `x` of type `T` may be passed to -functions requiring types to satisfy `I2`, as in this example: +Given a generic type variable `T` with facet type `I1`, it satisfies a facet +type `I2` as long as the requirements of `I1` are a superset of the requirements +of `I2`. This means a value `x` of type `T` may be passed to functions requiring +types to satisfy `I2`, as in this example: ``` interface Printable { fn Print[self: Self](); } @@ -1014,12 +1013,12 @@ fn PrintDrawPrint[T1:! PrintAndRender](x1: T1) { } ``` -## Combining interfaces by anding type-of-types +## Combining interfaces by anding facet types In order to support functions that require more than one interface to be -implemented, we provide a combination operator on type-of-types, written `&`. -This operator gives the type-of-type with the union of all the requirements and -the union of the names minus any conflicts. +implemented, we provide a combination operator on facet types, written `&`. This +operator gives the facet type with the union of all the requirements and the +union of the names minus any conflicts. ``` interface Printable { @@ -1030,7 +1029,7 @@ interface Renderable { fn Draw[self: Self](); } -// `Printable & Renderable` is syntactic sugar for this type-of-type: +// `Printable & Renderable` is syntactic sugar for this facet type: constraint { require Self impls Printable; require Self impls Renderable; @@ -1072,7 +1071,7 @@ interface EndOfGame { fn Draw[self: Self](); fn Winner[self: Self](player: i32); } -// `Renderable & EndOfGame` is syntactic sugar for this type-of-type: +// `Renderable & EndOfGame` is syntactic sugar for this facet type: constraint { require Self impls Renderable; require Self impls EndOfGame; @@ -1107,13 +1106,13 @@ fn RenderTieGame[T:! RenderableAndEndOfGame](x: T) { ``` Reserving the name when there is a conflict is part of resolving what happens -when you combine more than two type-of-types. If `x` is forbidden in `A`, it is +when you combine more than two facet types. If `x` is forbidden in `A`, it is forbidden in `A & B`, whether or not `B` defines the name `x`. This makes `&` associative and commutative, and so it is well defined on sets of interfaces, or -other type-of-types, independent of order. +other facet types, independent of order. -Note that we do _not_ consider two type-of-types using the same name to mean the -same thing to be a conflict. For example, combining a type-of-type with itself +Note that we do _not_ consider two facet types using the same name to mean the +same thing to be a conflict. For example, combining a facet type with itself gives itself, `MyTypeOfType & MyTypeOfType == MyTypeOfType`. Also, given two [interface extensions](#interface-extension) of a common base interface, the combination should not conflict on any names in the common base. @@ -1124,21 +1123,21 @@ considered using `+`, See [#531](https://github.com/carbon-language/carbon-lang/issues/531) for the discussion. -**Future work:** We may want to define another operator on type-of-types for -adding requirements to a type-of-type without affecting the names, and so avoid +**Future work:** We may want to define another operator on facet types for +adding requirements to a facet type without affecting the names, and so avoid the possibility of name conflicts. Note this means the operation is not commutative. If we call this operator `[&]`, then `A [&] B` has the names of `A` and `B [&] A` has the names of `B`. ``` -// `Printable [&] Renderable` is syntactic sugar for this type-of-type: +// `Printable [&] Renderable` is syntactic sugar for this facet type: constraint { require Self impls Printable; require Self impls Renderable; alias Print = Printable.Print; } -// `Renderable [&] EndOfGame` is syntactic sugar for this type-of-type: +// `Renderable [&] EndOfGame` is syntactic sugar for this facet type: constraint { require Self impls Renderable; require Self impls EndOfGame; @@ -1173,7 +1172,7 @@ type. For example, in C++, requires all containers to also satisfy the requirements of `DefaultConstructible`, `CopyConstructible`, `EqualityComparable`, and `Swappable`. This is already a capability for -[type-of-types in general](#type-of-types). For consistency we will use the same +[facet types in general](#facet-types). For consistency we will use the same semantics and syntax as we do for [named constraints](#named-constraints): ``` @@ -2456,9 +2455,9 @@ So far, we have restricted a generic type parameter by saying it has to implement an interface or a set of interfaces. There are a variety of other constraints we would like to be able to express, such as applying restrictions to its associated types and associated constants. This is done using the `where` -operator that adds constraints to a type-of-type. +operator that adds constraints to a facet type. -The where operator can be applied to a type-of-type in a declaration context: +The where operator can be applied to a facet type in a declaration context: ``` // Constraints on function parameters: @@ -2484,14 +2483,13 @@ We also allow you to name constraints using a `where` operator in a `let` or described in the ["constraint use cases"](#constraint-use-cases) section, but generally look like boolean expressions that should evaluate to `true`. -The result of applying a `where` operator to a type-of-type is another -type-of-type. Note that this expands the kinds of requirements that -type-of-types can have from just interface requirements to also include the -various kinds of constraints discussed later in this section. In addition, it -can introduce relationships between different type variables, such as that a -member of one is equal to the member of another. The `where` operator is not -associative, so a type expression using multiple must use round parens `(`...`)` -to specify grouping. +The result of applying a `where` operator to a facet type is another facet type. +Note that this expands the kinds of requirements that facet types can have from +just interface requirements to also include the various kinds of constraints +discussed later in this section. In addition, it can introduce relationships +between different type variables, such as that a member of one is equal to the +member of another. The `where` operator is not associative, so a type expression +using multiple must use round parens `(`...`)` to specify grouping. **Comparison with other languages:** Both Swift and Rust use `where` clauses on declarations instead of in the expression syntax. These happen after the type @@ -2658,10 +2656,10 @@ fn Map[CT:! Container, (c: CT, f: FT) -> Vector(FT.OutputType); ``` -###### Satisfying both type-of-types +###### Satisfying both facet types If the two types being constrained to be equal have been declared with different -type-of-types, then the actual type value they are set to will have to satisfy +facet types, then the actual type value they are set to will have to satisfy both constraints. For example, if `SortedContainer.ElementType` is declared to be `Comparable`, then in this declaration: @@ -2719,8 +2717,8 @@ fn SortContainer ``` In contrast to [a same type constraint](#same-type-constraints), this does not -say what type `ElementType` exactly is, just that it must satisfy some -type-of-type. +say what type `ElementType` exactly is, just that it must satisfy some facet +type. **Note:** `Container` defines `ElementType` as having type `type`, but `ContainerType.ElementType` has type `Comparable`. This is because @@ -2755,7 +2753,7 @@ fn F[ContainerType:! ContainerInterface ``` We would like to be able to name this constraint, defining a -`RandomAccessContainer` to be a type-of-type whose types satisfy +`RandomAccessContainer` to be a facet type whose types satisfy `ContainerInterface` with an `IteratorType` satisfying `RandomAccessIterator`. ``` @@ -3341,11 +3339,11 @@ fn F[T:! Transitive](t: T) { Since adding an `observe` declaration only adds external implementations of interfaces to generic types, they may be added without breaking existing code. -## Other constraints as type-of-types +## Other constraints as facet types -There are some constraints that we will naturally represent as named -type-of-types. These can either be used directly to constrain a generic type -parameter, or in a `where ... impls ...` clause to constrain an associated type. +There are some constraints that we will naturally represent as named facet +types. These can either be used directly to constrain a generic type parameter, +or in a `where ... impls ...` clause to constrain an associated type. The compiler determines which types implement these interfaces, developers can not explicitly implement these interfaces for their own types. @@ -3354,7 +3352,7 @@ not explicitly implement these interfaces for their own types. ### Is a derived class -Given a type `T`, `Extends(T)` is a type-of-type whose values are types that are +Given a type `T`, `Extends(T)` is a facet type whose values are types that are derived from `T`. That is, `Extends(T)` is the set of all types `U` that are subtypes of `T`. @@ -3377,15 +3375,15 @@ fn DownCast[T:! type](p: T*, U:! type where .Self extends T) -> U*; ### Type compatible with another type -Given a type `U`, define the type-of-type `CompatibleWith(U)` as follows: +Given a type `U`, define the facet type `CompatibleWith(U)` as follows: > `CompatibleWith(U)` is a type whose values are types `T` such that `T` and `U` > are [compatible](terminology.md#compatible-types). That is values of types `T` > and `U` can be cast back and forth without any change in representation (for > example `T` is an [adapter](#adapting-types) for `U`). -To support this, we extend the requirements that type-of-types are allowed to -have to include a "data representation requirement" option. +To support this, we extend the requirements that facet types are allowed to have +to include a "data representation requirement" option. `CompatibleWith` determines an equivalence relationship between types. Specifically, given two types `T1` and `T2`, they are equivalent if @@ -3515,7 +3513,7 @@ assert((s1 as SongByArtistThenTitle).Compare(s2) == CompareResult.Less); ``` -### Sized types and type-of-types +### Sized types and facet types What is the size of a type? @@ -3533,8 +3531,8 @@ What is the size of a type? essentially equivalent to having dynamic size. A type is called _sized_ if it is in the first two categories, and _unsized_ -otherwise. Note: something with size 0 is still considered "sized". The -type-of-type `Sized` is defined as follows: +otherwise. Note: something with size 0 is still considered "sized". The facet +type `Sized` is defined as follows: > `Sized` is a type whose values are types `T` that are "sized" -- that is the > size of `T` is known, though possibly only generically. @@ -3592,7 +3590,7 @@ local variables when using the dynamic strategy? Or should we only allow `MaybeBox` values to be instantiated locally? Or should this just be a case where the compiler won't necessarily use the dynamic strategy? -**Open question:** Should the `Sized` type-of-type expose an associated constant +**Open question:** Should the `Sized` facet type expose an associated constant with the size? So you could say `T.ByteSize` in the above example to get a generic int value with the size of `T`. Similarly you might say `T.ByteStride` to get the number of bytes used for each element of an array of `T`. @@ -3630,7 +3628,7 @@ requests those capabilities? ### Destructor constraints -There are four type-of-types related to +There are four facet types related to [the destructors of types](/docs/design/classes.md#destructors): - `Concrete` types may be local or member variables. @@ -3640,7 +3638,7 @@ There are four type-of-types related to using the `UnsafeDelete` method on the correct `Allocator`, but it may be unsafe. The concerning case is deleting a pointer to a derived class through a pointer to its base class without a virtual destructor. -- `TrivialDestructor` types have empty destructors. This type-of-type may be +- `TrivialDestructor` types have empty destructors. This facet type may be used with [specialization](#lookup-resolution-and-specialization) to unlock specific optimizations. @@ -3649,16 +3647,16 @@ There are four type-of-types related to conform to the decision on [question-for-leads issue #1058: "How should interfaces for core functionality be named?"](https://github.com/carbon-language/carbon-lang/issues/1058). -The type-of-types `Concrete`, `Deletable`, and `TrivialDestructor` all extend +The facet types `Concrete`, `Deletable`, and `TrivialDestructor` all extend `Destructible`. Combinations of them may be formed using -[the `&` operator](#combining-interfaces-by-anding-type-of-types). For example, -a generic function that both instantiates and deletes values of a type `T` would +[the `&` operator](#combining-interfaces-by-anding-facet-types). For example, a +generic function that both instantiates and deletes values of a type `T` would require `T` implement `Concrete & Deletable`. -Types are forbidden from explicitly implementing these type-of-types directly. +Types are forbidden from explicitly implementing these facet types directly. Instead they use [`destructor` declarations in their class definition](/docs/design/classes.md#destructors) -and the compiler uses them to determine which of these type-of-types are +and the compiler uses them to determine which of these facet types are implemented. ## Generic `let` @@ -3857,8 +3855,8 @@ class Array(T:! type, template N:! i64) { ``` Inside the scope of this `impl` definition, both `P` and `T` refer to the same -type, but `P` has the type-of-type of `Printable` and so has a `Print` member. -The relationship between `T` and `P` is as if there was a `where P == T` clause. +type, but `P` has the facet type of `Printable` and so has a `Print` member. The +relationship between `T` and `P` is as if there was a `where P == T` clause. **TODO:** Need to resolve whether the `T` name can be reused, or if we require that you need to use new names, like `P`, when creating new type variables. @@ -4608,7 +4606,7 @@ The declaration of an interface implementation consists of: - an optional deduced parameter list in square brackets `[`...`]`, - a type, including an optional parameter pattern, - the keyword `as`, and -- a [type-of-type](#type-of-types), including an optional +- a [facet type](#facet-types), including an optional [parameter pattern](#parameterized-interfaces) and [`where` clause](#where-constraints) assigning [associated constants](#associated-constants) and @@ -5485,8 +5483,8 @@ the unparameterized impl when there is an exact match. To reduce the boilerplate needed to support these implicit conversions when defining operator overloads, Carbon has the `like` operator. This operator can -only be used in the type or type-of-type part of an `impl` declaration, as part -of a forward declaration or definition, in a place of a type. +only be used in the type or facet type part of an `impl` declaration, as part of +a forward declaration or definition, in a place of a type. ``` // Notice `f64` has been replaced by `like f64` @@ -5695,9 +5693,9 @@ be [implied constraints](#implied-constraints) on the function's parameters. ### Specialization -[Specialization](terminology.md#generic-specialization) is used to improve -performance in specific cases when a general strategy would be inefficient. For -example, you might use +[Specialization](terminology.md#checked-generic-specialization) is used to +improve performance in specific cases when a general strategy would be +inefficient. For example, you might use [binary search](https://en.wikipedia.org/wiki/Binary_search_algorithm) for containers that support random access and keep their contents in sorted order but [linear search](https://en.wikipedia.org/wiki/Linear_search) in other cases. @@ -5893,7 +5891,7 @@ arguments. ### Range constraints on generic integers -We currently only support `where` clauses on type-of-types. We may want to also +We currently only support `where` clauses on facet types. We may want to also support constraints on generic integers. The constraint with the most expected value is the ability to do comparisons like `<`, or `>=`. For example, you might constrain the `N` member of [`NSpacePoint`](#associated-constants) using an diff --git a/docs/design/generics/goals.md b/docs/design/generics/goals.md index 6aab5982d1f25..366c40c6e7b87 100644 --- a/docs/design/generics/goals.md +++ b/docs/design/generics/goals.md @@ -54,8 +54,8 @@ forward-looking. ## Background Carbon will support -[generics](terminology.md#generic-versus-template-parameters) to support generic -programming by way of +[checked generics](terminology.md#checked-versus-template-parameters) to support +generic programming by way of [parameterization of language constructs](terminology.md#parameterized-language-constructs) with [early type checking](terminology.md#early-versus-late-type-checking) and [complete definition checking](terminology.md#complete-definition-checking). diff --git a/docs/design/generics/overview.md b/docs/design/generics/overview.md index b703bf9b3be81..9785fda1c01bc 100644 --- a/docs/design/generics/overview.md +++ b/docs/design/generics/overview.md @@ -21,7 +21,7 @@ pointers to other design documents that dive deeper into individual topics. - [Contrast with templates](#contrast-with-templates) - [Implementing interfaces](#implementing-interfaces) - [Accessing members of interfaces](#accessing-members-of-interfaces) - - [Type-of-types](#type-of-types) + - [Facet types](#facet-types) - [Generic functions](#generic-functions) - [Deduced parameters](#deduced-parameters) - [Generic type parameters](#generic-type-parameters) @@ -71,11 +71,11 @@ Summary of how Carbon generics work: external, in which case the implementation is allowed to be defined in the library defining the interface. - Interfaces are used as the type of a generic type parameter, acting as a - _type-of-type_. Type-of-types in general specify the capabilities and + _facet type_. Facet types in general specify the capabilities and requirements of the type. Types define specific implementations of those capabilities. Inside such a generic function, the API of the type is [erased](terminology.md#type-erasure), except for the names defined in the - type-of-type. + facet type. - _Deduced parameters_ are parameters whose values are determined by the values and (most commonly) the types of the explicit arguments. Generic type parameters are typically deduced. @@ -85,9 +85,9 @@ Summary of how Carbon generics work: - Interfaces can require other interfaces be implemented. - Interfaces can [extend](terminology.md#extending-an-interface) required interfaces. -- The `&` operation on type-of-types allows you conveniently combine - interfaces. It gives you all the names that don't conflict. -- You may also declare a new type-of-type directly using +- The `&` operation on facet types allows you conveniently combine interfaces. + It gives you all the names that don't conflict. +- You may also declare a new facet type directly using ["named constraints"](terminology.md#named-constraints). Named constraints can express requirements that multiple interfaces be implemented, and give you control over how name conflicts are handled. @@ -180,7 +180,7 @@ definition is required after seeing the call sites once all the [instantiations](terminology.md#instantiation) are known. Note: [Generics terminology](terminology.md) goes into more detail about the -[differences between generics and templates](terminology.md#generic-versus-template-parameters). +[differences between checked and template generic parameters](terminology.md#checked-versus-template-parameters). ### Implementing interfaces @@ -250,7 +250,7 @@ song.(Comparable.Less)(song); song.(Printable.Print)(); ``` -### Type-of-types +### Facet types To type check a function, the compiler needs to be able to verify that uses of a value match the capabilities of the value's type. In `SortVector`, the parameter @@ -259,19 +259,19 @@ specific type value assigned to `T` is not known when type checking the `SortVector` function. Instead it is the constraints on `T` that let the compiler know what operations may be performed on values of type `T`. Those constraints are represented by the type of `T`, a -[**_type-of-type_**](terminology.md#type-of-type). +[**_facet type_**](terminology.md#facet-type). -In general, a type-of-type describes the capabilities of a type, while a type +In general, a facet type describes the capabilities of a type, while a type defines specific implementations of those capabilities. An interface, like -`Comparable`, may be used as a type-of-type. In that case, the constraint on the +`Comparable`, may be used as a facet type. In that case, the constraint on the type is that it must implement the interface `Comparable`. -A type-of-type also defines a set of names and a mapping to corresponding +A facet type also defines a set of names and a mapping to corresponding qualified names. Those names are used for [simple member lookup](terminology.md#simple-member-access) in scopes where the value of the type is not known, such as when the type is a generic parameter. -You may combine interfaces into new type-of-types using +You may combine interfaces into new facet types using [the `&` operator](#combining-interfaces) or [named constraints](#named-constraints). @@ -332,10 +332,10 @@ fn PrintIt(p: Song*) { Inside the function body, you can treat the generic type parameter just like any other type. There is no need to refer to or access generic parameters differently because they are defined as generic, as long as you only refer to -the names defined by [type-of-type](#type-of-types) for the type parameter. +the names defined by [facet type](#facet-types) for the type parameter. -You may also refer to any of the methods of interfaces required by the -type-of-type using a +You may also refer to any of the methods of interfaces required by the facet +type using a [qualified member access expression](#accessing-members-of-interfaces), as shown in the following sections. @@ -398,8 +398,8 @@ k.IsEqual(k); ### Combining interfaces -The `&` operation on type-of-types allows you conveniently combine interfaces. -It gives you all the names that don't conflict. +The `&` operation on facet types allows you conveniently combine interfaces. It +gives you all the names that don't conflict. ``` interface Renderable { @@ -431,7 +431,7 @@ fn BothDraws[T:! Renderable & EndOfGame](game_state: T*) { #### Named constraints -You may also declare a new type-of-type directly using +You may also declare a new facet type directly using ["named constraints"](terminology.md#named-constraints). Named constraints can express requirements that multiple interfaces be implemented, and give you control over how name conflicts are handled. Named constraints have other @@ -462,11 +462,10 @@ fn CallItAll[T:! Combined](game_state: T*, int winner) { #### Type erasure Inside a generic function, the API of a type argument is -[erased](terminology.md#type-erasure) except for the names defined in the -type-of-type. An equivalent model is to say an -[archetype](terminology.md#archetype) is used for type checking and name lookup -when the actual type is not known in that scope. The archetype has members -dictated by the type-of-type. +[erased](terminology.md#type-erasure) except for the names defined in the facet +type. An equivalent model is to say an [archetype](terminology.md#archetype) is +used for type checking and name lookup when the actual type is not known in that +scope. The archetype has members dictated by the facet type. For example: If there were a class `CDCover` defined this way: @@ -587,7 +586,7 @@ fn CompileError[T:! type, U:! Equatable(T)](x: U) -> T; ### Constraints -Type-of-types can be further constrained using a `where` clause: +Facet types can be further constrained using a `where` clause: ``` fn FindFirstPrime[T:! Container where .Element == i32] diff --git a/docs/design/generics/terminology.md b/docs/design/generics/terminology.md index d7d4fe40e74b7..acc9b4e42ce15 100644 --- a/docs/design/generics/terminology.md +++ b/docs/design/generics/terminology.md @@ -11,7 +11,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## Table of contents - [Parameterized language constructs](#parameterized-language-constructs) -- [Generic versus template parameters](#generic-versus-template-parameters) +- [Checked versus template parameters](#checked-versus-template-parameters) - [Polymorphism](#polymorphism) - [Parametric polymorphism](#parametric-polymorphism) - [Compile-time duck typing](#compile-time-duck-typing) @@ -46,18 +46,18 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Instantiation](#instantiation) - [Specialization](#specialization) - [Template specialization](#template-specialization) - - [Generic specialization](#generic-specialization) + - [Checked generic specialization](#checked-generic-specialization) - [Conditional conformance](#conditional-conformance) - [Interface type parameters and associated types](#interface-type-parameters-and-associated-types) - [Type constraints](#type-constraints) -- [Type-of-type](#type-of-type) +- [Facet type](#facet-type) - [References](#references) ## Parameterized language constructs -Generally speaking, when we talk about either templates or a generics system, we +Generally speaking, when we talk about generics, either checked or template, we are talking about generalizing some language construct by adding a parameter to it. Language constructs here primarily would include functions and types, but we may want to support parameterizing other language constructs like @@ -67,29 +67,29 @@ This parameter broadens the scope of the language construct on an axis defined by that parameter, for example it could define a family of functions instead of a single one. -## Generic versus template parameters +## Checked versus template parameters -When we are distinguishing between generics and templates in Carbon, it is on a +When we distinguish between checked and template generics in Carbon, it is on a parameter by parameter basis. A single function can take a mix of regular, -generic, and template parameters. +checked, and template parameters. - **Regular parameters**, or "dynamic parameters", are designated using the "\`:` \" syntax (or "\"). -- **Generic parameters** are designated using `:!` between the name and the +- **Checked parameters** are designated using `:!` between the name and the type (so it is "\`:!` \"). - **Template parameters** are designated using "`template` \`:!` \". -The syntax for generic and template parameters was decided in +The syntax for checked and template parameters was decided in [questions-for-leads issue #565](https://github.com/carbon-language/carbon-lang/issues/565). -Expected difference between generics and templates: +Expected difference between checked and template parameters: - - @@ -107,7 +107,7 @@ Expected difference between generics and templates: - @@ -138,7 +138,7 @@ Expected difference between generics and templates: ### Polymorphism -Generics and templates provide different forms of +Generics provide different forms of [polymorphism]() than object-oriented programming with inheritance. That uses [subtype polymorphism](https://en.wikipedia.org/wiki/Subtyping) where different @@ -186,19 +186,19 @@ Templates work with ad-hoc polymorphism in two ways: will only resolve that call after the types are known. In Carbon, we expect there to be a compile error if overloading of some name -prevents a generic function from being typechecked from its definition alone. -For example, let's say we have some overloaded function called `F` that has two -overloads: +prevents a checked generic function from being typechecked from its definition +alone. For example, let's say we have some overloaded function called `F` that +has two overloads: ``` fn F[template T:! type](x: T*) -> T; fn F(x: Int) -> bool; ``` -A generic function `G` can call `F` with a type like `T*` that cannot possibly -call the `F(Int)` overload for `F`, and so it can consistently determine the -return type of `F`. But `G` can't call `F` with an argument that could match -either overload. +A checked generic function `G` can call `F` with a type like `T*` that cannot +possibly call the `F(Int)` overload for `F`, and so it can consistently +determine the return type of `F`. But `G` can't call `F` with an argument that +could match either overload. **Note:** It is undecided what to do in the situation where `F` is overloaded, but the signatures are consistent and so callers could still typecheck calls to @@ -220,15 +220,15 @@ they could be removed and the function would still have the same capabilities. Constraints only affect the caller, which will use them to resolve overloaded calls to the template and provide clearer error messages. -With generics using constrained genericity, the function body can be checked -against the signature at the time of definition. Note that it is still perfectly -permissible to have no constraints on a type; that just means that you can only -perform operations that work for all types (such as manipulate pointers to -values of that type) in the body of the function. +With checked generics using constrained genericity, the function body can be +checked against the signature at the time of definition. Note that it is still +perfectly permissible to have no constraints on a type; that just means that you +can only perform operations that work for all types (such as manipulate pointers +to values of that type) in the body of the function. ### Dependent names -A name is said to be _dependent_ if it depends on some generic or template +A name is said to be _dependent_ if it depends on some checked or template parameter. Note: this matches [the use of the term "dependent" in C++](https://www.google.com/search?q=c%2B%2B+dependent+name), not as in [dependent types](https://en.wikipedia.org/wiki/Dependent_type). @@ -240,7 +240,7 @@ parameterized code for correctness _independently_ of any particular arguments. It includes type checking and other semantic checks. It is possible, even with templates, to check semantics of expressions that are not [dependent](#dependent-names) on any template parameter in the definition. -Adding constraints to template parameters and/or switching them to be generic +Adding constraints to template parameters and/or switching them to be checked allows the compiler to increase how much of the definition can be checked. Any remaining checks are delayed until [instantiation](#instantiation), which can fail. @@ -258,7 +258,7 @@ instantiate the implementation (for example, [type erasure](#type-erasure) or Early type checking is where expressions and statements are type checked when the definition of the function body is compiled, as part of definition checking. -This occurs for regular and generic values. +This occurs for regular and checked generic values. Late type checking is where expressions and statements may only be fully typechecked once calling information is known. Late type checking delays @@ -441,11 +441,10 @@ function signatures can change from base class to derived class, see [covariance and contravariance in Wikipedia](). In a generics context, we are specifically interested in the subtyping -relationships between [type-of-types](#type-of-type). In particular, a -type-of-type encompasses a set of [type constraints](#type-constraints), and you -can convert a type from a more-restrictive type-of-type to another type-of-type -whose constraints are implied by the first. C++ concepts terminology uses the -term +relationships between [facet types](#facet-type). In particular, a facet type +encompasses a set of [type constraints](#type-constraints), and you can convert +a type from a more-restrictive facet type to another facet type whose +constraints are implied by the first. C++ concepts terminology uses the term ["subsumes"](https://en.cppreference.com/w/cpp/language/constraints#Partial_ordering_of_constraints) to talk about this partial ordering of constraints, but we avoid that term since it is at odds with the use of the term in @@ -614,13 +613,13 @@ might have a specialization `std::vector` that is implemented in terms of templated type can be changed in a specialization, as happens for `std::vector`. -### Generic specialization +### Checked generic specialization -Specialization of generics, or types used by generics, is restricted to changing -the implementation _without_ affecting the interface. This restriction is needed -to preserve the ability to perform type checking of generic definitions that -reference a type that can be specialized, without statically knowing which -specialization will be used. +Specialization of checked generics, or types used by checked generics, is +restricted to changing the implementation _without_ affecting the interface. +This restriction is needed to preserve the ability to perform type checking of +generic definitions that reference a type that can be specialized, without +statically knowing which specialization will be used. While there is nothing fundamentally incompatible about specialization with generics, even when implemented using witness tables, the result may be @@ -759,12 +758,12 @@ express, for example: Note that type constraints can be a restriction on one type parameter or associated type, or can define a relationship between multiple types. -## Type-of-type +## Facet type -A type-of-type is the type used when declaring some type parameter. It foremost +A facet type is the type used when declaring some type parameter. It foremost determines which types are legal arguments for that type parameter, also known as [type constraints](#type-constraints). For template parameters, that is all a -type-of-type does. For generic parameters, it also determines the API that is +facet type does. For generic parameters, it also determines the API that is available in the body of the function. ## References diff --git a/docs/project/faq.md b/docs/project/faq.md index 1fd462fbfb4e2..57fb650c8769f 100644 --- a/docs/project/faq.md +++ b/docs/project/faq.md @@ -461,7 +461,7 @@ migration of C++ code. References: - [Generics: Goals: Better compiler experience](/docs/design/generics/goals.md#better-compiler-experience) -- [Generics: Terminology: Generic versus template parameters](/docs/design/generics/terminology.md#generic-versus-template-parameters) +- [Generics: Terminology: Checked versus template parameters](/docs/design/generics/terminology.md#checked-versus-template-parameters) ### What is Carbon's memory model? From 79534cd10df157ae13e81621080386be515c9408 Mon Sep 17 00:00:00 2001 From: Josh L Date: Mon, 10 Jul 2023 22:55:59 +0000 Subject: [PATCH 02/83] Checkpoint progress. --- docs/design/generics/terminology.md | 108 +++++++++++++++++++++------- 1 file changed, 82 insertions(+), 26 deletions(-) diff --git a/docs/design/generics/terminology.md b/docs/design/generics/terminology.md index acc9b4e42ce15..d2319f6d7a50a 100644 --- a/docs/design/generics/terminology.md +++ b/docs/design/generics/terminology.md @@ -21,6 +21,11 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Definition checking](#definition-checking) - [Complete definition checking](#complete-definition-checking) - [Early versus late type checking](#early-versus-late-type-checking) +- [Types and `type`](#types-and-type) +- [Facet type](#facet-type) +- [Facet](#facet) +- [Generic type](#generic-type) +- [Generic type parameter](#generic-type-parameter) - [Deduced parameter](#deduced-parameter) - [Interface](#interface) - [Structural interfaces](#structural-interfaces) @@ -50,7 +55,6 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Conditional conformance](#conditional-conformance) - [Interface type parameters and associated types](#interface-type-parameters-and-associated-types) - [Type constraints](#type-constraints) -- [Facet type](#facet-type) - [References](#references) @@ -258,13 +262,71 @@ instantiate the implementation (for example, [type erasure](#type-erasure) or Early type checking is where expressions and statements are type checked when the definition of the function body is compiled, as part of definition checking. -This occurs for regular and checked generic values. +This occurs for regular and checked-generic values. Late type checking is where expressions and statements may only be fully typechecked once calling information is known. Late type checking delays complete definition checking. This occurs for [template-dependent](#dependent-names) values. +## Types and `type` + +A _type_ is a value of type `type`. Conversely, `type` is the type of all types. + +Expressions in type position, for example the return type of a function or the +expression to the right of a `:` or `:!` in a binding, are implicitly cast to +type `type`. This means that it is legal to put a value that is not a type where +a type is expected, as long as it has an implicit conversion to `type` that may +be performed at compile time. + +## Facet type + +A _facet type_ is a [type](#types-and-type) whose values are some subset of the +values of `type`, determined by a set of [type constraints](#type-constraints): + +- [Interfaces](#interface) and [named constraints](#named-constraints) are + facet types whose constraints are that the interface or named constraint is + satisfied by the type. +- The values produced by `&` operations between facet types and by `where` + expressions are facet types, whose set of constraints are determined by the + `&` or `where` expression. +- `type` is a facet type whose set of constraints is empty. + +A facet type is the type used when declaring some type parameter. It foremost +determines which types are legal arguments for that type parameter. For template +parameters, that is all a facet type does. For checked parameters, it also +determines the API that is available in the body of the definition of the +[generic function, class, or other entity](#parameterized-language-constructs). + +## Facet + +A _facet_ is a value of a [facet type](#facet-type). For example, +`i32 as Hashable` is a facet, and `Hashable` is a facet type. Note that all +types are facets, since [`type`](#types-and-type) is considered a facet type. +Not all facets are types, though. + +## Generic type + +We use the term _generic type_ to refer to a [type](#types-and-type) or +[facet](#facet) introduced by a `:!` binding (with or without the `template` +modifier), such as in a (checked or template) generic parameter or associated +constant. In the binding `T:! Hashable`, `T` is a generic type. + +It is worth noting that a generic type is _not_ necessarily a type. It may +instead be a facet with a type that is a facet type other than `type`. + +## Generic type parameter + +A generic type parameter is a [parameter](#parameterized-language-constructs) +that is a [generic type](#generic-type). Equivalently, it is a parameter +declared using a `:!` binding, with or without the `template` modifier, and a +[facet type](#facet-type). For example, in `class HashSet(T:! Hashable)`, `T` is +a generic type parameter for the class `HashSet`, with the facet type +`Hashable`. + +A generic type parameter declared with the `template` modifier is referred to as +a _template type parameter_, and as a _checked type parameter_ otherwise. + ## Deduced parameter A deduced parameter is listed in the optional `[` `]` section right after the @@ -331,10 +393,12 @@ type must implement and constraints on the ## Associated entity An _associated entity_ is a requirement in an interface that a type's -implementation of the interface must satisfy by having a matching member. A +implementation of the interface must satisfy by having a matching definition. A requirement that the type define a value for a member constant is called an _associated constant_, and similarly an _associated function_ or _associated -type_. +[generic type](#generic-type)_. Note that other languages use the term +"associated type" instead of "associated generic type," but in Carbon a generic +type is a [facet](#facet) may not be a type. Different types can satisfy an interface with different definitions for a given member. These definitions are _associated_ with what type is implementing the @@ -469,8 +533,8 @@ permitted, always has the same meaning as an explicit cast. ## Coherence -A generics system has the _implementation coherence_ property, or simply -_coherence_, if there is a single answer to the question "what is the +A generics or interface system has the _implementation coherence_ property, or +simply _coherence_, if there is a single answer to the question "what is the implementation of this interface for this type, if any?" independent of context, such as the libraries imported into a given file. @@ -512,9 +576,9 @@ and as a workaround for "Type erasure" is where a type's API is replaced by a subset. Everything outside of the preserved subset is said to have been "erased". This can happen in a -variety of contexts including both generics and runtime polymorphism. For -generics, type erasure restricts a type to just the API required by the -constraints on a generic function. +variety of contexts including both checked generics and runtime polymorphism. +For checked generics, type erasure restricts a type to just the API required by +the constraints on that type stated in the signature of the function. An example of type erasure in runtime polymorphism in C++ is casting from a pointer of a derived type to a pointer to an abstract base type. Only the API of @@ -548,14 +612,14 @@ interface. ## Witness tables [Witness tables](https://forums.swift.org/t/where-does-the-term-witness-table-come-from/54334/4) -are an implementation strategy where values passed to a generic parameter are -compiled into a table of required functionality. That table is then filled in -for a given passed-in type with references to the implementation on the original -type. The generic is implemented using calls into entries in the witness table, -which turn into calls to the original type. This doesn't necessarily imply a -runtime indirection: it may be a purely compile-time separation of concerns. -However, it insists on a full abstraction boundary between the generic user of a -type and the concrete implementation. +are an implementation strategy where values passed to a generic type parameter +are compiled into a table of required functionality. That table is then filled +in for a given passed-in type with references to the implementation on the +original type. The generic is implemented using calls into entries in the +witness table, which turn into calls to the original type. This doesn't +necessarily imply a runtime indirection: it may be a purely compile-time +separation of concerns. However, it insists on a full abstraction boundary +between the generic user of a type and the concrete implementation. A simple way to imagine a witness table is as a struct of function pointers, one per method in the interface. However, in practice, it's more complex because it @@ -737,7 +801,7 @@ DoAdd(apple, orange); ## Type constraints -Type constraints restrict which types are legal for template or generic +Type constraints restrict which types are legal for template or checked parameters or associated types. They help define semantics under which they should be called, and prevent incorrect calls. @@ -758,14 +822,6 @@ express, for example: Note that type constraints can be a restriction on one type parameter or associated type, or can define a relationship between multiple types. -## Facet type - -A facet type is the type used when declaring some type parameter. It foremost -determines which types are legal arguments for that type parameter, also known -as [type constraints](#type-constraints). For template parameters, that is all a -facet type does. For generic parameters, it also determines the API that is -available in the body of the function. - ## References - [#447: Generics terminology](https://github.com/carbon-language/carbon-lang/pull/447) From 36dccdee7f5e29ba316edaed839948a79b44d2ee Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 11 Jul 2023 19:37:35 +0000 Subject: [PATCH 03/83] Checkpoint progress. --- docs/design/generics/details.md | 55 ++++++++++++++--------------- docs/design/generics/terminology.md | 39 +++++++++----------- 2 files changed, 44 insertions(+), 50 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 6c98cc2d185c1..9f8e3afcc88ac 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -201,10 +201,9 @@ needed. For more on this, see When the implementation of `ConvertibleToString` for `Song` is defined as internal, every member of `ConvertibleToString` is also a member of `Song`. This includes members of `ConvertibleToString` that are not explicitly named in the -`impl` definition but have defaults. Whether the implementation is defined as -[internal](terminology.md#internal-impl) or -[external](terminology.md#external-impl), you may access the `ToString` function -for a `Song` value `s` by a writing function call +`impl` definition but have defaults. Whether the type +[extends the implementation](terminology.md#extending-an-impl) or not, you may +access the `ToString` function for a `Song` value `s` by a writing function call [using a qualified member access expression](terminology.md#qualified-member-access-expression), like `s.(ConvertibleToString.ToString)()`. @@ -358,9 +357,8 @@ class Player { ### External impl -Interfaces may also be implemented for a type -[externally](terminology.md#external-impl), by using `impl` without `extend`. An -external impl does not add the interface's methods to the type. +Interfaces may also be implemented for a type without adding the interface's +methods to the type by using `impl` without `extend`. ``` class Point2 { @@ -628,13 +626,12 @@ acts like a [supertype](https://en.wikipedia.org/wiki/Subtyping) of any `T` implementing `Vector`. For name lookup purposes, an archetype is considered to have -[implemented its constraint internally](terminology.md#internal-impl). The only -oddity is that the archetype may have different names for members than specific -types `T` that implement interfaces from the constraint -[externally](terminology.md#external-impl). This difference in names can also -occur for supertypes in C++, for example members in a derived class can hide -members in the base class with the same name, though it is not that common for -it to come up in practice. +[extend the implementation of its constraint](terminology.md#extending-an-impl). +The only oddity is that the archetype may have different names for members than +specific types `T` that don't extend the implementation of interfaces from the +constraint. This difference in names can also occur for supertypes in C++, for +example members in a derived class can hide members in the base class with the +same name, though it is not that common for it to come up in practice. The behavior of calling `AddAndScaleGeneric` with a value of a specific type like `Point` is to set `T` to `Point` after all the names have been qualified. @@ -647,9 +644,9 @@ fn AddAndScaleForPoint(a: Point, b: Point, s: Double) -> Point { ``` This qualification gives a consistent interpretation to the body of the function -even when the type supplied by the caller -[implements the interface externally](terminology.md#external-impl), as `Point2` -does: +even when the type supplied by the caller does not +[extend the implementation of the interface](terminology.md#extending-an-impl), +like `Point2`: ``` // AddAndScaleGeneric with T = Point2 @@ -1909,7 +1906,8 @@ fn Complex64.CloserToOrigin[self: Self](them: Self) -> bool { ### Use case: Accessing external names Consider a case where a function will call several functions from an interface -that is [implemented externally](terminology.md#external-impl) for a type. +that the type does not +[extend the implementation of](terminology.md#extending-an-impl). ``` interface DrawingContext { @@ -1922,10 +1920,10 @@ interface DrawingContext { impl Window as DrawingContext { ... } ``` -An adapter can make that much more convenient by making a compatible type where -the interface is [implemented internally](terminology.md#internal-impl). This -avoids having to [qualify](terminology.md#qualified-member-access-expression) -each call to methods in the interface. +An adapter can make that more convenient by making a compatible type that does +extends the implementation of the interface. This avoids having to +[qualify](terminology.md#qualified-member-access-expression) each call to +methods in the interface. ``` class DrawInWindow { @@ -3164,8 +3162,9 @@ fn F[T:! Transitive](t: T) { A value of type `A`, such as the return value of `GetA()`, has the API of `P`. Any such value also implements `Q`, and since the compiler can see that by way of a single `where` equality, values of type `A` are treated as if they -implement `Q` [externally](terminology.md#external-impl). However, the compiler -will require a cast to `B` or `C` to see that the type implements `R`. +implement `Q` [without extending it](terminology.md#extending-an-impl). However, +the compiler will require a cast to `B` or `C` to see that the type implements +`R`. ``` fn TakesPQR[U:! P & Q & R](u: U); @@ -3621,10 +3620,10 @@ fn SortByAddress[T:! type](v: Vector(T*)*) { ... } In particular, the compiler should in general avoid monomorphizing to generate multiple instantiations of the function in this case. -**Open question:** Should `TypeId` be -[implemented externally](terminology.md#external-impl) for types to avoid name -pollution (`.TypeName`, `.TypeHash`, etc.) unless the function specifically -requests those capabilities? +**Open question:** We have not yet decided whether types should +[extend their implementation](terminology.md#extending-an-impl) of `TypeId`. The +reason not to is to avoid name pollution (`.TypeName`, `.TypeHash`, etc.) unless +the function specifically requests those capabilities. ### Destructor constraints diff --git a/docs/design/generics/terminology.md b/docs/design/generics/terminology.md index d2319f6d7a50a..60098b5101b5a 100644 --- a/docs/design/generics/terminology.md +++ b/docs/design/generics/terminology.md @@ -33,8 +33,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Named constraints](#named-constraints) - [Associated entity](#associated-entity) - [Impl: Implementation of an interface](#impl-implementation-of-an-interface) - - [Internal impl](#internal-impl) - - [External impl](#external-impl) + - [Extending an impl](#extending-an-impl) - [Member access](#member-access) - [Simple member access](#simple-member-access) - [Qualified member access expression](#qualified-member-access-expression) @@ -190,7 +189,7 @@ Templates work with ad-hoc polymorphism in two ways: will only resolve that call after the types are known. In Carbon, we expect there to be a compile error if overloading of some name -prevents a checked generic function from being typechecked from its definition +prevents a checked-generic function from being typechecked from its definition alone. For example, let's say we have some overloaded function called `F` that has two overloads: @@ -420,20 +419,16 @@ values for associated types, etc. are given. Implementations are needed for by requiring an impl to be defined. In can still make sense to implement a named constraint as a way to implement all of the interfaces it requires. -### Internal impl +### Extending an impl -A type that implements an interface _internally_ has all the named members of -the interface as named members of the type. This means that the members of the -interface are available by way of both +A type that _extends_ the implementation an interface has all the named members +of the interface as named members of the type. This means that the members of +the interface are available by way of both [simple member access and qualified member access expressions](#member-access). -### External impl - -In contrast, a type that implements an interface _externally_ does not include -the named members of the interface in the type. The members of the interface are -still implemented by the type, though, and so may be accessed using -[qualified member access expressions](#qualified-member-access-expression) for -those members. +If a type implements an interface without extending, the members of the +interface may only be accessed using +[qualified member access expressions](#qualified-member-access-expression). ## Member access @@ -446,12 +441,13 @@ qualified names, which we call a _qualified member access expression_. Simple member access has the from `object.member`, where `member` is a word naming a member of `object`. This form may be used to access members of -interfaces [implemented internally](#internal-impl) by the type of `object`. +interfaces when the type of `object` +[extends the implementation](#extending-an-impl) of that interface. -If `String` implements `Printable` internally, then `s1.Print()` calls the -`Print` method of `Printable` using simple member access. In this case, the name -`Print` is used without qualifying it with the name of the interface it is a -member of since it is recognized as a member of the type itself as well. +If `String` extends its implementation of `Printable`, then `s1.Print()` calls +the `Print` method of `Printable` using simple member access. In this case, the +name `Print` is used without qualifying it with the name of the interface it is +a member of since it is recognized as a member of the type itself as well. ### Qualified member access expression @@ -470,8 +466,7 @@ method may be called using the qualified member name by writing the qualified member access expression `s1.(Comparable.Less)(s2)`. This form may be used to access any member of an interface implemented for a -type, whether it is implemented [internally](#internal-impl) or -[externally](#external-impl). +type, whether or not it [extends the implementation](#extending-an-impl). ## Compatible types @@ -600,7 +595,7 @@ the archetype is the supertype of all types satisfying the interface. In addition to satisfying all the requirements of its constraint, the archetype also has the member names of its constraint. Effectively it is considered to -[implement the constraint internally](#internal-impl). +[extend the implementation of the constraint](#extending-an-impl). ## Extending an interface From afc6b8cb1f58699d0dd47fd02d65fe888b2f000a Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 11 Jul 2023 23:23:52 +0000 Subject: [PATCH 04/83] Checkpoint progress. --- docs/design/generics/details.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 9f8e3afcc88ac..0c0b3f051bf62 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -507,7 +507,7 @@ though: - It makes reading code harder, since you have to search the file for these declarations that affect name lookup. -**Comparison with other languages:** Both Rust and Swift support external +**Comparison with other languages:** Both Rust and Swift support out-of-line implementation. [Swift's syntax](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html#ID277) does this as an "extension" of the original type. In Rust, all implementations From 51ebdb58de00bfe13da3cc83390ae66aba66bc21 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 12 Jul 2023 05:05:57 +0000 Subject: [PATCH 05/83] Checkpoint progress. --- docs/design/generics/terminology.md | 54 ++++++++++++++--------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/docs/design/generics/terminology.md b/docs/design/generics/terminology.md index 60098b5101b5a..b3d8a7ca803cc 100644 --- a/docs/design/generics/terminology.md +++ b/docs/design/generics/terminology.md @@ -239,9 +239,9 @@ not as in [dependent types](https://en.wikipedia.org/wiki/Dependent_type). ### Definition checking Definition checking is the process of semantically checking the definition of -parameterized code for correctness _independently_ of any particular arguments. -It includes type checking and other semantic checks. It is possible, even with -templates, to check semantics of expressions that are not +parameterized code for correctness _independently_ of any particular argument +values. It includes type checking and other semantic checks. It is possible, +even with templates, to check semantics of expressions that are not [dependent](#dependent-names) on any template parameter in the definition. Adding constraints to template parameters and/or switching them to be checked allows the compiler to increase how much of the definition can be checked. Any @@ -253,8 +253,9 @@ fail. Complete definition checking is when the definition can be _fully_ semantically checked, including type checking. It is an especially useful property because it enables _separate_ semantic checking of the definition, a prerequisite to -separate compilation. It also enables implementation strategies that don’t -instantiate the implementation (for example, [type erasure](#type-erasure) or +separate compilation. It also is a requirement for implementation strategies +that don’t instantiate the implementation (for example, +[type erasure](#type-erasure) or [dynamic-dispatch witness tables](#dynamic-dispatch-witness-table)). #### Early versus late type checking @@ -336,14 +337,6 @@ function name in a function signature: Deduced arguments are determined as a result of pattern matching the explicit argument values (usually the types of those values) to the explicit parameters. -Note that function signatures can typically be rewritten to avoid using deduced -parameters: - -``` -fn F[template T:! type](value: T); -// is equivalent to: -fn F(value: (template T:! type)); -``` See more [here](overview.md#deduced-parameters). @@ -355,7 +348,8 @@ to know about the interface requirements to call the function, not anything about the implementation of the function body, and the compiler can check the function body without knowing anything more about the caller. Callers of the function provide a value that has an implementation of the API and the body of -the function may then use that API (and nothing else). +the function may then use that API. In the case of a checked generic, the +function may _only_ use that API. ### Structural interfaces @@ -416,8 +410,9 @@ values for associated types, etc. are given. Implementations are needed for [nominal interfaces](#nominal-interfaces); [structural interfaces](#structural-interfaces) and [named constraints](#named-constraints) define conformance implicitly instead of -by requiring an impl to be defined. In can still make sense to implement a named -constraint as a way to implement all of the interfaces it requires. +by requiring an impl to be defined. In can still make sense to explicitly +implement a named constraint as a way to implement all of the interfaces it +requires. ### Extending an impl @@ -533,18 +528,21 @@ simply _coherence_, if there is a single answer to the question "what is the implementation of this interface for this type, if any?" independent of context, such as the libraries imported into a given file. -This is typically enforced by making sure the definition of the implementation -must be imported if you import both the interface and the type. This may be done -by requiring the implementation to be in the same library as the interface or -type. This is called an _orphan rule_, meaning we don't allow an implementation -that is not with either of its parents (parent type or parent interface). - -Note that in addition to an orphan rule that implementations are visible when -queried, coherence also requires a rule for resolving what happens if there are -multiple non-orphan implementations. In Rust, this is called the -[overlap rule or overlap check](https://rust-lang.github.io/chalk/book/clauses/coherence.html#chalk-overlap-check). -This could be just producing an error in that situation, or picking one using -some specialization rule. +This is enforced using two kinds of rules: + +- An _orphan rule_ is a restriction on which files may declare a particular + implementation. This is to ensure that the implementation is imported any + time it could be used. For example, if neither the type nor the interface is + parameterized, the orphan rule requires that the implementation must be in + the same library as the interface or type. The rule is we don't allow an + _orphan_ implementation that is not with either of its parents (parent type + or parent interface). +- An _overlap rule_ is a way to _consistently_ select a single implementation + when multiple implementations apply. In Carbon, overlap is resolved by + picking a single implementation using a rule that picks the one that is + considered most specialized. In Rust, by contrast, the + [overlap rule or overlap check](https://rust-lang.github.io/chalk/book/clauses/coherence.html#chalk-overlap-check) + instead produces an error if two implementations apply at once. ## Adapting a type From 0377c361359feb2d739d3e7a41aed8c25d6784b0 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 12 Jul 2023 22:37:27 +0000 Subject: [PATCH 06/83] finish a pass on terminology --- docs/design/generics/terminology.md | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/docs/design/generics/terminology.md b/docs/design/generics/terminology.md index b3d8a7ca803cc..1532d3cae84e1 100644 --- a/docs/design/generics/terminology.md +++ b/docs/design/generics/terminology.md @@ -50,7 +50,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Instantiation](#instantiation) - [Specialization](#specialization) - [Template specialization](#template-specialization) - - [Checked generic specialization](#checked-generic-specialization) + - [Checked-generic specialization](#checked-generic-specialization) - [Conditional conformance](#conditional-conformance) - [Interface type parameters and associated types](#interface-type-parameters-and-associated-types) - [Type constraints](#type-constraints) @@ -303,7 +303,13 @@ determines the API that is available in the body of the definition of the A _facet_ is a value of a [facet type](#facet-type). For example, `i32 as Hashable` is a facet, and `Hashable` is a facet type. Note that all types are facets, since [`type`](#types-and-type) is considered a facet type. -Not all facets are types, though. +Not all facets are types, though: `i32 as Hashable` is of type `Hashable` not +`type`, so it is a facet that is not a type. However, in places where a type is +expected, for example to the right of a `:` in a binding or after the `->` in a +function declaration, there is an automatic implicit conversion to `type`. This +means that a facet may be used in those positions. For example, the facet +`i32 as Hashable` will implicitly convert to `(i32 as Hashable) as type`, which +is `i32`, in those contexts. ## Generic type @@ -389,9 +395,8 @@ An _associated entity_ is a requirement in an interface that a type's implementation of the interface must satisfy by having a matching definition. A requirement that the type define a value for a member constant is called an _associated constant_, and similarly an _associated function_ or _associated -[generic type](#generic-type)_. Note that other languages use the term -"associated type" instead of "associated generic type," but in Carbon a generic -type is a [facet](#facet) may not be a type. +type_. Note that an associated type will be a [generic type](#generic-type), and +so may not be a type, but a [facet](#facet) usable as a type. Different types can satisfy an interface with different definitions for a given member. These definitions are _associated_ with what type is implementing the @@ -587,7 +592,7 @@ of "type erasure" used in Carbon. A placeholder type is used when type checking a function in place of a generic type parameter. This allows type checking when the specific type to be used is -not known at type checking time. The type satisfies just its constraint and no +not known at type-checking time. The type satisfies just its constraint and no more, so it acts as the most general type satisfying the interface. In this way the archetype is the supertype of all types satisfying the interface. @@ -662,15 +667,16 @@ and other errors may only happen for **some** instantiations. ### Template specialization -Specialization in C++ is essentially overloading in the context of a template. -The template is overloaded to have a different definition for some subset of the +Specialization in C++ is essentially overloading, or +[ad-hoc polymorphism](#ad-hoc-polymorphism), in the context of a template. The +template is overloaded to have a different definition for some subset of the possible template argument values. For example, the C++ type `std::vector` might have a specialization `std::vector` that is implemented in terms of `std::vector` to reduce code size. In C++, even the interface of a templated type can be changed in a specialization, as happens for `std::vector`. -### Checked generic specialization +### Checked-generic specialization Specialization of checked generics, or types used by checked generics, is restricted to changing the implementation _without_ affecting the interface. @@ -679,7 +685,7 @@ generic definitions that reference a type that can be specialized, without statically knowing which specialization will be used. While there is nothing fundamentally incompatible about specialization with -generics, even when implemented using witness tables, the result may be +checked generics, even when implemented using witness tables, the result may be surprising because the selection of the specialized generic happens outside of the witness-table-based indirection between the generic code and the concrete implementation. Provided all selection relies exclusively on interfaces, this @@ -821,3 +827,6 @@ associated type, or can define a relationship between multiple types. - [#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) +- [#2138: Checked and template generic terminology](https://github.com/carbon-language/carbon-lang/pull/2138) +- [#2360: Types are values of type `type`](https://github.com/carbon-language/carbon-lang/pull/2360) +- [#2760: Consistent `class` and `interface` syntax](https://github.com/carbon-language/carbon-lang/pull/2760) From d75f2e6328b2db432f187da18469b3b38b34428e Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 13 Jul 2023 19:28:27 +0000 Subject: [PATCH 07/83] internal/external -> extend --- docs/design/generics/details.md | 446 +++++++++++++++++++------------- 1 file changed, 259 insertions(+), 187 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 0c0b3f051bf62..62a6b5accdb4c 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -13,8 +13,13 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Overview](#overview) - [Interfaces](#interfaces) - [Implementing interfaces](#implementing-interfaces) + - [Inline `impl`](#inline-impl) + - [`extend impl`](#extend-impl) + - [Out-of-line `impl`](#out-of-line-impl) + - [Defining an `impl` in another library than the type](#defining-an-impl-in-another-library-than-the-type) + - [Forward `impl` declaration](#forward-impl-declaration) - [Implementing multiple interfaces](#implementing-multiple-interfaces) - - [External impl](#external-impl) + - [Avoiding name collisions](#avoiding-name-collisions) - [Qualified member names and compound member access](#qualified-member-names-and-compound-member-access) - [Access](#access) - [Generics](#generics) @@ -251,15 +256,17 @@ have different definitions for `Add` and `Scale`, so we say their definitions are _associated_ with what type is implementing `Vector`. The `impl` defines what is associated with the implementing type for that interface. +### Inline `impl` + An impl may be defined inline inside the type definition: ``` -class Point { +class Point_Inline { var x: f64; var y: f64; - extend impl as Vector { + impl as Vector { // In this scope, the `Self` keyword is an - // alias for `Point`. + // alias for `Point_Inline`. fn Add[self: Self](b: Self) -> Self { return {.x = a.x + b.x, .y = a.y + b.y}; } @@ -270,38 +277,178 @@ class Point { } ``` +### `extend impl` + Interfaces that are implemented inline with the `extend` keyword contribute to the type's API: ``` -var p1: Point = {.x = 1.0, .y = 2.0}; -var p2: Point = {.x = 2.0, .y = 4.0}; +class Point_Extend { + var x: f64; + var y: f64; + extend impl as Vector { + fn Add[self: Self](b: Self) -> Self { + return {.x = a.x + b.x, .y = a.y + b.y}; + } + fn Scale[self: Self](v: f64) -> Self { + return {.x = a.x * v, .y = a.y * v}; + } + } +} + +var p1: Point_Extend = {.x = 1.0, .y = 2.0}; +var p2: Point_Extend = {.x = 2.0, .y = 4.0}; Assert(p1.Scale(2.0) == p2); Assert(p1.Add(p1) == p2); ``` -**Note:** A type may implement any number of different interfaces, but may -provide at most one implementation of any single interface. This makes the act -of selecting an implementation of an interface for a type unambiguous throughout -the whole program. +Without `extend`, those methods may only be accessed with +[qualified member names and compound member access](#qualified-member-names-and-compound-member-access): + +``` +// Point_Inline did not use `extend` when +// implementing `Vector`: +var a: Point_Inline = {.x = 1.0, .y = 2.0}; +// `a` does *not* have `Add` and `Scale` methods: +// ❌ Error: a.Add(a.Scale(2.0)); +``` + +This is consistent with the general Carbon rule that if the names of another +entity affect a class' API, then that is mentioned with an `extend` declaration +in the `class` definition. -**Comparison with other languages:** Rust defines implementations lexically +**Comparison with other languages:** Rust only defines implementations lexically outside of the `class` definition. This Carbon approach means that a type's API is described by declarations inside the `class` definition and doesn't change afterwards. -**References:** This interface implementation syntax was accepted in +**References:** Carbon's interface implementation syntax was first defined in [proposal #553](https://github.com/carbon-language/carbon-lang/pull/553). In particular, see [the alternatives considered](/proposals/p0553.md#interface-implementation-syntax). +This syntax was changed to use `extend` in +[proposal #2760: Consistent `class` and `interface` syntax](https://github.com/carbon-language/carbon-lang/pull/2760). + +### Out-of-line `impl` + +An impl may also be defined after the type definition, by naming the type +between `impl` and `as`: + +``` +class Point_OutOfLine { + var x: f64; + var y: f64; +} + +impl Point_OutOfLine as Vector { + // In this scope, the `Self` keyword is an + // alias for `Point_OutOfLine`. + fn Add[self: Self](b: Self) -> Self { + return {.x = a.x + b.x, .y = a.y + b.y}; + } + fn Scale[self: Self](v: f64) -> Self { + return {.x = a.x * v, .y = a.y * v}; + } +} +``` + +Since `extend impl` may only be used inside the class definition, out-of-line +definitions do not contribute to the class' API unless there is a corresponding +[forward declaration in the class definition using `extend](#forward-impl-declaration). + +Conversely, being declared or defined lexically inside the class means that +implementation is available to other members defined in the class. For example, +it would allow implementing another interface or method that requires this +interface to be implemented. + +**Open question:** Do implementations need to be defined lexically inside the +class to get access to private members, or is it sufficient to be defined in the +same library as the class? + +**Comparison with other languages:** Both Rust and Swift support out-of-line +implementation. +[Swift's syntax](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html#ID277) +does this as an "extension" of the original type. In Rust, all implementations +are out-of-line as in +[this example](https://doc.rust-lang.org/rust-by-example/trait.html). Unlike +Swift and Rust, we don't allow a type's API to be modified outside its +definition. So in Carbon a type's API is consistent no matter what is imported, +unlike Swift and Rust. + +#### Defining an `impl` in another library than the type + +An out-of-line `impl` declaration is allowed to be defined in a different +library from `Point_OutOfLine`, restricted by +[the coherence/orphan rules](#impl-lookup) that ensure that the implementation +of an interface can't change based on imports. In particular, the `impl` +declaration is allowed in the library defining the interface (`Vector` in this +case) in addition to the library that defines the type (`Point_OutOfLine` here). +This (at least partially) addresses +[the expression problem](https://eli.thegreenplace.net/2016/the-expression-problem-and-its-solutions). + +You can't use `extend` outside the class definition, so an `impl` declarations +in a different library will never affect the class' API. This means that the API +of a class such as `Point_OutOfLine` doesn't change based on what is imported. +It would be particularly bad if two different libraries implemented interfaces +with conflicting names that both affected the API of a single type. As a +consequence of this restriction, you can find all the names of direct members +(those available by [simple member access](terminology.md#simple-member-access)) +of a type in the definition of that type. The only thing that may be in another +library is an `impl` of an interface. + +**Rejected alternative:** We could allow types to have different APIs in +different files based on explicit configuration in that file. For example, we +could support a declaration that a given interface or a given method of an +interface is "in scope" for a particular type in this file. With that +declaration, the method could be called using +[simple member access](terminology.md#simple-member-access). This avoids most +concerns arising from name collisions between interfaces. It has a few downsides +though: + +- It increases variability between files, since the same type will have + different APIs depending on these declarations. This makes it harder to + copy-paste code between files. +- It makes reading code harder, since you have to search the file for these + declarations that affect name lookup. + +### Forward `impl` declaration + +An `impl` declaration may be forward declared and then defined later. If this is +done using [`extend` to add to the type's API](#extend-impl), only the +declaration in the class definition will use the `extend` keyword, as in this +example: + +``` +class Point_ExtendForward { + var x: f64; + var y: f64; + // Forward declaration in class definition using `extend`. + // Signals that you should look in the definition of + // `Vector` since those methods are included in this type. + extend impl as Vector; +} + +// Definition outside class definition does not. +impl Point_ExtendForward as Vector { + fn Add[self: Self](b: Self) -> Self { + return {.x = a.x + b.x, .y = a.y + b.y}; + } + fn Scale[self: Self](v: f64) -> Self { + return {.x = a.x * v, .y = a.y * v}; + } +} +``` + +More about forward declaring implementations in +[its dedicated section](#declaring-implementations). ### Implementing multiple interfaces To implement more than one interface when defining a type, simply include an -`impl` block per interface. +`impl` block or forward declaration per interface. ``` -class Point { +class Point_2Extend { var x: f64; var y: f64; extend impl as Vector { @@ -314,24 +461,13 @@ class Point { } ``` -In this case, all the functions `Add`, `Scale`, and `Draw` end up a part of the -API for `Point`. This means you can't implement two interfaces that have a name -in common (unless you use an `impl` without `extend` for one or both, for an -[external impl](#external-impl)). +Since both were declared using `extend`, all the functions `Add`, `Scale`, and +`Draw` end up a part of the API for `Point_2Extend`. -``` -class GameBoard { - extend impl as Drawable { - fn Draw[self: Self]() { ... } - } - extend impl as EndOfGame { - // ❌ Error: `GameBoard` has two methods named - // `Draw` with the same signature. - fn Draw[self: Self]() { ... } - fn Winner[self: Self](player: i32) { ... } - } -} -``` +**Note:** A type may implement any number of different interfaces, but may +provide at most one implementation of any single interface. This makes the act +of selecting an implementation of an interface for a type unambiguous throughout +the whole program. **Open question:** Should we have some syntax for the case where you want both names to be given the same implementation? It seems like that might be a common @@ -355,93 +491,42 @@ class Player { } ``` -### External impl - -Interfaces may also be implemented for a type without adding the interface's -methods to the type by using `impl` without `extend`. - -``` -class Point2 { - var x: f64; - var y: f64; - - impl as Vector { - // In this scope, the `Self` keyword is an - // alias for `Point2`. - fn Add[self: Self](b: Self) -> Self { - return {.x = a.x + b.x, .y = a.y + b.y}; - } - fn Scale[self: Self](v: f64) -> Self { - return {.x = a.x * v, .y = a.y * v}; - } - } -} - -var a: Point2 = {.x = 1.0, .y = 2.0}; -// `a` does *not* have `Add` and `Scale` methods: -// ❌ Error: a.Add(a.Scale(2.0)); -``` +### Avoiding name collisions -An external impl may include the name of the implementing type before `as`, -which is required to define it out-of-line: +To avoid name collisions, you can't extend implementations of two interfaces +that have a name in common: ``` -class Point3 { - var x: f64; - var y: f64; -} - -impl Point3 as Vector { - // In this scope, the `Self` keyword is an - // alias for `Point3`. - fn Add[self: Self](b: Self) -> Self { - return {.x = a.x + b.x, .y = a.y + b.y}; +class GameBoard { + extend impl as Drawable { + fn Draw[self: Self]() { ... } } - fn Scale[self: Self](v: f64) -> Self { - return {.x = a.x * v, .y = a.y * v}; + extend impl as EndOfGame { + // ❌ Error: `GameBoard` has two methods named `Draw`. + fn Draw[self: Self]() { ... } + fn Winner[self: Self](player: i32) { ... } } } - -var a: Point3 = {.x = 1.0, .y = 2.0}; -// `a` does *not* have `Add` and `Scale` methods: -// ❌ Error: a.Add(a.Scale(2.0)); ``` -**References:** The external interface implementation syntax was decided in -[proposal #553](https://github.com/carbon-language/carbon-lang/pull/553), and -then replaced in -[proposal #2760](https://github.com/carbon-language/carbon-lang/pull/2760). - -An external `impl` declaration is allowed to be defined in a different library -from `Point3`, restricted by [the coherence/orphan rules](#impl-lookup) that -ensure that the implementation of an interface can't change based on imports. In -particular, the `impl` declaration is allowed in the library defining the -interface (`Vector` in this case) in addition to the library that defines the -type (`Point3` here). This (at least partially) addresses -[the expression problem](https://eli.thegreenplace.net/2016/the-expression-problem-and-its-solutions). +To implement two interfaces that have a name in common, omit `extend` for one or +both. -Carbon requires `impl` declarations in a different library to be external so -that the API of `Point3` doesn't change based on what is imported. It would be -particularly bad if two different libraries implemented interfaces with -conflicting names that both affected the API of a single type. As a consequence -of this restriction, you can find all the names of direct members (those -available by [simple member access](terminology.md#simple-member-access)) of a -type in the definition of that type. The only thing that may be in another -library is an `impl` of an interface. - -You might also use an external `impl` to implement an interface for a type to -avoid cluttering the API of that type, for example to avoid a name collision. A -syntax for reusing method implementations allows us to do this selectively when -needed. In this case, the external `impl` may be declared lexically inside the -class scope. +You might also omit `extend` when implementing an interface for a type to avoid +cluttering the API of that type or to avoid a name collision with another member +of that type. A syntax for reusing method implementations allows us to include +names from an implementation selectively: ``` -class Point4a { +class Point_ReuseMethodInImpl { var x: f64; var y: f64; + // `Add()` is a method of `Point_ReuseMethodInImpl`. fn Add[self: Self](b: Self) -> Self { return {.x = self.x + b.x, .y = self.y + b.y}; } + // No `extend`, so other members of `Vector` are not + // part of `Point_ReuseMethodInImpl`'s API. impl as Vector { alias Add = Point4a.Add; // Syntax TBD fn Scale[self: Self](v: f64) -> Self { @@ -452,9 +537,11 @@ class Point4a { // OR: -class Point4b { +class Point_IncludeMethodFromImpl { var x: f64; var y: f64; + // No `extend`, so members of `Vector` are not + // part of `Point_IncludeMethodFromImpl`'s API. impl as Vector { fn Add[self: Self](b: Self) -> Self { return {.x = self.x + b.x, .y = self.y + b.y}; @@ -463,12 +550,15 @@ class Point4b { return {.x = self.x * v, .y = self.y * v}; } } + // Include `Add` explicitly as a member. alias Add = Vector.Add; } // OR: -class Point4c { +// This is the same as `Point_ReuseMethodInImpl`, +// except the `impl` is out-of-line. +class Point_ReuseByOutOfLine { var x: f64; var y: f64; fn Add[self: Self](b: Self) -> Self { @@ -476,68 +566,44 @@ class Point4c { } } -impl Point4c as Vector { - alias Add = Point4c.Add; // Syntax TBD +impl Point_ReuseByOutOfLine as Vector { + alias Add = Point_ReuseByOutOfLine.Add; // Syntax TBD fn Scale[self: Self](v: f64) -> Self { return {.x = self.x * v, .y = self.y * v}; } } ``` -Being defined lexically inside the class means that implementation is available -to other members defined in the class. For example, it would allow implementing -another interface or method that requires this interface to be implemented. - -**Open question:** Do implementations need to be defined lexically inside the -class to get access to private members, or is it sufficient to be defined in the -same library as the class? - -**Rejected alternative:** We could allow types to have different APIs in -different files based on explicit configuration in that file. For example, we -could support a declaration that a given interface or a given method of an -interface is "in scope" for a particular type in this file. With that -declaration, the method could be called using -[simple member access](terminology.md#simple-member-access). This avoids most -concerns arising from name collisions between interfaces. It has a few downsides -though: - -- It increases variability between files, since the same type will have - different APIs depending on these declarations. This makes it harder to - copy-paste code between files. -- It makes reading code harder, since you have to search the file for these - declarations that affect name lookup. +### Qualified member names and compound member access -**Comparison with other languages:** Both Rust and Swift support out-of-line -implementation. -[Swift's syntax](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html#ID277) -does this as an "extension" of the original type. In Rust, all implementations -are out-of-line as in -[this example](https://doc.rust-lang.org/rust-by-example/trait.html). Unlike -Swift and Rust, we don't allow a type's API to be modified outside its -definition. So in Carbon a type's API is consistent no matter what is imported, -unlike Swift and Rust. +``` +class Point_NoExtend { + var x: f64; + var y: f64; +} -### Qualified member names and compound member access +impl Point_NoExtend as Vector { ... } +``` -Given a value of type `Point3` and an interface `Vector` implemented for that -type, you can access the methods from that interface using a +Given a value of type `Point_NoExtend` and an interface `Vector` implemented for +that type, you can access the methods from that interface using a [qualified member access expression](terminology.md#qualified-member-access-expression) whether or not the implementation is done with an -[external `impl` declaration](#external-impl). The qualified member access +[`extend impl` declaration](#extend-impl). The qualified member access expression writes the member's _qualified name_ in the parentheses of the [compound member access syntax](/docs/design/expressions/member_access.md): ``` -var p1: Point3 = {.x = 1.0, .y = 2.0}; -var p2: Point3 = {.x = 2.0, .y = 4.0}; +var p1: Point_NoExtend = {.x = 1.0, .y = 2.0}; +var p2: Point_NoExtend = {.x = 2.0, .y = 4.0}; Assert(p1.(Vector.Scale)(2.0) == p2); Assert(p1.(Vector.Add)(p1) == p2); ``` Note that the name in the parens is looked up in the containing scope, not in -the names of members of `Point3`. So if there was another interface `Drawable` -with method `Draw` defined in the `Plot` package also implemented for `Point3`, -as in: +the names of members of `Point_NoExtend`. So if there was another interface +`Drawable` with method `Draw` defined in the `Plot` package also implemented for +`Point_NoExtend`, as in: ``` package Plot; @@ -547,7 +613,7 @@ interface Drawable { fn Draw[self: Self](); } -impl Points.Point3 as Drawable { ... } +impl Points.Point_NoExtend as Drawable { ... } ``` You could access `Draw` with a qualified name: @@ -556,7 +622,7 @@ You could access `Draw` with a qualified name: import Plot; import Points; -var p: Points.Point3 = {.x = 1.0, .y = 2.0}; +var p: Points.Point_NoExtend = {.x = 1.0, .y = 2.0}; p.(Plot.Drawable.Draw)(); ``` @@ -591,7 +657,7 @@ Here is a function that can accept values of any type that has implemented the fn AddAndScaleGeneric[T:! Vector](a: T, b: T, s: f64) -> T { return a.Add(b).Scale(s); } -var v: Point = AddAndScaleGeneric(a, w, 2.5); +var v: Point_Extend = AddAndScaleGeneric(a, w, 2.5); ``` Here `T` is a type whose type is `Vector`. The `:!` syntax means that `T` is a @@ -634,11 +700,14 @@ example members in a derived class can hide members in the base class with the same name, though it is not that common for it to come up in practice. The behavior of calling `AddAndScaleGeneric` with a value of a specific type -like `Point` is to set `T` to `Point` after all the names have been qualified. +like `Point_Extend` is to set `T` to `Point_Extend` after all the names have +been qualified. ``` -// AddAndScaleGeneric with T = Point -fn AddAndScaleForPoint(a: Point, b: Point, s: Double) -> Point { +// AddAndScaleGeneric with T = Point_Extend +fn AddAndScaleForPoint_Extend( + a: Point_Extend, b: Point_Extend, s: Double) + -> Point_Extend { return a.(Vector.Add)(b).(Vector.Scale)(s); } ``` @@ -646,11 +715,11 @@ fn AddAndScaleForPoint(a: Point, b: Point, s: Double) -> Point { This qualification gives a consistent interpretation to the body of the function even when the type supplied by the caller does not [extend the implementation of the interface](terminology.md#extending-an-impl), -like `Point2`: +like `Point_NoExtend`: ``` -// AddAndScaleGeneric with T = Point2 -fn AddAndScaleForPoint2(a: Point2, b: Point2, s: Double) -> Point2 { +// AddAndScaleGeneric with T = Point_NoExtend +fn AddAndScaleForPoint_NoExtend(a: Point_NoExtend, b: Point_NoExtend, s: Double) -> Point_NoExtend { // ✅ This works even though `a.Add(b).Scale(s)` wouldn't. return a.(Vector.Add)(b).(Vector.Scale)(s); } @@ -660,16 +729,18 @@ fn AddAndScaleForPoint2(a: Point2, b: Point2, s: Double) -> Point2 { From the caller's perspective, the return type is the result of substituting the caller's values for the generic parameters into the return type expression. So -`AddAndScaleGeneric` called with `Point` values returns a `Point` and called -with `Point2` values returns a `Point2`. So looking up a member on the resulting -value will look in `Point` or `Point2` rather than `Vector`. +`AddAndScaleGeneric` called with `Point_Extend` values returns a `Point_Extend` +and called with `Point_NoExtend` values returns a `Point_NoExtend`. So looking +up a member on the resulting value will look in `Point_Extend` or +`Point_NoExtend` rather than `Vector`. This is part of realizing [the goal that generic functions can be used in place of regular functions without changing the return type that callers see](goals.md#path-from-regular-functions). In this example, `AddAndScaleGeneric` can be substituted for -`AddAndScaleForPoint` and `AddAndScaleForPoint2` without affecting the return -types. This requires the return value to be converted to the type that the -caller expects instead of the erased type used inside the generic function. +`AddAndScaleForPoint_Extend` and `AddAndScaleForPoint_NoExtend` without +affecting the return types. This requires the return value to be converted to +the type that the caller expects instead of the erased type used inside the +generic function. A generic caller of a generic function performs the same substitution process to determine the return type, but the result may be generic. In this example of @@ -755,26 +826,26 @@ class Vector { } ``` -The [impl of Vector for Point](#implementing-interfaces) would be a value of -this type: +The [impl of Vector for Point_Inline](#inline-impl) would be a value of this +type: ``` -var VectorForPoint: Vector = { - .Self = Point, +var VectorForPoint_Inline: Vector = { + .Self = Point_Inline, // `lambda` is **placeholder** syntax for defining a // function value. - .Add = lambda(a: Point, b: Point) -> Point { + .Add = lambda(a: Point_Inline, b: Point_Inline) -> Point_Inline { return {.x = a.x + b.x, .y = a.y + b.y}; }, - .Scale = lambda(a: Point, v: f64) -> Point { + .Scale = lambda(a: Point_Inline, v: f64) -> Point_Inline { return {.x = a.x * v, .y = a.y * v}; }, }; ``` Since generic arguments (where the parameter is declared using `:!`) are passed -at compile time, so the actual value of `VectorForPoint` can be used to generate -the code for functions using that impl. This is the +at compile time, so the actual value of `VectorForPoint_Inline` can be used to +generate the code for functions using that impl. This is the [static-dispatch witness table](terminology.md#static-dispatch-witness-table) approach. @@ -1733,8 +1804,8 @@ there is no implicit conversion from `B` to `A`, matching `adapt` without `extend` but unlike class extension. To avoid or resolve name conflicts between interfaces, an `impl` may be declared -[external](#external-impl). The names in that interface may then be pulled in -individually or renamed using `alias` declarations. +without [`extend`](#extend-impl). The names in that interface may then be pulled +in individually or renamed using `alias` declarations. ``` class SongRenderToPrintDriver { @@ -2669,15 +2740,16 @@ fn Contains ``` the `where` constraint means `CT.ElementType` must satisfy `Comparable` as well. -However, inside the body of `Contains`, `CT.ElementType` will only act like the -implementation of `Comparable` is [external](#external-impl). That is, items -from the `needles` container won't directly have a `Compare` method member, but -can still be implicitly converted to `Comparable` and can still call `Compare` -using the compound member access syntax, `needle.(Comparable.Compare)(elt)`. The -rule is that an `==` `where` constraint between two type variables does not -modify the set of member names of either type. (If you write -`where .ElementType = String` with a `=` and a concrete type, then -`.ElementType` is actually set to `String` including the complete `String` API.) +However, inside the body of `Contains`, `CT.ElementType` will act like the +implementation of `Comparable` is declared without [`extend`](#extend-impl). +That is, items from the `needles` container won't directly have a `Compare` +method member, but can still be implicitly converted to `Comparable` and can +still call `Compare` using the compound member access syntax, +`needle.(Comparable.Compare)(elt)`. The rule is that an `==` `where` constraint +between two type variables does not modify the set of member names of either +type. (If you write `where .ElementType = String` with a `=` and a concrete +type, then `.ElementType` is actually set to `String` including the complete +`String` API.) Note that `==` constraints are symmetric, so the previous declaration of `Contains` is equivalent to an alternative declaration where `CT` is declared @@ -3782,8 +3854,8 @@ interface when its element type satisfies the same interface: if the element type is comparable. - A container is copyable if its elements are. -This may be done with an [external `impl`](#external-impl) by specifying a more -specific implementing type to the left of the `as` in the declaration: +This may be done by specifying a more specific implementing type to the left of +the `as` in the declaration: ``` interface Printable { @@ -3805,7 +3877,7 @@ impl forall [T:! Printable] Vector(T) as Printable { ``` Note that no `forall` clause or type may be specified when declaring an `impl` -with the `extend` keyword: +with the [`extend`](#extend-impl) keyword: ``` class Array(T:! type, template N:! i64) { @@ -3950,8 +4022,8 @@ than one root type, so the `impl` declaration will use a type variable for the This means that every type is the common type with itself. -Blanket impl declarations must always be [external](#external-impl) and defined -lexically out-of-line. +Blanket impl declarations may never be declared using [`extend`](#extend-impl) +and must always be defined lexically [out-of-line](#out-of-line-impl). #### Difference between a blanket impl and a named constraint @@ -3982,8 +4054,8 @@ class BigInt { impl forall [T:! ImplicitAs(i32)] BigInt as AddTo(T) { ... } ``` -Wildcard impl declarations must always be [external](#external-impl), to avoid -having the names in the interface defined for the type multiple times. +Wildcard impl declarations may never be declared using [`extend`](#extend-impl), +to avoid having the names in the interface defined for the type multiple times. ### Combinations From 52017c9c8c959a548a603b2dd42823e9d7045a42 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 13 Jul 2023 21:32:54 +0000 Subject: [PATCH 08/83] Fix references to external section --- docs/design/README.md | 31 +++++++++++++++++++------------ docs/design/classes.md | 8 ++++---- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/docs/design/README.md b/docs/design/README.md index 9218614c05b81..ef585d9316a1c 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -2419,7 +2419,7 @@ class ContactInfo { > > - [Aliases](aliases.md) > - ["Aliasing" in "Code and name organization"](code_and_name_organization/README.md#aliasing) -> - [`alias` a name from an external impl](generics/details.md#external-impl) +> - [`alias` a name from an interface impl](generics/details.md#avoiding-name-collisions) > - [`alias` a name in a named constraint](generics/details.md#named-constraints) > - Proposal > [#107: Code and name organization](https://github.com/carbon-language/carbon-lang/pull/107) @@ -2676,7 +2676,7 @@ sufficient. class Circle { var radius: f32; - extend impl as Printable { + impl as Printable { fn Print[self: Self]() { Carbon.Print("Circle with radius: {0}", self.radius); } @@ -2684,16 +2684,23 @@ class Circle { } ``` -In this case, `Print` is a member of `Circle`. Interfaces may also be -implemented [externally](generics/details.md#external-impl), which means the -members of the interface are not direct members of the type. Those methods may -still be called using compound member access syntax -([1](expressions/member_access.md), -[2](generics/details.md#qualified-member-names-and-compound-member-access)) to -qualify the name of the member, as in `x.(Printable.Print)()`. External -implementations don't have to be in the same library as the type definition, -subject to the orphan rule ([1](generics/details.md#impl-lookup), -[2](generics/details.md#orphan-rule)) for +In this case, `Print` is not a direct member of `Circle`, but: + +- `Circle` may be passed to functions expecting a type that implements + `Printable`, and +- the members of `Printable` such as `Print` may be called using compound + member access syntax ([1](expressions/member_access.md), + [2](generics/details.md#qualified-member-names-and-compound-member-access)) + to qualify the name of the member, as in `c.(Printable.Print)()`. + +To include the members of the interface as direct members of the type, use the +[`extend`](generics/details.md#extend-impl) keyword, as in +`extend impl as Printable`. This is only permitted on `impl` declarations in the +body of a class definition. + +Without `extend`, implementations don't have to be in the same library as the +type definition, subject to the orphan rule +([1](generics/details.md#impl-lookup), [2](generics/details.md#orphan-rule)) for [coherence](generics/terminology.md#coherence). Interfaces and implementations may be diff --git a/docs/design/classes.md b/docs/design/classes.md index 22d071d0f12ec..e18eed8831806 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -1174,11 +1174,11 @@ methods whose implementation may be overridden in a derived class. Only methods defined in the scope of the class definition may be virtual, not any defined in -[external interface `impl` declarations](/docs/design/generics/details.md#external-impl). +[out-of-line interface `impl` declarations](/docs/design/generics/details.md#out-of-line-impl). Interface methods may be implemented using virtual methods when the -[impl is internal](/docs/design/generics/details.md#implementing-interfaces), -and calls to those methods by way of the interface will do virtual dispatch just -like a direct call to the method does. +[impl is inline](/docs/design/generics/details.md#inline-impl), and calls to +those methods by way of the interface will do virtual dispatch just like a +direct call to the method does. [Class functions](#class-functions) may not be declared virtual. From 2ffeb6c036ec156f46f68e62df7b550711941e5b Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 14 Jul 2023 17:27:49 +0000 Subject: [PATCH 09/83] Move away from internal/external --- docs/design/README.md | 4 ++-- docs/design/generics/overview.md | 19 ++++++++++--------- docs/design/pattern_matching.md | 8 ++++---- docs/design/sum_types.md | 2 +- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/docs/design/README.md b/docs/design/README.md index ef585d9316a1c..91e5acf94e77c 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -3336,8 +3336,8 @@ need to be overridden for a Carbon type, that can be done with a nonmember C++ function. Carbon interfaces with no C++ equivalent, such as -[`CommonTypeWith(U)`](#common-type), may be implemented for C++ types externally -in Carbon code. To satisfy the orphan rule +[`CommonTypeWith(U)`](#common-type), may be implemented for C++ types +out-of-line in Carbon code. To satisfy the orphan rule ([1](generics/details.md#impl-lookup), [2](generics/details.md#orphan-rule)), each C++ library will have a corresponding Carbon wrapper library that must be imported instead of the C++ library if the Carbon wrapper exists. **TODO:** diff --git a/docs/design/generics/overview.md b/docs/design/generics/overview.md index 9785fda1c01bc..0975dbfb7f172 100644 --- a/docs/design/generics/overview.md +++ b/docs/design/generics/overview.md @@ -66,10 +66,12 @@ Summary of how Carbon generics work: methods, functions, and other entities for types to implement. - Types must explicitly _implement_ interfaces to indicate that they support its functionality. A given type may implement an interface at most once. -- Implementations may be part of the type's definition, in which case you can - directly call the interface's methods on those types. Or, they may be - external, in which case the implementation is allowed to be defined in the - library defining the interface. +- Implementations may be declared inline in the body of a class definition, or + out-of-line. +- Types may extend an implementation declared inline, in which case you can + directly call the interface's methods on those types. +- Out-of-line implementations may be defined in the library defining the + interface. - Interfaces are used as the type of a generic type parameter, acting as a _facet type_. Facet types in general specify the capabilities and requirements of the type. Types define specific implementations of those @@ -230,19 +232,18 @@ library defining either the class or the interface. #### Accessing members of interfaces -The methods of an interface implemented internally within the class definition -may be called with the +Methods from an interface that a class extends may be called with the [simple member access syntax](terminology.md#simple-member-access). Methods of all implemented interfaces may be called with a [qualified member access expression](terminology.md#qualified-member-access-expression), -whether they are defined internally or externally. +whether the class extends them or not. ``` var song: Song; // `song.Print()` is allowed, unlike `song.Play()`. song.Print(); -// `Less` is defined in `Comparable`, which is -// implemented externally for `Song` +// `Less` is defined in `Comparable`, which `Song` +// does not extend the implementation of. song.(Comparable.Less)(song); // Can also call `Print` using a qualified member // access expression, using the compound member access diff --git a/docs/design/pattern_matching.md b/docs/design/pattern_matching.md index a0bf127537e23..167f70f8068d4 100644 --- a/docs/design/pattern_matching.md +++ b/docs/design/pattern_matching.md @@ -93,7 +93,7 @@ reusing the result from an earlier comparison: ```carbon class ChattyIntMatcher { - external impl as EqWith(i32) { + impl as EqWith(i32) { fn Eq[me: ChattyIntMatcher](other: i32) { Print("Matching {0}", other); return other == 1; @@ -144,7 +144,7 @@ value matches the scope of the binding. ```carbon class NoisyDestructor { fn Make() -> Self { return {}; } - external impl i32 as ImplicitAs(NoisyDestructor) { + impl i32 as ImplicitAs(NoisyDestructor) { fn Convert[me: i32]() -> Self { return Make(); } } destructor { @@ -265,7 +265,7 @@ before pattern matching is performed. ```carbon fn G[T:! Type](p: T*); -class X { external impl as ImplicitAs(i32*); } +class X { impl as ImplicitAs(i32*); } // ✅ Deduces `T = i32` then implicitly and // trivially converts `p` to `i32*`. fn H1(p: i32*) { G(p); } @@ -441,7 +441,7 @@ match (Optional(i32).None) { } class X { - external impl as ImplicitAs(Optional(i32)); + impl as ImplicitAs(Optional(i32)); } match ({} as X) { diff --git a/docs/design/sum_types.md b/docs/design/sum_types.md index c0c23328f7b27..0c38b8a027812 100644 --- a/docs/design/sum_types.md +++ b/docs/design/sum_types.md @@ -83,7 +83,7 @@ match (my_opt) { have limited flexibility. There is no way to control the representation of a `choice` type, or define methods or other members for it (although you can extend it to implement interfaces, using an -[external `impl`](generics/overview.md#implementing-interfaces) or +[out-of-line `impl`](generics/details.md#out-of-line-impl) or [adapter](generics/overview.md#adapting-types)). However, a `class` type can be extended to behave like a sum type. This is much more verbose than a `choice` declaration, but gives the author full control over the representation and class From 4b408ece4ed01af67c5d36eec23238764791aca1 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 14 Jul 2023 20:09:41 +0000 Subject: [PATCH 10/83] Fix internal/external in details --- docs/design/generics/details.md | 122 +++++++++++++++++--------------- 1 file changed, 64 insertions(+), 58 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 62a6b5accdb4c..0d33388ef7eb6 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -41,7 +41,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Use case: Using independent libraries together](#use-case-using-independent-libraries-together) - [Use case: Defining an impl for use by other types](#use-case-defining-an-impl-for-use-by-other-types) - [Use case: Private impl](#use-case-private-impl) - - [Use case: Accessing external names](#use-case-accessing-external-names) + - [Use case: Accessing interface names](#use-case-accessing-interface-names) - [Adapter with stricter invariants](#adapter-with-stricter-invariants) - [Associated constants](#associated-constants) - [Associated class functions](#associated-class-functions) @@ -203,8 +203,8 @@ somewhere else as long as Carbon can be guaranteed to see the definition when needed. For more on this, see [the implementing interfaces section](#implementing-interfaces) below. -When the implementation of `ConvertibleToString` for `Song` is defined as -internal, every member of `ConvertibleToString` is also a member of `Song`. This +When the implementation of `ConvertibleToString` for `Song` is declared with +`extend`, every member of `ConvertibleToString` is also a member of `Song`. This includes members of `ConvertibleToString` that are not explicitly named in the `impl` definition but have defaults. Whether the type [extends the implementation](terminology.md#extending-an-impl) or not, you may @@ -754,8 +754,8 @@ fn DoubleThreeTimes[U:! Vector](a: U) -> U { the return type of `AddAndScaleGeneric` is found by substituting in the `U` from `DoubleThreeTimes` for the `T` from `AddAndScaleGeneric` in the return type -expression of `AddAndScaleGeneric`. `U` is an archetype of `Vector`, and so -implements `Vector` internally and therefore has a `Scale` method. +expression of `AddAndScaleGeneric`. `U` is an archetype of `Vector`, and so acts +as if it extends `Vector` and therefore has a `Scale` method. If `U` had a more specific type, the return value would have the additional capabilities of `U`. For example, given a parameterized type `GeneralPoint` @@ -773,16 +773,16 @@ fn CallWithGeneralPoint[C:! Numeric](p: GeneralPoint(C)) -> C { // deduced to be `GeneralPoint(C)`. // ❌ Illegal: AddAndScaleGeneric(p, p, 2.0).Scale(2.0); - // `GeneralPoint(C)` implements `Vector` externally, and so - // does not have a `Scale` method. + // `GeneralPoint(C)` implements but does not extend `Vector`, + // and so does not have a `Scale` method. // ✅ Allowed: `GeneralPoint(C)` has a `Get` method AddAndScaleGeneric(p, p, 2.0).Get(0); - // ✅ Allowed: `GeneralPoint(C)` implements `Vector` - // externally, and so has a `Vector.Scale` method. - // `Vector.Scale` returns `Self` which is `GeneralPoint(C)` - // again, and so has a `Get` method. + // ✅ Allowed: `GeneralPoint(C)` implements `Vector`, and so has + // a `Vector.Scale` method. `Vector.Scale` returns `Self` + // which is `GeneralPoint(C)` again, and so has a `Get` + // method. return AddAndScaleGeneric(p, p, 2.0).(Vector.Scale)(2.0).Get(0); } ``` @@ -790,7 +790,7 @@ fn CallWithGeneralPoint[C:! Numeric](p: GeneralPoint(C)) -> C { The result of the call to `AddAndScaleGeneric` from `CallWithGeneralPoint` has type `GeneralPoint(C)` and so has a `Get` method and a `Vector.Scale` method. But, in contrast to how `DoubleThreeTimes` works, since `Vector` is implemented -externally the return value in this case does not directly have a `Scale` +without `extend` the return value in this case does not directly have a `Scale` method. ### Implementation model @@ -1557,7 +1557,7 @@ though could be defined in the `impl` block of `IncidenceGraph`, } ``` -- Implementing `Graph` externally. +- Implementing `Graph` out-of-line. ``` class MyEdgeListIncidenceGraph { @@ -1814,12 +1814,13 @@ class SongRenderToPrintDriver { // Add a new `Print()` member function. fn Print[self: Self]() { ... } - // Avoid name conflict with new `Print` function by making - // the implementation of the `Printable` interface external. + // Avoid name conflict with new `Print` + // function by implementing the `Printable` + // interface without `extend`. impl as Printable = Song; - // Make the `Print` function from `Printable` available - // under the name `PrintToScreen`. + // Make the `Print` function from `Printable` + // available under the name `PrintToScreen`. alias PrintToScreen = Printable.Print; } ``` @@ -1974,7 +1975,7 @@ fn Complex64.CloserToOrigin[self: Self](them: Self) -> bool { } ``` -### Use case: Accessing external names +### Use case: Accessing interface names Consider a case where a function will call several functions from an interface that the type does not @@ -3244,26 +3245,29 @@ fn TakesPQR[U:! P & Q & R](u: U); fn G[T:! Transitive](t: T) { var a: T.A = t.GetA(); - // ✅ Allowed: `T.A` implements `P`. + // ✅ Allowed: `T.A` implements `P` and + // includes its API, as if it extends `P`. a.InP(); - // ✅ Allowed: `T.A` implements `Q` externally. + // ✅ Allowed: `T.A` implements (but does not + // extend) `Q`. a.(Q.InQ)(); // ❌ Not allowed: a.InQ(); // ✅ Allowed: values of type `T.A` may be cast - // to `T.B`, which implements `Q` internally. + // to `T.B`, which extends and implements `Q`. (a as T.B).InQ(); - // ✅ Allowed: `T.B` implements `R` externally. + // ✅ Allowed: `T.B` implements (but does not + // extend) `R`. (a as T.B).(R.InR)(); + // ❌ Not allowed: (a as T.B).InR(); // ❌ Not allowed: TakesPQR(a); // ✅ Allowed: `T.B` implements `P`, `Q`, and - // `R`, though the implementations of `P` - // and `R` are external. + // `R`, though `T.B` doesn't extend `P` or `R`. TakesPQR(a as T.B); } ``` @@ -3407,7 +3411,7 @@ fn F[T:! Transitive](t: T) { } ``` -Since adding an `observe` declaration only adds external implementations of +Since adding an `observe` declaration only adds non-extending implementations of interfaces to generic types, they may be added without breaking existing code. ## Other constraints as facet types @@ -3764,9 +3768,9 @@ The `where .Self == U` modifier allows values to implicitly convert between type `T`, the erased type, and type `U`, the concrete type. Note that implicit conversion is [only performed across a single `where` equality](#manual-type-equality). This -can be used to switch to the API of `C` when it is external, as an alternative -to [using an adapter](#use-case-accessing-external-names), or to simplify -inlining of a generic function while preserving semantics. +can be used to switch to the API of `C` when `U` does not extend `C`, as an +alternative to [using an adapter](#use-case-accessing-interface-names), or to +simplify inlining of a generic function while preserving semantics. ## Parameterized impl declarations @@ -3774,8 +3778,7 @@ There are cases where an impl definition should apply to more than a single type and interface combination. The solution is to parameterize the impl definition, so it applies to a family of types, interfaces, or both. This includes: -- Declare an impl for a parameterized type, which may be external or declared - out-of-line. +- Declare an impl for a parameterized type. - "Conditional conformance" where a parameterized type implements some interface if the parameter to the type satisfies some criteria, like implementing the same interface. @@ -3820,16 +3823,16 @@ impl forall [T:! type] Vector(T) as Iterable ``` The parameter for the type can be used as an argument to the interface being -implemented: +implemented, with or without `extend`: ``` class HashMap(Key:! Hashable, Value:! type) { extend impl as Has(Key) { ... } - extend impl as Contains(HashSet(Key)) { ... } + impl as Contains(HashSet(Key)) { ... } } ``` -or externally out-of-line: +or out-of-line: ``` class HashMap(Key:! Hashable, Value:! type) { ... } @@ -3914,13 +3917,13 @@ var no_print: Array(Unprintable, 2) = ...; no_print.Print(); ``` -It is still legal to declare or define an external impl lexically inside the -class scope, as in: +It is legal to declare or define a conditional impl lexically inside the class +scope without `extend`, as in: ``` class Array(T:! type, template N:! i64) { - // ✅ Allowed: external impl defined in class scope may use `forall` - // and may specify a type. + // ✅ Allowed: non-extending impl defined in class scope may + // use `forall` and may specify a type. impl forall [P:! Printable] Array(P, N) as Printable { ... } } ``` @@ -4684,7 +4687,7 @@ The declaration of an interface implementation consists of: [associated types](#associated-types). **Note:** The `extend` keyword, when present, is not part of the declaration. It -is only present for internal `impl` declarations in class scope. +precedes the `impl` declaration in class scope. An implementation of an interface for a type may be forward declared subject to these rules: @@ -4705,11 +4708,11 @@ these rules: does not apply to `impl as` declarations in an interface or named constraint definition, as those are considered interface requirements not forward declarations. -- Every internal implementation must be declared (or defined) inside the scope - of the class definition. It may also be declared before the class definition - or defined afterwards. Note that the class itself is incomplete in the scope - of the class definition, but member function bodies defined inline are - processed +- Every extending implementation must be declared (or defined) inside the + scope of the class definition. It may also be declared before the class + definition or defined afterwards. Note that the class itself is incomplete + in the scope of the class definition, but member function bodies defined + inline are processed [as if they appeared immediately after the end of the outermost enclosing class](/docs/project/principles/information_accumulation.md#exceptions). - For [coherence](goals.md#coherence), we require that any impl that matches an [impl lookup](#impl-lookup) query in the same file, must be declared @@ -4791,11 +4794,9 @@ interface Interface4 { let T4:! type; } -// Forward declaration of external implementations +// Out-of-line forward declarations impl MyClass as Interface1 where .T1 = i32; impl MyClass as Interface2 where .T2 = bool; - -// Forward declaration of an internal implementation impl MyClass as Interface3 where .T3 = f32; impl MyClass as Interface4 where .T4 = String; @@ -4808,42 +4809,47 @@ interface Interface6 { // Definition of the previously declared class type class MyClass { - // Definition of previously declared external impl. + // Inline definition of previously declared impl. // Note: no need to repeat assignments to associated // constants. impl as Interface1 where _ { } - // Definition of previously declared internal impl. + // Inline extending definition of previously declared + // impl. + // Note: `extend` only appears on the declaration in + // class scope // Note: allowed even though `MyClass` is incomplete. // Note: allowed but not required to repeat `where` // clause. extend impl as Interface3 where .T3 = f32 { } - // Redeclaration of previously declared internal impl. - // Every internal implementation must be declared in - // the class definition. + // Extending redeclaration of previously declared + // impl. Every extending implementation must be + // declared in the class definition. extend impl as Interface4 where _; - // Forward declaration of external implementation. + // Inline forward declaration of implementation. impl MyClass as Interface5 where .T5 = u64; // or: impl as Interface5 where .T5 = u64; - // Forward declaration of internal implementation. + // Forward declaration of extending implementation. extend impl as Interface6 where .T6 = u8; - // Not: extend impl MyClass as Interface6 where .T6 = u8; + // *Not*: + // extend impl MyClass as Interface6 where .T6 = u8; + // No optional type after `extend impl`, it must be + // followed immediately by `as` } // It would be legal to move the following definitions // from the API file to the implementation file for // this library. -// Definition of implementations previously declared -// external. +// Definitions of previously declared implementations. impl MyClass as Interface2 where _ { } impl MyClass as Interface5 where _ { } -// Definition of implementations previously declared -// internal. +// Definition of previously declared extending +// implementations. impl MyClass as Interface4 where _ { } impl MyClass as Interface6 where _ { } ``` From f8de9bb032353c24166f040ef2e00126e2c1790e Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 14 Jul 2023 20:20:38 +0000 Subject: [PATCH 11/83] Missed one since it was capitalized --- docs/design/expressions/member_access.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 56a6edd01ee1b..8c7b5d0222f25 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -169,7 +169,8 @@ impl i32 as Printable; class Point { var x: i32; var y: i32; - // Internal impl injects the name `Print` into class `Point`. + // Extending impl injects the name `Print` into class + // `Point`. extend impl as Printable; } From 99243e9773a67d8281fa04dfc1e1434296d267f2 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 14 Jul 2023 23:57:42 +0000 Subject: [PATCH 12/83] Update generics goals --- docs/design/generics/goals.md | 323 +++++++++++++++++----------------- 1 file changed, 161 insertions(+), 162 deletions(-) diff --git a/docs/design/generics/goals.md b/docs/design/generics/goals.md index 366c40c6e7b87..8a781ea0d023e 100644 --- a/docs/design/generics/goals.md +++ b/docs/design/generics/goals.md @@ -14,13 +14,13 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Background](#background) - [Generic parameters](#generic-parameters) - [Interfaces](#interfaces) - - [Relationship to templates](#relationship-to-templates) -- [Goals](#goals) +- [Templates](#templates) +- [Checked-generic goals](#checked-generic-goals) - [Use cases](#use-cases) - [Generic programming](#generic-programming) - [Upgrade path from C++ abstract interfaces](#upgrade-path-from-c-abstract-interfaces) - [Dependency injection](#dependency-injection) - - [Generics instead of open overloading and ADL](#generics-instead-of-open-overloading-and-adl) + - [Checked generics instead of open overloading and ADL](#checked-generics-instead-of-open-overloading-and-adl) - [Performance](#performance) - [Better compiler experience](#better-compiler-experience) - [Encapsulation](#encapsulation) @@ -34,11 +34,10 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Interfaces are nominal](#interfaces-are-nominal) - [Interop and evolution](#interop-and-evolution) - [Bridge for C++ customization points](#bridge-for-c-customization-points) -- [What we are not doing](#what-we-are-not-doing) +- [What we are not doing with checked generics](#what-we-are-not-doing-with-checked-generics) - [Not the full flexibility of templates](#not-the-full-flexibility-of-templates) - - [Template use cases that are out of scope](#template-use-cases-that-are-out-of-scope) - - [Generics will be checked when defined](#generics-will-be-checked-when-defined) - - [Specialization strategy](#specialization-strategy) + - [Checked generics will be checked when defined](#checked-generics-will-be-checked-when-defined) + - [Implementation strategy](#implementation-strategy) - [References](#references) @@ -53,18 +52,19 @@ forward-looking. ## Background -Carbon will support -[checked generics](terminology.md#checked-versus-template-parameters) to support -generic programming by way of -[parameterization of language constructs](terminology.md#parameterized-language-constructs) -with [early type checking](terminology.md#early-versus-late-type-checking) and +Carbon will support both +[checked and template generics](terminology.md#checked-versus-template-parameters) +to support generic programming by way of +[parameterization of language constructs](terminology.md#parameterized-language-constructs). + +Carbon's checked generics will feature +[early type checking](terminology.md#early-versus-late-type-checking) and [complete definition checking](terminology.md#complete-definition-checking). -This is in contrast with the +Carbon's [template generics](#templates), in contrast, will more closely follow +the [compile-time duck typing](https://en.wikipedia.org/wiki/Duck_typing#Templates_or_generic_types) -approach of C++ templates, and _in addition_ to -[template support in Carbon](#relationship-to-templates), if we decide to -support templates in Carbon beyond interoperability with C++ templates. +approach of C++ templates. ### Generic parameters @@ -105,7 +105,7 @@ languages: (compile-time only) - [Abstract base classes]() in C++, etc. (run-time only) -- [Go interfaces](https://gobyexample.com/interfaces) (run-time only) +- [Go interfaces](https://gobyexample.com/interfaces) In addition to specifying the methods available on a type, we may in the future expand the role of interfaces to allow other type constraints, such as on size, @@ -116,7 +116,7 @@ instead of, rather than in addition to, standard inheritance-and-classes object-oriented language support. For the moment, everything beyond specifying the _methods_ available is out of scope. -### Relationship to templates +## Templates The entire idea of statically typed languages is that coding against specific types and interfaces is a better model and experience. Unfortunately, templates @@ -124,37 +124,37 @@ don't provide many of those benefits to programmers until it's too late, when users are consuming the API. Templates also come with high overhead, such as [template error messages](#better-compiler-experience). -We want Carbon code to move towards more rigorously type checked constructs. +We want Carbon code to move towards more rigorously type-checked constructs. However, existing C++ code is full of unrestricted usage of compile-time duck-typed templates. They are incredibly convenient to write and so likely will continue to exist for a long time. -The question of whether Carbon has direct support for templates is out of scope -for this document. The generics design is not completely separate from -templates, so it is written as if Carbon will have its own templating system. It -is assumed to be similar to C++ templates with some specific changes: +Carbon will have direct support for templates in addition to checked generics. +Carbon's template system will be similar to C++ templates with some specific +changes: -- It may have some limitations to be more compatible with generics, much like - how we - [restrict overloading](#generics-instead-of-open-overloading-and-adl). +- It may have some limitations to be more compatible with checked generics, + much like how we + [restrict overloading](#checked-generics-instead-of-open-overloading-and-adl). - We likely will have a different method of selecting between different template instantiations, since [SFINAE](https://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error) makes it difficult to deliver high quality compiler diagnostics. -We assume Carbon will have templates for a few different reasons: +Other aspects of Carbon will reduce the number of situations where errors will +only be detected after the template is +[instantiated](terminology.md#instantiation): -- Carbon generics will definitely have to interact with _C++_ templates, and - many of the issues will be similar. -- We want to leave room in the design for templates, since it seems like it - would be easier to remove templates if they are not pulling their weight - than figure out how to add them in if they turn out to be needed. -- We may want to have templates in Carbon as a temporary measure, to make it - easier for users to transition off of C++ templates. +- Carbon's grammar won't require type information to disambiguate parsing, and + so definitions may be parsed without knowing the values of template + arguments. +- Carbon's name resolution is more restricted. Every package has its own + namespace, and there will be no + [argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl). -## Goals +## Checked-generic goals -Our goal for generics support in Carbon is to get most of the expressive +Our goal for checked generics support in Carbon is to get most of the expressive benefits of C++ templates and open overloading with fewer downsides. Additionally, we want to support some dynamic dispatch use cases; for example, in cases that inheritance struggles with. @@ -162,7 +162,7 @@ in cases that inheritance struggles with. ### Use cases To clarify the expressive range we are aiming for, here are some specific use -cases we expect Carbon generics to cover. +cases we expect Carbon checked generics to cover. #### Generic programming @@ -183,18 +183,19 @@ generally be used with [static dispatch](#dispatch-control). #### Upgrade path from C++ abstract interfaces -Interfaces in C++ are often represented by abstract base classes. Generics -should offer an alternative that does not rely on inheritance. This means looser -coupling and none of the problems of multiple inheritance. Some people, such as +Interfaces in C++ are often represented by abstract base classes. Checked +generics should offer an alternative that does not rely on inheritance. This +means looser coupling and none of the problems of multiple inheritance. Some +people, such as [Sean Parent](https://sean-parent.stlab.cc/papers-and-presentations/#better-code-runtime-polymorphism), advocate for runtime polymorphism patterns in C++ that avoid inheritance because it can cause runtime performance, correctness, and code maintenance problems in some situations. Those patterns require a lot of boilerplate and complexity in C++. It would be nice if those patterns were simpler to express with Carbon -generics. More generally, Carbon generics will provide an alternative for those -situations inheritance doesn't handle as well. As a specific example, we would -like Carbon generics to supplant the need to support multiple inheritance in -Carbon. +checked generics. More generally, Carbon checked generics will provide an +alternative for those situations inheritance doesn't handle as well. As a +specific example, we would like Carbon checked generics to supplant the need to +support multiple inheritance in Carbon. This is a case that would use [dynamic dispatch](#dispatch-control). @@ -202,15 +203,16 @@ This is a case that would use [dynamic dispatch](#dispatch-control). Types which only support subclassing for test stubs and mocks, as in ["dependency injection"](https://en.wikipedia.org/wiki/Dependency_injection), -should be able to easily migrate to generics. This extends outside the realm of -testing, allowing general configuration of how dependencies can be satisfied. -For example, generics might be used to configure how a library writes logs. +should be able to easily migrate to checked generics. This extends outside the +realm of testing, allowing general configuration of how dependencies can be +satisfied. For example, checked generics might be used to configure how a +library writes logs. This would allow you to avoid the runtime overhead of virtual functions, using [static dispatch](#dispatch-control) without the [poor build experience of templates](#better-compiler-experience). -#### Generics instead of open overloading and ADL +#### Checked generics instead of open overloading and ADL One name lookup problem we would like to avoid is caused by open overloading. Overloading is where you provide multiple implementations of a function with the @@ -226,20 +228,22 @@ function was originally defined. Together these enable This is commonly used to provide a type-specific implementation of some operation, but doesn't provide any enforcement of consistency across the different overloads. It makes the meaning of code dependent on which overloads -are imported, and is at odds with being able to type check a function -generically. +are imported, and is at odds with being able to type check a function's +definition before instantiation. Our goal is to address this use case, known more generally as [the expression problem](https://eli.thegreenplace.net/2016/the-expression-problem-and-its-solutions), -with a generics mechanism that does enforce consistency so that type checking is -possible without seeing all implementations. This will be Carbon's replacement -for open overloading. As a consequence, Carbon generics will need to be able to -support operator overloading. +with a checked-generics mechanism that does enforce consistency so that type +checking is possible without seeing all implementations. This will be Carbon's +replacement for open overloading. This is encapsulated in Carbon's +["one static open extension mechanism" principle](/docs/project/principles/static_open_extension.md). +As a consequence, Carbon checked generics will need to be able to support +operator overloading. A specific example is the absolute value function `Abs`. We would like to write `Abs(x)` for a variety of types. For some types `T`, such as `Int32` or `Float64`, the return type will be the same `T`. For other types, such as -`Complex64` or `Quaternion`, the return type will be different. The generic +`Complex64` or `Quaternion`, the return type will be different. Checked generic functions that call `Abs` will need a way to specify whether they only operate on `T` such that `Abs` has signature `T -> T`. @@ -250,14 +254,14 @@ overloading, which will ### Performance For any real-world C++ template, there shall be an idiomatic reformulation in -Carbon generics that has equal or better performance. +Carbon checked generics that has equal or better performance. [Performance is the top priority for Carbon](/docs/project/goals.md#performance-critical-software), -and we expect to use generics pervasively, and so they can't compromise that -goal in release builds. +and we expect to use checked generics pervasively, and so they can't compromise +that goal in release builds. **Nice to have:** There are cases where we should aim to do better than C++ -templates. For example, the additional structure of generics should make it -easier to reduce generated code duplication, reducing code size and cache +templates. For example, the additional structure of checked generics should make +it easier to reduce generated code duplication, reducing code size and cache misses. ### Better compiler experience @@ -266,12 +270,13 @@ Compared to C++ templates, we expect to reduce build times, particularly in development builds. We also expect the compiler to be able to report clearer errors, and report them earlier in the build process. -One source of improvement is that the bodies of generic functions and types can -be type checked once when they are defined, instead of every time they are used. -This is both a reduction in the total work done, and how errors can be reported -earlier. On use, the errors can be a lot clearer since they will be of the form -"argument did not satisfy function's contract as stated in its signature" -instead of "substitution failed at this line of the function's implementation." +One source of improvement is that the bodies of checked generic functions and +types can be type checked once when they are defined, instead of every time they +are used. This is both a reduction in the total work done, and how errors can be +reported earlier. On use, the errors can be a lot clearer since they will be of +the form "argument did not satisfy function's contract as stated in its +signature" instead of "substitution failed at this line of the function's +implementation." **Nice to have:** In development builds, we will have the option of using [dynamic dispatch](#dispatch-control) to reduce build times. We may also be able @@ -284,31 +289,30 @@ arguments or identical implementations and only generating code for them once. With a template, the implementation is part of the interface and types are only checked when the function is called and the template is instantiated. -A generic function is type checked when it is defined, and type checking can't -use any information that is only known when the function is instantiated such as -the exact argument types. Furthermore, calls to a generic function may be type -checked using only its declaration, not its body. You should be able to call a -generic function using only a forward declaration. +A checked-generic function is type checked when it is defined, and type checking +can't use any information that is only known when the function is instantiated +such as the exact argument types. Furthermore, calls to a checked-generic +function may be type checked using only its declaration, not its body. ### Predictability -A general property of generics is they are more predictable than templates. They -make clear when a type satisfies the requirements of a function; they have a -documented contract. Further, that contract is enforced by the compiler, not -sensitive to implementation details in the function body. This eases evolution -by reducing (but not eliminating) the impact of +A general property of checked generics is they are more predictable than +templates. They make clear when a type satisfies the requirements of a function; +they have a documented contract. Further, that contract is enforced by the +compiler, not sensitive to implementation details in the function body. This +eases evolution by reducing (but not eliminating) the impact of [Hyrum's law](https://www.hyrumslaw.com/). **Nice to have:** We also want well-defined boundaries between what is legal and not. This is "will my code be accepted by the compiler" predictability. We would prefer to avoid algorithms in the compiler with the form "run for up to N steps and report an error if it isn't resolved by then." For example, C++ compilers -will typically have a template recursion limit. With generics, these problems -arise due to trying to reason whether something is legal in all possible -instantiations, rather than with specific, concrete types. +will typically have a template recursion limit. With checked generics, these +problems arise due to trying to reason whether something is legal in all +possible instantiations, rather than with specific, concrete types. Some of this is likely unavoidable or too costly to avoid, as most existing -generics systems +checked generics systems [have undecidable aspects to their type system](https://3fx.ch/typing-is-hard.html), including [Rust](https://sdleffler.github.io/RustTypeSystemTuringComplete/) and [Swift](https://forums.swift.org/t/swift-type-checking-is-undecidable/39024). We @@ -343,13 +347,13 @@ we should invest in putting the proof search into IDEs or other tooling. Enable simple user control of whether to use dynamic or static dispatch. **Implementation strategy:** There are two strategies for generating code for -generic functions: +checked-generic functions: -- Static specialization strategy: Like template parameters, the values for - generic parameters must be statically known at the callsite, or known to be - a generic parameter to the calling function. This can generate separate, - specialized versions of each combination of generic and template arguments, - in order to optimize for those types or values. +- Static strategy: Like template parameters, the values for checked parameters + must be statically known at the callsite, or known to be a generic parameter + to the calling function. This can generate separate, specialized versions of + each combination of checked and template arguments, in order to optimize for + those types or values. - Dynamic strategy: This is when the compiler generates a single version of the function that uses runtime dispatch to get something semantically equivalent to separate instantiation, but likely with different size, build @@ -363,17 +367,17 @@ code analysis, specific features used in the code, or profiling -- maybe some specific specializations are needed for performance, but others would just be code bloat. -We require that all generic functions can be compiled using the static -specialization strategy. For example, the values for generic parameters must be +We require that all checked generic functions can be compiled using the static +specialization strategy. For example, the values for checked parameters must be statically known at the callsite. Other limitations are -[listed below](#specialization-strategy). +[listed below](#implementation-strategy). -**Nice to have:** It is desirable that the majority of functions with generic +**Nice to have:** It is desirable that the majority of functions with checked parameters also support the dynamic strategy. Specific features may prevent the compiler from using the dynamic strategy, but they should ideally be relatively rare, and easy to identify. Language features should avoid making it observable whether function code generated once or many times. For example, you should not -be able to take the address of a function with generic parameters, or determine +be able to take the address of a function with checked parameters, or determine if a function was instantiated more than once using function-local static variables. @@ -382,12 +386,14 @@ limit the extent it is used automatically by implementations. For example, the following features would benefit substantially from guaranteed monomorphization: - Field packing in class layout. For example, packing a `bool` into the lower - bits of a pointer, or packing bit-fields with generic widths. + bits of a pointer, or packing bit-fields with checked-parameter widths. - Allocating local variables in stack storage. Without monomorphization, we would need to perform dynamic memory allocation -- whether on the stack or - the heap -- for local variables whose sizes depend on generic parameters. + the heap -- for local variables whose sizes depend on checked parameters. - Passing parameters to functions. We cannot pass values of generic types in registers. +- Finding [specialized](terminology.md#specialization) implementations of + interfaces beyond those clearly needed from the function signature. While it is possible to address these with dynamic dispatch, handling some of them might have far-reaching and surprising performance implications. We don't @@ -407,53 +413,50 @@ acceptable even when running a development or debug build. ### Upgrade path from templates We want there to be a natural, incremental upgrade path from templated code to -generic code. -[Assuming Carbon will support templates directly](#relationship-to-templates), -the first step of migrating C++ template code would be to first convert it to a -Carbon template. The problem is then how to convert templates to generics within -Carbon. This gives us these sub-goals: - -- Users should be able to convert a single template parameter to be generic at - a time. A hybrid function with both template and generic parameters has all +checked-generic code. The first step of migrating C++ template code would be to +first convert it to a [Carbon template](#templates). The problem is then how to +convert templates to checked generics within Carbon. This gives us these +sub-goals: + +- Users should be able to convert a single template parameter to be checked at + a time. A hybrid function with both template and checked parameters has all the limitations of a template function: it can't be completely definition checked, it can't use the dynamic strategy, etc. Even so, there are still benefits from enforcing the function's declared contract for those parameters that have been converted. -- Converting from a template parameter to a generic parameter should be safe. +- Converting from a template parameter to a checked parameter should be safe. It should either work or fail to compile, never silently change semantics. - We should minimize the effort to convert functions and types from templated - to generic. Ideally it should just require specifying the type constraints, + to checked. Ideally it should just require specifying the type constraints, affecting just the signature of the function, not its body. -- **Nice to have:** It should be legal to call templated code from generic - code when it would have the same semantics as if called from non-generic - code, and an error otherwise. This is to allow more templated functions to - be converted to generics, instead of requiring them to be converted - specifically in bottom-up order. -- **Nice to have:** Provide a way to migrate from a template to a generic - without immediately updating all of the types used with the template. For - example, if the generic code requires types to implement a new interface, - one possible solution would use the original template code to provide an - implementation for that interface for any type that structurally has the - methods used by the original template. - -If Carbon does not end up having direct support for templates, the transition -will necessarily be less incremental. +- **Nice to have:** It should be legal to call templated code from + checked-generic code when it would have the same semantics as if called from + non-generic code, and an error otherwise. This is to allow more templated + functions to be converted to checked generics, instead of requiring them to + be converted specifically in bottom-up order. +- **Nice to have:** Provide a way to migrate from a template to a checked + generic without immediately updating all of the types used with the + template. For example, if the checked-generic code requires types to + implement a new interface, one possible solution would use the original + template code to provide an implementation for that interface for any type + that structurally has the methods used by the original template. ### Path from regular functions -Replacing a regular, non-parameterized function with a generic function should -not affect existing callers of the function. There may be some differences, such -as when taking the address of the function, but ordinary calls should not see -any difference. In particular, the return type of a generic function should -match, without any type erasure or additional named members. +Replacing a regular, non-parameterized function with a checked-generic function +should not affect existing callers of the function. There may be some +differences, such as when taking the address of the function, but ordinary calls +should not see any difference. In particular, the return type of a +checked-generic function should match, without any type erasure or additional +named members. ### Coherence We want the generics system to have the [_coherence_ property](terminology.md#coherence), so that the implementation of -an interface for a type is well defined. Since a generic function only depends -on interface implementations, they will always behave consistently on a given -type, independent of context. For more on this, see +an interface for a type is well defined. Since a checked-generic function only +depends on interface implementations, they will always behave consistently on a +given type, independent of context. For more on this, see [this description of what coherence is and why Rust enforces it](https://github.com/Ixrec/rust-orphan-rules#what-is-coherence). Coherence greatly simplifies the language design, since it reduces the need for @@ -490,7 +493,7 @@ We should have some mechanism for addressing these use cases. There are multiple approaches that could work: - Interface implementations could be external to types and are passed in to - generic functions separately. + checked-generic functions separately. - There could be some way to create multiple types that are compatible with a given value that you can switch between using casts to select different interface implementations. This is the approach used by Rust @@ -518,14 +521,15 @@ the definition of `T`. ### Learn from others -Many languages have implemented generics systems, and we should learn from those -experiences. We should copy what works and makes sense in the context of Carbon, -and change decisions that led to undesirable compromises. We are taking the -strongest guidance from Rust and Swift, which have similar goals and significant -experience with the implementation and usability of generics. They both use -nominal interfaces, were designed with generics from the start, and produce -native code. Contrast with Go which uses structural interfaces, or Java which -targets a virtual machine that predated its generics feature. +Many languages have implemented checked-generics systems, and we should learn +from those experiences. We should copy what works and makes sense in the context +of Carbon, and change decisions that led to undesirable compromises. We are +taking the strongest guidance from Rust and Swift, which have similar goals and +significant experience with the implementation and usability of checked +generics. They both use nominal interfaces, were designed with checked generics +from the start, and produce native code. Contrast with Go which uses structural +interfaces, or Java which targets a virtual machine that predated its generics +feature. For example, Rust has found that supporting defaults for interface methods is a valuable feature. It is useful for [evolution](#interop-and-evolution), @@ -559,8 +563,8 @@ interface, even if those methods happen to have the same signature. ### Interop and evolution [Evolution is a high priority for Carbon](/docs/project/goals.md#software-and-language-evolution), -and so will need mechanisms to support evolution when using generics. New -additions to an interface might: +and so will need mechanisms to support evolution when using checked generics. +New additions to an interface might: - need default implementations - be marked "upcoming" to allow for a period of transition @@ -610,44 +614,36 @@ Similarly, we will want some way to implement Carbon interfaces for C++ types. For example, we might have a template implementation of an `Addable` interface for any C++ type that implements `operator+`. -## What we are not doing +## What we are not doing with checked generics -What are we **not** doing with generics, particularly things that some other -languages do? +What are we **not** doing with checked generics, particularly things that some +other languages do? ### Not the full flexibility of templates -Generics don't need to provide full flexibility of C++ templates: +Checked generics don't need to provide full flexibility of C++ templates: -- The current assumption is that - [Carbon templates](#relationship-to-templates) will cover those cases that - don't fit inside generics, such as code that relies on compile-time duck - typing. +- [Carbon templates](#templates) will cover those cases that don't fit inside + checked generics, such as code that relies on compile-time duck typing. - We won't allow a specialization of some generic interface for some particular type to actually expose a _different_ interface, with different methods or different types in method signatures. This would break modular type checking. - [Template metaprogramming](https://en.wikipedia.org/wiki/Template_metaprogramming) - will not be supported by Carbon generics. We expect to address those use - cases with metaprogramming or templates in Carbon. - -### Template use cases that are out of scope + will not be supported by Carbon checked generics. We expect to address those + use cases with metaprogramming or [templates](#templates) in Carbon. +- [Expression templates](https://en.wikipedia.org/wiki/Expression_templates) + are out of scope. It may be possible to express this approach in Carbon's + checked-generics system, but they won't drive any accommodation in the + checked-generics design. -We will also not require Carbon generics to support -[expression templates](https://en.wikipedia.org/wiki/Expression_templates), -[variadics](https://en.wikipedia.org/wiki/Variadic_function), or -[variadic templates](https://en.wikipedia.org/wiki/Variadic_template). Those are -all out of scope. It would be fine for our generics system to support these -features, but they won't drive any accommodation in the generics design, at -least until we have some resolution about templates in Carbon. - -### Generics will be checked when defined +### Checked generics will be checked when defined C++ compilers must defer full type checking of templates until they are -instantiated by the user. Carbon will not defer type checking of generic +instantiated by the user. Carbon will not defer type checking of checked-generic definitions. -### Specialization strategy +### Implementation strategy We want all generic Carbon code to support [static dispatch](#dispatch-control). This means we won't support unbounded type families. Unbounded type families are @@ -671,9 +667,9 @@ types without bound. That is, calling `Sort` on a `List(Int)` would internally call `Sort` on a `List(List(Int))` and so on recursively without any static limit. -We won't require all generic Carbon code to support dynamic dispatch, but we -would like it to be an implementation option for the compiler in the majority of -cases. +We won't require all checked-generic Carbon code to support dynamic dispatch, +but we would like it to be an implementation option for the compiler in the +majority of cases. Lastly, runtime specialization is out of scope as an implementation strategy. That is, some language runtimes JIT a specialization when it is first needed, @@ -681,5 +677,8 @@ but it is not a goal for Carbon to support such an implementation strategy. ## References +Proposals: + - [#24: Generics goals](https://github.com/carbon-language/carbon-lang/pull/24) - [#950: Generic details 6: remove facets](https://github.com/carbon-language/carbon-lang/pull/950) +- [#2138: Checked and template generic terminology](https://github.com/carbon-language/carbon-lang/pull/2138) From 573840a1a23c4a5b55df1809b36a2ad01ace8ff1 Mon Sep 17 00:00:00 2001 From: Josh L Date: Sat, 15 Jul 2023 00:06:54 +0000 Subject: [PATCH 13/83] Checkpoint progress. --- docs/design/generics/goals.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/design/generics/goals.md b/docs/design/generics/goals.md index 8a781ea0d023e..7bef347863caf 100644 --- a/docs/design/generics/goals.md +++ b/docs/design/generics/goals.md @@ -482,7 +482,7 @@ cases remain: - They should be some way of selecting between multiple implementations of an interface for a given type. For example, a _Song_ might support multiple orderings, such as by title or by artist. These would be represented by - having multiple implementations of a _Comparable_ interface. + having multiple implementations of a _Ordered_ interface. - In order to allow libraries to be composed, there must be some way of saying a type implements an interface that is in another package that the authors of the type were unaware of. This is especially important since the library @@ -611,7 +611,7 @@ to work from C++, and Carbon functions could use that interface to invoke `swap` on C++ types. Similarly, we will want some way to implement Carbon interfaces for C++ types. -For example, we might have a template implementation of an `Addable` interface +For example, we might have a template implementation of an `AddWith` interface for any C++ type that implements `operator+`. ## What we are not doing with checked generics @@ -652,7 +652,7 @@ when recursion creates an infinite collection of types, such as in or: ```carbon -fn Sort[T:! Comparable](list: List(T)) -> List(T) { +fn Sort[T:! Ordered](list: List(T)) -> List(T) { if (list.size() == 1) return list; var chunks: List(List(T)) = FormChunks(list, sqrt(list.size())); chunks = chunks.ApplyToEach(Sort); @@ -661,11 +661,10 @@ fn Sort[T:! Comparable](list: List(T)) -> List(T) { } ``` -This, given an implementation of `Comparable` for any list with elements that -are themselves `Comparable`, would recursively call itself to produce a set of -types without bound. That is, calling `Sort` on a `List(Int)` would internally -call `Sort` on a `List(List(Int))` and so on recursively without any static -limit. +This, given an implementation of `Ordered` for any list with elements that are +themselves `Ordered`, would recursively call itself to produce a set of types +without bound. That is, calling `Sort` on a `List(Int)` would internally call +`Sort` on a `List(List(Int))` and so on recursively without any static limit. We won't require all checked-generic Carbon code to support dynamic dispatch, but we would like it to be an implementation option for the compiler in the From 5aa831650d67fe77a36991faa1adbad698b9bfb3 Mon Sep 17 00:00:00 2001 From: Josh L Date: Sun, 16 Jul 2023 19:54:00 +0000 Subject: [PATCH 14/83] Checkpoint progress. --- docs/design/generics/goals.md | 2 +- docs/design/generics/terminology.md | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/docs/design/generics/goals.md b/docs/design/generics/goals.md index 7bef347863caf..ae783aa9e79e2 100644 --- a/docs/design/generics/goals.md +++ b/docs/design/generics/goals.md @@ -55,7 +55,7 @@ forward-looking. Carbon will support both [checked and template generics](terminology.md#checked-versus-template-parameters) to support generic programming by way of -[parameterization of language constructs](terminology.md#parameterized-language-constructs). +[parameterization of language constructs](terminology.md#generic-means-compile-time-parameterized). Carbon's checked generics will feature [early type checking](terminology.md#early-versus-late-type-checking) and diff --git a/docs/design/generics/terminology.md b/docs/design/generics/terminology.md index 1532d3cae84e1..3172462b4e4ed 100644 --- a/docs/design/generics/terminology.md +++ b/docs/design/generics/terminology.md @@ -10,7 +10,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## Table of contents -- [Parameterized language constructs](#parameterized-language-constructs) +- [Generic means compile-time parameterized](#generic-means-compile-time-parameterized) - [Checked versus template parameters](#checked-versus-template-parameters) - [Polymorphism](#polymorphism) - [Parametric polymorphism](#parametric-polymorphism) @@ -58,12 +58,13 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -## Parameterized language constructs +## Generic means compile-time parameterized -Generally speaking, when we talk about generics, either checked or template, we -are talking about generalizing some language construct by adding a parameter to -it. Language constructs here primarily would include functions and types, but we -may want to support parameterizing other language constructs like +Generally speaking, when we talk about _generics_, either checked or template, +we are talking about generalizing some language construct by adding a +compile-time parameter, called a _generic parameter_, to it. Language constructs +here primarily would include functions and types, but we may want to support +parameterizing other language constructs like [interfaces](#interface-type-parameters-and-associated-types). This parameter broadens the scope of the language construct on an axis defined @@ -296,7 +297,7 @@ A facet type is the type used when declaring some type parameter. It foremost determines which types are legal arguments for that type parameter. For template parameters, that is all a facet type does. For checked parameters, it also determines the API that is available in the body of the definition of the -[generic function, class, or other entity](#parameterized-language-constructs). +[generic function, class, or other entity](#generic-means-compile-time-parameterized). ## Facet @@ -323,9 +324,10 @@ instead be a facet with a type that is a facet type other than `type`. ## Generic type parameter -A generic type parameter is a [parameter](#parameterized-language-constructs) -that is a [generic type](#generic-type). Equivalently, it is a parameter -declared using a `:!` binding, with or without the `template` modifier, and a +A generic type parameter is a +[parameter](#generic-means-compile-time-parameterized) that is a +[generic type](#generic-type). Equivalently, it is a parameter declared using a +`:!` binding, with or without the `template` modifier, and a [facet type](#facet-type). For example, in `class HashSet(T:! Hashable)`, `T` is a generic type parameter for the class `HashSet`, with the facet type `Hashable`. From d3c911a22f63fb16c0f600021a9108b279c6542b Mon Sep 17 00:00:00 2001 From: Josh L Date: Sun, 16 Jul 2023 19:59:17 +0000 Subject: [PATCH 15/83] Checkpoint progress. --- docs/design/generics/goals.md | 2 +- docs/design/generics/terminology.md | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/design/generics/goals.md b/docs/design/generics/goals.md index ae783aa9e79e2..2ab42d952c48c 100644 --- a/docs/design/generics/goals.md +++ b/docs/design/generics/goals.md @@ -55,7 +55,7 @@ forward-looking. Carbon will support both [checked and template generics](terminology.md#checked-versus-template-parameters) to support generic programming by way of -[parameterization of language constructs](terminology.md#generic-means-compile-time-parameterized). +[compile-time parameterization of language constructs](terminology.md#generic-means-compile-time-parameterized). Carbon's checked generics will feature [early type checking](terminology.md#early-versus-late-type-checking) and diff --git a/docs/design/generics/terminology.md b/docs/design/generics/terminology.md index 3172462b4e4ed..d45f73d6d7089 100644 --- a/docs/design/generics/terminology.md +++ b/docs/design/generics/terminology.md @@ -324,10 +324,9 @@ instead be a facet with a type that is a facet type other than `type`. ## Generic type parameter -A generic type parameter is a -[parameter](#generic-means-compile-time-parameterized) that is a -[generic type](#generic-type). Equivalently, it is a parameter declared using a -`:!` binding, with or without the `template` modifier, and a +A _generic type parameter_ is a +[generic parameter](#generic-means-compile-time-parameterized) that is a +[generic type](#generic-type). Equivalently, it is a generic parameter with a [facet type](#facet-type). For example, in `class HashSet(T:! Hashable)`, `T` is a generic type parameter for the class `HashSet`, with the facet type `Hashable`. From 38c6b19268e32fe6eee83bdc9e3dfb4993ce0122 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 28 Jul 2023 20:08:10 +0000 Subject: [PATCH 16/83] Terminology update --- docs/design/generics/details.md | 4 +- docs/design/generics/overview.md | 12 +-- docs/design/generics/terminology.md | 132 +++++++++++++++------------- 3 files changed, 77 insertions(+), 71 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 0d33388ef7eb6..eaad7c498d55c 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -2264,7 +2264,7 @@ class DynamicArray(T:! type) { ``` For context, see -["Interface type parameters and associated types" in the generics terminology document](terminology.md#interface-type-parameters-and-associated-types). +["Interface parameters and associated constants" in the generics terminology document](terminology.md#interface-parameters-and-associated-constants). **Comparison with other languages:** Both [Rust](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#specifying-placeholder-types-in-trait-definitions-with-associated-types) @@ -2314,7 +2314,7 @@ interface at most once. If instead you want a family of related interfaces, one per possible value of a type parameter, multiple of which could be implemented for a single type, you would use -[parameterized interfaces](terminology.md#interface-type-parameters-and-associated-types). +[parameterized interfaces](terminology.md#interface-parameters-and-associated-constants). To write a parameterized version of the stack interface, instead of using associated types, write a parameter list after the name of the interface instead of the associated type declaration: diff --git a/docs/design/generics/overview.md b/docs/design/generics/overview.md index 0975dbfb7f172..4804d9961a8c0 100644 --- a/docs/design/generics/overview.md +++ b/docs/design/generics/overview.md @@ -30,7 +30,7 @@ pointers to other design documents that dive deeper into individual topics. - [Named constraints](#named-constraints) - [Type erasure](#type-erasure) - [Adapting types](#adapting-types) - - [Interface input and output types](#interface-input-and-output-types) + - [Interface inputs and outputs](#interface-inputs-and-outputs) - [Associated types](#associated-types) - [Parameterized interfaces](#parameterized-interfaces) - [Constraints](#constraints) @@ -517,13 +517,13 @@ class SongByTitle { Values of type `Song` may be cast to `SongByArtist` or `SongByTitle` to get a specific sort order. -### Interface input and output types +### Interface inputs and outputs -[Associated types and interface parameters](terminology.md#interface-type-parameters-and-associated-types) +[Associated constants and interface parameters](terminology.md#interface-parameters-and-associated-constants) allow function signatures to vary with the implementing type. The biggest -difference between these is that associated types ("output types") may be -deduced from a type, and types can implement the same interface multiple times -with different interface parameters ("input types"). +difference between these is that associated constants ("outputs") may be deduced +from a type, and types can implement the same interface multiple times with +different interface parameters ("inputs"). #### Associated types diff --git a/docs/design/generics/terminology.md b/docs/design/generics/terminology.md index d45f73d6d7089..50bab8bfc0804 100644 --- a/docs/design/generics/terminology.md +++ b/docs/design/generics/terminology.md @@ -24,8 +24,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Types and `type`](#types-and-type) - [Facet type](#facet-type) - [Facet](#facet) -- [Generic type](#generic-type) -- [Generic type parameter](#generic-type-parameter) +- [Type expression](#type-expression) +- [Facet binding](#facet-binding) - [Deduced parameter](#deduced-parameter) - [Interface](#interface) - [Structural interfaces](#structural-interfaces) @@ -52,7 +52,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Template specialization](#template-specialization) - [Checked-generic specialization](#checked-generic-specialization) - [Conditional conformance](#conditional-conformance) -- [Interface type parameters and associated types](#interface-type-parameters-and-associated-types) +- [Interface parameters and associated constants](#interface-parameters-and-associated-constants) - [Type constraints](#type-constraints) - [References](#references) @@ -60,17 +60,27 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## Generic means compile-time parameterized -Generally speaking, when we talk about _generics_, either checked or template, -we are talking about generalizing some language construct by adding a -compile-time parameter, called a _generic parameter_, to it. Language constructs -here primarily would include functions and types, but we may want to support -parameterizing other language constructs like -[interfaces](#interface-type-parameters-and-associated-types). +Generally speaking, when we talk about _generics_, either +[checked or template](#checked-versus-template-parameters), we are talking about +generalizing some language construct by adding a compile-time parameter, called +a _generic parameter_, to it. So: + +- a _generic function_ is a function with at least one compile-time parameter, + which could be an explicit argument to the function or + [deduced](#deduced-parameter); +- a _generic type_ is a function with a compile-time parameter, for example a + container type parameterized by the type of the contained elements; +- a _generic interface_ is an [interface](#interface) with + [a compile-time parameter](#interface-parameters-and-associated-constants). This parameter broadens the scope of the language construct on an axis defined by that parameter, for example it could define a family of functions instead of a single one. +Note that different languages allow different things to be parameterized; for +example, Rust supports +[generic associated types](https://rust-lang.github.io/rfcs/1598-generic_associated_types.html). + ## Checked versus template parameters When we distinguish between checked and template generics in Carbon, it is on a @@ -312,27 +322,19 @@ means that a facet may be used in those positions. For example, the facet `i32 as Hashable` will implicitly convert to `(i32 as Hashable) as type`, which is `i32`, in those contexts. -## Generic type - -We use the term _generic type_ to refer to a [type](#types-and-type) or -[facet](#facet) introduced by a `:!` binding (with or without the `template` -modifier), such as in a (checked or template) generic parameter or associated -constant. In the binding `T:! Hashable`, `T` is a generic type. +## Type expression -It is worth noting that a generic type is _not_ necessarily a type. It may -instead be a facet with a type that is a facet type other than `type`. +A _type expression_ is an expression that is being used as a type. In some +cases, what is written in the source code is a value, like a [facet](#facet) or +tuple of types, that is not a type but has an implicit conversion to `type`. In +those cases, we are concerned with the type value after the implicit conversion. -## Generic type parameter +## Facet binding -A _generic type parameter_ is a -[generic parameter](#generic-means-compile-time-parameterized) that is a -[generic type](#generic-type). Equivalently, it is a generic parameter with a -[facet type](#facet-type). For example, in `class HashSet(T:! Hashable)`, `T` is -a generic type parameter for the class `HashSet`, with the facet type -`Hashable`. - -A generic type parameter declared with the `template` modifier is referred to as -a _template type parameter_, and as a _checked type parameter_ otherwise. +We use the term _facet binding_ to refer to the name introduced by a `:!` +binding pattern (with or without the `template` modifier) with a +[facet type](#facet-type). In the binding pattern `T:! Hashable`,`T` is a facet +binding, and the value of `T` is a [facet](#facet). ## Deduced parameter @@ -388,16 +390,16 @@ definition. The criteria for a named constraint, however, are less focused on the type's API and instead might include a set of nominal interfaces that the type must implement and constraints on the [associated entities](#associated-entity) and -[interface type parameters](#interface-type-parameters-and-associated-types). +[interface parameters](#interface-parameters-and-associated-constants). ## Associated entity An _associated entity_ is a requirement in an interface that a type's implementation of the interface must satisfy by having a matching definition. A requirement that the type define a value for a member constant is called an -_associated constant_, and similarly an _associated function_ or _associated -type_. Note that an associated type will be a [generic type](#generic-type), and -so may not be a type, but a [facet](#facet) usable as a type. +_associated constant_. If the type of the associated constant is a +[facet type](#facet-type), then it is called an _associated [facet](#facet)_. +Similarly, an interface can have _associated function_ or _associated method_. Different types can satisfy an interface with different definitions for a given member. These definitions are _associated_ with what type is implementing the @@ -412,7 +414,7 @@ instead of associated entity. An _impl_ is an implementation of an interface for a specific type, called the _implementing type_. It is the place where the function bodies are defined, -values for associated types, etc. are given. Implementations are needed for +values for associated constants, etc. are given. Implementations are needed for [nominal interfaces](#nominal-interfaces); [structural interfaces](#structural-interfaces) and [named constraints](#named-constraints) define conformance implicitly instead of @@ -622,7 +624,7 @@ between the generic user of a type and the concrete implementation. A simple way to imagine a witness table is as a struct of function pointers, one per method in the interface. However, in practice, it's more complex because it -must model things like associated types and interfaces. +must model things like associated facets and interfaces. Witness tables are called "dictionary passing" in Haskell. Outside of generics, a [vtable](https://en.wikipedia.org/wiki/Virtual_method_table) is a witness @@ -699,10 +701,11 @@ that it always supports, but satisfies additional interfaces under some conditions on the type argument. For example: `Array(T)` might implement `Comparable` if `T` itself implements `Comparable`, using lexicographical order. -## Interface type parameters and associated types +## Interface parameters and associated constants -_Interface type parameters_ and _associated types_ are both ways of allowing the -types in function signatures in an interface to vary. For example, different +_Interface parameters_ and [associated constants](#associated-entity) are both +ways of allowing the types in function signatures in an interface to vary. For +example, different [stacks]() will have different element types. That element type would be used as the parameter type of the `Push` function and the return type of the `Pop` function. As @@ -710,19 +713,18 @@ of the `Push` function and the return type of the `Pop` function. As we can distinguish these by whether they are input parameters or output parameters: -- An interface type parameter is a parameter or input to the interface type. - That means they must be specified before an implementation of the interface - may be determined. -- In contrast, associated types are outputs. This means that they are - determined by the implementation, and need not be specified in a type - constraint. +- An interface parameter is a parameter or input to the interface. That means + they must be specified before an implementation of the interface may be + determined. +- In contrast, associated constants are outputs. This means that they are + determined by the implementation, and need not be specified in a + [type constraint](#type-constraints). Functions using an interface as a constraint need not specify the value of its -associated types. An associated type is a kind of -[associated entity](#associated-entity). +associated constants. ``` -// Stack using associated types +// Stack using associated facets interface Stack { let ElementType:! type; fn Push[addr self: Self*](value: ElementType); @@ -731,7 +733,7 @@ interface Stack { // Works on any type implementing `Stack`. Return type // is determined by the type's implementation of `Stack`. -fn PeekAtTopOfStack[T: Stack](s: T*) -> T.ElementType { +fn PeekAtTopOfStack[T:! Stack](s: T*) -> T.ElementType { let ret: T.ElementType = s->Pop(); s->Push(ret); return ret; @@ -745,17 +747,17 @@ class FruitStack { } ``` -Associated types are particularly called for when the implementation of the -interface determines the type, not the caller. For example, the iterator type +Associated constants are particularly called for when the implementation of the +interface determines the value, not the caller. For example, the iterator type for a container is specific to the container and not something you would expect a user of the interface to specify. -If you have an interface with type parameters, a type can have multiple matching -impl declarations for different combinations of type parameters. As a result, -type parameters may not be deduced in a function call. However, if the interface -parameters are specified, a type can only have a single implementation of the -given interface. This unique implementation choice determines the values of -associated types. +If you have an interface with parameters, a type can have multiple matching +`impl` declarations for different combinations of argument values. As a result, +interface parameters may not be deduced in a function call. However, if the +interface parameters are specified, a type can only have a single implementation +of the given interface. This unique implementation choice determines the values +of associated constants. For example, we might have an interface that says how to perform addition with another type: @@ -789,8 +791,8 @@ fn DoAdd[T:! type, U:! AddWith(T)](x: U, y: T) -> U.ResultType { fn CompileError[T:! type, U:! AddWith(T)](x: U) -> T; ``` -Once the interface parameter can be determined, that determines the values for -associated types, such as `ResultType` in the example. As always, calls with +Once the interface parameters can be determined, that determines the values for +associated constants, such as `ResultType` in the example. As always, calls with types for which no implementation exists will be rejected at the call site: ``` @@ -799,28 +801,32 @@ types for which no implementation exists will be rejected at the call site: DoAdd(apple, orange); ``` +The type of an interface parameters and associated constants is commonly a +[facet type](#facet-type), but not always. For example, one might have an +integer type and be used to specify the size of an array type. + ## Type constraints -Type constraints restrict which types are legal for template or checked -parameters or associated types. They help define semantics under which they -should be called, and prevent incorrect calls. +Type constraints restrict which types are legal for generic parameters or +associated facets. They help define semantics under which they should be called, +and prevent incorrect calls. In general there are a number of different type relationships we would like to express, for example: - This function accepts two containers. The container types may be different, but the element types need to match. -- For this container interface we have associated types for iterators and +- For this container interface we have associated facets for iterators and elements. The iterator type's element type needs to match the container's element type. -- An interface may define an associated type that needs to be constrained to +- An interface may define an associated facet that needs to be constrained to implement some interfaces. - This type must be [compatible](#compatible-types) with another type. You might use this to define alternate implementations of a single interfaces, such as sorting order, for a single type. -Note that type constraints can be a restriction on one type parameter or -associated type, or can define a relationship between multiple types. +Note that type constraints can be a restriction on one facet parameter or +associated facet, or can define a relationship between multiple facets. ## References From fb26030fdc78f17532a6ba70983bb2a5cebccac3 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 28 Jul 2023 22:45:47 +0000 Subject: [PATCH 17/83] Checkpoint progress. --- docs/design/README.md | 22 +-- docs/design/generics/details.md | 238 ++++++++++++++-------------- docs/design/generics/overview.md | 19 +-- docs/design/generics/terminology.md | 8 +- 4 files changed, 148 insertions(+), 139 deletions(-) diff --git a/docs/design/README.md b/docs/design/README.md index b4a4fdfcc44eb..03b381670e3ce 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -87,7 +87,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Checked and template parameters](#checked-and-template-parameters) - [Interfaces and implementations](#interfaces-and-implementations) - [Combining constraints](#combining-constraints) - - [Associated types](#associated-types) + - [Associated constants](#associated-constants) - [Generic entities](#generic-entities) - [Generic Classes](#generic-classes) - [Generic choice types](#generic-choice-types) @@ -2700,7 +2700,7 @@ In addition to function requirements, interfaces can contain: - [requirements that other interfaces be implemented](generics/details.md#interface-requiring-other-interfaces) or [interfaces that this interface extends](generics/details.md#interface-extension) -- [associated types](generics/details.md#associated-types) and other +- [associated facets](generics/details.md#associated-facets) and other [associated constants](generics/details.md#associated-constants) - [interface defaults](generics/details.md#interface-defaults) - [`final` interface members](generics/details.md#final-members) @@ -2800,9 +2800,9 @@ fn DrawTies[T:! Renderable & GameResult](x: T) { > - Proposal > [#553: Generics details part 1](https://github.com/carbon-language/carbon-lang/pull/553) -### Associated types +### Associated constants -An associated type is a type member of an interface whose value is determined by +An associated constant is a member of an interface whose value is determined by the implementation of that interface for a specific type. These values are set to compile-time values in implementations, and so use the [`:!` generic syntax](#checked-and-template-parameters) inside a @@ -2810,7 +2810,7 @@ to compile-time values in implementations, and so use the allows types in the signatures of functions in the interface to vary. For example, an interface describing a [stack]() might use an -associated type to represent the type of elements stored in the stack. +associated constant to represent the type of elements stored in the stack. ``` interface StackInterface { @@ -2821,8 +2821,8 @@ interface StackInterface { } ``` -Then different types implementing `StackInterface` can specify different type -values for the `ElementType` member of the interface using a `where` clause: +Then different types implementing `StackInterface` can specify different values +for the `ElementType` member of the interface using a `where` clause: ```carbon class IntStack { @@ -2842,7 +2842,7 @@ class FruitStack { > References: > -> - [Generics: Associated types](generics/details.md#associated-types) +> - [Generics: Associated constants](generics/details.md#associated-constants) > - Proposal > [#731: Generics details 2: adapters, associated types, parameterized interfaces](https://github.com/carbon-language/carbon-lang/pull/731) > - Proposal @@ -2924,8 +2924,8 @@ a type can have distinct implementations of `AddWith(i32)` and `AddWith(BigInt)`. Parameters to an interface _determine_ which implementation is selected for a -type, in contrast to [associated types](#associated-types) which are _determined -by_ the implementation of an interface for a type. +type, in contrast to [associated constants](#associated-constants) which are +_determined by_ the implementation of an interface for a type. > References: > @@ -2996,7 +2996,7 @@ Carbon generics have a number of other features, including: same data representation as an existing type, so you may cast between the two types, but can implement different interfaces or implement interfaces differently. -- Additional requirements can be placed on the associated types of an +- Additional requirements can be placed on the associated constants of an interface using [`where` constraints](generics/details.md#where-constraints). - [Implied constraints](generics/details.md#implied-constraints) allows some diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index eaad7c498d55c..1dce9be96101c 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -45,7 +45,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Adapter with stricter invariants](#adapter-with-stricter-invariants) - [Associated constants](#associated-constants) - [Associated class functions](#associated-class-functions) -- [Associated types](#associated-types) +- [Associated facets](#associated-facets) - [Implementation model](#implementation-model-1) - [Parameterized interfaces](#parameterized-interfaces) - [Impl lookup](#impl-lookup) @@ -54,12 +54,12 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Constraint use cases](#constraint-use-cases) - [Set an associated constant to a specific value](#set-an-associated-constant-to-a-specific-value) - [Same type constraints](#same-type-constraints) - - [Set an associated type to a specific value](#set-an-associated-type-to-a-specific-value) - - [Equal generic types](#equal-generic-types) + - [Set an associated facet to a specific value](#set-an-associated-facet-to-a-specific-value) + - [Equal facet bindings](#equal-facet-bindings) - [Satisfying both facet types](#satisfying-both-facet-types) - - [Type bound for associated type](#type-bound-for-associated-type) - - [Type bounds on associated types in declarations](#type-bounds-on-associated-types-in-declarations) - - [Type bounds on associated types in interfaces](#type-bounds-on-associated-types-in-interfaces) + - [Type bound for associated facet](#type-bound-for-associated-facet) + - [Type bounds on associated facets in declarations](#type-bounds-on-associated-facets-in-declarations) + - [Type bounds on associated facets in interfaces](#type-bounds-on-associated-facets-in-interfaces) - [Combining constraints](#combining-constraints) - [Recursive constraints](#recursive-constraints) - [Parameterized type implements interface](#parameterized-type-implements-interface) @@ -127,8 +127,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Evolution](#evolution) - [Testing](#testing) - [Impl with state](#impl-with-state) - - [Generic associated types and higher-ranked types](#generic-associated-types-and-higher-ranked-types) - - [Generic associated types](#generic-associated-types) + - [Generic associated facets and higher-ranked facets](#generic-associated-facets-and-higher-ranked-facets) + - [Generic associated facets](#generic-associated-facets) - [Higher-ranked types](#higher-ranked-types) - [Field requirements](#field-requirements) - [Bridge for C++ customization points](#bridge-for-c-customization-points) @@ -174,7 +174,7 @@ member of the type) could be a member of an interface and its implementation. There are a few cases why we would include another interface implementation as a member: -- [associated types](#associated-types) +- [associated facets](#associated-facets) - [type parameters](#parameterized-interfaces) - [interface requirements](#interface-requiring-other-interfaces) @@ -991,7 +991,7 @@ members of those interfaces. To declare a named constraint that includes other declarations for use with template parameters, use the `template` keyword before `constraint`. Method, -associated type, and associated function requirements may only be declared +associated constant, and associated function requirements may only be declared inside a `template constraint`. Note that a generic constraint ignores the names of members defined for a type, but a template constraint can depend on them. @@ -1371,8 +1371,8 @@ interface SetAlgebra { **Alternative considered:** The `extend` declarations are in the body of the `interface` definition instead of the header so we can use -[associated types (defined below)](#associated-types) also defined in the body -in parameters or constraints of the interface being extended. +[associated constants](terminology.md#associated-entity) also defined in the +body in parameters or constraints of the interface being extended. ``` // A type can implement `ConvertibleTo` many times, using @@ -1381,8 +1381,8 @@ interface ConvertibleTo(T:! type) { ... } // A type can only implement `PreferredConversion` once. interface PreferredConversion { - let AssociatedType:! type; - extend ConvertibleTo(AssociatedType); + let AssociatedFacet:! type; + extend ConvertibleTo(AssociatedFacet); } ``` @@ -2139,19 +2139,19 @@ Together associated methods and associated class functions are called _associated functions_, much like together methods and class functions are called [member functions](/docs/design/classes.md#member-functions). -## Associated types +## Associated facets -Associated types are [associated entities](terminology.md#associated-entity) -that happen to be types. These are particularly interesting since they can be -used in the signatures of associated methods or functions, to allow the -signatures of methods to vary from implementation to implementation. We already -have one example of this: the `Self` type discussed -[in the "Interfaces" section](#interfaces). For other cases, we can say that the -interface declares that each implementation will provide a type under a specific -name. For example: +Associated facets are [associated entities](terminology.md#associated-entity) +that happen to have a [facet type](terminology.md#facet-type). These are +particularly interesting since they can be used in the signatures of associated +methods or functions, to allow the signatures of methods to vary from +implementation to implementation. We already have one example of this: the +`Self` type discussed [in the "Interfaces" section](#interfaces). For other +cases, we can say that the interface declares that each implementation will +provide a facet under a specific name. For example: ``` -interface StackAssociatedType { +interface StackAssociatedFacet { let ElementType:! type; fn Push[addr self: Self*](value: ElementType); fn Pop[addr self: Self*]() -> ElementType; @@ -2159,11 +2159,11 @@ interface StackAssociatedType { } ``` -Here we have an interface called `StackAssociatedType` which defines two +Here we have an interface called `StackAssociatedFacet` which defines two methods, `Push` and `Pop`. The signatures of those two methods declare them as accepting or returning values with the type `ElementType`, which any implementer -of `StackAssociatedType` must also define. For example, maybe `DynamicArray` -implements `StackAssociatedType`: +of `StackAssociatedFacet` must also define. For example, maybe `DynamicArray` +implements `StackAssociatedFacet`: ``` class DynamicArray(T:! type) { @@ -2173,8 +2173,8 @@ class DynamicArray(T:! type) { fn Insert[addr self: Self*](pos: IteratorType, value: T); fn Remove[addr self: Self*](pos: IteratorType); - // Set the associated type `ElementType` to `T`. - extend impl as StackAssociatedType where .ElementType = T { + // Set the associated facet `ElementType` to `T`. + extend impl as StackAssociatedFacet where .ElementType = T { fn Push[addr self: Self*](value: ElementType) { self->Insert(self->End(), value); } @@ -2195,7 +2195,7 @@ class DynamicArray(T:! type) { The keyword `Self` can be used after the `as` in an `impl` declaration as a shorthand for the type being implemented, including in the `where` clause -specifying the values of associated types, as in: +specifying the values of associated facets, as in: ``` impl VeryLongTypeName as Add @@ -2206,16 +2206,16 @@ impl VeryLongTypeName as Add ``` **Alternatives considered:** See -[other syntax options considered in #731 for specifying associated types](/proposals/p0731.md#syntax-for-associated-constants). +[other syntax options considered in #731 for specifying associated facets](/proposals/p0731.md#syntax-for-associated-constants). In particular, it was deemed that -[Swift's approach of inferring the associated type from method signatures in the impl](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID190) +[Swift's approach of inferring an associated facet from method signatures in the impl](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID190) was unneeded complexity. -The definition of the `StackAssociatedType` is sufficient for writing a generic +The definition of the `StackAssociatedFacet` is sufficient for writing a generic function that operates on anything implementing that interface, for example: ``` -fn PeekAtTopOfStack[StackType:! StackAssociatedType](s: StackType*) +fn PeekAtTopOfStack[StackType:! StackAssociatedFacet](s: StackType*) -> StackType.ElementType { var top: StackType.ElementType = s->Pop(); s->Push(top); @@ -2224,11 +2224,11 @@ fn PeekAtTopOfStack[StackType:! StackAssociatedType](s: StackType*) ``` Inside the generic function `PeekAtTopOfStack`, the `ElementType` associated -type member of `StackType` is erased. This means `StackType.ElementType` has the -API dictated by the declaration of `ElementType` in the interface -`StackAssociatedType`. +facet member of `StackType` is erased. This means `StackType.ElementType` has +the API dictated by the declaration of `ElementType` in the interface +`StackAssociatedFacet`. -Outside the generic, associated types have the concrete type values determined +Outside the generic, associated facets have the concrete facet values determined by impl lookup, rather than the erased version of that type used inside a generic. @@ -2243,7 +2243,7 @@ This is another part of achieving [the goal that generic functions can be used in place of regular functions without changing the return type that callers see](goals.md#path-from-regular-functions) discussed in the [return type section](#return-type). -Associated types can also be implemented using a +Associated facets can also be implemented using a [member type](/docs/design/classes.md#member-type). ``` @@ -2269,11 +2269,11 @@ For context, see **Comparison with other languages:** Both [Rust](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#specifying-placeholder-types-in-trait-definitions-with-associated-types) and [Swift](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID189) -support associated types. +support these, but call them "associated types." ### Implementation model -The associated type can be modeled by a witness table field in the interface's +The associated facet can be modeled by a witness table field in the interface's witness table. ``` @@ -2308,7 +2308,7 @@ class Container(Self:! type) { ## Parameterized interfaces -Associated types don't change the fact that a type can only implement an +Associated constants don't change the fact that a type can only implement an interface at most once. If instead you want a family of related interfaces, one per possible value of a @@ -2316,8 +2316,8 @@ type parameter, multiple of which could be implemented for a single type, you would use [parameterized interfaces](terminology.md#interface-parameters-and-associated-constants). To write a parameterized version of the stack interface, instead of using -associated types, write a parameter list after the name of the interface instead -of the associated type declaration: +associated constants, write a parameter list after the name of the interface +instead of the associated constant declaration: ``` interface StackParameterized(ElementType:! type) { @@ -2359,9 +2359,9 @@ class Produce { } ``` -Unlike associated types in interfaces and parameters to types, interface +Unlike associated constants in interfaces and parameters to types, interface parameters can't be deduced. For example, if we were to rewrite -[the `PeekAtTopOfStack` example in the "associated types" section](#associated-types) +[the `PeekAtTopOfStack` example in the "associated facets" section](#associated-facets) for `StackParameterized(T)` it would generate a compile error: ``` @@ -2469,15 +2469,15 @@ class ReverseLookup(FromType:! type, ToType:! type) { ``` **Comparison with other languages:** Rust calls -[traits with type parameters "generic traits"](https://doc.rust-lang.org/reference/items/traits.html#generic-traits) +[traits with parameters "generic traits"](https://doc.rust-lang.org/reference/items/traits.html#generic-traits) and [uses them for operator overloading](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#default-generic-type-parameters-and-operator-overloading). [Rust uses the term "type parameters"](https://github.com/rust-lang/rfcs/blob/master/text/0195-associated-items.md#clearer-trait-matching) -for both interface type parameters and associated types. The difference is that -interface parameters are "inputs" since they _determine_ which `impl` to use, -and associated types are "outputs" since they are determined _by_ the `impl`, -but play no role in selecting the `impl`. +for both interface facet parameters and associated facets. The difference is +that interface parameters are "inputs" since they _determine_ which `impl` to +use, and associated constants are "outputs" since they are determined _by_ the +`impl`, but play no role in selecting the `impl`. ### Impl lookup @@ -2521,16 +2521,16 @@ support parameters. Parameters would work the same way as for interfaces. ## Where constraints -So far, we have restricted a generic type parameter by saying it has to +So far, we have restricted a generic facet parameter by saying it has to implement an interface or a set of interfaces. There are a variety of other constraints we would like to be able to express, such as applying restrictions -to its associated types and associated constants. This is done using the `where` -operator that adds constraints to a facet type. +to its associated constants. This is done using the `where` operator that adds +constraints to a facet type. The where operator can be applied to a facet type in a declaration context: ``` -// Constraints on function parameters: +// Constraints on generic function parameters: fn F[V:! D where ...](v: V) { ... } // Constraints on a class parameter: @@ -2604,10 +2604,11 @@ The "dot followed by the name of a member" construct, `.N` in the examples above, is called a _designator_. A designator refers to the value of that member for whatever type is to satisfy this constraint. -To name such a constraint, you may use a `let` or a `constraint` declaration: +To name such a constraint, you may use a `let template` or a `constraint` +declaration: ``` -let Point2DInterface:! auto = NSpacePoint where .N = 2; +let template Point2DInterface:! auto = NSpacePoint where .N = 2; constraint Point2DInterface { extend NSpacePoint where .N = 2; } @@ -2634,10 +2635,10 @@ interface PointCloud { #### Same type constraints -##### Set an associated type to a specific value +##### Set an associated facet to a specific value Functions accepting a generic type might also want to constrain one of its -associated types to be a specific, concrete type. For example, we might want to +associated facets to be a specific, concrete type. For example, we might want to have a function only accept stacks containing integers: ``` @@ -2651,25 +2652,27 @@ fn SumIntStack[T:! Stack where .ElementType = i32](s: T*) -> i32 { } ``` -To name these sorts of constraints, we could use `let` declarations or +To name these sorts of constraints, we could use `let template` declarations or `constraint` definitions: ``` -let IntStack:! auto = Stack where .ElementType = i32; +let template IntStack:! auto = Stack where .ElementType = i32; constraint IntStack { extend Stack where .ElementType = i32; } ``` This syntax is also used to specify the values of -[associated types](#associated-types) when implementing an interface for a type. +[associated facets](#associated-facets) when implementing an interface for a +type. -##### Equal generic types +##### Equal facet bindings -Alternatively, two generic types could be constrained to be equal to each other, -without specifying what that type is. This uses `==` instead of `=`. For -example, we could make the `ElementType` of an `Iterator` interface equal to the -`ElementType` of a `Container` interface as follows: +Alternatively, two [facet bindings](terminology.md#facet-binding) could be +constrained to be equal to each other, without specifying what the facet value +is. This uses `==` instead of `=`. For example, we could make the `ElementType` +of an `Iterator` interface equal to the `ElementType` of a `Container` interface +as follows: ``` interface Iterator { @@ -2683,7 +2686,7 @@ interface Container { } ``` -Given an interface with two associated types +Given an interface with two associated facets ``` interface PairInterface { @@ -2710,14 +2713,14 @@ interface HasEqualPair { This kind of constraint can be named: ``` -let EqualPair:! auto = +let template EqualPair:! auto = PairInterface where .Left == .Right; constraint EqualPair { extend PairInterface where .Left == .Right; } ``` -Another example of same type constraints is when associated types of two +Another example of same type constraints is when associated facets of two different interfaces are constrained to be equal: ``` @@ -2763,13 +2766,13 @@ fn Contains (haystack: SC, needles: CT) -> bool; ``` -#### Type bound for associated type +#### Type bound for associated facet -A `where` clause can express that a type must implement an interface. This is -more flexible than the usual approach of including that interface in the type -since it can be applied to associated type members as well. +A `where` clause can express that a facet binding must implement an interface. +This is more flexible than the usual approach of including that interface in the +type since it can be applied to associated facet members as well. -##### Type bounds on associated types in declarations +##### Type bounds on associated facets in declarations In the following example, normally the `ElementType` of a `Container` can be any type. The `SortContainer` function, however, takes a pointer to a type @@ -2797,7 +2800,7 @@ type. `Container`. This means we need to be a bit careful when talking about the type of `ContainerType` when there is a `where` clause modifying it. -##### Type bounds on associated types in interfaces +##### Type bounds on associated facets in interfaces Given these definitions (omitting `ElementType` for brevity): @@ -2814,7 +2817,7 @@ interface RandomAccessIterator { ``` We can then define a function that only accepts types that implement -`ContainerInterface` where its `IteratorType` associated type implements +`ContainerInterface` where its `IteratorType` associated facet implements `RandomAccessIterator`: ``` @@ -2828,7 +2831,7 @@ We would like to be able to name this constraint, defining a `ContainerInterface` with an `IteratorType` satisfying `RandomAccessIterator`. ``` -let RandomAccessContainer:! auto = +let template RandomAccessContainer:! auto = ContainerInterface where .IteratorType impls RandomAccessIterator; // or constraint RandomAccessContainer { @@ -2847,8 +2850,8 @@ fn F[ContainerType:! ContainerInterface #### Combining constraints Constraints can be combined by separating constraint clauses with the `and` -keyword. This example expresses a constraint that two associated types are equal -and satisfy an interface: +keyword. This example expresses a constraint that two associated facets are +equal and satisfy an interface: ``` fn EqualContainers @@ -2865,9 +2868,9 @@ type in a parameter list that is already using commas to separate parameters. #### Recursive constraints -We sometimes need to constrain a type to equal one of its associated types. In +We sometimes need to constrain a type to equal one of its associated facets. In this first example, we want to represent the function `Abs` which will return -`Self` for some but not all types, so we use an associated type `MagnitudeType` +`Self` for some but not all types, so we use an associated facet `MagnitudeType` to encode the return type: ``` @@ -2890,9 +2893,9 @@ the original container type. However, taking the slice of a slice always gives you the same type, and some functions want to only operate on containers whose slice type is the same as the container type. -To solve this problem, we think of `Self` as an actual associated type member of -every interface. We can then address it using `.Self` in a `where` clause, like -any other associated type member. +To solve this problem, we think of `Self` as an actual associated facet member +of every interface. We can then address it using `.Self` in a `where` clause, +like any other associated facet member. ``` fn Relu[T:! HasAbs where .MagnitudeType == .Self](x: T) { @@ -2906,7 +2909,7 @@ fn UseContainer[T:! Container where .SliceType == .Self](c: T) -> bool { ``` Notice that in an interface definition, `Self` refers to the type implementing -this interface while `.Self` refers to the associated type currently being +this interface while `.Self` refers to the associated facet currently being defined. ``` @@ -2925,11 +2928,11 @@ interface Container { These recursive constraints can be named: ``` -let RealAbs:! auto = HasAbs where .MagnitudeType == .Self; +let template RealAbs:! auto = HasAbs where .MagnitudeType == .Self; constraint RealAbs { extend HasAbs where .MagnitudeType == Self; } -let ContainerIsSlice:! auto = +let template ContainerIsSlice:! auto = Container where .SliceType == .Self; constraint ContainerIsSlice { extend Container where .SliceType == Self; @@ -3124,7 +3127,7 @@ say that the `EdgesFrom` would only be conditionally available when `Edge` does satisfy the constraints on `HashSet` arguments. Instead, Carbon will reject this definition, requiring the user to include all the constraints required for the other declarations in the interface in the declaration of the `Edge` associated -type. Similarly, a parameter to a class must be declared with all the +facet. Similarly, a parameter to a class must be declared with all the constraints needed to declare the members of the class that depend on that parameter. @@ -3198,8 +3201,8 @@ means that if two type expressions are only transitively equal, the user will need to include a sequence of casts or use an [`observe` declaration](#observe-declarations) to convert between them. -Given this interface `Transitive` that has associated types that are constrained -to all be equal, with interfaces `P`, `Q`, and `R`: +Given this interface `Transitive` that has associated facets that are +constrained to all be equal, with interfaces `P`, `Q`, and `R`: ``` interface P { fn InP[self: Self](); } @@ -3273,8 +3276,8 @@ fn G[T:! Transitive](t: T) { ``` The compiler may have several different `where` clauses to consider, -particularly when an interface has associated types that recursively satisfy the -same interface. For example, given this interface `Commute`: +particularly when an interface has associated facets that recursively satisfy +the same interface. For example, given this interface `Commute`: ``` interface Commute { @@ -3374,7 +3377,7 @@ prior to `X.Y.Y.X`. After an `observe` declaration, all of the listed type expressions are considered equal to each other using a single `where` equality. In this example, the `observe` declaration in the `Transitive` interface definition provides the -link between associated types `A` and `C` that allows function `F` to type +link between associated facets `A` and `C` that allows function `F` to type check. ``` @@ -3417,8 +3420,8 @@ interfaces to generic types, they may be added without breaking existing code. ## Other constraints as facet types There are some constraints that we will naturally represent as named facet -types. These can either be used directly to constrain a generic type parameter, -or in a `where ... impls ...` clause to constrain an associated type. +types. These can either be used directly to constrain a facet binding, or in a +`where ... impls ...` clause to constrain an associated facet. The compiler determines which types implement these interfaces, developers can not explicitly implement these interfaces for their own types. @@ -3579,7 +3582,7 @@ class ThenCompare( } } -let SongByArtistThenTitle: auto = +let template SongByArtistThenTitle: auto = ThenCompare(Song, (SongByArtist, SongByTitle)); var s1: Song = ...; var s2: SongByArtistThenTitle = @@ -4459,8 +4462,8 @@ Since we do not require the compiler to compare the definitions of functions, agreement is only possible for interfaces without any function members. If the Carbon compiler sees a matching `final` impl, it can assume it won't be -specialized so it can use the assignments of the associated types in that impl -definition. +specialized so it can use the assignments of the associated constants in that +impl definition. ``` fn F[T:! type](x: T) { @@ -4533,9 +4536,9 @@ differences between the Carbon and Rust plans: declared `final`. - Since a Rust impl is not specializable by default, generic functions can assume that if a matching blanket impl declaration is found, the associated - types from that impl will be used. In Carbon, if a generic function requires - an associated type to have a particular value, the function commonly will - need to state that using an explicit constraint. + constants from that impl will be used. In Carbon, if a generic function + requires an associated constant to have a particular value, the function + commonly will need to state that using an explicit constraint. - Carbon will not have the "fundamental" attribute used by Rust on types or traits, as described in [Rust RFC 1023: "Rebalancing Coherence"](https://rust-lang.github.io/rfcs/1023-rebalancing-coherence.html). @@ -4683,8 +4686,8 @@ The declaration of an interface implementation consists of: - a [facet type](#facet-types), including an optional [parameter pattern](#parameterized-interfaces) and [`where` clause](#where-constraints) assigning - [associated constants](#associated-constants) and - [associated types](#associated-types). + [associated constants](#associated-constants) including + [associated facets](#associated-facets). **Note:** The `extend` keyword, when present, is not part of the declaration. It precedes the `impl` declaration in class scope. @@ -4738,7 +4741,8 @@ after name and alias resolution. To agree: evaluation of type expressions is performed. Interface implementation declarations match if the type and interface -expressions match: +expressions match along with +[the `forall` clause](#parameterized-impl-declarations), if any: - If the type part is omitted, it is rewritten to `Self` in the context of the declaration. @@ -4746,7 +4750,7 @@ expressions match: scope, this should match the type name and optional parameter expression after `class`. So in `class MyClass { ... }`, `Self` is rewritten to `MyClass`. In `class Vector(T:! Movable) { ... }`, `Self` is rewritten to - `Vector(T:! Movable)`. + `forall [T:! Movable] Vector(T)`. - Types match if they have the same name after name and alias resolution and the same parameters, or are the same type parameter. - Interfaces match if they have the same name after name and alias resolution @@ -4856,8 +4860,8 @@ impl MyClass as Interface6 where _ { } ### Example of declaring interfaces with cyclic references -In this example, `Node` has an `EdgeType` associated type that is constrained to -implement `Edge`, and `Edge` has a `NodeType` associated type that is +In this example, `Node` has an `EdgeType` associated facet that is constrained +to implement `Edge`, and `Edge` has a `NodeType` associated facet that is constrained to implement `Node`. Furthermore, the `NodeType` of an `EdgeType` is the original type, and the other way around. This is accomplished by naming and then forward declaring the constraints that can't be stated directly: @@ -4993,7 +4997,7 @@ developers desire. As an example, in Rust the has one required method but dozens of "provided methods" with defaults. Defaults may also be provided for associated constants, such as associated -types, and interface parameters, using the `= ` syntax. +facets, and interface parameters, using the `= ` syntax. ``` interface Add(Right:! type = Self) { @@ -5007,7 +5011,7 @@ impl String as Add() { } ``` -Note that `Self` is a legal default value for an associated type or type +Note that `Self` is a legal default value for an associated facet or facet parameter. In this case the value of those names is not determined until `Self` is, so `Add()` is equivalent to the constraint: @@ -5021,7 +5025,7 @@ constraint AddDefault { Note also that the parenthesis are required after `Add`, even when all parameters are left as their default values. -More generally, default expressions may reference other associated types or +More generally, default expressions may reference other associated constants or `Self` as parameters to type constructors. For example: ``` @@ -5259,7 +5263,7 @@ interface B(T:! type) { An implementation of `B` for a set of types can only be valid if there is a visible implementation of `A` with the same `T` parameter for those types with -the `.Result` associated type set to `i32`. That is +the `.Result` associated facet set to `i32`. That is [not sufficient](/proposals/p1088.md#less-strict-about-requirements-with-where-clauses), though, unless the implementation of `A` can't be specialized, either because it is [marked `final`](#final-impl-declarations) or is not @@ -5931,14 +5935,14 @@ expected behavior of any type implementing that interface. A feature we might consider where an `impl` itself can have state. -### Generic associated types and higher-ranked types +### Generic associated facets and higher-ranked facets This would be some way to express the requirement that there is a way to go from a type to an implementation of an interface parameterized by that type. -#### Generic associated types +#### Generic associated facets -Generic associated types are about when this is a requirement of an interface. +Generic associated facets are about when this is a requirement of an interface. These are also called "associated type constructors." Rust has @@ -5948,7 +5952,7 @@ Rust has Higher-ranked types are used to represent this requirement in a function signature. They can be -[emulated using generic associated types](https://smallcultfollowing.com/babysteps//blog/2016/11/03/associated-type-constructors-part-2-family-traits/). +[emulated using generic associated facets](https://smallcultfollowing.com/babysteps//blog/2016/11/03/associated-type-constructors-part-2-family-traits/). ### Field requirements @@ -5980,7 +5984,7 @@ types at runtime, and there are also concerns about reasoning about comparisons between multiple generic integer parameters. For example, if `J < K` and `K <= L`, can we call a function that requires `J < L`? There is also a 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`. +parameter, as opposed to an associated facet, as in `N:! u32 where ___ >= 2`. ## References diff --git a/docs/design/generics/overview.md b/docs/design/generics/overview.md index 4804d9961a8c0..954d91cb60794 100644 --- a/docs/design/generics/overview.md +++ b/docs/design/generics/overview.md @@ -31,7 +31,7 @@ pointers to other design documents that dive deeper into individual topics. - [Type erasure](#type-erasure) - [Adapting types](#adapting-types) - [Interface inputs and outputs](#interface-inputs-and-outputs) - - [Associated types](#associated-types) + - [Associated constants](#associated-constants) - [Parameterized interfaces](#parameterized-interfaces) - [Constraints](#constraints) - [Parameterized impl declarations](#parameterized-impl-declarations) @@ -525,12 +525,12 @@ difference between these is that associated constants ("outputs") may be deduced from a type, and types can implement the same interface multiple times with different interface parameters ("inputs"). -#### Associated types +#### Associated constants -Expect types that vary in an interface to be associated types by default. Since -associated types may be deduced, they are more convenient to use. Imagine a -`Stack` interface. Different types implementing `Stack` will have different -element types: +Expect parts of function signatures that vary in an interface to be associated +constants by default. Since associated constants may be deduced, they are more +convenient to use. Imagine a `Stack` interface. Different types implementing +`Stack` will have different element types: ``` interface Stack { @@ -541,7 +541,7 @@ interface Stack { } ``` -`ElementType` is an associated type of the interface `Stack`. Types that +`ElementType` is an associated constant of the interface `Stack`. Types that implement `Stack` give `ElementType` a specific value of some type implementing `Movable`. Functions that accept a type implementing `Stack` can deduce the `ElementType` from the stack type. @@ -581,7 +581,8 @@ fn FindInVector[T:! type, U:! Equatable(T)](v: Vector(T), needle: U) // ❌ This is forbidden. Since `U` could implement `Equatable` // multiple times, there is no way to determine the value for `T`. -// Contrast with `PeekAtTopOfStack` in the associated type example. +// Contrast with `PeekAtTopOfStack` in the associated constant +// example. fn CompileError[T:! type, U:! Equatable(T)](x: U) -> T; ``` @@ -608,7 +609,7 @@ 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). +of associated constants. ``` class Vector(T:! Movable) { diff --git a/docs/design/generics/terminology.md b/docs/design/generics/terminology.md index 50bab8bfc0804..0956aac0461b9 100644 --- a/docs/design/generics/terminology.md +++ b/docs/design/generics/terminology.md @@ -398,8 +398,12 @@ An _associated entity_ is a requirement in an interface that a type's implementation of the interface must satisfy by having a matching definition. A requirement that the type define a value for a member constant is called an _associated constant_. If the type of the associated constant is a -[facet type](#facet-type), then it is called an _associated [facet](#facet)_. -Similarly, an interface can have _associated function_ or _associated method_. +[facet type](#facet-type), then it is called an _associated [facet](#facet)_, +which corresponds to what is called an "associated type" in other languages +([Swift](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/generics/#Associated-Types), +[Rust](https://doc.rust-lang.org/reference/items/associated-items.html#associated-types)). +Similarly, an interface can have _associated function_, _associated method_, or +_associated class function_. Different types can satisfy an interface with different definitions for a given member. These definitions are _associated_ with what type is implementing the From c453b2c49366c0a395e7f1b11840704bc389cfb5 Mon Sep 17 00:00:00 2001 From: Josh L Date: Mon, 31 Jul 2023 22:30:08 +0000 Subject: [PATCH 18/83] Checkpoint progress. --- docs/design/generics/details.md | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 1dce9be96101c..535bd6b854903 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -661,7 +661,9 @@ var v: Point_Extend = AddAndScaleGeneric(a, w, 2.5); ``` Here `T` is a type whose type is `Vector`. The `:!` syntax means that `T` is a -_[checked generic parameter](terminology.md#checked-versus-template-parameters)_. +_[checked generic](terminology.md#checked-versus-template-parameters)_ binding. +Since this specifically is in a function declaration, it marks a _checked +[generic parameter](terminology.md#generic-means-compile-time-parameterized)_. That means it must be known to the caller, but we will only use the information present in the signature of the function to type check the body of `AddAndScaleGeneric`'s definition. In this case, we know that any value of type @@ -2952,7 +2954,7 @@ The `.Self` construct follows these rules: thing or isn't a type. - You get the innermost, most-specific type for `.Self` if it is introduced twice in a scope. By the previous rule, it is only legal if they all refer - to the same generic parameter. + to the same facet binding. So in `X:! A where ...`, `.Self` is introduced twice, after the `:!` and the `where`. This is allowed since both times it means `X`. After the `:!`, `.Self` @@ -3192,7 +3194,8 @@ fn F[T:! SomeInterface](x: T) { We want to know if the return type of method `T.H` is the same as the parameter type of `T.G` in order to typecheck the function. However, determining whether -two type expressions are transitively equal is in general undecidable, as +two [type expressions](terminology.md#type-expression) are transitively equal is +in general undecidable, as [has been shown in Swift](https://forums.swift.org/t/swift-type-checking-is-undecidable/39024). Carbon's approach is to only allow implicit conversions between two type @@ -3338,9 +3341,10 @@ can find a sequence that prove they are equal. #### `observe` declarations -An `observe` declaration lists a sequence of type expressions that are equal by -some same-type `where` constraints. These `observe` declarations may be included -in an `interface` definition or a function body, as in: +An `observe` declaration lists a sequence of +[type expressions](terminology.md#type-expression) that are equal by some +same-type `where` constraints. These `observe` declarations may be included in +an `interface` definition or a function body, as in: ``` interface Commute { @@ -4738,7 +4742,7 @@ after name and alias resolution. To agree: declarations, they must match. - Types agree if they correspond to the same expression tree, after name and alias resolution and canonicalization of parentheses. Note that no other - evaluation of type expressions is performed. + evaluation of expressions is performed. Interface implementation declarations match if the type and interface expressions match along with @@ -5749,10 +5753,10 @@ template, using `:!` or `template...:!`, not dynamic, with a plain `:`. Two types are the same if they have the same name and the same arguments. Carbon's [manual type equality](#manual-type-equality) approach means that the -compiler may not always be able to tell when two type expressions are equal -without help from the user, in the form of -[`observe` declarations](#observe-declarations). This means Carbon will not in -general be able to determine when types are unequal. +compiler may not always be able to tell when two +[type expressions](terminology.md#type-expression) are equal without help from +the user, in the form of [`observe` declarations](#observe-declarations). This +means Carbon will not in general be able to determine when types are unequal. Unlike an [interface's parameters](#parameterized-interfaces), a type's parameters may be [deduced](terminology.md#deduced-parameter), as in: From 07e132fc0e683c85971d6135c0a0ce2bc2f74678 Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 1 Aug 2023 00:00:11 +0000 Subject: [PATCH 19/83] Checkpoint progress. --- docs/design/README.md | 25 +++++----- docs/design/expressions/member_access.md | 20 ++++---- docs/design/generics/details.md | 21 +++++--- docs/design/generics/terminology.md | 62 ++++++++++++++++++------ 4 files changed, 86 insertions(+), 42 deletions(-) diff --git a/docs/design/README.md b/docs/design/README.md index 03b381670e3ce..f70ab059d7d71 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -653,14 +653,14 @@ means they cannot be modified and their address generally cannot be taken. The values of r-value expressions are broken down into three kinds, called _value phases_: -- A _constant_ has a value known at compile time, and that value is available - during type checking, for example to use as the size of an array. These - include literals ([integer](#integer-literals), +- A _template constant_ has a value known at compile time, and that value is + available during type checking, for example to use as the size of an array. + These include literals ([integer](#integer-literals), [floating-point](#floating-point-literals), [string](#string-literals)), concrete type values (like `f64` or `Optional(i32*)`), expressions in terms of constants, and values of [`template` parameters](#checked-and-template-parameters). -- A _symbolic value_ has a value that will be known at the code generation +- A _symbolic constant_ has a value that will be known at the code generation stage of compilation when [monomorphization](https://en.wikipedia.org/wiki/Monomorphization) happens, but is not known during type checking. This includes @@ -668,19 +668,22 @@ phases_: expressions with checked-generic arguments, like `Optional(T*)`. - A _runtime value_ has a dynamic value only known at runtime. -Carbon will automatically convert a constant to a symbolic value, or any value -to a runtime value: +Carbon will automatically convert a template constant to a symbolic constant, or +any value to a runtime value: ```mermaid graph TD; - A(constant)-->B(symbolic value)-->C(runtime value); + A(template constant)-->B(symbolic constant)-->C(runtime value); D(l-value)-->C; ``` -Constants convert to symbolic values and to runtime values. Symbolic values will -generally convert into runtime values if an operation that inspects the value is -performed on them. Runtime values will convert into constants or to symbolic -values if constant evaluation of the runtime expression succeeds. +Template constants convert to symbolic constants and to runtime values. Symbolic +constants will generally convert into runtime values if an operation that +inspects the value is performed on them. Runtime values will convert into +constants if constant evaluation of the runtime expression succeeds. + +Template constants and symbolic constants are collectively called _constants_ +and correspond to declarations using `:!`. > **Note:** Conversion of runtime values to other phases is provisional, as are > the semantics of r-values. See pending proposal diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 8c7b5d0222f25..cf0ea2df6a663 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -137,9 +137,8 @@ remaining cases we wish to support: Note that because a type is a value, and a facet type is a type, these cases are overlapping and not entirely separable. -If any of the above lookups ever looks for members of a type parameter, it -should consider members of the facet type, treating the type parameter as an -archetype. +If any of the above lookups ever looks for members of a facet binding, it should +consider members of the facet type, treating the facet binding as an archetype. **Note:** If lookup is performed into a type that involves a template parameter, the lookup will be performed both in the context of the template definition and @@ -150,8 +149,9 @@ For a simple member access, the word is looked up in the following types: - If the first operand can be evaluated and evaluates to a type, that type. - If the type of the first operand can be evaluated, that type. -- If the type of the first operand is a generic type parameter, and the type - of that generic type parameter can be evaluated, that facet type. +- If the type of the first operand is a checked-generic facet binding, and the + type of that checked-generic facet binding can be evaluated, that facet + type. The results of these lookups are [combined](#lookup-ambiguity). @@ -188,7 +188,7 @@ fn PrintPointTwice() { p.(Printable.Print)(); } fn GenericPrint[T:! Printable](a: T) { - // ✅ OK, type of `a` is the type parameter `T`; + // ✅ OK, type of `a` is the facet binding `T`; // `Print` found in the type of `T`, namely `Printable`. a.Print(); } @@ -358,10 +358,10 @@ the member access expression, `V`: ``` The appropriate `impl T as I` implementation is located. The program is invalid -if no such `impl` exists. When `T` or `I` depends on a generic parameter, a -suitable constraint must be specified to ensure that such an `impl` will exist. -When `T` or `I` depends on a template parameter, this check is deferred until -the argument for the template parameter is known. +if no such `impl` exists. When `T` or `I` depends on a checked generic binding, +a suitable constraint must be specified to ensure that such an `impl` will +exist. When `T` or `I` depends on a template parameter, this check is deferred +until the argument for the template parameter is known. `M` is replaced by the member of the `impl` that corresponds to `M`. diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 535bd6b854903..f72eac98a6de6 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -140,7 +140,10 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## Overview -This document goes into the details of the design of generic type parameters. +This document goes into the details of the design of Carbon's +[generics](terminology.md#generic-means-compile-time-parameterized), by which we +mean parameterizing some language construct with compile-time parameters. These +parameters can be types, [facets](terminology.md#facet), or other values. Imagine we want to write a function parameterized by a type argument. Maybe our function is `PrintToStdout` and let's say we want to operate on values that have @@ -650,6 +653,8 @@ declaration of the `impl`. ## Generics +FIXME: Generics -> Checked generics + Here is a function that can accept values of any type that has implemented the `Vector` interface: @@ -661,8 +666,9 @@ var v: Point_Extend = AddAndScaleGeneric(a, w, 2.5); ``` Here `T` is a type whose type is `Vector`. The `:!` syntax means that `T` is a -_[checked generic](terminology.md#checked-versus-template-parameters)_ binding. -Since this specifically is in a function declaration, it marks a _checked +_[constant binding](terminology.md#bindings)_, here specifically a _symbolic +constant binding_. Since this binding pattern is in a function declaration, it +marks a _checked [generic parameter](terminology.md#generic-means-compile-time-parameterized)_. That means it must be known to the caller, but we will only use the information present in the signature of the function to type check the body of @@ -876,7 +882,7 @@ An interface's name may be used in a few different contexts: - as a namespace name in [a qualified name](#qualified-member-names-and-compound-member-access), and - as a [facet type](terminology.md#facet-type) for - [a generic type parameter](#generics). + [a facet binding](#generics). While interfaces are examples of facet types, facet types are a more general concept, for which interfaces are a building block. @@ -953,7 +959,7 @@ whenever an interface may be. This includes all of these [a qualified name](#qualified-member-names-and-compound-member-access). For example, `VectorLegoFish.VAdd` refers to the same name as `Vector.Add`. - A named constraint may be used as a [facet type](terminology.md#facet-type) - for [a generic type parameter](#generics). + for [a facet binding](#generics). We don't expect developers to directly define many named constraints, but other constructs we do expect them to use will be defined in terms of them. For @@ -1716,7 +1722,7 @@ compiler provides it as ### Adapter compatibility -Consider a type with a generic type parameter, like a hash map: +Consider a type with a facet parameter, like a hash map: ``` interface Hashable { ... } @@ -4684,7 +4690,8 @@ The declaration of an interface implementation consists of: - optional modifier keyword `final`, - the keyword introducer `impl`, -- an optional deduced parameter list in square brackets `[`...`]`, +- an optional `forall` followed by a deduced parameter list in square brackets + `[`...`]`, - a type, including an optional parameter pattern, - the keyword `as`, and - a [facet type](#facet-types), including an optional diff --git a/docs/design/generics/terminology.md b/docs/design/generics/terminology.md index 0956aac0461b9..f50e011441866 100644 --- a/docs/design/generics/terminology.md +++ b/docs/design/generics/terminology.md @@ -21,6 +21,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Definition checking](#definition-checking) - [Complete definition checking](#complete-definition-checking) - [Early versus late type checking](#early-versus-late-type-checking) +- [Bindings](#bindings) - [Types and `type`](#types-and-type) - [Facet type](#facet-type) - [Facet](#facet) @@ -280,15 +281,47 @@ typechecked once calling information is known. Late type checking delays complete definition checking. This occurs for [template-dependent](#dependent-names) values. +## Bindings + +_Binding patterns_ associate a name with a type and a value. This is used to +declare function parameters, in `let` and `var` declarations, as well as to +declare [generic parameters](#generic-means-compile-time-parameterized). There +are three kinds of binding patterns, corresponding to +[the three value phases](/docs/design/README.md#value-categories-and-value-phases): + +- A _runtime binding pattern_ binds to a dynamic value at runtime, and is + written using a `:`, as in `x: i32`. +- A _symbolic constant binding pattern_ or _symbolic binding pattern_ binds to + a compile-time value that is not known when type checking, and is used to + declare [checked generics](#checked-versus-template-parameters) parameters. + These binding use `:!`, as in `T:! type`. +- A _template constant binding pattern_ or _template binding pattern_ binds to + a compile-time value that is known when type checking, and is used to + declare [template](#checked-versus-template-parameters) parameters. These + bindings use the keyword `template` in addition to `:!`, as in + `template T:! type`. + +The last two binding patterns, which are about binding a compile-time value, are +called _constant binding patterns_, and correspond to those binding patterns +that use `:!`. + +The name being declared, which is the identifier to the left of the `:` or `:!`, +is called a _binding_, or more specifically a _runtime binding_, _constant +binding_, _symbolic binding_, or _template binding_. The expression to the right +defining the type of the binding pattern is called the _binding type +expression_, a kind of [type expression](#type-expression). For example, in +`T:! Hashable`, `T` is the binding (a symbolic binding in this case), and +`Hashable` is the binding type expression. + ## Types and `type` A _type_ is a value of type `type`. Conversely, `type` is the type of all types. -Expressions in type position, for example the return type of a function or the -expression to the right of a `:` or `:!` in a binding, are implicitly cast to -type `type`. This means that it is legal to put a value that is not a type where -a type is expected, as long as it has an implicit conversion to `type` that may -be performed at compile time. +Expressions in type position, for example a [binding type expression](#bindings) +or the return type of a function, are implicitly cast to type `type`. This means +that it is legal to put a value that is not a type where a type is expected, as +long as it has an implicit conversion to `type` that may be performed at compile +time. ## Facet type @@ -316,11 +349,11 @@ A _facet_ is a value of a [facet type](#facet-type). For example, types are facets, since [`type`](#types-and-type) is considered a facet type. Not all facets are types, though: `i32 as Hashable` is of type `Hashable` not `type`, so it is a facet that is not a type. However, in places where a type is -expected, for example to the right of a `:` in a binding or after the `->` in a -function declaration, there is an automatic implicit conversion to `type`. This -means that a facet may be used in those positions. For example, the facet -`i32 as Hashable` will implicitly convert to `(i32 as Hashable) as type`, which -is `i32`, in those contexts. +expected, for example in a [binding type expression](#bindings) or after the +`->` in a function declaration, there is an automatic implicit conversion to +`type`. This means that a facet may be used in those positions. For example, the +facet `i32 as Hashable` will implicitly convert to `(i32 as Hashable) as type`, +which is `i32`, in those contexts. ## Type expression @@ -331,10 +364,11 @@ those cases, we are concerned with the type value after the implicit conversion. ## Facet binding -We use the term _facet binding_ to refer to the name introduced by a `:!` -binding pattern (with or without the `template` modifier) with a -[facet type](#facet-type). In the binding pattern `T:! Hashable`,`T` is a facet -binding, and the value of `T` is a [facet](#facet). +We use the term _facet binding_ to refer to the name introduced by a +[constant binding pattern](#bindings) (using `:!` with or without the `template` +modifier) with a [facet type](#facet-type). In the binding pattern +`T:! Hashable`, `T` is a facet binding, and the value of `T` is a +[facet](#facet). ## Deduced parameter From cd54e3b5ca793c5198e0fb4c346d560cd2d11018 Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 1 Aug 2023 04:44:12 +0000 Subject: [PATCH 20/83] Checkpoint progress. --- docs/design/README.md | 7 ++++--- docs/design/generics/details.md | 15 +++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/design/README.md b/docs/design/README.md index f70ab059d7d71..056731efc671a 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -1708,8 +1708,9 @@ two methods `Distance` and `Offset`: The philosophy of inheritance support in Carbon is to focus on use cases where inheritance is a good match, and use other features for other cases. For example, [mixins](#mixins) for implementation reuse and [generics](#generics) -for separating interface from implementation. This allows Carbon to move away -from [multiple inheritance](https://en.wikipedia.org/wiki/Multiple_inheritance), +for [separating interface from implementation](#interfaces-and-implementations). +This allows Carbon to move away from +[multiple inheritance](https://en.wikipedia.org/wiki/Multiple_inheritance), which doesn't have as efficient of an implementation strategy. Classes by default are @@ -3546,7 +3547,7 @@ just values and [pointers](#pointer-types). Rust also shows the value of functions parameterized by lifetimes. Since lifetimes are only used to establish safety properties of the code, there is no reason to pay the cost of monomorphization for those parameters. So we need a -[generics system](#generics) that can reason about code before it is +[checked-generics system](#generics) that can reason about code before it is instantiated, unlike C++ templates. In conclusion, there are two patterns in how Carbon diverges from C++: diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index f72eac98a6de6..94bcf4a8c18c2 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -22,7 +22,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Avoiding name collisions](#avoiding-name-collisions) - [Qualified member names and compound member access](#qualified-member-names-and-compound-member-access) - [Access](#access) -- [Generics](#generics) +- [Checked generics](#checked-generics) - [Return type](#return-type) - [Implementation model](#implementation-model) - [Interfaces recap](#interfaces-recap) @@ -184,8 +184,9 @@ member: The function expresses that the type argument is passed in [statically](terminology.md#static-dispatch-witness-table), basically generating a separate function body for every different type passed in, by using the -"generic argument" syntax `:!`, see [the generics section](#generics) below. The -interface contains enough information to +"generic argument" syntax `:!`, see +[the checked-generics section](#checked-generics) below. The interface contains +enough information to [type and definition check](terminology.md#complete-definition-checking) the function body -- you can only call functions defined in the interface in the function body. Contrast this with making the type a template argument, where you @@ -651,9 +652,7 @@ No access control modifiers are allowed on `impl` declarations, an `impl` is always visible to the intersection of the visibility of all names used in the declaration of the `impl`. -## Generics - -FIXME: Generics -> Checked generics +## Checked generics Here is a function that can accept values of any type that has implemented the `Vector` interface: @@ -882,7 +881,7 @@ An interface's name may be used in a few different contexts: - as a namespace name in [a qualified name](#qualified-member-names-and-compound-member-access), and - as a [facet type](terminology.md#facet-type) for - [a facet binding](#generics). + [a facet binding](#checked-generics). While interfaces are examples of facet types, facet types are a more general concept, for which interfaces are a building block. @@ -959,7 +958,7 @@ whenever an interface may be. This includes all of these [a qualified name](#qualified-member-names-and-compound-member-access). For example, `VectorLegoFish.VAdd` refers to the same name as `Vector.Add`. - A named constraint may be used as a [facet type](terminology.md#facet-type) - for [a facet binding](#generics). + for [a facet binding](#checked-generics). We don't expect developers to directly define many named constraints, but other constructs we do expect them to use will be defined in terms of them. For From 9e11df2855e67216c703724766e067764222f494 Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 1 Aug 2023 17:27:34 +0000 Subject: [PATCH 21/83] Checkpoint progress. --- docs/design/README.md | 117 ++++++++++++++++------- docs/design/expressions/if.md | 5 +- docs/design/expressions/member_access.md | 31 +++--- docs/design/pattern_matching.md | 17 +++- docs/design/templates.md | 12 +-- 5 files changed, 122 insertions(+), 60 deletions(-) diff --git a/docs/design/README.md b/docs/design/README.md index 056731efc671a..c264ade6bcdd0 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -19,6 +19,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Code and comments](#code-and-comments) - [Build modes](#build-modes) - [Types are values](#types-are-values) + - [Values usable as types](#values-usable-as-types) - [Primitive types](#primitive-types) - [`bool`](#bool) - [Integer types](#integer-types) @@ -380,8 +381,8 @@ The behavior of the Carbon compiler depends on the _build mode_: Expressions compute values in Carbon, and these values are always strongly typed much like in C++. However, an important difference from C++ is that types are -themselves modeled as values; specifically, compile-time-constant values. This -has a number of consequences: +themselves modeled as values; specifically, compile-time-constant values of type +`type`. This has a number of consequences: - Names for types are in the same namespace shared with functions, variables, namespaces, and so on. @@ -392,14 +393,43 @@ has a number of consequences: - Function call syntax is used to specify parameters to a type, like `HashMap(String, i64)`. -Some values, such as `()` and `{}`, may even be used as types, but only act like -types when they are in a type position, like after a `:` in a variable -declaration or the return type after a `->` in a function declaration. Any -expression in a type position must be -[a constants or symbolic value](#value-categories-and-value-phases) so the -compiler can resolve whether the value can be used as a type. This also puts -limits on how much operators can do different things for types. This is good for -consistency, but is a significant restriction on Carbon's design. +### Values usable as types + +A value used in a type position, like after a `:` in a variable declaration or +the return type after a `->` in a function declaration, must be: + +- [a constant value](#value-categories-and-value-phases), so the compiler can + evaluate it at compile time, and +- have a defined implicit conversion to type `type`. + +The actual type used is the result of the conversion to type `type`. Of course +this includes values that already are of type `type`, but also allows some +non-type values to be used in a type position. + +For example, the value `(bool, bool)` represents a [tuple](#tuples) of types, +but is not itself a type, since it doesn't have type `type`. It does have a +defined implicit conversion to type `type`, which results in the value +`(bool, bool) as type`. This means `(bool, bool)` may be used in a type +position. `(bool, bool) as type` is the type of the value `(true, false)` (among +others), so this code is legal: + +```carbon +var b: (bool, bool) = (true, false);` +``` + +There is some need to be careful here, since the declaration makes it look like +the type of `b` is `(bool, bool)`, when in fact it is `(bool, bool) as type`. +`(bool, bool) as type` and `(bool, bool)` are different values since they have +different types: the first has type `type`, and the second has type +`(type, type) as type`. + +In addition to the types of [tuples](#tuples), this also comes up with +[struct types](#struct-types) and [facets](generics/terminology.md#facet). + +> References: +> +> - Proposal +> [#2360: Types are values of type `type`](https://github.com/carbon-language/carbon-lang/pull/2360) ## Primitive types @@ -973,10 +1003,11 @@ example through side effects of the destructor, copy, and move operations, but the program's correctness must not depend on which option the Carbon implementation chooses. -A [generic binding](#checked-and-template-parameters) uses `:!` instead of a +A [constant binding](#checked-and-template-parameters) uses `:!` instead of a colon (`:`) and can only match -[constant or symbolic values](#value-categories-and-value-phases), not run-time -values. +[constant values](#value-categories-and-value-phases), not run-time values. A +`template` keyword before the binding selects a template constant binding +instead of a symbolic constant binding. The keyword `auto` may be used in place of the type in a binding pattern, as long as the type can be deduced from the type of a value in the same @@ -2499,12 +2530,15 @@ fn C.G() { } ``` +**FIXME: "symbolic facet binding" is the technically correct combination of +terms, but probably needs more context here.** + [Member name lookup](expressions/member_access.md) follows a similar philosophy. -If a [checked-generic type parameter](#checked-and-template-parameters) is known -to implement multiple interfaces due to a constraint using +If a [symbolic facet binding](#checked-and-template-parameters) is known to +implement multiple interfaces due to a constraint using [`&`](#combining-constraints) or [`where` clauses](generics/details.md#where-constraints), member name lookup -into that type will look in all of the interfaces. If it is found in multiple, +into that facet will look in all of the interfaces. If it is found in multiple, the name must be disambiguated by qualifying using compound member access ([1](expressions/member_access.md), [2](generics/details.md#qualified-member-names-and-compound-member-access)). A @@ -2568,7 +2602,7 @@ like `i32` and `bool` refer to types defined within this package, based on the Generics allow Carbon constructs like [functions](#functions) and [classes](#classes) to be written with compile-time parameters and apply generically to different types using those parameters. For example, this `Min` -function has a type parameter `T` that can be any type that implements the +function has a type\* parameter `T` that can be any type that implements the `Ordered` interface. ```carbon @@ -2586,11 +2620,16 @@ Assert(Min(a, b) == 1); Assert(Min("abc", "xyz") == "abc"); ``` -Since the `T` type parameter is in the deduced parameter list in square brackets +Since the `T` parameter is in the deduced parameter list in square brackets (`[`...`]`) before the explicit parameter list in parentheses (`(`...`)`), the value of `T` is determined from the types of the explicit arguments instead of being passed as a separate explicit argument. +(\*) Note: `T` here may be thought of as a type parameter, but its values are +actually [facets](generics/terminology.md#facet), which are +[values usable as types](#values-usable-as-types). The `T` in this example is +not itself a type. + > References: **TODO:** Revisit > > - [Generics: Overview](generics/overview.md) @@ -2600,6 +2639,8 @@ being passed as a separate explicit argument. > [#553: Generics details part 1](https://github.com/carbon-language/carbon-lang/pull/553) > - Proposal > [#950: Generic details 6: remove facets](https://github.com/carbon-language/carbon-lang/pull/950) +> - Proposal +> [#2360: Types are values of type `type`](https://github.com/carbon-language/carbon-lang/pull/2360) ### Checked and template parameters @@ -2608,12 +2649,11 @@ compile time. Generic parameters may either be _checked_ or _template_, and default to checked. "Checked" here means that the body of `Min` is type checked when the function is -defined, independent of the specific type values `T` is instantiated with, and -name lookup is delegated to the constraint on `T` (`Ordered` in this case). This -type checking is equivalent to saying the function would pass type checking -given any type `T` that implements the `Ordered` interface. Subsequent calls to -`Min` only need to check that the deduced type value of `T` implements -`Ordered`. +defined, independent of the specific values `T` is instantiated with, and name +lookup is delegated to the constraint on `T` (`Ordered` in this case). This type +checking is equivalent to saying the function would pass type checking given any +type `T` that implements the `Ordered` interface. Subsequent calls to `Min` only +need to check that the deduced value of `T` implements `Ordered`. The parameter could alternatively be declared to be a _template_ generic parameter by prefixing with the `template` keyword, as in `template T:! type`. @@ -2658,22 +2698,25 @@ class Array(template T:! type, template N:! i64) if N >= 0 and N < MaxArraySize / sizeof(T); ``` -Member lookup into a template type parameter is done in the actual type value -provided by the caller, _in addition_ to any constraints. This means member name -lookup and type checking for anything -[dependent](generics/terminology.md#dependent-names) on the template parameter -can't be completed until the template is instantiated with a specific concrete -type. When the constraint is just `type`, this gives semantics similar to C++ -templates. Constraints can then be added incrementally, with the compiler -verifying that the semantics stay the same. Once all constraints have been -added, removing the word `template` to switch to a checked parameter is safe. +Member lookup into a template parameter is done in the actual value provided by +the caller, _in addition_ to any constraints. This means member name lookup and +type checking for anything [dependent](generics/terminology.md#dependent-names) +on the template parameter can't be completed until the template is instantiated +with a specific concrete type. When the constraint is just `type`, this gives +semantics similar to C++ templates. Constraints can then be added incrementally, +with the compiler verifying that the semantics stay the same. Once all +constraints have been added, removing the word `template` to switch to a checked +parameter is safe. The [value phase](#value-categories-and-value-phases) of a checked parameter is -a symbolic value whereas the value phase of a template parameter is constant. +"symbolic constant" whereas the value phase of a template parameter is "template +constant." A binding pattern using `:!` is a _constant binding pattern_; more +specifically a _template binding pattern_ if it uses `template`, and a _symbolic +binding pattern_ if it does not. Although checked generics are generally preferred, templates enable translation of code between C++ and Carbon, and address some cases where the type checking -rigor of generics are problematic. +rigor of checked generics are problematic. > References: > @@ -2699,6 +2742,10 @@ interface Printable { } ``` +An interface is kind of [facet type](generics/terminology.md#facet-type), and +the values of this type are [facets](generics/terminology.md#facet), which are +[values usable as types](#values-usable-as-types). + In addition to function requirements, interfaces can contain: - [requirements that other interfaces be implemented](generics/details.md#interface-requiring-other-interfaces) diff --git a/docs/design/expressions/if.md b/docs/design/expressions/if.md index f61e8ecf75cd9..e758784326435 100644 --- a/docs/design/expressions/if.md +++ b/docs/design/expressions/if.md @@ -175,8 +175,9 @@ _Note:_ This rule is intended to be considered more specialized than the other rules in this document. Because this `impl` is declared `final`, `T.(CommonType(T)).Result` is always -assumed to be `T`, even in contexts where `T` involves a generic parameter and -so the result would normally be an unknown type whose facet type is `type`. +assumed to be `T`, even in contexts where `T` involves a generic parameter or +symbolic binding and so the result would normally be an unknown type whose facet +type is `type`. ``` fn F[T:! Hashable](c: bool, x: T, y: T) -> HashCode { diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index cf0ea2df6a663..e09dbe2e76c44 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -14,7 +14,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Member resolution](#member-resolution) - [Package and namespace members](#package-and-namespace-members) - [Lookup within values](#lookup-within-values) - - [Templates and generics](#templates-and-generics) + - [Constant bindings](#constant-bindings) - [Lookup ambiguity](#lookup-ambiguity) - [`impl` lookup](#impl-lookup) - [Instance binding](#instance-binding) @@ -140,10 +140,10 @@ overlapping and not entirely separable. If any of the above lookups ever looks for members of a facet binding, it should consider members of the facet type, treating the facet binding as an archetype. -**Note:** If lookup is performed into a type that involves a template parameter, +**Note:** If lookup is performed into a type that involves a template binding, the lookup will be performed both in the context of the template definition and in the context of the template instantiation, as described in -[templates and generics](#templates-and-generics). +[constant bindings](#constant-bindings). For a simple member access, the word is looked up in the following types: @@ -197,12 +197,13 @@ fn CallGenericPrint(p: Point) { } ``` -#### Templates and generics +#### Constant bindings -If the value or type of the first operand depends on a template or generic -parameter, the lookup is performed from a context where the value of that -parameter is unknown. Evaluation of an expression involving the parameter may -still succeed, but will result in a symbolic value involving that parameter. +If the value or type of the first operand depends on a checked or template +generic parameter, or in fact any constant binding, the lookup is performed from +a context where the value of that parameter is unknown. Evaluation of an +expression involving the parameter may still succeed, but will result in a +symbolic value involving that parameter. ``` class GenericWrapper(T:! type) { @@ -228,14 +229,14 @@ fn G[template T:! type](x: TemplateWrapper(T)) -> T { > nothing. In any case, as described below, the lookup will be performed again > when `T` is known. -If the value or type depends on any template parameters, the lookup is redone -from a context where the values of those parameters are known, but where the -values of any generic parameters are still unknown. The lookup results from -these two contexts are [combined](#lookup-ambiguity). +If the value or type depends on any template binding, the lookup is redone from +a context where the values of those binding are known, but where the values of +any symbolic bindings are still unknown. The lookup results from these two +contexts are [combined](#lookup-ambiguity). -**Note:** All lookups are done from a context where the values of any generic -parameters that are in scope are unknown. Unlike for a template parameter, the -actual value of a generic parameter never affects the result of member +**Note:** All lookups are done from a context where the values of any symbolic +bindings that are in scope are unknown. Unlike for a template binding, the +actual value of a symbolic binding never affects the result of member resolution. ```carbon diff --git a/docs/design/pattern_matching.md b/docs/design/pattern_matching.md index 167f70f8068d4..da01dfe356386 100644 --- a/docs/design/pattern_matching.md +++ b/docs/design/pattern_matching.md @@ -224,9 +224,22 @@ A `:!` can be used in place of `:` for a binding that is usable at compile time. - _generic-pattern_ ::= `unused` `template`? _identifier_ `:!` _expression_ - _proper-pattern_ ::= _generic-pattern_ +**FIXME:** Maybe this should just be: + +- _generic-pattern_ ::= `unused`? `template`? _identifier_ `:!` _expression_ +- _generic-pattern_ ::= `template`? `_` `:!` _expression_ +- _proper-pattern_ ::= _generic-pattern_ + +**FIXME:** Or: + +- _generic-pattern_ ::= `template`? _identifier_ `:!` _expression_ +- _generic-pattern_ ::= `template`? `_` `:!` _expression_ +- _generic-pattern_ ::= `unused` `template`? _identifier_ `:!` _expression_ +- _proper-pattern_ ::= _generic-pattern_ + ```carbon // ✅ `F` takes a generic type parameter `T` and a parameter `x` of type `T`. -fn F(T:! Type, x: T) { +fn F(T:! type, x: T) { var v: T = x; } ``` @@ -256,7 +269,7 @@ The `auto` keyword is only permitted in specific contexts. Currently these are: - As the type of a binding. It is anticipated that `auto` may be permitted in more contexts in the future, -for example as a generic argument in a parameterized type that appears in a +for example as a placeholder argument in a parameterized type that appears in a context where `auto` is allowed, such as `Vector(auto)` or `auto*`. When the type of a binding requires type deduction, the type is deduced against diff --git a/docs/design/templates.md b/docs/design/templates.md index 3a848086e1437..be062837f0589 100644 --- a/docs/design/templates.md +++ b/docs/design/templates.md @@ -101,11 +101,11 @@ specialization, but that is an area that we want to explore cautiously. Because we consider only specific _parameters_ to be templated and they could be individually migrated to a constrained interface using the -[generics system](README.md#generics), constraining templates themselves may be -less critical. Instead, we expect parameterized types and functions may use a -mixture of generic parameters and templated parameters based on where they are -constrained. +[checked-generics system](README.md#generics), constraining templates themselves +may be less critical. Instead, we expect parameterized types and functions may +use a mixture of checked-generic parameters and templated parameters based on +where they are constrained. However, if there are still use cases, we would like to explore applying the -interface constraints of the generics system directly to template parameters -rather than create a new constraint system. +interface constraints of the checked-generics system directly to template +parameters rather than create a new constraint system. From baaf1a68ccf9177a50ee658613e9322d8d8e3301 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 3 Aug 2023 00:15:24 +0000 Subject: [PATCH 22/83] iterate on overview --- docs/design/generics/overview.md | 63 ++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/docs/design/generics/overview.md b/docs/design/generics/overview.md index 954d91cb60794..895cf720fe29c 100644 --- a/docs/design/generics/overview.md +++ b/docs/design/generics/overview.md @@ -43,8 +43,20 @@ pointers to other design documents that dive deeper into individual topics. ## Goals -The goal of Carbon generics is to provide an alternative to Carbon (or C++) -templates. Generics in this form should provide many advantages, including: +Carbon [generics](terminology.md#generic-means-compile-time-parameterized) +supports generalizing code to apply to more situations by adding compile-time +parameters, allowing +[generic programming](https://en.wikipedia.org/wiki/Generic_programming). Carbon +supports both +[checked and template](terminology.md#checked-versus-template-parameters) +generics. + +Template generics support a similar model as C++ templates, to help with interop +and migration. They can be more convenient to write, and support some use cases, +like [metaprogramming](https://en.wikipedia.org/wiki/Metaprogramming), that are +difficult with checked generics. + +Checked generics are an alternative that has advantages including: - Function calls and bodies are checked independently against the function signatures. @@ -52,6 +64,9 @@ templates. Generics in this form should provide many advantages, including: - Fast builds, particularly development builds. - Support for both static and dynamic dispatch. +Checked generics do have some restrictions, but are expected to be more +appropriate at public API boundaries than templates. + For more detail, see [the detailed discussion of generics goals](goals.md) and [generics terminology](terminology.md). @@ -59,30 +74,40 @@ For more detail, see [the detailed discussion of generics goals](goals.md) and Summary of how Carbon generics work: -- _Generics_ are parameterized functions and types that can apply generally. - They are used to avoid writing specialized, near-duplicate code for similar - situations. -- Generics are written using _interfaces_ which have a name and describe - methods, functions, and other entities for types to implement. +- _Generics_ are compile-time parameterized functions, types, and other + language constructs. Those parameters allow a single definition to apply + more generally. They are used to avoid writing specialized, near-duplicate + code for similar situations. +- The definition of a _checked_ generic is typechecked once, without having to + know the specific values of the generic parameters it is instantiated with. + Typechecking the definition of a checked generic requires a precise contract + specifying the requirements on the argument values. +- For parameters that will be used as types, those requirements are written + using _interfaces_. Interfaces have a name and describe methods, functions, + and other entities for types to implement. - Types must explicitly _implement_ interfaces to indicate that they support its functionality. A given type may implement an interface at most once. - Implementations may be declared inline in the body of a class definition, or out-of-line. -- Types may extend an implementation declared inline, in which case you can +- Types may _extend_ an implementation declared inline, in which case you can directly call the interface's methods on those types. - Out-of-line implementations may be defined in the library defining the - interface. -- Interfaces are used as the type of a generic type parameter, acting as a - _facet type_. Facet types in general specify the capabilities and - requirements of the type. Types define specific implementations of those - capabilities. Inside such a generic function, the API of the type is - [erased](terminology.md#type-erasure), except for the names defined in the - facet type. + interface rather than the type. +- Interfaces may be used as the type of a generic parameter. Interfaces are + _facet types_, whose values are the subset of all types that implement the + interface. Facet types in general specify the capabilities and requirements + of the type. The value of a interface is called a _facet_, and are not + types, but are usable as types. +- With a template generic, the concrete argument value used by the caller is + used for name lookup and typechecking. With checked generics, that is all + done with the declared restrictions expressed as the types of bindings in + the declaration. Inside the body of a checked generic with a facet + parameter, the API of the facet is just the names defined by the facet type. - _Deduced parameters_ are parameters whose values are determined by the - values and (most commonly) the types of the explicit arguments. Generic type - parameters are typically deduced. -- A function with a generic type parameter can have the same function body as - an unparameterized one. Functions can freely mix generic, template, and + values and (most commonly) the types of the explicit arguments. Generic + facet parameters are typically deduced. +- A function with a generic facet parameter can have the same function body as + an unparameterized one. Functions can freely mix checked, template, and regular parameters. - Interfaces can require other interfaces be implemented. - Interfaces can [extend](terminology.md#extending-an-interface) required From 895a54678d57e2675e72a66933bd15d0de925288 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 3 Aug 2023 21:09:44 +0000 Subject: [PATCH 23/83] Checkpoint progress. --- docs/design/generics/overview.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/design/generics/overview.md b/docs/design/generics/overview.md index 895cf720fe29c..0c3efa8efe7d0 100644 --- a/docs/design/generics/overview.md +++ b/docs/design/generics/overview.md @@ -79,20 +79,20 @@ Summary of how Carbon generics work: more generally. They are used to avoid writing specialized, near-duplicate code for similar situations. - The definition of a _checked_ generic is typechecked once, without having to - know the specific values of the generic parameters it is instantiated with. - Typechecking the definition of a checked generic requires a precise contract - specifying the requirements on the argument values. + know the specific argument values of the generic parameters it is + instantiated with. Typechecking the definition of a checked generic requires + a precise contract specifying the requirements on the argument values. - For parameters that will be used as types, those requirements are written using _interfaces_. Interfaces have a name and describe methods, functions, and other entities for types to implement. - Types must explicitly _implement_ interfaces to indicate that they support - its functionality. A given type may implement an interface at most once. + their functionality. A given type may implement an interface at most once. - Implementations may be declared inline in the body of a class definition, or out-of-line. - Types may _extend_ an implementation declared inline, in which case you can directly call the interface's methods on those types. - Out-of-line implementations may be defined in the library defining the - interface rather than the type. + interface as an alternative to the type. - Interfaces may be used as the type of a generic parameter. Interfaces are _facet types_, whose values are the subset of all types that implement the interface. Facet types in general specify the capabilities and requirements @@ -103,12 +103,12 @@ Summary of how Carbon generics work: done with the declared restrictions expressed as the types of bindings in the declaration. Inside the body of a checked generic with a facet parameter, the API of the facet is just the names defined by the facet type. -- _Deduced parameters_ are parameters whose values are determined by the - values and (most commonly) the types of the explicit arguments. Generic - facet parameters are typically deduced. -- A function with a generic facet parameter can have the same function body as - an unparameterized one. Functions can freely mix checked, template, and - regular parameters. +- _Deduced parameters_ are parameters whose values are determined by the the + types of the explicit arguments. Generic facet parameters are typically + deduced. +- A function with a generic parameter can have the same function body as an + unparameterized one. Functions can freely mix checked, template, and regular + parameters. - Interfaces can require other interfaces be implemented. - Interfaces can [extend](terminology.md#extending-an-interface) required interfaces. From 3f445fa6804a57e1009a0b2ccc6e4e335546cf05 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 3 Aug 2023 22:01:45 +0000 Subject: [PATCH 24/83] Finished a pass through generics overview --- docs/design/generics/overview.md | 86 +++++++++++++++++--------------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/docs/design/generics/overview.md b/docs/design/generics/overview.md index 0c3efa8efe7d0..664a9595e233d 100644 --- a/docs/design/generics/overview.md +++ b/docs/design/generics/overview.md @@ -24,7 +24,7 @@ pointers to other design documents that dive deeper into individual topics. - [Facet types](#facet-types) - [Generic functions](#generic-functions) - [Deduced parameters](#deduced-parameters) - - [Generic type parameters](#generic-type-parameters) + - [Facet parameters](#facet-parameters) - [Requiring or extending another interface](#requiring-or-extending-another-interface) - [Combining interfaces](#combining-interfaces) - [Named constraints](#named-constraints) @@ -92,7 +92,8 @@ Summary of how Carbon generics work: - Types may _extend_ an implementation declared inline, in which case you can directly call the interface's methods on those types. - Out-of-line implementations may be defined in the library defining the - interface as an alternative to the type. + interface as an alternative to the type, or + [the library defining a type argument](#parameterized-impl-declarations). - Interfaces may be used as the type of a generic parameter. Interfaces are _facet types_, whose values are the subset of all types that implement the interface. Facet types in general specify the capabilities and requirements @@ -141,8 +142,9 @@ elements: fn SortVector(T:! Comparable, a: Vector(T)*) { ... } ``` -The syntax above adds a `!` to indicate that the parameter named `T` is generic -and the caller will have to provide a value known at compile time. +The syntax above adds a `!` to indicate that the parameter named `T` is +compile-time. By default compile-time parameters are _checked_, the `template` +keyword may be added to make it a _template generic_. Given an `i32` vector `iv`, `SortVector(i32, &iv)` is equivalent to `SortInt32Vector(&iv)`. Similarly for a `String` vector `sv`, @@ -155,14 +157,16 @@ This ability to generalize makes `SortVector` a _generic_. ### Interfaces The `SortVector` function requires a definition of `Comparable`, with the goal -that the compiler can: +that the compiler can perform checking. This has two pieces: -- completely type check a generic definition without information from where - it's called. -- completely type check a call to a generic with information only from the - function's signature, and not from its body. +- definition checking: completely type check a checked-generic definition + without information from calls; +- encapsulation: completely type check a call to a generic with information + only from the function's signature, and not from its body. For Rust, this is + called + "[Rust's Golden Rule](https://steveklabnik.com/writing/rusts-golden-rule)." -In this example, then, `Comparable` is an _interface_. +In this example, `Comparable` is an _interface_. Interfaces describe all the requirements needed for the type `T`. Given that the compiler knows `T` satisfies those requirements, it can type check the body of @@ -201,9 +205,9 @@ an interface. #### Contrast with templates -Contrast these generics with a C++ template, where the compiler may be able to -do some checking given a function definition, but more checking of the -definition is required after seeing the call sites once all the +Contrast these checked generics with a Carbon or C++ template, where the +compiler may be able to do some checking given a function definition, but more +checking of the definition is required after seeing the call sites once all the [instantiations](terminology.md#instantiation) are known. Note: [Generics terminology](terminology.md) goes into more detail about the @@ -252,8 +256,9 @@ impl Song as Comparable { Implementations may be defined within the class definition itself or out-of-line. Implementations may optionally start with the `extend` keyword to say the members of the interface are also members of the class, which may only -be used in a class scope. Otherwise, implementations may be defined in the -library defining either the class or the interface. +be used in a class scope. Out-of-line implementations may be defined in the +library defining the class, the interface, or +[a type argument](#parameterized-impl-declarations). #### Accessing members of interfaces @@ -280,11 +285,11 @@ song.(Printable.Print)(); To type check a function, the compiler needs to be able to verify that uses of a value match the capabilities of the value's type. In `SortVector`, the parameter -`T` is a type, but that type is a generic parameter. That means that the -specific type value assigned to `T` is not known when type checking the -`SortVector` function. Instead it is the constraints on `T` that let the -compiler know what operations may be performed on values of type `T`. Those -constraints are represented by the type of `T`, a +`T` is a type, but that type is a checked-generic, or _symbolic_, parameter. +That means that the specific type value assigned to `T` is not known when type +checking the `SortVector` function. Instead it is the constraints on `T` that +let the compiler know what operations may be performed on values of type `T`. +Those constraints are represented by the type of `T`, a [**_facet type_**](terminology.md#facet-type). In general, a facet type describes the capabilities of a type, while a type @@ -340,9 +345,9 @@ call site. fn Illegal[T:! type, U:! type](x: T) -> U { ... } ``` -#### Generic type parameters +#### Facet parameters -A function with a generic type parameter can have the same function body as an +A function with a facet parameter can have the same function body as an unparameterized one. ``` @@ -355,20 +360,23 @@ fn PrintIt(p: Song*) { } ``` -Inside the function body, you can treat the generic type parameter just like any -other type. There is no need to refer to or access generic parameters -differently because they are defined as generic, as long as you only refer to -the names defined by [facet type](#facet-types) for the type parameter. +Inside the function body, you can treat the facet parameter just like any other +type. There is no need to refer to or access generic parameters differently +because they are defined as generic, as long as you only refer to the names +defined by [facet type](#facet-types) for the facet parameter. You may also refer to any of the methods of interfaces required by the facet type using a -[qualified member access expression](#accessing-members-of-interfaces), as shown -in the following sections. +[qualified member access expression](#accessing-members-of-interfaces). -A function can have a mix of generic, template, and regular parameters. -Likewise, it's allowed to pass a template or generic value to a generic or -regular parameter. _However, passing a generic value to a template parameter is -future work._ +A function can have a mix of checked, template, and regular parameters. A +checked parameter is defined using a symbolic binding pattern, a template +parameter using a template binding pattern, and a regular parameter using a +runtime binding pattern. Likewise, it's allowed to pass a symbolic or template +binding or value to a generic or regular parameter. _We have decided to support +passing a symbolic value to a template parameter, see +[leads issue #2153: Checked generics calling templates](https://github.com/carbon-language/carbon-lang/issues/2153), +but incorporating it into the design is future work._ ### Requiring or extending another interface @@ -487,7 +495,7 @@ fn CallItAll[T:! Combined](game_state: T*, int winner) { #### Type erasure -Inside a generic function, the API of a type argument is +Inside a generic function, the API of a facet argument is [erased](terminology.md#type-erasure) except for the names defined in the facet type. An equivalent model is to say an [archetype](terminology.md#archetype) is used for type checking and name lookup when the actual type is not known in that @@ -567,9 +575,9 @@ interface Stack { ``` `ElementType` is an associated constant of the interface `Stack`. Types that -implement `Stack` give `ElementType` a specific value of some type implementing -`Movable`. Functions that accept a type implementing `Stack` can deduce the -`ElementType` from the stack type. +implement `Stack` give `ElementType` a specific value that is some type (really, +facet) implementing `Movable`. Functions that accept a type implementing `Stack` +can deduce the `ElementType` from the stack type. ``` // ✅ This is allowed, since the type of the stack will determine @@ -616,7 +624,7 @@ fn CompileError[T:! type, U:! Equatable(T)](x: U) -> T; Facet types can be further constrained using a `where` clause: ``` -fn FindFirstPrime[T:! Container where .Element == i32] +fn FindFirstPrime[T:! Container where .Element = i32] (c: T, i: i32) -> Optional(i32) { // The elements of `c` have type `T.Element`, which is `i32`. ... @@ -649,8 +657,8 @@ parameters can have constraints to restrict when the implementation applies. When multiple implementations apply, there is a rule to pick which one is considered the most specific: -- All type parameters in each `impl` declaration are replaced with question - marks `?`. This is called the type structure of the `impl` declaration. +- All parameters in each `impl` declaration are replaced with question marks + `?`. This is called the type structure of the `impl` declaration. - Given two type structures, find the first difference when read from left-to-right. The one with a `?` is less specific, the one with a concrete type name in that position is more specific. From 9538ba16c13700aa06a40dd77a3557b428d075bb Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 16 Aug 2023 20:21:49 +0000 Subject: [PATCH 25/83] Incomplete work on member_access --- docs/design/expressions/member_access.md | 160 ++++++++++++++++++----- 1 file changed, 127 insertions(+), 33 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index ead76f937b579..80b0a35f09d74 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -13,9 +13,11 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Overview](#overview) - [Member resolution](#member-resolution) - [Package and namespace members](#package-and-namespace-members) - - [Lookup within values](#lookup-within-values) + - [Types and facets](#types-and-facets) + - [Values](#values) + - [Facet binding](#facet-binding) - [Constant bindings](#constant-bindings) - - [Lookup ambiguity](#lookup-ambiguity) + - [Lookup ambiguity](#lookup-ambiguity) - [`impl` lookup](#impl-lookup) - [Instance binding](#instance-binding) - [Non-instance members](#non-instance-members) @@ -136,45 +138,85 @@ fn CallMyFunction2() { } ``` -### Lookup within values +### Types and facets -When the first operand is not a package or namespace name, there are three -remaining cases we wish to support: +Like the previous case, types (including +[facet types](/docs/design/generics/terminology.md#facet-type)) and facets have +member names, and lookup searches those names. For example: -- The first operand is a value, and lookup should consider members of the - value's type. -- The first operand is a type, and lookup should consider members of that - type. For example, `i32.Least` should find the member constant `Least` of - the type `i32`. -- The first operand is a facet type, and lookup should consider members of - that facet type. For example, `Addable.Add` should find the member function - `Add` of the interface `Addable`. Because a facet type is a type, this is a - special case of the previous bullet. +- `i32.Least` should find the member constant `Least` of the type `i32`. +- `Addable.Add` should find the member function `Add` of the interface + `Addable`. Because a facet type is a type, this is a special case of the + previous bullet. -Note that because a type is a value, and a facet type is a type, these cases are -overlapping and not entirely separable. +Unlike the previous case, both simple and compound member access is allowed. -If any of the above lookups ever looks for members of a facet binding, it should -consider members of the facet type, treating the facet binding as an archetype. +In the case of a facet, such as `T as Cowboy`, its members are members of the +`impl`, `T as Cowboy`, and not interface members, and so no further `impl` +lookup is performed. + +```carbon +interface Cowboy { + fn Draw[self: Self](); +} + +interface Renderable { + fn Draw[self: Self](); +} + +class Avatar { + extend impl Avatar as Cowboy; + extend impl Avatar as Renderable; +} +``` + +Simple member access `(Avatar as Cowboy).Draw` finds the `Cowboy.Draw` +implementation for `Avatar`, ignoring `Renderable.Draw`. + +### Values + +If the first operand is not a type, package, namespace, or facet it does not +have member names, and lookup is performed into the type of the first operand +instead. FIXME + +### Facet binding + +FIXME: + +If any of the above lookups ever looks for members of a +[facet binding](/docs/design/generics/terminology.md#facet-binding), it should +consider members of the facet type, treating the facet binding as an +[archetype](/docs/design/generics/terminology.md#archetype). + +FIXME: **Note:** If lookup is performed into a type that involves a template binding, the lookup will be performed both in the context of the template definition and in the context of the template instantiation, as described in [constant bindings](#constant-bindings). +FIXME: + For a simple member access, the word is looked up in the following types: - If the first operand can be evaluated and evaluates to a type, that type. - If the type of the first operand can be evaluated, that type. -- If the type of the first operand is a checked-generic facet binding, and the - type of that checked-generic facet binding can be evaluated, that facet - type. +- If the type of the first operand is a symbolic facet binding, and the type + of that symbolic facet binding can be evaluated, that facet type. + +FIXME: The results of these lookups are [combined](#lookup-ambiguity). +FIXME: + For a compound member access, the second operand is evaluated as a constant to determine the member being accessed. The evaluation is required to succeed and -to result in a member of a type or interface. +to result in a member of a facet, type, or interface. If the result is an +instance member, then [instance binding](#instance-binding) is always performed +on the first operand. + +FIXME: For example: @@ -302,7 +344,12 @@ fn DrawWidget(r: RoundWidget, s: SquareWidget) { } ``` -#### Lookup ambiguity +### Lookup ambiguity + +FIXME: this can arise due to template bindings looking both in the instantiated +type and the archetype derived from the facet type. it can also arise due to a +type extending multiple things each of which defines the name, or `X & Y` which +acts like something that extends `X` and `Y`. Multiple lookups can be performed when resolving a member access expression. If more than one member is found, after performing [`impl` lookup](#impl-lookup) if @@ -312,11 +359,44 @@ the lookup results is the unique member that was found. ## `impl` lookup -When the second operand of a member access expression resolves to a member of an -interface `I`, and the first operand is a value other than a facet type, _`impl` -lookup_ is performed to map the member of the interface to the corresponding -member of the relevant `impl`. The member of the `impl` replaces the member of -the interface in all further processing of the member access expression. +`impl` lookup maps a member of an interface to the corresponding member of the +relevant `impl`. It is performed when member access names an interface member, +except when the member was found by a search of a facet type scope in a simple +member access expression. + +- For a simple member access `a.b` where `b` names a member of an interface + `I`: + - If the interface member was found by searching a non-facet-type scope + `T`, for example a class or an adapter, then `impl` lookup is performed + for `T as I`. For example: + - `MyClass.AliasForInterfaceMember` finds the member of the + `impl MyClass as I`, not the interface member. + - `my_value.AliasForInterfaceMember`, with `my_value: MyClass`, finds + the member of the `impl MyClass as I`, and performs + [instance binding](#instance-binding) if the member is an instance + member. + - In the case where the member was found in a base class of the class + that was searched, `T` is the derived class that was searched, not + the base class in which the name was declared. + - More generally, if the member was found in something the type + extends, such as a facet type or mixin, `T` is the type that was + initially searched, not what it extended. + - Otherwise, `impl` lookup is not performed: + - `MyInterface.AliasForInterfaceMember` finds the member in the + interface `MyInterface` and does not perform `impl` lookup. +- For a compound member access `a.(b)` where `b` names a member of an + interface `I`, `impl` lookup is performed for `T as I`, where: + - If `b` is an instance member, `T` is the type of `a`. + - `my_value.(Interface.InstanceInterfaceMember)()`, where `my_value` + is of type `MyClass`, uses `MyClass as Interface`. + - `MyClass.(Interface.InstanceInterfaceMember)()` uses + `type as Interface`. + - Otherwise, `a` is implicitly converted to `I`, and `T` is the result of + symbolically evaluating the converted expression. + - `MyClass.(Interface.NonInstanceInterfaceMember)()` uses + `MyClass as Interface`. + - `my_value.(Interface.NonInstanceInterfaceMember)()` is an error + unless `my_value` implicitly converts to a type. ```carbon interface Addable { @@ -338,7 +418,8 @@ class Integer { } fn SumIntegers(v: Vector(Integer)) -> Integer { - // Member resolution resolves the name `Sum` to #2. + // Member resolution resolves the name `Sum` to #2, which is + // not an instance member. // `impl` lookup then locates the `impl Integer as Addable`, // and determines that the member access refers to #4, // which is then called. @@ -346,18 +427,21 @@ fn SumIntegers(v: Vector(Integer)) -> Integer { } fn AddTwoIntegers(a: Integer, b: Integer) -> Integer { - // Member resolution resolves the name `Add` to #1. + // Member resolution resolves the name `Add` to #1, which is + // an instance member. // `impl` lookup then locates the `impl Integer as Addable`, // and determines that the member access refers to #3. // Finally, instance binding will be performed as described later. // This can be written more verbosely and explicitly as any of: // - `return a.(Integer.Add)(b);` // - `return a.(Addable.Add)(b);` - // - `return a.(Integer.(Addable.Add))(b);` + // - `return a.((Integer as Addable).Add)(b);` return a.Add(b); } ``` +FIXME: left off here, needs updates: + The type `T` that is expected to implement `I` depends on the first operand of the member access expression, `V`: @@ -518,13 +602,16 @@ value other than a type, then _instance binding_ is performed, as follows: - For a field member in class `C`, `V` is required to be of type `C` or of a type derived from `C`. The result is the corresponding subobject within `V`. - The result is an lvalue if `V` is an lvalue. + The result is a + [reference expression](/docs/design/values.md#reference-expressions) if `V` + is a reference expression. ```carbon var dims: auto = {.width = 1, .height = 2}; // `dims.width` denotes the field `width` of the object `dims`. Print(dims.width); - // `dims` is an lvalue, so `dims.height` is an lvalue. + // `dims` is a reference expression, so `dims.height` is a + // reference expression. dims.height = 3; ``` @@ -552,6 +639,11 @@ value other than a type, then _instance binding_ is performed, as follows: } ``` +If `M` is an instance member, then compound member access `V.(M)` always +performs instance binding, whether or not `V` is a type. To get the `M` member +of interface `I` for a type `T`, use `(T as I).M`, as this doesn't attempt to +perform instance binding on `T`, in contrast to `T.(I.M)`. + ## Non-instance members If instance binding is not performed, the result is the member `M` determined by @@ -673,3 +765,5 @@ var n: i32 = 1 + X.Y; - Proposal [#989: member access expressions](https://github.com/carbon-language/carbon-lang/pull/989) - [Question for leads: constrained template name lookup](https://github.com/carbon-language/carbon-lang/issues/949) +- Proposal + [#2360: Types are values of type `type``](https://github.com/carbon-language/carbon-lang/pull/2360) From cedabbb095c8770ef4d8751e2030bb702c4af57d Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 23 Aug 2023 00:23:49 +0000 Subject: [PATCH 26/83] Iterating on member_access.md --- docs/design/expressions/member_access.md | 352 +++++++++++++---------- 1 file changed, 206 insertions(+), 146 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 80b0a35f09d74..60e79627e07d4 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -17,7 +17,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Values](#values) - [Facet binding](#facet-binding) - [Constant bindings](#constant-bindings) - - [Lookup ambiguity](#lookup-ambiguity) + - [Lookup ambiguity](#lookup-ambiguity) - [`impl` lookup](#impl-lookup) - [Instance binding](#instance-binding) - [Non-instance members](#non-instance-members) @@ -60,17 +60,19 @@ Compound member accesses allow specifying a qualified member name. For example: ```carbon -package Widgets api; -interface Widget { +namespace Widgets; + +interface Widgets.Widget { fn Grow[addr self: Self*](factor: f64); } -class Cog { + +class Widgets.Cog { var size: i32; fn Make(size: i32) -> Self; extend impl as Widgets.Widget; } -fn GrowSomeCogs() { +fn Widgets.GrowSomeCogs() { var cog1: Cog = Cog.Make(1); var cog2: Cog = cog1.Make(2); var cog_pointer: Cog* = &cog2; @@ -112,6 +114,15 @@ A member access expression is processed using the following steps: The process of _member resolution_ determines which member `M` a member access expression is referring to. +For a simple member access, the second operand is a word, which must name a +member of the first operand. + +For a compound member access, the second operand is evaluated as a constant to +determine the member being accessed. The evaluation is required to succeed and +to result in a member of a facet, type, or interface. If the result is an +instance member, then [instance binding](#instance-binding) is always performed +on the first operand. + ### Package and namespace members If the first operand is a package or namespace name, the expression must be a @@ -141,19 +152,18 @@ fn CallMyFunction2() { ### Types and facets Like the previous case, types (including -[facet types](/docs/design/generics/terminology.md#facet-type)) and facets have -member names, and lookup searches those names. For example: +[facet types](/docs/design/generics/terminology.md#facet-type)) have member +names, and lookup searches those names. For example: -- `i32.Least` should find the member constant `Least` of the type `i32`. -- `Addable.Add` should find the member function `Add` of the interface - `Addable`. Because a facet type is a type, this is a special case of the - previous bullet. +- `i32.Least` finds the member constant `Least` of the type `i32`. +- `Add.Op` finds the member function `Op` of the interface `Add`. Because a + facet type is a type, this is a special case of the previous bullet. Unlike the previous case, both simple and compound member access is allowed. -In the case of a facet, such as `T as Cowboy`, its members are members of the -`impl`, `T as Cowboy`, and not interface members, and so no further `impl` -lookup is performed. +Facets, such as `T as Cowboy`, also have members. Specifically, the members of +the `impl`, `T as Cowboy`. Being part of the `impl` rather than the interface, +no further [`impl` lookup](#impl-lookup) is needed. ```carbon interface Cowboy { @@ -176,60 +186,21 @@ implementation for `Avatar`, ignoring `Renderable.Draw`. ### Values If the first operand is not a type, package, namespace, or facet it does not -have member names, and lookup is performed into the type of the first operand -instead. FIXME - -### Facet binding - -FIXME: +have member names, and member lookup is performed into the type of the first +operand instead. -If any of the above lookups ever looks for members of a -[facet binding](/docs/design/generics/terminology.md#facet-binding), it should -consider members of the facet type, treating the facet binding as an -[archetype](/docs/design/generics/terminology.md#archetype). - -FIXME: - -**Note:** If lookup is performed into a type that involves a template binding, -the lookup will be performed both in the context of the template definition and -in the context of the template instantiation, as described in -[constant bindings](#constant-bindings). - -FIXME: - -For a simple member access, the word is looked up in the following types: - -- If the first operand can be evaluated and evaluates to a type, that type. -- If the type of the first operand can be evaluated, that type. -- If the type of the first operand is a symbolic facet binding, and the type - of that symbolic facet binding can be evaluated, that facet type. - -FIXME: - -The results of these lookups are [combined](#lookup-ambiguity). - -FIXME: - -For a compound member access, the second operand is evaluated as a constant to -determine the member being accessed. The evaluation is required to succeed and -to result in a member of a facet, type, or interface. If the result is an -instance member, then [instance binding](#instance-binding) is always performed -on the first operand. - -FIXME: - -For example: - -``` +```carbon interface Printable { fn Print[self: Self](); } + impl i32 as Printable; + class Point { var x: i32; var y: i32; - // Extending impl injects the name `Print` into class - // `Point`. + // Extending impl injects the name `Print` into + // class `Point`. extend impl as Printable; } @@ -243,19 +214,39 @@ fn PrintPointTwice() { // ✅ OK, `Print` found in type of `p`, namely `Point`. p.Print(); - // ✅ OK, `Print` found in the type `Printable`. + // ✅ OK, `Print` found in the type `Printable`, and + // `Printable.Print` found in the type of `p`. p.(Printable.Print)(); } +``` + +### Facet binding + +If any of the above lookups ever looks for members of a +[facet binding](/docs/design/generics/terminology.md#facet-binding), it should +consider members of the facet type, treating the facet binding as an +[archetype](/docs/design/generics/terminology.md#archetype). + +For example: + +``` +interface Printable { + fn Print[self: Self](); +} + fn GenericPrint[T:! Printable](a: T) { // ✅ OK, type of `a` is the facet binding `T`; // `Print` found in the type of `T`, namely `Printable`. a.Print(); } -fn CallGenericPrint(p: Point) { - GenericPrint(p); -} ``` +**Note:** If lookup is performed into a type that involves a template binding, +the lookup will be performed both in the context of the template definition and +in the context of the template instantiation, as described in +[constant bindings](#constant-bindings). The results of these lookups are +[combined](#lookup-ambiguity). + #### Constant bindings If the value or type of the first operand depends on a checked or template @@ -264,7 +255,7 @@ a context where the value of that parameter is unknown. Evaluation of an expression involving the parameter may still succeed, but will result in a symbolic value involving that parameter. -``` +```carbon class GenericWrapper(T:! type) { var field: T; } @@ -273,48 +264,98 @@ fn F[T:! type](x: GenericWrapper(T)) -> T { return x.field; } -class TemplateWrapper(template T:! type) { - var field: T; +interface Renderable { + fn Draw[self: Self](); } -fn G[template T:! type](x: TemplateWrapper(T)) -> T { - // 🤷 Not yet decided. - return x.field; +fn DrawChecked[T:! Renderable](c: T) { + // `Draw` resolves to `Renderable.Draw`. + c.Draw(); } -``` -> **TODO:** The behavior of `G` above is not yet fully decided. If class -> templates can be specialized, then we cannot know the members of -> `TemplateWrapper(T)` without knowing `T`, so this first lookup will find -> nothing. In any case, as described below, the lookup will be performed again -> when `T` is known. +class Cowboy { fn Draw[self: Self](); } +impl Cowboy as Renderable { fn Draw[self: Self](); } + +fn CallsDrawChecked(c: Cowboy) { + // ✅ Calls member of `impl Cowboy as Renderable`. + DrawChecked(c); + // In contrast to this which calls member of `Cowboy`: + c.Draw(); +} +``` If the value or type depends on any template binding, the lookup is redone from a context where the values of those binding are known, but where the values of any symbolic bindings are still unknown. The lookup results from these two contexts are [combined](#lookup-ambiguity). +```carbon +fn DrawTemplate[template T:! type](c: T) { + // `Draw` not found in `type`, looked up in the + // actual deduced value of `T`. + c.Draw(); +} + +fn CallsDrawTemplate(c: Cowboy) { + // ✅ Calls member of `Cowboy`: + DrawTemplate(c); + // Same behavior as: + c.Draw(); +} +``` + +> **TODO:** The behavior of this code depends on whether we decide to allow +> class templates to be specialized: +> +> ```carbon +> class TemplateWrapper(template T:! type) { +> var field: T; +> } +> fn G[template T:! type](x: TemplateWrapper(T)) -> T { +> // 🤷 Not yet decided. +> return x.field; +> } +> ``` +> +> If class specialization is allowed, then we cannot know the members of +> `TemplateWrapper(T)` without knowing `T`, so this first lookup will find +> nothing. In any case, the lookup will be performed again when `T` is known. + **Note:** All lookups are done from a context where the values of any symbolic bindings that are in scope are unknown. Unlike for a template binding, the actual value of a symbolic binding never affects the result of member resolution. +##### Lookup ambiguity + +Multiple lookups can be performed when resolving a member access expression with +a [template binding](#constant-bindings). We resolve this the same way as when +looking in multiple interfaces that are +[combined with `&`](/docs/design/generics/details.md#combining-interfaces-by-anding-facet-types): + +- If more than one distinct member is found, after performing + [`impl` lookup](#impl-lookup) if necessary, the lookup is ambiguous, and the + program is invalid. +- If no members are found, the program is invalid. +- Otherwise, the result of combining the lookup results is the unique member + that was found. + ```carbon -class Cowboy { fn Draw[self: Self](); } interface Renderable { fn Draw[self: Self](); } + +fn DrawTemplate2[template T:! Renderable](c: T) { + // Member lookup finds `Renderable.Draw` and the `Draw` + // member of the actual deduced value of `T`, if any. + c.Draw(); +} + +class Cowboy { fn Draw[self: Self](); } impl Cowboy as Renderable { fn Draw[self: Self](); } -fn DrawDirect(c: Cowboy) { c.Draw(); } -fn DrawGeneric[T:! Renderable](c: T) { c.Draw(); } -fn DrawTemplate[template T:! Renderable](c: T) { c.Draw(); } -fn Draw(c: Cowboy) { - // ✅ Calls member of `Cowboy`. - DrawDirect(c); - // ✅ Calls member of `impl Cowboy as Renderable`. - DrawGeneric(c); - // ❌ Error: ambiguous. - DrawTemplate(c); +class Pig { } +impl Pig as Renderable { + fn Draw[self: Self](); } class RoundWidget { @@ -331,31 +372,32 @@ class SquareWidget { } } -fn DrawWidget(r: RoundWidget, s: SquareWidget) { - // ✅ OK, lookup in type and lookup in facet type find the same entity. - DrawTemplate(r); +fn FlyTemplate[template T:! type](c: T) { + c.Fly(); +} - // ✅ OK, lookup in type and lookup in facet type find the same entity. - DrawTemplate(s); +fn Draw(c: Cowboy, p: Pig, r: RoundWidget, s: SquareWidget) { + // ❌ Error: ambiguous. `Cowboy.Draw` and + // `(Cowboy as Renderable).Draw` are different. + DrawTemplate2(c); - // ✅ OK, found in type. - r.Draw(); - s.Draw(); -} -``` + // ✅ OK, lookup in type `Pig` finds nothing, so uses + // lookup in facet type `Pig as Renderable`. + DrawTemplate2(p); -### Lookup ambiguity + // ✅ OK, lookup in type `RoundWidget` and lookup in facet + // type `RoundWidget as Renderable` find the same entity. + DrawTemplate2(r); -FIXME: this can arise due to template bindings looking both in the instantiated -type and the archetype derived from the facet type. it can also arise due to a -type extending multiple things each of which defines the name, or `X & Y` which -acts like something that extends `X` and `Y`. + // ✅ OK, lookup in type `SquareWidget` and lookup in facet + // type `SquareWidget as Renderable` find the same entity. + DrawTemplate2(s); -Multiple lookups can be performed when resolving a member access expression. If -more than one member is found, after performing [`impl` lookup](#impl-lookup) if -necessary, the lookup is ambiguous, and the program is invalid. Similarly, if no -members are found, the program is invalid. Otherwise, the result of combining -the lookup results is the unique member that was found. + // ❌ Error: `Fly` method not found in `Pig` or + // `Pig as type`. + FlyTemplate(p); +} +``` ## `impl` lookup @@ -364,39 +406,20 @@ relevant `impl`. It is performed when member access names an interface member, except when the member was found by a search of a facet type scope in a simple member access expression. -- For a simple member access `a.b` where `b` names a member of an interface - `I`: - - If the interface member was found by searching a non-facet-type scope - `T`, for example a class or an adapter, then `impl` lookup is performed - for `T as I`. For example: - - `MyClass.AliasForInterfaceMember` finds the member of the - `impl MyClass as I`, not the interface member. - - `my_value.AliasForInterfaceMember`, with `my_value: MyClass`, finds - the member of the `impl MyClass as I`, and performs - [instance binding](#instance-binding) if the member is an instance - member. - - In the case where the member was found in a base class of the class - that was searched, `T` is the derived class that was searched, not - the base class in which the name was declared. - - More generally, if the member was found in something the type - extends, such as a facet type or mixin, `T` is the type that was - initially searched, not what it extended. - - Otherwise, `impl` lookup is not performed: - - `MyInterface.AliasForInterfaceMember` finds the member in the - interface `MyInterface` and does not perform `impl` lookup. -- For a compound member access `a.(b)` where `b` names a member of an - interface `I`, `impl` lookup is performed for `T as I`, where: - - If `b` is an instance member, `T` is the type of `a`. - - `my_value.(Interface.InstanceInterfaceMember)()`, where `my_value` - is of type `MyClass`, uses `MyClass as Interface`. - - `MyClass.(Interface.InstanceInterfaceMember)()` uses - `type as Interface`. - - Otherwise, `a` is implicitly converted to `I`, and `T` is the result of - symbolically evaluating the converted expression. - - `MyClass.(Interface.NonInstanceInterfaceMember)()` uses - `MyClass as Interface`. - - `my_value.(Interface.NonInstanceInterfaceMember)()` is an error - unless `my_value` implicitly converts to a type. +For a simple member access `a.b` where `b` names a member of an interface `I`: + +- If the interface member was found by searching a non-facet-type scope `T`, + for example a class or an adapter, then `impl` lookup is performed for + `T as I`. + - In the case where the member was found in a base class of the class that + was searched, `T` is the derived class that was searched, not the base + class in which the name was declared. + - More generally, if the member was found in something the type extends, + such as a facet type or mixin, `T` is the type that was initially + searched, not what it extended. +- Otherwise, `impl` lookup is not performed: + +For example: ```carbon interface Addable { @@ -406,6 +429,7 @@ interface Addable { default fn Sum[Seq:! Iterable where .ValueType = Self](seq: Seq) -> Self { // ... } + alias AliasForSum = Sum; } class Integer { @@ -415,13 +439,44 @@ class Integer { // #4, generated from default implementation for #2. // fn Sum[...](...); } + + alias AliasForAdd = Addable.Add; } +``` +- For `Integer.Sum`, member resolution resolves the name `Sum` to \#2, which + is not an instance member. `impl` lookup then locates the + `impl Integer as Addable`, and determines that the member access refers to + \#4. +- For `a.Add(b)` where `a: Integer`, member resolution resolves the name `Add` + to \#1, which is an instance member. `impl` lookup then locates the + `impl Integer as Addable`, and determines that the member access refers to + \#3. Finally, instance binding will be performed as described later. +- `Integer.AliasForAdd` finds \#3, the `Add` member of the facet type + `Integer as Addable`, not \#1, the interface member `Addable.Add`. +- `my_int.AliasForAdd`, with `my_int: Integer`, finds \#3, the `Add` member of + the facet type `Integer as Addable`, and performs + [instance binding](#instance-binding) if the member is an instance member. +- `Addable.AliasForSum` finds \#2, the member in the interface `Addable`, and + does not perform `impl` lookup. + +For a compound member access `a.(b)` where `b` names a member of an interface +`I`, `impl` lookup is performed for `T as I`, where: + +- If `b` is an instance member, `T` is the type of `a`. + - `my_value.(Interface.InstanceInterfaceMember)()`, where `my_value` is of + type `MyClass`, uses `MyClass as Interface`. + - `MyClass.(Interface.InstanceInterfaceMember)()` uses + `type as Interface`. +- Otherwise, `a` is implicitly converted to `I`, and `T` is the result of + symbolically evaluating the converted expression. + - `MyClass.(Interface.NonInstanceInterfaceMember)()` uses + `MyClass as Interface`. so + - `my_value.(Interface.NonInstanceInterfaceMember)()` is an error unless + `my_value` implicitly converts to a type. + +```carbon fn SumIntegers(v: Vector(Integer)) -> Integer { - // Member resolution resolves the name `Sum` to #2, which is - // not an instance member. - // `impl` lookup then locates the `impl Integer as Addable`, - // and determines that the member access refers to #4, // which is then called. return Integer.Sum(v); } @@ -432,15 +487,18 @@ fn AddTwoIntegers(a: Integer, b: Integer) -> Integer { // `impl` lookup then locates the `impl Integer as Addable`, // and determines that the member access refers to #3. // Finally, instance binding will be performed as described later. + return a.Add(b); // This can be written more verbosely and explicitly as any of: // - `return a.(Integer.Add)(b);` + // ^ performs impl lookup here // - `return a.(Addable.Add)(b);` + // ^ impl lookup and instance binding here // - `return a.((Integer as Addable).Add)(b);` - return a.Add(b); + // no impl lookup needed } ``` -FIXME: left off here, needs updates: +FIXME: needs updates: The type `T` that is expected to implement `I` depends on the first operand of the member access expression, `V`: @@ -566,6 +624,7 @@ fn DrawWidget(r: RoundWidget, s: SquareWidget) { base class WidgetBase { // ✅ OK, even though `WidgetBase` does not implement `Renderable`. alias Draw = Renderable.Draw; + fn DrawAll[T:! Renderable](v: Vector(T)) { for (var w: T in v) { // ✅ OK. Unqualified lookup for `Draw` finds alias `WidgetBase.Draw` @@ -575,6 +634,7 @@ base class WidgetBase { // `Renderable`. Finally, the member function is bound to `w` as // described in "Instance binding". w.(Draw)(); + // ❌ Error: `Self.Draw` performs `impl` lookup, which fails // because `WidgetBase` does not implement `Renderable`. w.(Self.Draw)(); From 9b126b10195055e05a8b66f0f77256db91790a84 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 23 Aug 2023 21:52:16 +0000 Subject: [PATCH 27/83] Checkpoint progress. --- docs/design/expressions/member_access.md | 156 +++++++++++++---------- 1 file changed, 91 insertions(+), 65 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 60e79627e07d4..5a6f8eb1aa368 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -417,7 +417,10 @@ For a simple member access `a.b` where `b` names a member of an interface `I`: - More generally, if the member was found in something the type extends, such as a facet type or mixin, `T` is the type that was initially searched, not what it extended. -- Otherwise, `impl` lookup is not performed: +- Otherwise, `impl` lookup is not performed. + +[Instance binding](#instance-binding) may also apply if the member is an +instance member. For example: @@ -463,87 +466,108 @@ class Integer { For a compound member access `a.(b)` where `b` names a member of an interface `I`, `impl` lookup is performed for `T as I`, where: -- If `b` is an instance member, `T` is the type of `a`. - - `my_value.(Interface.InstanceInterfaceMember)()`, where `my_value` is of - type `MyClass`, uses `MyClass as Interface`. - - `MyClass.(Interface.InstanceInterfaceMember)()` uses - `type as Interface`. +- If `b` is an instance member, `T` is the type of `a`. In this case, + [instance binding](#instance-binding) is always performed. - Otherwise, `a` is implicitly converted to `I`, and `T` is the result of - symbolically evaluating the converted expression. - - `MyClass.(Interface.NonInstanceInterfaceMember)()` uses - `MyClass as Interface`. so - - `my_value.(Interface.NonInstanceInterfaceMember)()` is an error unless - `my_value` implicitly converts to a type. + symbolically evaluating the converted expression. In this case, + [instance binding](#instance-binding) is never performed. + +For example: ```carbon -fn SumIntegers(v: Vector(Integer)) -> Integer { - // which is then called. - return Integer.Sum(v); +interface Interface { + fn InstanceInterfaceMember[self: Self](); + fn NonInstanceInterfaceMember(); } -fn AddTwoIntegers(a: Integer, b: Integer) -> Integer { - // Member resolution resolves the name `Add` to #1, which is - // an instance member. - // `impl` lookup then locates the `impl Integer as Addable`, - // and determines that the member access refers to #3. - // Finally, instance binding will be performed as described later. - return a.Add(b); - // This can be written more verbosely and explicitly as any of: - // - `return a.(Integer.Add)(b);` - // ^ performs impl lookup here - // - `return a.(Addable.Add)(b);` - // ^ impl lookup and instance binding here - // - `return a.((Integer as Addable).Add)(b);` - // no impl lookup needed +class MyClass {} +impl MyClass as Interface; + +fn F(my_value: MyClass) { + // Since `Interface.InstanceInterfaceMember` is an instance + // member of `Interface`, `T` is set to the type of `my_value`, + // and so uses `MyClass as Interface`. + my_value.(Interface.InstanceInterfaceMember)(); + + // ❌ By the same logic, `T` is set to the type of `MyClass`, + // and so uses `type as Interface`, which isn't implemented. + MyClass.(Interface.InstanceInterfaceMember)(); + + // Since `Interface.NonInstanceInterfaceMember` is a + // non-instance member of `Interface`, `MyClass` is implicitly + // converted to `Interface`, and so uses `MyClass as Interface`. + MyClass.(Interface.NonInstanceInterfaceMember)(); + + // ❌ This is an error unless `my_value` implicitly converts to + // a type. + my_value.(Interface.NonInstanceInterfaceMember)(); } ``` -FIXME: needs updates: +FIXME: Maybe instead continue the previous example? + +```carbon +fn AddTwoIntegers(a: Integer, b: Integer) -> Integer { + // Since `Addable.Add` is an instance member of `Addable`, `T` + // is set to the type of `a`, and so uses `Integer as Addable`. + return a.(Addable.Add)(b); + // ^ impl lookup and instance binding here + // Impl lookup transforms this into: + // return a.((Integer as Addable).Add)(b); + // which no longer requires impl lookup. -The type `T` that is expected to implement `I` depends on the first operand of -the member access expression, `V`: + // ❌ By the same logic, in this example, `T` is set to the + // type of `Integer`, and so uses `type as Addable`, which + // isn't implemented. + return Integer.(Addable.Add)(...); +} -- If `V` can be evaluated and evaluates to a type, then `T` is `V`. - ```carbon - // `V` is `Integer`. `T` is `V`, which is `Integer`. - // Alias refers to #2. - alias AddIntegers = Integer.Add; - ``` -- Otherwise, `T` is the type of `V`. - ```carbon - let a: Integer = {}; - // `V` is `a`. `T` is the type of `V`, which is `Integer`. - // `a.Add` refers to #2. - let twice_a: Integer = a.Add(a); - ``` +fn SumIntegers(v: Vector(Integer)) -> Integer { + // Since `Addable.Sum` is a non-instance member of `Addable`, + // `Integer` is implicitly converted to `Addable`, and so uses + // `Integer as Addable`. + Integer.(Addable.Sum)(v); + // ^ impl lookup but no instance binding here + // Impl lookup transforms this into: + // ((Integer as Addable).Sum)(v); + // which no longer requires impl lookup. + + var a: Integer; + // ❌ This is an error since `a` does not implicitly convert to + // a type. + a.(Addable.Sum)(...); +} +``` The appropriate `impl T as I` implementation is located. The program is invalid if no such `impl` exists. When `T` or `I` depends on a checked generic binding, a suitable constraint must be specified to ensure that such an `impl` will -exist. When `T` or `I` depends on a template parameter, this check is deferred -until the argument for the template parameter is known. +exist. When `T` or `I` depends on a template binding, this check is deferred +until the value for the template binding is known. `M` is replaced by the member of the `impl` that corresponds to `M`. +FIXME: Left off here + ```carbon interface I { - // #1 + // #5 default fn F[self: Self]() {} let N:! i32; } class C { extend impl as I where .N = 5 { - // #2 + // #6 fn F[self: C]() {} } } // `V` is `I` and `M` is `I.F`. Because `V` is a facet type, -// `impl` lookup is not performed, and the alias binds to #1. +// `impl` lookup is not performed, and the alias binds to #5. alias A1 = I.F; // `V` is `C` and `M` is `I.F`. Because `V` is a type, `impl` -// lookup is performed with `T` being `C`, and the alias binds to #2. +// lookup is performed with `T` being `C`, and the alias binds to #6. alias A2 = C.F; let c: C = {}; @@ -562,31 +586,33 @@ instance member. var c: C; // `V` is `c` and `M` is `I.F`. Because `V` is not a type, `T` is the // type of `c`, which is `C`. `impl` lookup is performed, and `M` is -// replaced with #2. Then instance binding is performed. +// replaced with #6. Then instance binding is performed. c.F(); ``` +FIXME: Okay after this + **Note:** When an interface member is added to a class by an alias, `impl` lookup is not performed as part of handling the alias, but will happen when naming the interface member as a member of the class. ```carbon interface Renderable { - // #1 + // #7 fn Draw[self: Self](); } class RoundWidget { impl as Renderable { - // #2 + // #8 fn Draw[self: Self](); } - // `Draw` names the member of the `Renderable` interface. + // `Draw` names #7, the member of the `Renderable` interface. alias Draw = Renderable.Draw; } class SquareWidget { - // #3 + // #9 fn Draw[self: Self]() {} impl as Renderable { alias Draw = Self.Draw; @@ -595,29 +621,29 @@ class SquareWidget { fn DrawWidget(r: RoundWidget, s: SquareWidget) { // ✅ OK: In the inner member access, the name `Draw` resolves to the - // member `Draw` of `Renderable`, #1, which `impl` lookup replaces with - // the member `Draw` of `impl RoundWidget as Renderable`, #2. + // member `Draw` of `Renderable`, #7, which `impl` lookup replaces with + // the member `Draw` of `impl RoundWidget as Renderable`, #8. // The outer member access then forms a bound member function that - // calls #2 on `r`, as described in "Instance binding". + // calls #8 on `r`, as described in "Instance binding". r.(RoundWidget.Draw)(); // ✅ OK: In the inner member access, the name `Draw` resolves to the - // member `Draw` of `SquareWidget`, #3. + // member `Draw` of `SquareWidget`, #9. // The outer member access then forms a bound member function that - // calls #3 on `s`. + // calls #9 on `s`. s.(SquareWidget.Draw)(); // ❌ Error: In the inner member access, the name `Draw` resolves to the - // member `Draw` of `SquareWidget`, #3. + // member `Draw` of `SquareWidget`, #9. // The outer member access fails because we can't call - // #3, `Draw[self: SquareWidget]()`, on a `RoundWidget` object `r`. + // #9, `Draw[self: SquareWidget]()`, on a `RoundWidget` object `r`. r.(SquareWidget.Draw)(); // ❌ Error: In the inner member access, the name `Draw` resolves to the - // member `Draw` of `Renderable`, #1, which `impl` lookup replaces with - // the member `Draw` of `impl RoundWidget as Renderable`, #2. + // member `Draw` of `Renderable`, #7, which `impl` lookup replaces with + // the member `Draw` of `impl RoundWidget as Renderable`, #8. // The outer member access fails because we can't call - // #2, `Draw[self: RoundWidget]()`, on a `SquareWidget` object `s`. + // #8, `Draw[self: RoundWidget]()`, on a `SquareWidget` object `s`. s.(RoundWidget.Draw)(); } From a392b27f04cea14b6ecb8d0e72c9893a47ca8d2d Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 25 Aug 2023 20:55:49 +0000 Subject: [PATCH 28/83] Checkpoint progress. --- docs/design/expressions/member_access.md | 247 +++++++++++++---------- 1 file changed, 143 insertions(+), 104 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 5a6f8eb1aa368..a117110a3f8f3 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -19,6 +19,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Constant bindings](#constant-bindings) - [Lookup ambiguity](#lookup-ambiguity) - [`impl` lookup](#impl-lookup) + - [`impl` lookup for simple member access](#impl-lookup-for-simple-member-access) + - [`impl` lookup for compound member access](#impl-lookup-for-compound-member-access) - [Instance binding](#instance-binding) - [Non-instance members](#non-instance-members) - [Non-vacuous member access restriction](#non-vacuous-member-access-restriction) @@ -406,9 +408,12 @@ relevant `impl`. It is performed when member access names an interface member, except when the member was found by a search of a facet type scope in a simple member access expression. +### `impl` lookup for simple member access + For a simple member access `a.b` where `b` names a member of an interface `I`: -- If the interface member was found by searching a non-facet-type scope `T`, +- If the interface member was found by searching a + non-[facet-type](/docs/design/generics/terminology.md#facet-type) scope `T`, for example a class or an adapter, then `impl` lookup is performed for `T as I`. - In the case where the member was found in a base class of the class that @@ -419,10 +424,7 @@ For a simple member access `a.b` where `b` names a member of an interface `I`: searched, not what it extended. - Otherwise, `impl` lookup is not performed. -[Instance binding](#instance-binding) may also apply if the member is an -instance member. - -For example: +For the following examples, consider these definitions: ```carbon interface Addable { @@ -447,6 +449,39 @@ class Integer { } ``` +The type `T` that is expected to implement `I` depends on the first operand of +the member access expression, `V`: + +- If `V` can be evaluated and evaluates to a + [type](/docs/design/generics/terminology.md#types-and-type), then `T` is + `V`. + ```carbon + // `V` is `Integer`. `T` is `V`, which is `Integer`. + // Alias refers to #2. + alias AddIntegers = Integer.Add; + ``` +- Otherwise, `T` is the type of `V`. + + ```carbon + let a: Integer = {}; + // `V` is `a`. `T` is the type of `V`, which is `Integer`. + // `a.Add` refers to #2. + let twice_a: Integer = a.Add(a); + ``` + +The appropriate `impl T as I` implementation is located. The program is invalid +if no such `impl` exists. When `T` or `I` depends on a checked generic binding, +a suitable constraint must be specified to ensure that such an `impl` will +exist. When `T` or `I` depends on a template binding, this check is deferred +until the value for the template binding is known. + +`M` is replaced by the member of the `impl` that corresponds to `M`. + +[Instance binding](#instance-binding) may also apply if the member is an +instance member. + +Following the above example: + - For `Integer.Sum`, member resolution resolves the name `Sum` to \#2, which is not an instance member. `impl` lookup then locates the `impl Integer as Addable`, and determines that the member access refers to @@ -459,95 +494,14 @@ class Integer { `Integer as Addable`, not \#1, the interface member `Addable.Add`. - `my_int.AliasForAdd`, with `my_int: Integer`, finds \#3, the `Add` member of the facet type `Integer as Addable`, and performs - [instance binding](#instance-binding) if the member is an instance member. + [instance binding](#instance-binding) since the member is an instance + member. - `Addable.AliasForSum` finds \#2, the member in the interface `Addable`, and does not perform `impl` lookup. -For a compound member access `a.(b)` where `b` names a member of an interface -`I`, `impl` lookup is performed for `T as I`, where: - -- If `b` is an instance member, `T` is the type of `a`. In this case, - [instance binding](#instance-binding) is always performed. -- Otherwise, `a` is implicitly converted to `I`, and `T` is the result of - symbolically evaluating the converted expression. In this case, - [instance binding](#instance-binding) is never performed. +FIXME: Is the following example still needed? -For example: - -```carbon -interface Interface { - fn InstanceInterfaceMember[self: Self](); - fn NonInstanceInterfaceMember(); -} - -class MyClass {} -impl MyClass as Interface; - -fn F(my_value: MyClass) { - // Since `Interface.InstanceInterfaceMember` is an instance - // member of `Interface`, `T` is set to the type of `my_value`, - // and so uses `MyClass as Interface`. - my_value.(Interface.InstanceInterfaceMember)(); - - // ❌ By the same logic, `T` is set to the type of `MyClass`, - // and so uses `type as Interface`, which isn't implemented. - MyClass.(Interface.InstanceInterfaceMember)(); - - // Since `Interface.NonInstanceInterfaceMember` is a - // non-instance member of `Interface`, `MyClass` is implicitly - // converted to `Interface`, and so uses `MyClass as Interface`. - MyClass.(Interface.NonInstanceInterfaceMember)(); - - // ❌ This is an error unless `my_value` implicitly converts to - // a type. - my_value.(Interface.NonInstanceInterfaceMember)(); -} -``` - -FIXME: Maybe instead continue the previous example? - -```carbon -fn AddTwoIntegers(a: Integer, b: Integer) -> Integer { - // Since `Addable.Add` is an instance member of `Addable`, `T` - // is set to the type of `a`, and so uses `Integer as Addable`. - return a.(Addable.Add)(b); - // ^ impl lookup and instance binding here - // Impl lookup transforms this into: - // return a.((Integer as Addable).Add)(b); - // which no longer requires impl lookup. - - // ❌ By the same logic, in this example, `T` is set to the - // type of `Integer`, and so uses `type as Addable`, which - // isn't implemented. - return Integer.(Addable.Add)(...); -} - -fn SumIntegers(v: Vector(Integer)) -> Integer { - // Since `Addable.Sum` is a non-instance member of `Addable`, - // `Integer` is implicitly converted to `Addable`, and so uses - // `Integer as Addable`. - Integer.(Addable.Sum)(v); - // ^ impl lookup but no instance binding here - // Impl lookup transforms this into: - // ((Integer as Addable).Sum)(v); - // which no longer requires impl lookup. - - var a: Integer; - // ❌ This is an error since `a` does not implicitly convert to - // a type. - a.(Addable.Sum)(...); -} -``` - -The appropriate `impl T as I` implementation is located. The program is invalid -if no such `impl` exists. When `T` or `I` depends on a checked generic binding, -a suitable constraint must be specified to ensure that such an `impl` will -exist. When `T` or `I` depends on a template binding, this check is deferred -until the value for the template binding is known. - -`M` is replaced by the member of the `impl` that corresponds to `M`. - -FIXME: Left off here +Here is another example: ```carbon interface I { @@ -562,7 +516,7 @@ class C { } } -// `V` is `I` and `M` is `I.F`. Because `V` is a facet type, +// `V` is `I` and `M` is `I.F`. Because `I` is a facet type, // `impl` lookup is not performed, and the alias binds to #5. alias A1 = I.F; @@ -579,19 +533,6 @@ let c: C = {}; let Z: i32 = c.N; ``` -[Instance binding](#instance-binding) may also apply if the member is an -instance member. - -```carbon -var c: C; -// `V` is `c` and `M` is `I.F`. Because `V` is not a type, `T` is the -// type of `c`, which is `C`. `impl` lookup is performed, and `M` is -// replaced with #6. Then instance binding is performed. -c.F(); -``` - -FIXME: Okay after this - **Note:** When an interface member is added to a class by an alias, `impl` lookup is not performed as part of handling the alias, but will happen when naming the interface member as a member of the class. @@ -680,8 +621,106 @@ fn DrawTriangle(t: TriangleWidget) { } ``` +### `impl` lookup for compound member access + +For a compound member access `a.(b)` where `b` names a member of an interface +`I`, `impl` lookup is performed for `T as I`, where: + +- If `b` is an instance member, `T` is the type of `a`. In this case, + [instance binding](#instance-binding) is always performed. +- Otherwise, `a` is implicitly converted to `I`, and `T` is the result of + symbolically evaluating the converted expression. In this case, + [instance binding](#instance-binding) is never performed. + +For example: + +```carbon +interface Interface { + fn InstanceInterfaceMember[self: Self](); + fn NonInstanceInterfaceMember(); +} + +class MyClass {} +impl MyClass as Interface; + +fn F(my_value: MyClass) { + // Since `Interface.InstanceInterfaceMember` is an instance + // member of `Interface`, `T` is set to the type of `my_value`, + // and so uses `MyClass as Interface`. + my_value.(Interface.InstanceInterfaceMember)(); + + // ❌ By the same logic, `T` is set to the type of `MyClass`, + // and so uses `type as Interface`, which isn't implemented. + MyClass.(Interface.InstanceInterfaceMember)(); + + // Since `Interface.NonInstanceInterfaceMember` is a + // non-instance member of `Interface`, `MyClass` is implicitly + // converted to `Interface`, and so uses `MyClass as Interface`. + MyClass.(Interface.NonInstanceInterfaceMember)(); + + // ❌ This is an error unless `my_value` implicitly converts to + // a type. + my_value.(Interface.NonInstanceInterfaceMember)(); +} +``` + +FIXME: Maybe instead continue the previous example? + +```carbon +fn AddTwoIntegers(a: Integer, b: Integer) -> Integer { + // Since `Addable.Add` is an instance member of `Addable`, `T` + // is set to the type of `a`, and so uses `Integer as Addable`. + return a.(Addable.Add)(b); + // ^ impl lookup and instance binding here + // Impl lookup transforms this into: + // return a.((Integer as Addable).Add)(b); + // which no longer requires impl lookup. + + // ❌ By the same logic, in this example, `T` is set to the + // type of `Integer`, and so uses `type as Addable`, which + // isn't implemented. + return Integer.(Addable.Add)(...); +} + +fn SumIntegers(v: Vector(Integer)) -> Integer { + // Since `Addable.Sum` is a non-instance member of `Addable`, + // `Integer` is implicitly converted to `Addable`, and so uses + // `Integer as Addable`. + Integer.(Addable.Sum)(v); + // ^ impl lookup but no instance binding here + // Impl lookup transforms this into: + // ((Integer as Addable).Sum)(v); + // which no longer requires impl lookup. + + var a: Integer; + // ❌ This is an error since `a` does not implicitly convert to + // a type. + a.(Addable.Sum)(...); +} +``` + ## Instance binding +FIXME: new text + +Next, _instance binding_ may be performed. This associates an expression with a +particular object instance. For example, this is the value bound to `self` when +calling a method. + +For the simple member access syntax `x.y`, if `x` is an entity that has member +names, such as a namespace or a type, then `y` is looked up within `x`, and +instance binding is not performed. Otherwise, `y` is looked up within the type +of `x` and instance binding is performed if an instance member is found. + +The compound member access syntax `x.(Y)`, where `Y` names an instance member, +always performs instance binding. Therefore, for a suitable `DebugPrintable`: + +- `1.(DebugPrintable.Print)()` prints `1`. +- `i32.(DebugPrintable.Print)()` prints `i32`. +- `1.(i32.(DebugPrintable.Print))()` is an error. + +FIXME: old text + If member resolution and `impl` lookup produce a member `M` that is an instance member -- that is, a field or a method -- and the first operand `V` of `.` is a value other than a type, then _instance binding_ is performed, as follows: From aa2d84e8ae4bcb2a4805eeac049a7d29ea3198de Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 25 Aug 2023 21:42:29 +0000 Subject: [PATCH 29/83] Checkpoint progress. --- docs/design/expressions/member_access.md | 93 ++++++++++++++---------- 1 file changed, 55 insertions(+), 38 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index a117110a3f8f3..39542bba7e7f6 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -648,6 +648,7 @@ fn F(my_value: MyClass) { // member of `Interface`, `T` is set to the type of `my_value`, // and so uses `MyClass as Interface`. my_value.(Interface.InstanceInterfaceMember)(); + // ^ impl lookup and instance binding here // ❌ By the same logic, `T` is set to the type of `MyClass`, // and so uses `type as Interface`, which isn't implemented. @@ -701,8 +702,6 @@ fn SumIntegers(v: Vector(Integer)) -> Integer { ## Instance binding -FIXME: new text - Next, _instance binding_ may be performed. This associates an expression with a particular object instance. For example, this is the value bound to `self` when calling a method. @@ -712,18 +711,7 @@ names, such as a namespace or a type, then `y` is looked up within `x`, and instance binding is not performed. Otherwise, `y` is looked up within the type of `x` and instance binding is performed if an instance member is found. -The compound member access syntax `x.(Y)`, where `Y` names an instance member, -always performs instance binding. Therefore, for a suitable `DebugPrintable`: - -- `1.(DebugPrintable.Print)()` prints `1`. -- `i32.(DebugPrintable.Print)()` prints `i32`. -- `1.(i32.(DebugPrintable.Print))()` is an error. - -FIXME: old text - -If member resolution and `impl` lookup produce a member `M` that is an instance -member -- that is, a field or a method -- and the first operand `V` of `.` is a -value other than a type, then _instance binding_ is performed, as follows: +If instance binding is performed: - For a field member in class `C`, `V` is required to be of type `C` or of a type derived from `C`. The result is the corresponding subobject within `V`. @@ -764,10 +752,35 @@ value other than a type, then _instance binding_ is performed, as follows: } ``` -If `M` is an instance member, then compound member access `V.(M)` always -performs instance binding, whether or not `V` is a type. To get the `M` member -of interface `I` for a type `T`, use `(T as I).M`, as this doesn't attempt to -perform instance binding on `T`, in contrast to `T.(I.M)`. +The compound member access syntax `x.(Y)`, where `Y` names an instance member, +always performs instance binding. It is an error if `Y` is already bound to an +instance member. For example: + +```carbon +interface DebugPrint { + // instance member + fn Print[self:Self](); +} +impl i32 as DebugPrint; +impl type as DebugPrint; + +fn Debug() { + var i: i32 = 1; + + // Prints `1` using `(i32 as DebugPrint).Print` bound to `i`. + i.(DebugPrintable.Print)(); + + // Prints `i32` using `(type as DebugPrint).Print` bound to `i32`. + i32.(DebugPrintable.Print)(); + + // ❌ This is an error since `i32.(DebugPrintable.Print)` is + // already bound, and may not be bound again to `i`. + i.(i32.(DebugPrintable.Print))(); +} +``` + +To get the `M` member of interface `I` for a type `T`, use `(T as I).M`, as this +doesn't attempt to perform instance binding on `T`, in contrast to `T.(I.M)`. ## Non-instance members @@ -818,30 +831,34 @@ always used for lookup. interface Printable { fn Print[self: Self](); } -impl i32 as Printable { - fn Print[self: Self](); -} +impl i32 as Printable; + fn MemberAccess(n: i32) { - // ✅ OK: `Printable.Print` is the interface member. - // `i32.(Printable.Print)` is the corresponding member of the `impl`. - // `n.(i32.(Printable.Print))` is a bound member function naming that member. - n.(i32.(Printable.Print))(); - - // ✅ Same as above, `n.(Printable.Print)` is effectively interpreted as - // `n.(T.(Printable.Print))()`, where `T` is the type of `n`, - // because `n` does not evaluate to a type. Performs impl lookup - // and then instance binding. + // ✅ OK: `(i32 as Printable).Print` is the `Print` member of the + // `i32 as Printable` facet corresponding to the `Printable.Print` + // interface member. + // `n.((i32 as Printable).Print)` is that member function bound to `n`. + n.((i32 as Printable).Print)(); + + // ✅ Same as above, `n.(Printable.Print)` is effectively interpreted + // as `n.((T as Printable).Print)()`, where `T` is the type of `n`. + // Performs impl lookup and then instance binding. n.(Printable.Print)(); } -// ✅ OK, member `Print` of interface `Printable`. -alias X1 = Printable.Print; -// ❌ Error, compound access doesn't perform impl lookup or instance binding. -alias X2 = Printable.(Printable.Print); -// ✅ OK, member `Print` of `impl i32 as Printable`. -alias X3 = i32.(Printable.Print); -// ❌ Error, compound access doesn't perform impl lookup or instance binding. -alias X4 = i32.(i32.(Printable.Print)); +interface Factory { + fn Make() -> Self; +} +impl i32 as Factory; + +// ✅ OK, member `Make` of interface `Factory`. +alias X1 = Factory.Make; +// ❌ Error, compound access without impl lookup or instance binding. +alias X2 = Factory.(Factory.Make); +// ✅ OK, member `Make` of `impl i32 as Factory`. +alias X3 = (i32 as Factory).Make; +// ❌ Error, compound access without impl lookup or instance binding. +alias X4 = i32.((i32 as Factory).Make); ``` ## Precedence and associativity From 4618f9a28924d2b6a320e201d8799cd551a6ed0c Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 29 Aug 2023 19:29:04 +0000 Subject: [PATCH 30/83] Checkpoint progress. --- docs/design/README.md | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/docs/design/README.md b/docs/design/README.md index e7e3747b91326..880f1bdf75763 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -877,8 +877,7 @@ Carbon.Print(a[0]); > **TODO:** Slices > **Note:** This is provisional, no design for arrays has been through the -> proposal process yet. See pending proposal -> [#1928: Arrays](https://github.com/carbon-language/carbon-lang/pull/1928). +> proposal process yet. ## Expressions @@ -1425,6 +1424,8 @@ for (var name: String in names) { > - [`for` loops](control_flow/loops.md#for) > - Proposal > [#353: Add C++-like `for` loops](https://github.com/carbon-language/carbon-lang/pull/353) +> - Proposal +> [#1885: ranged-based `for` for user-defined types](https://github.com/carbon-language/carbon-lang/pull/1885) ##### `break` @@ -2703,8 +2704,8 @@ not itself a type. ### Checked and template parameters -The `:!` indicates that the `T` parameter is generic, and therefore bound at -compile time. Generic parameters may either be _checked_ or _template_, and +The `:!` marks it as a compile-time binding, and so `T` is a compile-time +parameter. Compile-time parameters may either be _checked_ or _template_, and default to checked. "Checked" here means that the body of `Min` is type checked when the function is @@ -2757,6 +2758,9 @@ class Array(template T:! type, template N:! i64) if N >= 0 and N < MaxArraySize / sizeof(T); ``` +> **TODO:** The design for template constraints is still under development. The +> `if` clause approach here is provisional. + Member lookup into a template parameter is done in the actual value provided by the caller, _in addition_ to any constraints. This means member name lookup and type checking for anything [dependent](generics/terminology.md#dependent-names) @@ -2789,9 +2793,9 @@ rigor of checked generics are problematic. ### Interfaces and implementations -_Interfaces_ specify a set of requirements that a types might satisfy. -Interfaces act both as constraints on types a caller might supply and -capabilities that may be assumed of types that satisfy that constraint. +_Interfaces_ specify a set of requirements that a type might satisfy. Interfaces +act both as constraints on types a caller might supply and capabilities that may +be assumed of types that satisfy that constraint. ```carbon interface Printable { @@ -2873,8 +2877,8 @@ by replacing the definition scope in curly braces (`{`...`}`) with a semicolon. ### Combining constraints -A function can require calling types to implement multiple interfaces by -combining them using an ampersand (`&`): +A function can require calling types to implement multiple interfaces (or other +facet types) by combining them using an ampersand (`&`): ```carbon fn PrintMin[T:! Ordered & Printable](x: T, y: T) { @@ -2966,9 +2970,9 @@ Many Carbon entities, not just functions, may be made generic by adding #### Generic Classes Classes may be defined with an optional explicit parameter list. All parameters -to a class must be generic, and so defined with `:!`, either with or without the -`template` prefix. For example, to define a stack that can hold values of any -type `T`: +to a class must be compile-time, and so defined with `:!`, either with or +without the `template` prefix. For example, to define a stack that can hold +values of any type `T`: ```carbon class Stack(T:! type) { @@ -3045,7 +3049,7 @@ _determined by_ the implementation of an interface for a type. #### Generic implementations -An `impl` declaration may be parameterized by adding `forall [`_generic +An `impl` declaration may be parameterized by adding `forall [`_compile-time parameter list_`]` after the `impl` keyword introducer, as in: ```carbon @@ -3095,7 +3099,8 @@ Carbon generics have a number of other features, including: - [Named constraints](generics/details.md#named-constraints) may be used to disambiguate when combining two interfaces that have name conflicts. Named - constraints may be implemented and otherwise used in place of an interface. + constraints define facet types, and may be implemented and otherwise used in + place of an interface. - [Template constraints](generics/details.md#named-constraints) are a kind of named constraint that can contain structural requirements. For example, a template constraint could match any type that has a function with a specific @@ -3111,13 +3116,13 @@ Carbon generics have a number of other features, including: [`where` constraints](generics/details.md#where-constraints). - [Implied constraints](generics/details.md#implied-constraints) allows some constraints to be deduced and omitted from a function signature. -- [Dynamic erased types](generics/details.md#runtime-type-fields) can hold any - value with a type implementing an interface, and allows the functions in - that interface to be called using +- _Planned_ [dynamic erased types](generics/details.md#runtime-type-fields) + can hold any value with a type implementing an interface, and allows the + functions in that interface to be called using [dynamic dispatch](https://en.wikipedia.org/wiki/Dynamic_dispatch), for some interfaces marked "`dyn`-safe". **Note:** Provisional. -- [Variadics](generics/details.md#variadic-arguments) supports variable-length - parameter lists. **Note:** Provisional. +- _Planned_ [variadics](generics/details.md#variadic-arguments) supports + variable-length parameter lists. **Note:** Provisional. > References: > From bb870f1e216f3ff37d5e3b49efd825e2d973991b Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 29 Aug 2023 21:01:31 +0000 Subject: [PATCH 31/83] Checkpoint progress. --- docs/design/expressions/if.md | 5 ++--- docs/design/templates.md | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/design/expressions/if.md b/docs/design/expressions/if.md index e758784326435..8ab47552f1956 100644 --- a/docs/design/expressions/if.md +++ b/docs/design/expressions/if.md @@ -175,9 +175,8 @@ _Note:_ This rule is intended to be considered more specialized than the other rules in this document. Because this `impl` is declared `final`, `T.(CommonType(T)).Result` is always -assumed to be `T`, even in contexts where `T` involves a generic parameter or -symbolic binding and so the result would normally be an unknown type whose facet -type is `type`. +assumed to be `T`, even in contexts where `T` involves a symbolic binding and so +the result would normally be an unknown type whose facet type is `type`. ``` fn F[T:! Hashable](c: bool, x: T, y: T) -> HashCode { diff --git a/docs/design/templates.md b/docs/design/templates.md index be062837f0589..fe77637fbe4e7 100644 --- a/docs/design/templates.md +++ b/docs/design/templates.md @@ -103,8 +103,8 @@ Because we consider only specific _parameters_ to be templated and they could be individually migrated to a constrained interface using the [checked-generics system](README.md#generics), constraining templates themselves may be less critical. Instead, we expect parameterized types and functions may -use a mixture of checked-generic parameters and templated parameters based on -where they are constrained. +use a mixture of checked and template generic parameters based on where they are +constrained. However, if there are still use cases, we would like to explore applying the interface constraints of the checked-generics system directly to template From 45380aa5bf084e61f1a754fca15e219fdeff656b Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 29 Aug 2023 22:15:46 +0000 Subject: [PATCH 32/83] Split out witness tables as an appendix --- docs/design/generics/appendix-witness.md | 218 +++++++++++++++++++++++ docs/design/generics/details.md | 159 ++++------------- docs/design/generics/terminology.md | 75 +++----- 3 files changed, 275 insertions(+), 177 deletions(-) create mode 100644 docs/design/generics/appendix-witness.md diff --git a/docs/design/generics/appendix-witness.md b/docs/design/generics/appendix-witness.md new file mode 100644 index 0000000000000..e30adf11f3780 --- /dev/null +++ b/docs/design/generics/appendix-witness.md @@ -0,0 +1,218 @@ +# Generics appendix: Witness tables + + + + + +## Table of contents + +- [Overview](#overview) +- [Terminology](#terminology) + - [Witness tables](#witness-tables) + - [Dynamic-dispatch witness table](#dynamic-dispatch-witness-table) + - [Static-dispatch witness table](#static-dispatch-witness-table) +- [Limitations of witness tables](#limitations-of-witness-tables) + - [Associated constants](#associated-constants) + - [Blanket implementations](#blanket-implementations) + - [Specialization](#specialization) +- [Examples of how you might implement Carbon's generics with witness tables](#examples-of-how-you-might-implement-carbons-generics-with-witness-tables) + - [FIXME: old text from details.md "Overview" section](#fixme-old-text-from-detailsmd-overview-section) + - [FIXME: Checked-generics implementation model](#fixme-checked-generics-implementation-model) + - [FIXME: Associated facets implementation model](#fixme-associated-facets-implementation-model) + - [FIXME: Sized type implementation model](#fixme-sized-type-implementation-model) + + + +## Overview + +Witness tables are a strategy for implementing generics, specifically for +allowing the behavior of a generic function to vary with the values of generic +parameters. They have some nice properties: + +- They can be used both for runtime and compile-time dispatch. +- They can support separate compilation even with compile-time dispatch. + +However, it can be a challenge to implement some features of a generic system +with witness tables. This leads to either limitations on the generic system or +additional runtime overhead. + +Swift uses witness tables for both static and dynamic dispatch, accepting both +limitations and overhead. Carbon and Rust only use witness tables for dynamic +dispatch, and apply limitations to control the runtime overhead when using that +feature. As an implementation detail, Carbon compilers might also use witness +tables for static dispatch, for example when the code conforms to the +limitations of what witness tables support. However, part of the point of this +document is to state the limitations and obstacles of doing that. + +## Terminology + +### Witness tables + +[Witness tables](https://forums.swift.org/t/where-does-the-term-witness-table-come-from/54334/4) +are an implementation strategy where values passed to a generic type parameter +are compiled into a table of required functionality. That table is then filled +in for a given passed-in type with references to the implementation on the +original type. The generic is implemented using calls into entries in the +witness table, which turn into calls to the original type. This doesn't +necessarily imply a runtime indirection: it may be a purely compile-time +separation of concerns. However, it insists on a full abstraction boundary +between the generic user of a type and the concrete implementation. + +A simple way to imagine a witness table is as a struct of function pointers, one +per method in the interface. However, in practice, it's more complex because it +must model things like associated facets and interfaces. + +Witness tables are called "dictionary passing" in Haskell. Outside of generics, +a [vtable](https://en.wikipedia.org/wiki/Virtual_method_table) is a witness +table that witnesses that a class is a descendant of an abstract base class, and +is passed as part of the object instead of separately. + +### Dynamic-dispatch witness table + +For dynamic-dispatch witness tables, actual function pointers are formed and +used as a dynamic, runtime indirection. As a result, the generic code **will +not** be duplicated for different witness tables. + +### Static-dispatch witness table + +For static-dispatch witness tables, the implementation is required to collapse +the table indirections at compile time. As a result, the generic code **will** +be duplicated for different witness tables. + +Static-dispatch may be implemented as a performance optimization for +dynamic-dispatch that increases generated code size. The final compiled output +may not retain the witness table. + +## Limitations of witness tables + +### Associated constants + +FIXME + +### Blanket implementations + +FIXME + +### Specialization + +FIXME + +## Examples of how you might implement Carbon's generics with witness tables + +### FIXME: old text from details.md "Overview" section + +We can think of the interface as defining a struct type whose members are +function pointers, and an implementation of an interface as a value of that +struct with actual function pointer values. An implementation is a table mapping +the interface's functions to function pointers. For more on this, see +[the implementation model section](#fixme-checked-generics-implementation-model). + +For example, the [type's size](#fixme-sized-type-implementation-model) +(represented by an integer constant member of the type) could be a member of an +interface and its implementation. There are a few cases why we would include +another interface implementation as a member: + +- [associated facets](details.md#associated-facets) +- [type parameters](details.md#parameterized-interfaces) +- [interface requirements](details.md#interface-requiring-other-interfaces) + +### FIXME: Checked-generics implementation model + +A possible model for generating code for a generic function is to use a +[witness table](#witness-tables) to represent how a type implements an +interface: + +- [Interfaces](details.md#interfaces) are types of witness tables. +- An [impl](details.md#implementing-interfaces) is a witness table value. + +Type checking is done with just the interface. The impl is used during code +generation time, possibly using +[monomorphization](https://en.wikipedia.org/wiki/Monomorphization) to have a +separate instantiation of the function for each combination of the generic +argument values. The compiler is free to use other implementation strategies, +such as passing the witness table for any needed implementations, if that can be +predicted. + +For the example above, [the Vector interface](details.md#interfaces) could be +thought of defining a witness table type like: + +``` +class Vector { + // `Self` is the representation type, which is only + // known at compile time. + var Self:! type; + // `fnty` is **placeholder** syntax for a "function type", + // so `Add` is a function that takes two `Self` parameters + // and returns a value of type `Self`. + var Add: fnty(a: Self, b: Self) -> Self; + var Scale: fnty(a: Self, v: f64) -> Self; +} +``` + +The [impl of Vector for Point_Inline](details.md#inline-impl) would be a value +of this type: + +``` +var VectorForPoint_Inline: Vector = { + .Self = Point_Inline, + // `lambda` is **placeholder** syntax for defining a + // function value. + .Add = lambda(a: Point_Inline, b: Point_Inline) -> Point_Inline { + return {.x = a.x + b.x, .y = a.y + b.y}; + }, + .Scale = lambda(a: Point_Inline, v: f64) -> Point_Inline { + return {.x = a.x * v, .y = a.y * v}; + }, +}; +``` + +Since generic arguments (where the parameter is declared using `:!`) are passed +at compile time, so the actual value of `VectorForPoint_Inline` can be used to +generate the code for functions using that impl. This is the +[static-dispatch witness table](#static-dispatch-witness-table) approach. + +### FIXME: Associated facets implementation model + +The associated facet can be modeled by a witness table field in the interface's +witness table. + +``` +interface Iterator { + fn Advance[addr self: Self*](); +} + +interface Container { + let IteratorType:! Iterator; + fn Begin[addr self: Self*]() -> IteratorType; +} +``` + +is represented by: + +``` +class Iterator(Self:! type) { + var Advance: fnty(this: Self*); + ... +} +class Container(Self:! type) { + // Representation type for the iterator. + let IteratorType:! type; + // Witness that IteratorType implements Iterator. + var iterator_impl: Iterator(IteratorType)*; + + // Method + var Begin: fnty (this: Self*) -> IteratorType; + ... +} +``` + +#### FIXME: Sized type implementation model + +This requires a special integer field be included in the witness table type to +hold the size of the type. This field will only be known generically, so if its +value is used for type checking, we need some way of evaluating those type tests +symbolically. diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 94bcf4a8c18c2..9f4c0b895e96a 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -24,7 +24,6 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Access](#access) - [Checked generics](#checked-generics) - [Return type](#return-type) - - [Implementation model](#implementation-model) - [Interfaces recap](#interfaces-recap) - [Facet types](#facet-types) - [Named constraints](#named-constraints) @@ -46,7 +45,6 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Associated constants](#associated-constants) - [Associated class functions](#associated-class-functions) - [Associated facets](#associated-facets) - - [Implementation model](#implementation-model-1) - [Parameterized interfaces](#parameterized-interfaces) - [Impl lookup](#impl-lookup) - [Parameterized named constraints](#parameterized-named-constraints) @@ -77,7 +75,6 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Example: Multiple implementations of the same interface](#example-multiple-implementations-of-the-same-interface) - [Example: Creating an impl out of other implementations](#example-creating-an-impl-out-of-other-implementations) - [Sized types and facet types](#sized-types-and-facet-types) - - [Implementation model](#implementation-model-2) - [`TypeId`](#typeid) - [Destructor constraints](#destructor-constraints) - [Generic `let`](#generic-let) @@ -142,7 +139,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception This document goes into the details of the design of Carbon's [generics](terminology.md#generic-means-compile-time-parameterized), by which we -mean parameterizing some language construct with compile-time parameters. These +mean generalizing some language construct with compile-time parameters. These parameters can be types, [facets](terminology.md#facet), or other values. Imagine we want to write a function parameterized by a type argument. Maybe our @@ -164,35 +161,31 @@ doc). Basically, the user passes in a value for `val`, and the type of `val` determines `T`. `T` still gets passed into the function though, and it plays an important role -- it defines the key used to look up interface implementations. -We can think of the interface as defining a struct type whose members are -function pointers, and an implementation of an interface as a value of that -struct with actual function pointer values. An implementation is a table mapping -the interface's functions to function pointers. For more on this, see -[the implementation model section](#implementation-model). - -In addition to function pointer members, interfaces can include any constants -that belong to a type. For example, the -[type's size](#sized-types-and-facet-types) (represented by an integer constant -member of the type) could be a member of an interface and its implementation. -There are a few cases why we would include another interface implementation as a -member: - -- [associated facets](#associated-facets) -- [type parameters](#parameterized-interfaces) -- [interface requirements](#interface-requiring-other-interfaces) - -The function expresses that the type argument is passed in -[statically](terminology.md#static-dispatch-witness-table), basically generating -a separate function body for every different type passed in, by using the -"generic argument" syntax `:!`, see -[the checked-generics section](#checked-generics) below. The interface contains -enough information to +That interface implementation has the definitions of the functions declared in +the interface. For example, the types `i32` and `String` would have different +implementations of the `ToString` method of the `ConvertibleToString` interface. + +In addition to function members, interfaces can include other members that +associate a constant value for any implementing type, called _associated +constants_. For example, this can allow a container interface to include the +type of iterators that are returned from and passed to various container +methods. + +The function expresses that the type argument is passed in statically, basically +generating a separate function body for every different type passed in, by using +the "compile-time parameter" syntax `:!`. By default, this defines a +[checked-generics parameter](#checked-generics) below. In this case, the +interface contains enough information to [type and definition check](terminology.md#complete-definition-checking) the function body -- you can only call functions defined in the interface in the -function body. Contrast this with making the type a template argument, where you -could just use `type` instead of an interface and it will work as long as the -function is only called with types that allow the definition of the function to -compile. The interface bound has other benefits: +function body. + +Alternatively, the `template` keyword can be included in the signature to make +the type a template parameter. In this case, you could just use `type` instead +of an interface and it will work as long as the function is only called with +types that allow the definition of the function to compile. + +The interface bound has other benefits: - allows the compiler to deliver clearer error messages, - documents expectations, and @@ -224,6 +217,12 @@ type, the user will have to explicitly cast to that type in order to select those alternate implementations. For more on this, see [the adapting type section](#adapting-types) below. +We originally considered following Swift and using a witness table +implementation strategy for checked generics, but ultimately decided to only use +that for the dynamic-dispatch case. This is because of the limitations of that +strategy prevent some features that we considered important, as described in +[the witness-table appendix](appendix-witness.md). + ## Interfaces An [interface](terminology.md#interface), defines an API that a given type can @@ -800,62 +799,6 @@ But, in contrast to how `DoubleThreeTimes` works, since `Vector` is implemented without `extend` the return value in this case does not directly have a `Scale` method. -### Implementation model - -A possible model for generating code for a generic function is to use a -[witness table](terminology.md#witness-tables) to represent how a type -implements an interface: - -- [Interfaces](#interfaces) are types of witness tables. -- An [impl](#implementing-interfaces) is a witness table value. - -Type checking is done with just the interface. The impl is used during code -generation time, possibly using -[monomorphization](https://en.wikipedia.org/wiki/Monomorphization) to have a -separate instantiation of the function for each combination of the generic -argument values. The compiler is free to use other implementation strategies, -such as passing the witness table for any needed implementations, if that can be -predicted. - -For the example above, [the Vector interface](#interfaces) could be thought of -defining a witness table type like: - -``` -class Vector { - // `Self` is the representation type, which is only - // known at compile time. - var Self:! type; - // `fnty` is **placeholder** syntax for a "function type", - // so `Add` is a function that takes two `Self` parameters - // and returns a value of type `Self`. - var Add: fnty(a: Self, b: Self) -> Self; - var Scale: fnty(a: Self, v: f64) -> Self; -} -``` - -The [impl of Vector for Point_Inline](#inline-impl) would be a value of this -type: - -``` -var VectorForPoint_Inline: Vector = { - .Self = Point_Inline, - // `lambda` is **placeholder** syntax for defining a - // function value. - .Add = lambda(a: Point_Inline, b: Point_Inline) -> Point_Inline { - return {.x = a.x + b.x, .y = a.y + b.y}; - }, - .Scale = lambda(a: Point_Inline, v: f64) -> Point_Inline { - return {.x = a.x * v, .y = a.y * v}; - }, -}; -``` - -Since generic arguments (where the parameter is declared using `:!`) are passed -at compile time, so the actual value of `VectorForPoint_Inline` can be used to -generate the code for functions using that impl. This is the -[static-dispatch witness table](terminology.md#static-dispatch-witness-table) -approach. - ## Interfaces recap Interfaces have a name and a definition. @@ -2278,41 +2221,6 @@ For context, see and [Swift](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID189) support these, but call them "associated types." -### Implementation model - -The associated facet can be modeled by a witness table field in the interface's -witness table. - -``` -interface Iterator { - fn Advance[addr self: Self*](); -} - -interface Container { - let IteratorType:! Iterator; - fn Begin[addr self: Self*]() -> IteratorType; -} -``` - -is represented by: - -``` -class Iterator(Self:! type) { - var Advance: fnty(this: Self*); - ... -} -class Container(Self:! type) { - // Representation type for the iterator. - let IteratorType:! type; - // Witness that IteratorType implements Iterator. - var iterator_impl: Iterator(IteratorType)*; - - // Method - var Begin: fnty (this: Self*) -> IteratorType; - ... -} -``` - ## Parameterized interfaces Associated constants don't change the fact that a type can only implement an @@ -3682,13 +3590,6 @@ with the size? So you could say `T.ByteSize` in the above example to get a generic int value with the size of `T`. Similarly you might say `T.ByteStride` to get the number of bytes used for each element of an array of `T`. -#### Implementation model - -This requires a special integer field be included in the witness table type to -hold the size of the type. This field will only be known generically, so if its -value is used for type checking, we need some way of evaluating those type tests -symbolically. - ### `TypeId` There are some capabilities every type can provide. For example, every type diff --git a/docs/design/generics/terminology.md b/docs/design/generics/terminology.md index 198b096fa47b6..b612ddf7f8ab0 100644 --- a/docs/design/generics/terminology.md +++ b/docs/design/generics/terminology.md @@ -45,9 +45,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Type erasure](#type-erasure) - [Archetype](#archetype) - [Extending an interface](#extending-an-interface) -- [Witness tables](#witness-tables) - - [Dynamic-dispatch witness table](#dynamic-dispatch-witness-table) - - [Static-dispatch witness table](#static-dispatch-witness-table) +- [Dynamic-dispatch witness table](#dynamic-dispatch-witness-table) - [Instantiation](#instantiation) - [Specialization](#specialization) - [Template specialization](#template-specialization) @@ -132,7 +130,7 @@ Expected difference between checked and template parameters: - @@ -648,42 +646,30 @@ of another interface, plus some additional API. Types implementing the extended interface should automatically be considered to have implemented the narrower interface. -## Witness tables - -[Witness tables](https://forums.swift.org/t/where-does-the-term-witness-table-come-from/54334/4) -are an implementation strategy where values passed to a generic type parameter -are compiled into a table of required functionality. That table is then filled -in for a given passed-in type with references to the implementation on the -original type. The generic is implemented using calls into entries in the -witness table, which turn into calls to the original type. This doesn't -necessarily imply a runtime indirection: it may be a purely compile-time -separation of concerns. However, it insists on a full abstraction boundary -between the generic user of a type and the concrete implementation. - -A simple way to imagine a witness table is as a struct of function pointers, one -per method in the interface. However, in practice, it's more complex because it -must model things like associated facets and interfaces. - -Witness tables are called "dictionary passing" in Haskell. Outside of generics, -a [vtable](https://en.wikipedia.org/wiki/Virtual_method_table) is a witness -table that witnesses that a class is a descendant of an abstract base class, and -is passed as part of the object instead of separately. - -### Dynamic-dispatch witness table - -For dynamic-dispatch witness tables, actual function pointers are formed and -used as a dynamic, runtime indirection. As a result, the generic code **will -not** be duplicated for different witness tables. - -### Static-dispatch witness table - -For static-dispatch witness tables, the implementation is required to collapse -the table indirections at compile time. As a result, the generic code **will** -be duplicated for different witness tables. - -Static-dispatch may be implemented as a performance optimization for -dynamic-dispatch that increases generated code size. The final compiled output -may not retain the witness table. +## Dynamic-dispatch witness table + +Dynamic-dispatch +[witness tables](https://forums.swift.org/t/where-does-the-term-witness-table-come-from/54334/4) +are an implementation strategy that uses a table accessed at runtime to allow +behavior of a function to vary. This allows a function to work with any type +implementing a facet type (such as an interface). For example, the witness table +might contain pointers to the implementations of the functions of the interface. +This can be done to reduce the size of generated code, at the expense of +additional indirection at runtime. + +It can also allow a function to dynamically dispatch when the runtime type of a +value is not known. This is the implementation strategy for +[boxed protocol types in Swift](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/opaquetypes/#Boxed-Protocol-Types) +and +[trait objects in Rust](https://doc.rust-lang.org/book/ch17-02-trait-objects.html). +Note that this often comes with limitations, since for example it is much more +difficult to support when the associated types of the interface are not known. + +Typically a reference to the witness table will be passed separately from the +object, unlike a +[virtual method table](https://en.wikipedia.org/wiki/Virtual_method_table), +which otherwise is very similar to a witness table, "witnessing" the specific +descendant of a base class. ## Instantiation @@ -693,7 +679,7 @@ replaces the template components with the concrete type and its implementation operations. It allows duck typing and lazy binding. Instantiation implies template code **will** be duplicated. -Unlike [static-dispatch witness tables](#static-dispatch-witness-table) and +Unlike static-dispatch witness tables (as in Swift) and [monomorphization (as in Rust)](https://doc.rust-lang.org/book/ch10-01-syntax.html#performance-of-code-using-generics), this is done **before** type checking completes. Only when the template is used with a concrete type is the template fully type checked, and it type checks @@ -725,13 +711,6 @@ This restriction is needed to preserve the ability to perform type checking of generic definitions that reference a type that can be specialized, without statically knowing which specialization will be used. -While there is nothing fundamentally incompatible about specialization with -checked generics, even when implemented using witness tables, the result may be -surprising because the selection of the specialized generic happens outside of -the witness-table-based indirection between the generic code and the concrete -implementation. Provided all selection relies exclusively on interfaces, this -still satisfies the fundamental constraints of generics. - ## Conditional conformance Conditional conformance is when you have a parameterized type that has one API From 40da45e3b3cae1a34891f048742cb00b07e72fbc Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 30 Aug 2023 16:01:20 +0000 Subject: [PATCH 33/83] Finish up witness table appendix --- docs/design/generics/appendix-witness.md | 138 +++++++++++++++-------- 1 file changed, 90 insertions(+), 48 deletions(-) diff --git a/docs/design/generics/appendix-witness.md b/docs/design/generics/appendix-witness.md index e30adf11f3780..b5e420891da5b 100644 --- a/docs/design/generics/appendix-witness.md +++ b/docs/design/generics/appendix-witness.md @@ -19,11 +19,10 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Associated constants](#associated-constants) - [Blanket implementations](#blanket-implementations) - [Specialization](#specialization) -- [Examples of how you might implement Carbon's generics with witness tables](#examples-of-how-you-might-implement-carbons-generics-with-witness-tables) - - [FIXME: old text from details.md "Overview" section](#fixme-old-text-from-detailsmd-overview-section) - - [FIXME: Checked-generics implementation model](#fixme-checked-generics-implementation-model) - - [FIXME: Associated facets implementation model](#fixme-associated-facets-implementation-model) - - [FIXME: Sized type implementation model](#fixme-sized-type-implementation-model) +- [Implementing some Carbon generic features with witness tables](#implementing-some-carbon-generic-features-with-witness-tables) + - [Overview](#overview-1) + - [Example](#example) + - [Associated facets example](#associated-facets-example) @@ -37,8 +36,8 @@ parameters. They have some nice properties: - They can support separate compilation even with compile-time dispatch. However, it can be a challenge to implement some features of a generic system -with witness tables. This leads to either limitations on the generic system or -additional runtime overhead. +with witness tables. This leads to limitations on the generic system, additional +runtime overhead, or both. Swift uses witness tables for both static and dynamic dispatch, accepting both limitations and overhead. Carbon and Rust only use witness tables for dynamic @@ -91,36 +90,64 @@ may not retain the witness table. ### Associated constants -FIXME +An interface with associated constants can use that to allow the signature of a +function to vary. A similar issue arises with argument and return values +involving `Self`. This adds to the cost of calling such functions, for example +if they are not passed by pointer, then the generated code must support +arguments and return values with a size only known at runtime. + +For this reason, Rust's dynamic trait dispatch system, trait objects, only works +with traits that are +["object safe,"](https://doc.rust-lang.org/reference/items/traits.html#object-safety) +which includes a requirement that +[all the associated types have specified values](https://github.com/rust-lang/rfcs/blob/master/text/0195-associated-items.md#trait-objects). +This reduces the expressivity of Rust traits to the subset that could be +supported by a C++ abstract base class. + +Swift instead supports types with size only known at runtime for its +[ABI stability and dynamic linking features](https://faultlore.com/blah/swift-abi/#what-is-abi-stability-and-dynamic-linking), +and can use that to +[support more generic features with dynamic dispatch](https://faultlore.com/blah/swift-abi/#polymorphic-generics). +This comes with runtime overhead. ### Blanket implementations -FIXME +[Blanket implementations](details.md#blanket-impl-declarations) allow you define +an implementation of interface `Y` for any type implementing interface `X`. This +allows a function to use the functionality of `Y` while only having a +requirement that `X` be implemented. This creates the problem of how to go from +a witness table for `X` to a witness table for `Y`. -### Specialization +Rust supports blanket implementations using monomorphization, but this only +works with static dispatch. Swift does not support blanket implementations. This +is possibly a result of the limitations of using witness tables to implement +generics. -FIXME +### Specialization -## Examples of how you might implement Carbon's generics with witness tables +Specialization compounds the difficulty of the previous two issues. -### FIXME: old text from details.md "Overview" section +An interface with an associated type might be implemented using witness tables +by including a reference to the associated type's witness table in the witness +table for the interface. This doesn't give you a witness table for parameterized +types using the associated type as an argument. Synthesizing those witness +tables is particularly tricky if the implementation is different for specific +types due to specialization. -We can think of the interface as defining a struct type whose members are -function pointers, and an implementation of an interface as a value of that -struct with actual function pointer values. An implementation is a table mapping -the interface's functions to function pointers. For more on this, see -[the implementation model section](#fixme-checked-generics-implementation-model). +Similarly, a blanket implementation can guarantee that some implementation of an +interface exists. Specialization means that actual implementation of that +interface for specific types is not the one given by the blanket implementation. +Furthermore, that specialized implementation may be in an unrelated library. +They may be found anywhere in the program, not necessarily in the dependencies +of the code that needs to use a particular witness table. -For example, the [type's size](#fixme-sized-type-implementation-model) -(represented by an integer constant member of the type) could be a member of an -interface and its implementation. There are a few cases why we would include -another interface implementation as a member: +As a result, specialization is not supported by Swift, which uses witness +tables. Specialization is being considered for Rust, and is compatible with its +monomorphization model used for static dispatch. -- [associated facets](details.md#associated-facets) -- [type parameters](details.md#parameterized-interfaces) -- [interface requirements](details.md#interface-requiring-other-interfaces) +## Implementing some Carbon generic features with witness tables -### FIXME: Checked-generics implementation model +### Overview A possible model for generating code for a generic function is to use a [witness table](#witness-tables) to represent how a type implements an @@ -129,23 +156,46 @@ interface: - [Interfaces](details.md#interfaces) are types of witness tables. - An [impl](details.md#implementing-interfaces) is a witness table value. -Type checking is done with just the interface. The impl is used during code -generation time, possibly using -[monomorphization](https://en.wikipedia.org/wiki/Monomorphization) to have a -separate instantiation of the function for each combination of the generic -argument values. The compiler is free to use other implementation strategies, -such as passing the witness table for any needed implementations, if that can be -predicted. +We can think of the interface as defining a struct type with a field for every +interface member. An implementation of that interface for a type is a value of +that struct type, which we call a witness or witness table. For example, the +function and method members of an interface correspond to function pointer +fields. An implementation will have function pointer values pointing to the +functions defining the implementation of that interface for a given type. This +is like a [vtable](https://en.wikipedia.org/wiki/Virtual_method_table), except +stored separately from the object. + +A witness might +[have references to other witness tables](#associated-facets-example), in order +to support these interface features and members: + +- [associated facets](details.md#associated-facets) +- [type parameters](details.md#parameterized-interfaces) +- [interface requirements](details.md#interface-requiring-other-interfaces) + +It also could contain constants, to store the values of +[associated constants](details.md#associated-constants), or the type's size. + +### Example -For the example above, [the Vector interface](details.md#interfaces) could be -thought of defining a witness table type like: +For example, given this `Vector` interface: + +```carbon +interface Vector { + fn Add[self: Self](b: Self) -> Self; + fn Scale[self: Self](v: f64) -> Self; +} +``` + +from [the generic details design](details.md#interfaces) could be thought of +defining a witness table type like: ``` class Vector { // `Self` is the representation type, which is only // known at compile time. var Self:! type; - // `fnty` is **placeholder** syntax for a "function type", + // `fnty` is placeholder syntax for a "function type", // so `Add` is a function that takes two `Self` parameters // and returns a value of type `Self`. var Add: fnty(a: Self, b: Self) -> Self; @@ -159,7 +209,7 @@ of this type: ``` var VectorForPoint_Inline: Vector = { .Self = Point_Inline, - // `lambda` is **placeholder** syntax for defining a + // `lambda` is placeholder syntax for defining a // function value. .Add = lambda(a: Point_Inline, b: Point_Inline) -> Point_Inline { return {.x = a.x + b.x, .y = a.y + b.y}; @@ -171,11 +221,10 @@ var VectorForPoint_Inline: Vector = { ``` Since generic arguments (where the parameter is declared using `:!`) are passed -at compile time, so the actual value of `VectorForPoint_Inline` can be used to -generate the code for functions using that impl. This is the -[static-dispatch witness table](#static-dispatch-witness-table) approach. +at compile time, the actual value of `VectorForPoint_Inline` can be used to +generate the code for functions using that impl. -### FIXME: Associated facets implementation model +### Associated facets example The associated facet can be modeled by a witness table field in the interface's witness table. @@ -209,10 +258,3 @@ class Container(Self:! type) { ... } ``` - -#### FIXME: Sized type implementation model - -This requires a special integer field be included in the witness table type to -hold the size of the type. This field will only be known generically, so if its -value is used for type checking, we need some way of evaluating those type tests -symbolically. From c9a05001207b1139a799dc73b5c9bc62e9928a14 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 31 Aug 2023 03:30:29 +0000 Subject: [PATCH 34/83] Iterate on README --- docs/design/README.md | 144 ++++++++++++++++++++++++++++-------------- 1 file changed, 95 insertions(+), 49 deletions(-) diff --git a/docs/design/README.md b/docs/design/README.md index ac4dda48d5fcb..5f628f34581b0 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -91,6 +91,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Checked and template parameters](#checked-and-template-parameters) - [Interfaces and implementations](#interfaces-and-implementations) - [Combining constraints](#combining-constraints) + - [Template name lookup](#template-name-lookup) - [Associated constants](#associated-constants) - [Generic entities](#generic-entities) - [Generic Classes](#generic-classes) @@ -1041,7 +1042,7 @@ implementation chooses. A [compile-time binding](#checked-and-template-parameters) uses `:!` instead of a colon (`:`) and can only match [compile-time constants](#expression-phases), not run-time values. A `template` keyword before the binding selects a template -constant binding instead of a symbolic constant binding. +binding instead of a symbolic binding. The keyword `auto` may be used in place of the type in a binding pattern, as long as the type can be deduced from the type of a value in the same @@ -2569,8 +2570,9 @@ The general principle of Carbon name lookup is that we look up names in all relevant scopes, and report an error if the name is found to refer to more than one different entity. So Carbon requires disambiguation by adding qualifiers instead of doing any -[shadowing](https://en.wikipedia.org/wiki/Variable_shadowing) of names. For an -example, see [the "package scope" section](#package-scope). +[shadowing](https://en.wikipedia.org/wiki/Variable_shadowing) of names. +[Member name lookup](expressions/member_access.md) follows a similar philosophy. +For an example, see [the "package scope" section](#package-scope). Unqualified name lookup walks the semantically-enclosing scopes, not only the lexically-enclosing ones. So when a lookup is performed within @@ -2591,21 +2593,6 @@ fn C.G() { } ``` -**FIXME: "symbolic facet binding" is the technically correct combination of -terms, but probably needs more context here.** - -[Member name lookup](expressions/member_access.md) follows a similar philosophy. -If a [symbolic facet binding](#checked-and-template-parameters) is known to -implement multiple interfaces due to a constraint using -[`&`](#combining-constraints) or -[`where` clauses](generics/details.md#where-constraints), member name lookup -into that facet will look in all of the interfaces. If it is found in multiple, -the name must be disambiguated by qualifying using compound member access -([1](expressions/member_access.md), -[2](generics/details.md#qualified-member-names-and-compound-member-access)). A -[template-generic type parameter](#checked-and-template-parameters) performs -look up into the caller's type in addition to the constraint. - Carbon also rejects cases that would be invalid if all declarations in the file, including ones appearing later, were visible everywhere, not only after their point of appearance: @@ -2661,10 +2648,10 @@ like `i32` and `bool` refer to types defined within this package, based on the ## Generics Generics allow Carbon constructs like [functions](#functions) and -[classes](#classes) to be written with compile-time parameters and apply -generically to different types using those parameters. For example, this `Min` -function has a type\* parameter `T` that can be any type that implements the -`Ordered` interface. +[classes](#classes) to be written with compile-time parameters to generalize +across different values of those parameters. For example, this `Min` function +has a type\* parameter `T` that can be any type that implements the `Ordered` +interface. ```carbon fn Min[T:! Ordered](x: T, y: T) -> T { @@ -2705,9 +2692,9 @@ not itself a type. ### Checked and template parameters -The `:!` marks it as a compile-time binding, and so `T` is a compile-time -parameter. Compile-time parameters may either be _checked_ or _template_, and -default to checked. +The `:!` marks it as a compile-time binding pattern, and so `T` is a +compile-time parameter. Compile-time parameters may either be _checked_ or +_template_, and default to checked. "Checked" here means that the body of `Min` is type checked when the function is defined, independent of the specific values `T` is instantiated with, and name @@ -2736,7 +2723,7 @@ A template parameter can still use a constraint. The `Min` example could have been declared as: ```carbon -fn Min[template T:! Ordered](x: T, y: T) -> T { +fn TemplatedMin[template T:! Ordered](x: T, y: T) -> T { return if x <= y then x else y; } ``` @@ -2751,7 +2738,7 @@ controlled by the SFINAE rule of C++ ([1](https://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error), [2](https://en.cppreference.com/w/cpp/language/sfinae)) but by explicit `if` clauses evaluated at compile-time. The `if` clause is at the end of the -declaration, and the condition can only use constant values known at +declaration, and the condition can only use compile-time constants known at type-checking time, including `template` parameters. ```carbon @@ -2762,16 +2749,6 @@ class Array(template T:! type, template N:! i64) > **TODO:** The design for template constraints is still under development. The > `if` clause approach here is provisional. -Member lookup into a template parameter is done in the actual value provided by -the caller, _in addition_ to any constraints. This means member name lookup and -type checking for anything [dependent](generics/terminology.md#dependent-names) -on the template parameter can't be completed until the template is instantiated -with a specific concrete type. When the constraint is just `type`, this gives -semantics similar to C++ templates. Constraints can then be added incrementally, -with the compiler verifying that the semantics stay the same. Once all -constraints have been added, removing the word `template` to switch to a checked -parameter is safe. - The [expression phase](#expression-phases) of a checked parameter is a symbolic constant whereas the expression phase of a template parameter is template constant. A binding pattern using `:!` is a _compile-time binding pattern_; more @@ -2789,8 +2766,6 @@ rigor of checked generics are problematic. > [#553: Generics details part 1](https://github.com/carbon-language/carbon-lang/pull/553) > - Question-for-leads issue > [#949: Constrained template name lookup](https://github.com/carbon-language/carbon-lang/issues/949) -> - Proposal -> [#989: Member access expressions](https://github.com/carbon-language/carbon-lang/pull/989) ### Interfaces and implementations @@ -2825,9 +2800,16 @@ that they do. Simply having a `Print` function with the right signature is not sufficient. ```carbon +// Class `Text` does not implement the `Printable` interface. +class Text { + fn Print[self: Self](); +} + class Circle { var radius: f32; + // This `impl` declaration establishes that `Circle` implements + // `Printable`. impl as Printable { fn Print[self: Self]() { Carbon.Print("Circle with radius: {0}", self.radius); @@ -2839,11 +2821,27 @@ class Circle { In this case, `Print` is not a direct member of `Circle`, but: - `Circle` may be passed to functions expecting a type that implements - `Printable`, and -- the members of `Printable` such as `Print` may be called using compound + `Printable`. + + ```carbon + fn GenericPrint[T:! Printable](x: T) { + // Look up into `T` delegates to `Printable`, so this + // finds `Printable.Print`: + x.Print(); + } + ``` + +- The members of `Printable` such as `Print` may be called using compound member access syntax ([1](expressions/member_access.md), [2](generics/details.md#qualified-member-names-and-compound-member-access)) - to qualify the name of the member, as in `c.(Printable.Print)()`. + to qualify the name of the member, as in: + + ```carbon + fn CirclePrint(c: Circle) { + // Succeeds, even though `c.Print()` would not. + c.(Printable.Print)(); + } + ``` To include the members of the interface as direct members of the type, use the [`extend`](generics/details.md#extend-impl) keyword, as in @@ -2870,6 +2868,8 @@ by replacing the definition scope in curly braces (`{`...`}`) with a semicolon. > - Proposal > [#624: Coherence: terminology, rationale, alternatives considered](https://github.com/carbon-language/carbon-lang/pull/624) > - Proposal +> [#989: Member access expressions](https://github.com/carbon-language/carbon-lang/pull/989) +> - Proposal > [#990: Generics details 8: interface default and final members](https://github.com/carbon-language/carbon-lang/pull/990) > - Proposal > [#1084: Generics details 9: forward declarations](https://github.com/carbon-language/carbon-lang/pull/1084) @@ -2915,14 +2915,60 @@ fn DrawTies[T:! Renderable & GameResult](x: T) { > - Proposal > [#553: Generics details part 1](https://github.com/carbon-language/carbon-lang/pull/553) +### Template name lookup + +Member lookup into a template parameter is done in the actual value provided by +the caller, _in addition_ to any constraints. This means member name lookup and +type checking for anything [dependent](generics/terminology.md#dependent-names) +on the template parameter can't be completed until the template is instantiated +with a specific concrete type. When the constraint is just `type`, this gives +semantics similar to C++ templates. + +```carbon +class Game { + fn Draw[self: Self]() -> bool; + impl as Renderable { + fn Draw[self: Self](); + } +} + +fn TemplateDraw[template T:! type](x: T) { + // Calls `Game.Draw` when `T` is `Game`: + x.Draw(); +} + +fn ConstrainedTemplateDraw[template T:! Renderable](x: T) { + // ❌ Error when `T` is `Game`: Finds both `T.Draw` and + // `Renderable.Draw`, and they are different. + x.Draw(); +} + +fn CheckedGenericDraw[T:! Renderable](x: T) { + // Always calls `Renderable.Draw`, even when `T` is `Game`: + x.Draw(); +} +``` + +This allows a safe transition from template to checked generics. Constraints can +be added incrementally, with the compiler verifying that the semantics stay the +same. If adding the constraint would change which function gets called, an error +is triggered, as in `ConstrainedTemplateDraw` from the example. Once all +constraints have been added, removing the word `template` to switch to a checked +parameter is safe. + +> References: +> +> - Proposal +> [#989: Member access expressions](https://github.com/carbon-language/carbon-lang/pull/989) + ### Associated constants An associated constant is a member of an interface whose value is determined by the implementation of that interface for a specific type. These values are set to compile-time values in implementations, and so use the -[`:!` generic syntax](#checked-and-template-parameters) inside a -[`let` declaration](#constant-let-declarations) without an initializer. This -allows types in the signatures of functions in the interface to vary. For +[`:!` compile-time binding pattern syntax](#checked-and-template-parameters) +inside a [`let` declaration](#constant-let-declarations) without an initializer. +This allows types in the signatures of functions in the interface to vary. For example, an interface describing a [stack]() might use an associated constant to represent the type of elements stored in the stack. @@ -2932,7 +2978,7 @@ interface StackInterface { let ElementType:! Movable; fn Push[addr self: Self*](value: ElementType); fn Pop[addr self: Self*]() -> ElementType; - fn IsEmpty[addr self: Self*]() -> bool; + fn IsEmpty[self: Self]() -> bool; } ``` @@ -3004,7 +3050,7 @@ fn PeekTopOfStack[T:! type](s: Stack(T)*) -> T { return top; } -// `int_stack` has type `Stack(i32)`, so `T` is deduced to be `i32`. +// `int_stack` has type `Stack(i32)`, so `T` is deduced to be `i32`: PeekTopOfStack(&int_stack); ``` @@ -3072,7 +3118,7 @@ pick which definition is selected. These rules ensure: same implementation is always selected for a given query. - Libraries will work together as long as they pass their separate checks. - A generic function can assume that some impl will be successfully selected - if it can see an impl that applies, even though another more specific impl + if it can see an impl that applies, even though another more-specific impl may be selected. Implementations may be marked @@ -3241,7 +3287,7 @@ reference expression. Operators that can take multiple arguments, such as function calling operator `f(4)`, have a [variadic](generics/details.md#variadic-arguments) parameter -list. +list. **TODO: Variadics are still provisional.** Whether and how a value supports other operations, such as being copied, swapped, or set into an [unformed state](#unformed-state), is also determined by From 98c6a5cb75d74fc54f9b7ad82e4dad246a40214b Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 31 Aug 2023 03:53:36 +0000 Subject: [PATCH 35/83] Update member_access --- docs/design/expressions/member_access.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 75f2831163e67..71181e16805f3 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -301,7 +301,7 @@ interface Renderable { fn Draw[self: Self](); } fn DrawChecked[T:! Renderable](c: T) { - // `Draw` resolves to `Renderable.Draw`. + // `Draw` resolves to `(T as Renderable).Draw`. c.Draw(); } @@ -378,8 +378,8 @@ interface Renderable { } fn DrawTemplate2[template T:! Renderable](c: T) { - // Member lookup finds `Renderable.Draw` and the `Draw` - // member of the actual deduced value of `T`, if any. + // Member lookup finds `(T as Renderable).Draw` and the + // `Draw` member of the actual deduced value of `T`, if any. c.Draw(); } From 026607a423285840049420d83f5a322823b89689 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 31 Aug 2023 16:06:36 +0000 Subject: [PATCH 36/83] Checkpoint progress. --- docs/design/README.md | 19 ++++++++++--------- docs/design/expressions/member_access.md | 11 +++++------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/design/README.md b/docs/design/README.md index 5f628f34581b0..6329c5a38ec79 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -3037,7 +3037,7 @@ In this example: - `Stack` is a type parameterized by a type `T`. - `T` may be used within the definition of `Stack` anywhere a normal type would be used. -- `Array(T)` instantiates generic type `Array` with its parameter set to `T`. +- `Array(T)` instantiates generic type `Array` with its argument set to `T`. - `Stack(i32)` instantiates `Stack` with `T` set to `i32`. The values of type parameters are part of a type's value, and so may be deduced @@ -3184,8 +3184,8 @@ Carbon generics have a number of other features, including: ### Generic type equality and `observe` declarations -Determining whether two types must be equal in a generic context is in general -undecidable, as +Determining whether two types must be equal in a checked-generic context is in +general undecidable, as [has been shown in Swift](https://forums.swift.org/t/swift-type-checking-is-undecidable/39024). To make compilation fast, the Carbon compiler will limit its search to a depth @@ -3618,11 +3618,12 @@ language features like checked generics in their design and implementation. Where possible, we will also try to provide implementations of Carbon's standard library container _interfaces_ for the relevant C++ container types so that they -can be directly used with generic Carbon code. This should allow generic code in -Carbon to work seamlessly with both Carbon and C++ containers without -performance loss or constraining the Carbon container implementations. In the -other direction, Carbon containers will satisfy C++ container requirements, so -templated C++ code can operate directly on Carbon containers as well. +can be directly used with checked-generic Carbon code. This should allow +checked-generic code in Carbon to work seamlessly with both Carbon and C++ +containers without performance loss or constraining the Carbon container +implementations. In the other direction, Carbon containers will satisfy C++ +container requirements, so templated C++ code can operate directly on Carbon +containers as well. ### Inheritance @@ -3686,7 +3687,7 @@ existing languages like Rust and Swift to understand what fundamental capabilities they ended up needing. The two components that stand out are: - Expanded type system that includes more semantic information. -- More pervasive use of type system abstractions (typically generics). +- More pervasive use of type system abstractions (typically checked generics). For migrating C++ code, we also need the ability to add features and migrate code to use those new features incrementally and over time. This requires diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index 71181e16805f3..d2134464a7083 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -17,7 +17,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Values](#values) - [Facet binding](#facet-binding) - [Compile-time bindings](#compile-time-bindings) - - [Lookup ambiguity](#lookup-ambiguity) + - [Lookup ambiguity](#lookup-ambiguity) - [`impl` lookup](#impl-lookup) - [`impl` lookup for simple member access](#impl-lookup-for-simple-member-access) - [`impl` lookup for compound member access](#impl-lookup-for-compound-member-access) @@ -254,10 +254,9 @@ fn PrintPointTwice() { ### Facet binding -If any of the above lookups would search for members of a -[facet binding](/docs/design/generics/terminology.md#facet-binding) `T:! C`, it -searches the facet `T as C` instead, treating the facet binding as an -[archetype](/docs/design/generics/terminology.md#archetype). +A search for members of a facet binding `T:! C` treats the facet binding as an +[archetype](/docs/design/generics/terminology.md#archetype), and finds members +of the facet `T` of facet type `C`. For example: @@ -358,7 +357,7 @@ bindings that are in scope are unknown. Unlike for a template binding, the actual value of a symbolic binding never affects the result of member resolution. -##### Lookup ambiguity +#### Lookup ambiguity Multiple lookups can be performed when resolving a member access expression with a [template binding](#compile-time-bindings). We resolve this the same way as From 7207b0bb027122728d29e3921873bc7c14d8924a Mon Sep 17 00:00:00 2001 From: Josh L Date: Sat, 2 Sep 2023 00:16:22 +0000 Subject: [PATCH 37/83] Checkpoint progress. --- docs/design/generics/appendix-witness.md | 10 + docs/design/generics/details.md | 279 ++++++++++++----------- 2 files changed, 160 insertions(+), 129 deletions(-) diff --git a/docs/design/generics/appendix-witness.md b/docs/design/generics/appendix-witness.md index b5e420891da5b..45502e583fb02 100644 --- a/docs/design/generics/appendix-witness.md +++ b/docs/design/generics/appendix-witness.md @@ -19,6 +19,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Associated constants](#associated-constants) - [Blanket implementations](#blanket-implementations) - [Specialization](#specialization) + - [Calling templated functions](#calling-templated-functions) - [Implementing some Carbon generic features with witness tables](#implementing-some-carbon-generic-features-with-witness-tables) - [Overview](#overview-1) - [Example](#example) @@ -145,6 +146,15 @@ As a result, specialization is not supported by Swift, which uses witness tables. Specialization is being considered for Rust, and is compatible with its monomorphization model used for static dispatch. +### Calling templated functions + +Carbon's planned approach to support calling a templated function from a +checked-generic function, decided in +[issue #2153](https://github.com/carbon-language/carbon-lang/issues/2153), +relies on monomorphization. Trying to rely on witness tables would result in +different semantics for calling the same function with the same types, depending +on which witness tables were available at the callsite. + ## Implementing some Carbon generic features with witness tables ### Overview diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 9f4c0b895e96a..3cf54e332994d 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -130,7 +130,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Field requirements](#field-requirements) - [Bridge for C++ customization points](#bridge-for-c-customization-points) - [Variadic arguments](#variadic-arguments) - - [Range constraints on generic integers](#range-constraints-on-generic-integers) + - [Range constraints on symbolic integers](#range-constraints-on-symbolic-integers) - [References](#references) @@ -142,7 +142,7 @@ This document goes into the details of the design of Carbon's mean generalizing some language construct with compile-time parameters. These parameters can be types, [facets](terminology.md#facet), or other values. -Imagine we want to write a function parameterized by a type argument. Maybe our +Imagine we want to write a function with a type (or facet) parameter. Maybe our function is `PrintToStdout` and let's say we want to operate on values that have a type for which we have an implementation of the `ConvertibleToString` interface. The `ConvertibleToString` interface has a `ToString` method returning @@ -166,10 +166,10 @@ the interface. For example, the types `i32` and `String` would have different implementations of the `ToString` method of the `ConvertibleToString` interface. In addition to function members, interfaces can include other members that -associate a constant value for any implementing type, called _associated -constants_. For example, this can allow a container interface to include the -type of iterators that are returned from and passed to various container -methods. +associate a [compile-time value](/docs/design/README.md#expression-phases) for +any implementing type, called _associated constants_. For example, this can +allow a container interface to include the type of iterators that are returned +from and passed to various container methods. The function expresses that the type argument is passed in statically, basically generating a separate function body for every different type passed in, by using @@ -244,9 +244,12 @@ Each declaration in the interface defines an [associated entity](terminology.md#associated-entity). In this example, `Vector` has two associated methods, `Add` and `Scale`. -An interface defines a facet type, that is a type whose values are types. The -values of an interface are any types implementing the interface, and so provide -definitions for all the functions (and other members) declared in the interface. +An interface defines a [facet type](terminology.md#facet-type), that is a type +whose values are [facets](terminology.md#facet). Every type implementing the +interface, and so has definitions for all the functions (and other members) +declared in the interface, has a corresponding facet value. So if the type +`Point` implements interface `Vector`, the facet value `Point as Vector` has +type `Vector`. ## Implementing interfaces @@ -663,19 +666,23 @@ fn AddAndScaleGeneric[T:! Vector](a: T, b: T, s: f64) -> T { var v: Point_Extend = AddAndScaleGeneric(a, w, 2.5); ``` -Here `T` is a type whose type is `Vector`. The `:!` syntax means that `T` is a -_[constant binding](terminology.md#bindings)_, here specifically a _symbolic -constant binding_. Since this binding pattern is in a function declaration, it -marks a _checked -[generic parameter](terminology.md#generic-means-compile-time-parameterized)_. -That means it must be known to the caller, but we will only use the information -present in the signature of the function to type check the body of -`AddAndScaleGeneric`'s definition. In this case, we know that any value of type -`T` implements the `Vector` interface and so has an `Add` and a `Scale` method. +Here `T` is a facet whose type is `Vector`. The `:!` syntax means that `T` is a +_[compile-time binding](terminology.md#bindings)_. Here specifically it declares +a _symbolic binding_ since it did not use the `template` keyword to mark it as a +_template binding_. **References:** The `:!` syntax was accepted in [proposal #676](https://github.com/carbon-language/carbon-lang/pull/676). +Since this symbolic binding pattern is in a function declaration, it marks a +_[checked](terminology.md#checked-versus-template-parameters) +[generic parameter](terminology.md#generic-means-compile-time-parameterized)_. +That means its value must be known to the caller at compile-time, but we will +only use the information present in the signature of the function to type check +the body of `AddAndScaleGeneric`'s definition. In this case, we know that any +value of facet `T` implements the `Vector` interface and so has an `Add` and a +`Scale` method. + Names are looked up in the body of `AddAndScaleGeneric` for values of type `T` in `Vector`. This means that `AddAndScaleGeneric` is interpreted as equivalent to adding a `Vector` @@ -725,7 +732,9 @@ like `Point_NoExtend`: ``` // AddAndScaleGeneric with T = Point_NoExtend -fn AddAndScaleForPoint_NoExtend(a: Point_NoExtend, b: Point_NoExtend, s: Double) -> Point_NoExtend { +fn AddAndScaleForPoint_NoExtend( + a: Point_NoExtend, b: Point_NoExtend, s: Double) + -> Point_NoExtend { // ✅ This works even though `a.Add(b).Scale(s)` wouldn't. return a.(Vector.Add)(b).(Vector.Scale)(s); } @@ -746,11 +755,12 @@ In this example, `AddAndScaleGeneric` can be substituted for `AddAndScaleForPoint_Extend` and `AddAndScaleForPoint_NoExtend` without affecting the return types. This requires the return value to be converted to the type that the caller expects instead of the erased type used inside the -generic function. +checked-generic function. -A generic caller of a generic function performs the same substitution process to -determine the return type, but the result may be generic. In this example of -calling a generic from another generic, +A checked-generic caller of a checked-generic function performs the same +substitution process to determine the return type, but the result may be a +symbolic value. In this example of calling a checked generic from another +checked generic, ``` fn DoubleThreeTimes[U:! Vector](a: U) -> U { @@ -841,10 +851,24 @@ An interface is one particularly simple example of a facet type. For example, interface `Vector`. Its set of names consists of `Add` and `Scale` which are aliases for the corresponding qualified names inside `Vector` as a namespace. -The requirements determine which types are values of a given facet type. The set -of names in a facet type determines the API of a generic type value and define -the result of [member access](/docs/design/expressions/member_access.md) into -the facet type. +The requirements determine which types may be implicitly converted to a given +facet type. The result of this conversion is a [facet](terminology.md#facet). +For example, `Point_Inline` from [the "Inline `impl`" section](#inline-impl) +implements `Vector`, so `Point_Inline` may be implicitly converted to `Vector` +as considered as a type. The result is `Point_Inline as Vector`, which has the +members of `Vector` instead of the members of `Point_Inline`. If the facet +`Point_Inline as Vector` is used in a type position, it is implicitly converted +back to type `type`, see +["values usable as types" in the design overview](/docs/design/README.md#values-usable-as-types). +This recovers the original type for the facet, so +`(Point_Inline as Vector) as type` is `Point_Inline` again. + +However, when a facet type like `Vector` is used as the binding type of a +symbolic binding, as in `T:! Vector`, the symbolic facet binding `T` is +disassociated with whatever facet value `T` is eventually bound to. Instead, `T` +is treated as an [archetype](terminology.md#archetype), with the members and +[member access](/docs/design/expressions/member_access.md) determined by the +names of the facet type. This general structure of facet types holds not just for interfaces, but others described in the rest of this document. @@ -852,11 +876,11 @@ described in the rest of this document. ## Named constraints If the interfaces discussed above are the building blocks for facet types, -[generic named constraints](terminology.md#named-constraints) describe how they -may be composed together. Unlike interfaces which are nominal, the name of a -named constraint is not a part of its value. Two different named constraints -with the same definition are equivalent even if they have different names. This -is because types don't have to explicitly specify which named constraints they +[named constraints](terminology.md#named-constraints) describe how they may be +composed together. Unlike interfaces which are nominal, the name of a named +constraint is not a part of its value. Two different named constraints with the +same definition are equivalent even if they have different names. This is +because types don't have to explicitly specify which named constraints they implement, types automatically implement any named constraints they can satisfy. A named constraint definition can contain interface requirements using `impl` @@ -935,15 +959,16 @@ type." This is consistent with the [use of `auto` in the C++20 Abbreviated function template feature](https://en.cppreference.com/w/cpp/language/function_template#Abbreviated_function_template). In general, the declarations in `constraint` definition match a subset of the -declarations in an `interface`. Named constraints used with generics, as opposed -to templates, should only include required interfaces and aliases to named -members of those interfaces. +declarations in an `interface`. Named constraints used with checked generics, as +opposed to templates, should only include required interfaces and aliases to +named members of those interfaces. To declare a named constraint that includes other declarations for use with template parameters, use the `template` keyword before `constraint`. Method, associated constant, and associated function requirements may only be declared -inside a `template constraint`. Note that a generic constraint ignores the names -of members defined for a type, but a template constraint can depend on them. +inside a `template constraint`. Note that a checked-generic constraint ignores +the names of members defined for a type, but a template constraint can depend on +them. There is an analogy between declarations used in a `constraint` and in an `interface` definition. If an `interface` `I` has (non-`alias`) declarations @@ -1001,10 +1026,10 @@ design document, once it exists. There is a subtyping relationship between facet types that allows calls of one generic function from another as long as it has a subset of the requirements. -Given a generic type variable `T` with facet type `I1`, it satisfies a facet +Given a symbolic facet binding `T` with facet type `I1`, it satisfies a facet type `I2` as long as the requirements of `I1` are a superset of the requirements -of `I2`. This means a value `x` of type `T` may be passed to functions requiring -types to satisfy `I2`, as in this example: +of `I2`. This means a value `x: T` may be passed to functions requiring types to +satisfy `I2`, as in this example: ``` interface Printable { fn Print[self: Self](); } @@ -1687,9 +1712,9 @@ var thriller_count: Optional(i32) = play_count.Find(Song("Thriller")); ``` -Since the `Find` function is generic, it can only use the capabilities that -`HashMap` requires of `KeyT` and `ValueT`. This allows us to evaluate when we -can convert between two different arguments to a parameterized type. Consider +Since the `Find` function is a checked generic, it can only use the capabilities +that `HashMap` requires of `KeyT` and `ValueT`. This allows us to evaluate when +we can convert between two different arguments to a parameterized type. Consider two adapters of `Song` that implement `Hashable`: ``` @@ -1778,7 +1803,7 @@ class SongRenderToPrintDriver { ### Use case: Using independent libraries together Imagine we have two packages that are developed independently. Package -`CompareLib` defines an interface `CompareLib.Comparable` and a generic +`CompareLib` defines an interface `CompareLib.Comparable` and a checked-generic algorithm `CompareLib.Sort` that operates on types that implement `CompareLib.Comparable`. Package `SongLib` defines a type `SongLib.Song`. Neither has a dependency on the other, so neither package defines an @@ -1978,10 +2003,10 @@ and an adapter to address this use case. In addition to associated methods, we allow other kinds of [associated entities](terminology.md#associated-entity). For consistency, we use -the same syntax to describe a constant in an interface as in a type without -assigning a value. As constants, they are declared using the `let` introducer. -For example, a fixed-dimensional point type could have the dimension as an -associated constant. +the same syntax to describe a compile-time constant in an interface as in a type +without assigning a value. As constants, they are declared using the `let` +introducer. For example, a fixed-dimensional point type could have the dimension +as an associated constant. ``` interface NSpacePoint { @@ -2161,8 +2186,9 @@ In particular, it was deemed that [Swift's approach of inferring an associated facet from method signatures in the impl](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID190) was unneeded complexity. -The definition of the `StackAssociatedFacet` is sufficient for writing a generic -function that operates on anything implementing that interface, for example: +The definition of the `StackAssociatedFacet` is sufficient for writing a +checked-generic function that operates on anything implementing that interface, +for example: ``` fn PeekAtTopOfStack[StackType:! StackAssociatedFacet](s: StackType*) @@ -2173,14 +2199,14 @@ fn PeekAtTopOfStack[StackType:! StackAssociatedFacet](s: StackType*) } ``` -Inside the generic function `PeekAtTopOfStack`, the `ElementType` associated -facet member of `StackType` is erased. This means `StackType.ElementType` has -the API dictated by the declaration of `ElementType` in the interface -`StackAssociatedFacet`. +Inside the checked-generic function `PeekAtTopOfStack`, the `ElementType` +associated facet member of `StackType` is erased. This means +`StackType.ElementType` has the API dictated by the declaration of `ElementType` +in the interface `StackAssociatedFacet`. -Outside the generic, associated facets have the concrete facet values determined -by impl lookup, rather than the erased version of that type used inside a -generic. +Outside the checked-generic, associated facets have the concrete facet values +determined by impl lookup, rather than the erased version of that type used +inside a checked-generic. ``` var my_array: DynamicArray(i32) = (1, 2, 3); @@ -2330,17 +2356,17 @@ class Complex { } ``` -All interface parameters must be marked as "generic", using the `:!` syntax. -This reflects these two properties of these parameters: +All interface parameters must be marked as "symbolic", using the `:!` binding +pattern syntax. This reflects these two properties of these parameters: - They must be resolved at compile-time, and so can't be passed regular dynamic values. -- We allow either generic or template values to be passed in. +- We allow either symbolic or template values to be passed in. -**Note:** Interface parameters aren't required to be types, but that is the vast -majority of cases. As an example, if we had an interface that allowed a type to -define how the tuple-member-read operator would work, the index of the member -could be an interface parameter: +**Note:** Interface parameters aren't required to be facets, but that is the +vast majority of cases. As an example, if we had an interface that allowed a +type to define how the tuple-member-read operator would work, the index of the +member could be an interface parameter: ``` interface ReadTupleMember(index:! u32) { @@ -2351,7 +2377,7 @@ interface ReadTupleMember(index:! u32) { ``` This requires that the index be known at compile time, but allows different -indices to be associated with different types. +indices to be associated with different values of `T`. **Caveat:** When implementing an interface twice for a type, the interface parameters are required to always be different. For example: @@ -2364,7 +2390,7 @@ class Bijection(FromType:! type, ToType:! type) { extend impl as Map(FromType, ToType) { ... } extend impl as Map(ToType, FromType) { ... } } -// ❌ Error: Bijection has twodifferent impl definitions of +// ❌ Error: Bijection has two different impl definitions of // interface Map(String, String) var oops: Bijection(String, String) = ...; ``` @@ -2552,9 +2578,9 @@ interface PointCloud { ##### Set an associated facet to a specific value -Functions accepting a generic type might also want to constrain one of its -associated facets to be a specific, concrete type. For example, we might want to -have a function only accept stacks containing integers: +Functions accepting a symbolic facet parameter might also want to constrain one +of its associated facets to be a specific, concrete type. For example, we might +want to have a function only accept stacks containing integers: ``` fn SumIntStack[T:! Stack where .ElementType = i32](s: T*) -> i32 { @@ -2882,7 +2908,7 @@ fn F[T:! InterfaceA where .A impls #### Parameterized type implements interface -There are times when a function will pass a generic type parameter of the +There are times when a function will pass a symbolic facet parameter of the function as an argument to a parameterized type, as in the previous case, and in addition the function needs the result to implement a specific interface. @@ -2908,7 +2934,7 @@ fn PrintThree #### Another type implements parameterized interface In this case, we need some other type to implement an interface parameterized by -a generic type parameter. The syntax for this case follows the previous case, +a symbolic facet parameter. The syntax for this case follows the previous case, except now the `.Self` parameter is on the interface to the right of the `impls`. For example, we might need a type parameter `T` to support explicit conversion from an integer type like `i32`: @@ -2970,7 +2996,7 @@ redundant ways to express a restriction, following the ### Implied constraints -Imagine we have a generic function that accepts an arbitrary `HashMap`: +Imagine we have a checked-generic function that accepts an arbitrary `HashMap`: ``` fn LookUp[KeyType:! type](hm: HashMap(KeyType, i32)*, @@ -3008,7 +3034,7 @@ fn PrintValueOrDefault[KeyType:! Printable, ``` In this case, Carbon will accept the definition and infer the needed constraints -on the generic type parameter. This is both more concise for the author of the +on the symbolic facet parameter. This is both more concise for the author of the code and follows the ["don't repeat yourself" principle](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself). This redundancy is undesirable since it means if the needed constraints for @@ -3020,8 +3046,8 @@ will have already satisfied these constraints. This implied constraint is equivalent to the explicit constraint that each parameter and return type [is legal](#must-be-legal-type-argument-constraints). -**Note:** These implied constraints affect the _requirements_ of a generic type -parameter, but not its _member names_. This way you can always look at the +**Note:** These implied constraints affect the _requirements_ of a symbolic +facet parameter, but not its _member names_. This way you can always look at the declaration to see how name resolution works, without having to look up the definitions of everything it is used as an argument to. @@ -3056,8 +3082,8 @@ support some form of this feature as part of their type inference (and #### Must be legal type argument constraints -Now consider the case that the generic type parameter is going to be used as an -argument to a parameterized type in a function body, not in the signature. If +Now consider the case that the symbolic facet parameter is going to be used as +an argument to a parameterized type in a function body, not in the signature. If the parameterized type was explicitly mentioned in the signature, the implied constraint feature would ensure all of its requirements were met. The developer can create a trivial @@ -3097,7 +3123,7 @@ interface Graph { ### Manual type equality -Imagine we have some function with generic parameters: +Imagine we have some function with symbolic parameters: ``` fn F[T:! SomeInterface](x: T) { @@ -3332,7 +3358,7 @@ fn F[T:! Transitive](t: T) { ``` Since adding an `observe` declaration only adds non-extending implementations of -interfaces to generic types, they may be added without breaking existing code. +interfaces to symbolic facets, they may be added without breaking existing code. ## Other constraints as facet types @@ -3515,7 +3541,7 @@ What is the size of a type? - It could be fully known and fixed at compile time -- this is true of primitive types (`i32`, `f64`, and so on), most [classes](/docs/design/classes.md), and most other concrete types. -- It could be known generically. This means that it will be known at codegen +- It could be known symbolically. This means that it will be known at codegen time, but not at type-checking time. - It could be dynamic. For example, it could be a [dynamic type](#runtime-type-fields), a slice, variable-sized type (such as @@ -3530,7 +3556,7 @@ otherwise. Note: something with size 0 is still considered "sized". The facet type `Sized` is defined as follows: > `Sized` is a type whose values are types `T` that are "sized" -- that is the -> size of `T` is known, though possibly only generically. +> size of `T` is known, though possibly only symbolically Knowing a type is sized is a precondition to declaring variables of that type, taking values of that type as parameters, returning values of that type, and @@ -3566,7 +3592,7 @@ fn F[T:! type](x: T*) { // T is unsized. var z: T; } -// T is sized, but its size is only known generically. +// T is sized, but its size is only known symbolically. fn G[T: DefaultConstructible](x: T*) { // ✅ Allowed: T is default constructible, which means sized. var y: T = T.Default(); @@ -3577,18 +3603,11 @@ var z: Name = Name.Default();; G(&z); ``` -**Open question:** Even if the size is fixed, it won't be known at the time of -compiling the generic function if we are using the dynamic strategy. Should we -automatically -[box]() -local variables when using the dynamic strategy? Or should we only allow -`MaybeBox` values to be instantiated locally? Or should this just be a case -where the compiler won't necessarily use the dynamic strategy? - **Open question:** Should the `Sized` facet type expose an associated constant with the size? So you could say `T.ByteSize` in the above example to get a -generic int value with the size of `T`. Similarly you might say `T.ByteStride` -to get the number of bytes used for each element of an array of `T`. +symbolic integer value with the size of `T`. Similarly you might say +`T.ByteStride` to get the number of bytes used for each element of an array of +`T`. ### `TypeId` @@ -3596,11 +3615,11 @@ There are some capabilities every type can provide. For example, every type should be able to return its name or identify whether it is equal to another type. It is rare, however, for code to need to access these capabilities, so we relegate these capabilities to an interface called `TypeId` that all types -automatically implement. This way generic code can indicate that it needs those -capabilities by including `TypeId` in the list of requirements. In the case -where no type capabilities are needed, for example the code is only manipulating -pointers to the type, you would write `T:! type` and get the efficiency of -`void*` but without giving up type safety. +automatically implement. This way checked-generic code can indicate that it +needs those capabilities by including `TypeId` in the list of requirements. In +the case where no type capabilities are needed, for example the code is only +manipulating pointers to the type, you would write `T:! type` and get the +efficiency of `void*` but without giving up type safety. ``` fn SortByAddress[T:! type](v: Vector(T*)*) { ... } @@ -3638,8 +3657,8 @@ conform to the decision on The facet types `Concrete`, `Deletable`, and `TrivialDestructor` all extend `Destructible`. Combinations of them may be formed using [the `&` operator](#combining-interfaces-by-anding-facet-types). For example, a -generic function that both instantiates and deletes values of a type `T` would -require `T` implement `Concrete & Deletable`. +checked-generic function that both instantiates and deletes values of a type `T` +would require `T` implement `Concrete & Deletable`. Types are forbidden from explicitly implementing these facet types directly. Instead they use @@ -3663,7 +3682,7 @@ fn F(...) { } ``` -gets rewritten to: +is generally equivalent to: ``` fn F(...) { @@ -3996,9 +4015,9 @@ and for selecting which impl to use that achieve these three goals: [a goal for Carbon](goals.md#coherence). More detail can be found in [this appendix with the rationale and alternatives considered](appendix-coherence.md). - Libraries will work together as long as they pass their separate checks. -- A generic function can assume that some impl will be successfully selected - if it can see an impl that applies, even though another more specific impl - may be selected. +- A checked-generic function can assume that some impl will be successfully + selected if it can see an impl that applies, even though another more + specific impl may be selected. For this to work, we need a rule that picks a single `impl` in the case where there are multiple `impl` definitions that match a particular type and interface @@ -4270,9 +4289,9 @@ this time there are no known alternatives. Unfortunately, the approach Carbon uses to avoid undecidability for type equality, [providing an explicit proof in the source](#manual-type-equality), can't be used here. The code triggering the query asking whether some type implements an -interface will typically be generic code with know specific knowledge about the -types involved, and won't be in a position to provide a manual proof that the -implementation should exist. +interface will typically be checked-generic code with no specific knowledge +about the types involved, and won't be in a position to provide a manual proof +that the implementation should exist. **Open question:** Is there some restriction on `impl` declarations that would allow our desired use cases, but allow the compiler to detect non-terminating @@ -4446,9 +4465,9 @@ differences between the Carbon and Rust plans: declared `final`. - Since a Rust impl is not specializable by default, generic functions can assume that if a matching blanket impl declaration is found, the associated - constants from that impl will be used. In Carbon, if a generic function - requires an associated constant to have a particular value, the function - commonly will need to state that using an explicit constraint. + constants from that impl will be used. In Carbon, if a checked-generic + function requires an associated constant to have a particular value, the + function commonly will need to state that using an explicit constraint. - Carbon will not have the "fundamental" attribute used by Rust on types or traits, as described in [Rust RFC 1023: "Rebalancing Coherence"](https://rust-lang.github.io/rfcs/1023-rebalancing-coherence.html). @@ -5632,10 +5651,10 @@ impl forall [T:! IntLike] like T ## Parameterized types -Types may have generic parameters. Those parameters may be used to specify types -in the declarations of its members, such as data fields, member functions, and -even interfaces being implemented. For example, a container type might be -parameterized by the type of its elements: +Generic types may be defined by giving them compile-time parameters. Those +parameters may be used to specify types in the declarations of its members, such +as data fields, member functions, and even interfaces being implemented. For +example, a container type might be parameterized by the type of its elements: ``` class HashMap( @@ -5655,8 +5674,9 @@ class HashMap( } ``` -Note that, unlike functions, every parameter to a type must either be generic or -template, using `:!` or `template...:!`, not dynamic, with a plain `:`. +Note that, unlike functions, every parameter to a type must be compile-time, +either symbolic using `:!` or template using `template...:!`, not dynamic, with +a plain `:`. Two types are the same if they have the same name and the same arguments. Carbon's [manual type equality](#manual-type-equality) approach means that the @@ -5762,8 +5782,8 @@ Note that the constraint on `T` is just `Movable`, not `Movable & OptionalStorage`, since the `Movable` requirement is [sufficient to guarantee](#lookup-resolution-and-specialization) that some implementation of `OptionalStorage` exists for `T`. Carbon does not require -callers of `Optional`, even generic callers, to specify that the argument type -implements `OptionalStorage`: +callers of `Optional`, even checked-generic callers, to specify that the +argument type implements `OptionalStorage`: ``` // ✅ Allowed: `T` just needs to be `Movable` to form `Optional(T)`. @@ -5797,15 +5817,15 @@ class Optional(T:! Movable) { ### Dynamic types -Generics provide enough structure to support runtime dispatch for values with -types that vary at runtime, without giving up type safety. Both Rust and Swift -have demonstrated the value of this feature. +Checked-generics provide enough structure to support runtime dispatch for values +with types that vary at runtime, without giving up type safety. Both Rust and +Swift have demonstrated the value of this feature. #### Runtime type parameters This feature is about allowing a function's type parameter to be passed in as a -dynamic (non-generic) parameter. All values of that type would still be required -to have the same type. +dynamic (non-compile-time) parameter. All values of that type would still be +required to have the same type. #### Runtime type fields @@ -5834,8 +5854,8 @@ There are a collection of use cases for making different changes to interfaces that are already in use. These should be addressed either by describing how they can be accomplished with existing generics features, or by adding features. -In addition, evolution from (C++ or Carbon) templates to generics needs to be -supported and made safe. +In addition, evolution from (C++ or Carbon) templates to checked generics needs +to be supported and made safe. ### Testing @@ -5878,13 +5898,14 @@ See details in [the goals document](goals.md#bridge-for-c-customization-points). ### Variadic arguments -Some facility for allowing a function to generically take a variable number of -arguments. +Some facility for allowing a function to take a variable number of arguments, +with the [definition checked](terminology.md#complete-definition-checking) +independent of calls. -### Range constraints on generic integers +### Range constraints on symbolic integers We currently only support `where` clauses on facet types. We may want to also -support constraints on generic integers. The constraint with the most expected +support constraints on symbolic integers. The constraint with the most expected value is the ability to do comparisons like `<`, or `>=`. For example, you might constrain the `N` member of [`NSpacePoint`](#associated-constants) using an expression like `PointT:! NSpacePoint where 2 <= .N and .N <= 3`. @@ -5892,7 +5913,7 @@ expression like `PointT:! NSpacePoint where 2 <= .N and .N <= 3`. The concern here is supporting this at compile time with more benefit than complexity. For example, we probably don't want to support integer-range based types at runtime, and there are also concerns about reasoning about comparisons -between multiple generic integer parameters. For example, if `J < K` and +between multiple symbolic integer parameters. For example, if `J < K` and `K <= L`, can we call a function that requires `J < L`? There is also a secondary syntactic concern about how to write this kind of constraint on a parameter, as opposed to an associated facet, as in `N:! u32 where ___ >= 2`. From d211bedbba01f61fb95e0363ea64ada26f592765 Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 5 Sep 2023 22:46:10 +0000 Subject: [PATCH 38/83] Checkpoint progress. --- docs/design/functions.md | 2 +- docs/design/generics/README.md | 4 +-- docs/design/generics/appendix-witness.md | 8 ++--- docs/design/generics/goals.md | 43 ++++++++++++------------ docs/design/generics/terminology.md | 2 +- docs/design/pattern_matching.md | 42 ++++++++++++----------- docs/project/faq.md | 28 +++++++-------- 7 files changed, 66 insertions(+), 63 deletions(-) diff --git a/docs/design/functions.md b/docs/design/functions.md index e09a176c66aa1..4d89aa97607bf 100644 --- a/docs/design/functions.md +++ b/docs/design/functions.md @@ -151,7 +151,7 @@ the `a` and `b` parameters of `Add`. Other designs build upon basic function syntax to add advanced features: - [Generic functions](generics/overview.md#generic-functions) adds support for - deduced parameters and generic type parameters. + deduced parameters and compile-time parameters. - [Class member functions](classes.md#member-functions) adds support for methods and class functions. diff --git a/docs/design/generics/README.md b/docs/design/generics/README.md index b0dece1a65bcb..fa5b2b2beb766 100644 --- a/docs/design/generics/README.md +++ b/docs/design/generics/README.md @@ -16,6 +16,4 @@ feature of Carbon: direction. - [Terminology](terminology.md) - A glossary establishing common terminology for describing the design. -- [Detailed design](details.md) - In depth description of how generic type - parameters work. -- ~~Rejected alternatives~~ - not implemented yet +- [Detailed design](details.md) - In-depth description. diff --git a/docs/design/generics/appendix-witness.md b/docs/design/generics/appendix-witness.md index 45502e583fb02..9de1b4ea4550a 100644 --- a/docs/design/generics/appendix-witness.md +++ b/docs/design/generics/appendix-witness.md @@ -53,10 +53,10 @@ document is to state the limitations and obstacles of doing that. ### Witness tables [Witness tables](https://forums.swift.org/t/where-does-the-term-witness-table-come-from/54334/4) -are an implementation strategy where values passed to a generic type parameter -are compiled into a table of required functionality. That table is then filled -in for a given passed-in type with references to the implementation on the -original type. The generic is implemented using calls into entries in the +are an implementation strategy where values passed to a compile-time type +binding are compiled into a table of required functionality. That table is then +filled in for a given passed-in type with references to the implementation on +the original type. The generic is implemented using calls into entries in the witness table, which turn into calls to the original type. This doesn't necessarily imply a runtime indirection: it may be a purely compile-time separation of concerns. However, it insists on a full abstraction boundary diff --git a/docs/design/generics/goals.md b/docs/design/generics/goals.md index 2ab42d952c48c..e848934b651cb 100644 --- a/docs/design/generics/goals.md +++ b/docs/design/generics/goals.md @@ -12,7 +12,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Purpose of this document](#purpose-of-this-document) - [Background](#background) - - [Generic parameters](#generic-parameters) + - [Compile-time parameters](#compile-time-parameters) - [Interfaces](#interfaces) - [Templates](#templates) - [Checked-generic goals](#checked-generic-goals) @@ -66,34 +66,35 @@ the [compile-time duck typing](https://en.wikipedia.org/wiki/Duck_typing#Templates_or_generic_types) approach of C++ templates. -### Generic parameters +### Compile-time parameters -Generic functions and generic types will all take some "generic parameters", +Generic functions and generic types will all take some compile-time parameters, which will frequently be types, and in some cases will be [deduced](terminology.md#deduced-parameter) from the types of the values of explicit parameters. -If a generic parameter is a type, the generic function's signature can specify -constraints that the caller's type must satisfy. For example, a resizable array -type (like C++'s `std::vector`) might have a generic type parameter with the -constraint that the type must be movable and have a static size. A sort function -might apply to any array whose elements are comparable and movable. +If a compile-time parameter is a type, the generic function's signature can +specify constraints that the caller's type must satisfy. For example, a +resizable array type (like C++'s `std::vector`) might have a compile-time type +parameter with the constraint that the type must be movable and have a static +size. A sort function might apply to any array whose elements are comparable and +movable. -A constraint might involve multiple generic parameters. For example, a merge -function might apply to two arbitrary containers so long as their elements have -the same type. +A constraint might involve multiple compile-time parameters. For example, a +merge function might apply to two arbitrary containers so long as their elements +have the same type. ### Interfaces -We need some way to express the constraints on a generic type parameter. In +We need some way to express the constraints on a compile-time type parameter. In Carbon we express these "type constraints" by saying we restrict to types that implement specific [_interfaces_](terminology.md#interface). Interfaces describe an API a type could implement; for example, it might specify a set of functions, including names and signatures. A type implementing an interface may be passed -as a generic type argument to a function that has that interface as a -requirement of its generic type parameter. Then, the functions defined in the -interface may be called in the body of the function. Further, interfaces have -names that allow them to be reused. +as a compile-time type argument to a function that has that interface as a +requirement of its compile-time type parameter. Then, the functions defined in +the interface may be called in the body of the function. Further, interfaces +have names that allow them to be reused. Similar compile-time and run-time constructs may be found in other programming languages: @@ -350,10 +351,10 @@ Enable simple user control of whether to use dynamic or static dispatch. checked-generic functions: - Static strategy: Like template parameters, the values for checked parameters - must be statically known at the callsite, or known to be a generic parameter - to the calling function. This can generate separate, specialized versions of - each combination of checked and template arguments, in order to optimize for - those types or values. + must be statically known at the callsite, or known to be a compile-time + parameter to the calling function. This can generate separate, specialized + versions of each combination of checked and template arguments, in order to + optimize for those types or values. - Dynamic strategy: This is when the compiler generates a single version of the function that uses runtime dispatch to get something semantically equivalent to separate instantiation, but likely with different size, build @@ -390,7 +391,7 @@ following features would benefit substantially from guaranteed monomorphization: - Allocating local variables in stack storage. Without monomorphization, we would need to perform dynamic memory allocation -- whether on the stack or the heap -- for local variables whose sizes depend on checked parameters. -- Passing parameters to functions. We cannot pass values of generic types in +- Passing parameters to functions. We cannot pass values of types in registers. - Finding [specialized](terminology.md#specialization) implementations of interfaces beyond those clearly needed from the function signature. diff --git a/docs/design/generics/terminology.md b/docs/design/generics/terminology.md index 0947003d2bde2..265abbe41641b 100644 --- a/docs/design/generics/terminology.md +++ b/docs/design/generics/terminology.md @@ -67,7 +67,7 @@ called a _generic parameter_, to it. So: - a _generic function_ is a function with at least one compile-time parameter, which could be an explicit argument to the function or [deduced](#deduced-parameter); -- a _generic type_ is a function with a compile-time parameter, for example a +- a _generic type_ is a type with a compile-time parameter, for example a container type parameterized by the type of the contained elements; - a _generic interface_ is an [interface](#interface) with [a compile-time parameter](#interface-parameters-and-associated-constants). diff --git a/docs/design/pattern_matching.md b/docs/design/pattern_matching.md index da01dfe356386..97e0bdc983d44 100644 --- a/docs/design/pattern_matching.md +++ b/docs/design/pattern_matching.md @@ -18,7 +18,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Name bindings](#name-bindings) - [Unused bindings](#unused-bindings) - [Alternatives considered](#alternatives-considered-1) - - [Generic bindings](#generic-bindings) + - [Compile-time bindings](#compile-time-bindings) - [`auto` and type deduction](#auto-and-type-deduction) - [Alternatives considered](#alternatives-considered-2) - [`var`](#var) @@ -214,39 +214,43 @@ fn J(unused n: i32); - [Anonymous, named identifiers](/proposals/p2022.md#anonymous-named-identifiers) - [Attributes](/proposals/p2022.md#attributes) -#### Generic bindings +#### Compile-time bindings A `:!` can be used in place of `:` for a binding that is usable at compile time. -- _generic-pattern_ ::= `unused`? `template`? _identifier_ `:!` _expression_ -- _generic-pattern_ ::= `template`? _identifier_ `:!` _expression_ -- _generic-pattern_ ::= `template`? `_` `:!` _expression_ -- _generic-pattern_ ::= `unused` `template`? _identifier_ `:!` _expression_ -- _proper-pattern_ ::= _generic-pattern_ +- _compile-time-pattern_ ::= `unused`? `template`? _identifier_ `:!` + _expression_ +- _compile-time-pattern_ ::= `template`? _identifier_ `:!` _expression_ +- _compile-time-pattern_ ::= `template`? `_` `:!` _expression_ +- _compile-time-pattern_ ::= `unused` `template`? _identifier_ `:!` + _expression_ +- _proper-pattern_ ::= _compile-time-pattern_ **FIXME:** Maybe this should just be: -- _generic-pattern_ ::= `unused`? `template`? _identifier_ `:!` _expression_ -- _generic-pattern_ ::= `template`? `_` `:!` _expression_ -- _proper-pattern_ ::= _generic-pattern_ +- _compile-time-pattern_ ::= `unused`? `template`? _identifier_ `:!` + _expression_ +- _compile-time-pattern_ ::= `template`? `_` `:!` _expression_ +- _proper-pattern_ ::= _compile-time-pattern_ -**FIXME:** Or: +**FIXME:** Or, if we want to keep the "unused" patterns separate: -- _generic-pattern_ ::= `template`? _identifier_ `:!` _expression_ -- _generic-pattern_ ::= `template`? `_` `:!` _expression_ -- _generic-pattern_ ::= `unused` `template`? _identifier_ `:!` _expression_ -- _proper-pattern_ ::= _generic-pattern_ +- _compile-time-pattern_ ::= `template`? _identifier_ `:!` _expression_ +- _compile-time-pattern_ ::= `template`? `_` `:!` _expression_ +- _compile-time-pattern_ ::= `unused` `template`? _identifier_ `:!` + _expression_ +- _proper-pattern_ ::= _compile-time-pattern_ ```carbon -// ✅ `F` takes a generic type parameter `T` and a parameter `x` of type `T`. +// ✅ `F` takes a symbolic facet parameter `T` and a parameter `x` of type `T`. fn F(T:! type, x: T) { var v: T = x; } ``` -The `template` keyword indicates the binding is introducing a template -parameter, so name lookups into the parameter should be deferred until its value -is known. +The `template` keyword indicates the binding pattern is introducing a template +binding, so name lookups into the binding will not be fully resolved until its +value is known. #### `auto` and type deduction diff --git a/docs/project/faq.md b/docs/project/faq.md index 57fb650c8769f..6248d704be490 100644 --- a/docs/project/faq.md +++ b/docs/project/faq.md @@ -336,9 +336,9 @@ in scope. It's also worth noting that Carbon [doesn't use _any_ kind of brackets](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/README.md#checked-and-template-parameters) -to mark template or generic parameters, so if Carbon had angle brackets, they -would mean something different than they do in C++, which could cause confusion. -We do use square brackets to mark _deduced_ parameters, as in: +to mark template or checked-generic parameters, so if Carbon had angle brackets, +they would mean something different than they do in C++, which could cause +confusion. We do use square brackets to mark _deduced_ parameters, as in: ``` fn Sort[T:! Comparable](a: Vector(T)*) @@ -349,7 +349,7 @@ particular, deduced parameters are never mentioned at the callsite, so those square brackets are never part of the expression syntax. See [Proposal #676: `:!` generic syntax](/proposals/p0676.md) for more -background on how and why we chose our current generics syntax. +background on how and why we chose our current compile-time parameter syntax. ### Why do variable declarations have to start with `var` or `let`? @@ -444,19 +444,19 @@ will handle both templates (matching C++) and checked generics (common in other languages: Rust, Swift, Go, Kotlin, Java, and so on). The key difference between the two is that template arguments can only finish -type-checking _during_ instantiation, whereas generics specify an interface with -which arguments can finish type-checking _without_ instantiation. This has a -couple of important benefits: - -- Type-checking errors for generics happen earlier, making it easier for the - compiler to produce helpful diagnostics. -- Generic functions can generate less compiled output, allowing compilation - with many uses to be faster. +type-checking _during_ instantiation, whereas checked generics specify an +interface with which arguments can finish type-checking _without_ instantiation. +This has a couple of important benefits: + +- Type-checking errors for checked generics happen earlier, making it easier + for the compiler to produce helpful diagnostics. +- Checked-generic functions can generate less compiled output, allowing + compilation with many uses to be faster. - For comparison, template instantiations are a major factor for C++ compilation latency. -Although Carbon will prefer generics over templates, templates are provided for -migration of C++ code. +Although Carbon will prefer checked generics over templates, templates are +provided for migration of C++ code. References: From ed9ab2bb76342bf7670adf5f9e8077ad5ca3a66b Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 5 Sep 2023 23:03:05 +0000 Subject: [PATCH 39/83] Checkpoint progress. --- docs/design/pattern_matching.md | 35 +++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/docs/design/pattern_matching.md b/docs/design/pattern_matching.md index 97e0bdc983d44..976ce6c3d682f 100644 --- a/docs/design/pattern_matching.md +++ b/docs/design/pattern_matching.md @@ -14,8 +14,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Pattern Syntax and Semantics](#pattern-syntax-and-semantics) - [Expression patterns](#expression-patterns) - [Alternatives considered](#alternatives-considered) - - [Bindings](#bindings) - - [Name bindings](#name-bindings) + - [Binding patterns](#binding-patterns) + - [Name binding patterns](#name-binding-patterns) - [Unused bindings](#unused-bindings) - [Alternatives considered](#alternatives-considered-1) - [Compile-time bindings](#compile-time-bindings) @@ -46,16 +46,17 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception A _pattern_ is an expression-like syntax that describes the structure of some value. The pattern may contain unknowns, so it can potentially match multiple values, and those unknowns may have names, in which case they are called -_bindings_. When a pattern is executed by giving it a value called the +_binding patterns_. When a pattern is executed by giving it a value called the _scrutinee_, it determines whether the scrutinee matches the pattern, and if so, determines the values of the bindings. ## Pattern Syntax and Semantics Expressions are patterns, as described below. A pattern that is not an -expression, because it contains pattern-specific syntax such as a binding, is a -_proper pattern_. Many expression forms, such as arbitrary function calls, are -not permitted as proper patterns, so cannot contain bindings. +expression, because it contains pattern-specific syntax such as a binding +pattern, is a _proper pattern_. Many expression forms, such as arbitrary +function calls, are not permitted as proper patterns, so cannot contain binding +patterns. - _pattern_ ::= _proper-pattern_ @@ -116,24 +117,28 @@ fn F() { - [Introducer syntax for expression patterns](/proposals/p2188.md#introducer-syntax-for-expression-patterns) -### Bindings +### Binding patterns -#### Name bindings +#### Name binding patterns -A name binding is a pattern. +A name binding pattern is a pattern. - _binding-pattern_ ::= _identifier_ `:` _expression_ - _proper-pattern_ ::= _binding-pattern_ -The type of the _identifier_ is specified by the _expression_. The scrutinee is -implicitly converted to that type if necessary. +The _identifier_ specifies the name of the _binding_. The type of the binding is +specified by the _expression_. The scrutinee is implicitly converted to that +type if necessary. The binding is then _bound_ to the converted value. ```carbon fn F() -> i32 { match (5) { // ✅ `5` is implicitly converted to `i32`. - // Returns `5 as i32`. - case n: i32 => { return n; } + case n: i32 => { + // The binding `n` has the value `5 as i32`, + // which is the value returned. + return n; + } } } ``` @@ -305,8 +310,8 @@ scrutinee. - _proper-pattern_ ::= `var` _proper-pattern_ A `var` pattern matches when its nested pattern matches. The type of the storage -is the resolved type of the nested _pattern_. Any bindings within the nested -pattern refer to portions of the corresponding storage rather than to the +is the resolved type of the nested _pattern_. Any binding patterns within the +nested pattern refer to portions of the corresponding storage rather than to the scrutinee. ```carbon From 7d4ba5f8a79306a63e3c6e96ec46ffaaf0c36db4 Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 5 Sep 2023 23:36:39 +0000 Subject: [PATCH 40/83] Checkpoint progress. --- docs/design/generics/appendix-coherence.md | 10 +++++----- docs/design/generics/appendix-witness.md | 12 ++++++------ docs/design/generics/details.md | 5 +++-- docs/design/generics/terminology.md | 3 ++- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/docs/design/generics/appendix-coherence.md b/docs/design/generics/appendix-coherence.md index 35d5124c7a9d4..ffa26496aca18 100644 --- a/docs/design/generics/appendix-coherence.md +++ b/docs/design/generics/appendix-coherence.md @@ -36,8 +36,8 @@ implements interfaces. There are a few main problematic use cases to consider: `Song` type to support "by title", "by artist", and "by album" orderings. - Implementing an interface for a type when there is no relationship between the libraries defining the interface and the type. -- When the implementation of an interface for a type uses an associated type - that can't be referenced from the file or files where the implementation is +- When the implementation of an interface for a type relies on something that + can't be referenced from the file or files where the implementation is allowed to be defined. These last two cases are highlighted as concerns in Rust in @@ -208,9 +208,9 @@ This has some downsides: varies instead of being known statically. - It is slower to execute from dynamic dispatch and the inability to inline. - In some cases it may not be feasible to use dynamic dispatch. For example, - if an interface method returns an associated type, we might not know the - calling convention of the function without knowing some details about the - type. + if the return type of an interface method involves an associated constant, + we might not know the calling convention of the function without knowing + some details about the value of that constant. As a result, this doesn't make sense as the default behavior for Carbon based on its [goals](/docs/project/goals.md). That being said, this could be a feature diff --git a/docs/design/generics/appendix-witness.md b/docs/design/generics/appendix-witness.md index 9de1b4ea4550a..e44a8996ea211 100644 --- a/docs/design/generics/appendix-witness.md +++ b/docs/design/generics/appendix-witness.md @@ -128,12 +128,12 @@ generics. Specialization compounds the difficulty of the previous two issues. -An interface with an associated type might be implemented using witness tables -by including a reference to the associated type's witness table in the witness -table for the interface. This doesn't give you a witness table for parameterized -types using the associated type as an argument. Synthesizing those witness -tables is particularly tricky if the implementation is different for specific -types due to specialization. +An interface with an associated facet might be implemented using witness tables +by including a reference to the associated facet's witness table in the witness +table for the interface. This doesn't, though, give you a witness table for +parameterized types using the associated facet as an argument. Synthesizing +those witness tables is particularly tricky if the implementation is different +for specific types due to specialization. Similarly, a blanket implementation can guarantee that some implementation of an interface exists. Specialization means that actual implementation of that diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 3cf54e332994d..ccfd116408208 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -2482,7 +2482,7 @@ class S(T:! B where ...) { // Constraints on an interface parameter: interface A(T:! B where ...) { - // Constraints on an associated type: + // Constraints on an associated facet: let U:! C where ...; // Constraints on an associated method: fn G[self: Self, V:! D where ...](v: V); @@ -5874,7 +5874,8 @@ a type to an implementation of an interface parameterized by that type. #### Generic associated facets Generic associated facets are about when this is a requirement of an interface. -These are also called "associated type constructors." +These are also called +"[associated type constructors](https://smallcultfollowing.com/babysteps/blog/2016/11/02/associated-type-constructors-part-1-basic-concepts-and-introduction/)." Rust has [stabilized this feature](https://github.com/rust-lang/rust/pull/96709). diff --git a/docs/design/generics/terminology.md b/docs/design/generics/terminology.md index 265abbe41641b..950ee179f96ea 100644 --- a/docs/design/generics/terminology.md +++ b/docs/design/generics/terminology.md @@ -663,7 +663,8 @@ value is not known. This is the implementation strategy for and [trait objects in Rust](https://doc.rust-lang.org/book/ch17-02-trait-objects.html). Note that this often comes with limitations, since for example it is much more -difficult to support when the associated types of the interface are not known. +difficult to support when the associated constants of the interface are not +known. Typically a reference to the witness table will be passed separately from the object, unlike a From 8b3b852b6f67ecf2f70fdb67c9e1e7285dc43e79 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 6 Sep 2023 00:03:17 +0000 Subject: [PATCH 41/83] Checkpoint progress. --- docs/design/classes.md | 2 +- docs/design/expressions/indexing.md | 2 +- docs/design/values.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index 6c943527c423a..9a3ff29790222 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -325,7 +325,7 @@ that support dynamic dispatch are called _object-safe_, following - They don't have a `Self` in the signature of a method in a contravariant position like a parameter. -- They don't have free associated types or other associated items used in a +- They don't have free associated facets or other associated items used in a method signature. The restrictions on object-safe interfaces match the restrictions on base class diff --git a/docs/design/expressions/indexing.md b/docs/design/expressions/indexing.md index aab8b0928f98e..d9f8fe8671049 100644 --- a/docs/design/expressions/indexing.md +++ b/docs/design/expressions/indexing.md @@ -51,7 +51,7 @@ pointer, and do not automatically chain with customized dereference interfaces. **Open question:** It's not clear that the lack of chaining is necessary, and it might be more expressive for the pointer type returned by the `Addr` methods to -be an associated type with a default to allow types to produce custom +be an associated facet with a default to allow types to produce custom pointer-like types on their indexing boundary and have them still be automatically dereferenced. diff --git a/docs/design/values.md b/docs/design/values.md index 1224b6bd5eb4d..d153d558df931 100644 --- a/docs/design/values.md +++ b/docs/design/values.md @@ -1010,8 +1010,8 @@ placeholder `value_rep = T;`, we need to explore exactly what the best relationship is with the customization point. For example, should this syntax immediately forward declare `impl as ReferenceImplicitAs where .T = T`, thereby allowing an out-of-line definition of the `Convert` method and `... where _` to -pick up the associated type from the syntax. Alternatively, the syntactic marker -might be integrated into the `impl` declaration for `ReferenceImplicitAs` +pick up the associated constant from the syntax. Alternatively, the syntactic +marker might be integrated into the `impl` declaration for `ReferenceImplicitAs` itself. ## Alternatives considered From 1f8ca85d615632baa0d9bbf03e2a338d31347aa4 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 6 Sep 2023 01:16:45 +0000 Subject: [PATCH 42/83] Checkpoint progress. --- docs/design/classes.md | 7 ++++--- docs/design/expressions/comparison_operators.md | 6 +++--- docs/design/functions.md | 2 +- docs/design/lexical_conventions/symbolic_tokens.md | 4 ++-- docs/design/naming_conventions.md | 2 +- docs/design/type_inference.md | 3 ++- docs/design/values.md | 6 ++++-- 7 files changed, 17 insertions(+), 13 deletions(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index 9a3ff29790222..cf9d557c2180d 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -910,7 +910,7 @@ which must be an then match pattern '`self:` _type_' against it". If the method declaration also includes -[deduced generic parameters](/docs/design/generics/overview.md#deduced-parameters), +[deduced compile-time parameters](/docs/design/generics/overview.md#deduced-parameters), the `self` parameter must be in the same list in square brackets `[`...`]`. The `self` parameter may appear in any position in that list, as long as it appears after any names needed to describe its type. @@ -1644,8 +1644,9 @@ interface Allocator { } ``` -To pass a pointer to a base class without a virtual destructor to a generic -function expecting a `Deletable` type, use the `UnsafeAllowDelete` +To pass a pointer to a base class without a virtual destructor to a +checked-generic function expecting a `Deletable` type, use the +`UnsafeAllowDelete` [type adapter](/docs/design/generics/details.md#adapting-types). ``` diff --git a/docs/design/expressions/comparison_operators.md b/docs/design/expressions/comparison_operators.md index 159c6f0b14678..262264ad5e2fa 100644 --- a/docs/design/expressions/comparison_operators.md +++ b/docs/design/expressions/comparison_operators.md @@ -241,8 +241,8 @@ User-defined types can extend the behavior of the comparison operators by implementing interfaces. In this section, various properties are specified that such implementations "should" satisfy. These properties are not enforced in general, but the standard library might detect violations of some of them in -some circumstances. These properties may be assumed by generic code, resulting -in unexpected behavior if they are violated. +some circumstances. These properties may be assumed by checked-generic code, +resulting in unexpected behavior if they are violated. #### Equality @@ -460,7 +460,7 @@ than or equivalent to themselves. **TODO:** The standard library should provide a way to specify that an ordering is a weak, partial, or total ordering, and a way to request such an ordering in -a generic. +a checked generic. #### Compatibility of equality and ordering diff --git a/docs/design/functions.md b/docs/design/functions.md index 4d89aa97607bf..58aa4a2f6cc46 100644 --- a/docs/design/functions.md +++ b/docs/design/functions.md @@ -158,7 +158,7 @@ Other designs build upon basic function syntax to add advanced features: ## Alternatives considered - [Function keyword](/proposals/p0438.md#function-keyword) -- [Only allow `auto` return types if parameters are generic](/proposals/p0826.md#only-allow-auto-return-types-if-parameters-are-generic) +- [Only allow `auto` return types if parameters are compile-time](/proposals/p0826.md#only-allow-auto-return-types-if-parameters-are-generic) - [Provide alternate function syntax for concise return type inference](/proposals/p0826.md#provide-alternate-function-syntax-for-concise-return-type-inference) - [Allow separate declaration and definition](/proposals/p0826.md#allow-separate-declaration-and-definition) diff --git a/docs/design/lexical_conventions/symbolic_tokens.md b/docs/design/lexical_conventions/symbolic_tokens.md index 26bbb48d13823..81b6f447efcbc 100644 --- a/docs/design/lexical_conventions/symbolic_tokens.md +++ b/docs/design/lexical_conventions/symbolic_tokens.md @@ -95,8 +95,8 @@ source file: | `{` and `}` | Struct literals, blocks of control flow statements, and the bodies of definitions (classes, functions, etc.) | | `,` | Separate tuple and struct elements | | `.` | Member access | -| `:` | Name bindings | -| `:!` | Generic binding | +| `:` | Name binding patterns | +| `:!` | Compile-time binding patterns | | `;` | Statement separator | ## Alternatives considered diff --git a/docs/design/naming_conventions.md b/docs/design/naming_conventions.md index ab13049bd1c36..3d4d268202e88 100644 --- a/docs/design/naming_conventions.md +++ b/docs/design/naming_conventions.md @@ -41,7 +41,7 @@ In other words: | Types | `UpperCamelCase` | Resolved at compile-time. | | Functions | `UpperCamelCase` | Resolved at compile-time. | | Methods | `UpperCamelCase` | Methods, including virtual methods, are equivalent to functions. | -| Generic parameters | `UpperCamelCase` | May vary based on inputs, but are ultimately resolved at compile-time. | +| Compile-time parameters | `UpperCamelCase` | May vary based on inputs, but are ultimately resolved at compile-time. | | Compile-time constants | `UpperCamelCase` | Resolved at compile-time. See [constants](#constants) for more remarks. | | Variables | `lower_snake_case` | May be reassigned and thus require runtime information. | | Member variables | `lower_snake_case` | Behave like variables. | diff --git a/docs/design/type_inference.md b/docs/design/type_inference.md index 06067ad705a36..a8be98beb3d05 100644 --- a/docs/design/type_inference.md +++ b/docs/design/type_inference.md @@ -43,7 +43,8 @@ constant `IntLiteral(1)` value, whereas most languages would suggest a variable integer type, such as `i64`. Carbon might also make it an error. Although type inference currently only addresses `auto` for variables and function return types, this is something that will be considered as part of type inference in -general, because it also affects generics, templates, lambdas, and return types. +general, because it also affects checked generics, templates, lambdas, and +return types. ## Alternatives considered diff --git a/docs/design/values.md b/docs/design/values.md index d153d558df931..2df8e0d975754 100644 --- a/docs/design/values.md +++ b/docs/design/values.md @@ -338,7 +338,7 @@ that fit into a machine register, but also the efficiency of minimal copies when working with types where a copy would require extra allocations or other costly resources. This directly helps programmers by providing a simpler model to select the mechanism of passing function inputs. But it is also important to -enable generic code that needs a single type model that will have generically +enable generic code that needs a single type model that will have consistently good performance. When forming a value expression from a reference expression, Carbon @@ -591,7 +591,9 @@ storage. None of this impacts the type system and so an implementation can freely select specific strategies here based on concrete types without harming generic code. -There is always a generic fallback as well if monomorphization isn't desired. +Even if generics were to be implemented without monomorphization, for example +dynamic dispatch of object-safe interfaces, there is a conservatively correct +strategy that will work for any type. This freedom mirrors that of [input values](#value-expressions) where might be implemented as either a reference or a copy without breaking genericity. Here From f04f46860620cb6bdcf692d361691cedb752d9a0 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 6 Sep 2023 05:48:16 +0000 Subject: [PATCH 43/83] Checkpoint progress. --- docs/design/generics/details.md | 38 ++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index ccfd116408208..c8541fcb080b6 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -242,14 +242,14 @@ The syntax here is to match [how the same members would be defined in a type](/docs/design/classes.md#methods). Each declaration in the interface defines an [associated entity](terminology.md#associated-entity). In this example, `Vector` -has two associated methods, `Add` and `Scale`. +has two associated methods, `Add` and `Scale`. A type +[implements an interface](#implementing-interfaces) by providing definitions for +all the associated entities declared in the interface, An interface defines a [facet type](terminology.md#facet-type), that is a type whose values are [facets](terminology.md#facet). Every type implementing the -interface, and so has definitions for all the functions (and other members) -declared in the interface, has a corresponding facet value. So if the type -`Point` implements interface `Vector`, the facet value `Point as Vector` has -type `Vector`. +interface has a corresponding facet value. So if the type `Point` implements +interface `Vector`, the facet value `Point as Vector` has type `Vector`. ## Implementing interfaces @@ -274,10 +274,10 @@ class Point_Inline { // In this scope, the `Self` keyword is an // alias for `Point_Inline`. fn Add[self: Self](b: Self) -> Self { - return {.x = a.x + b.x, .y = a.y + b.y}; + return {.x = self.x + b.x, .y = self.y + b.y}; } fn Scale[self: Self](v: f64) -> Self { - return {.x = a.x * v, .y = a.y * v}; + return {.x = self.x * v, .y = self.y * v}; } } } @@ -294,10 +294,10 @@ class Point_Extend { var y: f64; extend impl as Vector { fn Add[self: Self](b: Self) -> Self { - return {.x = a.x + b.x, .y = a.y + b.y}; + return {.x = self.x + b.x, .y = self.y + b.y}; } fn Scale[self: Self](v: f64) -> Self { - return {.x = a.x * v, .y = a.y * v}; + return {.x = self.x * v, .y = self.y * v}; } } } @@ -324,8 +324,8 @@ entity affect a class' API, then that is mentioned with an `extend` declaration in the `class` definition. **Comparison with other languages:** Rust only defines implementations lexically -outside of the `class` definition. This Carbon approach means that a type's API -is described by declarations inside the `class` definition and doesn't change +outside of the `class` definition. Carbon's approach results in every type's API +is described by declarations inside its `class` definition and doesn't change afterwards. **References:** Carbon's interface implementation syntax was first defined in @@ -335,6 +335,8 @@ particular, see This syntax was changed to use `extend` in [proposal #2760: Consistent `class` and `interface` syntax](https://github.com/carbon-language/carbon-lang/pull/2760). +**FIXME: Left off here.** + ### Out-of-line `impl` An impl may also be defined after the type definition, by naming the type @@ -350,10 +352,10 @@ impl Point_OutOfLine as Vector { // In this scope, the `Self` keyword is an // alias for `Point_OutOfLine`. fn Add[self: Self](b: Self) -> Self { - return {.x = a.x + b.x, .y = a.y + b.y}; + return {.x = self.x + b.x, .y = self.y + b.y}; } fn Scale[self: Self](v: f64) -> Self { - return {.x = a.x * v, .y = a.y * v}; + return {.x = self.x * v, .y = self.y * v}; } } ``` @@ -437,10 +439,10 @@ class Point_ExtendForward { // Definition outside class definition does not. impl Point_ExtendForward as Vector { fn Add[self: Self](b: Self) -> Self { - return {.x = a.x + b.x, .y = a.y + b.y}; + return {.x = self.x + b.x, .y = self.y + b.y}; } fn Scale[self: Self](v: f64) -> Self { - return {.x = a.x * v, .y = a.y * v}; + return {.x = self.x * v, .y = self.y * v}; } } ``` @@ -534,7 +536,8 @@ class Point_ReuseMethodInImpl { // No `extend`, so other members of `Vector` are not // part of `Point_ReuseMethodInImpl`'s API. impl as Vector { - alias Add = Point4a.Add; // Syntax TBD + // Syntax TBD: + alias Add = Point_ReuseMethodInImpl.Add; fn Scale[self: Self](v: f64) -> Self { return {.x = self.x * v, .y = self.y * v}; } @@ -573,7 +576,8 @@ class Point_ReuseByOutOfLine { } impl Point_ReuseByOutOfLine as Vector { - alias Add = Point_ReuseByOutOfLine.Add; // Syntax TBD + // Syntax TBD: + alias Add = Point_ReuseByOutOfLine.Add; fn Scale[self: Self](v: f64) -> Self { return {.x = self.x * v, .y = self.y * v}; } From 28750c28e00d7ba7a85c447bab834f167489a8fe Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 6 Sep 2023 06:10:29 +0000 Subject: [PATCH 44/83] Checkpoint progress. --- docs/design/generics/details.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index c8541fcb080b6..9d259289cea34 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -335,8 +335,6 @@ particular, see This syntax was changed to use `extend` in [proposal #2760: Consistent `class` and `interface` syntax](https://github.com/carbon-language/carbon-lang/pull/2760). -**FIXME: Left off here.** - ### Out-of-line `impl` An impl may also be defined after the type definition, by naming the type @@ -651,8 +649,8 @@ interface being implemented: - Otherwise, if the type or interface is private but declared in an API file, then the `impl` must be declared in the same file so the existence of that `impl` is visible to all files in that library. -- Otherwise, the `impl` must be defined in the public API file of the library, - so it is visible in all places that might use it. +- Otherwise, the `impl` must be declared in the public API file of the + library, so it is visible in all places that might use it. No access control modifiers are allowed on `impl` declarations, an `impl` is always visible to the intersection of the visibility of all names used in the @@ -660,6 +658,8 @@ declaration of the `impl`. ## Checked generics +**FIXME: Left off here.** + Here is a function that can accept values of any type that has implemented the `Vector` interface: From 79f804d7bf6748230fab96e7cdb9dd998e37938e Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 6 Sep 2023 06:26:31 +0000 Subject: [PATCH 45/83] Add appendix links to README --- docs/design/generics/README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/design/generics/README.md b/docs/design/generics/README.md index fa5b2b2beb766..46c1bd1af9076 100644 --- a/docs/design/generics/README.md +++ b/docs/design/generics/README.md @@ -11,9 +11,14 @@ feature of Carbon: - [Overview](overview.md) - A high-level description of the generics design, with pointers to other design documents that dive deeper into individual - topics. + topics - [Goals](goals.md) - The motivation and principles guiding the design - direction. + direction - [Terminology](terminology.md) - A glossary establishing common terminology - for describing the design. -- [Detailed design](details.md) - In-depth description. + for describing the design +- [Detailed design](details.md) - In-depth description + - [Appendix: Witness tables](appendix-witness.md) - Describes an + implementation strategy for checked generics, and Carbon's rationale for + only using it for dynamic dispatch + - [Appendix: Coherence](appendix-coherence.md) - Describes the rationale + for Carbon's choice to have coherent generics, and the alternatives From 015feedf384c1bc72a81f630faa761f0d4df3ba1 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 6 Sep 2023 22:38:01 +0000 Subject: [PATCH 46/83] Checkpoint progress. --- docs/design/generics/details.md | 53 +++++++++++++++++---------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 9d259289cea34..6d594db54cba3 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -23,6 +23,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Qualified member names and compound member access](#qualified-member-names-and-compound-member-access) - [Access](#access) - [Checked generics](#checked-generics) + - [Symbolic facet bindings](#symbolic-facet-bindings) - [Return type](#return-type) - [Interfaces recap](#interfaces-recap) - [Facet types](#facet-types) @@ -658,8 +659,6 @@ declaration of the `impl`. ## Checked generics -**FIXME: Left off here.** - Here is a function that can accept values of any type that has implemented the `Vector` interface: @@ -683,9 +682,18 @@ _[checked](terminology.md#checked-versus-template-parameters) [generic parameter](terminology.md#generic-means-compile-time-parameterized)_. That means its value must be known to the caller at compile-time, but we will only use the information present in the signature of the function to type check -the body of `AddAndScaleGeneric`'s definition. In this case, we know that any -value of facet `T` implements the `Vector` interface and so has an `Add` and a -`Scale` method. +the body of `AddAndScaleGeneric`'s definition. + +### Symbolic facet bindings + +In our example, `T` is a facet which may be used in type position in the rest of +the function. Furthermore, since it omits the keyword `template` prefix, this is +a symbolic binding. so we need to be able to typecheck the body of the function +without knowing the specific value `T` from the caller. + +This typechecking is done by looking at the constraint on `T`. In the example, +the constraint on `T` says that every value of `T` implements the `Vector` +interface and so has a `Vector.Add` and a `Vector.Scale` method. Names are looked up in the body of `AddAndScaleGeneric` for values of type `T` in `Vector`. This means that `AddAndScaleGeneric` is interpreted as equivalent @@ -757,8 +765,8 @@ This is part of realizing [the goal that generic functions can be used in place of regular functions without changing the return type that callers see](goals.md#path-from-regular-functions). In this example, `AddAndScaleGeneric` can be substituted for `AddAndScaleForPoint_Extend` and `AddAndScaleForPoint_NoExtend` without -affecting the return types. This requires the return value to be converted to -the type that the caller expects instead of the erased type used inside the +affecting the return types. This may require a conversion of the return value to +the type that the caller expects, from the erased type used inside a checked-generic function. A checked-generic caller of a checked-generic function performs the same @@ -838,7 +846,7 @@ An interface's name may be used in a few different contexts: - as a namespace name in [a qualified name](#qualified-member-names-and-compound-member-access), and - as a [facet type](terminology.md#facet-type) for - [a facet binding](#checked-generics). + [a facet binding](#symbolic-facet-bindings). While interfaces are examples of facet types, facet types are a more general concept, for which interfaces are a building block. @@ -868,9 +876,10 @@ This recovers the original type for the facet, so `(Point_Inline as Vector) as type` is `Point_Inline` again. However, when a facet type like `Vector` is used as the binding type of a -symbolic binding, as in `T:! Vector`, the symbolic facet binding `T` is -disassociated with whatever facet value `T` is eventually bound to. Instead, `T` -is treated as an [archetype](terminology.md#archetype), with the members and +symbolic binding, as in `T:! Vector`, the +[symbolic facet binding](#symbolic-facet-bindings) `T` is disassociated with +whatever facet value `T` is eventually bound to. Instead, `T` is treated as an +[archetype](terminology.md#archetype), with the members and [member access](/docs/design/expressions/member_access.md) determined by the names of the facet type. @@ -887,9 +896,9 @@ same definition are equivalent even if they have different names. This is because types don't have to explicitly specify which named constraints they implement, types automatically implement any named constraints they can satisfy. -A named constraint definition can contain interface requirements using `impl` -declarations and names using `alias` declarations. Note that this allows us to -declare the aspects of a facet type directly. +A named constraint definition can contain interface requirements using +`require Self impls` declarations and names using `alias` declarations. Note +that this allows us to declare the aspects of a facet type directly. ``` constraint VectorLegoFish { @@ -903,9 +912,9 @@ constraint VectorLegoFish { } ``` -An `impl` requirement may alternatively be on a named constraint, instead of an -interface, to add all the requirements of another named constraint without -adding any of the names: +A `require Self impls` requirement may alternatively be on a named constraint, +instead of an interface, to add all the requirements of another named constraint +without adding any of the names: ``` constraint DrawVectorLegoFish { @@ -929,7 +938,7 @@ whenever an interface may be. This includes all of these [a qualified name](#qualified-member-names-and-compound-member-access). For example, `VectorLegoFish.VAdd` refers to the same name as `Vector.Add`. - A named constraint may be used as a [facet type](terminology.md#facet-type) - for [a facet binding](#checked-generics). + for [a facet binding](#symbolic-facet-bindings). We don't expect developers to directly define many named constraints, but other constructs we do expect them to use will be defined in terms of them. For @@ -954,13 +963,7 @@ var i: i32 = Identity(3); var s: String = Identity("string"); ``` -**Aside:** We can define `auto` as syntactic sugar for `(template _:! type)`. -This definition allows you to use `auto` as the type for a local variable whose -type can be statically determined by the compiler. It also allows you to use -`auto` as the type of a function parameter, to mean "accepts a value of any -type, and this function will be instantiated separately for every different -type." This is consistent with the -[use of `auto` in the C++20 Abbreviated function template feature](https://en.cppreference.com/w/cpp/language/function_template#Abbreviated_function_template). +**FIXME: Left off here** In general, the declarations in `constraint` definition match a subset of the declarations in an `interface`. Named constraints used with checked generics, as From d58134005dc7eb7108efd147016fdc19f4d43b85 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 6 Sep 2023 23:19:04 +0000 Subject: [PATCH 47/83] Checkpoint progress. --- docs/design/generics/details.md | 80 +++++++++++++++------------------ 1 file changed, 35 insertions(+), 45 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 6d594db54cba3..d5e0221974ea4 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -963,12 +963,10 @@ var i: i32 = Identity(3); var s: String = Identity("string"); ``` -**FIXME: Left off here** - In general, the declarations in `constraint` definition match a subset of the -declarations in an `interface`. Named constraints used with checked generics, as -opposed to templates, should only include required interfaces and aliases to -named members of those interfaces. +declarations in an `interface`. These named constraints can be used with checked +generics, as opposed to templates, and only include required interfaces and +aliases to named members of those interfaces. To declare a named constraint that includes other declarations for use with template parameters, use the `template` keyword before `constraint`. Method, @@ -977,9 +975,9 @@ inside a `template constraint`. Note that a checked-generic constraint ignores the names of members defined for a type, but a template constraint can depend on them. -There is an analogy between declarations used in a `constraint` and in an -`interface` definition. If an `interface` `I` has (non-`alias`) declarations -`X`, `Y`, and `Z`, like so: +There is an analogy between declarations used in a `template constraint` and in +an `interface` definition. If an `interface` `I` has (non-`alias`, +non-`require`) declarations `X`, `Y`, and `Z`, like so: ``` interface I { @@ -995,7 +993,7 @@ Then a type implementing `I` would have `impl as I` with definitions for `X`, ``` class ImplementsI { // ... - extend impl as I { + impl as I { X { ... } Y { ... } Z { ... } @@ -1003,11 +1001,10 @@ class ImplementsI { } ``` -But the corresponding `constraint` or `template constraint`, `S`: +But a `template constraint`, `S`: ``` -// or template constraint S { -constraint S { +template constraint S { X; Y; Z; @@ -1025,9 +1022,6 @@ class ImplementsS { } ``` -**TODO:** Move the `template constraint` and `auto` content to the template -design document, once it exists. - ### Subtyping between facet types There is a subtyping relationship between facet types that allows calls of one @@ -1109,8 +1103,7 @@ var s: Sprite = ...; PrintThenDraw(s); ``` -Any conflicting names between the two types are replaced with a name that is an -error to use. +It is an error to use any names that conflict between the two interfaces. ``` interface Renderable { @@ -1121,15 +1114,10 @@ interface EndOfGame { fn Draw[self: Self](); fn Winner[self: Self](player: i32); } -// `Renderable & EndOfGame` is syntactic sugar for this facet type: -constraint { - require Self impls Renderable; - require Self impls EndOfGame; - alias Center = Renderable.Center; - // Open question: `forbidden`, `invalid`, or something else? - forbidden Draw - message "Ambiguous, use either `(Renderable.Draw)` or `(EndOfGame.Draw)`."; - alias Winner = EndOfGame.Winner; +fn F[T:! Renderable & EndOfGame](x: T) { + // ❌ Error: Ambiguous, use either `(Renderable.Draw)` + // or `(EndOfGame.Draw)`. + x.Draw(); } ``` @@ -1148,18 +1136,15 @@ constraint RenderableAndEndOfGame { } fn RenderTieGame[T:! RenderableAndEndOfGame](x: T) { - // Calls Renderable.Draw() + // ✅ Calls `Renderable.Draw`: x.RenderableDraw(); - // Calls EndOfGame.Draw() + // ✅ Calls `EndOfGame.Draw`: x.TieGame(); } ``` -Reserving the name when there is a conflict is part of resolving what happens -when you combine more than two facet types. If `x` is forbidden in `A`, it is -forbidden in `A & B`, whether or not `B` defines the name `x`. This makes `&` -associative and commutative, and so it is well defined on sets of interfaces, or -other facet types, independent of order. +Note that `&` is associative and commutative, and so it is well defined on sets +of interfaces, or other facet types, independent of order. Note that we do _not_ consider two facet types using the same name to mean the same thing to be a conflict. For example, combining a facet type with itself @@ -1167,12 +1152,6 @@ gives itself, `MyTypeOfType & MyTypeOfType == MyTypeOfType`. Also, given two [interface extensions](#interface-extension) of a common base interface, the combination should not conflict on any names in the common base. -**Rejected alternative:** Instead of using `&` as the combining operator, we -considered using `+`, -[like Rust](https://rust-lang.github.io/rfcs/0087-trait-bounds-with-plus.html). -See [#531](https://github.com/carbon-language/carbon-lang/issues/531) for the -discussion. - **Future work:** We may want to define another operator on facet types for adding requirements to a facet type without affecting the names, and so avoid the possibility of name conflicts. Note this means the operation is not @@ -1210,9 +1189,13 @@ interface is enough to be able to use the operator to access the functionality. **Alternatives considered:** See [Carbon: Access to interface methods](https://docs.google.com/document/d/17IXDdu384x1t9RimQ01bhx4-nWzs4ZEeke4eO6ImQNc/edit?resourcekey=0-Fe44R-0DhQBlw0gs2ujNJA). -**Comparison with other languages:** This `&` operation on interfaces works very -similarly to Rust's `+` operation, with the main difference being how you +**Rejected alternative:** Instead of using `&` as the combining operator, we +considered using `+`, +[like Rust](https://rust-lang.github.io/rfcs/0087-trait-bounds-with-plus.html). +The main difference from Rust's `+` is how you [qualify names when there is a conflict](https://doc.rust-lang.org/rust-by-example/trait/disambiguating.html). +See [issue #531](https://github.com/carbon-language/carbon-lang/issues/531) for +the discussion. ## Interface requiring other interfaces @@ -1223,7 +1206,8 @@ requires all containers to also satisfy the requirements of `DefaultConstructible`, `CopyConstructible`, `EqualityComparable`, and `Swappable`. This is already a capability for [facet types in general](#facet-types). For consistency we will use the same -semantics and syntax as we do for [named constraints](#named-constraints): +semantics and `require Self impls` syntax as we do for +[named constraints](#named-constraints): ``` interface Equatable { fn Equals[self: Self](rhs: Self) -> bool; } @@ -1299,7 +1283,8 @@ benefits: place. - This reduces the boilerplate for types implementing `Hashable`. -We expect this concept to be common enough to warrant dedicated syntax: +We expect this concept to be common enough to warrant dedicated `interface` +syntax: ``` interface Equatable { fn Equals[self: Self](rhs: Self) -> bool; } @@ -1357,13 +1342,16 @@ interface SetAlgebra { body in parameters or constraints of the interface being extended. ``` -// A type can implement `ConvertibleTo` many times, using -// different values of `T`. +// A type can implement `ConvertibleTo` many times, +// using different values of `T`. interface ConvertibleTo(T:! type) { ... } // A type can only implement `PreferredConversion` once. interface PreferredConversion { let AssociatedFacet:! type; + // `extend` is in the body of an `interface` + // definition. This allows extending an expression + // that uses an associated facet. extend ConvertibleTo(AssociatedFacet); } ``` @@ -1561,6 +1549,8 @@ eventually be provided. declared lexically in the class scope in this case. That would allow earlier detection of missing definitions. +**FIXME: Left off here.** + ### Use case: overload resolution Implementing an extended interface is an example of a more specific match for From 9a7dcda3b08ae4f523cc3fba4be43b654e93ffd8 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 7 Sep 2023 04:02:25 +0000 Subject: [PATCH 48/83] Checkpoint progress. --- docs/design/README.md | 7 +++-- docs/design/generics/details.md | 49 ++++++++------------------------- 2 files changed, 15 insertions(+), 41 deletions(-) diff --git a/docs/design/README.md b/docs/design/README.md index 6329c5a38ec79..15c940e718115 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -1009,6 +1009,8 @@ patterns. > - [Pattern matching](pattern_matching.md) > - Proposal > [#162: Basic Syntax](https://github.com/carbon-language/carbon-lang/pull/162) +> - Proposal +> [#2188: Pattern matching syntax and semantics](https://github.com/carbon-language/carbon-lang/pull/2188) ### Binding patterns @@ -1588,14 +1590,13 @@ fn Foo() -> f32 { } ``` -> **Note:** This is provisional, no design for `match` statements has been -> through the proposal process yet. - > References: > > - [Pattern matching](pattern_matching.md) > - Question-for-leads issue > [#1283: how should pattern matching and implicit conversion interact?](https://github.com/carbon-language/carbon-lang/issues/1283) +> - Proposal +> [#2188: Pattern matching syntax and semantics](https://github.com/carbon-language/carbon-lang/pull/2188) ## User-defined types diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index d5e0221974ea4..5dd807a09fac2 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -34,7 +34,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Interface extension](#interface-extension) - [`extend` and `impl` with named constraints](#extend-and-impl-with-named-constraints) - [Diamond dependency issue](#diamond-dependency-issue) - - [Use case: overload resolution](#use-case-overload-resolution) + - [Use case: detecting unreachable matches](#use-case-detecting-unreachable-matches) - [Adapting types](#adapting-types) - [Adapter compatibility](#adapter-compatibility) - [Extending adapter](#extending-adapter) @@ -1549,45 +1549,18 @@ eventually be provided. declared lexically in the class scope in this case. That would allow earlier detection of missing definitions. -**FIXME: Left off here.** +### Use case: detecting unreachable matches -### Use case: overload resolution +If one interface extends another, that gives the information to the compiler +that the extending interface is implemented for a subset of types as the +extended interface. This can be used to detect unreachable code. -Implementing an extended interface is an example of a more specific match for -[lookup resolution](#lookup-resolution-and-specialization). For example, this -could be used to provide different implementations of an algorithm depending on -the capabilities of the iterator being passed in: - -``` -interface ForwardIntIterator { - fn Advance[addr self: Self*](); - fn Get[self: Self]() -> i32; -} -interface BidirectionalIntIterator { - extend ForwardIntIterator; - fn Back[addr self: Self*](); -} -interface RandomAccessIntIterator { - extend BidirectionalIntIterator; - fn Skip[addr self: Self*](offset: i32); - fn Difference[self: Self](rhs: Self) -> i32; -} - -fn SearchInSortedList[IterT:! ForwardIntIterator] - (begin: IterT, end: IterT, needle: i32) -> bool { - ... // does linear search -} -// Will prefer the following overload when it matches -// since it is more specific. -fn SearchInSortedList[IterT:! RandomAccessIntIterator] - (begin: IterT, end: IterT, needle: i32) -> bool { - ... // does binary search -} -``` - -This would be an example of the more general rule that an interface `A` -requiring an implementation of interface `B` means `A` is more specific than -`B`. +For example, the [`impl` prioritization rule](#prioritization-rule) is used to +pick between `impl` declarations based on an explicit priority ordering given by +the user. If the broader interface is prioritized over the more specific +interface, the compiler can conclude that the more specific declaration will +never be selected and report an error. Similar situations could be detected in +function overloading. ## Adapting types From 818952ac93367fe161c615411d44ab3ca5d65fd3 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 7 Sep 2023 04:18:39 +0000 Subject: [PATCH 49/83] Checkpoint progress. --- docs/design/generics/details.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 5dd807a09fac2..0fbaff1cf35ad 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -1682,10 +1682,11 @@ var thriller_count: Optional(i32) = play_count.Find(Song("Thriller")); ``` -Since the `Find` function is a checked generic, it can only use the capabilities -that `HashMap` requires of `KeyT` and `ValueT`. This allows us to evaluate when -we can convert between two different arguments to a parameterized type. Consider -two adapters of `Song` that implement `Hashable`: +Since the `KeyT` and `ValueT` are symbolic parameters, the `Find` function is a +checked generic, and it can only use the capabilities of `KeyT` and `ValueT` +specified as requirements. This allows us to evaluate when we can convert +between two different arguments to a parameterized type. Consider two adapters +of `Song` that implement `Hashable`: ``` class PlayableSong { @@ -1743,6 +1744,13 @@ The resulting type `SongByArtist` would: - implement `Hashable`, but differently than `Song`, and - implement `Printable`, inherited from `Song`. +The rule is that when looking up if `SongByArtist` implements an interface `I` +and no implementation is found, the compiler repeats the search to see if `Song` +implements `I`. If that is found, it is reused if possible. The reuse will be +successful if all types that reference `Self` in the signatures of interface's +functions can be cast to the corresponding type with `SongByArtist` substituted +in for `Song`. + Unlike the similar `class B { extend base: A; }` notation, `class B { extend adapt A; }` is permitted even if `A` is a final class. Also, there is no implicit conversion from `B` to `A`, matching `adapt` without @@ -1770,6 +1778,8 @@ class SongRenderToPrintDriver { } ``` +**FIXME: Left off here.** + ### Use case: Using independent libraries together Imagine we have two packages that are developed independently. Package From 343d683c73d8b872fdda64efebcba55773eb7313 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 7 Sep 2023 22:17:24 +0000 Subject: [PATCH 50/83] Checkpoint progress. --- docs/design/generics/details.md | 128 ++++++++++++++++++++------------ 1 file changed, 82 insertions(+), 46 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 0fbaff1cf35ad..2bc21f8f2a0c3 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -42,7 +42,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Use case: Defining an impl for use by other types](#use-case-defining-an-impl-for-use-by-other-types) - [Use case: Private impl](#use-case-private-impl) - [Use case: Accessing interface names](#use-case-accessing-interface-names) - - [Adapter with stricter invariants](#adapter-with-stricter-invariants) + - [Future work: Adapter with stricter invariants](#future-work-adapter-with-stricter-invariants) - [Associated constants](#associated-constants) - [Associated class functions](#associated-class-functions) - [Associated facets](#associated-facets) @@ -1778,8 +1778,6 @@ class SongRenderToPrintDriver { } ``` -**FIXME: Left off here.** - ### Use case: Using independent libraries together Imagine we have two packages that are developed independently. Package @@ -1859,12 +1857,12 @@ class ComparableFromDifference(T:! Difference) { } class IntWrapper { var x: i32; - extend impl as Difference { + impl as Difference { fn Sub[self: Self](rhs: Self) -> i32 { return left.x - right.x; } } - extend impl as Comparable = ComparableFromDifferenceFn(IntWrapper); + impl as Comparable = ComparableFromDifferenceFn(IntWrapper); } ``` @@ -1877,7 +1875,7 @@ class ComparableFromDifferenceFn adapt T; extend impl as Comparable { fn Less[self: Self](rhs: Self) -> bool { - return Difference(self, rhs) < 0; + return Difference(self as T, rhs as T) < 0; } } } @@ -1886,7 +1884,7 @@ class IntWrapper { fn Difference(left: Self, right: Self) { return left.x - right.x; } - extend impl as Comparable = + impl as Comparable = ComparableFromDifferenceFn(IntWrapper, Difference); } ``` @@ -1924,9 +1922,9 @@ class ByReal { } fn Complex64.CloserToOrigin[self: Self](them: Self) -> bool { - var me_mag: ByReal = self * self.Conj() as ByReal; + var self_mag: ByReal = self * self.Conj() as ByReal; var them_mag: ByReal = them * them.Conj() as ByReal; - return me_mag.Less(them_mag); + return self_mag.Less(them_mag); } ``` @@ -1966,7 +1964,20 @@ fn Render(w: Window) { } ``` -### Adapter with stricter invariants +**Note:** Another way to achieve this is to use a local symbolic facet constant: + +```carbon +fn Render(w: Window) { + let DrawInWindow:! Draw = Window; + let d: DrawInWindow = w as DrawInWindow; + d.SetPen(...); + d.SetFill(...); + d.DrawRectangle(...); + ... +} +``` + +### Future work: Adapter with stricter invariants **Future work:** Rust also uses the newtype idiom to create types with additional invariants or other information encoded in the type @@ -2037,17 +2048,21 @@ Assert(Point2D.N == 2); Assert(Point3D.N == 3); fn PrintPoint[PointT:! NSpacePoint](p: PointT) { - for (var i: i32 = 0; i < PointT.N; ++i) { + var i: i32 = 0 + while (i < PointT.N) { if (i > 0) { Print(", "); } Print(p.Get(i)); + ++i; } } fn ExtractPoint[PointT:! NSpacePoint]( p: PointT, dest: Array(f64, PointT.N)*) { - for (var i: i32 = 0; i < PointT.N; ++i) { + var i: i32 = 0; + while (i < PointT.N) { (*dest)[i] = p.Get(i); + ++i; } } ``` @@ -2055,8 +2070,8 @@ fn ExtractPoint[PointT:! NSpacePoint]( **Comparison with other languages:** This feature is also called [associated constants in Rust](https://doc.rust-lang.org/reference/items/associated-items.html#associated-constants). -**Aside:** In general, the use of `:!` here means these `let` declarations will -only have compile-time and not runtime storage associated with them. +**Aside:** The use of `:!` here means these `let` declarations will only have +compile-time and not runtime storage associated with them. ### Associated class functions @@ -2074,7 +2089,7 @@ class MySerializableType { extend impl as DeserializeFromString { fn Deserialize(serialized: String) -> Self { - return (.i = StringToInt(serialized)); + return {.i = StringToInt(serialized)}; } } } @@ -2096,14 +2111,14 @@ called [member functions](/docs/design/classes.md#member-functions). ## Associated facets -Associated facets are [associated entities](terminology.md#associated-entity) -that happen to have a [facet type](terminology.md#facet-type). These are -particularly interesting since they can be used in the signatures of associated -methods or functions, to allow the signatures of methods to vary from -implementation to implementation. We already have one example of this: the -`Self` type discussed [in the "Interfaces" section](#interfaces). For other -cases, we can say that the interface declares that each implementation will -provide a facet under a specific name. For example: +Associated facets are [associated constants](#associated-constants) that happen +to have a [facet type](terminology.md#facet-type). These are particularly +interesting since they can be used in the signatures of associated methods or +functions, to allow the signatures of methods to vary from implementation to +implementation. We already have one example of this: the `Self` type discussed +[in the "Interfaces" section](#interfaces). For other cases, we can say that the +interface declares that each implementation will provide a facet constant under +a specified name. For example: ``` interface StackAssociatedFacet { @@ -2160,11 +2175,11 @@ impl VeryLongTypeName as Add } ``` -**Alternatives considered:** See -[other syntax options considered in #731 for specifying associated facets](/proposals/p0731.md#syntax-for-associated-constants). -In particular, it was deemed that -[Swift's approach of inferring an associated facet from method signatures in the impl](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID190) -was unneeded complexity. +> **Alternatives considered:** See +> [other syntax options considered in #731 for specifying associated facets](/proposals/p0731.md#syntax-for-associated-constants). +> In particular, it was deemed that +> [Swift's approach of inferring an associated facet from method signatures in the impl](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID190) +> was unneeded complexity. The definition of the `StackAssociatedFacet` is sufficient for writing a checked-generic function that operates on anything implementing that interface, @@ -2180,12 +2195,14 @@ fn PeekAtTopOfStack[StackType:! StackAssociatedFacet](s: StackType*) ``` Inside the checked-generic function `PeekAtTopOfStack`, the `ElementType` -associated facet member of `StackType` is erased. This means +associated facet member of `StackType` is an +[archetype](terminology.md#archetype), like other +[symbolic facet bindings](#symbolic-facet-bindings). This means `StackType.ElementType` has the API dictated by the declaration of `ElementType` in the interface `StackAssociatedFacet`. Outside the checked-generic, associated facets have the concrete facet values -determined by impl lookup, rather than the erased version of that type used +determined by impl lookup, rather than the erased version of that facet used inside a checked-generic. ``` @@ -2235,10 +2252,10 @@ interface at most once. If instead you want a family of related interfaces, one per possible value of a type parameter, multiple of which could be implemented for a single type, you would use -[parameterized interfaces](terminology.md#interface-parameters-and-associated-constants). -To write a parameterized version of the stack interface, instead of using -associated constants, write a parameter list after the name of the interface -instead of the associated constant declaration: +[_parameterized interfaces_](terminology.md#interface-parameters-and-associated-constants), +also known as _generic interfaces_. To write a parameterized version of the +stack interface, instead of using associated constants, write a parameter list +after the name of the interface: ``` interface StackParameterized(ElementType:! type) { @@ -2293,14 +2310,31 @@ fn BrokenPeekAtTopOfStackParameterized ``` This error is because the compiler can not determine if `T` should be `Fruit` or -`Veggie` when passing in argument of type `Produce*`. The function's signature -would have to be changed so that the value for `T` could be determined from the -explicit parameters. +`Veggie` when passing in argument of type `Produce*`. Either `T` should be +replaced by a concrete type, like `Fruit`: ``` -fn PeekAtTopOfStackParameterized - [T:! type, StackType:! StackParameterized(T)] - (s: StackType*, _:! singleton_type_of(T)) -> T { ... } +fn PeekAtTopOfFruitStack + [StackType:! StackParameterized(Fruit)] + (s: StackType*) -> T { ... } + +var produce: Produce = ...; +var top_fruit: Fruit = + PeekAtTopOfFruitStack(&produce); +``` + +Or the value for `T` would be passed explicitly, using `where` constraints +described [in this section](#another-type-implements-parameterized-interface): + +``` +fn PeekAtTopOfStackParameterizedImpl + (T:! type, StackType:! StackParameterized(T), s: StackType*) -> T { + ... +} +fn PeekAtTopOfStackParameterized[StackType:! type] + (s: StackType*, T:! type where StackType is StackParameterized(T)) -> T { + return PeekAtTopOfStackParameterizedImpl(T, StackType, s); +} var produce: Produce = ...; var top_fruit: Fruit = @@ -2309,12 +2343,11 @@ var top_veggie: Veggie = PeekAtTopOfStackParameterized(&produce, Veggie); ``` -The pattern `_:! singleton_type_of(T)` is a placeholder syntax for an expression -that will only match `T`, until issue -[#578: Value patterns as function parameters](https://github.com/carbon-language/carbon-lang/issues/578) -is resolved. Using that pattern in the explicit parameter list allows us to make -`T` available earlier in the declaration so it can be passed as the argument to -the parameterized interface `StackParameterized`. +> **Note:** Alternative ways of declaraing `PeekAtTopOfStackParameterized` are +> described and discussed in +> [#578: Value patterns as function parameters](https://github.com/carbon-language/carbon-lang/issues/578). + +**FIXME: Left off here.** This approach is useful for the `ComparableTo(T)` interface, where a type might be comparable with multiple other types, and in fact interfaces for @@ -2343,6 +2376,9 @@ pattern syntax. This reflects these two properties of these parameters: dynamic values. - We allow either symbolic or template values to be passed in. +**Future work:** We might also allow `template` bindings for interface +parameters, once we have a use case. + **Note:** Interface parameters aren't required to be facets, but that is the vast majority of cases. As an example, if we had an interface that allowed a type to define how the tuple-member-read operator would work, the index of the From 1150a141ca9561ab5cf1f57df0ffef218dcce482 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 8 Sep 2023 17:44:13 +0000 Subject: [PATCH 51/83] Checkpoint progress. --- docs/design/README.md | 24 +++--- docs/design/classes.md | 4 +- docs/design/generics/details.md | 143 ++++++++++++++++---------------- docs/design/generics/goals.md | 8 +- 4 files changed, 89 insertions(+), 90 deletions(-) diff --git a/docs/design/README.md b/docs/design/README.md index 15c940e718115..3a7e62de6ae58 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -2850,9 +2850,8 @@ To include the members of the interface as direct members of the type, use the body of a class definition. Without `extend`, implementations don't have to be in the same library as the -type definition, subject to the orphan rule -([1](generics/details.md#impl-lookup), [2](generics/details.md#orphan-rule)) for -[coherence](generics/terminology.md#coherence). +type definition, subject to the [orphan rule](generics/details.md#orphan-rule) +for [coherence](generics/terminology.md#coherence). Interfaces and implementations may be [forward declared](generics/details.md#forward-declarations-and-cyclic-references) @@ -3538,18 +3537,14 @@ function. Carbon interfaces with no C++ equivalent, such as [`CommonTypeWith(U)`](#common-type), may be implemented for C++ types -out-of-line in Carbon code. To satisfy the orphan rule -([1](generics/details.md#impl-lookup), [2](generics/details.md#orphan-rule)), -each C++ library will have a corresponding Carbon wrapper library that must be -imported instead of the C++ library if the Carbon wrapper exists. **TODO:** -Perhaps it will automatically be imported, so a wrapper may be added without -requiring changes to importers? +out-of-line in Carbon code. To satisfy the +[orphan rule](generics/details.md#orphan-rule), each C++ library will have a +corresponding Carbon wrapper library that must be imported instead of the C++ +library if the Carbon wrapper exists. **TODO:** Perhaps it will automatically be +imported, so a wrapper may be added without requiring changes to importers? ### Templates -> **Note:** This is provisional, no design for this has been through the -> proposal process yet. - Carbon supports both [checked and template generics](#checked-and-template-parameters). This provides a migration path for C++ template code: @@ -3576,6 +3571,11 @@ toolchain. However, even when using Carbon's generated C++ headers for interop, we will include the ability where possible to use a Carbon generic from C++ as if it were a C++ template. +> References: +> +> - Proposal +> [#2200: Template generics](https://github.com/carbon-language/carbon-lang/pull/2200) + ### Standard types > **Note:** This is provisional, no design for this has been through the diff --git a/docs/design/classes.md b/docs/design/classes.md index cf9d557c2180d..c384acf1226ab 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -311,8 +311,8 @@ implemented in descendants. While it is typical for this case to be associated with single-level inheritance hierarchies, there are some cases where there is an interface at the root of a type hierarchy and polymorphic types as interior branches of the tree. The case -of generic interfaces extending or requiring other interface would also be -modeled by deeper inheritance hierarchies. +of interfaces extending or requiring other interface would also be modeled by +deeper inheritance hierarchies. An interface as base class needs to either have a virtual destructor or forbid deallocation. diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 2bc21f8f2a0c3..e1b412cd05dae 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -47,7 +47,6 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Associated class functions](#associated-class-functions) - [Associated facets](#associated-facets) - [Parameterized interfaces](#parameterized-interfaces) - - [Impl lookup](#impl-lookup) - [Parameterized named constraints](#parameterized-named-constraints) - [Where constraints](#where-constraints) - [Constraint use cases](#constraint-use-cases) @@ -386,7 +385,7 @@ unlike Swift and Rust. An out-of-line `impl` declaration is allowed to be defined in a different library from `Point_OutOfLine`, restricted by -[the coherence/orphan rules](#impl-lookup) that ensure that the implementation +[the coherence/orphan rules](#orphan-rule) that ensure that the implementation of an interface can't change based on imports. In particular, the `impl` declaration is allowed in the library defining the interface (`Vector` in this case) in addition to the library that defines the type (`Point_OutOfLine` here). @@ -2347,15 +2346,14 @@ var top_veggie: Veggie = > described and discussed in > [#578: Value patterns as function parameters](https://github.com/carbon-language/carbon-lang/issues/578). -**FIXME: Left off here.** - -This approach is useful for the `ComparableTo(T)` interface, where a type might -be comparable with multiple other types, and in fact interfaces for -[operator overloads](#operator-overloading) more generally. Example: +Parameterized interfaces are useful for +[operator overloads](#operator-overloading). For example, the `EqWith(T)` and +`OrderedWith(T)` interfaces have a parameter that allows type to be comparable +with multiple other types, as in: ``` -interface EquatableWith(T:! type) { - fn Equals[self: Self](rhs: T) -> bool; +interface EqWith(T:! type) { + fn Equal[self: Self](rhs: T) -> bool; ... } class Complex { @@ -2363,9 +2361,9 @@ class Complex { var imag: f64; // Can implement this interface more than once // as long as it has different arguments. - extend impl as EquatableWith(f64) { ... } - // Same as: impl as EquatableWith(Complex) { ... } - extend impl as EquatableWith(Self) { ... } + extend impl as EqWith(f64) { ... } + // Same as: impl as EqWith(Complex) { ... } + extend impl as EqWith(Self) { ... } } ``` @@ -2436,53 +2434,18 @@ that interface parameters are "inputs" since they _determine_ which `impl` to use, and associated constants are "outputs" since they are determined _by_ the `impl`, but play no role in selecting the `impl`. -### Impl lookup - -Let's say you have some interface `I(T, U(V))` being implemented for some type -`A(B(C(D), E))`. To satisfy the [orphan rule for coherence](#orphan-rule), that -`impl` must be defined in some library that must be imported in any code that -looks up whether that interface is implemented for that type. This requires that -`impl` is defined in the same library that defines the interface or one of the -names needed by the type. That is, the `impl` must be defined with one of `I`, -`T`, `U`, `V`, `A`, `B`, `C`, `D`, or `E`. We further require anything looking -up this `impl` to import the _definitions_ of all of those names. Seeing a -forward declaration of these names is insufficient, since you can presumably see -forward declarations without seeing an `impl` with the definition. This -accomplishes a few goals: - -- The compiler can check that there is only one definition of any `impl` that - is actually used, avoiding - [One Definition Rule (ODR)](https://en.wikipedia.org/wiki/One_Definition_Rule) - problems. -- Every attempt to use an `impl` will see the exact same `impl`, making the - interpretation and semantics of code consistent no matter its context, in - accordance with the - [low context-sensitivity principle](/docs/project/principles/low_context_sensitivity.md). -- Allowing the `impl` to be defined with either the interface or the type - addresses the - [expression problem](https://eli.thegreenplace.net/2016/the-expression-problem-and-its-solutions). - -Note that [the rules for specialization](#lookup-resolution-and-specialization) -do allow there to be more than one `impl` to be defined for a type, by -unambiguously picking one as most specific. - -**References:** Implementation coherence is -[defined in terminology](terminology.md#coherence), and is -[a goal for Carbon](goals.md#coherence). More detail can be found in -[this appendix with the rationale and alternatives considered](appendix-coherence.md). - ### Parameterized named constraints -We should also allow the [named constraint](#named-constraints) construct to -support parameters. Parameters would work the same way as for interfaces. +Carbon also allows the [named constraint](#named-constraints) construct to +support parameters. Those parameters work the same way as for interfaces. ## Where constraints -So far, we have restricted a generic facet parameter by saying it has to -implement an interface or a set of interfaces. There are a variety of other -constraints we would like to be able to express, such as applying restrictions -to its associated constants. This is done using the `where` operator that adds -constraints to a facet type. +So far, we have restricted a [symbolic facet binding](#symbolic-facet-bindings) +by saying it has to implement an interface or a set of interfaces. There are a +variety of other constraints we would like to be able to express, such as +applying restrictions to associated constants. This is done using the `where` +operator that adds constraints to a facet type. The where operator can be applied to a facet type in a declaration context: @@ -2518,21 +2481,23 @@ between different type variables, such as that a member of one is equal to the member of another. The `where` operator is not associative, so a type expression using multiple must use round parens `(`...`)` to specify grouping. -**Comparison with other languages:** Both Swift and Rust use `where` clauses on -declarations instead of in the expression syntax. These happen after the type -that is being constrained has been given a name and use that name to express the -constraint. +> **Comparison with other languages:** Both Swift and Rust use `where` clauses +> on declarations instead of in the expression syntax. These happen after the +> type that is being constrained has been given a name and use that name to +> express the constraint. +> +> Rust also supports +> [directly passing in the values for associated types](https://rust-lang.github.io/rfcs/0195-associated-items.html#constraining-associated-types) +> when using a trait as a constraint. This is helpful when specifying concrete +> types for all associated types in a trait in order to +> [make it object safe so it can be used to define a trait object type](https://rust-lang.github.io/rfcs/0195-associated-items.html#trait-objects). +> +> Rust is adding trait aliases +> ([RFC](https://github.com/rust-lang/rfcs/blob/master/text/1733-trait-alias.md), +> [tracking issue](https://github.com/rust-lang/rust/issues/41517)) to support +> naming some classes of constraints. -Rust also supports -[directly passing in the values for associated types](https://rust-lang.github.io/rfcs/0195-associated-items.html#constraining-associated-types) -when using a trait as a constraint. This is helpful when specifying concrete -types for all associated types in a trait in order to -[make it object safe so it can be used to define a trait object type](https://rust-lang.github.io/rfcs/0195-associated-items.html#trait-objects). - -Rust is adding trait aliases -([RFC](https://github.com/rust-lang/rfcs/blob/master/text/1733-trait-alias.md), -[tracking issue](https://github.com/rust-lang/rust/issues/41517)) to support -naming some classes of constraints. +**FIXME: Left off here.** ### Constraint use cases @@ -4085,6 +4050,39 @@ interface. This is achieved with the _orphan rule_. **Orphan rule:** Some name from the type structure of an `impl` declaration must be defined in the same library as the `impl`, that is some name must be _local_. +Let's say you have some interface `I(T, U(V))` being implemented for some type +`A(B(C(D), E))`. To satisfy the orphan rule for coherence, that `impl` must be +defined in some library that must be imported in any code that looks up whether +that interface is implemented for that type. This requires that `impl` is +defined in the same library that defines the interface or one of the names +needed by the type. That is, the `impl` must be defined with one of `I`, `T`, +`U`, `V`, `A`, `B`, `C`, `D`, or `E`. We further require anything looking up +this `impl` to import the _definitions_ of all of those names. Seeing a forward +declaration of these names is insufficient, since you can presumably see forward +declarations without seeing an `impl` with the definition. This accomplishes a +few goals: + +- The compiler can check that there is only one definition of any `impl` that + is actually used, avoiding + [One Definition Rule (ODR)](https://en.wikipedia.org/wiki/One_Definition_Rule) + problems. +- Every attempt to use an `impl` will see the exact same `impl`, making the + interpretation and semantics of code consistent no matter its context, in + accordance with the + [low context-sensitivity principle](/docs/project/principles/low_context_sensitivity.md). +- Allowing the `impl` to be defined with either the interface or the type + partially addresses the + [expression problem](https://eli.thegreenplace.net/2016/the-expression-problem-and-its-solutions). + +Note that [the rules for specialization](#lookup-resolution-and-specialization) +do allow there to be more than one `impl` to be defined for a type, by +unambiguously picking one as most specific. + +**References:** Implementation coherence is +[defined in terminology](terminology.md#coherence), and is +[a goal for Carbon generics](goals.md#coherence). More detail can be found in +[this appendix with the rationale and alternatives considered](appendix-coherence.md). + Only the implementing interface and types (self type and type parameters) in the type structure are relevant here; an interface mentioned in a constraint is not sufficient since it @@ -4663,10 +4661,11 @@ these rules: in the scope of the class definition, but member function bodies defined inline are processed [as if they appeared immediately after the end of the outermost enclosing class](/docs/project/principles/information_accumulation.md#exceptions). -- For [coherence](goals.md#coherence), we require that any impl that matches - an [impl lookup](#impl-lookup) query in the same file, must be declared - before the query. This can be done with a definition or a forward - declaration. +- For [coherence](goals.md#coherence), we require that any `impl` declaration + that matches an impl lookup query in the same file, must be declared before + the query. This can be done with a definition or a forward declaration. This + matches the + [information accumulation principle](/docs/project/principles/information_accumulation.md). ### Matching and agreeing diff --git a/docs/design/generics/goals.md b/docs/design/generics/goals.md index e848934b651cb..7fb7858bca8da 100644 --- a/docs/design/generics/goals.md +++ b/docs/design/generics/goals.md @@ -626,10 +626,10 @@ Checked generics don't need to provide full flexibility of C++ templates: - [Carbon templates](#templates) will cover those cases that don't fit inside checked generics, such as code that relies on compile-time duck typing. -- We won't allow a specialization of some generic interface for some - particular type to actually expose a _different_ interface, with different - methods or different types in method signatures. This would break modular - type checking. +- We won't allow a specialization of some interface for some particular type + to actually expose a _different_ interface, with different methods or + different types in method signatures. This would break modular type + checking. - [Template metaprogramming](https://en.wikipedia.org/wiki/Template_metaprogramming) will not be supported by Carbon checked generics. We expect to address those use cases with metaprogramming or [templates](#templates) in Carbon. From 927063c3843db57ae792dd52411a54864bcefd4e Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 8 Sep 2023 22:47:09 +0000 Subject: [PATCH 52/83] Checkpoint progress. --- docs/design/generics/details.md | 35 +++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index e1b412cd05dae..4d0b02da428d1 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -49,9 +49,13 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Parameterized interfaces](#parameterized-interfaces) - [Parameterized named constraints](#parameterized-named-constraints) - [Where constraints](#where-constraints) + - [Kinds of `where` constraints](#kinds-of-where-constraints) + - [Rewrite constraints](#rewrite-constraints) + - [Same-type constraints](#same-type-constraints) + - [`impls` constraints](#impls-constraints) - [Constraint use cases](#constraint-use-cases) - [Set an associated constant to a specific value](#set-an-associated-constant-to-a-specific-value) - - [Same type constraints](#same-type-constraints) + - [Same type constraints](#same-type-constraints-1) - [Set an associated facet to a specific value](#set-an-associated-facet-to-a-specific-value) - [Equal facet bindings](#equal-facet-bindings) - [Satisfying both facet types](#satisfying-both-facet-types) @@ -2497,7 +2501,25 @@ using multiple must use round parens `(`...`)` to specify grouping. > [tracking issue](https://github.com/rust-lang/rust/issues/41517)) to support > naming some classes of constraints. -**FIXME: Left off here.** +### Kinds of `where` constraints + +**FIXME: Write this.** + +#### Rewrite constraints + +**FIXME: Write this.** + +> **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. + +#### Same-type constraints + +**FIXME: Write this.** + +#### `impls` constraints + +**FIXME: Write this.** ### Constraint use cases @@ -2540,9 +2562,7 @@ 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. +**FIXME: Update this.** A constraint to say that two associated constants should have the same value without specifying what specific value they should have must use `==` instead of @@ -2555,6 +2575,9 @@ interface PointCloud { } ``` +**References:** The `=` and `==` options were last updated in +[proposal #2173](https://github.com/carbon-language/carbon-lang/pull/2173). + #### Same type constraints ##### Set an associated facet to a specific value @@ -2712,7 +2735,7 @@ fn SortContainer (container_to_sort: ContainerType*); ``` -In contrast to [a same type constraint](#same-type-constraints), this does not +In contrast to [a same type constraint](#same-type-constraints-1), this does not say what type `ElementType` exactly is, just that it must satisfy some facet type. From 6a0c51cf75eae49372e93dfacc2799ca75799703 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 8 Sep 2023 23:19:37 +0000 Subject: [PATCH 53/83] Checkpoint progress. --- docs/design/generics/details.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 4d0b02da428d1..ec896b21aedd6 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -2513,6 +2513,9 @@ using multiple must use round parens `(`...`)` to specify grouping. > clauses that write a boolean expression that evaluates to `true` when the > constraint is satisfied. +**References:** Rewrite constraints were introduced with +[proposal #2173](https://github.com/carbon-language/carbon-lang/pull/2173). + #### Same-type constraints **FIXME: Write this.** @@ -5966,6 +5969,7 @@ parameter, as opposed to an associated facet, as in `N:! u32 where ___ >= 2`. - [#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) - [#983: Generic details 7: final impls](https://github.com/carbon-language/carbon-lang/pull/983) +- [#989: Member access expressions](https://github.com/carbon-language/carbon-lang/pull/989) - [#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) - [#1084: Generics details 9: forward declarations](https://github.com/carbon-language/carbon-lang/pull/1084) @@ -5974,7 +5978,13 @@ parameter, as opposed to an associated facet, as in `N:! u32 where ___ >= 2`. - [#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) - [#2107: Clarify rules around `Self` and `.Self`](https://github.com/carbon-language/carbon-lang/pull/2107) +- [#2138: Checked and template generic terminology](https://github.com/carbon-language/carbon-lang/pull/2138) +- [#2173: Associated constant assignment versus equality](https://github.com/carbon-language/carbon-lang/pull/2173) +- [#2200: Template generics](https://github.com/carbon-language/carbon-lang/pull/2200) - [#2347: What can be done with an incomplete interface](https://github.com/carbon-language/carbon-lang/pull/2347) +- [#2360: Types are values of type `type`](https://github.com/carbon-language/carbon-lang/pull/2360) - [#2376: Constraints must use `Self`](https://github.com/carbon-language/carbon-lang/pull/2376) - [#2483: Replace keyword `is` with `impls`](https://github.com/carbon-language/carbon-lang/pull/2483) - [#2760: Consistent `class` and `interface` syntax](https://github.com/carbon-language/carbon-lang/pull/2760) +- [#2964: Expression phase terminology](https://github.com/carbon-language/carbon-lang/pull/2964) +- [#3162: Reduce ambiguity in terminology](https://github.com/carbon-language/carbon-lang/pull/3162) From 1c74755b852f2905dd343c5842bfac5827c2bb08 Mon Sep 17 00:00:00 2001 From: Josh L Date: Sat, 9 Sep 2023 00:15:59 +0000 Subject: [PATCH 54/83] Checkpoint progress. --- docs/design/generics/details.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index ec896b21aedd6..5fbe8462df5d2 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -50,12 +50,9 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Parameterized named constraints](#parameterized-named-constraints) - [Where constraints](#where-constraints) - [Kinds of `where` constraints](#kinds-of-where-constraints) - - [Rewrite constraints](#rewrite-constraints) - - [Same-type constraints](#same-type-constraints) - - [`impls` constraints](#impls-constraints) - [Constraint use cases](#constraint-use-cases) - [Set an associated constant to a specific value](#set-an-associated-constant-to-a-specific-value) - - [Same type constraints](#same-type-constraints-1) + - [Same type constraints](#same-type-constraints) - [Set an associated facet to a specific value](#set-an-associated-facet-to-a-specific-value) - [Equal facet bindings](#equal-facet-bindings) - [Satisfying both facet types](#satisfying-both-facet-types) @@ -2503,26 +2500,29 @@ using multiple must use round parens `(`...`)` to specify grouping. ### Kinds of `where` constraints -**FIXME: Write this.** +There are three kinds of `where` constraints, each of which uses a different +binary operator: -#### Rewrite constraints +- _Rewrite constraints_: `where`...`=`... +- _Same-type constraints_: `where`...`==`... +- _Implements constraints_: `where`...`impls`... -**FIXME: Write this.** +A rewrite constraint is written `where .A = B`, where `A` is the name of an +[associated constant](#associated-constants) which is rewritten to `B`. > **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. -**References:** Rewrite constraints were introduced with -[proposal #2173](https://github.com/carbon-language/carbon-lang/pull/2173). - -#### Same-type constraints +A same-type constraint is written `where X == Y`, where `X` and `Y` both name +facets. The constraint is that `X as type` must be the same as `Y as type`. -**FIXME: Write this.** +An implements constraint is written `where T impls C`, where `T` is a facet and +`C` is a facet type. The constraint is that `T` satisfies the requirements of +`C`. -#### `impls` constraints - -**FIXME: Write this.** +**References:** Rewrite and same-type constraints were defined in +[proposal #2173](https://github.com/carbon-language/carbon-lang/pull/2173). ### Constraint use cases @@ -2738,7 +2738,7 @@ fn SortContainer (container_to_sort: ContainerType*); ``` -In contrast to [a same type constraint](#same-type-constraints-1), this does not +In contrast to [a same type constraint](#same-type-constraints), this does not say what type `ElementType` exactly is, just that it must satisfy some facet type. From ef6ea394d193c6e5b9539466afc3b0004c75785b Mon Sep 17 00:00:00 2001 From: Josh L Date: Sun, 10 Sep 2023 20:19:49 +0000 Subject: [PATCH 55/83] Checkpoint progress. --- docs/design/generics/details.md | 184 +++++++++++++++++--------------- 1 file changed, 95 insertions(+), 89 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 5fbe8462df5d2..0ea8d6588b979 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -50,6 +50,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Parameterized named constraints](#parameterized-named-constraints) - [Where constraints](#where-constraints) - [Kinds of `where` constraints](#kinds-of-where-constraints) + - [Named constraint constants](#named-constraint-constants) - [Constraint use cases](#constraint-use-cases) - [Set an associated constant to a specific value](#set-an-associated-constant-to-a-specific-value) - [Same type constraints](#same-type-constraints) @@ -2446,7 +2447,7 @@ So far, we have restricted a [symbolic facet binding](#symbolic-facet-bindings) by saying it has to implement an interface or a set of interfaces. There are a variety of other constraints we would like to be able to express, such as applying restrictions to associated constants. This is done using the `where` -operator that adds constraints to a facet type. +operator that adds constraints to a [facet type](#facet-types). The where operator can be applied to a facet type in a declaration context: @@ -2498,6 +2499,9 @@ using multiple must use round parens `(`...`)` to specify grouping. > [tracking issue](https://github.com/rust-lang/rust/issues/41517)) to support > naming some classes of constraints. +**References:** `where` constraints were added in proposal +[#818: Constraints for generics (generics details 3)](https://github.com/carbon-language/carbon-lang/pull/818). + ### Kinds of `where` constraints There are three kinds of `where` constraints, each of which uses a different @@ -2510,19 +2514,59 @@ binary operator: A rewrite constraint is written `where .A = B`, where `A` is the name of an [associated constant](#associated-constants) which is rewritten to `B`. +The "dot followed by the name of a member" construct, like `.A`, is called a +_designator_. The name of the designator is looked up in the constraint, and +refers to the value of that member for whatever type is to satisfy this +constraint. + > **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. A same-type constraint is written `where X == Y`, where `X` and `Y` both name -facets. The constraint is that `X as type` must be the same as `Y as type`. +facets. The constraint is that `X as type` must be the same as `Y as type`. In +cases where a constraint may be written in either form, prefer a rewrite +constraint over a same-type constraint. An implements constraint is written `where T impls C`, where `T` is a facet and `C` is a facet type. The constraint is that `T` satisfies the requirements of `C`. -**References:** Rewrite and same-type constraints were defined in +**References:** The definition of rewrite and same-type constraints were [proposal #2173](https://github.com/carbon-language/carbon-lang/pull/2173). +Implements constraints switched using the `impls` keyword in +[proposal #2483](https://github.com/carbon-language/carbon-lang/pull/2483). + +### Named constraint constants + +A facet type with a `where` constraint, such as `C where `, can be +named two different ways: + +- Using `let template` as in: + + ```carbon + let template NameOfConstraint:! auto = C where ; + ``` + + or, since the type of a facet type is `type`: + + ```carbon + let template NameOfConstraint:! type = C where ; + ``` + +- Using a [named constraint](#named-constraints) with the `constraint` keyword + introducer: + + ```carbon + constraint NameOfConstraint { + extend C where ; + } + ``` + +Whichever approach is used, the result is `NameOfConstraint` is a compile-time +constant that is equivalent to `C where `. + +**FIXME: Left off here.** ### Constraint use cases @@ -2547,40 +2591,20 @@ interface Has2DPoint { } ``` -The "dot followed by the name of a member" construct, `.N` in the examples -above, is called a _designator_. A designator refers to the value of that member -for whatever type is to satisfy this constraint. - -To name such a constraint, you may use a `let template` or a `constraint` -declaration: - -``` -let template Point2DInterface:! auto = NSpacePoint where .N = 2; -constraint Point2DInterface { - extend NSpacePoint where .N = 2; -} -``` - This syntax is also used to specify the values of [associated constants](#associated-constants) when implementing an interface for a type. -**FIXME: Update this.** - A constraint to say that two associated constants should have the same value -without specifying what specific value they should have must use `==` instead of -`=`: +without specifying what specific value they should have also uses `=`: ``` interface PointCloud { let Dim:! i32; - let PointT:! NSpacePoint where .N == Dim; + let PointT:! NSpacePoint where .N = Dim; } ``` -**References:** The `=` and `==` options were last updated in -[proposal #2173](https://github.com/carbon-language/carbon-lang/pull/2173). - #### Same type constraints ##### Set an associated facet to a specific value @@ -2600,16 +2624,6 @@ fn SumIntStack[T:! Stack where .ElementType = i32](s: T*) -> i32 { } ``` -To name these sorts of constraints, we could use `let template` declarations or -`constraint` definitions: - -``` -let template IntStack:! auto = Stack where .ElementType = i32; -constraint IntStack { - extend Stack where .ElementType = i32; -} -``` - This syntax is also used to specify the values of [associated facets](#associated-facets) when implementing an interface for a type. @@ -2658,16 +2672,6 @@ interface HasEqualPair { } ``` -This kind of constraint can be named: - -``` -let template EqualPair:! auto = - PairInterface where .Left == .Right; -constraint EqualPair { - extend PairInterface where .Left == .Right; -} -``` - Another example of same type constraints is when associated facets of two different interfaces are constrained to be equal: @@ -2774,27 +2778,6 @@ fn F[ContainerType:! ContainerInterface (c: ContainerType); ``` -We would like to be able to name this constraint, defining a -`RandomAccessContainer` to be a facet type whose types satisfy -`ContainerInterface` with an `IteratorType` satisfying `RandomAccessIterator`. - -``` -let template RandomAccessContainer:! auto = - ContainerInterface where .IteratorType impls RandomAccessIterator; -// or -constraint RandomAccessContainer { - extend ContainerInterface - where .IteratorType impls RandomAccessIterator; -} - -// With the above definition: -fn F[ContainerType:! RandomAccessContainer](c: ContainerType); -// is equivalent to: -fn F[ContainerType:! ContainerInterface - where .IteratorType impls RandomAccessIterator] - (c: ContainerType); -``` - #### Combining constraints Constraints can be combined by separating constraint clauses with the `and` @@ -2873,23 +2856,25 @@ interface Container { } ``` -These recursive constraints can be named: +Note that [naming](#named-constraint-constants) a recursive constraint using +using the [`constraint` introducer](#named-constraints) approach, we can name +the implementing type using `Self` instead of `.Self`, since they refer to the +same type: ``` -let template RealAbs:! auto = HasAbs where .MagnitudeType == .Self; constraint RealAbs { extend HasAbs where .MagnitudeType == Self; + // Equivalent to: + extend HasAbs where .MagnitudeType == .Self; } -let template ContainerIsSlice:! auto = - Container where .SliceType == .Self; + constraint ContainerIsSlice { extend Container where .SliceType == Self; + // Equivalent to: + extend Container where .SliceType == .Self; } ``` -Note that using the `constraint` approach we can name these constraints using -`Self` instead of `.Self`, since they refer to the same type. - The `.Self` construct follows these rules: - `X :!` introduces `.Self:! type`, where references to `.Self` are resolved @@ -2901,6 +2886,7 @@ The `.Self` construct follows these rules: - You get the innermost, most-specific type for `.Self` if it is introduced twice in a scope. By the previous rule, it is only legal if they all refer to the same facet binding. +- `.Self` may not be on the left side of the `=` in a rewrite constraint. So in `X:! A where ...`, `.Self` is introduced twice, after the `:!` and the `where`. This is allowed since both times it means `X`. After the `:!`, `.Self` @@ -2960,8 +2946,8 @@ fn Double[T:! Mul where i32 impls As(.Self)](x: T) -> T { We don't allow a `where` constraint unless it applies a restriction to the current type. This means referring to some -[designator](#set-an-associated-constant-to-a-specific-value), like -`.MemberName`, or [`.Self`](#recursive-constraints). Examples: +[designator](#kinds-of-where-constraints), like `.MemberName`, or +[`.Self`](#recursive-constraints). Examples: - `Container where .ElementType = i32` - `type where Vector(.Self) impls Sortable` @@ -3371,23 +3357,45 @@ interfaces to symbolic facets, they may be added without breaking existing code. There are some constraints that we will naturally represent as named facet types. These can either be used directly to constrain a facet binding, or in a -`where ... impls ...` clause to constrain an associated facet. +`where ... impls ...` [implements constraint](#kinds-of-where-constraints) to +constrain an associated facet. The compiler determines which types implement these interfaces, developers can -not explicitly implement these interfaces for their own types. +not explicitly implement these interfaces for their own types. To support these, +we extend the requirements that facet types are allowed to include beyond +interfaces implemented and [`where` clauses](#where-constraints). **Open question:** Are these names part of the prelude or in a standard library? ### Is a derived class -Given a type `T`, `Extends(T)` is a facet type whose values are types that are -derived from `T`. That is, `Extends(T)` is the set of all types `U` that are -subtypes of `T`. +Given a type `T`, `Extends(T)` is a facet type whose values are facets that are +(transitively) [derived from](/docs/design/classes.md#inheritance) `T`. That is, +`U:! Extends(T)` means `U` has an `extend base: T;` declaration, or there is a +chain `extend base` declarations connecting `T` to `U`. ``` +base class BaseType { ... } + fn F[T:! Extends(BaseType)](p: T*); -fn UpCast[T:! type](p: T*, U:! type where T impls Extends(.Self)) -> U*; -fn DownCast[T:! type](p: T*, U:! Extends(T)) -> U*; +fn UpCast[U:! type] + (p: U*, V:! type where U impls Extends(.Self)) -> V*; +fn DownCast[X:! type](p: X*, Y:! Extends(X)) -> Y*; + +class DerivedType { + extend base: BaseType; +} +var d: DerivedType = {}; +// `T` is set to `DerivedType` +// `DerivedType impls Extends(BaseType)` +F(&d); + +// `U` is set to `DerivedType` +let p: BaseType* = UpCast(&d, BaseType); + +// `X` is set to `BaseType` +// `Y` is set to facet `DerivedType as Extends(BaseType)`. +Assert(DownCast(p, DerivedType) == &d); ``` **Open question:** Alternatively, we could define a new `extends` operator: @@ -3405,13 +3413,11 @@ fn DownCast[T:! type](p: T*, U:! type where .Self extends T) -> U*; Given a type `U`, define the facet type `CompatibleWith(U)` as follows: -> `CompatibleWith(U)` is a type whose values are types `T` such that `T` and `U` -> are [compatible](terminology.md#compatible-types). That is values of types `T` -> and `U` can be cast back and forth without any change in representation (for -> example `T` is an [adapter](#adapting-types) for `U`). - -To support this, we extend the requirements that facet types are allowed to have -to include a "data representation requirement" option. +> `CompatibleWith(U)` is a facet type whose values are facets `T` such that +> `T as type` and `U as type` are [compatible](terminology.md#compatible-types). +> That is values of `T` and `U` as types can be cast back and forth without any +> change in representation (for example `T` is an [adapter](#adapting-types) for +> `U`). `CompatibleWith` determines an equivalence relationship between types. Specifically, given two types `T1` and `T2`, they are equivalent if From 60485dcec7a2a3349647b76da1976b349b397741 Mon Sep 17 00:00:00 2001 From: Josh L Date: Mon, 11 Sep 2023 21:56:06 +0000 Subject: [PATCH 56/83] Big restructuring --- docs/design/generics/details.md | 1874 ++++++++++++++++++++++--------- docs/design/pattern_matching.md | 17 - 2 files changed, 1322 insertions(+), 569 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 0ea8d6588b979..b03568203774a 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -22,7 +22,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Avoiding name collisions](#avoiding-name-collisions) - [Qualified member names and compound member access](#qualified-member-names-and-compound-member-access) - [Access](#access) -- [Checked generics](#checked-generics) +- [Checked-generic functions](#checked-generic-functions) - [Symbolic facet bindings](#symbolic-facet-bindings) - [Return type](#return-type) - [Interfaces recap](#interfaces-recap) @@ -50,26 +50,34 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Parameterized named constraints](#parameterized-named-constraints) - [Where constraints](#where-constraints) - [Kinds of `where` constraints](#kinds-of-where-constraints) - - [Named constraint constants](#named-constraint-constants) - - [Constraint use cases](#constraint-use-cases) - - [Set an associated constant to a specific value](#set-an-associated-constant-to-a-specific-value) - - [Same type constraints](#same-type-constraints) - - [Set an associated facet to a specific value](#set-an-associated-facet-to-a-specific-value) - - [Equal facet bindings](#equal-facet-bindings) - - [Satisfying both facet types](#satisfying-both-facet-types) - - [Type bound for associated facet](#type-bound-for-associated-facet) - - [Type bounds on associated facets in declarations](#type-bounds-on-associated-facets-in-declarations) - - [Type bounds on associated facets in interfaces](#type-bounds-on-associated-facets-in-interfaces) + - [Rewrite constraints](#rewrite-constraints) + - [Combining constraints with `&`](#combining-constraints-with-) + - [Combining constraints with `and`](#combining-constraints-with-and) + - [Combining constraints with `extends`](#combining-constraints-with-extends) + - [Combining constraints with `impl as` and `is`](#combining-constraints-with-impl-as-and-is) + - [Constraint resolution](#constraint-resolution) + - [Precise rules and termination](#precise-rules-and-termination) + - [Qualified name lookup](#qualified-name-lookup) + - [Type substitution](#type-substitution) + - [Examples](#examples) + - [Termination](#termination) + - [Same-type constraints](#same-type-constraints) + - [Implementation of same-type `ImplicitAs`](#implementation-of-same-type-implicitas) + - [Manual type equality](#manual-type-equality) + - [Observe declarations](#observe-declarations) + - [`observe` declarations](#observe-declarations-1) + - [Implements constraints](#implements-constraints) + - [Implied constraints](#implied-constraints) - [Combining constraints](#combining-constraints) - [Recursive constraints](#recursive-constraints) + - [Satisfying both facet types](#satisfying-both-facet-types) + - [Constraints must use a designator](#constraints-must-use-a-designator) + - [Referencing names in the interface being defined](#referencing-names-in-the-interface-being-defined) + - [Constraint examples and use cases](#constraint-examples-and-use-cases) - [Parameterized type implements interface](#parameterized-type-implements-interface) - [Another type implements parameterized interface](#another-type-implements-parameterized-interface) - - [Constraints must use a designator](#constraints-must-use-a-designator) - - [Implied constraints](#implied-constraints) - [Must be legal type argument constraints](#must-be-legal-type-argument-constraints) - - [Referencing names in the interface being defined](#referencing-names-in-the-interface-being-defined) - - [Manual type equality](#manual-type-equality) - - [`observe` declarations](#observe-declarations) + - [Named constraint constants](#named-constraint-constants) - [Other constraints as facet types](#other-constraints-as-facet-types) - [Is a derived class](#is-a-derived-class) - [Type compatible with another type](#type-compatible-with-another-type) @@ -176,8 +184,8 @@ from and passed to various container methods. The function expresses that the type argument is passed in statically, basically generating a separate function body for every different type passed in, by using the "compile-time parameter" syntax `:!`. By default, this defines a -[checked-generics parameter](#checked-generics) below. In this case, the -interface contains enough information to +[checked-generics parameter](#checked-generic-functions) below. In this case, +the interface contains enough information to [type and definition check](terminology.md#complete-definition-checking) the function body -- you can only call functions defined in the interface in the function body. @@ -658,7 +666,7 @@ No access control modifiers are allowed on `impl` declarations, an `impl` is always visible to the intersection of the visibility of all names used in the declaration of the `impl`. -## Checked generics +## Checked-generic functions Here is a function that can accept values of any type that has implemented the `Vector` interface: @@ -675,8 +683,8 @@ _[compile-time binding](terminology.md#bindings)_. Here specifically it declares a _symbolic binding_ since it did not use the `template` keyword to mark it as a _template binding_. -**References:** The `:!` syntax was accepted in -[proposal #676](https://github.com/carbon-language/carbon-lang/pull/676). +> **References:** The `:!` syntax was accepted in +> [proposal #676](https://github.com/carbon-language/carbon-lang/pull/676). Since this symbolic binding pattern is in a function declaration, it marks a _[checked](terminology.md#checked-versus-template-parameters) @@ -685,6 +693,9 @@ That means its value must be known to the caller at compile-time, but we will only use the information present in the signature of the function to type check the body of `AddAndScaleGeneric`'s definition. +Note that types may also be given compile-time parameters, see the +["parameterized types" section](#parameterized-types). + ### Symbolic facet bindings In our example, `T` is a facet which may be used in type position in the rest of @@ -2133,8 +2144,8 @@ interface StackAssociatedFacet { Here we have an interface called `StackAssociatedFacet` which defines two methods, `Push` and `Pop`. The signatures of those two methods declare them as accepting or returning values with the type `ElementType`, which any implementer -of `StackAssociatedFacet` must also define. For example, maybe `DynamicArray` -implements `StackAssociatedFacet`: +of `StackAssociatedFacet` must also define. For example, maybe a `DynamicArray` +[parameterized type](#parameterized-types) implements `StackAssociatedFacet`: ``` class DynamicArray(T:! type) { @@ -2472,8 +2483,9 @@ interface A(T:! B where ...) { We also allow you to name constraints using a `where` operator in a `let` or `constraint` definition. The expressions that can follow the `where` keyword are -described in the ["constraint use cases"](#constraint-use-cases) section, but -generally look like boolean expressions that should evaluate to `true`. +described in the ["kinds of `where` constraints"](#kinds-of-where-constraints) +section, but generally look like boolean expressions that should evaluate to +`true`. The result of applying a `where` operator to a facet type is another facet type. Note that this expands the kinds of requirements that facet types can have from @@ -2526,7 +2538,9 @@ constraint. A same-type constraint is written `where X == Y`, where `X` and `Y` both name facets. The constraint is that `X as type` must be the same as `Y as type`. In cases where a constraint may be written in either form, prefer a rewrite -constraint over a same-type constraint. +constraint over a same-type constraint. Note that switching between the two +forms does not change which types satisfies the constraint, and so is a +compatible change for callers. An implements constraint is written `where T impls C`, where `T` is a facet and `C` is a facet type. The constraint is that `T` satisfies the requirements of @@ -2537,459 +2551,1042 @@ An implements constraint is written `where T impls C`, where `T` is a facet and Implements constraints switched using the `impls` keyword in [proposal #2483](https://github.com/carbon-language/carbon-lang/pull/2483). -### Named constraint constants - -A facet type with a `where` constraint, such as `C where `, can be -named two different ways: - -- Using `let template` as in: - - ```carbon - let template NameOfConstraint:! auto = C where ; - ``` - - or, since the type of a facet type is `type`: - - ```carbon - let template NameOfConstraint:! type = C where ; - ``` - -- Using a [named constraint](#named-constraints) with the `constraint` keyword - introducer: - - ```carbon - constraint NameOfConstraint { - extend C where ; - } - ``` - -Whichever approach is used, the result is `NameOfConstraint` is a compile-time -constant that is equivalent to `C where `. - **FIXME: Left off here.** -### Constraint use cases +#### Rewrite constraints -#### Set an associated constant to a specific value - -We might need to write a function that only works with a specific value of an -[associated constant](#associated-constants) `N`. In this case, the name of the -associated constant is written after a `.`, followed by an `=`, and then the -value: +In a rewrite constraint, the left-hand operand of `=` must be a `.` followed by +the name of an associated constant. `.Self` is not permitted. ``` -fn PrintPoint2D[PointT:! NSpacePoint where .N = 2](p: PointT) { - Print(p.Get(0), ", ", p.Get(1)); +interface RewriteSelf { + // ❌ Error: `.Self` is not the name of an associated constant. + let Me:! type where .Self = Self; +} +interface HasAssoc { + let Assoc:! type; +} +interface RewriteSingleLevel { + // ✅ Uses of `A.Assoc` will be rewritten to `i32`. + let A:! HasAssoc where .Assoc = i32; +} +interface RewriteMultiLevel { + // ❌ Error: Only one level of associated constant is permitted. + let B:! RewriteSingleLevel where .A.Assoc = i32; } ``` -Similarly in an interface definition: +This notation is permitted anywhere a constraint can be written, and results in +a new constraint with a different interface: the named member effectively no +longer names an associated constant of the constrained type, and is instead +treated as a rewrite rule that expands to the right-hand side of the constraint, +with any mentioned parameters substituted into that type. ``` -interface Has2DPoint { - let PointT:! NSpacePoint where .N = 2; +interface Container { + let Element:! type; + let Slice:! Container where .Element = Element; + fn Add[addr me: Self*](x: Element); +} +// `T.Slice.Element` rewritten to `T.Element` +// because type of `T.Slice` says `.Element = Element`. +// `T.Element` rewritten to `i32` +// because type of `T` says `.Element = i32`. +fn Add[T:! Container where .Element = i32](p: T*, y: T.Slice.Element) { + // ✅ Argument `y` has the same type `i32` as parameter `x` of + // `T.(Container.Add)`, which is also rewritten to `i32`. + p->Add(y); +} +``` + +Rewrites aren't performed on the left-hand side of such an `=`, so +`where .A = .B and .A = C` is not rewritten to `where .A = .B and .B = C`. +Instead, such a `where` clause is invalid when the constraint is +[resolved](#constraint-resolution) unless each rule for `.A` specifies the same +rewrite. + +Note that `T:! C where .R = i32` can result in a type `T.R` whose behavior is +different from the behavior of `T.R` given `T:! C`. For example, member lookup +into `T.R` can find different results and operations can therefore have +different behavior. However, this does not violate coherence because the facet +types `C` and `C where .R = i32` don't differ by merely having more type +information; rather, they are different facet types that have an isomorphic set +of values, somewhat like `i32` and `u32`. An `=` constraint is not merely +learning a new fact about a type, it is requesting different behavior. + +This approach addresses a few problems we have with same-type constraints: + +- [Equal types with different interfaces](/proposals/p2173.md#equal-types-with-different-interfaces) +- [Type canonicalization](/proposals/p2173.md#type-canonicalization) +- [Transitivity of equality](/proposals/p2173.md#transitivity-of-equality) + +##### Combining constraints with `&` + +Suppose we have `X = C where .R = A` and `Y = C where .R = B`. What should +`C & X` produce? What should `X & Y` produce? + +We could perhaps say that `X & Y` results in a facet type where the type of `R` +has the union of the interface of `A` and the interface of `B`, and that `C & X` +similarly results in a facet type where the type of `R` has the union of the +interface of `A` and the interface originally specified by `C`. However, this +proposal suggests a simpler rule: + +- Combining two rewrite rules with different rewrite targets results in a + facet type where the associated constant is ambiguous. Given `T:! X & Y`, + the type expression `T.R` is ambiguous between a rewrite to `A` and a + rewrite to `B`. But given `T:! X & X`, `T.R` is unambiguously rewritten to + `A`. +- Combining a constraint with a rewrite rule with a constraint with no rewrite + rule preserves the rewrite rule. For example, supposing that + `interface Container` extends `interface Iterable`, and `Iterable` has an + associated constant `Element`, the constraint + `Container & (Iterable where .Element = i32)` is the same as the constraint + `(Container & Iterable) where .Element = i32` which is the same as the + constraint `Container where .Element = i32`. + +If the rewrite for an associated constant is ambiguous, the facet type is +rejected during [constraint resolution](#constraint-resolution). + +##### Combining constraints with `and` + +It's possible for one `=` constraint in a `where` to refer to another. When this +happens, the facet type `C where A and B` is interpreted as +`(C where A) where B`, so rewrites in `A` are applied immediately to names in +`B`, but rewrites in `B` are not applied to names in `A` until the facet type is +[resolved](#constraint-resolution): + +``` +interface C { + let T:! type; + let U:! type; + let V:! type; +} +class M { + alias Me = Self; } +// ✅ Same as `C where .T = M and .U = M.Me`, which is +// the same as `C where .T = M and .U = M`. +fn F[A:! C where .T = M and .U = .T.Me]() {} +// ❌ No member `Me` in `A.T:! type`. +fn F[A:! C where .U = .T.Me and .T = M]() {} ``` -This syntax is also used to specify the values of -[associated constants](#associated-constants) when implementing an interface for -a type. +##### Combining constraints with `extends` -A constraint to say that two associated constants should have the same value -without specifying what specific value they should have also uses `=`: +Within an interface or named constraint, `extends` can be used to extend a +constraint that has rewrites. ``` -interface PointCloud { - let Dim:! i32; - let PointT:! NSpacePoint where .N = Dim; +interface A { + let T:! type; + let U:! type; } -``` +interface B { + extends A where .T = .U and .U = i32; +} + +var n: i32; -#### Same type constraints +// ✅ Resolved constraint on `T` is +// `B where .(A.T) = i32 and .(A.U) = i32`. +// `T.(A.T)` is rewritten to `i32`. +fn F(T:! B) -> T.(A.T) { return n; } +``` -##### Set an associated facet to a specific value +##### Combining constraints with `impl as` and `is` -Functions accepting a symbolic facet parameter might also want to constrain one -of its associated facets to be a specific, concrete type. For example, we might -want to have a function only accept stacks containing integers: +Within an interface or named constraint, the `impl T as C` syntax does not +permit `=` constraints to be specified directly. However, such constraints can +be specified indirectly, for example if `C` is a named constraint that contains +rewrites. Because these constraints don't change the type of `T`, rewrite +constraints in this context will never result in a rewrite being performed, and +instead are equivalent to `==` constraints. ``` -fn SumIntStack[T:! Stack where .ElementType = i32](s: T*) -> i32 { - var sum: i32 = 0; - while (!s->IsEmpty()) { - // s->Pop() has type `T.ElementType` == i32: - sum += s->Pop(); - } - return sum; +interface A { + let T:! type; + let U:! type; +} +constraint C { + extends A where .T = .U and .U = i32; } +constraint D { + extends A where .T == .U and .U == i32; +} +interface B { + // OK, equivalent to `impl as D;` or + // `impl as A where .T == .U and .U == i32;`. + impl as C; +} + +var n: i32; + +// ❌ No implicit conversion from `i32` to `T.(A.T)`. +// Resolved constraint on `T` is +// `B where T.(A.T) == T.(A.U) and T.(A.U) = i32`. +// `T.(A.T)` is single-step equal to `T.(A.U)`, and +// `T.(A.U)` is single-step equal to `i32`, but +// `T.(A.T)` is not single-step equal to `i32`. +fn F(T:! B) -> T.(A.T) { return n; } ``` -This syntax is also used to specify the values of -[associated facets](#associated-facets) when implementing an interface for a -type. +A purely syntactic check is used to determine if an `=` is specified directly in +an expression: -##### Equal facet bindings +- An `=` constraint is specified directly in its enclosing `where` expression. +- If an `=` constraint is specified directly in an operand of an `&` or + `(`...`)`, then it is specified directly in that enclosing expression. -Alternatively, two [facet bindings](terminology.md#facet-binding) could be -constrained to be equal to each other, without specifying what the facet value -is. This uses `==` instead of `=`. For example, we could make the `ElementType` -of an `Iterator` interface equal to the `ElementType` of a `Container` interface -as follows: +In an `impl as C` or `impl T as C` declaration in an interface or named +constraint, `C` is not allowed to directly specify any `=` constraints: ``` -interface Iterator { - let ElementType:! type; - ... -} -interface Container { - let ElementType:! type; - let IteratorType:! Iterator where .ElementType == ElementType; - ... +// Compile-time identity function. +fn Identity[T:! type](x:! T) -> T { return x; } + +interface E { + // ❌ Rewrite constraint specified directly. + impl as A where .T = .U and .U = i32; + // ❌ Rewrite constraint specified directly. + impl as type & (A where .T = .U and .U = i32); + // ✅ Not specified directly, but does not result + // in any rewrites being performed. + impl as Identity(A where .T = .U and .U = i32); } ``` -Given an interface with two associated facets +The same rules apply to `is` constraints. Note that `.T == U` constraints are +also not allowed in this context, because the reference to `.T` is rewritten to +`.Self.T`, and `.Self` is ambiguous. ``` -interface PairInterface { - let Left:! type; - let Right:! type; -} -``` +// ❌ Rewrite constraint specified directly in `is`. +fn F[T:! A where .U is (A where .T = i32)](); -we can constrain them to be equal in a function signature: +// ❌ Reference to `.T` in same-type constraint is ambiguous: +// does this mean the outer or inner `.Self.T`? +fn G[T:! A where .U is (A where .T == i32)](); +// ✅ Not specified directly, but does not result +// in any rewrites being performed. Return type +// is not rewritten to `i32`. +fn H[T:! type where .Self is C]() -> T.(A.U); + +// ✅ Return type is rewritten to `i32`. +fn I[T:! C]() -> T.(A.U); ``` -fn F[MatchedPairType:! PairInterface where .Left == .Right] - (x: MatchedPairType*); -``` -or in an interface definition: +##### Constraint resolution + +When a facet type is used as the declared type of a type `T`, the constraints +that were specified within that facet type are _resolved_ to determine the +constraints that apply to `T`. This happens: + +- When the constraint is used explicitly, when declaring a generic parameter + or associated constant of the form `T:! Constraint`. +- When declaring that a type implements a constraint with an `impl` + declaration, such as `impl T as Constraint`. Note that this does not include + `impl ... as` constraints appearing in `interface` or `constraint` + declarations. + +In each case, the following steps are performed to resolve the facet type's +abstract constraints into a set of constraints on `T`: + +- If multiple rewrites are specified for the same associated constant, they + are required to be identical, and duplicates are discarded. +- Rewrites are performed on other rewrites in order to find a fixed point, + where no rewrite applies within any other rewrite. If no fixed point exists, + the generic parameter declaration or `impl` declaration is invalid. +- Rewrites are performed throughout the other constraints in the facet type -- + that is, in any `==` constraints and `is` constraints -- and the type + `.Self` is replaced by `T` throughout the constraint. ``` -interface HasEqualPair { - let P:! PairInterface where .Left == .Right; -} +// ✅ `.T` in `.U = .T` is rewritten to `i32` when initially +// forming the facet type. +// Nothing to do during constraint resolution. +fn InOrder[A:! C where .T = i32 and .U = .T]() {} +// ✅ Facet type has `.T = .U` before constraint resolution. +// That rewrite is resolved to `.T = i32`. +fn Reordered[A:! C where .T = .U and .U = i32]() {} +// ✅ Facet type has `.U = .T` before constraint resolution. +// That rewrite is resolved to `.U = i32`. +fn ReorderedIndirect[A:! (C where .T = i32) & (C where .U = .T)]() {} +// ❌ Constraint resolution fails because +// no fixed point of rewrites exists. +fn Cycle[A:! C where .T = .U and .U = .T]() {} ``` -Another example of same type constraints is when associated facets of two -different interfaces are constrained to be equal: +To find a fixed point, we can perform rewrites on other rewrites, cycling +between them until they don't change or until a rewrite would apply to itself. +In the latter case, we have found a cycle and can reject the constraint. Note +that it's not sufficient to expand only a single rewrite until we hit this +condition: ``` -fn Map[CT:! Container, - FT:! Function where .InputType == CT.ElementType] - (c: CT, f: FT) -> Vector(FT.OutputType); +// ❌ Constraint resolution fails because +// no fixed point of rewrites exists. +// If we only expand the right-hand side of `.T`, +// we find `.U`, then `.U*`, then `.U**`, and so on, +// and never detect a cycle. +// If we alternate between them, we find +// `.T = .U*`, then `.U = .U**`, then `.V = .U***`, +// then `.T = .U**`, then detect that the `.U` rewrite +// would apply to itself. +fn IndirectCycle[A:! C where .T = .U and .U = .V* and .V = .U*](); ``` -###### Satisfying both facet types +After constraint resolution, no references to rewritten associated constants +remain in the constraints on `T`. -If the two types being constrained to be equal have been declared with different -facet types, then the actual type value they are set to will have to satisfy -both constraints. For example, if `SortedContainer.ElementType` is declared to -be `Comparable`, then in this declaration: +If a facet type is never used to constrain a type, it is never subject to +constraint resolution, and it is possible for a facet type to be formed for +which constraint resolution would always fail. For example: ``` -fn Contains - [SC:! SortedContainer, - CT:! Container where .ElementType == SC.ElementType] - (haystack: SC, needles: CT) -> bool; +package Broken api; + +interface X { + let T:! type; + let U:! type; +} +let Bad:! auto = (X where .T = .U) & (X where .U = .T); +// Bad is not used here. ``` -the `where` constraint means `CT.ElementType` must satisfy `Comparable` as well. -However, inside the body of `Contains`, `CT.ElementType` will act like the -implementation of `Comparable` is declared without [`extend`](#extend-impl). -That is, items from the `needles` container won't directly have a `Compare` -method member, but can still be implicitly converted to `Comparable` and can -still call `Compare` using the compound member access syntax, -`needle.(Comparable.Compare)(elt)`. The rule is that an `==` `where` constraint -between two type variables does not modify the set of member names of either -type. (If you write `where .ElementType = String` with a `=` and a concrete -type, then `.ElementType` is actually set to `String` including the complete -`String` API.) +In such cases, the facet type `Broken.Bad` is not usable: any attempt to use +that facet type to constrain a type would perform constraint resolution, which +would always fail because it would discover a cycle between the rewrites for +`.T` and for `.U`. In order to ensure that such cases are diagnosed, a trial +constraint resolution is performed for all facet types. Note that this trial +resolution can be skipped for facet types that are actually used, which is the +common case. -Note that `==` constraints are symmetric, so the previous declaration of -`Contains` is equivalent to an alternative declaration where `CT` is declared -first and the `where` clause is attached to `SortedContainer`: +##### Precise rules and termination -``` -fn Contains - [CT:! Container, - SC:! SortedContainer where .ElementType == CT.ElementType] - (haystack: SC, needles: CT) -> bool; -``` +This section explains the detailed rules used to implement rewrites. There are +two properties we aim to satisfy: -#### Type bound for associated facet +1. After type-checking, no symbolic references to associated constants that + have an associated rewrite rule remain. +2. Type-checking always terminates in a reasonable amount of time, ideally + linear in the size of the problem. -A `where` clause can express that a facet binding must implement an interface. -This is more flexible than the usual approach of including that interface in the -type since it can be applied to associated facet members as well. +Rewrites are applied in two different phases of program analysis. -##### Type bounds on associated facets in declarations +- During qualified name lookup and type checking for qualified member access, + if a rewritten member is looked up, the right-hand side's value and type are + used for subsequent checks. +- During substitution, if the symbolic name of an associated constant is + substituted into, and substitution into the left-hand side results in a + value with a rewrite for that constant, that rewrite is applied. -In the following example, normally the `ElementType` of a `Container` can be any -type. The `SortContainer` function, however, takes a pointer to a type -satisfying `Container` with the additional constraint that its `ElementType` -must satisfy the `Comparable` interface, using an `impls` constraint: +In each case, we always rewrite to a value that satisfies property 1 above, and +these two steps are the only places where we might form a symbolic reference to +an associated cosntant, so property 1 is recursively satisfied. Moreover, we +apply only one rewrite in each of the above cases, satisfying property 2. + +###### Qualified name lookup + +Qualified name lookup into either a type parameter or into an expression whose +type is a symbolic type `T` -- either a type parameter or an associated type -- +considers names from the facet type `C` of `T`, that is, from `T`’s declared +type. ``` -interface Container { - let ElementType:! type; - ... +interface C { + let M:! i32; + let U:! C; +} +fn F[T:! C](x: T) { + // Value is C.M in all four of these + let a: i32 = x.M; + let b: i32 = T.M; + let c: i32 = x.U.M; + let d: i32 = T.U.M; } +``` + +When looking up the name `N`, if `C` is an interface `I` and `N` is the name of +an associated constant in that interface, the result is a symbolic value +representing "the member `N` of `I`". If `C` is formed by combining interfaces +with `&`, all such results are required to find the same associated constant, +otherwise we reject for ambiguity. + +If a member of a class or interface is named in a qualified name lookup, the +type of the result is determined by performing a substitution. For an interface, +`Self` is substituted for the self type, and any parameters for that class or +interface (and enclosing classes or interfaces, if any) are substituted into the +declared type. -fn SortContainer - [ContainerType:! Container where .ElementType impls Comparable] - (container_to_sort: ContainerType*); ``` +interface SelfIface { + fn Get[me: Self]() -> Self; +} +class UsesSelf(T:! type) { + // Equivalent to `fn Make() -> UsesSelf(T)*;` + fn Make() -> Self*; + impl as SelfIface; +} -In contrast to [a same type constraint](#same-type-constraints), this does not -say what type `ElementType` exactly is, just that it must satisfy some facet -type. +// ✅ `T = i32` is substituted into the type of `UsesSelf(T).Make`, +// so the type of `UsesSelf(i32).Make` is `fn () -> UsesSelf(i32)*`. +let x: UsesSelf(i32)* = UsesSelf(i32).Make(); -**Note:** `Container` defines `ElementType` as having type `type`, but -`ContainerType.ElementType` has type `Comparable`. This is because -`ContainerType` has type `Container where .ElementType impls Comparable`, not -`Container`. This means we need to be a bit careful when talking about the type -of `ContainerType` when there is a `where` clause modifying it. +// ✅ `Self = UsesSelf(i32)` is substituted into the type +// of `SelfIface.Get`, so the type of `UsesSelf(i32).(SelfIface.Get)` +// is `fn [me: UsesSelf(i32)]() -> UsesSelf(i32)`. +let y: UsesSelf(i32) = x->Get(); +``` -##### Type bounds on associated facets in interfaces +None of this is new in this proposal. This proposal adds the rule: -Given these definitions (omitting `ElementType` for brevity): +If a facet type `C` into which lookup is performed includes a `where` clause +saying `.N = U`, and the result of qualified name lookup is the associated +constant `N`, that result is replaced by `U`, and the type of the result is the +type of `U`. No substitution is performed in this step, not even a `Self` +substitution -- any necessary substitutions were already performed when forming +the facet type `C`, and we don’t consider either the declared type or value of +the associated constant at all for this kind of constraint. Going through an +example in detail: ``` -interface IteratorInterface { ... } -interface ContainerInterface { - let IteratorType:! IteratorInterface; - ... +interface A { + let T:! type; } -interface RandomAccessIterator { - extend IteratorInterface; - ... +interface B { + let U:! type; + // More explicitly, this is of type `A where .(A.T) = Self.(B.U)` + let V:! A where .T = U; +} +// Type of T is B. +fn F[T:! B](x: T) { + // The type of the expression `T` is `B`. + // `T.V` finds `B.V` with type `A where .(A.T) = Self.(B.U)`. + // We substitute `Self` = `T` giving the type of `u` as + // `A where .(A.T) = T.(B.U)`. + let u:! auto = T.V; + // The type of `u` is `A where .(A.T) = T.(B.U)`. + // Lookup for `u.T` resolves it to `u.(A.T)`. + // So the result of the qualified member access is `T.(B.U)`, + // and the type of `v` is the type of `T.(B.U)`, namely `type`. + // No substitution is performed in this step. + let v:! auto = u.T; } ``` -We can then define a function that only accepts types that implement -`ContainerInterface` where its `IteratorType` associated facet implements -`RandomAccessIterator`: +The more complex case of ``` -fn F[ContainerType:! ContainerInterface - where .IteratorType impls RandomAccessIterator] - (c: ContainerType); +fn F2[T:! B where .U = i32](x: T); ``` -#### Combining constraints +is discussed later. -Constraints can be combined by separating constraint clauses with the `and` -keyword. This example expresses a constraint that two associated facets are -equal and satisfy an interface: +###### Type substitution + +At various points during the type-checking of a Carbon program, we need to +substitute a set of (binding, value) pairs into a symbolic value. We saw an +example above: substituting `Self = T` into the type `A where .(A.T) = Self.U` +to produce the value `A where .(A.T) = T.U`. Another important case is the +substitution of inferred parameter values into the type of a function when +type-checking a function call: ``` -fn EqualContainers - [CT1:! Container, - CT2:! Container where .ElementType impls HasEquality - and .ElementType == CT1.ElementType] - (c1: CT1*, c2: CT2*) -> bool; +fn F[T:! C](x: T) -> T; +fn G(n: i32) -> i32 { + // Deduces T = i32, which is substituted + // into the type `fn (x: T) -> T` to produce + // `fn (x: i32) -> i32`, giving `i32` as the type + // of the call expression. + return F(n); +} ``` -**Comparison with other languages:** Swift and Rust use commas `,` to separate -constraint clauses, but that only works because they place the `where` in a -different position in a declaration. In Carbon, the `where` is attached to a -type in a parameter list that is already using commas to separate parameters. - -#### Recursive constraints - -We sometimes need to constrain a type to equal one of its associated facets. In -this first example, we want to represent the function `Abs` which will return -`Self` for some but not all types, so we use an associated facet `MagnitudeType` -to encode the return type: +Qualified name lookup is not re-done as a result of type substitution. For a +template, we imagine there’s a completely separate step that happens before type +substitution, where qualified name lookup is redone based on the actual value of +template arguments; this proceeds as described in the previous section. +Otherwise, we performed the qualified name lookup when type-checking the +generic, and do not do it again: ``` -interface HasAbs { - extend Numeric; - let MagnitudeType:! Numeric; - fn Abs[self: Self]() -> MagnitudeType; +interface IfaceHasX { + let X:! type; +} +class ClassHasX { + class X {} +} +interface HasAssoc { + let Assoc:! IfaceHasX; } -``` -For types representing subsets of the real numbers, such as `i32` or `f32`, the -`MagnitudeType` will match `Self`, the type implementing an interface. For types -representing complex numbers, the types will be different. For example, the -`Abs()` applied to a `Complex64` value would produce a `f32` result. The goal is -to write a constraint to restrict to the first case. +// Qualified name lookup finds `T.(HasAssoc.Assoc).(IfaceHasX.X)`. +fn F(T:! HasAssoc) -> T.Assoc.X; -In a second example, when you take the slice of a type implementing `Container` -you get a type implementing `Container` which may or may not be the same type as -the original container type. However, taking the slice of a slice always gives -you the same type, and some functions want to only operate on containers whose -slice type is the same as the container type. +fn G(T:! HasAssoc where .Assoc = ClassHasX) { + // `T.Assoc` rewritten to `ClassHasX` by qualified name lookup. + // Names `ClassHasX.X`. + var a: T.Assoc.X = {}; + // Substitution produces `ClassHasX.(IfaceHasX.X)`, + // not `ClassHasX.X`. + var b: auto = F(T); +} +``` -To solve this problem, we think of `Self` as an actual associated facet member -of every interface. We can then address it using `.Self` in a `where` clause, -like any other associated facet member. +During substitution, we might find a member access that named an opaque symbolic +associated constant in the original value can now be resolved to some specific +value. It’s important that we perform this resolution: ``` -fn Relu[T:! HasAbs where .MagnitudeType == .Self](x: T) { - // T.MagnitudeType == T so the following is allowed: - return (x.Abs() + x) / 2; +interface A { + let T:! type; } -fn UseContainer[T:! Container where .SliceType == .Self](c: T) -> bool { - // T.SliceType == T so `c` and `c.Slice(...)` can be compared: - return c == c.Slice(...); +class K { fn Member(); } +fn H[U:! A](x: U) -> U.T; +fn J[V:! A where .T = K](y: V) { + // We need the interface of `H(y)` to include + // `K.Member` in order for this lookup to succeed. + H(y).Member(); +} +``` + +The values being substituted into the symbolic value are themselves already +fully substituted and resolved, and in particular, satisfy property 1 given +above. + +Substitution proceeds by recursively rebuilding each symbolic value, bottom-up, +replacing each substituted binding with its value. Doing this naively will +propagate values like `i32` in the `F`/`G` case earlier in this section, but +will not propagate rewrite constants like in the `H`/`J` case. The reason is +that the `.T = K` constraint is in the _type_ of the substituted value, rather +than in the substituted value itself: deducing `T = i32` and converting `i32` to +the type `C` of `T` preserves the value `i32`, but deducing `U = V` and +converting `V` to the type `A` of `U` discards the rewrite constraint. + +In order to apply rewrites during substitution, we associate a set of rewrites +with each value produced by the recursive rebuilding procedure. This is somewhat +like having substitution track a refined facet type for the type of each value, +but we don’t need -- or want -- for the type to change during this process, only +for the rewrites to be properly applied. For a substituted binding, this set of +rewrites is the rewrites found on the type of the corresponding value prior to +conversion to the type of the binding. When rebuilding a member access +expression, if we have a rewrite corresponding to the accessed member, then the +resulting value is the target of the rewrite, and its set of rewrites is that +found in the type of the target of the rewrite, if any. Because the target of +the rewrite is fully resolved already, we can ask for its type without +triggering additional work. In other cases, the rewrite set is empty; all +necessary rewrites were performed when type-checking the value we're +substituting into. + +Continuing an example from [qualified name lookup](#qualified-name-lookup): + +``` +interface A { + let T:! type; +} +interface B { + let U:! type; + let V:! A where .T = U; +} + +// Type of the expression `T` is `B where .(B.U) = i32` +fn F2[T:! B where .U = i32](x: T) { + // The type of the expression `T` is `B where .U = i32`. + // `T.V` is looked up and finds the associated type `(B.V)`. + // The declared type is `A where .(A.T) = Self.U`. + // We substitute `Self = T` with rewrite `.U = i32`. + // The resulting type is `A where .(A.T) = i32`. + // So `u` is `T.V` with type `A where .(A.T) = i32`. + let u:! auto = T.V; + // The type of `u` is `A where .(A.T) = i32`. + // Lookup for `u.T` resolves it to `u.(A.T)`. + // So the result of the qualified member access is `i32`, + // and the type of `v` is the type of `i32`, namely `type`. + // No substitution is performed in this step. + let v:! auto = u.T; } ``` -Notice that in an interface definition, `Self` refers to the type implementing -this interface while `.Self` refers to the associated facet currently being -defined. +###### Examples ``` interface Container { - let ElementType:! type; - - let SliceType:! Container - where .ElementType == ElementType and - .SliceType == .Self; + let Element:! type; +} +interface SliceableContainer { + extends Container; + let Slice:! Container where .Element = Self.(Container.Element); +} +// ❌ Qualified name lookup rewrites this facet type to +// `SliceableContainer where .(Container.Element) = .Self.(Container.Element)`. +// Constraint resolution rejects this because this rewrite forms a cycle. +fn Bad[T:! SliceableContainer where .Element = .Slice.Element](x: T.Element) {} +``` + +``` +interface Helper { + let D:! type; +} +interface Example { + let B:! type; + let C:! Helper where .D = B; +} +// ✅ `where .D = ...` by itself is fine. +// `T.C.D` is rewritten to `T.B`. +fn Allowed(T:! Example, x: T.C.D); +// ❌ But combined with another rewrite, creates an infinite loop. +// `.C.D` is rewritten to `.B`, resulting in `where .B = .B`, +// which causes an error during constraint resolution. +// Using `==` instead of `=` would make this constraint redundant, +// rather than it being an error. +fn Error(T:! Example where .B = .C.D, x: T.C.D); +``` - fn GetSlice[addr self: Self*] - (start: IteratorType, end: IteratorType) -> SliceType; +``` +interface Allowed; +interface AllowedBase { + let A:! Allowed; } +interface Allowed { + extends AllowedBase where .A = .Self; +} +// ✅ The final type of `x` is `T`. It is computed as follows: +// In `((T.A).A).A`, the inner `T.A` is rewritten to `T`, +// resulting in `((T).A).A`, which is then rewritten to +// `(T).A`, which is then rewritten to `T`. +fn F(T:! Allowed, x: ((T.A).A).A); ``` -Note that [naming](#named-constraint-constants) a recursive constraint using -using the [`constraint` introducer](#named-constraints) approach, we can name -the implementing type using `Self` instead of `.Self`, since they refer to the -same type: - ``` -constraint RealAbs { - extend HasAbs where .MagnitudeType == Self; - // Equivalent to: - extend HasAbs where .MagnitudeType == .Self; +interface MoveYsRight; +constraint ForwardDeclaredConstraint(X:! MoveYsRight); +interface MoveYsRight { + let X:! MoveYsRight; + // Means `Y:! MoveYsRight where .X = X.Y` + let Y:! ForwardDeclaredConstraint(X); } - -constraint ContainerIsSlice { - extend Container where .SliceType == Self; - // Equivalent to: - extend Container where .SliceType == .Self; +constraint ForwardDeclaredConstraint(X:! MoveYsRight) { + extends MoveYsRight where .X = X.Y; } +// ✅ The final type of `x` is `T.X.Y.Y`. It is computed as follows: +// The type of `T` is `MoveYsRight`. +// The type of `T.Y` is determined as follows: +// - Qualified name lookup finds `MoveYsRight.Y`. +// - The declared type is `ForwardDeclaredConstraint(Self.X)`. +// - That is a named constraint, for which we perform substitution. +// Substituting `X = Self.X` gives the type +// `MoveYsRight where .X = Self.X.Y`. +// - Substituting `Self = T` gives the type +// `MoveYsRight where .X = T.X.Y`. +// The type of `T.Y.Y` is determined as follows: +// - Qualified name lookup finds `MoveYsRight.Y`. +// - The declared type is `ForwardDeclaredConstraint(Self.X)`. +// - That is a named constraint, for which we perform substitution. +// Substituting `X = Self.X` gives the type +// `MoveYsRight where .X = Self.X.Y`. +// - Substituting `Self = T.Y` with +// rewrite `.X = T.X.Y` gives the type +// `MoveYsRight where .X = T.Y.X.Y`, but +// `T.Y.X` is replaced by `T.X.Y`, giving +// `MoveYsRight where .X = T.X.Y.Y`. +// The type of `T.Y.Y.X` is determined as follows: +// - Qualified name lookup finds `MoveYsRight.X`. +// - The type of `T.Y.Y` says to rewrite that to `T.X.Y.Y`. +// - The result is `T.X.Y.Y`, of type `MoveYsRight`. +fn F4(T:! MoveYsRight, x: T.Y.Y.X); ``` -The `.Self` construct follows these rules: +###### Termination -- `X :!` introduces `.Self:! type`, where references to `.Self` are resolved - to `X`. This allows you to use `.Self` as an interface parameter as in - `X:! I(.Self)`. -- `A where` introduces `.Self:! A` and `.Foo` for each member `Foo` of `A` -- It's an error to reference `.Self` if it refers to more than one different - thing or isn't a type. -- You get the innermost, most-specific type for `.Self` if it is introduced - twice in a scope. By the previous rule, it is only legal if they all refer - to the same facet binding. -- `.Self` may not be on the left side of the `=` in a rewrite constraint. +Each of the above steps performs at most one rewrite, and doesn't introduce any +new recursive type-checking steps, so should not introduce any new forms of +non-termination. Rewrite constraints thereby give us a deterministic, +terminating type canonicalization mechanism for associated constants: in `A.B`, +if the type of `A` specifies that `.B = C`, then `A.B` is replaced by `C`. +Equality of types constrained in this way is transitive. -So in `X:! A where ...`, `.Self` is introduced twice, after the `:!` and the -`where`. This is allowed since both times it means `X`. After the `:!`, `.Self` -has the type `type`, which gets refined to `A` after the `where`. In contrast, -it is an error if `.Self` could mean two different things, as in: +However, some existing forms of non-termination may remain, such as template +instantiation triggering another template instantiation. Such cases will need to +be detected and handled in some way, such as by a depth limit, but doing so +doesn't compromise the soundness of the type system. -``` -// ❌ Illegal: `.Self` could mean `T` or `T.A`. -fn F[T:! InterfaceA where .A impls - (InterfaceB where .B == .Self)](x: T); -``` +#### Same-type constraints -#### Parameterized type implements interface +**FIXME** -There are times when a function will pass a symbolic facet parameter of the -function as an argument to a parameterized type, as in the previous case, and in -addition the function needs the result to implement a specific interface. +A same-type constraint describes that two type expressions are known to evaluate +to the same value. Unlike a rewrite constraint, however, the two type +expressions are treated as distinct types when type-checking a generic that +refers to them. -``` -// Some parameterized type. -class Vector(T:! type) { ... } +Same-type constraints are brought into scope, looked up, and resolved exactly as +if there were a `SameAs(U:! type)` interface and a `T == U` impl corresponded to +`T is SameAs(U)`, except that `==` is commutative. As such, it's not possible to +ask for a list of types that are the same as a given type, nor to ask whether +there exists a type that is the same as a given type and has some property. But +it is possible to ask whether two types are (non-transitively) the same. + +In order for same-type constraints to be useful, they must allow the two types +to be treated as actually being the same in some context. This can be +accomplished by the use of `==` constraints in an `impl`, such as in the +built-in implementation of `ImplicitAs`: + +``` +final impl forall [T:! type, U:! type where .Self == T] T as ImplicitAs(U) { + fn Convert[me: Self](other: U) -> U { ... } +} +``` + +It superficially seems like it would be convenient if such implementations were +made available implicitly – for example, by writing +`impl forall [T:! type] T as ImplicitAs(T)` – but in more complex examples that +turns out to be problematic. Consider: -// Parameterized type implements interface only for some arguments. -impl Vector(String) as Printable { ... } +``` +interface CommonTypeWith(U:! type) { + let Result:! type; +} +final impl forall [T:! type] T as CommonTypeWith(T) where .Result = T {} -// Constraint: `T` such that `Vector(T)` implements `Printable` -fn PrintThree - [T:! type where Vector(.Self) impls Printable] - (a: T, b: T, c: T) { - var v: Vector(T) = (a, b, c); - Print(v); +fn F[T:! Potato, U:! Hashable where .Self == T](x: T, y: U) -> auto { + // What is T.CommonTypeWith(U).Result? Is it T or U? + return (if cond then x else y).Hash(); } ``` -**Comparison with other languages:** This use case was part of the -[Rust rationale for adding support for `where` clauses](https://rust-lang.github.io/rfcs/0135-where.html#motivation). +With this proposal, `impl` validation for `T as CommonTypeWith(U)` fails: we +cannot pick a common type when given two distinct type expressions, even if we +know they evaluate to the same type, because we would not know which API the +result should have. -#### Another type implements parameterized interface +##### Implementation of same-type `ImplicitAs` -In this case, we need some other type to implement an interface parameterized by -a symbolic facet parameter. The syntax for this case follows the previous case, -except now the `.Self` parameter is on the interface to the right of the -`impls`. For example, we might need a type parameter `T` to support explicit -conversion from an integer type like `i32`: +It is possible to implement the above `impl` of `ImplicitAs` directly in Carbon, +without a compiler builtin, by taking advantage of the built-in conversion +between `C where .A = X` and `C where .A == X`: ``` -interface As(T:! type) { - fn Convert[self: Self]() -> T; +interface EqualConverter { + let T:! type; + fn Convert(t: T) -> Self; +} +fn EqualConvert[T:! type](t: T, U:! EqualConverter where .T = T) -> U { + return U.Convert(t); +} +impl forall [U:! type] U as EqualConverter where .T = U { + fn Convert(u: U) -> U { return u; } } -fn Double[T:! Mul where i32 impls As(.Self)](x: T) -> T { - return x * (2 as T); +impl forall [T:! type, U:! type where .Self == T] T as ImplicitAs(U) { + fn Convert[self: Self]() -> U { return EqualConvert(self, U); } } ``` -### Constraints must use a designator +The transition from `(T as ImplicitAs(U)).Convert`, where we know that `U == T`, +to `EqualConverter.Convert`, where we know that `.T = U`, allows a same-type +constraint to be used to perform a rewrite. -We don't allow a `where` constraint unless it applies a restriction to the -current type. This means referring to some -[designator](#kinds-of-where-constraints), like `.MemberName`, or -[`.Self`](#recursive-constraints). Examples: +This implementation is in use in Carbon Explorer. -- `Container where .ElementType = i32` -- `type where Vector(.Self) impls Sortable` -- `Addable where i32 impls AddableWith(.Result)` +##### Manual type equality -Constraints that only refer to other types should be moved to the type that is -declared last. So: +Imagine we have some function with symbolic parameters: -```carbon -// ❌ Error: `where A == B` does not use `.Self` or a designator -fn F[A:! type, B:! type, C:! type where A == B](a: A, b: B, c: C); ``` +fn F[T:! SomeInterface](x: T) { + x.G(x.H()); +} +``` + +We want to know if the return type of method `T.H` is the same as the parameter +type of `T.G` in order to typecheck the function. However, determining whether +two [type expressions](terminology.md#type-expression) are transitively equal is +in general undecidable, as +[has been shown in Swift](https://forums.swift.org/t/swift-type-checking-is-undecidable/39024). + +Carbon's approach is to only allow implicit conversions between two type +expressions that are constrained to be equal in a single where clause. This +means that if two type expressions are only transitively equal, the user will +need to include a sequence of casts or use an +[`observe` declaration](#observe-declarations) to convert between them. + +Given this interface `Transitive` that has associated facets that are +constrained to all be equal, with interfaces `P`, `Q`, and `R`: + +``` +interface P { fn InP[self: Self](); } +interface Q { fn InQ[self: Self](); } +interface R { fn InR[self: Self](); } + +interface Transitive { + let A:! P; + let B:! Q where .Self == A; + let C:! R where .Self == B; + + fn GetA[self: Self]() -> A; + fn TakesC[self: Self](c: C); +} +``` + +A cast to `B` is needed to call `TakesC` with a value of type `A`, so each step +only relies on one equality: + +``` +fn F[T:! Transitive](t: T) { + // ✅ Allowed + t.TakesC(t.GetA() as T.B); + + // ✅ Allowed + let b: T.B = t.GetA(); + t.TakesC(b); + + // ❌ Not allowed: t.TakesC(t.GetA()); +} +``` + +A value of type `A`, such as the return value of `GetA()`, has the API of `P`. +Any such value also implements `Q`, and since the compiler can see that by way +of a single `where` equality, values of type `A` are treated as if they +implement `Q` [without extending it](terminology.md#extending-an-impl). However, +the compiler will require a cast to `B` or `C` to see that the type implements +`R`. + +``` +fn TakesPQR[U:! P & Q & R](u: U); + +fn G[T:! Transitive](t: T) { + var a: T.A = t.GetA(); + + // ✅ Allowed: `T.A` implements `P` and + // includes its API, as if it extends `P`. + a.InP(); + + // ✅ Allowed: `T.A` implements (but does not + // extend) `Q`. + a.(Q.InQ)(); + + // ❌ Not allowed: a.InQ(); + + // ✅ Allowed: values of type `T.A` may be cast + // to `T.B`, which extends and implements `Q`. + (a as T.B).InQ(); + + // ✅ Allowed: `T.B` implements (but does not + // extend) `R`. + (a as T.B).(R.InR)(); + // ❌ Not allowed: (a as T.B).InR(); + + // ❌ Not allowed: TakesPQR(a); + + // ✅ Allowed: `T.B` implements `P`, `Q`, and + // `R`, though `T.B` doesn't extend `P` or `R`. + TakesPQR(a as T.B); +} +``` + +The compiler may have several different `where` clauses to consider, +particularly when an interface has associated facets that recursively satisfy +the same interface. For example, given this interface `Commute`: + +``` +interface Commute { + let X:! Commute; + let Y:! Commute where .X == X.Y; + + fn GetX[self: Self]() -> X; + fn GetY[self: Self]() -> Y; + fn TakesXXY[self: Self](xxy: X.X.Y); +} +``` + +and a function `H` taking a value with some type implementing this interface, +then the following would be legal statements in `H`: + +``` +fn H[C: Commute](c: C) { + // ✅ Legal: argument has type `C.X.X.Y` + c.TakesXXY(c.GetX().GetX().GetY()); + + // ✅ Legal: argument has type `C.X.Y.X` which is equal + // to `C.X.X.Y` following only one `where` clause. + c.TakesXXY(c.GetX().GetY().GetX()); + + // ✅ Legal: cast is legal since it matches a `where` + // clause, and produces an argument that has type + // `C.X.Y.X`. + c.TakesXXY(c.GetY().GetX().GetX() as C.X.Y.X); +} +``` + +That last call would not be legal without the cast, though. + +**Comparison with other languages:** Other languages such as Swift and Rust +instead perform automatic type equality. In practice this means that their +compiler can reject some legal programs based on heuristics simply to avoid +running for an unbounded length of time. + +The benefits of the manual approach include: + +- fast compilation, since the compiler does not need to explore a potentially + large set of combinations of equality restrictions, supporting + [Carbon's goal of fast and scalable development](/docs/project/goals.md#fast-and-scalable-development); +- expressive and predictable semantics, since there are no limitations on how + complex a set of constraints can be supported; and +- simplicity. + +The main downsides are: + +- manual work for the source code author to prove to the compiler that types + are equal; and +- verbosity. + +We expect that rich error messages and IDE tooling will be able to suggest +changes to the source code when a single equality constraint is not sufficient +to show two type expressions are equal, but a more extensive automated search +can find a sequence that prove they are equal. + +##### Observe declarations + +Same-type constraints are non-transitive, just like other constraints such as +`ImplicitAs`. The developer can use an `observe` declaration to bring a new +same-type constraint into scope: + +``` +observe A == B == C; +``` + +notionally does much the same thing as + +``` +impl A as SameAs(C) { ... } +``` + +where the `impl` makes use of `A is SameAs(B)` and `B is SameAs(C)`. + +##### `observe` declarations + +An `observe` declaration lists a sequence of +[type expressions](terminology.md#type-expression) that are equal by some +same-type `where` constraints. These `observe` declarations may be included in +an `interface` definition or a function body, as in: + +``` +interface Commute { + let X:! Commute; + let Y:! Commute where .X == X.Y; + ... + observe X.X.Y == X.Y.X == Y.X.X; +} + +fn H[C: Commute](c: C) { + observe C.X.Y.Y == C.Y.X.Y == C.Y.Y.X; + ... +} +``` + +Every type expression after the first must be equal to some earlier type +expression in the sequence by a single `where` equality constraint. In this +example, + +``` +interface Commute { + let X:! Commute; + let Y:! Commute where .X == X.Y; + ... + // ✅ Legal: + observe X.X.Y.Y == X.Y.X.Y == Y.X.X.Y == X.Y.Y.X; +} +``` + +the expression `X.Y.Y.X` is one equality away from `X.Y.X.Y` and so it is +allowed. This is even though `X.Y.X.Y` isn't the type expression immediately +prior to `X.Y.Y.X`. + +After an `observe` declaration, all of the listed type expressions are +considered equal to each other using a single `where` equality. In this example, +the `observe` declaration in the `Transitive` interface definition provides the +link between associated facets `A` and `C` that allows function `F` to type +check. + +``` +interface P { fn InP[self: Self](); } +interface Q { fn InQ[self: Self](); } +interface R { fn InR[self: Self](); } + +interface Transitive { + let A:! P; + let B:! Q where .Self == A; + let C:! R where .Self == B; + + fn GetA[self: Self]() -> A; + fn TakesC[self: Self](c: C); + + // Without this `observe` declaration, the + // calls in `F` below would not be allowed. + observe A == B == C; +} + +fn TakesPQR[U:! P & Q & R](u: U); + +fn F[T:! Transitive](t: T) { + var a: T.A = t.GetA(); + + // ✅ Allowed: `T.A` == `T.C` + t.TakesC(a); + a.(R.InR()); + + // ✅ Allowed: `T.A` implements `P`, + // `T.A` == `T.B` that implements `Q`, and + // `T.A` == `T.C` that implements `R`. + TakesPQR(a); +} +``` + +Since adding an `observe` declaration only adds non-extending implementations of +interfaces to symbolic facets, they may be added without breaking existing code. -must be replaced by: +#### Implements constraints -```carbon -// ✅ Allowed -fn F[A:! type, B:! type where A == .Self, C:! type](a: A, b: B, c: C); -``` +**FIXME** -This includes `where` clauses used in an `impl` declaration: +A `where` clause can express that a facet binding must implement an interface. +This is more flexible than the usual approach of including that interface in the +type since it can be applied to associated facet members as well. + +In the following example, normally the `ElementType` of a `Container` can be any +type. The `SortContainer` function, however, takes a pointer to a type +satisfying `Container` with the additional constraint that its `ElementType` +must satisfy the `Comparable` interface, using an `impls` constraint: ``` -// ❌ Error: `where T impls B` does not use `.Self` or a designator -impl forall [T:! type] T as A where T impls B {} -// ✅ Allowed -impl forall [T:! type where .Self impls B] T as A {} -// ✅ Allowed -impl forall [T:! B] T as A {} +interface Container { + let ElementType:! type; + ... +} + +fn SortContainer + [ContainerType:! Container where .ElementType impls Comparable] + (container_to_sort: ContainerType*); ``` -This clarifies the meaning of the `where` clause and reduces the number of -redundant ways to express a restriction, following the -[one-way principle](/docs/project/principles/one_way.md). +In contrast to a [rewrite constraint](#rewrite-constraints) or a +[same-type constraint](#same-type-constraints), this does not say what type +`ElementType` exactly is, just that it must satisfy some facet type. -**Alternative considered:** This rule was added in proposal -[#2376](https://github.com/carbon-language/carbon-lang/pull/2376), which -[considered whether this rule should be added](/proposals/p2376.md#alternatives-considered). +**Note:** `Container` defines `ElementType` as having type `type`, but +`ContainerType.ElementType` has type `Comparable`. This is because +`ContainerType` has type `Container where .ElementType impls Comparable`, not +`Container`. This means we need to be a bit careful when talking about the type +of `ContainerType` when there is a `where` clause modifying it. -### Implied constraints +**FIXME: can also be used to say a facet implements an interface without +including that interface's API.** -Imagine we have a checked-generic function that accepts an arbitrary `HashMap`: +##### Implied constraints + +Imagine we have a checked-generic function that accepts an arbitrary `HashMap` +[parameterized type](#parameterized-types): ``` fn LookUp[KeyType:! type](hm: HashMap(KeyType, i32)*, @@ -3011,19 +3608,19 @@ class HashMap( In this case, `KeyType` gets `Hashable` and so on as _implied constraints_. Effectively that means that these functions are automatically rewritten to add a -`where` constraint on `KeyType` attached to the `HashMap` type: +`where`...`impls` constraint on `KeyType`: ``` -fn LookUp[KeyType:! type] - (hm: HashMap(KeyType, i32)* - where KeyType impls Hashable & EqualityComparable & Movable, - k: KeyType) -> i32; +fn LookUp[ + KeyType:! type + where .Self impls Hashable & EqualityComparable & Movable] + (hm: HashMap(KeyType, i32)*, k: KeyType) -> i32; -fn PrintValueOrDefault[KeyType:! Printable, - ValueT:! Printable & HasDefault] - (map: HashMap(KeyType, ValueT) - where KeyType impls Hashable & EqualityComparable & Movable, - key: KeyT); +fn PrintValueOrDefault[ + KeyType:! Printable + where .Self impls Hashable & EqualityComparable & Movable, + ValueT:! Printable & HasDefault] + (map: HashMap(KeyType, ValueT), key: KeyT); ``` In this case, Carbon will accept the definition and infer the needed constraints @@ -3073,285 +3670,456 @@ and support some form of this feature as part of their type inference (and [the Rust community is considering expanding support](http://smallcultfollowing.com/babysteps//blog/2022/04/12/implied-bounds-and-perfect-derive/#expanded-implied-bounds)). -#### Must be legal type argument constraints +#### Combining constraints -Now consider the case that the symbolic facet parameter is going to be used as -an argument to a parameterized type in a function body, not in the signature. If -the parameterized type was explicitly mentioned in the signature, the implied -constraint feature would ensure all of its requirements were met. The developer -can create a trivial -[parameterized type implements interface](#parameterized-type-implements-interface) -`where` constraint to just say the type is a legal with this argument, by saying -that the parameterized type implements `type`, which all types do. +Constraints can be combined by separating constraint clauses with the `and` +keyword. This example expresses a constraint that two associated facets are +equal and satisfy an interface: -For example, a function that adds its parameters to a `HashSet` to deduplicate -them, needs them to be `Hashable` and so on. To say "`T` is a type where -`HashSet(T)` is legal," we can write: +``` +fn EqualContainers + [CT1:! Container, + CT2:! Container where .ElementType impls HasEquality + and .ElementType = CT1.ElementType] + (c1: CT1*, c2: CT2*) -> bool; +``` + +**Comparison with other languages:** Swift and Rust use commas `,` to separate +constraint clauses, but that only works because they place the `where` in a +different position in a declaration. In Carbon, the `where` is attached to a +type in a parameter list that is already using commas to separate parameters. + +#### Recursive constraints + +**FIXME: LINKED** + +We sometimes need to constrain a type to equal one of its associated facets. In +this first example, we want to represent the function `Abs` which will return +`Self` for some but not all types, so we use an associated facet `MagnitudeType` +to encode the return type: ``` -fn NumDistinct[T:! type where HashSet(.Self) impls type] - (a: T, b: T, c: T) -> i32 { - var set: HashSet(T); - set.Add(a); - set.Add(b); - set.Add(c); - return set.Size(); +interface HasAbs { + extend Numeric; + let MagnitudeType:! Numeric; + fn Abs[self: Self]() -> MagnitudeType; } ``` -This has the same advantages over repeating the constraints on `HashSet` -arguments in the type of `T` as the general implied constraints above. +For types representing subsets of the real numbers, such as `i32` or `f32`, the +`MagnitudeType` will match `Self`, the type implementing an interface. For types +representing complex numbers, the types will be different. For example, the +`Abs()` applied to a `Complex64` value would produce a `f32` result. The goal is +to write a constraint to restrict to the first case. -### Referencing names in the interface being defined +In a second example, when you take the slice of a type implementing `Container` +you get a type implementing `Container` which may or may not be the same type as +the original container type. However, taking the slice of a slice always gives +you the same type, and some functions want to only operate on containers whose +slice type is the same as the container type. -The constraint in a `where` clause is required to only reference earlier names -from this scope, as in this example: +To solve this problem, we think of `Self` as an actual associated facet member +of every interface. We can then address it using `.Self` in a `where` clause, +like any other associated facet member. ``` -interface Graph { - let E: Edge; - let V: Vert where .E == E and .Self == E.V; +fn Relu[T:! HasAbs where .MagnitudeType = .Self](x: T) { + // T.MagnitudeType == T so the following is allowed: + return (x.Abs() + x) / 2; +} +fn UseContainer[T:! Container where .SliceType = .Self](c: T) -> bool { + // T.SliceType == T so `c` and `c.Slice(...)` can be compared: + return c == c.Slice(...); } ``` -### Manual type equality +Notice that in an interface definition, `Self` refers to the type implementing +this interface while `.Self` refers to the associated facet currently being +defined. -Imagine we have some function with symbolic parameters: +``` +interface Container { + let ElementType:! type; + + let SliceType:! Container + where .ElementType = ElementType and + .SliceType = .Self; + fn GetSlice[addr self: Self*] + (start: IteratorType, end: IteratorType) -> SliceType; +} ``` -fn F[T:! SomeInterface](x: T) { - x.G(x.H()); + +Note that [naming](#named-constraint-constants) a recursive constraint using +using the [`constraint` introducer](#named-constraints) approach, we can name +the implementing type using `Self` instead of `.Self`, since they refer to the +same type: + +``` +constraint RealAbs { + extend HasAbs where .MagnitudeType = Self; + // Equivalent to: + extend HasAbs where .MagnitudeType = .Self; +} + +constraint ContainerIsSlice { + extend Container where .SliceType = Self; + // Equivalent to: + extend Container where .SliceType = .Self; } ``` -We want to know if the return type of method `T.H` is the same as the parameter -type of `T.G` in order to typecheck the function. However, determining whether -two [type expressions](terminology.md#type-expression) are transitively equal is -in general undecidable, as -[has been shown in Swift](https://forums.swift.org/t/swift-type-checking-is-undecidable/39024). +The `.Self` construct follows these rules: -Carbon's approach is to only allow implicit conversions between two type -expressions that are constrained to be equal in a single where clause. This -means that if two type expressions are only transitively equal, the user will -need to include a sequence of casts or use an -[`observe` declaration](#observe-declarations) to convert between them. +- `X :!` introduces `.Self:! type`, where references to `.Self` are resolved + to `X`. This allows you to use `.Self` as an interface parameter as in + `X:! I(.Self)`. +- `A where` introduces `.Self:! A` and `.Foo` for each member `Foo` of `A` +- It's an error to reference `.Self` if it refers to more than one different + thing or isn't a type. +- You get the innermost, most-specific type for `.Self` if it is introduced + twice in a scope. By the previous rule, it is only legal if they all refer + to the same facet binding. +- `.Self` may not be on the left side of the `=` in a rewrite constraint. -Given this interface `Transitive` that has associated facets that are -constrained to all be equal, with interfaces `P`, `Q`, and `R`: +So in `X:! A where ...`, `.Self` is introduced twice, after the `:!` and the +`where`. This is allowed since both times it means `X`. After the `:!`, `.Self` +has the type `type`, which gets refined to `A` after the `where`. In contrast, +it is an error if `.Self` could mean two different things, as in: + +``` +// ❌ Illegal: `.Self` could mean `T` or `T.A`. +fn F[T:! InterfaceA where .A impls + (InterfaceB where .B = .Self)](x: T); +``` + +### Satisfying both facet types + +If the two types being constrained to be equal have been declared with different +facet types, then the actual type value they are set to will have to satisfy +both constraints. For example, if `SortedContainer.ElementType` is declared to +be `Comparable`, then in these declarations: + +``` +fn Contains_Rewrite + [SC:! SortedContainer, + CT:! Container where .ElementType = SC.ElementType] + (haystack: SC, needles: CT) -> bool; + +// Similarly with `==` same-type constraints: +fn Contains_SameType + [SC:! SortedContainer, + CT:! Container where .ElementType == SC.ElementType] + (haystack: SC, needles: CT) -> bool; +``` + +the `where` constraints in both cases mean `CT.ElementType` must satisfy +`Comparable` as well. + +**FIXME: Separate rewrite and `==` behavior.** + +However, inside the body of `Contains`, `CT.ElementType` will act like the +implementation of `Comparable` is declared without [`extend`](#extend-impl). +That is, items from the `needles` container won't directly have a `Compare` +method member, but can still be implicitly converted to `Comparable` and can +still call `Compare` using the compound member access syntax, +`needle.(Comparable.Compare)(elt)`. The rule is that an `==` `where` constraint +between two type variables does not modify the set of member names of either +type. (If you write `where .ElementType = String` with a `=` and a concrete +type, then `.ElementType` is actually set to `String` including the complete +`String` API.) + +Note that `==` constraints are symmetric, so the previous declaration of +`Contains` is equivalent to an alternative declaration where `CT` is declared +first and the `where` clause is attached to `SortedContainer`: + +``` +fn Contains + [CT:! Container, + SC:! SortedContainer where .ElementType == CT.ElementType] + (haystack: SC, needles: CT) -> bool; +``` + +### Constraints must use a designator + +We don't allow a `where` constraint unless it applies a restriction to the +current type. This means referring to some +[designator](#kinds-of-where-constraints), like `.MemberName`, or +[`.Self`](#recursive-constraints). Examples: + +- `Container where .ElementType = i32` +- `type where Vector(.Self) impls Sortable` +- `Addable where i32 impls AddableWith(.Result)` + +Constraints that only refer to other types should be moved to the type that is +declared last. So: + +```carbon +// ❌ Error: `where A == B` does not use `.Self` or a designator +fn F[A:! type, B:! type, C:! type where A == B](a: A, b: B, c: C); +``` + +must be replaced by: + +```carbon +// ✅ Allowed +fn F[A:! type, B:! type where A == .Self, C:! type](a: A, b: B, c: C); +``` + +This includes `where` clauses used in an `impl` declaration: + +``` +// ❌ Error: `where T impls B` does not use `.Self` or a designator +impl forall [T:! type] T as A where T impls B {} +// ✅ Allowed +impl forall [T:! type where .Self impls B] T as A {} +// ✅ Allowed +impl forall [T:! B] T as A {} +``` + +This clarifies the meaning of the `where` clause and reduces the number of +redundant ways to express a restriction, following the +[one-way principle](/docs/project/principles/one_way.md). + +**Alternative considered:** This rule was added in proposal +[#2376](https://github.com/carbon-language/carbon-lang/pull/2376), which +[considered whether this rule should be added](/proposals/p2376.md#alternatives-considered). + +### Referencing names in the interface being defined -``` -interface P { fn InP[self: Self](); } -interface Q { fn InQ[self: Self](); } -interface R { fn InR[self: Self](); } +The constraint in a `where` clause is required to only reference earlier names +from this scope, as in this example: -interface Transitive { - let A:! P; - let B:! Q where .Self == A; - let C:! R where .Self == B; +``` +// ❌ Illegal: `E` references `V` declared later. +interface Graph { + let E: Edge where .V = V; + let V: Vert where .E = E; +} - fn GetA[self: Self]() -> A; - fn TakesC[self: Self](c: C); +// ✅ Allowed: Only references to earlier names. +interface Graph { + let E: Edge; + let V: Vert where .E = E and .Self == E.V; } ``` -A cast to `B` is needed to call `TakesC` with a value of type `A`, so each step -only relies on one equality: +### Constraint examples and use cases -``` -fn F[T:! Transitive](t: T) { - // ✅ Allowed - t.TakesC(t.GetA() as T.B); +- **Set [associated constant](#associated-constants) to a constant:** For + example in `NSpacePoint where .N = 2`, the associated constant `N` of + `NSpacePoint` must be `2`. This syntax is also used to specify the values of + associated constants when implementing an interface for a type, as in + `impl MyPoint as NSpacePoint where .N = 2 {`...`}`. - // ✅ Allowed - let b: T.B = t.GetA(); - t.TakesC(b); +- **Set an [associated facet](#associated-facets) to a specific value:** + Associated facets are treated like any other associated constant. So + `Stack where .ElementType = i32` is a facet type that restricts to types + that implement the `Stack` interface with integer elements, as in: - // ❌ Not allowed: t.TakesC(t.GetA()); -} -``` + ``` + fn SumIntStack[T:! Stack where .ElementType = i32] + (s: T*) -> i32 { + var sum: i32 = 0; + while (!s->IsEmpty()) { + // s->Pop() returns a value of type + // `T.ElementType` which is `i32`: + sum += s->Pop(); + } + return sum; + } + ``` -A value of type `A`, such as the return value of `GetA()`, has the API of `P`. -Any such value also implements `Q`, and since the compiler can see that by way -of a single `where` equality, values of type `A` are treated as if they -implement `Q` [without extending it](terminology.md#extending-an-impl). However, -the compiler will require a cast to `B` or `C` to see that the type implements -`R`. + Note that this is a case that can use an `==` same-type constraint instead + of an `=` rewrite constraint. -``` -fn TakesPQR[U:! P & Q & R](u: U); +- **One [associated constant](#associated-constants) must equal another:** For + example with this definition of the interface `PointCloud`: -fn G[T:! Transitive](t: T) { - var a: T.A = t.GetA(); + ``` + interface PointCloud { + let Dim:! i32; + let PointT:! NSpacePoint where .N = Dim; + } + ``` - // ✅ Allowed: `T.A` implements `P` and - // includes its API, as if it extends `P`. - a.InP(); + an implementation of `PointCloud` for a type `T` will have + `T.PointT.N == T.Dim`. - // ✅ Allowed: `T.A` implements (but does not - // extend) `Q`. - a.(Q.InQ)(); +- **Equal facet bindings:** - // ❌ Not allowed: a.InQ(); + For example, we could make the `ElementType` of an `Iterator` interface + equal to the `ElementType` of a `Container` interface as follows: - // ✅ Allowed: values of type `T.A` may be cast - // to `T.B`, which extends and implements `Q`. - (a as T.B).InQ(); + ``` + interface Iterator { + let ElementType:! type; + ... + } + interface Container { + let ElementType:! type; + let IteratorType:! Iterator where .ElementType = ElementType; + ... + } + ``` - // ✅ Allowed: `T.B` implements (but does not - // extend) `R`. - (a as T.B).(R.InR)(); - // ❌ Not allowed: (a as T.B).InR(); + In a function signature, this may be done by referencing an earlier + parameter: - // ❌ Not allowed: TakesPQR(a); + ``` + fn Map[CT:! Container, + FT:! Function where .InputType = CT.ElementType] + (c: CT, f: FT) -> Vector(FT.OutputType); + ``` - // ✅ Allowed: `T.B` implements `P`, `Q`, and - // `R`, though `T.B` doesn't extend `P` or `R`. - TakesPQR(a as T.B); -} -``` + In that example, `FT.InputType` is constrained to equal `CT.InputType`. + Given an interface with two associated facets -The compiler may have several different `where` clauses to consider, -particularly when an interface has associated facets that recursively satisfy -the same interface. For example, given this interface `Commute`: + ``` + interface PairInterface { + let Left:! type; + let Right:! type; + } + ``` -``` -interface Commute { - let X:! Commute; - let Y:! Commute where .X == X.Y; + we can constrain them to be equal using + `PairInterface where .Left = .Right`. - fn GetX[self: Self]() -> X; - fn GetY[self: Self]() -> Y; - fn TakesXXY[self: Self](xxy: X.X.Y); -} -``` + Note that this is a case that can use an `==` same-type constraint instead + of an `=` rewrite constraint. -and a function `H` taking a value with some type implementing this interface, -then the following would be legal statements in `H`: +- **Associated facet implements interface:** Given these definitions (omitting + `ElementType` for brevity): -``` -fn H[C: Commute](c: C) { - // ✅ Legal: argument has type `C.X.X.Y` - c.TakesXXY(c.GetX().GetX().GetY()); + ```carbon + interface IteratorInterface { ... } + interface ContainerInterface { + let IteratorType:! IteratorInterface; + // ... + } + interface RandomAccessIterator { + extend IteratorInterface; + // ... + } + ``` - // ✅ Legal: argument has type `C.X.Y.X` which is equal - // to `C.X.X.Y` following only one `where` clause. - c.TakesXXY(c.GetX().GetY().GetX()); + We can then define a function that only accepts types that implement + `ContainerInterface` where its `IteratorType` associated facet implements + `RandomAccessIterator`: - // ✅ Legal: cast is legal since it matches a `where` - // clause, and produces an argument that has type - // `C.X.Y.X`. - c.TakesXXY(c.GetY().GetX().GetX() as C.X.Y.X); -} -``` + ```carbon + fn F[ContainerType:! ContainerInterface + where .IteratorType impls RandomAccessIterator] + (c: ContainerType); + ``` -That last call would not be legal without the cast, though. +#### Parameterized type implements interface -**Comparison with other languages:** Other languages such as Swift and Rust -instead perform automatic type equality. In practice this means that their -compiler can reject some legal programs based on heuristics simply to avoid -running for an unbounded length of time. +**FIXME: LINKED** -The benefits of the manual approach include: +There are times when a function will pass a symbolic facet parameter of the +function as an argument to a [parameterized type](#parameterized-types), as in +the previous case, and in addition the function needs the result to implement a +specific interface. -- fast compilation, since the compiler does not need to explore a potentially - large set of combinations of equality restrictions, supporting - [Carbon's goal of fast and scalable development](/docs/project/goals.md#fast-and-scalable-development); -- expressive and predictable semantics, since there are no limitations on how - complex a set of constraints can be supported; and -- simplicity. +``` +// Some parameterized type. +class Vector(T:! type) { ... } -The main downsides are: +// Parameterized type implements interface only for some arguments. +impl Vector(String) as Printable { ... } -- manual work for the source code author to prove to the compiler that types - are equal; and -- verbosity. +// Constraint: `T` such that `Vector(T)` implements `Printable` +fn PrintThree + [T:! type where Vector(.Self) impls Printable] + (a: T, b: T, c: T) { + var v: Vector(T) = (a, b, c); + Print(v); +} +``` -We expect that rich error messages and IDE tooling will be able to suggest -changes to the source code when a single equality constraint is not sufficient -to show two type expressions are equal, but a more extensive automated search -can find a sequence that prove they are equal. +**Comparison with other languages:** This use case was part of the +[Rust rationale for adding support for `where` clauses](https://rust-lang.github.io/rfcs/0135-where.html#motivation). -#### `observe` declarations +#### Another type implements parameterized interface -An `observe` declaration lists a sequence of -[type expressions](terminology.md#type-expression) that are equal by some -same-type `where` constraints. These `observe` declarations may be included in -an `interface` definition or a function body, as in: +**FIXME: LINKED** + +In this case, we need some other type to implement an interface parameterized by +a symbolic facet parameter. The syntax for this case follows the previous case, +except now the `.Self` parameter is on the interface to the right of the +`impls`. For example, we might need a type parameter `T` to support explicit +conversion from an integer type like `i32`: ``` -interface Commute { - let X:! Commute; - let Y:! Commute where .X == X.Y; - ... - observe X.X.Y == X.Y.X == Y.X.X; +interface As(T:! type) { + fn Convert[self: Self]() -> T; } -fn H[C: Commute](c: C) { - observe C.X.Y.Y == C.Y.X.Y == C.Y.Y.X; - ... +fn Double[T:! Mul where i32 impls As(.Self)](x: T) -> T { + return x * (2 as T); } ``` -Every type expression after the first must be equal to some earlier type -expression in the sequence by a single `where` equality constraint. In this -example, +#### Must be legal type argument constraints + +**FIXME: LINKED** + +Now consider the case that the symbolic facet parameter is going to be used as +an argument to a parameterized type in a function body, not in the signature. If +the parameterized type was explicitly mentioned in the signature, the +[implied constraint](#implied-constraints) feature would ensure all of its +requirements were met. The developer can create a trivial +[parameterized type implements interface](#parameterized-type-implements-interface) +`where` constraint to just say the type is a legal with this argument, by saying +that the parameterized type implements `type`, which all types do. + +For example, a function that adds its parameters to a `HashSet` to deduplicate +them, needs them to be `Hashable` and so on. To say "`T` is a type where +`HashSet(T)` is legal," we can write: ``` -interface Commute { - let X:! Commute; - let Y:! Commute where .X == X.Y; - ... - // ✅ Legal: - observe X.X.Y.Y == X.Y.X.Y == Y.X.X.Y == X.Y.Y.X; +fn NumDistinct[T:! type where HashSet(.Self) impls type] + (a: T, b: T, c: T) -> i32 { + var set: HashSet(T); + set.Add(a); + set.Add(b); + set.Add(c); + return set.Size(); } ``` -the expression `X.Y.Y.X` is one equality away from `X.Y.X.Y` and so it is -allowed. This is even though `X.Y.X.Y` isn't the type expression immediately -prior to `X.Y.Y.X`. - -After an `observe` declaration, all of the listed type expressions are -considered equal to each other using a single `where` equality. In this example, -the `observe` declaration in the `Transitive` interface definition provides the -link between associated facets `A` and `C` that allows function `F` to type -check. +This has the same advantages over repeating the constraints on `HashSet` +arguments in the type of `T` as the general implied constraints above. -``` -interface P { fn InP[self: Self](); } -interface Q { fn InQ[self: Self](); } -interface R { fn InR[self: Self](); } +### Named constraint constants -interface Transitive { - let A:! P; - let B:! Q where .Self == A; - let C:! R where .Self == B; +A facet type with a `where` constraint, such as `C where `, can be +named two different ways: - fn GetA[self: Self]() -> A; - fn TakesC[self: Self](c: C); +- Using `let template` as in: - // Without this `observe` declaration, the - // calls in `F` below would not be allowed. - observe A == B == C; -} + ```carbon + let template NameOfConstraint:! auto = C where ; + ``` -fn TakesPQR[U:! P & Q & R](u: U); + or, since the type of a facet type is `type`: -fn F[T:! Transitive](t: T) { - var a: T.A = t.GetA(); + ```carbon + let template NameOfConstraint:! type = C where ; + ``` - // ✅ Allowed: `T.A` == `T.C` - t.TakesC(a); - a.(R.InR()); +- Using a [named constraint](#named-constraints) with the `constraint` keyword + introducer: - // ✅ Allowed: `T.A` implements `P`, - // `T.A` == `T.B` that implements `Q`, and - // `T.A` == `T.C` that implements `R`. - TakesPQR(a); -} -``` + ```carbon + constraint NameOfConstraint { + extend C where ; + } + ``` -Since adding an `observe` declaration only adds non-extending implementations of -interfaces to symbolic facets, they may be added without breaking existing code. +Whichever approach is used, the result is `NameOfConstraint` is a compile-time +constant that is equivalent to `C where `. ## Other constraints as facet types @@ -3735,8 +4503,8 @@ so it applies to a family of types, interfaces, or both. This includes: ### Impl for a parameterized type -Interfaces may be implemented for a parameterized type. This can be done -lexically in the class' scope: +Interfaces may be implemented for a [parameterized type](#parameterized-types). +This can be done lexically in the class' scope: ``` class Vector(T:! type) { @@ -5703,6 +6471,8 @@ parameters may be used to specify types in the declarations of its members, such as data fields, member functions, and even interfaces being implemented. For example, a container type might be parameterized by the type of its elements: +**FIXME: member functions may have additional parameters.** + ``` class HashMap( KeyType:! Hashable & EqualityComparable & Movable, diff --git a/docs/design/pattern_matching.md b/docs/design/pattern_matching.md index 976ce6c3d682f..2bb378874cf1d 100644 --- a/docs/design/pattern_matching.md +++ b/docs/design/pattern_matching.md @@ -223,23 +223,6 @@ fn J(unused n: i32); A `:!` can be used in place of `:` for a binding that is usable at compile time. -- _compile-time-pattern_ ::= `unused`? `template`? _identifier_ `:!` - _expression_ -- _compile-time-pattern_ ::= `template`? _identifier_ `:!` _expression_ -- _compile-time-pattern_ ::= `template`? `_` `:!` _expression_ -- _compile-time-pattern_ ::= `unused` `template`? _identifier_ `:!` - _expression_ -- _proper-pattern_ ::= _compile-time-pattern_ - -**FIXME:** Maybe this should just be: - -- _compile-time-pattern_ ::= `unused`? `template`? _identifier_ `:!` - _expression_ -- _compile-time-pattern_ ::= `template`? `_` `:!` _expression_ -- _proper-pattern_ ::= _compile-time-pattern_ - -**FIXME:** Or, if we want to keep the "unused" patterns separate: - - _compile-time-pattern_ ::= `template`? _identifier_ `:!` _expression_ - _compile-time-pattern_ ::= `template`? `_` `:!` _expression_ - _compile-time-pattern_ ::= `unused` `template`? _identifier_ `:!` From d2617189b7d2d6479da27ad52ab4f7682b11655f Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 12 Sep 2023 18:44:06 +0000 Subject: [PATCH 57/83] Checkpoint progress. --- docs/design/generics/details.md | 412 +++++++++++++++++--------------- 1 file changed, 217 insertions(+), 195 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index b03568203774a..2e6f377876d5a 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -50,12 +50,13 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Parameterized named constraints](#parameterized-named-constraints) - [Where constraints](#where-constraints) - [Kinds of `where` constraints](#kinds-of-where-constraints) + - [Recursive constraints](#recursive-constraints) - [Rewrite constraints](#rewrite-constraints) - [Combining constraints with `&`](#combining-constraints-with-) - [Combining constraints with `and`](#combining-constraints-with-and) - [Combining constraints with `extends`](#combining-constraints-with-extends) - - [Combining constraints with `impl as` and `is`](#combining-constraints-with-impl-as-and-is) - - [Constraint resolution](#constraint-resolution) + - [Combining constraints with `impl as` and `impls`](#combining-constraints-with-impl-as-and-impls) + - [Rewrite constraint resolution](#rewrite-constraint-resolution) - [Precise rules and termination](#precise-rules-and-termination) - [Qualified name lookup](#qualified-name-lookup) - [Type substitution](#type-substitution) @@ -69,7 +70,6 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Implements constraints](#implements-constraints) - [Implied constraints](#implied-constraints) - [Combining constraints](#combining-constraints) - - [Recursive constraints](#recursive-constraints) - [Satisfying both facet types](#satisfying-both-facet-types) - [Constraints must use a designator](#constraints-must-use-a-designator) - [Referencing names in the interface being defined](#referencing-names-in-the-interface-being-defined) @@ -2551,7 +2551,124 @@ An implements constraint is written `where T impls C`, where `T` is a facet and Implements constraints switched using the `impls` keyword in [proposal #2483](https://github.com/carbon-language/carbon-lang/pull/2483). -**FIXME: Left off here.** +**Alternatives considered:** + +- [Different equality constraint operators for symbolic and constants](/proposals/p2173.md#status-quo) +- [Single one-step equality constraint operators that merges constraints](/proposals/p2173.md#equal-types-with-different-interfaces) +- [Restrict constraints to allow computable type equality](/proposals/p2173.md#restrict-constraints-to-allow-computable-type-equality) +- [Find a fully transitive approach to type equality](/proposals/p2173.md#find-a-fully-transitive-approach-to-type-equality) +- [Different syntax for rewrite constraint](/proposals/p2173.md#different-syntax-for-rewrite-constraint) +- [Different syntax for same-type constraint](/proposals/p2173.md#different-syntax-for-same-type-constraint) +- [Required ordering for rewrites](/proposals/p2173.md#required-ordering-for-rewrites) +- [Multi-constraint `where` clauses](/proposals/p2173.md#multi-constraint-where-clauses) +- [Rewrite constraints in `impl as` constraints](/proposals/p2173.md#rewrite-constraints-in-impl-as-constraints) + +#### Recursive constraints + +**FIXME: LINKED** + +We sometimes need to constrain a type to equal one of its associated facets. In +this first example, we want to represent the function `Abs` which will return +`Self` for some but not all types, so we use an associated facet `MagnitudeType` +to encode the return type: + +``` +interface HasAbs { + extend Numeric; + let MagnitudeType:! Numeric; + fn Abs[self: Self]() -> MagnitudeType; +} +``` + +For types representing subsets of the real numbers, such as `i32` or `f32`, the +`MagnitudeType` will match `Self`, the type implementing an interface. For types +representing complex numbers, the types will be different. For example, the +`Abs()` applied to a `Complex64` value would produce a `f32` result. The goal is +to write a constraint to restrict to the first case. + +In a second example, when you take the slice of a type implementing `Container` +you get a type implementing `Container` which may or may not be the same type as +the original container type. However, taking the slice of a slice always gives +you the same type, and some functions want to only operate on containers whose +slice type is the same as the container type. + +To solve this problem, we think of `Self` as an actual associated facet member +of every interface. We can then address it using `.Self` in a `where` clause, +like any other associated facet member. + +``` +fn Relu[T:! HasAbs where .MagnitudeType = .Self](x: T) { + // T.MagnitudeType == T so the following is allowed: + return (x.Abs() + x) / 2; +} +fn UseContainer[T:! Container where .SliceType = .Self](c: T) -> bool { + // T.SliceType == T so `c` and `c.Slice(...)` can be compared: + return c == c.Slice(...); +} +``` + +Notice that in an interface definition, `Self` refers to the type implementing +this interface while `.Self` refers to the associated facet currently being +defined. + +``` +interface Container { + let ElementType:! type; + + let SliceType:! Container + where .ElementType = ElementType and + // `.Self` means `SliceType`. + .SliceType = .Self; + + // `Self` means the type implementing `Container`. + fn GetSlice[addr self: Self*] + (start: IteratorType, end: IteratorType) -> SliceType; +} +``` + +Note that [naming](#named-constraint-constants) a recursive constraint using the +[`constraint` introducer](#named-constraints) approach, we can name the +implementing type using `Self` instead of `.Self`, since they refer to the same +type: + +``` +constraint RealAbs { + extend HasAbs where .MagnitudeType = Self; + // Equivalent to: + extend HasAbs where .MagnitudeType = .Self; +} + +constraint ContainerIsSlice { + extend Container where .SliceType = Self; + // Equivalent to: + extend Container where .SliceType = .Self; +} +``` + +The `.Self` construct follows these rules: + +- `X :!` introduces `.Self:! type`, where references to `.Self` are resolved. + to `X`. This allows you to use `.Self` as an interface parameter as in + `X:! I(.Self)`. +- `A where` introduces `.Self:! A` and `.Foo` _designator_ for each member + `Foo` of `A`. +- It's an error to reference `.Self` if it refers to more than one different + thing or isn't a facet. +- You get the innermost, most-specific type for `.Self` if it is introduced + twice in a scope. By the previous rule, it is only legal if they all refer + to the same facet binding. +- `.Self` may not be on the left side of the `=` in a rewrite constraint. + +So in `X:! A where ...`, `.Self` is introduced twice, after the `:!` and the +`where`. This is allowed since both times it means `X`. After the `:!`, `.Self` +has the type `type`, which gets refined to `A` after the `where`. In contrast, +it is an error if `.Self` could mean two different things, as in: + +``` +// ❌ Illegal: `.Self` could mean `T` or `T.A`. +fn F[T:! InterfaceA where .A impls + (InterfaceB where .B = .Self)](x: T); +``` #### Rewrite constraints @@ -2586,7 +2703,7 @@ with any mentioned parameters substituted into that type. interface Container { let Element:! type; let Slice:! Container where .Element = Element; - fn Add[addr me: Self*](x: Element); + fn Add[addr self: Self*](x: Element); } // `T.Slice.Element` rewritten to `T.Element` // because type of `T.Slice` says `.Element = Element`. @@ -2602,50 +2719,55 @@ fn Add[T:! Container where .Element = i32](p: T*, y: T.Slice.Element) { Rewrites aren't performed on the left-hand side of such an `=`, so `where .A = .B and .A = C` is not rewritten to `where .A = .B and .B = C`. Instead, such a `where` clause is invalid when the constraint is -[resolved](#constraint-resolution) unless each rule for `.A` specifies the same -rewrite. +[resolved](#rewrite-constraint-resolution) unless each rule for `.A` specifies +the same rewrite. Note that `T:! C where .R = i32` can result in a type `T.R` whose behavior is different from the behavior of `T.R` given `T:! C`. For example, member lookup into `T.R` can find different results and operations can therefore have -different behavior. However, this does not violate coherence because the facet -types `C` and `C where .R = i32` don't differ by merely having more type -information; rather, they are different facet types that have an isomorphic set -of values, somewhat like `i32` and `u32`. An `=` constraint is not merely -learning a new fact about a type, it is requesting different behavior. - -This approach addresses a few problems we have with same-type constraints: - -- [Equal types with different interfaces](/proposals/p2173.md#equal-types-with-different-interfaces) -- [Type canonicalization](/proposals/p2173.md#type-canonicalization) -- [Transitivity of equality](/proposals/p2173.md#transitivity-of-equality) +different behavior. However, this does not violate +[coherence](/proposals/p2173.md#coherence) because the facet types `C` and +`C where .R = i32` don't differ by merely having more type information; rather, +they are different facet types that have an isomorphic set of values, somewhat +like `i32` and `u32`. An `=` constraint is not merely learning a new fact about +a type, it is requesting different behavior. + +This approach has some good properties that +[same-type constraints](#same-type-constraints) have problems with: + +- [Equal types with different interfaces](/proposals/p2173.md#equal-types-with-different-interfaces): + When an associated facet is constrained to be a concrete type, it is + desirable for the associated facet to behave like that concrete type. +- [Type canonicalization](/proposals/p2173.md#type-canonicalization): to + enable efficient type equality. +- [Transitivity of equality of types](/proposals/p2173.md#transitivity-of-equality) ##### Combining constraints with `&` Suppose we have `X = C where .R = A` and `Y = C where .R = B`. What should `C & X` produce? What should `X & Y` produce? -We could perhaps say that `X & Y` results in a facet type where the type of `R` -has the union of the interface of `A` and the interface of `B`, and that `C & X` -similarly results in a facet type where the type of `R` has the union of the -interface of `A` and the interface originally specified by `C`. However, this -proposal suggests a simpler rule: - - Combining two rewrite rules with different rewrite targets results in a facet type where the associated constant is ambiguous. Given `T:! X & Y`, the type expression `T.R` is ambiguous between a rewrite to `A` and a rewrite to `B`. But given `T:! X & X`, `T.R` is unambiguously rewritten to `A`. - Combining a constraint with a rewrite rule with a constraint with no rewrite - rule preserves the rewrite rule. For example, supposing that - `interface Container` extends `interface Iterable`, and `Iterable` has an - associated constant `Element`, the constraint + rule preserves the rewrite rule, so `C & X` is the same as `X`. For example, + supposing that `interface Container` extends `interface Iterable`, and + `Iterable` has an associated constant `Element`, the constraint `Container & (Iterable where .Element = i32)` is the same as the constraint `(Container & Iterable) where .Element = i32` which is the same as the constraint `Container where .Element = i32`. If the rewrite for an associated constant is ambiguous, the facet type is -rejected during [constraint resolution](#constraint-resolution). +rejected during [constraint resolution](#rewrite-constraint-resolution). + +> **Alternative considered:** We could perhaps say that `X & Y` results in a +> facet type where the type of `R` has the union of the interface of `A` and the +> interface of `B`, and that `C & X` similarly results in a facet type where the +> type of `R` has the union of the interface of `A` and the interface originally +> specified by `C`. ##### Combining constraints with `and` @@ -2653,7 +2775,7 @@ It's possible for one `=` constraint in a `where` to refer to another. When this happens, the facet type `C where A and B` is interpreted as `(C where A) where B`, so rewrites in `A` are applied immediately to names in `B`, but rewrites in `B` are not applied to names in `A` until the facet type is -[resolved](#constraint-resolution): +[resolved](#rewrite-constraint-resolution): ``` interface C { @@ -2693,7 +2815,7 @@ var n: i32; fn F(T:! B) -> T.(A.T) { return n; } ``` -##### Combining constraints with `impl as` and `is` +##### Combining constraints with `impl as` and `impls` Within an interface or named constraint, the `impl T as C` syntax does not permit `=` constraints to be specified directly. However, such constraints can @@ -2755,35 +2877,38 @@ interface E { } ``` -The same rules apply to `is` constraints. Note that `.T == U` constraints are +The same rules apply to `impls` constraints. Note that `.T == U` constraints are also not allowed in this context, because the reference to `.T` is rewritten to `.Self.T`, and `.Self` is ambiguous. ``` -// ❌ Rewrite constraint specified directly in `is`. -fn F[T:! A where .U is (A where .T = i32)](); +// ❌ Rewrite constraint specified directly in `impls`. +fn F[T:! A where .U impls (A where .T = i32)](); // ❌ Reference to `.T` in same-type constraint is ambiguous: // does this mean the outer or inner `.Self.T`? -fn G[T:! A where .U is (A where .T == i32)](); +fn G[T:! A where .U impls (A where .T == i32)](); // ✅ Not specified directly, but does not result // in any rewrites being performed. Return type // is not rewritten to `i32`. -fn H[T:! type where .Self is C]() -> T.(A.U); +fn H[T:! type where .Self impls C]() -> T.(A.U); // ✅ Return type is rewritten to `i32`. fn I[T:! C]() -> T.(A.U); ``` -##### Constraint resolution +##### Rewrite constraint resolution + +**FIXME: Linked** -When a facet type is used as the declared type of a type `T`, the constraints +When a facet type is used as the declared type of a facet `T`, the constraints that were specified within that facet type are _resolved_ to determine the constraints that apply to `T`. This happens: -- When the constraint is used explicitly, when declaring a generic parameter - or associated constant of the form `T:! Constraint`. +- When the constraint is used explicitly, when declaring symbolic binding, + like a generic parameter or associated constant, of the form + `T:! Constraint`. - When declaring that a type implements a constraint with an `impl` declaration, such as `impl T as Constraint`. Note that this does not include `impl ... as` constraints appearing in `interface` or `constraint` @@ -2798,7 +2923,7 @@ abstract constraints into a set of constraints on `T`: where no rewrite applies within any other rewrite. If no fixed point exists, the generic parameter declaration or `impl` declaration is invalid. - Rewrites are performed throughout the other constraints in the facet type -- - that is, in any `==` constraints and `is` constraints -- and the type + that is, in any `==` constraints and `impls` constraints -- and the type `.Self` is replaced by `T` throughout the constraint. ``` @@ -2888,9 +3013,9 @@ apply only one rewrite in each of the above cases, satisfying property 2. ###### Qualified name lookup -Qualified name lookup into either a type parameter or into an expression whose -type is a symbolic type `T` -- either a type parameter or an associated type -- -considers names from the facet type `C` of `T`, that is, from `T`’s declared +Qualified name lookup into either a facet parameter or into an expression whose +type is a symbolic type `T` -- either a facet parameter or an associated facet +-- considers names from the facet type `C` of `T`, that is, from `T`’s declared type. ``` @@ -2921,7 +3046,7 @@ declared type. ``` interface SelfIface { - fn Get[me: Self]() -> Self; + fn Get[self: Self]() -> Self; } class UsesSelf(T:! type) { // Equivalent to `fn Make() -> UsesSelf(T)*;` @@ -2935,12 +3060,10 @@ let x: UsesSelf(i32)* = UsesSelf(i32).Make(); // ✅ `Self = UsesSelf(i32)` is substituted into the type // of `SelfIface.Get`, so the type of `UsesSelf(i32).(SelfIface.Get)` -// is `fn [me: UsesSelf(i32)]() -> UsesSelf(i32)`. +// is `fn [self: UsesSelf(i32)]() -> UsesSelf(i32)`. let y: UsesSelf(i32) = x->Get(); ``` -None of this is new in this proposal. This proposal adds the rule: - If a facet type `C` into which lookup is performed includes a `where` clause saying `.N = U`, and the result of qualified name lookup is the associated constant `N`, that result is replaced by `U`, and the type of the result is the @@ -2959,17 +3082,17 @@ interface B { // More explicitly, this is of type `A where .(A.T) = Self.(B.U)` let V:! A where .T = U; } -// Type of T is B. -fn F[T:! B](x: T) { - // The type of the expression `T` is `B`. - // `T.V` finds `B.V` with type `A where .(A.T) = Self.(B.U)`. - // We substitute `Self` = `T` giving the type of `u` as - // `A where .(A.T) = T.(B.U)`. - let u:! auto = T.V; - // The type of `u` is `A where .(A.T) = T.(B.U)`. +// Type of W is B. +fn F[W:! B](x: W) { + // The type of the expression `W` is `B`. + // `W.V` finds `B.V` with type `A where .(A.T) = Self.(B.U)`. + // We substitute `Self` = `W` giving the type of `u` as + // `A where .(A.T) = W.(B.U)`. + let u:! auto = W.V; + // The type of `u` is `A where .(A.T) = W.(B.U)`. // Lookup for `u.T` resolves it to `u.(A.T)`. - // So the result of the qualified member access is `T.(B.U)`, - // and the type of `v` is the type of `T.(B.U)`, namely `type`. + // So the result of the qualified member access is `W.(B.U)`, + // and the type of `v` is the type of `W.(B.U)`, namely `type`. // No substitution is performed in this step. let v:! auto = u.T; } @@ -2978,7 +3101,7 @@ fn F[T:! B](x: T) { The more complex case of ``` -fn F2[T:! B where .U = i32](x: T); +fn F2[Z:! B where .U = i32](x: Z); ``` is discussed later. @@ -2987,8 +3110,8 @@ is discussed later. At various points during the type-checking of a Carbon program, we need to substitute a set of (binding, value) pairs into a symbolic value. We saw an -example above: substituting `Self = T` into the type `A where .(A.T) = Self.U` -to produce the value `A where .(A.T) = T.U`. Another important case is the +example above: substituting `Self = W` into the type `A where .(A.T) = Self.U` +to produce the value `A where .(A.T) = W.U`. Another important case is the substitution of inferred parameter values into the type of a function when type-checking a function call: @@ -3007,8 +3130,8 @@ Qualified name lookup is not re-done as a result of type substitution. For a template, we imagine there’s a completely separate step that happens before type substitution, where qualified name lookup is redone based on the actual value of template arguments; this proceeds as described in the previous section. -Otherwise, we performed the qualified name lookup when type-checking the -generic, and do not do it again: +Otherwise, we performed the qualified name lookup when type-checking symbolic +expressions, and do not do it again: ``` interface IfaceHasX { @@ -3090,15 +3213,15 @@ interface B { let V:! A where .T = U; } -// Type of the expression `T` is `B where .(B.U) = i32` -fn F2[T:! B where .U = i32](x: T) { - // The type of the expression `T` is `B where .U = i32`. - // `T.V` is looked up and finds the associated type `(B.V)`. +// Type of the expression `Z` is `B where .(B.U) = i32` +fn F2[Z:! B where .U = i32](x: Z) { + // The type of the expression `Z` is `B where .U = i32`. + // `Z.V` is looked up and finds the associated facet `(B.V)`. // The declared type is `A where .(A.T) = Self.U`. - // We substitute `Self = T` with rewrite `.U = i32`. + // We substitute `Self = Z` with rewrite `.U = i32`. // The resulting type is `A where .(A.T) = i32`. - // So `u` is `T.V` with type `A where .(A.T) = i32`. - let u:! auto = T.V; + // So `u` is `Z.V` with type `A where .(A.T) = i32`. + let u:! auto = Z.V; // The type of `u` is `A where .(A.T) = i32`. // Lookup for `u.T` resolves it to `u.(A.T)`. // So the result of the qualified member access is `i32`, @@ -3213,19 +3336,18 @@ doesn't compromise the soundness of the type system. #### Same-type constraints -**FIXME** - A same-type constraint describes that two type expressions are known to evaluate to the same value. Unlike a rewrite constraint, however, the two type -expressions are treated as distinct types when type-checking a generic that -refers to them. +expressions are treated as distinct types when type-checking a symbolic +expression that refers to them. Same-type constraints are brought into scope, looked up, and resolved exactly as if there were a `SameAs(U:! type)` interface and a `T == U` impl corresponded to `T is SameAs(U)`, except that `==` is commutative. As such, it's not possible to ask for a list of types that are the same as a given type, nor to ask whether there exists a type that is the same as a given type and has some property. But -it is possible to ask whether two types are (non-transitively) the same. +it is possible to ask whether two types are (non-transitively) **FIXME: add +"known to be"?** the same. In order for same-type constraints to be useful, they must allow the two types to be treated as actually being the same in some context. This can be @@ -3234,7 +3356,7 @@ built-in implementation of `ImplicitAs`: ``` final impl forall [T:! type, U:! type where .Self == T] T as ImplicitAs(U) { - fn Convert[me: Self](other: U) -> U { ... } + fn Convert[self: Self](other: U) -> U { ... } } ``` @@ -3260,6 +3382,8 @@ cannot pick a common type when given two distinct type expressions, even if we know they evaluate to the same type, because we would not know which API the result should have. +**FIXME: Left off here.** + ##### Implementation of same-type `ImplicitAs` It is possible to implement the above `impl` of `ImplicitAs` directly in Carbon, @@ -3689,110 +3813,6 @@ constraint clauses, but that only works because they place the `where` in a different position in a declaration. In Carbon, the `where` is attached to a type in a parameter list that is already using commas to separate parameters. -#### Recursive constraints - -**FIXME: LINKED** - -We sometimes need to constrain a type to equal one of its associated facets. In -this first example, we want to represent the function `Abs` which will return -`Self` for some but not all types, so we use an associated facet `MagnitudeType` -to encode the return type: - -``` -interface HasAbs { - extend Numeric; - let MagnitudeType:! Numeric; - fn Abs[self: Self]() -> MagnitudeType; -} -``` - -For types representing subsets of the real numbers, such as `i32` or `f32`, the -`MagnitudeType` will match `Self`, the type implementing an interface. For types -representing complex numbers, the types will be different. For example, the -`Abs()` applied to a `Complex64` value would produce a `f32` result. The goal is -to write a constraint to restrict to the first case. - -In a second example, when you take the slice of a type implementing `Container` -you get a type implementing `Container` which may or may not be the same type as -the original container type. However, taking the slice of a slice always gives -you the same type, and some functions want to only operate on containers whose -slice type is the same as the container type. - -To solve this problem, we think of `Self` as an actual associated facet member -of every interface. We can then address it using `.Self` in a `where` clause, -like any other associated facet member. - -``` -fn Relu[T:! HasAbs where .MagnitudeType = .Self](x: T) { - // T.MagnitudeType == T so the following is allowed: - return (x.Abs() + x) / 2; -} -fn UseContainer[T:! Container where .SliceType = .Self](c: T) -> bool { - // T.SliceType == T so `c` and `c.Slice(...)` can be compared: - return c == c.Slice(...); -} -``` - -Notice that in an interface definition, `Self` refers to the type implementing -this interface while `.Self` refers to the associated facet currently being -defined. - -``` -interface Container { - let ElementType:! type; - - let SliceType:! Container - where .ElementType = ElementType and - .SliceType = .Self; - - fn GetSlice[addr self: Self*] - (start: IteratorType, end: IteratorType) -> SliceType; -} -``` - -Note that [naming](#named-constraint-constants) a recursive constraint using -using the [`constraint` introducer](#named-constraints) approach, we can name -the implementing type using `Self` instead of `.Self`, since they refer to the -same type: - -``` -constraint RealAbs { - extend HasAbs where .MagnitudeType = Self; - // Equivalent to: - extend HasAbs where .MagnitudeType = .Self; -} - -constraint ContainerIsSlice { - extend Container where .SliceType = Self; - // Equivalent to: - extend Container where .SliceType = .Self; -} -``` - -The `.Self` construct follows these rules: - -- `X :!` introduces `.Self:! type`, where references to `.Self` are resolved - to `X`. This allows you to use `.Self` as an interface parameter as in - `X:! I(.Self)`. -- `A where` introduces `.Self:! A` and `.Foo` for each member `Foo` of `A` -- It's an error to reference `.Self` if it refers to more than one different - thing or isn't a type. -- You get the innermost, most-specific type for `.Self` if it is introduced - twice in a scope. By the previous rule, it is only legal if they all refer - to the same facet binding. -- `.Self` may not be on the left side of the `=` in a rewrite constraint. - -So in `X:! A where ...`, `.Self` is introduced twice, after the `:!` and the -`where`. This is allowed since both times it means `X`. After the `:!`, `.Self` -has the type `type`, which gets refined to `A` after the `where`. In contrast, -it is an error if `.Self` could mean two different things, as in: - -``` -// ❌ Illegal: `.Self` could mean `T` or `T.A`. -fn F[T:! InterfaceA where .A impls - (InterfaceB where .B = .Self)](x: T); -``` - ### Satisfying both facet types If the two types being constrained to be equal have been declared with different @@ -3988,8 +4008,8 @@ interface Graph { Note that this is a case that can use an `==` same-type constraint instead of an `=` rewrite constraint. -- **Associated facet implements interface:** Given these definitions (omitting - `ElementType` for brevity): +- **[Associated facet](#associated-facets) implements interface:** Given these + definitions (omitting `ElementType` for brevity): ```carbon interface IteratorInterface { ... } @@ -4018,15 +4038,15 @@ interface Graph { **FIXME: LINKED** There are times when a function will pass a symbolic facet parameter of the -function as an argument to a [parameterized type](#parameterized-types), as in -the previous case, and in addition the function needs the result to implement a -specific interface. +function as an argument to a [parameterized type](#parameterized-types), and the +function needs the result to implement a specific interface. ``` -// Some parameterized type. +// A parameterized type class Vector(T:! type) { ... } -// Parameterized type implements interface only for some arguments. +// The parameterized type `Vector` implements interface +// `Printable` only for some arguments. impl Vector(String) as Printable { ... } // Constraint: `T` such that `Vector(T)` implements `Printable` @@ -4049,7 +4069,7 @@ In this case, we need some other type to implement an interface parameterized by a symbolic facet parameter. The syntax for this case follows the previous case, except now the `.Self` parameter is on the interface to the right of the `impls`. For example, we might need a type parameter `T` to support explicit -conversion from an integer type like `i32`: +conversion from an `i32`: ``` interface As(T:! type) { @@ -4057,7 +4077,7 @@ interface As(T:! type) { } fn Double[T:! Mul where i32 impls As(.Self)](x: T) -> T { - return x * (2 as T); + return x * ((2 as i32) as T); } ``` @@ -4066,13 +4086,14 @@ fn Double[T:! Mul where i32 impls As(.Self)](x: T) -> T { **FIXME: LINKED** Now consider the case that the symbolic facet parameter is going to be used as -an argument to a parameterized type in a function body, not in the signature. If -the parameterized type was explicitly mentioned in the signature, the -[implied constraint](#implied-constraints) feature would ensure all of its -requirements were met. The developer can create a trivial +an argument to a [parameterized type](#parameterized-types) in a function body, +but not in the signature. If the parameterized type was explicitly mentioned in +the signature, the [implied constraint](#implied-constraints) feature would +ensure all of its requirements were met. To say a parameterized type is allowed +to be passed a specific argument, just write that it `impls type`, which all +types do. This is a trivial case of a [parameterized type implements interface](#parameterized-type-implements-interface) -`where` constraint to just say the type is a legal with this argument, by saying -that the parameterized type implements `type`, which all types do. +`where` constraint. For example, a function that adds its parameters to a `HashSet` to deduplicate them, needs them to be `Hashable` and so on. To say "`T` is a type where @@ -4090,7 +4111,8 @@ fn NumDistinct[T:! type where HashSet(.Self) impls type] ``` This has the same advantages over repeating the constraints on `HashSet` -arguments in the type of `T` as the general implied constraints above. +arguments in the type of `T` as other +[implied constraints](#implied-constraints). ### Named constraint constants From 9b8a58c28597d9474bb59eca9bd30257bac6c767 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 13 Sep 2023 20:41:32 +0000 Subject: [PATCH 58/83] Checkpoint progress. --- docs/design/generics/details.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 2e6f377876d5a..ab932b2c6e856 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -4900,10 +4900,10 @@ Note that [the rules for specialization](#lookup-resolution-and-specialization) do allow there to be more than one `impl` to be defined for a type, by unambiguously picking one as most specific. -**References:** Implementation coherence is -[defined in terminology](terminology.md#coherence), and is -[a goal for Carbon generics](goals.md#coherence). More detail can be found in -[this appendix with the rationale and alternatives considered](appendix-coherence.md). +> **References:** Implementation coherence is +> [defined in terminology](terminology.md#coherence), and is +> [a goal for Carbon generics](goals.md#coherence). More detail can be found in +> [this appendix with the rationale and alternatives considered](appendix-coherence.md). Only the implementing interface and types (self type and type parameters) in the type structure are relevant here; an interface mentioned in a constraint is not From a1b91e154162860b5224ff87171d076c43755797 Mon Sep 17 00:00:00 2001 From: Josh L Date: Wed, 13 Sep 2023 21:05:01 +0000 Subject: [PATCH 59/83] Checkpoint progress. --- docs/design/generics/details.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index ab932b2c6e856..866db0ac1b067 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -4328,7 +4328,7 @@ class ThenCompare( } } -let template SongByArtistThenTitle: auto = +let template SongByArtistThenTitle:! auto = ThenCompare(Song, (SongByArtist, SongByTitle)); var s1: Song = ...; var s2: SongByArtistThenTitle = From 9f8bea22a301ca7da9bd2d5528448f5577a62a99 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 15 Sep 2023 17:23:07 +0000 Subject: [PATCH 60/83] Checkpoint progress. --- docs/design/generics/details.md | 280 +++++++++++++++++++------------- 1 file changed, 163 insertions(+), 117 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 866db0ac1b067..c671a25afacaf 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -66,7 +66,6 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Implementation of same-type `ImplicitAs`](#implementation-of-same-type-implicitas) - [Manual type equality](#manual-type-equality) - [Observe declarations](#observe-declarations) - - [`observe` declarations](#observe-declarations-1) - [Implements constraints](#implements-constraints) - [Implied constraints](#implied-constraints) - [Combining constraints](#combining-constraints) @@ -121,6 +120,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Observing a type implements an interface](#observing-a-type-implements-an-interface) - [Observing interface requirements](#observing-interface-requirements) - [Observing blanket impl declarations](#observing-blanket-impl-declarations) + - [Observing equal to a type implementing an interface](#observing-equal-to-a-type-implementing-an-interface) - [Operator overloading](#operator-overloading) - [Binary operators](#binary-operators) - [`like` operator for implicit conversions](#like-operator-for-implicit-conversions) @@ -3337,17 +3337,17 @@ doesn't compromise the soundness of the type system. #### Same-type constraints A same-type constraint describes that two type expressions are known to evaluate -to the same value. Unlike a rewrite constraint, however, the two type -expressions are treated as distinct types when type-checking a symbolic -expression that refers to them. +to the same value. Unlike a [rewrite constraint](#rewrite-constraints), however, +the two type expressions are treated as distinct types when type-checking a +symbolic expression that refers to them. Same-type constraints are brought into scope, looked up, and resolved exactly as if there were a `SameAs(U:! type)` interface and a `T == U` impl corresponded to `T is SameAs(U)`, except that `==` is commutative. As such, it's not possible to ask for a list of types that are the same as a given type, nor to ask whether there exists a type that is the same as a given type and has some property. But -it is possible to ask whether two types are (non-transitively) **FIXME: add -"known to be"?** the same. +it is possible to ask whether two types are (non-transitively) known to be the +same. In order for same-type constraints to be useful, they must allow the two types to be treated as actually being the same in some context. This can be @@ -3382,8 +3382,6 @@ cannot pick a common type when given two distinct type expressions, even if we know they evaluate to the same type, because we would not know which API the result should have. -**FIXME: Left off here.** - ##### Implementation of same-type `ImplicitAs` It is possible to implement the above `impl` of `ImplicitAs` directly in Carbon, @@ -3411,28 +3409,17 @@ The transition from `(T as ImplicitAs(U)).Convert`, where we know that `U == T`, to `EqualConverter.Convert`, where we know that `.T = U`, allows a same-type constraint to be used to perform a rewrite. -This implementation is in use in Carbon Explorer. - ##### Manual type equality -Imagine we have some function with symbolic parameters: - -``` -fn F[T:! SomeInterface](x: T) { - x.G(x.H()); -} -``` - -We want to know if the return type of method `T.H` is the same as the parameter -type of `T.G` in order to typecheck the function. However, determining whether -two [type expressions](terminology.md#type-expression) are transitively equal is -in general undecidable, as +A same-type constraint establishes +[type expressions](terminology.md#type-expression) are equal, and allows +implicit conversions between them. However, determining whether two type +expressions are _transitively_ equal is in general undecidable, as [has been shown in Swift](https://forums.swift.org/t/swift-type-checking-is-undecidable/39024). -Carbon's approach is to only allow implicit conversions between two type -expressions that are constrained to be equal in a single where clause. This -means that if two type expressions are only transitively equal, the user will -need to include a sequence of casts or use an +Carbon does not combine these equalities between type expressions. This means +that if two type expressions are only transitively equal, the user will need to +include a sequence of casts or use an [`observe` declaration](#observe-declarations) to convert between them. Given this interface `Transitive` that has associated facets that are @@ -3469,46 +3456,6 @@ fn F[T:! Transitive](t: T) { } ``` -A value of type `A`, such as the return value of `GetA()`, has the API of `P`. -Any such value also implements `Q`, and since the compiler can see that by way -of a single `where` equality, values of type `A` are treated as if they -implement `Q` [without extending it](terminology.md#extending-an-impl). However, -the compiler will require a cast to `B` or `C` to see that the type implements -`R`. - -``` -fn TakesPQR[U:! P & Q & R](u: U); - -fn G[T:! Transitive](t: T) { - var a: T.A = t.GetA(); - - // ✅ Allowed: `T.A` implements `P` and - // includes its API, as if it extends `P`. - a.InP(); - - // ✅ Allowed: `T.A` implements (but does not - // extend) `Q`. - a.(Q.InQ)(); - - // ❌ Not allowed: a.InQ(); - - // ✅ Allowed: values of type `T.A` may be cast - // to `T.B`, which extends and implements `Q`. - (a as T.B).InQ(); - - // ✅ Allowed: `T.B` implements (but does not - // extend) `R`. - (a as T.B).(R.InR)(); - // ❌ Not allowed: (a as T.B).InR(); - - // ❌ Not allowed: TakesPQR(a); - - // ✅ Allowed: `T.B` implements `P`, `Q`, and - // `R`, though `T.B` doesn't extend `P` or `R`. - TakesPQR(a as T.B); -} -``` - The compiler may have several different `where` clauses to consider, particularly when an interface has associated facets that recursively satisfy the same interface. For example, given this interface `Commute`: @@ -3516,12 +3463,31 @@ the same interface. For example, given this interface `Commute`: ``` interface Commute { let X:! Commute; + // **FIXME: Not allowed (at least not by Explorer) + // since `Commute` is incomplete here.** let Y:! Commute where .X == X.Y; fn GetX[self: Self]() -> X; fn GetY[self: Self]() -> Y; fn TakesXXY[self: Self](xxy: X.X.Y); } + +// **FIXME: Maybe the following?** +interface Commute; +constraint Helper(T:! Commute); + +interface Commute { + let X:! Commute; + let Y:! Helper(X); + + fn GetX[self: Self]() -> X; + fn GetY[self: Self]() -> Y; + fn TakesXXY[self: Self](xxy: X.X.Y); +} + +constraint Helper(T:! Commute) { + extend Commute where .X == T.Y; +} ``` and a function `H` taking a value with some type implementing this interface, @@ -3572,9 +3538,8 @@ can find a sequence that prove they are equal. ##### Observe declarations -Same-type constraints are non-transitive, just like other constraints such as -`ImplicitAs`. The developer can use an `observe` declaration to bring a new -same-type constraint into scope: +Same-type constraints are non-transitive, just like `ImplicitAs`. The developer +can use an `observe` declaration to bring a new same-type constraint into scope: ``` observe A == B == C; @@ -3588,14 +3553,13 @@ impl A as SameAs(C) { ... } where the `impl` makes use of `A is SameAs(B)` and `B is SameAs(C)`. -##### `observe` declarations - -An `observe` declaration lists a sequence of +In general, an `observe` declaration lists a sequence of [type expressions](terminology.md#type-expression) that are equal by some same-type `where` constraints. These `observe` declarations may be included in an `interface` definition or a function body, as in: ``` +// **FIXME: Not clear how to fix this example.** interface Commute { let X:! Commute; let Y:! Commute where .X == X.Y; @@ -3614,6 +3578,7 @@ expression in the sequence by a single `where` equality constraint. In this example, ``` +// **FIXME: Not clear how to fix this example.** interface Commute { let X:! Commute; let Y:! Commute where .X == X.Y; @@ -3633,7 +3598,7 @@ the `observe` declaration in the `Transitive` interface definition provides the link between associated facets `A` and `C` that allows function `F` to type check. -``` +```carbon interface P { fn InP[self: Self](); } interface Q { fn InQ[self: Self](); } interface R { fn InR[self: Self](); } @@ -3651,32 +3616,70 @@ interface Transitive { observe A == B == C; } -fn TakesPQR[U:! P & Q & R](u: U); - fn F[T:! Transitive](t: T) { var a: T.A = t.GetA(); - // ✅ Allowed: `T.A` == `T.C` + // ✅ Allowed: `T.A` values implicitly convert to + // `T.C` using `observe` in interface definition. t.TakesC(a); - a.(R.InR()); + + // ✅ Allowed: `T.C` extends and implements `R`. + (a as T.C).InR(); +} +``` + +Only the current type is searched for interface implementations, so the call to +`InR()` would be illegal without the cast. However, an +[`observe`...`==`...`impls` declaration](#observing-equal-to-a-type-implementing-an-interface) +can be used to identify interfaces that must be implemented through some equal +type. This does not [extending](terminology.md#extending-an-impl) the API of the +type, that is solely determined by the definition of the type. Continuing the +previous example: + +```carbon +fn TakesPQR[U:! P & Q & R](u: U); + +fn G[T:! Transitive](t: T) { + var a: T.A = t.GetA(); + + // ✅ Allowed: `T.A` implements `P` and + // includes its API, as if it extends `P`. + a.InP(); + + // ❌ Illegal: only the current type is + // searched for interface implementations. + a.(Q.InQ()); + + // ✅ Allowed: values of type `T.A` may be cast + // to `T.B`, which extends and implements `Q`. + (a as T.B).InQ(); + + // ✅ Allowed: `T.A` == `T.B` that implements `Q`. + observe T.A == T.B impls Q; + a.(Q.InQ()); + + // ❌ Illegal: `T.A` still does not extend `Q`. + a.InQ(); // ✅ Allowed: `T.A` implements `P`, - // `T.A` == `T.B` that implements `Q`, and - // `T.A` == `T.C` that implements `R`. + // `T.A` == `T.B` that implements `Q` (observe above), + // and `T.A` == `T.C` that implements `R`. + observe T.A == T.C impls R; TakesPQR(a); } ``` -Since adding an `observe` declaration only adds non-extending implementations of -interfaces to symbolic facets, they may be added without breaking existing code. +Since adding an `observe`...`impls` declaration only adds non-extending +implementations of interfaces to symbolic facets, they may be added without +breaking existing code. #### Implements constraints -**FIXME** - -A `where` clause can express that a facet binding must implement an interface. -This is more flexible than the usual approach of including that interface in the -type since it can be applied to associated facet members as well. +An _implements constraint_ is written `where T impls C`, and expresses that the +facet `T` must implement the requirements of facet type `C`. This is more +flexible than using +[`&` to add a constraint](#combining-interfaces-by-anding-facet-types) since it +can be applied to [associated facet](#associated-facets) members as well. In the following example, normally the `ElementType` of a `Container` can be any type. The `SortContainer` function, however, takes a pointer to a type @@ -3696,7 +3699,8 @@ fn SortContainer In contrast to a [rewrite constraint](#rewrite-constraints) or a [same-type constraint](#same-type-constraints), this does not say what type -`ElementType` exactly is, just that it must satisfy some facet type. +`ElementType` exactly is, just that it must satisfy requirements of some facet +type. **Note:** `Container` defines `ElementType` as having type `type`, but `ContainerType.ElementType` has type `Comparable`. This is because @@ -3704,8 +3708,13 @@ In contrast to a [rewrite constraint](#rewrite-constraints) or a `Container`. This means we need to be a bit careful when talking about the type of `ContainerType` when there is a `where` clause modifying it. -**FIXME: can also be used to say a facet implements an interface without -including that interface's API.** +An implements constraint can be applied to [`.Self`](#recursive-constraints), as +in `I where .Self impls C`. This has the same requirements as `I & C`, but that +`where` clause does not affect the API. This means that a +[symbolic facet binding](#symbolic-facet-bindings) with that facet type, so `T` +in `T:! I where .Self impls C`, is represented by an +[archetype](terminology.md#archetype) that implements both `I` and `C`, but only +[extends](terminology.md#extending-an-impl) `I`. ##### Implied constraints @@ -3732,7 +3741,7 @@ class HashMap( In this case, `KeyType` gets `Hashable` and so on as _implied constraints_. Effectively that means that these functions are automatically rewritten to add a -`where`...`impls` constraint on `KeyType`: +`where .Self impls` constraint on `KeyType`: ``` fn LookUp[ @@ -3760,10 +3769,10 @@ will have already satisfied these constraints. This implied constraint is equivalent to the explicit constraint that each parameter and return type [is legal](#must-be-legal-type-argument-constraints). -**Note:** These implied constraints affect the _requirements_ of a symbolic -facet parameter, but not its _member names_. This way you can always look at the -declaration to see how name resolution works, without having to look up the -definitions of everything it is used as an argument to. +> **Note:** These implied constraints affect the _requirements_ of a symbolic +> facet parameter, but not its _member names_. This way you can always look at +> the declaration to see how name resolution works, without having to look up +> the definitions of everything it is used as an argument to. **Limitation:** To limit readability concerns and ambiguity, this feature is limited to a single signature. Consider this interface declaration: @@ -3815,18 +3824,22 @@ type in a parameter list that is already using commas to separate parameters. ### Satisfying both facet types -If the two types being constrained to be equal have been declared with different -facet types, then the actual type value they are set to will have to satisfy -both constraints. For example, if `SortedContainer.ElementType` is declared to -be `Comparable`, then in these declarations: +If the two facet bindings being constrained to be equal, using either a +[rewrite constraint](#rewrite-constraints) or a +[same-type constraint](#same-type-constraints), have been declared with +different facet types, then the actual type value they are set to will have to +satisfy the requirements of both facet types. For example, if +`SortedContainer.ElementType` is declared to have a `Comparable` requirement, +then in these declarations: ``` +// With `=` rewrite constraint: fn Contains_Rewrite [SC:! SortedContainer, CT:! Container where .ElementType = SC.ElementType] (haystack: SC, needles: CT) -> bool; -// Similarly with `==` same-type constraints: +// With `==` same-type constraint: fn Contains_SameType [SC:! SortedContainer, CT:! Container where .ElementType == SC.ElementType] @@ -3834,27 +3847,30 @@ fn Contains_SameType ``` the `where` constraints in both cases mean `CT.ElementType` must satisfy -`Comparable` as well. +`Comparable` as well. However, the behavior inside the body of these two inside +the body of the two functions is different. + +In `Contains_Rewrite`, `CT.ElementType` is rewritten to `SC.ElementType` and +uses the facet type of `SC.ElementType`. -**FIXME: Separate rewrite and `==` behavior.** +In `Contains_SameType`, the `where` clause does not affect the API of +`CT.ElementType`, and it would not even be considered to implement `Comparable` +unless there is some declaration like +`observe CT.ElementType == SC.ElementType impls Comparable`. Even then, the +items from the `needles` container won't directly have a `Compare` method +member. -However, inside the body of `Contains`, `CT.ElementType` will act like the -implementation of `Comparable` is declared without [`extend`](#extend-impl). -That is, items from the `needles` container won't directly have a `Compare` -method member, but can still be implicitly converted to `Comparable` and can -still call `Compare` using the compound member access syntax, -`needle.(Comparable.Compare)(elt)`. The rule is that an `==` `where` constraint -between two type variables does not modify the set of member names of either -type. (If you write `where .ElementType = String` with a `=` and a concrete -type, then `.ElementType` is actually set to `String` including the complete -`String` API.) +The rule is that an same-type `where` constraint between two type variables does +not modify the set of member names of either type. This is in contrast to +rewrite constraints like `where .ElementType = String` with a `=`, then +`.ElementType` is actually set to `String` including the complete `String` API. Note that `==` constraints are symmetric, so the previous declaration of -`Contains` is equivalent to an alternative declaration where `CT` is declared -first and the `where` clause is attached to `SortedContainer`: +`Contains_SameType` is equivalent to an alternative declaration where `CT` is +declared first and the `where` clause is attached to `SortedContainer`: ``` -fn Contains +fn Contains_SameType_Equivalent [CT:! Container, SC:! SortedContainer where .ElementType == CT.ElementType] (haystack: SC, needles: CT) -> bool; @@ -3907,6 +3923,8 @@ redundant ways to express a restriction, following the ### Referencing names in the interface being defined +**FIXME: left off here.** + The constraint in a `where` clause is required to only reference earlier names from this scope, as in this example: @@ -6052,7 +6070,7 @@ One situation where this occurs is when there is a chain of [interfaces requiring other interfaces](#interface-requiring-other-interfaces-revisited). During the `impl` validation done during type checking, Carbon will only consider the interfaces that are direct requirements of the interfaces the type -is known to implement. An `observe...impls` declaration can be used to add an +is known to implement. An `observe`...`impls` declaration can be used to add an interface that is a direct requirement to the set of interfaces whose direct requirements will be considered for that type. This allows a developer to provide a proof that there is a sequence of requirements that demonstrate that a @@ -6095,7 +6113,7 @@ compiler can't determine the impl to select. ### Observing blanket impl declarations -An `observe...impls` declaration can also be used to observe that a type +An `observe`...`impls` declaration can also be used to observe that a type implements an interface because there is a [blanket impl declaration](#blanket-impl-declarations) in terms of requirements a type is already known to satisfy. Without an `observe` declaration, Carbon @@ -6141,6 +6159,34 @@ In the case of an error, a quality Carbon implementation will do a deeper search for chains of requirements and blanket impl declarations and suggest `observe` declarations that would make the code compile if any solution is found. +### Observing equal to a type implementing an interface + +The [`observe`...`==` form](#observe-declarations) can be combined with the +`observe`...`impls` form to show that a type implements an interface because it +is equal to another type that is known to implement that interface. + +```carbon +interface I { + fn F(); +} + +fn G(T:! I, U:! type where .Self == T) { + // ❌ Illegal: No implementation of `I` for `U`. + U.(I.F)(); + + // ✅ Allowed: Implementation of `I` for `U` + // through `T`. + observe U == T impls I; + U.(I.F)(); + + // ❌ Illegal: `U` does not extend `I`. + U.F(); +} +``` + +Multiple `==` clauses are allowed in an `observe` declaration, so you may write +`observe A == B == C impls I;`. + ## Operator overloading Operations are overloaded for a type by implementing an interface specific to @@ -6493,7 +6539,7 @@ parameters may be used to specify types in the declarations of its members, such as data fields, member functions, and even interfaces being implemented. For example, a container type might be parameterized by the type of its elements: -**FIXME: member functions may have additional parameters.** +**FIXME: add a bit that member functions may have additional parameters.** ``` class HashMap( From 50523e768f1d34d688941cac6f78a9e213c770b2 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 15 Sep 2023 20:14:43 +0000 Subject: [PATCH 61/83] Finished where constraints section --- docs/design/generics/details.md | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index c671a25afacaf..aa6eae517b257 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -2565,8 +2565,6 @@ Implements constraints switched using the `impls` keyword in #### Recursive constraints -**FIXME: LINKED** - We sometimes need to constrain a type to equal one of its associated facets. In this first example, we want to represent the function `Abs` which will return `Self` for some but not all types, so we use an associated facet `MagnitudeType` @@ -2900,7 +2898,7 @@ fn I[T:! C]() -> T.(A.U); ##### Rewrite constraint resolution -**FIXME: Linked** +**FIXME: Should the precise rules for constraints be moved into an appendix?** When a facet type is used as the declared type of a facet `T`, the constraints that were specified within that facet type are _resolved_ to determine the @@ -3482,6 +3480,7 @@ interface Commute { fn GetX[self: Self]() -> X; fn GetY[self: Self]() -> Y; + // **FIXME: Don't think it is legal to write `X.X.Y` here.** fn TakesXXY[self: Self](xxy: X.X.Y); } @@ -3923,8 +3922,6 @@ redundant ways to express a restriction, following the ### Referencing names in the interface being defined -**FIXME: left off here.** - The constraint in a `where` clause is required to only reference earlier names from this scope, as in this example: @@ -4053,11 +4050,10 @@ interface Graph { #### Parameterized type implements interface -**FIXME: LINKED** - -There are times when a function will pass a symbolic facet parameter of the -function as an argument to a [parameterized type](#parameterized-types), and the -function needs the result to implement a specific interface. +There are times when a function will pass a +[symbolic facet parameter](#symbolic-facet-bindings) of the function as an +argument to a [parameterized type](#parameterized-types), and the function needs +the result to implement a specific interface. ``` // A parameterized type @@ -4081,13 +4077,11 @@ fn PrintThree #### Another type implements parameterized interface -**FIXME: LINKED** - In this case, we need some other type to implement an interface parameterized by -a symbolic facet parameter. The syntax for this case follows the previous case, -except now the `.Self` parameter is on the interface to the right of the -`impls`. For example, we might need a type parameter `T` to support explicit -conversion from an `i32`: +a [symbolic facet parameter](#symbolic-facet-bindings). The syntax for this case +follows the previous case, except now the `.Self` parameter is on the interface +to the right of the `impls`. For example, we might need a type parameter `T` to +support explicit conversion from an `i32`: ``` interface As(T:! type) { @@ -4101,8 +4095,6 @@ fn Double[T:! Mul where i32 impls As(.Self)](x: T) -> T { #### Must be legal type argument constraints -**FIXME: LINKED** - Now consider the case that the symbolic facet parameter is going to be used as an argument to a [parameterized type](#parameterized-types) in a function body, but not in the signature. If the parameterized type was explicitly mentioned in From 6fa60d7115e5846c6b14cfc5eb9b334483eeaa17 Mon Sep 17 00:00:00 2001 From: Josh L Date: Sat, 16 Sep 2023 01:16:43 +0000 Subject: [PATCH 62/83] Checkpoint progress. --- docs/design/generics/details.md | 682 ++++++++++++++++---------------- 1 file changed, 350 insertions(+), 332 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index aa6eae517b257..05fff4e93791e 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -84,7 +84,6 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Example: Multiple implementations of the same interface](#example-multiple-implementations-of-the-same-interface) - [Example: Creating an impl out of other implementations](#example-creating-an-impl-out-of-other-implementations) - [Sized types and facet types](#sized-types-and-facet-types) - - [`TypeId`](#typeid) - [Destructor constraints](#destructor-constraints) - [Generic `let`](#generic-let) - [Parameterized impl declarations](#parameterized-impl-declarations) @@ -239,7 +238,7 @@ An [interface](terminology.md#interface), defines an API that a given type can implement. For example, an interface capturing a linear-algebra vector API might have two methods: -``` +```carbon interface Vector { // Here the `Self` keyword means // "the type implementing this interface". @@ -276,7 +275,7 @@ what is associated with the implementing type for that interface. An impl may be defined inline inside the type definition: -``` +```carbon class Point_Inline { var x: f64; var y: f64; @@ -298,7 +297,7 @@ class Point_Inline { Interfaces that are implemented inline with the `extend` keyword contribute to the type's API: -``` +```carbon class Point_Extend { var x: f64; var y: f64; @@ -321,7 +320,7 @@ Assert(p1.Add(p1) == p2); Without `extend`, those methods may only be accessed with [qualified member names and compound member access](#qualified-member-names-and-compound-member-access): -``` +```carbon // Point_Inline did not use `extend` when // implementing `Vector`: var a: Point_Inline = {.x = 1.0, .y = 2.0}; @@ -350,7 +349,7 @@ This syntax was changed to use `extend` in An impl may also be defined after the type definition, by naming the type between `impl` and `as`: -``` +```carbon class Point_OutOfLine { var x: f64; var y: f64; @@ -434,7 +433,7 @@ done using [`extend` to add to the type's API](#extend-impl), only the declaration in the class definition will use the `extend` keyword, as in this example: -``` +```carbon class Point_ExtendForward { var x: f64; var y: f64; @@ -463,7 +462,7 @@ More about forward declaring implementations in To implement more than one interface when defining a type, simply include an `impl` block or forward declaration per interface. -``` +```carbon class Point_2Extend { var x: f64; var y: f64; @@ -490,7 +489,7 @@ names to be given the same implementation? It seems like that might be a common case, but we won't really know if this is an important case until we get more experience. -``` +```carbon class Player { var name: String; extend impl as Icon { @@ -512,7 +511,7 @@ class Player { To avoid name collisions, you can't extend implementations of two interfaces that have a name in common: -``` +```carbon class GameBoard { extend impl as Drawable { fn Draw[self: Self]() { ... } @@ -533,7 +532,7 @@ cluttering the API of that type or to avoid a name collision with another member of that type. A syntax for reusing method implementations allows us to include names from an implementation selectively: -``` +```carbon class Point_ReuseMethodInImpl { var x: f64; var y: f64; @@ -594,7 +593,7 @@ impl Point_ReuseByOutOfLine as Vector { ### Qualified member names and compound member access -``` +```carbon class Point_NoExtend { var x: f64; var y: f64; @@ -611,7 +610,7 @@ whether or not the implementation is done with an expression writes the member's _qualified name_ in the parentheses of the [compound member access syntax](/docs/design/expressions/member_access.md): -``` +```carbon var p1: Point_NoExtend = {.x = 1.0, .y = 2.0}; var p2: Point_NoExtend = {.x = 2.0, .y = 4.0}; Assert(p1.(Vector.Scale)(2.0) == p2); @@ -623,7 +622,7 @@ the names of members of `Point_NoExtend`. So if there was another interface `Drawable` with method `Draw` defined in the `Plot` package also implemented for `Point_NoExtend`, as in: -``` +```carbon package Plot; import Points; @@ -636,7 +635,7 @@ impl Points.Point_NoExtend as Drawable { ... } You could access `Draw` with a qualified name: -``` +```carbon import Plot; import Points; @@ -671,7 +670,7 @@ declaration of the `impl`. Here is a function that can accept values of any type that has implemented the `Vector` interface: -``` +```carbon fn AddAndScaleGeneric[T:! Vector](a: T, b: T, s: f64) -> T { return a.Add(b).Scale(s); } @@ -713,7 +712,7 @@ to adding a `Vector` [qualification](#qualified-member-names-and-compound-member-access) to replace all simple member accesses of `T`: -``` +```carbon fn AddAndScaleGeneric[T:! Vector](a: T, b: T, s: Double) -> T { return a.(Vector.Add)(b).(Vector.Scale)(s); } @@ -740,7 +739,7 @@ The behavior of calling `AddAndScaleGeneric` with a value of a specific type like `Point_Extend` is to set `T` to `Point_Extend` after all the names have been qualified. -``` +```carbon // AddAndScaleGeneric with T = Point_Extend fn AddAndScaleForPoint_Extend( a: Point_Extend, b: Point_Extend, s: Double) @@ -754,7 +753,7 @@ even when the type supplied by the caller does not [extend the implementation of the interface](terminology.md#extending-an-impl), like `Point_NoExtend`: -``` +```carbon // AddAndScaleGeneric with T = Point_NoExtend fn AddAndScaleForPoint_NoExtend( a: Point_NoExtend, b: Point_NoExtend, s: Double) @@ -786,7 +785,7 @@ substitution process to determine the return type, but the result may be a symbolic value. In this example of calling a checked generic from another checked generic, -``` +```carbon fn DoubleThreeTimes[U:! Vector](a: U) -> U { return AddAndScaleGeneric(a, a, 2.0).Scale(2.0); } @@ -802,7 +801,7 @@ capabilities of `U`. For example, given a parameterized type `GeneralPoint` implementing `Vector`, and a function that takes a `GeneralPoint` and calls `AddAndScaleGeneric` with it: -``` +```carbon class GeneralPoint(C:! Numeric) { impl as Vector { ... } fn Get[self: Self](i: i32) -> C; @@ -912,7 +911,7 @@ A named constraint definition can contain interface requirements using `require Self impls` declarations and names using `alias` declarations. Note that this allows us to declare the aspects of a facet type directly. -``` +```carbon constraint VectorLegoFish { // Interface implementation requirements require Self impls Vector; @@ -928,7 +927,7 @@ A `require Self impls` requirement may alternatively be on a named constraint, instead of an interface, to add all the requirements of another named constraint without adding any of the names: -``` +```carbon constraint DrawVectorLegoFish { // The same as requiring both `Vector` and `LegoFish`. require Self impls VectorLegoFish; @@ -957,14 +956,14 @@ constructs we do expect them to use will be defined in terms of them. For example, if `type` were not a keyword, we could define the Carbon builtin `type` as: -``` +```carbon constraint type { } ``` That is, `type` is the facet type with no requirements (so matches every type), and defines no names. -``` +```carbon fn Identity[T:! type](x: T) -> T { // Can accept values of any type. But, since we know nothing about the // type, we don't know about any operations on `x` inside this function. @@ -991,7 +990,7 @@ There is an analogy between declarations used in a `template constraint` and in an `interface` definition. If an `interface` `I` has (non-`alias`, non-`require`) declarations `X`, `Y`, and `Z`, like so: -``` +```carbon interface I { X; Y; @@ -1002,7 +1001,7 @@ interface I { Then a type implementing `I` would have `impl as I` with definitions for `X`, `Y`, and `Z`, as in: -``` +```carbon class ImplementsI { // ... impl as I { @@ -1015,7 +1014,7 @@ class ImplementsI { But a `template constraint`, `S`: -``` +```carbon template constraint S { X; Y; @@ -1025,7 +1024,7 @@ template constraint S { would match any type with definitions for `X`, `Y`, and `Z` directly: -``` +```carbon class ImplementsS { // ... X { ... } @@ -1044,7 +1043,7 @@ type `I2` as long as the requirements of `I1` are a superset of the requirements of `I2`. This means a value `x: T` may be passed to functions requiring types to satisfy `I2`, as in this example: -``` +```carbon interface Printable { fn Print[self: Self](); } interface Renderable { fn Draw[self: Self](); } @@ -1076,7 +1075,7 @@ implemented, we provide a combination operator on facet types, written `&`. This operator gives the facet type with the union of all the requirements and the union of the names minus any conflicts. -``` +```carbon interface Printable { fn Print[self: Self](); } @@ -1117,7 +1116,7 @@ PrintThenDraw(s); It is an error to use any names that conflict between the two interfaces. -``` +```carbon interface Renderable { fn Center[self: Self]() -> (i32, i32); fn Draw[self: Self](); @@ -1137,7 +1136,7 @@ Conflicts can be resolved at the call site using a [qualified member access expression](#qualified-member-names-and-compound-member-access), or by defining a named constraint explicitly and renaming the methods: -``` +```carbon constraint RenderableAndEndOfGame { require Self impls Renderable; require Self impls EndOfGame; @@ -1170,7 +1169,7 @@ the possibility of name conflicts. Note this means the operation is not commutative. If we call this operator `[&]`, then `A [&] B` has the names of `A` and `B [&] A` has the names of `B`. -``` +```carbon // `Printable [&] Renderable` is syntactic sugar for this facet type: constraint { require Self impls Printable; @@ -1221,7 +1220,7 @@ requires all containers to also satisfy the requirements of semantics and `require Self impls` syntax as we do for [named constraints](#named-constraints): -``` +```carbon interface Equatable { fn Equals[self: Self](rhs: Self) -> bool; } interface Iterable { @@ -1249,7 +1248,7 @@ Like with named constraints, an interface implementation requirement doesn't by itself add any names to the interface, but again those can be added with `alias` declarations: -``` +```carbon interface Hashable { fn Hash[self: Self]() -> u64; require Self impls Equatable; @@ -1275,7 +1274,7 @@ When implementing an interface, we should allow implementing the aliased names as well. In the case of `Hashable` above, this includes all the members of `Equatable`, obviating the need to implement `Equatable` itself: -``` +```carbon class Song { extend impl as Hashable { fn Hash[self: Self]() -> u64 { ... } @@ -1298,7 +1297,7 @@ benefits: We expect this concept to be common enough to warrant dedicated `interface` syntax: -``` +```carbon interface Equatable { fn Equals[self: Self](rhs: Self) -> bool; } interface Hashable { @@ -1341,7 +1340,7 @@ The [`SetAlgebra` protocol](https://swiftdoc.org/v5.1/protocol/setalgebra/) extends `Equatable` and `ExpressibleByArrayLiteral`, which would be declared in Carbon: -``` +```carbon interface SetAlgebra { extend Equatable; extend ExpressibleByArrayLiteral; @@ -1353,7 +1352,7 @@ interface SetAlgebra { [associated constants](terminology.md#associated-entity) also defined in the body in parameters or constraints of the interface being extended. -``` +```carbon // A type can implement `ConvertibleTo` many times, // using different values of `T`. interface ConvertibleTo(T:! type) { ... } @@ -1373,7 +1372,7 @@ interface PreferredConversion { The `extend` declaration makes sense with the same meaning inside a [`constraint`](#named-constraints) definition, and so is also supported. -``` +```carbon interface Media { fn Play[self: Self](); } @@ -1390,7 +1389,7 @@ constraint Combined { This definition of `Combined` is equivalent to requiring both the `Media` and `Job` interfaces being implemented, and aliases their methods. -``` +```carbon // Equivalent constraint Combined { require Self impls Media; @@ -1404,7 +1403,7 @@ Notice how `Combined` has aliases for all the methods in the interfaces it requires. That condition is sufficient to allow a type to `impl` the named constraint: -``` +```carbon class Song { extend impl as Combined { fn Play[self: Self]() { ... } @@ -1415,7 +1414,7 @@ class Song { This is equivalent to implementing the required interfaces directly: -``` +```carbon class Song { extend impl as Media { fn Play[self: Self]() { ... } @@ -1432,7 +1431,7 @@ This is just like when you get an implementation of `Equatable` by implementing Conversely, an `interface` can extend a `constraint`: -``` +```carbon interface MovieCodec { extend Combined; @@ -1443,7 +1442,7 @@ interface MovieCodec { This gives `MovieCodec` the same requirements and names as `Combined`, and so is equivalent to: -``` +```carbon interface MovieCodec { require Self impls Media; alias Play = Media.Play; @@ -1459,7 +1458,7 @@ interface MovieCodec { Consider this set of interfaces, simplified from [this example generic graph library doc](https://docs.google.com/document/d/15Brjv8NO_96jseSesqer5HbghqSTJICJ_fTaZOH0Mg4/edit?usp=sharing&resourcekey=0-CYSbd6-xF8vYHv9m1rolEQ): -``` +```carbon interface Graph { fn Source[addr self: Self*](e: EdgeDescriptor) -> VertexDescriptor; fn Target[addr self: Self*](e: EdgeDescriptor) -> VertexDescriptor; @@ -1481,7 +1480,7 @@ We need to specify what happens when a graph type implements both `IncidenceGraph` and `EdgeListGraph`, since both interfaces extend the `Graph` interface. -``` +```carbon class MyEdgeListIncidenceGraph { extend impl as IncidenceGraph { ... } extend impl as EdgeListGraph { ... } @@ -1495,7 +1494,7 @@ though could be defined in the `impl` block of `IncidenceGraph`, - `IncidenceGraph` implements all methods of `Graph`, `EdgeListGraph` implements none of them. - ``` + ```carbon class MyEdgeListIncidenceGraph { extend impl as IncidenceGraph { fn Source[self: Self](e: EdgeDescriptor) -> VertexDescriptor { ... } @@ -1512,7 +1511,7 @@ though could be defined in the `impl` block of `IncidenceGraph`, - `IncidenceGraph` and `EdgeListGraph` implement all methods of `Graph` between them, but with no overlap. - ``` + ```carbon class MyEdgeListIncidenceGraph { extend impl as IncidenceGraph { fn Source[self: Self](e: EdgeDescriptor) -> VertexDescriptor { ... } @@ -1528,7 +1527,7 @@ though could be defined in the `impl` block of `IncidenceGraph`, - Explicitly implementing `Graph`. - ``` + ```carbon class MyEdgeListIncidenceGraph { extend impl as Graph { fn Source[self: Self](e: EdgeDescriptor) -> VertexDescriptor { ... } @@ -1541,7 +1540,7 @@ though could be defined in the `impl` block of `IncidenceGraph`, - Implementing `Graph` out-of-line. - ``` + ```carbon class MyEdgeListIncidenceGraph { extend impl as IncidenceGraph { ... } extend impl as EdgeListGraph { ... } @@ -1584,7 +1583,7 @@ therefore provides a way to create new types APIs, in particular with different interface implementations, by [adapting](terminology.md#adapting-a-type) them: -``` +```carbon interface Printable { fn Print[self: Self](); } @@ -1632,7 +1631,7 @@ This allows developers to provide implementations of new interfaces (as in Inside an adapter, the `Self` type matches the adapter. Members of the original type may be accessed either by a cast: -``` +```carbon class SongByTitle { adapt Song; extend impl as Comparable { @@ -1645,7 +1644,7 @@ class SongByTitle { or using a qualified member access expression: -``` +```carbon class SongByTitle { adapt Song; extend impl as Comparable { @@ -1673,7 +1672,7 @@ compiler provides it as Consider a type with a facet parameter, like a hash map: -``` +```carbon interface Hashable { ... } class HashMap(KeyT:! Hashable, ValueT:! type) { fn Find[self: Self](key: KeyT) -> Optional(ValueT); @@ -1683,7 +1682,7 @@ class HashMap(KeyT:! Hashable, ValueT:! type) { A user of this type will provide specific values for the key and value types: -``` +```carbon class Song { extend impl as Hashable { ... } // ... @@ -1700,7 +1699,7 @@ specified as requirements. This allows us to evaluate when we can convert between two different arguments to a parameterized type. Consider two adapters of `Song` that implement `Hashable`: -``` +```carbon class PlayableSong { adapt Song; extend impl as Hashable = Song; @@ -1732,7 +1731,7 @@ replacing an interface implementation. Users would indicate that an adapter starts from the original type's existing API by using the `extend` keyword before `adapt`: -``` +```carbon class Song { extend impl as Hashable { ... } extend impl as Printable { ... } @@ -1772,7 +1771,7 @@ To avoid or resolve name conflicts between interfaces, an `impl` may be declared without [`extend`](#extend-impl). The names in that interface may then be pulled in individually or renamed using `alias` declarations. -``` +```carbon class SongRenderToPrintDriver { extend adapt Song; @@ -1804,7 +1803,7 @@ an adapter that provides an implementation of `CompareLib.Comparable` for [`extend` facility of adapters](#extending-adapter) to preserve the `SongLib.Song` API. -``` +```carbon import CompareLib; import SongLib; @@ -1827,7 +1826,7 @@ class Song { The caller can either convert `SongLib.Song` values to `Song` when calling `CompareLib.Sort` or just start with `Song` values in the first place. -``` +```carbon var lib_song: SongLib.Song = ...; CompareLib.Sort((lib_song as Song,)); @@ -1846,7 +1845,7 @@ syntax. For example, given an interface `Comparable` for deciding which value is smaller: -``` +```carbon interface Comparable { fn Less[self: Self](rhs: Self) -> bool; } @@ -1855,7 +1854,7 @@ interface Comparable { We might define an adapter that implements `Comparable` for types that define another interface `Difference`: -``` +```carbon interface Difference { fn Sub[self: Self](rhs: Self) -> i32; } @@ -1881,7 +1880,7 @@ class IntWrapper { **TODO:** If we support function types, we could potentially pass a function to use to the adapter instead: -``` +```carbon class ComparableFromDifferenceFn (T:! type, Difference:! fnty(T, T)->i32) { adapt T; @@ -1911,7 +1910,7 @@ implement the interface on that instead. Any member of the class can cast its `self` parameter to the adapter type when it wants to make use of the private impl. -``` +```carbon // Public, in API file class Complex64 { // ... @@ -1946,7 +1945,7 @@ Consider a case where a function will call several functions from an interface that the type does not [extend the implementation of](terminology.md#extending-an-impl). -``` +```carbon interface DrawingContext { fn SetPen[self: Self](...); fn SetFill[self: Self](...); @@ -1962,7 +1961,7 @@ extends the implementation of the interface. This avoids having to [qualify](terminology.md#qualified-member-access-expression) each call to methods in the interface. -``` +```carbon class DrawInWindow { adapt Window; extend impl as DrawingContext = Window; @@ -2011,7 +2010,7 @@ without assigning a value. As constants, they are declared using the `let` introducer. For example, a fixed-dimensional point type could have the dimension as an associated constant. -``` +```carbon interface NSpacePoint { let N:! i32; // The following require: 0 <= i < N. @@ -2026,7 +2025,7 @@ 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`: -``` +```carbon class Point2D { extend impl as NSpacePoint where .N = 2 { fn Get[addr self: Self*](i: i32) -> f64 { ... } @@ -2055,7 +2054,7 @@ keyword. The list of assignments is subject to two restrictions: These values may be accessed as members of the type: -``` +```carbon Assert(Point2D.N == 2); Assert(Point3D.N == 3); @@ -2091,7 +2090,7 @@ To be consistent with normal [class function](/docs/design/classes.md#class-functions) declaration syntax, associated class functions are written using a `fn` declaration: -``` +```carbon interface DeserializeFromString { fn Deserialize(serialized: String) -> Self; } @@ -2132,7 +2131,7 @@ implementation. We already have one example of this: the `Self` type discussed interface declares that each implementation will provide a facet constant under a specified name. For example: -``` +```carbon interface StackAssociatedFacet { let ElementType:! type; fn Push[addr self: Self*](value: ElementType); @@ -2147,7 +2146,7 @@ accepting or returning values with the type `ElementType`, which any implementer of `StackAssociatedFacet` must also define. For example, maybe a `DynamicArray` [parameterized type](#parameterized-types) implements `StackAssociatedFacet`: -``` +```carbon class DynamicArray(T:! type) { class IteratorType { ... } fn Begin[addr self: Self*]() -> IteratorType; @@ -2179,7 +2178,7 @@ The keyword `Self` can be used after the `as` in an `impl` declaration as a shorthand for the type being implemented, including in the `where` clause specifying the values of associated facets, as in: -``` +```carbon impl VeryLongTypeName as Add // `Self` here means `VeryLongTypeName` where .Result == Self { @@ -2197,7 +2196,7 @@ The definition of the `StackAssociatedFacet` is sufficient for writing a checked-generic function that operates on anything implementing that interface, for example: -``` +```carbon fn PeekAtTopOfStack[StackType:! StackAssociatedFacet](s: StackType*) -> StackType.ElementType { var top: StackType.ElementType = s->Pop(); @@ -2217,7 +2216,7 @@ Outside the checked-generic, associated facets have the concrete facet values determined by impl lookup, rather than the erased version of that facet used inside a checked-generic. -``` +```carbon var my_array: DynamicArray(i32) = (1, 2, 3); // PeekAtTopOfStack's `StackType` is set to `DynamicArray(i32)` // with `StackType.ElementType` set to `i32`. @@ -2231,7 +2230,7 @@ discussed in the [return type section](#return-type). Associated facets can also be implemented using a [member type](/docs/design/classes.md#member-type). -``` +```carbon interface Container { let IteratorType:! Iterator; ... @@ -2269,7 +2268,7 @@ also known as _generic interfaces_. To write a parameterized version of the stack interface, instead of using associated constants, write a parameter list after the name of the interface: -``` +```carbon interface StackParameterized(ElementType:! type) { fn Push[addr self: Self*](value: ElementType); fn Pop[addr self: Self*]() -> ElementType; @@ -2280,7 +2279,7 @@ interface StackParameterized(ElementType:! type) { Then `StackParameterized(Fruit)` and `StackParameterized(Veggie)` would be considered different interfaces, with distinct implementations. -``` +```carbon class Produce { var fruit: DynamicArray(Fruit); var veggie: DynamicArray(Veggie); @@ -2314,7 +2313,7 @@ parameters can't be deduced. For example, if we were to rewrite [the `PeekAtTopOfStack` example in the "associated facets" section](#associated-facets) for `StackParameterized(T)` it would generate a compile error: -``` +```carbon // ❌ Error: can't deduce interface parameter `T`. fn BrokenPeekAtTopOfStackParameterized [T:! type, StackType:! StackParameterized(T)] @@ -2325,7 +2324,7 @@ This error is because the compiler can not determine if `T` should be `Fruit` or `Veggie` when passing in argument of type `Produce*`. Either `T` should be replaced by a concrete type, like `Fruit`: -``` +```carbon fn PeekAtTopOfFruitStack [StackType:! StackParameterized(Fruit)] (s: StackType*) -> T { ... } @@ -2338,7 +2337,7 @@ var top_fruit: Fruit = Or the value for `T` would be passed explicitly, using `where` constraints described [in this section](#another-type-implements-parameterized-interface): -``` +```carbon fn PeekAtTopOfStackParameterizedImpl (T:! type, StackType:! StackParameterized(T), s: StackType*) -> T { ... @@ -2364,7 +2363,7 @@ Parameterized interfaces are useful for `OrderedWith(T)` interfaces have a parameter that allows type to be comparable with multiple other types, as in: -``` +```carbon interface EqWith(T:! type) { fn Equal[self: Self](rhs: T) -> bool; ... @@ -2395,7 +2394,7 @@ vast majority of cases. As an example, if we had an interface that allowed a type to define how the tuple-member-read operator would work, the index of the member could be an interface parameter: -``` +```carbon interface ReadTupleMember(index:! u32) { let T:! type; // Returns self[index] @@ -2409,7 +2408,7 @@ indices to be associated with different values of `T`. **Caveat:** When implementing an interface twice for a type, the interface parameters are required to always be different. For example: -``` +```carbon interface Map(FromType:! type, ToType:! type) { fn Map[addr self: Self*](needle: FromType) -> Optional(ToType); } @@ -2426,7 +2425,7 @@ In this case, it would be better to have an [adapting type](#adapting-types) to contain the `impl` for the reverse map lookup, instead of implementing the `Map` interface twice: -``` +```carbon class Bijection(FromType:! type, ToType:! type) { extend impl as Map(FromType, ToType) { ... } } @@ -2462,7 +2461,7 @@ operator that adds constraints to a [facet type](#facet-types). The where operator can be applied to a facet type in a declaration context: -``` +```carbon // Constraints on generic function parameters: fn F[V:! D where ...](v: V) { ... } @@ -2570,7 +2569,7 @@ this first example, we want to represent the function `Abs` which will return `Self` for some but not all types, so we use an associated facet `MagnitudeType` to encode the return type: -``` +```carbon interface HasAbs { extend Numeric; let MagnitudeType:! Numeric; @@ -2594,7 +2593,7 @@ To solve this problem, we think of `Self` as an actual associated facet member of every interface. We can then address it using `.Self` in a `where` clause, like any other associated facet member. -``` +```carbon fn Relu[T:! HasAbs where .MagnitudeType = .Self](x: T) { // T.MagnitudeType == T so the following is allowed: return (x.Abs() + x) / 2; @@ -2609,7 +2608,7 @@ Notice that in an interface definition, `Self` refers to the type implementing this interface while `.Self` refers to the associated facet currently being defined. -``` +```carbon interface Container { let ElementType:! type; @@ -2629,7 +2628,7 @@ Note that [naming](#named-constraint-constants) a recursive constraint using the implementing type using `Self` instead of `.Self`, since they refer to the same type: -``` +```carbon constraint RealAbs { extend HasAbs where .MagnitudeType = Self; // Equivalent to: @@ -2662,7 +2661,7 @@ So in `X:! A where ...`, `.Self` is introduced twice, after the `:!` and the has the type `type`, which gets refined to `A` after the `where`. In contrast, it is an error if `.Self` could mean two different things, as in: -``` +```carbon // ❌ Illegal: `.Self` could mean `T` or `T.A`. fn F[T:! InterfaceA where .A impls (InterfaceB where .B = .Self)](x: T); @@ -2673,7 +2672,7 @@ fn F[T:! InterfaceA where .A impls In a rewrite constraint, the left-hand operand of `=` must be a `.` followed by the name of an associated constant. `.Self` is not permitted. -``` +```carbon interface RewriteSelf { // ❌ Error: `.Self` is not the name of an associated constant. let Me:! type where .Self = Self; @@ -2697,7 +2696,7 @@ longer names an associated constant of the constrained type, and is instead treated as a rewrite rule that expands to the right-hand side of the constraint, with any mentioned parameters substituted into that type. -``` +```carbon interface Container { let Element:! type; let Slice:! Container where .Element = Element; @@ -2775,7 +2774,7 @@ happens, the facet type `C where A and B` is interpreted as `B`, but rewrites in `B` are not applied to names in `A` until the facet type is [resolved](#rewrite-constraint-resolution): -``` +```carbon interface C { let T:! type; let U:! type; @@ -2796,7 +2795,7 @@ fn F[A:! C where .U = .T.Me and .T = M]() {} Within an interface or named constraint, `extends` can be used to extend a constraint that has rewrites. -``` +```carbon interface A { let T:! type; let U:! type; @@ -2822,7 +2821,7 @@ rewrites. Because these constraints don't change the type of `T`, rewrite constraints in this context will never result in a rewrite being performed, and instead are equivalent to `==` constraints. -``` +```carbon interface A { let T:! type; let U:! type; @@ -2860,7 +2859,7 @@ an expression: In an `impl as C` or `impl T as C` declaration in an interface or named constraint, `C` is not allowed to directly specify any `=` constraints: -``` +```carbon // Compile-time identity function. fn Identity[T:! type](x:! T) -> T { return x; } @@ -2879,7 +2878,7 @@ The same rules apply to `impls` constraints. Note that `.T == U` constraints are also not allowed in this context, because the reference to `.T` is rewritten to `.Self.T`, and `.Self` is ambiguous. -``` +```carbon // ❌ Rewrite constraint specified directly in `impls`. fn F[T:! A where .U impls (A where .T = i32)](); @@ -2924,7 +2923,7 @@ abstract constraints into a set of constraints on `T`: that is, in any `==` constraints and `impls` constraints -- and the type `.Self` is replaced by `T` throughout the constraint. -``` +```carbon // ✅ `.T` in `.U = .T` is rewritten to `i32` when initially // forming the facet type. // Nothing to do during constraint resolution. @@ -2946,7 +2945,7 @@ In the latter case, we have found a cycle and can reject the constraint. Note that it's not sufficient to expand only a single rewrite until we hit this condition: -``` +```carbon // ❌ Constraint resolution fails because // no fixed point of rewrites exists. // If we only expand the right-hand side of `.T`, @@ -2966,7 +2965,7 @@ If a facet type is never used to constrain a type, it is never subject to constraint resolution, and it is possible for a facet type to be formed for which constraint resolution would always fail. For example: -``` +```carbon package Broken api; interface X { @@ -3016,7 +3015,7 @@ type is a symbolic type `T` -- either a facet parameter or an associated facet -- considers names from the facet type `C` of `T`, that is, from `T`’s declared type. -``` +```carbon interface C { let M:! i32; let U:! C; @@ -3042,7 +3041,7 @@ type of the result is determined by performing a substitution. For an interface, interface (and enclosing classes or interfaces, if any) are substituted into the declared type. -``` +```carbon interface SelfIface { fn Get[self: Self]() -> Self; } @@ -3071,7 +3070,7 @@ the facet type `C`, and we don’t consider either the declared type or value of the associated constant at all for this kind of constraint. Going through an example in detail: -``` +```carbon interface A { let T:! type; } @@ -3098,7 +3097,7 @@ fn F[W:! B](x: W) { The more complex case of -``` +```carbon fn F2[Z:! B where .U = i32](x: Z); ``` @@ -3113,7 +3112,7 @@ to produce the value `A where .(A.T) = W.U`. Another important case is the substitution of inferred parameter values into the type of a function when type-checking a function call: -``` +```carbon fn F[T:! C](x: T) -> T; fn G(n: i32) -> i32 { // Deduces T = i32, which is substituted @@ -3131,7 +3130,7 @@ template arguments; this proceeds as described in the previous section. Otherwise, we performed the qualified name lookup when type-checking symbolic expressions, and do not do it again: -``` +```carbon interface IfaceHasX { let X:! type; } @@ -3159,7 +3158,7 @@ During substitution, we might find a member access that named an opaque symbolic associated constant in the original value can now be resolved to some specific value. It’s important that we perform this resolution: -``` +```carbon interface A { let T:! type; } @@ -3202,7 +3201,7 @@ substituting into. Continuing an example from [qualified name lookup](#qualified-name-lookup): -``` +```carbon interface A { let T:! type; } @@ -3231,7 +3230,7 @@ fn F2[Z:! B where .U = i32](x: Z) { ###### Examples -``` +```carbon interface Container { let Element:! type; } @@ -3245,7 +3244,7 @@ interface SliceableContainer { fn Bad[T:! SliceableContainer where .Element = .Slice.Element](x: T.Element) {} ``` -``` +```carbon interface Helper { let D:! type; } @@ -3264,7 +3263,7 @@ fn Allowed(T:! Example, x: T.C.D); fn Error(T:! Example where .B = .C.D, x: T.C.D); ``` -``` +```carbon interface Allowed; interface AllowedBase { let A:! Allowed; @@ -3279,7 +3278,7 @@ interface Allowed { fn F(T:! Allowed, x: ((T.A).A).A); ``` -``` +```carbon interface MoveYsRight; constraint ForwardDeclaredConstraint(X:! MoveYsRight); interface MoveYsRight { @@ -3352,7 +3351,7 @@ to be treated as actually being the same in some context. This can be accomplished by the use of `==` constraints in an `impl`, such as in the built-in implementation of `ImplicitAs`: -``` +```carbon final impl forall [T:! type, U:! type where .Self == T] T as ImplicitAs(U) { fn Convert[self: Self](other: U) -> U { ... } } @@ -3363,7 +3362,7 @@ made available implicitly – for example, by writing `impl forall [T:! type] T as ImplicitAs(T)` – but in more complex examples that turns out to be problematic. Consider: -``` +```carbon interface CommonTypeWith(U:! type) { let Result:! type; } @@ -3386,7 +3385,7 @@ It is possible to implement the above `impl` of `ImplicitAs` directly in Carbon, without a compiler builtin, by taking advantage of the built-in conversion between `C where .A = X` and `C where .A == X`: -``` +```carbon interface EqualConverter { let T:! type; fn Convert(t: T) -> Self; @@ -3423,7 +3422,7 @@ include a sequence of casts or use an Given this interface `Transitive` that has associated facets that are constrained to all be equal, with interfaces `P`, `Q`, and `R`: -``` +```carbon interface P { fn InP[self: Self](); } interface Q { fn InQ[self: Self](); } interface R { fn InR[self: Self](); } @@ -3441,7 +3440,7 @@ interface Transitive { A cast to `B` is needed to call `TakesC` with a value of type `A`, so each step only relies on one equality: -``` +```carbon fn F[T:! Transitive](t: T) { // ✅ Allowed t.TakesC(t.GetA() as T.B); @@ -3458,7 +3457,7 @@ The compiler may have several different `where` clauses to consider, particularly when an interface has associated facets that recursively satisfy the same interface. For example, given this interface `Commute`: -``` +```carbon interface Commute { let X:! Commute; // **FIXME: Not allowed (at least not by Explorer) @@ -3492,7 +3491,7 @@ constraint Helper(T:! Commute) { and a function `H` taking a value with some type implementing this interface, then the following would be legal statements in `H`: -``` +```carbon fn H[C: Commute](c: C) { // ✅ Legal: argument has type `C.X.X.Y` c.TakesXXY(c.GetX().GetX().GetY()); @@ -3540,13 +3539,13 @@ can find a sequence that prove they are equal. Same-type constraints are non-transitive, just like `ImplicitAs`. The developer can use an `observe` declaration to bring a new same-type constraint into scope: -``` +```carbon observe A == B == C; ``` notionally does much the same thing as -``` +```carbon impl A as SameAs(C) { ... } ``` @@ -3557,7 +3556,7 @@ In general, an `observe` declaration lists a sequence of same-type `where` constraints. These `observe` declarations may be included in an `interface` definition or a function body, as in: -``` +```carbon // **FIXME: Not clear how to fix this example.** interface Commute { let X:! Commute; @@ -3576,7 +3575,7 @@ Every type expression after the first must be equal to some earlier type expression in the sequence by a single `where` equality constraint. In this example, -``` +```carbon // **FIXME: Not clear how to fix this example.** interface Commute { let X:! Commute; @@ -3685,7 +3684,7 @@ type. The `SortContainer` function, however, takes a pointer to a type satisfying `Container` with the additional constraint that its `ElementType` must satisfy the `Comparable` interface, using an `impls` constraint: -``` +```carbon interface Container { let ElementType:! type; ... @@ -3720,7 +3719,7 @@ in `T:! I where .Self impls C`, is represented by an Imagine we have a checked-generic function that accepts an arbitrary `HashMap` [parameterized type](#parameterized-types): -``` +```carbon fn LookUp[KeyType:! type](hm: HashMap(KeyType, i32)*, k: KeyType) -> i32; @@ -3732,7 +3731,7 @@ fn PrintValueOrDefault[KeyType:! Printable, The `KeyType` in these declarations does not visibly satisfy the requirements of `HashMap`, which requires the type implement `Hashable` and other interfaces: -``` +```carbon class HashMap( KeyType:! Hashable & EqualityComparable & Movable, ...) { ... } @@ -3742,7 +3741,7 @@ In this case, `KeyType` gets `Hashable` and so on as _implied constraints_. Effectively that means that these functions are automatically rewritten to add a `where .Self impls` constraint on `KeyType`: -``` +```carbon fn LookUp[ KeyType:! type where .Self impls Hashable & EqualityComparable & Movable] @@ -3776,7 +3775,7 @@ parameter and return type [is legal](#must-be-legal-type-argument-constraints). **Limitation:** To limit readability concerns and ambiguity, this feature is limited to a single signature. Consider this interface declaration: -``` +```carbon interface GraphNode { let Edge:! type; fn EdgesFrom[self: Self]() -> HashSet(Edge); @@ -3808,7 +3807,7 @@ Constraints can be combined by separating constraint clauses with the `and` keyword. This example expresses a constraint that two associated facets are equal and satisfy an interface: -``` +```carbon fn EqualContainers [CT1:! Container, CT2:! Container where .ElementType impls HasEquality @@ -3831,7 +3830,7 @@ satisfy the requirements of both facet types. For example, if `SortedContainer.ElementType` is declared to have a `Comparable` requirement, then in these declarations: -``` +```carbon // With `=` rewrite constraint: fn Contains_Rewrite [SC:! SortedContainer, @@ -3868,7 +3867,7 @@ Note that `==` constraints are symmetric, so the previous declaration of `Contains_SameType` is equivalent to an alternative declaration where `CT` is declared first and the `where` clause is attached to `SortedContainer`: -``` +```carbon fn Contains_SameType_Equivalent [CT:! Container, SC:! SortedContainer where .ElementType == CT.ElementType] @@ -3903,7 +3902,7 @@ fn F[A:! type, B:! type where A == .Self, C:! type](a: A, b: B, c: C); This includes `where` clauses used in an `impl` declaration: -``` +```carbon // ❌ Error: `where T impls B` does not use `.Self` or a designator impl forall [T:! type] T as A where T impls B {} // ✅ Allowed @@ -3925,7 +3924,7 @@ redundant ways to express a restriction, following the The constraint in a `where` clause is required to only reference earlier names from this scope, as in this example: -``` +```carbon // ❌ Illegal: `E` references `V` declared later. interface Graph { let E: Edge where .V = V; @@ -3952,7 +3951,7 @@ interface Graph { `Stack where .ElementType = i32` is a facet type that restricts to types that implement the `Stack` interface with integer elements, as in: - ``` + ```carbon fn SumIntStack[T:! Stack where .ElementType = i32] (s: T*) -> i32 { var sum: i32 = 0; @@ -3971,7 +3970,7 @@ interface Graph { - **One [associated constant](#associated-constants) must equal another:** For example with this definition of the interface `PointCloud`: - ``` + ```carbon interface PointCloud { let Dim:! i32; let PointT:! NSpacePoint where .N = Dim; @@ -3986,7 +3985,7 @@ interface Graph { For example, we could make the `ElementType` of an `Iterator` interface equal to the `ElementType` of a `Container` interface as follows: - ``` + ```carbon interface Iterator { let ElementType:! type; ... @@ -4001,7 +4000,7 @@ interface Graph { In a function signature, this may be done by referencing an earlier parameter: - ``` + ```carbon fn Map[CT:! Container, FT:! Function where .InputType = CT.ElementType] (c: CT, f: FT) -> Vector(FT.OutputType); @@ -4010,7 +4009,7 @@ interface Graph { In that example, `FT.InputType` is constrained to equal `CT.InputType`. Given an interface with two associated facets - ``` + ```carbon interface PairInterface { let Left:! type; let Right:! type; @@ -4055,7 +4054,7 @@ There are times when a function will pass a argument to a [parameterized type](#parameterized-types), and the function needs the result to implement a specific interface. -``` +```carbon // A parameterized type class Vector(T:! type) { ... } @@ -4083,7 +4082,7 @@ follows the previous case, except now the `.Self` parameter is on the interface to the right of the `impls`. For example, we might need a type parameter `T` to support explicit conversion from an `i32`: -``` +```carbon interface As(T:! type) { fn Convert[self: Self]() -> T; } @@ -4109,7 +4108,7 @@ For example, a function that adds its parameters to a `HashSet` to deduplicate them, needs them to be `Hashable` and so on. To say "`T` is a type where `HashSet(T)` is legal," we can write: -``` +```carbon fn NumDistinct[T:! type where HashSet(.Self) impls type] (a: T, b: T, c: T) -> i32 { var set: HashSet(T); @@ -4155,15 +4154,17 @@ constant that is equivalent to `C where `. ## Other constraints as facet types -There are some constraints that we will naturally represent as named facet +There are some constraints that Carbon naturally represents as named facet types. These can either be used directly to constrain a facet binding, or in a -`where ... impls ...` [implements constraint](#kinds-of-where-constraints) to +`where ... impls ...` [implements constraint](#implements-constraints) to constrain an associated facet. -The compiler determines which types implement these interfaces, developers can -not explicitly implement these interfaces for their own types. To support these, -we extend the requirements that facet types are allowed to include beyond -interfaces implemented and [`where` clauses](#where-constraints). +The compiler determines which types implement these interfaces, developers are +not permitted to explicitly implement these interfaces for their own types. + +These facet types extend the requirements that facet types are allowed to +include beyond [interfaces implemented](#facet-types) and +[`where` clauses](#where-constraints). **Open question:** Are these names part of the prelude or in a standard library? @@ -4172,9 +4173,9 @@ interfaces implemented and [`where` clauses](#where-constraints). Given a type `T`, `Extends(T)` is a facet type whose values are facets that are (transitively) [derived from](/docs/design/classes.md#inheritance) `T`. That is, `U:! Extends(T)` means `U` has an `extend base: T;` declaration, or there is a -chain `extend base` declarations connecting `T` to `U`. +chain of `extend base` declarations connecting `T` to `U`. -``` +```carbon base class BaseType { ... } fn F[T:! Extends(BaseType)](p: T*); @@ -4198,9 +4199,10 @@ let p: BaseType* = UpCast(&d, BaseType); Assert(DownCast(p, DerivedType) == &d); ``` -**Open question:** Alternatively, we could define a new `extends` operator: +**Open question:** Alternatively, we could define a new `extends` operator for +use in `where` clauses: -``` +```carbon fn F[T:! type where .Self extends BaseType](p: T*); fn UpCast[T:! type](p: T*, U:! type where T extends .Self) -> U*; fn DownCast[T:! type](p: T*, U:! type where .Self extends T) -> U*; @@ -4214,20 +4216,20 @@ fn DownCast[T:! type](p: T*, U:! type where .Self extends T) -> U*; Given a type `U`, define the facet type `CompatibleWith(U)` as follows: > `CompatibleWith(U)` is a facet type whose values are facets `T` such that -> `T as type` and `U as type` are [compatible](terminology.md#compatible-types). -> That is values of `T` and `U` as types can be cast back and forth without any -> change in representation (for example `T` is an [adapter](#adapting-types) for -> `U`). +> `T as type` and `U as type` are +> [compatible types](terminology.md#compatible-types). That is values of `T` and +> `U` as types can be cast back and forth without any change in representation +> (for example `T` is an [adapter](#adapting-types) for `U`). `CompatibleWith` determines an equivalence relationship between types. Specifically, given two types `T1` and `T2`, they are equivalent if -`T1 impls CompatibleWith(T2)`. That is, if `T1` has the type -`CompatibleWith(T2)`. +`T1 impls CompatibleWith(T2)`, which is true if and only if +`T2 impls CompatibleWith(T1)`. -**Note:** Just like interface parameters, we require the user to supply `U`, -they may not be deduced. Specifically, this code would be illegal: +**Note:** Just like interface parameters, we require the user to supply `U`, it +may not be deduced. Specifically, this code would be illegal: -``` +```carbon fn Illegal[U:! type, T:! CompatibleWith(U)](x: T*) ... ``` @@ -4235,7 +4237,7 @@ In general there would be multiple choices for `U` given a specific `T` here, and no good way of picking one. However, similar code is allowed if there is another way of determining `U`: -``` +```carbon fn Allowed[U:! type, T:! CompatibleWith(U)](x: U*, y: T*) ... ``` @@ -4244,27 +4246,27 @@ fn Allowed[U:! type, T:! CompatibleWith(U)](x: U*, y: T*) ... In some cases, we need to restrict to types that implement certain interfaces the same way as the type `U`. -> The values of type `CompatibleWith(U, TT)` are types satisfying -> `CompatibleWith(U)` that have the same implementation of `TT` as `U`. +> The values of facet type `CompatibleWith(U, C)` are facets satisfying +> `CompatibleWith(U)` that have the same implementation of `C` as `U`. For example, if we have a type `HashSet(T)`: -``` +```carbon class HashSet(T:! Hashable) { ... } ``` Then `HashSet(T)` may be cast to `HashSet(U)` if `T impls CompatibleWith(U, Hashable)`. The one-parameter interpretation of -`CompatibleWith(U)` is recovered by letting the default for the second `TT` -parameter be `type`. +`CompatibleWith(U)` is recovered by letting the default for the second parameter +(`C`) be `type`. #### Example: Multiple implementations of the same interface This allows us to represent functions that accept multiple implementations of the same interface for a type. -``` -enum CompareResult { Less, Equal, Greater } +```carbon +choice CompareResult { Less, Equal, Greater } interface Comparable { fn Compare[self: Self](rhs: Self) -> CompareResult; } @@ -4272,9 +4274,9 @@ fn CombinedLess[T:! type](a: T, b: T, U:! CompatibleWith(T) & Comparable, V:! CompatibleWith(T) & Comparable) -> bool { match ((a as U).Compare(b as U)) { - case CompareResult.Less => { return True; } - case CompareResult.Greater => { return False; } - case CompareResult.Equal => { + case .Less => { return True; } + case .Greater => { return False; } + case .Equal => { return (a as V).Compare(b as V) == CompareResult.Less; } } @@ -4283,52 +4285,54 @@ fn CombinedLess[T:! type](a: T, b: T, Used as: -``` +```carbon class Song { ... } class SongByArtist { adapt Song; impl as Comparable { ... } } class SongByTitle { adapt Song; impl as Comparable { ... } } -var s1: Song = ...; -var s2: Song = ...; +let s1: Song = ...; +let s2: Song = ...; assert(CombinedLess(s1, s2, SongByArtist, SongByTitle) == True); ``` -We might generalize this to a list of implementations: - -``` -fn CombinedCompare[T:! type] - (a: T, b: T, CompareList:! List(CompatibleWith(T) & Comparable)) - -> CompareResult { - for (let U:! auto in CompareList) { - var result: CompareResult = (a as U).Compare(b); - if (result != CompareResult.Equal) { - return result; - } - } - return CompareResult.Equal; -} - -assert(CombinedCompare(Song(...), Song(...), (SongByArtist, SongByTitle)) == - CompareResult.Less); -``` - -**Open question:** How are compile-time lists of types declared and iterated -through? They will also be needed for -[variadic argument support](#variadic-arguments). +> **Open question:** We might generalize this to a list of implementations using +> variadics: +> +> ```carbon +> fn CombinedCompare[T:! type] +> (a: T, b: T, ... each CompareT:! CompatibleWith(T) & Comparable) +> -> CompareResult { +> ... block { +> let result: CompareResult = +> (a as each CompareT).Compare(b as each CompareT); +> if (result != CompareResult.Equal) { +> return result; +> } +> } +> return CompareResult.Equal; +> } +> +> assert(CombinedCompare(s1, s2, SongByArtist, SongByTitle) +> == CompareResult.Less); +> ``` +> +> However, [variadic support](#variadic-arguments) is still future work. #### Example: Creating an impl out of other implementations And then to package this functionality as an implementation of `Comparable`, we -combine `CompatibleWith` with [type adaptation](#adapting-types): +combine `CompatibleWith` with [type adaptation](#adapting-types) and +[variadics](#variadic-arguments): -``` +```carbon class ThenCompare( T:! type, - CompareList:! List(CompatibleWith(T) & Comparable)) { + ... each CompareT:! CompatibleWith(T) & Comparable) { adapt T; extend impl as Comparable { fn Compare[self: Self](rhs: Self) -> CompareResult { - for (let U:! auto in CompareList) { - var result: CompareResult = (self as U).Compare(rhs as U); + ... block { + let result: CompareResult = + (self as each CompareT).Compare(rhs as each CompareT); if (result != CompareResult.Equal) { return result; } @@ -4339,10 +4343,10 @@ class ThenCompare( } let template SongByArtistThenTitle:! auto = - ThenCompare(Song, (SongByArtist, SongByTitle)); + ThenCompare(Song, SongByArtist, SongByTitle); var s1: Song = ...; var s2: SongByArtistThenTitle = - Song(...) as SongByArtistThenTitle; + ({ ... } as Song) as SongByArtistThenTitle; assert((s1 as SongByArtistThenTitle).Compare(s2) == CompareResult.Less); ``` @@ -4377,12 +4381,9 @@ defining arrays of that type. Users will not typically need to express the `Sized` constraint explicitly, though, since it will usually be a dependency of some other constraint the type will need such as `Movable` or `Concrete`. -**Note:** The compiler will determine which types are "sized", this is not -something types will implement explicitly like ordinary interfaces. - Example: -``` +```carbon // In the Carbon standard library interface DefaultConstructible { // Types must be sized to be default constructible. @@ -4422,30 +4423,6 @@ symbolic integer value with the size of `T`. Similarly you might say `T.ByteStride` to get the number of bytes used for each element of an array of `T`. -### `TypeId` - -There are some capabilities every type can provide. For example, every type -should be able to return its name or identify whether it is equal to another -type. It is rare, however, for code to need to access these capabilities, so we -relegate these capabilities to an interface called `TypeId` that all types -automatically implement. This way checked-generic code can indicate that it -needs those capabilities by including `TypeId` in the list of requirements. In -the case where no type capabilities are needed, for example the code is only -manipulating pointers to the type, you would write `T:! type` and get the -efficiency of `void*` but without giving up type safety. - -``` -fn SortByAddress[T:! type](v: Vector(T*)*) { ... } -``` - -In particular, the compiler should in general avoid monomorphizing to generate -multiple instantiations of the function in this case. - -**Open question:** We have not yet decided whether types should -[extend their implementation](terminology.md#extending-an-impl) of `TypeId`. The -reason not to is to avoid name pollution (`.TypeName`, `.TypeHash`, etc.) unless -the function specifically requests those capabilities. - ### Destructor constraints There are four facet types related to @@ -4482,11 +4459,11 @@ implemented. ## Generic `let` A `let` statement inside a function body may be used to get the change in type -behavior of calling a generic function without having to introduce a function -call. +behavior of calling a checked-generic function without having to introduce a +function call. -``` -fn F(...) { +```carbon +fn SymbolicLet(...) { ... let T:! C = U; X; @@ -4497,8 +4474,8 @@ fn F(...) { is generally equivalent to: -``` -fn F(...) { +```carbon +fn SymbolicLet(...) { ... fn Closure(T:! C where .Self == U) { X; @@ -4517,28 +4494,66 @@ can be used to switch to the API of `C` when `U` does not extend `C`, as an alternative to [using an adapter](#use-case-accessing-interface-names), or to simplify inlining of a generic function while preserving semantics. +To get a template binding instead of symbolic binding, add the `template` +keyword before the binding pattern, as in: + +```carbon +fn TemplateLet(...) { + ... + let template T:! C = U; + X; + Y; + Z; +} +``` + +which is generally equivalent to: + +```carbon +fn TemplateLet(...) { + ... + fn Closure(template T:! C) { + X; + Y; + Z; + } + Closure(U); +} +``` + +In this case, the `where .Self == U` modifier is superfluous. + ## Parameterized impl declarations -There are cases where an impl definition should apply to more than a single type -and interface combination. The solution is to parameterize the impl definition, -so it applies to a family of types, interfaces, or both. This includes: +There are cases where an `impl` definition should apply to more than a single +type and interface combination. The solution is to parameterize the `impl` +definition, so it applies to a family of types, interfaces, or both. This +includes: -- Declare an impl for a parameterized type. -- "Conditional conformance" where a parameterized type implements some +- Defining an `impl` that applies to multiple arguments to a + [parameterized type](#parameterized-types). +- _Conditional conformance_ where a parameterized type implements some interface if the parameter to the type satisfies some criteria, like implementing the same interface. -- "Blanket" impl declarations where an interface is implemented for all types - that implement another interface, or some other criteria beyond being a - specific type. -- "Wildcard" impl declarations where a family of interfaces are implemented +- _Blanket_ `impl` declarations where an interface is implemented for all + types that implement another interface, or some other criteria beyond being + a specific type. +- _Wildcard_ `impl` declarations where a family of interfaces are implemented for single type. +The syntax for an out-of-line parameterized `impl` declaration is: + +> `impl forall [`__`]` __ `as` > +> _ [_ `where` _ ]_ `;` + +This may also be called a _generic `impl` declaration_. + ### Impl for a parameterized type Interfaces may be implemented for a [parameterized type](#parameterized-types). This can be done lexically in the class' scope: -``` +```carbon class Vector(T:! type) { impl as Iterable where .ElementType = T { ... @@ -4549,7 +4564,7 @@ class Vector(T:! type) { This is equivalent to naming the implementing type between `impl` and `as`, though this form is not allowed after `extend`: -``` +```carbon class Vector(T:! type) { impl Vector(T) as Iterable where .ElementType = T { ... @@ -4560,7 +4575,7 @@ class Vector(T:! type) { An out-of-line `impl` declaration must declare all parameters in a `forall` clause: -``` +```carbon impl forall [T:! type] Vector(T) as Iterable where .ElementType = T { ... @@ -4570,16 +4585,16 @@ impl forall [T:! type] Vector(T) as Iterable The parameter for the type can be used as an argument to the interface being implemented, with or without `extend`: -``` +```carbon class HashMap(Key:! Hashable, Value:! type) { extend impl as Has(Key) { ... } impl as Contains(HashSet(Key)) { ... } } ``` -or out-of-line: +or out-of-line the same `forall` parameter can be passed to both: -``` +```carbon class HashMap(Key:! Hashable, Value:! type) { ... } impl forall [Key:! Hashable, Value:! type] HashMap(Key, Value) as Has(Key) { ... } @@ -4589,6 +4604,8 @@ impl forall [Key:! Hashable, Value:! type] ### Conditional conformance +**FIXME: left off here.** + [Conditional conformance](terminology.md#conditional-conformance) is expressing that we have an `impl` of some interface for some type, but only if some additional type restrictions are met. Examples where this would be useful @@ -4605,7 +4622,7 @@ interface when its element type satisfies the same interface: This may be done by specifying a more specific implementing type to the left of the `as` in the declaration: -``` +```carbon interface Printable { fn Print[self: Self](); } @@ -4627,7 +4644,7 @@ impl forall [T:! Printable] Vector(T) as Printable { Note that no `forall` clause or type may be specified when declaring an `impl` with the [`extend`](#extend-impl) keyword: -``` +```carbon class Array(T:! type, template N:! i64) { // ❌ Illegal: nothing allowed before `as` after `extend impl`: extend impl forall [P:! Printable] Array(P, N) as Printable { ... } @@ -4640,7 +4657,7 @@ class Array(T:! type, template N:! i64) { Instead, the class can declare aliases to members of the interface. Those aliases will only be usable when the type implements the interface. -``` +```carbon class Array(T:! type, template N:! i64) { alias Print = Printable.Print; } @@ -4665,7 +4682,7 @@ no_print.Print(); It is legal to declare or define a conditional impl lexically inside the class scope without `extend`, as in: -``` +```carbon class Array(T:! type, template N:! i64) { // ✅ Allowed: non-extending impl defined in class scope may // use `forall` and may specify a type. @@ -4684,7 +4701,7 @@ that you need to use new names, like `P`, when creating new type variables. example, the interface `Foo(T)` is only implemented when the two types are equal. -``` +```carbon interface Foo(T:! type) { ... } class Pair(T:! type, U:! type) { ... } impl forall [T:! type] Pair(T, T) as Foo(T) { ... } @@ -4693,7 +4710,7 @@ impl forall [T:! type] Pair(T, T) as Foo(T) { ... } As before, you may also define the `impl` inline, but it may not be combined with the `extend` keyword: -``` +```carbon class Pair(T:! type, U:! type) { impl Pair(T, T) as Foo(T) { ... } } @@ -4702,7 +4719,7 @@ class Pair(T:! type, U:! type) { **Clarification:** The same interface may be implemented multiple times as long as there is no overlap in the conditions: -``` +```carbon class X(T:! type) { // ✅ Allowed: `X(T).F` consistently means `X(T).(Foo.F)` // even though that may have different definitions for @@ -4733,7 +4750,7 @@ in place of `Self` in the method declaration. For example, this is how to define a vector type that only has a `Sort` method if its elements implement the `Comparable` interface: -``` +```carbon class Vector(T:! type) { // `Vector(T)` has a `Sort()` method if `T` impls `Comparable`. fn Sort[C:! Comparable, addr self: Vector(C)*](); @@ -4757,13 +4774,13 @@ than one root type, so the `impl` declaration will use a type variable for the - Any type implementing `Ordered` should get an implementation of `PartiallyOrdered`. - ``` + ```carbon impl forall [T:! Ordered] T as PartiallyOrdered { ... } ``` - `T` implements `CommonType(T)` for all `T` - ``` + ```carbon impl forall [T:! type] T as CommonType(T) where .Result = T { } ``` @@ -4794,7 +4811,7 @@ of interfaces are implemented for a single `Self` type. For example, the `ImplicitAs(i32)`. The implementation would first convert `T` to `i32` and then add the `i32` to the `BigInt` value. -``` +```carbon class BigInt { impl forall [T:! ImplicitAs(i32)] as AddTo(T) { ... } } @@ -4811,7 +4828,7 @@ The different kinds of parameters to an `impl` declarations may be combined. For example, if `T` implements `As(U)`, then this implements `As(Optional(U))` for `Optional(T)`: -``` +```carbon impl forall [U:! type, T:! As(U)] Optional(T) as As(Optional(U)) { ... } ``` @@ -4843,13 +4860,13 @@ Given an impl declaration, find the type structure by deleting deduced parameters and replacing type parameters by a `?`. The type structure of this declaration: -``` +```carbon impl forall [T:! ..., U:! ...] Foo(T, i32) as Bar(String, U) { ... } ``` is: -``` +```carbon impl Foo(?, i32) as Bar(String, ?) ``` @@ -4934,7 +4951,7 @@ implementation of that type for that interface. Given two different type structures of impl declarations matching a query, for example: -``` +```carbon impl Foo(?, i32) as Bar(String, ?) impl Foo(?, ?) as Bar(String, f32) ``` @@ -4964,7 +4981,7 @@ block that matches is selected. keyword like `match_first` or `impl_priority` and then a sequence of impl declarations inside matching curly braces `{` ... `}`. -``` +```carbon match_first { // If T is Foo prioritized ahead of T is Bar impl forall [T:! Foo] T as Bar { ... } @@ -5011,7 +5028,7 @@ The test for whether something forms a cycle needs to be precise enough, and not erase too much information when considering this graph, that these `impl` declarations are not considered to form cycles with themselves: -``` +```carbon impl forall [T:! Printable] Optional(T) as Printable; impl forall [T:! type, U:! ComparableTo(T)] U as ComparableTo(Optional(T)); ``` @@ -5019,7 +5036,7 @@ impl forall [T:! type, U:! ComparableTo(T)] U as ComparableTo(Optional(T)); **Example:** If `T` implements `ComparableWith(U)`, then `U` should implement `ComparableWith(T)`. -``` +```carbon impl forall [U:! type, T:! ComparableWith(U)] U as ComparableWith(T); ``` @@ -5031,7 +5048,7 @@ types implement the same interface. selecting `impl` declarations that are inconsistent with each other. Consider an interface with two blanket `impl` declarations: -``` +```carbon class Y {} class N {} interface True {} @@ -5061,7 +5078,7 @@ There is no reason to to prefer one of these outcomes over the other. **Example:** Further, cycles can create contradictions in the type system: -``` +```carbon class A {} class B {} class C {} @@ -5121,7 +5138,7 @@ forever. `Optional(A) impls B`, if `Optional(Optional(A)) impls B`, and so on. This could be the result of a single impl: -``` +```carbon impl forall [A:! type where Optional(.Self) impls B] A as B { ... } ``` @@ -5150,7 +5167,7 @@ There are cases where knowing that a parameterized impl won't be specialized is particularly valuable. This could let the compiler know the return type of a generic function call, such as using an operator: -``` +```carbon // Interface defining the behavior of the prefix-* operator interface Deref { let Result:! type; @@ -5182,7 +5199,7 @@ anything about the return type of `Deref.Op` calls. This means `F` would in practice have to add a constraint, which is both verbose and exposes what should be implementation details: -``` +```carbon fn F[T:! type where Optional(T).(Deref.Result) == .Self and Ptr(T).(Deref.Result) == .Self](x: T) { // uses Ptr(T) and Optional(T) in implementation @@ -5192,7 +5209,7 @@ fn F[T:! type where Optional(T).(Deref.Result) == .Self To mark an impl as not able to be specialized, prefix it with the keyword `final`: -``` +```carbon class Ptr(T:! type) { ... // Note: added `final` @@ -5240,7 +5257,7 @@ If the Carbon compiler sees a matching `final` impl, it can assume it won't be specialized so it can use the assignments of the associated constants in that impl definition. -``` +```carbon fn F[T:! type](x: T) { var p: Ptr(T) = ...; // *p has type `T` @@ -5433,7 +5450,7 @@ An incomplete `C` cannot be used in the following contexts: **Future work:** It is currently undecided whether an interface needs to be complete to be extended, as in: -``` +```carbon interface I { extend C; } ``` @@ -5545,7 +5562,7 @@ For implementations to agree: ### Declaration examples -``` +```carbon // Forward declaration of interfaces interface Interface1; interface Interface2; @@ -5643,7 +5660,7 @@ constrained to implement `Node`. Furthermore, the `NodeType` of an `EdgeType` is the original type, and the other way around. This is accomplished by naming and then forward declaring the constraints that can't be stated directly: -``` +```carbon // Forward declare interfaces used in // parameter lists of constraints. interface Edge; @@ -5681,7 +5698,7 @@ To work around [the restriction about not being able to name an interface in its parameter list](#declaring-interfaces-and-named-constraints), instead include that requirement in the body of the interface. -``` +```carbon // Want to require that `T` satisfies `CommonType(Self)`, // but that can't be done in the parameter list. interface CommonType(T:! type) { @@ -5696,7 +5713,7 @@ constraints on members of `CommonType` are allowed, and that this `require T impls` declaration [must involve `Self`](#interface-requiring-other-interfaces-revisited). -``` +```carbon interface CommonType(T:! type) { let Result:! type; // ❌ Illegal: `CommonType` is incomplete @@ -5708,7 +5725,7 @@ Instead, a forward-declared named constraint can be used in place of the constraint that can only be defined later. This is [the same strategy used to work around cyclic references](#example-of-declaring-interfaces-with-cyclic-references). -``` +```carbon private constraint CommonTypeResult(T:! type, R:! type); interface CommonType(T:! type) { @@ -5736,7 +5753,7 @@ and prefixed with the `final` keyword. An interface may provide a default implementation of methods in terms of other methods in the interface. -``` +```carbon interface Vector { fn Add[self: Self](b: Self) -> Self; fn Scale[self: Self](v: f64) -> Self; @@ -5750,7 +5767,7 @@ interface Vector { A default function or method may also be defined out of line, later in the same file as the interface definition: -``` +```carbon interface Vector { fn Add[self: Self](b: Self) -> Self; fn Scale[self: Self](v: f64) -> Self; @@ -5776,7 +5793,7 @@ has one required method but dozens of "provided methods" with defaults. Defaults may also be provided for associated constants, such as associated facets, and interface parameters, using the `= ` syntax. -``` +```carbon interface Add(Right:! type = Self) { default let Result:! type = Self; fn DoAdd[self: Self](right: Right) -> Result; @@ -5792,7 +5809,7 @@ Note that `Self` is a legal default value for an associated facet or facet parameter. In this case the value of those names is not determined until `Self` is, so `Add()` is equivalent to the constraint: -``` +```carbon // Equivalent to Add() constraint AddDefault { extend Add(Self); @@ -5805,7 +5822,7 @@ parameters are left as their default values. More generally, default expressions may reference other associated constants or `Self` as parameters to type constructors. For example: -``` +```carbon interface Iterator { let Element:! type; default let Pointer:! type = Element*; @@ -5815,7 +5832,7 @@ interface Iterator { Carbon does **not** support providing a default implementation of a required interface. -``` +```carbon interface TotalOrder { fn TotalLess[self: Self](right: Self) -> bool; // ❌ Illegal: May not provide definition @@ -5831,7 +5848,7 @@ interface TotalOrder { The workaround for this restriction is to use a [blanket impl declaration](#blanket-impl-declarations) instead: -``` +```carbon interface TotalOrder { fn TotalLess[self: Self](right: Self) -> bool; require Self impls PartialOrder; @@ -5860,7 +5877,7 @@ As an alternative to providing a definition of an interface member as a default, members marked with the `final` keyword will not allow that definition to be overridden in `impl` definitions. -``` +```carbon interface TotalOrder { fn TotalLess[self: Self](right: Self) -> bool; final fn TotalGreater[self: Self](right: Self) -> bool { @@ -5888,7 +5905,7 @@ interface Add(T:! type = Self) { Final members may also be defined out-of-line: -``` +```carbon interface TotalOrder { fn TotalLess[self: Self](right: Self) -> bool; final fn TotalGreater[self: Self](right: Self) -> bool; @@ -5916,7 +5933,7 @@ Recall that an [interface can require another interface be implemented for the type](#interface-requiring-other-interfaces), as in: -``` +```carbon interface Iterable { require Self impls Equatable; // ... @@ -5929,7 +5946,7 @@ done with [conditional conformance](#conditional-conformance), we allow another type to be specified between `require` and `impls` to say some type other than `Self` must implement an interface. For example, -``` +```carbon interface IntLike { require i32 impls As(Self); // ... @@ -5939,7 +5956,7 @@ interface IntLike { says that if `Self` implements `IntLike`, then `i32` must implement `As(Self)`. Similarly, -``` +```carbon interface CommonTypeWith(T:! type) { require T impls CommonTypeWith(Self); // ... @@ -5976,7 +5993,7 @@ requirement will be implemented. This is like a [forward declaration of an impl](#declaring-implementations) except that the definition can be broader instead of being required to match exactly. -``` +```carbon // `Iterable` requires `Equatable`, so there must be some // impl of `Equatable` for `Vector(i32)` in this file. impl Vector(i32) as Iterable { ... } @@ -5996,7 +6013,7 @@ 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: -``` +```carbon impl forall [T:! type] T as CommonTypeWith(T) { ... } ``` @@ -6004,7 +6021,7 @@ Here is an example where the requirement of interface `Iterable` that the type implements interface `Equatable` is satisfied by a constraint in the `impl` declaration: -``` +```carbon 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 @@ -6016,7 +6033,7 @@ impl forall [T:! type where Foo(T) impls Equatable] This might be used to provide an implementation of `Equatable` for types that already satisfy the requirement of implementing `Iterable`: -``` +```carbon class Bar {} impl Foo(Bar) as Equatable {} // Gives `Foo(Bar) impls Iterable` using the blanket impl of @@ -6029,7 +6046,7 @@ An interface implementation requirement with a `where` clause is harder to satisfy. Consider an interface `B` that has a requirement that interface `A` is also implemented. -``` +```carbon interface A(T:! type) { let Result:! type; } @@ -6068,7 +6085,7 @@ requirements will be considered for that type. This allows a developer to provide a proof that there is a sequence of requirements that demonstrate that a type implements an interface, as in this example: -``` +```carbon interface A { } interface B { require Self impls A; } interface C { require Self impls B; } @@ -6111,7 +6128,7 @@ implements an interface because there is a a type is already known to satisfy. Without an `observe` declaration, Carbon will only use blanket impl declarations that are directly satisfied. -``` +```carbon interface A { } interface B { } interface C { } @@ -6185,7 +6202,7 @@ Operations are overloaded for a type by implementing an interface specific to that interface for that type. For example, types implement the `Negatable` interface to overload the unary `-` operator: -``` +```carbon // Unary `-`. interface Negatable { let Result:! type = Self; @@ -6209,7 +6226,7 @@ example, to say a type may be converted to another type using an `as` expression, implement the [`As` interface](/docs/design/expressions/as_expressions.md#extensibility): -``` +```carbon interface As(Dest:! type) { fn Convert[self: Self]() -> Dest; } @@ -6223,7 +6240,7 @@ Unlike `as`, for most binary operators the interface's argument will be the _type_ of the right-hand operand instead of its _value_. Consider an interface for a binary operator like `*`: -``` +```carbon // Binary `*`. interface MultipliableWith(U:! type) { let Result:! type = Self; @@ -6233,7 +6250,7 @@ interface MultipliableWith(U:! type) { A use of binary `*` in source code will be rewritten to use this interface: -``` +```carbon var left: Meters = ...; var right: f64 = ...; var result: auto = left * right; @@ -6248,7 +6265,7 @@ It is up to the developer to make those consistent when that is appropriate. The standard library will provide [adapters](#adapting-types) for defining the second implementation from the first, as in: -``` +```carbon interface ComparableWith(RHS:! type) { fn Compare[self: Self](right: RHS) -> CompareResult; } @@ -6276,7 +6293,7 @@ Further note that even if the reverse implementation exists, example, if we have two types that support comparison with anything implementing an interface that the other implements: -``` +```carbon interface IntLike { fn AsInt[self: Self]() -> i64; } @@ -6301,7 +6318,7 @@ impl forall [T:! IntLike] T as ComparableWith(PositiveInt); Then it will favor selecting the implementation based on the type of the left-hand operand: -``` +```carbon var even: EvenInt = ...; var positive: PositiveInt = ...; // Uses `EvenInt as ComparableWith(T)` impl @@ -6317,7 +6334,7 @@ to use, there are no automatic implicit conversions, unlike with function or method calls. Given both a method and an interface implementation for multiplying by a value of type `f64`: -``` +```carbon class Meters { fn Scale[self: Self](s: f64) -> Self; } @@ -6334,7 +6351,7 @@ the method will work with any argument that can be implicitly converted to `f64` but the operator overload will only work with values that have the specific type of `f64`: -``` +```carbon var height: Meters = ...; var scale: f32 = 1.25; // ✅ Allowed: `scale` implicitly converted @@ -6349,7 +6366,7 @@ The workaround is to define a parameterized implementation that performs the conversion. The implementation is for types that implement the [`ImplicitAs` interface](/docs/design/expressions/implicit_conversions.md#extensibility). -``` +```carbon // "Implementation Two" impl forall [T:! ImplicitAs(f64)] Meters as MultipliableWith(T) where .Result = Meters { @@ -6372,7 +6389,7 @@ defining operator overloads, Carbon has the `like` operator. This operator can only be used in the type or facet type part of an `impl` declaration, as part of a forward declaration or definition, in a place of a type. -``` +```carbon // Notice `f64` has been replaced by `like f64` // compared to "implementation one" above. impl Meters as MultipliableWith(like f64) @@ -6398,7 +6415,7 @@ main impl, which will trigger implicit conversions according to [Carbon's ordinary implicit conversion rules](/docs/design/expressions/implicit_conversions.md). In this example, there are two uses of `like`, producing three implementations -``` +```carbon impl like Meters as MultipliableWith(like f64) where .Result = Meters { fn Multiply[self: Self](other: f64) -> Result { @@ -6409,7 +6426,7 @@ impl like Meters as MultipliableWith(like f64) is equivalent to "implementation one", "implementation two", and: -``` +```carbon impl forall [T:! ImplicitAs(Meters)] T as MultipliableWith(f64) where .Result = Meters { fn Multiply[self: Self](other: f64) -> Result { @@ -6423,7 +6440,7 @@ impl forall [T:! ImplicitAs(Meters)] `like` may be used in forward declarations in a way analogous to impl definitions. -``` +```carbon impl like Meters as MultipliableWith(like f64) where .Result = Meters; } @@ -6431,7 +6448,7 @@ impl like Meters as MultipliableWith(like f64) is equivalent to: -``` +```carbon // All `like`s removed. Same as the declaration part of // "implementation one", without the body of the definition. impl Meters as MultipliableWith(f64) @@ -6459,13 +6476,13 @@ same way to match. The `like` operator may be nested, as in: -``` +```carbon impl like Vector(like String) as Printable; ``` Which will generate implementations with declarations: -``` +```carbon impl Vector(String) as Printable; impl forall [T:! ImplicitAs(Vector(String))] T as Printable; impl forall [T:! ImplicitAs(String)] Vector(T) as Printable; @@ -6479,7 +6496,7 @@ example, there existing an implicit conversion from `T` to `String` does not imply that there is one from `Vector(T)` to `Vector(String)`, so the following use of `like` is illegal: -``` +```carbon // ❌ Illegal: Can't convert a value with type // `Vector(T:! ImplicitAs(String))` // to `Vector(String)` for `self` @@ -6494,7 +6511,7 @@ The argument to `like` must either not mention any type parameters, or those parameters must be able to be determined due to being repeated outside of the `like` expression. -``` +```carbon // ✅ Allowed: no parameters impl like Meters as Printable; @@ -6533,7 +6550,7 @@ example, a container type might be parameterized by the type of its elements: **FIXME: add a bit that member functions may have additional parameters.** -``` +```carbon class HashMap( KeyType:! Hashable & EqualityComparable & Movable, ValueType:! Movable) { @@ -6565,7 +6582,7 @@ means Carbon will not in general be able to determine when types are unequal. Unlike an [interface's parameters](#parameterized-interfaces), a type's parameters may be [deduced](terminology.md#deduced-parameter), as in: -``` +```carbon fn ContainsKey[KeyType:! Movable, ValueType:! Movable] (haystack: HashMap(KeyType, ValueType), needle: KeyType) -> bool { ... } @@ -6600,7 +6617,7 @@ single byte. Clients of the optional library may want to add additional specializations for their own types. We make an interface that represents "the storage of `Optional(T)` for type `T`," written here as `OptionalStorage`: -``` +```carbon interface OptionalStorage { let Storage:! type; fn MakeNone() -> Storage; @@ -6613,7 +6630,7 @@ interface OptionalStorage { The default implementation of this interface is provided by a [blanket implementation](#blanket-impl-declarations): -``` +```carbon // Default blanket implementation impl forall [T:! Movable] T as OptionalStorage where .Storage = (bool, T) { @@ -6625,7 +6642,7 @@ This implementation can then be [specialized](#lookup-resolution-and-specialization) for more specific type patterns: -``` +```carbon // Specialization for pointers, using nullptr == None final impl forall [T:! type] T* as OptionalStorage where .Storage = Array(Byte, sizeof(T*)) { @@ -6642,7 +6659,7 @@ Further, libraries can implement `OptionalStorage` for their own types, assuming the interface is not marked `private`. Then the implementation of `Optional(T)` can delegate to `OptionalStorage` for anything that can vary with `T`: -``` +```carbon class Optional(T:! Movable) { fn None() -> Self { return {.storage = T.(OptionalStorage.MakeNone)()}; @@ -6662,7 +6679,7 @@ implementation of `OptionalStorage` exists for `T`. Carbon does not require callers of `Optional`, even checked-generic callers, to specify that the argument type implements `OptionalStorage`: -``` +```carbon // ✅ Allowed: `T` just needs to be `Movable` to form `Optional(T)`. // A `T:! OptionalStorage` constraint is not required. fn First[T:! Movable & Eq](v: Vector(T)) -> Optional(T); @@ -6676,7 +6693,7 @@ In this example, a `let` is used to avoid repeating `OptionalStorage` in the definition of `Optional`, since it has no name conflicts with the members of `Movable`: -``` +```carbon class Optional(T:! Movable) { private let U:! Movable & OptionalStorage = T; fn None() -> Self { @@ -6815,6 +6832,7 @@ parameter, as opposed to an associated facet, as in `N:! u32 where ___ >= 2`. - [#1327: Generics: `impl forall`](https://github.com/carbon-language/carbon-lang/pull/1327) - [#2107: Clarify rules around `Self` and `.Self`](https://github.com/carbon-language/carbon-lang/pull/2107) - [#2138: Checked and template generic terminology](https://github.com/carbon-language/carbon-lang/pull/2138) +- [Issue #2153: Checked generics calling templates](https://github.com/carbon-language/carbon-lang/issues/2153) - [#2173: Associated constant assignment versus equality](https://github.com/carbon-language/carbon-lang/pull/2173) - [#2200: Template generics](https://github.com/carbon-language/carbon-lang/pull/2200) - [#2347: What can be done with an incomplete interface](https://github.com/carbon-language/carbon-lang/pull/2347) From 83be850dddf8b8ebce32d0806293f508989138ee Mon Sep 17 00:00:00 2001 From: Josh L Date: Sat, 16 Sep 2023 01:21:00 +0000 Subject: [PATCH 63/83] Checkpoint progress. --- docs/design/generics/details.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 05fff4e93791e..26c53222f4f7c 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -4543,9 +4543,13 @@ includes: The syntax for an out-of-line parameterized `impl` declaration is: -> `impl forall [`__`]` __ `as` > + + +> `impl forall [`__`]` __ `as` > _ [_ `where` _ ]_ `;` + + This may also be called a _generic `impl` declaration_. ### Impl for a parameterized type From 4b6f9723c7064aacb86c0ca9c0ad80cdea007081 Mon Sep 17 00:00:00 2001 From: Josh L Date: Sat, 16 Sep 2023 01:22:08 +0000 Subject: [PATCH 64/83] Checkpoint progress. --- docs/design/generics/details.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 26c53222f4f7c..c760eaba49430 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -4545,6 +4545,8 @@ The syntax for an out-of-line parameterized `impl` declaration is: + + > `impl forall [`__`]` __ `as` > _ [_ `where` _ ]_ `;` From 168dd05f919deb1f580872036bad0c3149bc7d79 Mon Sep 17 00:00:00 2001 From: Josh L Date: Mon, 18 Sep 2023 23:35:17 +0000 Subject: [PATCH 65/83] Compile-time `let` --- docs/design/generics/details.md | 35 ++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index c760eaba49430..96974146a9f6d 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -85,7 +85,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Example: Creating an impl out of other implementations](#example-creating-an-impl-out-of-other-implementations) - [Sized types and facet types](#sized-types-and-facet-types) - [Destructor constraints](#destructor-constraints) -- [Generic `let`](#generic-let) +- [Compile-time `let`](#compile-time-let) - [Parameterized impl declarations](#parameterized-impl-declarations) - [Impl for a parameterized type](#impl-for-a-parameterized-type) - [Conditional conformance](#conditional-conformance) @@ -4456,7 +4456,7 @@ Instead they use and the compiler uses them to determine which of these facet types are implemented. -## Generic `let` +## Compile-time `let` A `let` statement inside a function body may be used to get the change in type behavior of calling a checked-generic function without having to introduce a @@ -4472,7 +4472,19 @@ fn SymbolicLet(...) { } ``` -is generally equivalent to: +This introduces a symbolic constant `T` with type `C` and value `U`. This +implicitly includes an [`observe T == U;` declaration](#observe-declarations), +when `T` and `U` are facets, which allows values to implicitly convert from the +concrete type `U` to the erased type `T`, as in: + +```carbon +let x: i32 = 7; +let T:! Add = i32; +// ✅ Allowed to convert `i32` values to `T`. +let y: T = x; +``` + +This makes the `SymbolicLet` function roughly equivalent to: ```carbon fn SymbolicLet(...) { @@ -4486,13 +4498,13 @@ fn SymbolicLet(...) { } ``` -The `where .Self == U` modifier allows values to implicitly convert between type -`T`, the erased type, and type `U`, the concrete type. Note that implicit -conversion is -[only performed across a single `where` equality](#manual-type-equality). This -can be used to switch to the API of `C` when `U` does not extend `C`, as an -alternative to [using an adapter](#use-case-accessing-interface-names), or to -simplify inlining of a generic function while preserving semantics. +The `where .Self == U` modifier captures the `observe` declaration introduced by +the `let` (at the cost of changing the type of `T`). + +A symbolic `let` can be used to switch to the API of `C` when `U` does not +extend `C`, as an alternative to +[using an adapter](#use-case-accessing-interface-names), or to simplify inlining +of a generic function while preserving semantics. To get a template binding instead of symbolic binding, add the `template` keyword before the binding pattern, as in: @@ -4507,7 +4519,8 @@ fn TemplateLet(...) { } ``` -which is generally equivalent to: +which introduces a template constant `T` with type `C` and value `U`. This is +roughly equivalent to: ```carbon fn TemplateLet(...) { From 6f0d8e413c0f83a465f9dec7bfbaa205acff7fec Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 19 Sep 2023 16:10:23 +0000 Subject: [PATCH 66/83] Checkpoint progress. --- docs/design/generics/details.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 96974146a9f6d..c4e58a814a8ae 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -3700,11 +3700,11 @@ In contrast to a [rewrite constraint](#rewrite-constraints) or a `ElementType` exactly is, just that it must satisfy requirements of some facet type. -**Note:** `Container` defines `ElementType` as having type `type`, but -`ContainerType.ElementType` has type `Comparable`. This is because -`ContainerType` has type `Container where .ElementType impls Comparable`, not -`Container`. This means we need to be a bit careful when talking about the type -of `ContainerType` when there is a `where` clause modifying it. +> **Note:** `Container` defines `ElementType` as having type `type`, but +> `ContainerType.ElementType` has type `Comparable`. This is because +> `ContainerType` has type `Container where .ElementType impls Comparable`, not +> `Container`. This means we need to be a bit careful when talking about the +> type of `ContainerType` when there is a `where` clause modifying it. An implements constraint can be applied to [`.Self`](#recursive-constraints), as in `I where .Self impls C`. This has the same requirements as `I & C`, but that From 40c10086722ee83c00bd092a12ad0cdd076e6be2 Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 19 Sep 2023 20:38:34 +0000 Subject: [PATCH 67/83] Checkpoint progress. --- docs/design/generics/details.md | 39 ++++++++++++--------------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index c4e58a814a8ae..dcb76999c9fd2 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -1163,40 +1163,27 @@ gives itself, `MyTypeOfType & MyTypeOfType == MyTypeOfType`. Also, given two [interface extensions](#interface-extension) of a common base interface, the combination should not conflict on any names in the common base. -**Future work:** We may want to define another operator on facet types for -adding requirements to a facet type without affecting the names, and so avoid -the possibility of name conflicts. Note this means the operation is not -commutative. If we call this operator `[&]`, then `A [&] B` has the names of `A` -and `B [&] A` has the names of `B`. +To add to the requirements of a facet type without affecting the names, and so +avoid the possibility of name conflicts, names, use a +[`where .Self impls` clause](#implements-constraints). -```carbon -// `Printable [&] Renderable` is syntactic sugar for this facet type: +``` +// `Printable where .Self impls Renderable` is equivalent to: constraint { require Self impls Printable; require Self impls Renderable; alias Print = Printable.Print; } - -// `Renderable [&] EndOfGame` is syntactic sugar for this facet type: -constraint { - require Self impls Renderable; - require Self impls EndOfGame; - alias Center = Renderable.Center; - alias Draw = Renderable.Draw; -} ``` -Note that all three expressions `A & B`, `A [&] B`, and `B [&] A` have the same -requirements, and so you would be able to switch a function declaration between -them without affecting callers. - -Nothing in this design depends on the `[&]` operator, and having both `&` and -`[&]` might be confusing for users, so it makes sense to postpone implementing -`[&]` until we have a demonstrated need. The `[&]` operator seems most useful -for adding requirements for interfaces used for +You might use this to add requirements on interfaces used for [operator overloading](#operator-overloading), where merely implementing the interface is enough to be able to use the operator to access the functionality. +Note that the expressions `A & B` and `A where .Self impls B` have the same +requirements, and so you would be able to switch a function declaration between +them without affecting callers. + **Alternatives considered:** See [Carbon: Access to interface methods](https://docs.google.com/document/d/17IXDdu384x1t9RimQ01bhx4-nWzs4ZEeke4eO6ImQNc/edit?resourcekey=0-Fe44R-0DhQBlw0gs2ujNJA). @@ -1975,12 +1962,14 @@ fn Render(w: Window) { } ``` -**Note:** Another way to achieve this is to use a local symbolic facet constant: +**Note:** Another way to achieve this is to use a +[local symbolic facet constant](#compile-time-let). ```carbon fn Render(w: Window) { let DrawInWindow:! Draw = Window; - let d: DrawInWindow = w as DrawInWindow; + // Implicit conversion to `w as DrawInWindow`. + let d: DrawInWindow = w; d.SetPen(...); d.SetFill(...); d.DrawRectangle(...); From dae31ac4593202dcd606db5a5c7cede6536ce41a Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 19 Sep 2023 21:20:20 +0000 Subject: [PATCH 68/83] Checkpoint progress. --- docs/design/generics/details.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index cc5a0c16ea9d0..21198930015cb 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -402,15 +402,15 @@ This (at least partially) addresses [the expression problem](https://eli.thegreenplace.net/2016/the-expression-problem-and-its-solutions). You can't use `extend` outside the class definition, so an `impl` declaration in -a different library will never affect the class' API. This means that the API of -a class such as `Point_OutOfLine` doesn't change based on what is imported. It -would be particularly bad if two different libraries implemented interfaces with -conflicting names that both affected the API of a single type. As a consequence -of this restriction, you can find all the names of direct members (those -available by [simple member access](terminology.md#simple-member-access)) of a -type in the definition of that type and entities referenced in by an `extend` -declaration in that definition. The only thing that may be in another library is -an `impl` of an interface. +a different library will never affect the class's API. This means that the API +of a class such as `Point_OutOfLine` doesn't change based on what is imported. +It would be particularly bad if two different libraries implemented interfaces +with conflicting names that both affected the API of a single type. As a +consequence of this restriction, you can find all the names of direct members +(those available by [simple member access](terminology.md#simple-member-access)) +of a type in the definition of that type and entities referenced in by an +`extend` declaration in that definition. The only thing that may be in another +library is an `impl` of an interface. **Rejected alternative:** We could allow types to have different APIs in different files based on explicit configuration in that file. For example, we From 39cb4496c3b44e3ed0d1ffc9aec05333d5999257 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 21 Sep 2023 16:30:43 +0000 Subject: [PATCH 69/83] Checkpoint progress. --- docs/design/generics/details.md | 130 +++++++++++++++++--------------- 1 file changed, 71 insertions(+), 59 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 21198930015cb..49de6419da030 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -4613,8 +4613,6 @@ impl forall [Key:! Hashable, Value:! type] ### Conditional conformance -**FIXME: left off here.** - [Conditional conformance](terminology.md#conditional-conformance) is expressing that we have an `impl` of some interface for some type, but only if some additional type restrictions are met. Examples where this would be useful @@ -4701,10 +4699,12 @@ class Array(T:! type, template N:! i64) { Inside the scope of this `impl` definition, both `P` and `T` refer to the same type, but `P` has the facet type of `Printable` and so has a `Print` member. The -relationship between `T` and `P` is as if there was a `where P == T` clause. +relationship between `T` and `P` is as if there was a +[`where P == T` clause](#same-type-constraints). -**TODO:** Need to resolve whether the `T` name can be reused, or if we require -that you need to use new names, like `P`, when creating new type variables. +**Open question:** Need to resolve whether the `T` name can be reused, or if we +require that you need to use new names, like `P`, when creating new type +variables. **Example:** Consider a type with two parameters, like `Pair(T, U)`. In this example, the interface `Foo(T)` is only implemented when the two types are @@ -4846,22 +4846,22 @@ This has a wildcard parameter `U`, and a condition on parameter `T`. ### Lookup resolution and specialization -As much as possible, we want rules for where an impl is allowed to be defined -and for selecting which impl to use that achieve these three goals: +As much as possible, we want rules for where an `impl` is allowed to be defined +and for selecting which `impl` definition to use that achieve these three goals: - Implementations have coherence, as [defined in terminology](terminology.md#coherence). This is [a goal for Carbon](goals.md#coherence). More detail can be found in [this appendix with the rationale and alternatives considered](appendix-coherence.md). - Libraries will work together as long as they pass their separate checks. -- A checked-generic function can assume that some impl will be successfully - selected if it can see an impl that applies, even though another more - specific impl may be selected. +- A checked-generic function can assume that some `impl` definition will be + successfully selected if it can see an `impl` declaration that applies, even + though another more specific `impl` definition may be selected. -For this to work, we need a rule that picks a single `impl` in the case where -there are multiple `impl` definitions that match a particular type and interface -combination. This is called _specialization_ when the rule is that most specific -implementation is chosen, for some definition of specific. +For this to work, we need a rule that picks a single `impl` definition in the +case where there are multiple `impl` definitions that match a particular type +and interface combination. This is called _specialization_ when the rule is that +most specific implementation is chosen, for some definition of "specific." #### Type structure of an impl declaration @@ -4882,8 +4882,8 @@ impl Foo(?, i32) as Bar(String, ?) To get a uniform representation across different `impl` definitions, before type parameters are replaced the declarations are normalized as follows: -- For impl declarations lexically inline in a class definition, the type is - added between the `impl` and `as` keywords if the type is left out. +- For `impl` declarations that are lexically inline in a class definition, the + type is added between the `impl` and `as` keywords if the type is left out. - Pointer types `T*` are replaced with `Ptr(T)`. - The `extend` keyword is removed, if present. - The `forall` clause introducing type parameters is removed, if present. @@ -4924,9 +4924,9 @@ few goals: is actually used, avoiding [One Definition Rule (ODR)](https://en.wikipedia.org/wiki/One_Definition_Rule) problems. -- Every attempt to use an `impl` will see the exact same `impl`, making the - interpretation and semantics of code consistent no matter its context, in - accordance with the +- Every attempt to use an `impl` will see the exact same `impl` definition, + making the interpretation and semantics of code consistent no matter its + context, in accordance with the [low context-sensitivity principle](/docs/project/principles/low_context_sensitivity.md). - Allowing the `impl` to be defined with either the interface or the type partially addresses the @@ -4978,55 +4978,67 @@ difference. Since at most one library can contain `impl` definitions with a given type structure, all `impl` definitions with a given type structure must be in the -same library. Furthermore by the [impl declaration access rules](#access), they -will be defined in the API file for the library if they could match any query -from outside the library. If there is more than one impl with that type +same library. Furthermore by the [`impl` declaration access rules](#access), +they will be defined in the API file for the library if they could match any +query from outside the library. If there is more than one `impl` with that type structure, they must be [defined](#implementing-interfaces) or [declared](#declaring-implementations) together in a prioritization block. Once -a type structure is selected for a query, the first impl in the prioritization -block that matches is selected. - -**Open question:** How are prioritization blocks written? A block starts with a -keyword like `match_first` or `impl_priority` and then a sequence of impl -declarations inside matching curly braces `{` ... `}`. +a type structure is selected for a query, the first `impl` declaration in the +prioritization block that matches is selected. -```carbon -match_first { - // If T is Foo prioritized ahead of T is Bar - impl forall [T:! Foo] T as Bar { ... } - impl forall [T:! Baz] T as Bar { ... } -} -``` - -**Open question:** How do we pick between two different prioritization blocks -when they contain a mixture of type structures? There are three options: - -- Prioritization blocks implicitly define all non-empty intersections of - contained `impl` declarations, which are then selected by their type - structure. -- The compiler first picks the impl with the type pattern most favored for the - query, and then picks the definition of the highest priority matching impl - in the same prioritization block. -- All the `impl` declarations in a prioritization block are required to have - the same type structure, at a cost in expressivity. +> **Open question:** How are prioritization blocks written? A block starts with +> a keyword like `match_first` or `impl_priority` and then a sequence of impl +> declarations inside matching curly braces `{` ... `}`. +> +> ```carbon +> match_first { +> // If T is Foo prioritized ahead of T is Bar +> impl forall [T:! Foo] T as Bar { ... } +> impl forall [T:! Baz] T as Bar { ... } +> } +> ``` -To see the difference between the first two options, consider two libraries with -type structures as follows: +To increase expressivity, Carbon allows prioritization blocks to contain a mix +of type structures, which is resolved using this rule: -- Library B has `impl (A, ?, ?, D) as I` and `impl (?, B, ?, D) as I` in the - same prioritization block. -- Library C has `impl (A, ?, C, ?) as I`. +> The compiler first picks the `impl` declaration with the type structure most +> favored for the query, and then picks the highest priority (earliest) matching +> `impl` declaration in the same prioritization block. -For the query `(A, B, C, D) as I`, using the intersection rule, library B is -considered to have the intersection impl with type structure -`impl (A, B, ?, D) as I` which is the most specific. If we instead just -considered the rules mentioned explicitly, then `impl (A, ?, C, ?) as I` from -library C is the most specific. The advantage of the implicit intersection rule -is that if library B is changed to add an impl with type structure -`impl (A, B, ?, D) as I`, it won't shift which library is serving that query. +> **Alternatives considered:** We considered two other options: +> +> - "Intersection rule:" Prioritization blocks implicitly define all non-empty +> intersections of contained `impl` declarations, which are then selected by +> their type structure. +> - "Same type structure rule:" All the `impl` declarations in a +> prioritization block are required to have the same type structure, at a +> cost in expressivity. This option was not chosen since it wouldn't support +> the different type structures created by the +> [`like` operator](#like-operator-for-implicit-conversions). +> +> To see the difference from the first option, consider two libraries with type +> structures as follows: +> +> - Library B has `impl (A, ?, ?, D) as I` and `impl (?, B, ?, D) as I` in the +> same prioritization block. +> - Library C has `impl (A, ?, C, ?) as I`. +> +> For the query `(A, B, C, D) as I`, using the intersection rule, library B is +> considered to have the intersection impl with type structure +> `impl (A, B, ?, D) as I` which is the most specific. If we instead just +> considered the rules mentioned explicitly, then `impl (A, ?, C, ?) as I` from +> library C is the most specific. The advantage of the implicit intersection +> rule is that if library B is changed to add an impl with type structure +> `impl (A, B, ?, D) as I`, it won't shift which library is serving that query. +> +> We chose between these alternatives in +> [the open discussion on 2023-07-18](https://docs.google.com/document/d/1gnJBTfY81fZYvI_QXjwKk1uQHYBNHGqRLI2BS_cYYNQ/edit?resourcekey=0-ql1Q1WvTcDvhycf8LbA9DQ#heading=h.7jxges9ojgy3). +> **TODO:** This decision needs to be approved in a proposal. #### Acyclic rule +**FIXME: Left off here.** + A cycle is when a query, such as "does type `T` implement interface `I`?", considers an impl that might match, and whether that impl matches is ultimately dependent on whether that query is true. These are cycles in the graph of (type, From dfd07f791646bf0a821582da91c640c7a6aacf1d Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 21 Sep 2023 16:36:51 +0000 Subject: [PATCH 70/83] Checkpoint progress. --- docs/design/generics/details.md | 42 ++++++++++++++++----------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 49de6419da030..0516dc8275dfb 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -3347,27 +3347,27 @@ final impl forall [T:! type, U:! type where .Self == T] T as ImplicitAs(U) { } ``` -It superficially seems like it would be convenient if such implementations were -made available implicitly – for example, by writing -`impl forall [T:! type] T as ImplicitAs(T)` – but in more complex examples that -turns out to be problematic. Consider: - -```carbon -interface CommonTypeWith(U:! type) { - let Result:! type; -} -final impl forall [T:! type] T as CommonTypeWith(T) where .Result = T {} - -fn F[T:! Potato, U:! Hashable where .Self == T](x: T, y: U) -> auto { - // What is T.CommonTypeWith(U).Result? Is it T or U? - return (if cond then x else y).Hash(); -} -``` - -With this proposal, `impl` validation for `T as CommonTypeWith(U)` fails: we -cannot pick a common type when given two distinct type expressions, even if we -know they evaluate to the same type, because we would not know which API the -result should have. +> **Alternative considered:** It superficially seems like it would be convenient +> if such implementations were made available implicitly –- for example, by +> writing `impl forall [T:! type] T as ImplicitAs(T)` -– but in more complex +> examples that turns out to be problematic. Consider: +> +> ```carbon +> interface CommonTypeWith(U:! type) { +> let Result:! type; +> } +> final impl forall [T:! type] T as CommonTypeWith(T) where .Result = T {} +> +> fn F[T:! Potato, U:! Hashable where .Self == T](x: T, y: U) -> auto { +> // What is T.CommonTypeWith(U).Result? Is it T or U? +> return (if cond then x else y).Hash(); +> } +> ``` +> +> With this alternative, `impl` validation for `T as CommonTypeWith(U)` fails: +> we cannot pick a common type when given two distinct type expressions, even if +> we know they evaluate to the same type, because we would not know which API +> the result should have. ##### Implementation of same-type `ImplicitAs` From c69f4cf2a3b6655a97cb19fce714cea06b8db6b8 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 21 Sep 2023 16:46:42 +0000 Subject: [PATCH 71/83] Checkpoint progress. --- docs/design/generics/details.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 0516dc8275dfb..b5824349c48ba 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -4474,6 +4474,10 @@ let T:! Add = i32; let y: T = x; ``` +> **TODO:** The implied `observe` declaration is from question-for-leads issue +> [#996](https://github.com/carbon-language/carbon-lang/issues/996) and should +> be approved in a proposal. + This makes the `SymbolicLet` function roughly equivalent to: ```carbon @@ -4526,6 +4530,13 @@ fn TemplateLet(...) { In this case, the `where .Self == U` modifier is superfluous. +> **References:** +> +> - Proposal +> [#950: Generics details 6: remove facets #950](https://github.com/carbon-language/carbon-lang/pull/950) +> - Question-for-leads issue +> [#996: Generic `let` with `auto`?](https://github.com/carbon-language/carbon-lang/issues/996) + ## Parameterized impl declarations There are cases where an `impl` definition should apply to more than a single From 59be2cf247a61d0b8b5b3a8d9aa8ac677f230d95 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 21 Sep 2023 16:54:15 +0000 Subject: [PATCH 72/83] Checkpoint progress. --- docs/design/generics/details.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index b5824349c48ba..16c4e210cd09e 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -5041,6 +5041,9 @@ of type structures, which is resolved using this rule: > library C is the most specific. The advantage of the implicit intersection > rule is that if library B is changed to add an impl with type structure > `impl (A, B, ?, D) as I`, it won't shift which library is serving that query. +> Ultimately we decided that it was too surprising to prioritize based on the +> implicit intersection of `impl` declarations, rather than something explicitly +> written in the code. > > We chose between these alternatives in > [the open discussion on 2023-07-18](https://docs.google.com/document/d/1gnJBTfY81fZYvI_QXjwKk1uQHYBNHGqRLI2BS_cYYNQ/edit?resourcekey=0-ql1Q1WvTcDvhycf8LbA9DQ#heading=h.7jxges9ojgy3). From 6bade887ea15be1cb6352149663f752c87ebeaa7 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 21 Sep 2023 19:49:11 +0000 Subject: [PATCH 73/83] Checkpoint progress. --- docs/design/generics/details.md | 182 ++++++++++++++++++++++++++------ 1 file changed, 148 insertions(+), 34 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 16c4e210cd09e..3335b05f755ef 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -101,6 +101,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Prioritization rule](#prioritization-rule) - [Acyclic rule](#acyclic-rule) - [Termination rule](#termination-rule) + - [Non-facet arguments](#non-facet-arguments) - [`final` impl declarations](#final-impl-declarations) - [Libraries that can contain a `final` impl](#libraries-that-can-contain-a-final-impl) - [Comparison to Rust](#comparison-to-rust) @@ -5051,13 +5052,12 @@ of type structures, which is resolved using this rule: #### Acyclic rule -**FIXME: Left off here.** - A cycle is when a query, such as "does type `T` implement interface `I`?", -considers an impl that might match, and whether that impl matches is ultimately -dependent on whether that query is true. These are cycles in the graph of (type, -interface) pairs where there is an edge from pair A to pair B if whether type A -implements interface A determines whether type B implements interface B. +considers an `impl` declaration that might match, and whether that `impl` +declaration matches is ultimately dependent on whether that query is true. These +are cycles in the graph of (type, interface) pairs where there is an edge from +pair A to pair B if whether type A implements interface A determines whether +type B implements interface B. The test for whether something forms a cycle needs to be precise enough, and not erase too much information when considering this graph, that these `impl` @@ -5102,12 +5102,12 @@ declarations are selected. - An implementation of `Z(i16)` for `i8` could come from the first blanket impl with `T == i8` and `U == i16` if `i16 impls Z(i8)` and - `i16.(Z(i8).Cond) == Y`. This condition is satisfied if `i16` implements + `(i16 as Z(i8)).Cond == Y`. This condition is satisfied if `i16` implements `Z(i8)` using the second blanket impl. In this case, - `i8.(Z(i16).Cond) == N`. + `(i8 as Z(i16)).Cond == N`. - Equally well `Z(i8)` could be implemented for `i16` using the first blanket impl and `Z(i16)` for `i8` using the second. In this case, - `i8.(Z(i16).Cond) == Y`. + `(i8 as Z(i16)).Cond == Y`. There is no reason to to prefer one of these outcomes over the other. @@ -5128,18 +5128,20 @@ match_first { } ``` -What is `i8.(D(i16).Cond)`? The answer is determined by which blanket impl is +What is `(i8 as D(i16)).Cond`? The answer is determined by which blanket impl is selected to implement `D(i16)` for `i8`: -- If the third blanket impl is selected, then `i8.(D(i16).Cond) == A`. This - implies that `i16.(D(i8).Cond) == B` using the second blanket impl. If that - is true, though, then our first impl choice was incorrect, since the first - blanket impl applies and is higher priority. So `i8.(D(i16).Cond) == C`. But - that means that `i16 as D(i8)` can't use the second blanket impl. -- For the second blanket impl to be selected, so `i8.(D(i16).Cond) == B`, - `i16.(D(i8).Cond)` would have to be `A`. This happens when `i16` implements - `D(i8)` using the third blanket impl. However, `i8.(D(i16).Cond) == B` means - that there is a higher priority implementation of `D(i8).Cond` for `i16`. +- If the third blanket impl is selected, then `(i8 as D(i16)).Cond == A`. This + implies that `(i16 as D(i8)).Cond == B` using the second blanket impl. If + that is true, though, then our first impl choice was incorrect, since the + first blanket impl applies and is higher priority. So + `(i8 as D(i16)).Cond == C`. But that means that `i16 as D(i8)` can't use the + second blanket impl. +- For the second blanket impl to be selected, so `(i8 as D(i16)).Cond == B`, + `(i16 as D(i8)).Cond` would have to be `A`. This happens when `i16` + implements `D(i8)` using the third blanket impl. However, + `(i8 as D(i16)).Cond == B` means that there is a higher priority + implementation of `D(i8).Cond` for `i16`. In either case, we arrive at a contradiction. @@ -5180,21 +5182,129 @@ impl forall [A:! type where Optional(.Self) impls B] A as B { ... } This problem can also result from a chain of `impl` declarations, as in `A impls B` if `A* impls C`, if `Optional(A) impls B`, and so on. -Rust solves this problem by imposing a recursion limit, much like C++ compilers -use to terminate template recursion. This goes against -[Carbon's goal of predictability in generics](goals.md#predictability), but at -this time there are no known alternatives. Unfortunately, the approach Carbon -uses to avoid undecidability for type equality, -[providing an explicit proof in the source](#manual-type-equality), can't be -used here. The code triggering the query asking whether some type implements an -interface will typically be checked-generic code with no specific knowledge -about the types involved, and won't be in a position to provide a manual proof -that the implementation should exist. - -**Open question:** Is there some restriction on `impl` declarations that would -allow our desired use cases, but allow the compiler to detect non-terminating -cases? Perhaps there is some sort of complexity measure Carbon can require -doesn't increase when recursing? +Determining whether a particular set of `impl` declarations terminates is +[equivalent to the halting problem](https://sdleffler.github.io/RustTypeSystemTuringComplete/) +(content warning: contains many instances of an obscene word as part of a +programming language name), and so is undecidable in general. Carbon adopts an +approximation that guarantees termination, but may mistakenly report an error +when the query would terminate if left to run long enough. The hope is that this +criteria is accurate on code that occurs in practice. + +Rule: the types in the `impl` query must never get strictly more complicated +when considering the same `impl` declaration again. The way we measure the +complexity of a set of types is by counting how many of each base type appears. +A base type is the name of a type without its parameters. For example, the base +types in this query `Pair(Optional(i32), bool) impls AddWith(Optional(i32))` +are: + +- `Pair` +- `Optional` twice +- `i32` twice +- `bool` +- `AddWith` + +A query is strictly more complicated if at least one count increases, and no +count decreases. So `Optional(Optional(i32))` is strictly more complicated than +`Optional(i32)` but not strictly more complicated than `Optional(bool)`. + +This rule, when combined with [the acyclic rule](#acyclic-rule) that a query +can't repeat exactly, +[guarantees termination](/proposals/p2687.md#proof-of-termination). + +Consider the example from before, + +```carbon +impl forall [A:! type where Optional(.Self) impls B] A as B; +``` + +This `impl` declaration matches the query `i32 impls B` as long as +`Optional(i32) impls B`. That is a strictly more complicated query, though, +since it contains all the base types of the starting query (`i32` and `B`), plus +one more (`Optional`). As a result, an error can be given after one step, rather +than after hitting a large recursion limit. And that error can state explicitly +what went wrong: we went from a query with no `Optional` to one with one, +without anything else decreasing. + +Note this only triggers a failure when the same `impl` declaration is considered +with the strictly more complicated query. For example, if the declaration is not +considered since there is a more specialized `impl` declaration that is +preferred by the [type-structure overlap rule](#overlap-rule), as in: + +``` +impl forall [A:! type where Optional(.Self) impls B] A as B; +impl Optional(bool) as B; +// OK, because we never consider the first `impl` +// declaration when looking for `Optional(bool) impls I`. +let U:! B = bool; +// Error: cycle with `i32 impls B` depending on +// `Optional(i32) impls B`, using the same `impl` +// declaration, as before. +let V:! B = i32; +``` + +> **Comparison with other languages:** Rust solves this problem by imposing a +> recursion limit, much like C++ compilers use to terminate template recursion. +> This goes against +> [Carbon's goal of predictability in generics](goals.md#predictability), +> because of the concern that increasing the number of steps needed to resolve +> an `impl` query could cause far away code to hit the recursion limit. +> +> Carbon's approach is robust in the face of refactoring: +> +> - It does not depend on the specifics of how an `impl` declaration is +> parameterized, only on the query. +> - It does not depend on the length of the chain of queries. +> - It does not depend on a measure of type-expression complexity, like depth. +> +> Carbon's approach also results in identifying the minimal steps in the loop, +> which makes error messages as short and understandable as possible. + +> **Alternatives considered:** +> +> - [Recursion limit](/proposals/p2687.md#problem) +> - [Measure complexity using type tree depth](/proposals/p2687.md#measure-complexity-using-type-tree-depth) +> - [Consider each type parameter in an `impl` declaration separately](/proposals/p2687.md#consider-each-type-parameter-in-an-impl-declaration-separately) +> - [Consider types in the interface being implemented as distinct](/proposals/p2687.md#consider-types-in-the-interface-being-implemented-as-distinct) +> - [Require some count to decrease](/proposals/p2687.md#require-some-count-to-decrease) +> - [Require non-type values to stay the same](/proposals/p2687.md#require-non-type-values-to-stay-the-same) + +> **References:** This algorithm is from proposal +> [#2687: Termination algorithm for impl selection](https://github.com/carbon-language/carbon-lang/pull/2687), +> replacing the recursion limit originally proposed in +> [#920: Generic parameterized impls (details 5)](https://github.com/carbon-language/carbon-lang/pull/920) +> before we came up with this algorithm. + +##### Non-facet arguments + +For non-facet arguments we have to expand beyond base types to consider other +kinds of keys. These other keys are in a separate namespace from base types. + +- Values with an integral type use the name of the type as the key and the + absolute value as a count. This means integer arguments are considered more + complicated if they increase in absolute value. For example, if the values + `2` and `-3` are used as arguments to parameters with type `i32`, then the + `i32` key will have count `5`. +- Every option of a choice type is its own key, counting how many times a + value using that option occurs. Any parameters to the option are recorded as + separate keys. For example, the `Optional(i32)` value of `.Some(7)` is + recorded as keys `.Some` (with a count of `1`) and `i32` (with a count of + `7`). +- Yet another namespace of keys is used to track counts of variadic arguments, + under the base type. This is to defend against having a variadic type `V` + that takes any number of `i32` arguments, with an infinite set of distinct + instantiations: `V(0)`, `V(0, 0)`, `V(0, 0, 0)`, ... + - A `tuple` key in this namespace is used to track the total number of + components of tuple values. The values of those elements will be tracked + using their own keys. + +Non-facet argument values not covered by these cases are deleted from the query +entirely for purposes of the termination algorithm. This requires that two +queries that only differ by non-facet arguments are considered identical and +therefore are rejected by the acyclic rule. Otherwise, we could construct an +infinite family of non-facet argument values that could be used to avoid +termination. + +**FIXME: Left off here.** ### `final` impl declarations @@ -6874,6 +6984,10 @@ parameter, as opposed to an associated facet, as in `N:! u32 where ___ >= 2`. - [#2360: Types are values of type `type`](https://github.com/carbon-language/carbon-lang/pull/2360) - [#2376: Constraints must use `Self`](https://github.com/carbon-language/carbon-lang/pull/2376) - [#2483: Replace keyword `is` with `impls`](https://github.com/carbon-language/carbon-lang/pull/2483) +- [#2687: Termination algorithm for impl selection](https://github.com/carbon-language/carbon-lang/pull/2687) - [#2760: Consistent `class` and `interface` syntax](https://github.com/carbon-language/carbon-lang/pull/2760) - [#2964: Expression phase terminology](https://github.com/carbon-language/carbon-lang/pull/2964) - [#3162: Reduce ambiguity in terminology](https://github.com/carbon-language/carbon-lang/pull/3162) + +**FIXME: issue #2495 updated `impl` terminology, should be noted in PR summary. +Also: https://github.com/carbon-language/carbon-lang/pull/2687 ** From 88e436b048f23ecfb3feaf2b7a3a7450f13b2f61 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 21 Sep 2023 20:28:15 +0000 Subject: [PATCH 74/83] Checkpoint progress. --- docs/design/generics/details.md | 53 ++++++++++++++++----------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 3335b05f755ef..159484c1dfb6f 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -5304,13 +5304,11 @@ therefore are rejected by the acyclic rule. Otherwise, we could construct an infinite family of non-facet argument values that could be used to avoid termination. -**FIXME: Left off here.** - ### `final` impl declarations There are cases where knowing that a parameterized impl won't be specialized is particularly valuable. This could let the compiler know the return type of a -generic function call, such as using an operator: +call to a generic function, such as using an operator: ```carbon // Interface defining the behavior of the prefix-* operator @@ -5411,21 +5409,21 @@ fn F[T:! type](x: T) { } ``` -**Alternatives considered:** - -- [Allow interfaces with member functions to compare equal](/proposals/p2868.md#allow-interfaces-with-member-functions-to-compare-equal) -- Mark associated constants as `final` instead of an `impl` declaration, in - proposals - [#983](/proposals/p0983.md#final-associated-constants-instead-of-final-impls) - and - [#2868](/proposals/p2868.md#mark-associated-constants-as-final-instead-of-an-impl-declaration) -- [Prioritize a `final impl` over a more specific `impl` on the overlap](/proposals/p2868.md#prioritize-a-final-impl-over-a-more-specific-impl-on-the-overlap) +> **Alternatives considered:** +> +> - [Allow interfaces with member functions to compare equal](/proposals/p2868.md#allow-interfaces-with-member-functions-to-compare-equal) +> - Mark associated constants as `final` instead of an `impl` declaration, in +> proposals +> [#983](/proposals/p0983.md#final-associated-constants-instead-of-final-impls) +> and +> [#2868](/proposals/p2868.md#mark-associated-constants-as-final-instead-of-an-impl-declaration) +> - [Prioritize a `final impl` over a more specific `impl` on the overlap](/proposals/p2868.md#prioritize-a-final-impl-over-a-more-specific-impl-on-the-overlap) #### Libraries that can contain a `final` impl -To prevent the possibility of two unrelated libraries defining conflicting impl -declarations, Carbon restricts which libraries may declare an impl as `final` to -only: +To prevent the possibility of two unrelated libraries defining conflicting +`impl` declarations, Carbon restricts which libraries may declare an impl as +`final` to only: - the library declaring the impl's interface and - the library declaring the root of the `Self` type. @@ -5464,9 +5462,9 @@ process, so Carbon can benefit from the work they have done. However, getting specialization to work for Rust is complicated by the need to maintain compatibility with existing Rust code. This motivates a number of Rust rules where Carbon can be simpler. As a result there are both similarities and -differences between the Carbon and Rust plans: +differences between the Carbon design and Rust plans: -- A Rust impl defaults to not being able to be specialized, with a `default` +- A Rust `impl` defaults to not being able to be specialized, with a `default` keyword used to opt-in to allowing specialization, reflecting the existing code base developed without specialization. Carbon `impl` declarations default to allowing specialization, with restrictions on which may be @@ -5490,9 +5488,9 @@ differences between the Carbon and Rust plans: [Little Orphan Impls: The ordered rule](http://smallcultfollowing.com/babysteps/blog/2015/01/14/little-orphan-impls/#the-ordered-rule), but the specifics are different. - Carbon is not planning to support any inheritance of implementation between - impl definitions. This is more important to Rust since Rust does not support - class inheritance for implementation reuse. Rust has considered multiple - approaches here, see + `impl` definitions. This is more important to Rust since Rust does not + support class inheritance for implementation reuse. Rust has considered + multiple approaches here, see [Aaron Turon: "Specialize to Reuse"](http://aturon.github.io/tech/2015/09/18/reuse/) and [Supporting blanket impls in specialization](http://smallcultfollowing.com/babysteps/blog/2016/10/24/supporting-blanket-impls-in-specialization/). @@ -5504,6 +5502,8 @@ differences between the Carbon and Rust plans: ordering on type structures, picking one as higher priority even without one being more specific in the sense of only applying to a subset of types. +**FIXME: Left off here.** + ## Forward declarations and cyclic references Interfaces, named constraints, and their implementations may be forward declared @@ -5538,10 +5538,7 @@ interface in its parameter list. There is a the use cases when this would come up. An expression forming a constraint, such as `C & D`, is incomplete if any of the -interfaces or constraints used in the expression are incomplete. A constraint -expression using a [`where` clause](#where-constraints), like `C where ...`, is -invalid if `C` is incomplete, since there is no way to look up member names of -`C` that appear after `where`. +interfaces or constraints used in the expression are incomplete. An interface or named constraint may be forward declared subject to these rules: @@ -5549,17 +5546,19 @@ An interface or named constraint may be forward declared subject to these rules: - Only the first declaration may have an access-control keyword. - An incomplete interface or named constraint may be used as constraints in declarations of types, functions, interfaces, or named constraints. This - includes an `impl as` or `extend` declaration inside an interface or named + includes an `require` or `extend` declaration inside an interface or named constraint, but excludes specifying the values for associated constants because that would involve name lookup into the incomplete constraint. - An attempt to define the body of a generic function using an incomplete - interface or named constraint is illegal. + interface or named constraint in its signature is illegal. - An attempt to call a generic function using an incomplete interface or named constraint in its signature is illegal. - Any name lookup into an incomplete interface or named constraint is an error. For example, it is illegal to attempt to access a member of an interface using `MyInterface.MemberName` or constrain a member using a - `where` clause. + [`where` clause](#where-constraints). + +**FIXME: Left off here.** If `C` is the name of an incomplete interface or named constraint, then it can be used in the following contexts: From 33da1036d820c6fd2e49958f2b5d861023db48f1 Mon Sep 17 00:00:00 2001 From: Josh L Date: Thu, 21 Sep 2023 20:30:44 +0000 Subject: [PATCH 75/83] Checkpoint progress. --- docs/design/generics/details.md | 42 ++++++++++++++++----------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 159484c1dfb6f..755d641878b82 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -5558,8 +5558,6 @@ An interface or named constraint may be forward declared subject to these rules: interface using `MyInterface.MemberName` or constrain a member using a [`where` clause](#where-constraints). -**FIXME: Left off here.** - If `C` is the name of an incomplete interface or named constraint, then it can be used in the following contexts: @@ -5567,8 +5565,8 @@ be used in the following contexts: - ✅ `C & D` - There may be conflicts between `C` and `D` making this invalid that will only be discovered once they are both complete. -- ✅ `interface `...` { impl` ... `as C; }` or `constraint `...` { impl` ... - `as C; }` +- ✅ `interface `...` { require` ... `impls C; }` or + `constraint `...` { require` ... `impls C; }` - Nothing implied by implementing `C` will be visible until `C` is complete. - ✅ `T:! C` ... `T impls C` @@ -5591,24 +5589,26 @@ An incomplete `C` cannot be used in the following contexts: - Need to see the definition of `C` to see if it implies `A`. - ❌ `impl` ... `as C {` ... `}` -**Future work:** It is currently undecided whether an interface needs to be -complete to be extended, as in: - -```carbon -interface I { extend C; } -``` - -There are three different approaches being considered: +> **Future work:** It is currently undecided whether an interface needs to be +> complete to be extended, as in: +> +> ```carbon +> interface I { extend C; } +> ``` +> +> There are three different approaches being considered: +> +> - If we detect name collisions between the members of the interface `I` and +> `C` when the interface `I` is defined, then we need `C` to be complete. +> - If we instead only generate errors on ambiguous use of members with the +> same name, as we do with `A & B`, then we don't need to require `C` to be +> complete. +> - Another option, being discussed in +> [#2355](https://github.com/carbon-language/carbon-lang/issues/2355), is +> that names in interface `I` shadow the names in any interface being +> extended, then `C` would not be required to be complete. -- If we detect name collisions between the members of the interface `I` and - `C` when the interface `I` is defined, then we need `C` to be complete. -- If we instead only generate errors on ambiguous use of members with the same - name, as we do with `A & B`, then we don't need to require `C` to be - complete. -- Another option, being discussed in - [#2355](https://github.com/carbon-language/carbon-lang/issues/2355), is that - names in interface `I` shadow the names in any interface being extended, - then `C` would not be required to be complete. +**FIXME: Left off here.** ### Declaring implementations From f6d1e1964ee339bd2258d46dcc886b0da1c99194 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 22 Sep 2023 03:51:07 +0000 Subject: [PATCH 76/83] Checkpoint progress. --- docs/design/generics/details.md | 94 +++++++++++++++-------------- docs/design/generics/terminology.md | 6 +- 2 files changed, 53 insertions(+), 47 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 755d641878b82..c9c5f0caf97b2 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -1645,8 +1645,8 @@ class SongByTitle { ``` **Comparison with other languages:** This matches the Rust idiom called -"newtype", which is used to implement traits on types while avoiding coherence -problems, see +"newtype", which is used to implement traits on types while avoiding +[coherence](terminology.md#coherence) problems, see [here](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types) and [here](https://github.com/Ixrec/rust-orphan-rules#user-content-why-are-the-orphan-rules-controversial). @@ -4911,11 +4911,12 @@ library depends on. #### Orphan rule -To achieve coherence, we need to ensure that any given impl can only be defined -in a library that must be imported for it to apply. Specifically, given a -specific type and specific interface, `impl` declarations that can match can -only be in libraries that must have been imported to name that type or -interface. This is achieved with the _orphan rule_. +To achieve [coherence](terminology.md#coherence), we need to ensure that any +given impl can only be defined in a library that must be imported for it to +apply. Specifically, given a specific type and specific interface, `impl` +declarations that can match can only be in libraries that must have been +imported to name that type or interface. This is achieved with the _orphan +rule_. **Orphan rule:** Some name from the type structure of an `impl` declaration must be defined in the same library as the `impl`, that is some name must be _local_. @@ -5502,8 +5503,6 @@ differences between the Carbon design and Rust plans: ordering on type structures, picking one as higher priority even without one being more specific in the sense of only applying to a subset of types. -**FIXME: Left off here.** - ## Forward declarations and cyclic references Interfaces, named constraints, and their implementations may be forward declared @@ -5608,8 +5607,6 @@ An incomplete `C` cannot be used in the following contexts: > that names in interface `I` shadow the names in any interface being > extended, then `C` would not be required to be complete. -**FIXME: Left off here.** - ### Declaring implementations The declaration of an interface implementation consists of: @@ -5618,46 +5615,43 @@ The declaration of an interface implementation consists of: - the keyword introducer `impl`, - an optional `forall` followed by a deduced parameter list in square brackets `[`...`]`, -- a type, including an optional parameter pattern, +- a type, including an optional [argument list](#parameterized-types), - the keyword `as`, and - a [facet type](#facet-types), including an optional - [parameter pattern](#parameterized-interfaces) and + [argument list](#parameterized-interfaces) and [`where` clause](#where-constraints) assigning [associated constants](#associated-constants) including [associated facets](#associated-facets). -**Note:** The `extend` keyword, when present, is not part of the declaration. It -precedes the `impl` declaration in class scope. +**Note:** The `extend` keyword, when present, is not part of the `impl` +declaration. It precedes the `impl` declaration in class scope. -An implementation of an interface for a type may be forward declared subject to +An implementation of an interface for a type may be forward declared, subject to these rules: - The definition must be in the same library as the declaration. They must either be in the same file, or the declaration can be in the API file and the definition in an impl file. **Future work:** Carbon may require - [parameterized impl definitions](#parameterized-impl-declarations) to be in - the API file, to support separate compilation. + [parameterized `impl` definitions](#parameterized-impl-declarations) to be + in the API file, to support separate compilation. - If there is both a forward declaration and a definition, only the first declaration must specify the assignment of associated constants with a `where` clause. Later declarations may omit the `where` clause by writing `where _` instead. -- You may forward declare an implementation of a defined interface but not an - incomplete interface. This allows the assignment of associated constants in - the `impl` declaration to be verified. An impl forward declaration may be - for any declared type, whether it is incomplete or defined. Note that this - does not apply to `impl as` declarations in an interface or named constraint - definition, as those are considered interface requirements not forward - declarations. -- Every extending implementation must be declared (or defined) inside the - scope of the class definition. It may also be declared before the class - definition or defined afterwards. Note that the class itself is incomplete - in the scope of the class definition, but member function bodies defined - inline are processed +- You can't forward declare an implementation of an incomplete interface. This + allows the assignment of associated constants in the `impl` declaration to + be verified with the declaration. An `impl` forward declaration may be for + any declared type, whether it is incomplete or defined. +- Every [extending implementation](#extend-impl) must be declared (or defined) + inside the scope of the class definition. It may also be declared before the + class definition or defined afterwards. Note that the class itself is + incomplete in the scope of the class definition, but member function bodies + defined inline are processed [as if they appeared immediately after the end of the outermost enclosing class](/docs/project/principles/information_accumulation.md#exceptions). -- For [coherence](goals.md#coherence), we require that any `impl` declaration - that matches an impl lookup query in the same file, must be declared before - the query. This can be done with a definition or a forward declaration. This - matches the +- For [coherence](terminology.md#coherence), we require that any `impl` + declaration that matches an impl lookup query in the same file, must be + declared before the query. This can be done with a definition or a forward + declaration. This matches the [information accumulation principle](/docs/project/principles/information_accumulation.md). ### Matching and agreeing @@ -5685,9 +5679,10 @@ expressions match along with - If the type part is omitted, it is rewritten to `Self` in the context of the declaration. - `Self` is rewritten to its meaning in the scope it is used. In a class - scope, this should match the type name and optional parameter expression - after `class`. So in `class MyClass { ... }`, `Self` is rewritten to - `MyClass`. In `class Vector(T:! Movable) { ... }`, `Self` is rewritten to + scope, this should match the type name and + [optional parameter expression](#parameterized-types) after `class`. So in + `class MyClass { ... }`, `Self` is rewritten to `MyClass`. In + `class Vector(T:! Movable) { ... }`, `Self` is rewritten to `forall [T:! Movable] Vector(T)`. - Types match if they have the same name after name and alias resolution and the same parameters, or are the same type parameter. @@ -5992,6 +5987,10 @@ interface TotalOrder { The workaround for this restriction is to use a [blanket impl declaration](#blanket-impl-declarations) instead: +**FIXME: Is it sensible to have both a `require` and a blanket implementation? +Does the blanket implementation satisfy the requirement so you only need to +implement `TotalOrder`?** + ```carbon interface TotalOrder { fn TotalLess[self: Self](right: Self) -> bool; @@ -6042,7 +6041,7 @@ interface Add(T:! type = Self) { // `AddWith` *always* equals `T` final let AddWith:! type = T; // Has a *default* of `Self` - let Result:! type = Self; + default let Result:! type = Self; fn DoAdd[self: Self](right: AddWith) -> Result; } ``` @@ -6111,8 +6110,9 @@ says that if `Self` implements `CommonTypeWith(T)`, then `T` must implement `CommonTypeWith(Self)`. An `require`...`impls` constraint in an `interface`, or `constraint`, definition -must still use `Self` in some way. It can be an argument to either the type or -interface. For example: +must still use `Self` in some way. It can be an argument to either the +[type](#parameterized-types) or [interface](#parameterized-interfaces). For +example: - ✅ Allowed: `require Self impls Equatable` - ✅ Allowed: `require Vector(Self) impls Equatable` @@ -6125,9 +6125,9 @@ interface. For example: This restriction allows the Carbon compiler to know where to look for facts about a type. If `require i32 impls Equatable` could appear in any `interface` definition, that implies having to search all of them when considering what -interfaces `i32` implements. This creates a coherence problem, since then the -set of facts true for a type would depend on which interfaces have been -imported. +interfaces `i32` implements. This would create a +[coherence](terminology.md#coherence) problem, since then the set of facts true +for a type would depend on which interfaces have been imported. When implementing an interface with an `require`...`impls` requirement, that requirement must be satisfied by an implementation in an imported library, an @@ -6150,7 +6150,7 @@ fn ProcessVector(v: Vector(i32)) { } // Satisfies the requirement that `Vector(i32)` must -// implement `Equatable` since `i32` impls `Equatable`. +// implement `Equatable` since `i32 impls Equatable`. impl forall [T:! Equatable] Vector(T) as Equatable { ... } ``` @@ -6209,6 +6209,8 @@ is [marked `final`](#final-impl-declarations) or is not libraries can't make `A` be implemented for fewer types, but can cause `.Result` to have a different assignment. +**FIXME: Left off here.** + ## Observing a type implements an interface An [`observe` declaration](#observe-declarations) can be used to show that two @@ -6259,8 +6261,8 @@ fn RequiresD[T:! D](x: T) { ``` Note that `observe` statements do not affect which impl is selected during code -generation. For coherence, the impl used for a (type, interface) pair must -always be the same, independent of context. The +generation. For [coherence](terminology.md#coherence), the impl used for a +(type, interface) pair must always be the same, independent of context. The [termination rule](#termination-rule) governs when compilation may fail when the compiler can't determine the impl to select. diff --git a/docs/design/generics/terminology.md b/docs/design/generics/terminology.md index 1e5e63bde5699..f36cf8490a7b2 100644 --- a/docs/design/generics/terminology.md +++ b/docs/design/generics/terminology.md @@ -570,7 +570,8 @@ permitted, always has the same meaning as an explicit cast. A generics or interface system has the _implementation coherence_ property, or simply _coherence_, if there is a single answer to the question "what is the implementation of this interface for this type, if any?" independent of context, -such as the libraries imported into a given file. +such as the libraries imported into a given file. Coherence is +[a goal of Carbon checked generics](goals.md#coherence). This is enforced using two kinds of rules: @@ -588,6 +589,9 @@ This is enforced using two kinds of rules: [overlap rule or overlap check](https://rust-lang.github.io/chalk/book/clauses/coherence.html#chalk-overlap-check) instead produces an error if two implementations apply at once. +The rationale for Carbon choosing coherence and alternatives considered may be +found in [this appendix](appendix-coherence.md) + ## Adapting a type A type can be adapted by creating a new type that is From 301216d9a7efd055851b6302fbe5fa7da22f7ed2 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 22 Sep 2023 17:26:30 +0000 Subject: [PATCH 77/83] Checkpoint progress. --- docs/design/expressions/arithmetic.md | 12 +- docs/design/expressions/bitwise.md | 12 +- docs/design/generics/details.md | 179 ++++++++++++++------------ 3 files changed, 110 insertions(+), 93 deletions(-) diff --git a/docs/design/expressions/arithmetic.md b/docs/design/expressions/arithmetic.md index 9ab4c69d0e912..0a3051bcf933c 100644 --- a/docs/design/expressions/arithmetic.md +++ b/docs/design/expressions/arithmetic.md @@ -193,7 +193,7 @@ following family of interfaces: ``` // Unary `-`. interface Negate { - let Result:! type = Self; + default let Result:! type = Self; fn Op[self: Self]() -> Result; } ``` @@ -201,7 +201,7 @@ interface Negate { ``` // Binary `+`. interface AddWith(U:! type) { - let Result:! type = Self; + default let Result:! type = Self; fn Op[self: Self](other: U) -> Result; } constraint Add { @@ -212,7 +212,7 @@ constraint Add { ``` // Binary `-`. interface SubWith(U:! type) { - let Result:! type = Self; + default let Result:! type = Self; fn Op[self: Self](other: U) -> Result; } constraint Sub { @@ -223,7 +223,7 @@ constraint Sub { ``` // Binary `*`. interface MulWith(U:! type) { - let Result:! type = Self; + default let Result:! type = Self; fn Op[self: Self](other: U) -> Result; } constraint Mul { @@ -234,7 +234,7 @@ constraint Mul { ``` // Binary `/`. interface DivWith(U:! type) { - let Result:! type = Self; + default let Result:! type = Self; fn Op[self: Self](other: U) -> Result; } constraint Div { @@ -245,7 +245,7 @@ constraint Div { ``` // Binary `%`. interface ModWith(U:! type) { - let Result:! type = Self; + default let Result:! type = Self; fn Op[self: Self](other: U) -> Result; } constraint Mod { diff --git a/docs/design/expressions/bitwise.md b/docs/design/expressions/bitwise.md index 55e0bea69b2e7..7c19f485aaea7 100644 --- a/docs/design/expressions/bitwise.md +++ b/docs/design/expressions/bitwise.md @@ -197,7 +197,7 @@ implementing the following family of interfaces: ``` // Unary `^`. interface BitComplement { - let Result:! type = Self; + default let Result:! type = Self; fn Op[self: Self]() -> Result; } ``` @@ -205,7 +205,7 @@ interface BitComplement { ``` // Binary `&`. interface BitAndWith(U:! type) { - let Result:! type = Self; + default let Result:! type = Self; fn Op[self: Self](other: U) -> Result; } constraint BitAnd { @@ -216,7 +216,7 @@ constraint BitAnd { ``` // Binary `|`. interface BitOrWith(U:! type) { - let Result:! type = Self; + default let Result:! type = Self; fn Op[self: Self](other: U) -> Result; } constraint BitOr { @@ -227,7 +227,7 @@ constraint BitOr { ``` // Binary `^`. interface BitXorWith(U:! type) { - let Result:! type = Self; + default let Result:! type = Self; fn Op[self: Self](other: U) -> Result; } constraint BitXor { @@ -238,7 +238,7 @@ constraint BitXor { ``` // Binary `<<`. interface LeftShiftWith(U:! type) { - let Result:! type = Self; + default let Result:! type = Self; fn Op[self: Self](other: U) -> Result; } constraint LeftShift { @@ -249,7 +249,7 @@ constraint LeftShift { ``` // Binary `>>`. interface RightShiftWith(U:! type) { - let Result:! type = Self; + default let Result:! type = Self; fn Op[self: Self](other: U) -> Result; } constraint RightShift { diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index c9c5f0caf97b2..536bc4d762556 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -6209,15 +6209,14 @@ is [marked `final`](#final-impl-declarations) or is not libraries can't make `A` be implemented for fewer types, but can cause `.Result` to have a different assignment. -**FIXME: Left off here.** - ## Observing a type implements an interface An [`observe` declaration](#observe-declarations) can be used to show that two types are equal so code can pass type checking without explicitly writing casts, -without requiring the compiler to do a unbounded search that may not terminate. -An `observe` declaration can also be used to show that a type implements an -interface, in cases where the compiler will not work this out for itself. +and without requiring the compiler to do a unbounded search that may not +terminate. An `observe` declaration can also be used to show that a type +implements an interface, in cases where the compiler will not work this out for +itself. ### Observing interface requirements @@ -6246,15 +6245,15 @@ fn RequiresD[T:! D](x: T) { // ❌ Illegal: No direct connection between `D` and `A`. // RequiresA(x); - // `T` impls `D` and `D` directly requires `C` to be + // `T impls D` and `D` directly requires `C` to be // implemented. observe T impls C; - // `T` impls `C` and `C` directly requires `B` to be + // `T impls C` and `C` directly requires `B` to be // implemented. observe T impls B; - // ✅ Allowed: `T` impls `B` and `B` directly requires + // ✅ Allowed: `T impls B` and `B` directly requires // `A` to be implemented. RequiresA(x); } @@ -6264,7 +6263,7 @@ Note that `observe` statements do not affect which impl is selected during code generation. For [coherence](terminology.md#coherence), the impl used for a (type, interface) pair must always be the same, independent of context. The [termination rule](#termination-rule) governs when compilation may fail when the -compiler can't determine the impl to select. +compiler can't determine the `impl` definition to select. ### Observing blanket impl declarations @@ -6345,24 +6344,26 @@ Multiple `==` clauses are allowed in an `observe` declaration, so you may write ## Operator overloading Operations are overloaded for a type by implementing an interface specific to -that interface for that type. For example, types implement the `Negatable` -interface to overload the unary `-` operator: +that interface for that type. For example, types implement +[the `Negate` interface](/docs/design/expressions/arithmetic.md#extensibility) +to overload the unary `-` operator: ```carbon // Unary `-`. -interface Negatable { - let Result:! type = Self; - fn Negate[self: Self]() -> Result; +interface Negate { + default let Result:! type = Self; + fn Op[self: Self]() -> Result; } ``` Expressions using operators are rewritten into calls to these interface methods. -For example, `-x` would be rewritten to `x.(Negatable.Negate)()`. +For example, `-x` would be rewritten to `x.(Negate.Op)()`. The interfaces and rewrites used for a given operator may be found in the [expressions design](/docs/design/expressions/README.md). [Question-for-leads issue #1058](https://github.com/carbon-language/carbon-lang/issues/1058) -defines the naming scheme for these interfaces. +defines the naming scheme for these interfaces, which was implemented in +[proposal #1178](https://github.com/carbon-language/carbon-lang/pull/1178). ### Binary operators @@ -6383,14 +6384,14 @@ parameterization of the interface means it can be implemented multiple times to support multiple operand types. Unlike `as`, for most binary operators the interface's argument will be the -_type_ of the right-hand operand instead of its _value_. Consider an interface -for a binary operator like `*`: +_type_ of the right-hand operand instead of its _value_. Consider +[the interface for a binary operator like `*`](/docs/design/expressions/arithmetic.md#extensibility): ```carbon // Binary `*`. -interface MultipliableWith(U:! type) { - let Result:! type = Self; - fn Multiply[self: Self](other: U) -> Result; +interface MulWith(U:! type) { + default let Result:! type = Self; + fn Op[self: Self](other: U) -> Result; } ``` @@ -6401,8 +6402,8 @@ var left: Meters = ...; var right: f64 = ...; var result: auto = left * right; // Equivalent to: -var equivalent: left.(MultipliableWith(f64).Result) - = left.(MultipliableWith(f64).Multiply)(right); +var equivalent: left.(MulWith(f64).Result) + = left.(MulWith(f64).Op)(right); ``` Note that if the types of the two operands are different, then swapping the @@ -6412,22 +6413,27 @@ standard library will provide [adapters](#adapting-types) for defining the second implementation from the first, as in: ```carbon -interface ComparableWith(RHS:! type) { - fn Compare[self: Self](right: RHS) -> CompareResult; +interface OrderedWith(U:! type) { + fn Compare[self: Self](u: U) -> Ordering; + // ... } -class ReverseComparison - (T:! type, U:! ComparableWith(RHS)) { +class ReverseComparison(T:! type, U:! OrderedWith(T)) { adapt T; - extend impl as ComparableWith(U) { - fn Compare[self: Self](right: RHS) -> CompareResult { - return ReverseCompareResult(right.Compare(self)); + extend impl as OrderedWith(U) { + fn Compare[self: Self](u: U) -> Ordering { + match (u.Compare(self)) { + case .Less => return .Greater; + case .Equivalent => return .Equivalent; + case .Greater => return .Less; + case .Incomparable => return .Incomparable; + } } } } -impl SongByTitle as ComparableWith(SongTitle); -impl SongTitle as ComparableWith(SongByTitle) +impl SongByTitle as OrderedWith(SongTitle) { ... } +impl SongTitle as OrderedWith(SongByTitle) = ReverseComparison(SongTitle, SongByTitle); ``` @@ -6435,7 +6441,7 @@ In some cases the reverse operation may not be defined. For example, a library might support subtracting a vector from a point, but not the other way around. Further note that even if the reverse implementation exists, -[the impl prioritization rule](#prioritization-rule) might not pick it. For +[the `impl` prioritization rule](#prioritization-rule) might not pick it. For example, if we have two types that support comparison with anything implementing an interface that the other implements: @@ -6446,48 +6452,48 @@ interface IntLike { class EvenInt { ... } impl EvenInt as IntLike; -impl EvenInt as ComparableWith(EvenInt); +impl EvenInt as OrderedWith(EvenInt); // Allow `EvenInt` to be compared with anything that // implements `IntLike`, in either order. -impl forall [T:! IntLike] EvenInt as ComparableWith(T); -impl forall [T:! IntLike] T as ComparableWith(EvenInt); +impl forall [T:! IntLike] EvenInt as OrderedWith(T); +impl forall [T:! IntLike] T as OrderedWith(EvenInt); class PositiveInt { ... } impl PositiveInt as IntLike; -impl PositiveInt as ComparableWith(PositiveInt); +impl PositiveInt as OrderedWith(PositiveInt); // Allow `PositiveInt` to be compared with anything that // implements `IntLike`, in either order. -impl forall [T:! IntLike] PositiveInt as ComparableWith(T); -impl forall [T:! IntLike] T as ComparableWith(PositiveInt); +impl forall [T:! IntLike] PositiveInt as OrderedWith(T); +impl forall [T:! IntLike] T as OrderedWith(PositiveInt); ``` -Then it will favor selecting the implementation based on the type of the -left-hand operand: +Then the compiler will favor selecting the implementation based on the type of +the left-hand operand: ```carbon var even: EvenInt = ...; var positive: PositiveInt = ...; -// Uses `EvenInt as ComparableWith(T)` impl +// Uses `EvenInt as OrderedWith(T)` impl if (even < positive) { ... } -// Uses `PositiveInt as ComparableWith(T)` impl +// Uses `PositiveInt as OrderedWith(T)` impl if (positive > even) { ... } ``` ### `like` operator for implicit conversions -Because the type of the operands is directly used to select the implementation -to use, there are no automatic implicit conversions, unlike with function or -method calls. Given both a method and an interface implementation for -multiplying by a value of type `f64`: +Because the type of the operands is directly used to select the operator +interface implementation, there are no automatic implicit conversions, unlike +with function or method calls. Given both a method and an interface +implementation for multiplying by a value of type `f64`: ```carbon class Meters { fn Scale[self: Self](s: f64) -> Self; } // "Implementation One" -impl Meters as MultipliableWith(f64) +impl Meters as MulWith(f64) where .Result = Meters { - fn Multiply[self: Self](other: f64) -> Result { + fn Op[self: Self](other: f64) -> Result { return self.Scale(other); } } @@ -6504,7 +6510,7 @@ var scale: f32 = 1.25; // from `f32` to `f64`. var allowed: Meters = height.Scale(scale); // ❌ Illegal: `Meters` doesn't implement -// `MultipliableWith(f32)`. +// `MulWith(f32)`. var illegal: Meters = height * scale; ``` @@ -6515,14 +6521,14 @@ conversion. The implementation is for types that implement the ```carbon // "Implementation Two" impl forall [T:! ImplicitAs(f64)] - Meters as MultipliableWith(T) where .Result = Meters { - fn Multiply[self: Self](other: T) -> Result { + Meters as MulWith(T) where .Result = Meters { + fn Op[self: Self](other: T) -> Result { // Carbon will implicitly convert `other` from type // `T` to `f64` to perform this call. - return self.(Meters.(MultipliableWith(f64).Multiply))(other); + return self.((Meters as MulWith(f64)).Op)(other); } } -// ✅ Allowed: uses `Meters as MultipliableWith(T)` impl +// ✅ Allowed: uses `Meters as MulWith(T)` impl // with `T == f32` since `f32 impls ImplicitAs(f64)`. var now_allowed: Meters = height * scale; ``` @@ -6538,9 +6544,9 @@ a forward declaration or definition, in a place of a type. ```carbon // Notice `f64` has been replaced by `like f64` // compared to "implementation one" above. -impl Meters as MultipliableWith(like f64) +impl Meters as MulWith(like f64) where .Result = Meters { - fn Multiply[self: Self](other: f64) -> Result { + fn Op[self: Self](other: f64) -> Result { return self.Scale(other); } } @@ -6552,19 +6558,30 @@ equivalent to "implementation one". The second implementation replaces the `like f64` with a parameter that ranges over types that can be implicitly converted to `f64`, equivalent to "implementation two". +> **Note:** We have decided to change the following in +> [a discussion on 2023-07-13](https://docs.google.com/document/d/1gnJBTfY81fZYvI_QXjwKk1uQHYBNHGqRLI2BS_cYYNQ/edit?resourcekey=0-ql1Q1WvTcDvhycf8LbA9DQ#heading=h.rs7m0kytcl4t). +> The new approach is to have one parameterized implementation replacing all of +> the `like` expressions on the left of the `as`, and another replacing all of +> the `like` expressions on the right of the `as`. However, in +> [a discussion on 2023-07-20](https://docs.google.com/document/d/1gnJBTfY81fZYvI_QXjwKk1uQHYBNHGqRLI2BS_cYYNQ/edit?resourcekey=0-ql1Q1WvTcDvhycf8LbA9DQ#heading=h.msdqbemd6axi), +> we decided that this change would not affect how we handle nested `like` +> expressions: `like Vector(like i32)` is still `like Vector(i32)` plus +> `Vector(like i32)`. These changes have not yet gone through the proposal +> process. + In general, each `like` adds one additional parameterized implementation. There is always the impl defined with all of the `like` expressions replaced by their arguments with the definition supplied in the source code. In addition, for each `like` expression, there is an automatic `impl` definition with it replaced by a new parameter. These additional automatic implementations will delegate to the -main impl, which will trigger implicit conversions according to +main `impl` definition, which will trigger implicit conversions according to [Carbon's ordinary implicit conversion rules](/docs/design/expressions/implicit_conversions.md). In this example, there are two uses of `like`, producing three implementations ```carbon -impl like Meters as MultipliableWith(like f64) +impl like Meters as MulWith(like f64) where .Result = Meters { - fn Multiply[self: Self](other: f64) -> Result { + fn Op[self: Self](other: f64) -> Result { return self.Scale(other); } } @@ -6574,20 +6591,20 @@ is equivalent to "implementation one", "implementation two", and: ```carbon impl forall [T:! ImplicitAs(Meters)] - T as MultipliableWith(f64) where .Result = Meters { - fn Multiply[self: Self](other: f64) -> Result { - // Will implicitly convert `self` to `Meters` in order to - // match the signature of this `Multiply` method. - return self.(Meters.(MultipliableWith(f64).Multiply))(other); + T as MulWith(f64) where .Result = Meters { + fn Op[self: Self](other: f64) -> Result { + // Will implicitly convert `self` to `Meters` in + // order to match the signature of this `Op` method. + return self.((Meters as MulWith(f64)).Op)(other); } } ``` -`like` may be used in forward declarations in a way analogous to impl +`like` may be used in `impl` forward declarations in a way analogous to `impl` definitions. ```carbon -impl like Meters as MultipliableWith(like f64) +impl like Meters as MulWith(like f64) where .Result = Meters; } ``` @@ -6597,25 +6614,23 @@ is equivalent to: ```carbon // All `like`s removed. Same as the declaration part of // "implementation one", without the body of the definition. -impl Meters as MultipliableWith(f64) - where .Result = Meters; +impl Meters as MulWith(f64) where .Result = Meters; // First `like` replaced with a wildcard. impl forall [T:! ImplicitAs(Meters)] - T as MultipliableWith(f64) where .Result = Meters; + T as MulWith(f64) where .Result = Meters; // Second `like` replaced with a wildcard. Same as the // declaration part of "implementation two", without the // body of the definition. impl forall [T:! ImplicitAs(f64)] - Meters as MultipliableWith(T) where .Result = Meters; + Meters as MulWith(T) where .Result = Meters; ``` -In addition, the generated impl definition for a `like` is implicitly injected -at the end of the (unique) source file in which the impl is first declared. That -is, it is injected in the API file if the impl is declared in an API file, and -in the sole impl file declaring the impl otherwise. This means an `impl` -declaration using `like` in an API file also makes the parameterized definition +In addition, the generated `impl` definition for a `like` is implicitly injected +at the end of the (unique) source file in which the `impl` is defined. That is, +it is injected in the API file if the `impl` definition is in an API file, and +in the sole impl file with the `impl` definition otherwise. If one `impl` declaration uses `like`, other declarations must use `like` in the same way to match. @@ -6667,28 +6682,30 @@ impl forall [T:! IntLike] like T as Printable; // ❌ Illegal: `T` being used in a `where` clause // is insufficient. impl forall [T:! IntLike] like T - as MultipliableWith(i64) where .Result = T; + as MulWith(i64) where .Result = T; // ❌ Illegal: `like` can't be used in a `where` // clause. -impl Meters as MultipliableWith(f64) +impl Meters as MulWith(f64) where .Result = like Meters; // ✅ Allowed: `T` can be determined by another // part of the query. impl forall [T:! IntLike] like T - as MultipliableWith(T) where .Result = T; + as MulWith(T) where .Result = T; impl forall [T:! IntLike] T - as MultipliableWith(like T) where .Result = T; + as MulWith(like T) where .Result = T; // ✅ Allowed: Only one `like` used at a time, so this // is equivalent to the above two examples. impl forall [T:! IntLike] like T - as MultipliableWith(like T) where .Result = T; + as MulWith(like T) where .Result = T; ``` ## Parameterized types +**FIXME: left off here.** + Generic types may be defined by giving them compile-time parameters. Those parameters may be used to specify types in the declarations of its members, such as data fields, member functions, and even interfaces being implemented. For @@ -6710,7 +6727,7 @@ class HashMap( // Parameters may be used in interfaces implemented. extend impl as Container where .ElementType = (KeyType, ValueType); - extend impl as ComparableWith(HashMap(KeyType, ValueType)); + impl as OrderedWith(HashMap(KeyType, ValueType)); } ``` From 260b8133e9e6bbda5a57f07d3763de20290dfbbd Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 22 Sep 2023 21:06:09 +0000 Subject: [PATCH 78/83] Checkpoint progress. --- docs/design/generics/details.md | 268 +++++++++++++++++--------------- 1 file changed, 144 insertions(+), 124 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 536bc4d762556..8cccd0d3a62e4 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -89,7 +89,6 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Parameterized impl declarations](#parameterized-impl-declarations) - [Impl for a parameterized type](#impl-for-a-parameterized-type) - [Conditional conformance](#conditional-conformance) - - [Conditional methods](#conditional-methods) - [Blanket impl declarations](#blanket-impl-declarations) - [Difference between a blanket impl and a named constraint](#difference-between-a-blanket-impl-and-a-named-constraint) - [Wildcard impl declarations](#wildcard-impl-declarations) @@ -125,6 +124,8 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Binary operators](#binary-operators) - [`like` operator for implicit conversions](#like-operator-for-implicit-conversions) - [Parameterized types](#parameterized-types) + - [Generic methods](#generic-methods) + - [Conditional methods](#conditional-methods) - [Specialization](#specialization) - [Future work](#future-work) - [Dynamic types](#dynamic-types) @@ -140,7 +141,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Field requirements](#field-requirements) - [Bridge for C++ customization points](#bridge-for-c-customization-points) - [Variadic arguments](#variadic-arguments) - - [Range constraints on symbolic integers](#range-constraints-on-symbolic-integers) + - [Value constraints for template parameters](#value-constraints-for-template-parameters) - [References](#references) @@ -1203,10 +1204,9 @@ Some interfaces will depend on other interfaces being implemented for the same type. For example, in C++, [the `Container` concept](https://en.cppreference.com/w/cpp/named_req/Container#Other_requirements) requires all containers to also satisfy the requirements of -`DefaultConstructible`, `CopyConstructible`, `EqualityComparable`, and -`Swappable`. This is already a capability for -[facet types in general](#facet-types). For consistency we will use the same -semantics and `require Self impls` syntax as we do for +`DefaultConstructible`, `CopyConstructible`, `Eq`, and `Swappable`. This is +already a capability for [facet types in general](#facet-types). For consistency +we will use the same semantics and `require Self impls` syntax as we do for [named constraints](#named-constraints): ```carbon @@ -1576,7 +1576,7 @@ APIs, in particular with different interface implementations, by interface Printable { fn Print[self: Self](); } -interface Comparable { +interface Ordered { fn Less[self: Self](rhs: Self) -> bool; } class Song { @@ -1584,7 +1584,7 @@ class Song { } class SongByTitle { adapt Song; - extend impl as Comparable { + extend impl as Ordered { fn Less[self: Self](rhs: Self) -> bool { ... } } } @@ -1595,7 +1595,7 @@ class FormattedSong { class FormattedSongByTitle { adapt Song; extend impl as Printable = FormattedSong; - extend impl as Comparable = SongByTitle; + extend impl as Ordered = SongByTitle; } ``` @@ -1623,7 +1623,7 @@ type may be accessed either by a cast: ```carbon class SongByTitle { adapt Song; - extend impl as Comparable { + extend impl as Ordered { fn Less[self: Self](rhs: Self) -> bool { return (self as Song).Title() < (rhs as Song).Title(); } @@ -1636,7 +1636,7 @@ or using a qualified member access expression: ```carbon class SongByTitle { adapt Song; - extend impl as Comparable { + extend impl as Ordered { fn Less[self: Self](rhs: Self) -> bool { return self.(Song.Title)() < rhs.(Song.Title)(); } @@ -1659,7 +1659,7 @@ compiler provides it as ### Adapter compatibility -Consider a type with a facet parameter, like a hash map: +Consider a [type with a facet parameter, like a hash map](#parameterized-types): ```carbon interface Hashable { ... } @@ -1730,7 +1730,7 @@ class SongByArtist { extend adapt Song; // Add an implementation of a new interface - extend impl as Comparable { ... } + extend impl as Ordered { ... } // Replace an existing implementation of an interface // with an alternative. @@ -1740,7 +1740,7 @@ class SongByArtist { The resulting type `SongByArtist` would: -- implement `Comparable`, unlike `Song`, +- implement `Ordered`, unlike `Song`, - implement `Hashable`, but differently than `Song`, and - implement `Printable`, inherited from `Song`. @@ -3673,7 +3673,7 @@ can be applied to [associated facet](#associated-facets) members as well. In the following example, normally the `ElementType` of a `Container` can be any type. The `SortContainer` function, however, takes a pointer to a type satisfying `Container` with the additional constraint that its `ElementType` -must satisfy the `Comparable` interface, using an `impls` constraint: +must satisfy the `Ordered` interface, using an `impls` constraint: ```carbon interface Container { @@ -3682,7 +3682,7 @@ interface Container { } fn SortContainer - [ContainerType:! Container where .ElementType impls Comparable] + [ContainerType:! Container where .ElementType impls Ordered] (container_to_sort: ContainerType*); ``` @@ -3692,8 +3692,8 @@ In contrast to a [rewrite constraint](#rewrite-constraints) or a type. > **Note:** `Container` defines `ElementType` as having type `type`, but -> `ContainerType.ElementType` has type `Comparable`. This is because -> `ContainerType` has type `Container where .ElementType impls Comparable`, not +> `ContainerType.ElementType` has type `Ordered`. This is because +> `ContainerType` has type `Container where .ElementType impls Ordered`, not > `Container`. This means we need to be a bit careful when talking about the > type of `ContainerType` when there is a `where` clause modifying it. @@ -3707,42 +3707,42 @@ in `T:! I where .Self impls C`, is represented by an ##### Implied constraints -Imagine we have a checked-generic function that accepts an arbitrary `HashMap` -[parameterized type](#parameterized-types): +Imagine we have a checked-generic function that accepts an arbitrary +[`HashMap` parameterized type](#parameterized-types): ```carbon -fn LookUp[KeyType:! type](hm: HashMap(KeyType, i32)*, - k: KeyType) -> i32; +fn LookUp[KeyT:! type](hm: HashMap(KeyT, i32)*, + k: KeyT) -> i32; -fn PrintValueOrDefault[KeyType:! Printable, +fn PrintValueOrDefault[KeyT:! Printable, ValueT:! Printable & HasDefault] - (map: HashMap(KeyType, ValueT), key: KeyT); + (map: HashMap(KeyT, ValueT), key: KeyT); ``` -The `KeyType` in these declarations does not visibly satisfy the requirements of +The `KeyT` in these declarations does not visibly satisfy the requirements of `HashMap`, which requires the type implement `Hashable` and other interfaces: ```carbon class HashMap( - KeyType:! Hashable & EqualityComparable & Movable, + KeyT:! Hashable & Eq & Movable, ...) { ... } ``` -In this case, `KeyType` gets `Hashable` and so on as _implied constraints_. +In this case, `KeyT` gets `Hashable` and so on as _implied constraints_. Effectively that means that these functions are automatically rewritten to add a -`where .Self impls` constraint on `KeyType`: +`where .Self impls` constraint on `KeyT`: ```carbon fn LookUp[ - KeyType:! type - where .Self impls Hashable & EqualityComparable & Movable] - (hm: HashMap(KeyType, i32)*, k: KeyType) -> i32; + KeyT:! type + where .Self impls Hashable & Eq & Movable] + (hm: HashMap(KeyT, i32)*, k: KeyT) -> i32; fn PrintValueOrDefault[ - KeyType:! Printable - where .Self impls Hashable & EqualityComparable & Movable, + KeyT:! Printable + where .Self impls Hashable & Eq & Movable, ValueT:! Printable & HasDefault] - (map: HashMap(KeyType, ValueT), key: KeyT); + (map: HashMap(KeyT, ValueT), key: KeyT); ``` In this case, Carbon will accept the definition and infer the needed constraints @@ -3818,8 +3818,8 @@ If the two facet bindings being constrained to be equal, using either a [same-type constraint](#same-type-constraints), have been declared with different facet types, then the actual type value they are set to will have to satisfy the requirements of both facet types. For example, if -`SortedContainer.ElementType` is declared to have a `Comparable` requirement, -then in these declarations: +`SortedContainer.ElementType` is declared to have a `Ordered` requirement, then +in these declarations: ```carbon // With `=` rewrite constraint: @@ -3836,18 +3836,17 @@ fn Contains_SameType ``` the `where` constraints in both cases mean `CT.ElementType` must satisfy -`Comparable` as well. However, the behavior inside the body of these two inside -the body of the two functions is different. +`Ordered` as well. However, the behavior inside the body of these two inside the +body of the two functions is different. In `Contains_Rewrite`, `CT.ElementType` is rewritten to `SC.ElementType` and uses the facet type of `SC.ElementType`. In `Contains_SameType`, the `where` clause does not affect the API of -`CT.ElementType`, and it would not even be considered to implement `Comparable` +`CT.ElementType`, and it would not even be considered to implement `Ordered` unless there is some declaration like -`observe CT.ElementType == SC.ElementType impls Comparable`. Even then, the -items from the `needles` container won't directly have a `Compare` method -member. +`observe CT.ElementType == SC.ElementType impls Ordered`. Even then, the items +from the `needles` container won't directly have a `Compare` method member. The rule is that an same-type `where` constraint between two type variables does not modify the set of member names of either type. This is in contrast to @@ -4258,12 +4257,12 @@ the same interface for a type. ```carbon choice CompareResult { Less, Equal, Greater } -interface Comparable { +interface Ordered { fn Compare[self: Self](rhs: Self) -> CompareResult; } fn CombinedLess[T:! type](a: T, b: T, - U:! CompatibleWith(T) & Comparable, - V:! CompatibleWith(T) & Comparable) -> bool { + U:! CompatibleWith(T) & Ordered, + V:! CompatibleWith(T) & Ordered) -> bool { match ((a as U).Compare(b as U)) { case .Less => { return True; } case .Greater => { return False; } @@ -4278,8 +4277,8 @@ Used as: ```carbon class Song { ... } -class SongByArtist { adapt Song; impl as Comparable { ... } } -class SongByTitle { adapt Song; impl as Comparable { ... } } +class SongByArtist { adapt Song; impl as Ordered { ... } } +class SongByTitle { adapt Song; impl as Ordered { ... } } let s1: Song = ...; let s2: Song = ...; assert(CombinedLess(s1, s2, SongByArtist, SongByTitle) == True); @@ -4290,7 +4289,7 @@ assert(CombinedLess(s1, s2, SongByArtist, SongByTitle) == True); > > ```carbon > fn CombinedCompare[T:! type] -> (a: T, b: T, ... each CompareT:! CompatibleWith(T) & Comparable) +> (a: T, b: T, ... each CompareT:! CompatibleWith(T) & Ordered) > -> CompareResult { > ... block { > let result: CompareResult = @@ -4310,16 +4309,16 @@ assert(CombinedLess(s1, s2, SongByArtist, SongByTitle) == True); #### Example: Creating an impl out of other implementations -And then to package this functionality as an implementation of `Comparable`, we +And then to package this functionality as an implementation of `Ordered`, we combine `CompatibleWith` with [type adaptation](#adapting-types) and [variadics](#variadic-arguments): ```carbon class ThenCompare( T:! type, - ... each CompareT:! CompatibleWith(T) & Comparable) { + ... each CompareT:! CompatibleWith(T) & Ordered) { adapt T; - extend impl as Comparable { + extend impl as Ordered { fn Compare[self: Self](rhs: Self) -> CompareResult { ... block { let result: CompareResult = @@ -4607,20 +4606,20 @@ The parameter for the type can be used as an argument to the interface being implemented, with or without `extend`: ```carbon -class HashMap(Key:! Hashable, Value:! type) { - extend impl as Has(Key) { ... } - impl as Contains(HashSet(Key)) { ... } +class HashMap(KeyT:! Hashable, ValueT:! type) { + extend impl as Has(KeyT) { ... } + impl as Contains(HashSet(KeyT)) { ... } } ``` or out-of-line the same `forall` parameter can be passed to both: ```carbon -class HashMap(Key:! Hashable, Value:! type) { ... } -impl forall [Key:! Hashable, Value:! type] - HashMap(Key, Value) as Has(Key) { ... } -impl forall [Key:! Hashable, Value:! type] - HashMap(Key, Value) as Contains(HashSet(Key)) { ... } +class HashMap(KeyT:! Hashable, ValueT:! type) { ... } +impl forall [KeyT:! Hashable, ValueT:! type] + HashMap(KeyT, ValueT) as Has(KeyT) { ... } +impl forall [KeyT:! Hashable, ValueT:! type] + HashMap(KeyT, ValueT) as Contains(HashSet(KeyT)) { ... } ``` ### Conditional conformance @@ -4764,28 +4763,6 @@ can only mean one thing, regardless of `T`. but bans cases where there could be ambiguity from overlap. [Rust also supports conditional conformance](https://doc.rust-lang.org/rust-by-example/generics/where.html). -#### Conditional methods - -A method could be defined conditionally for a type by using a more specific type -in place of `Self` in the method declaration. For example, this is how to define -a vector type that only has a `Sort` method if its elements implement the -`Comparable` interface: - -```carbon -class Vector(T:! type) { - // `Vector(T)` has a `Sort()` method if `T` impls `Comparable`. - fn Sort[C:! Comparable, addr self: Vector(C)*](); -} -``` - -**Comparison with other languages:** In -[Rust](https://doc.rust-lang.org/book/ch10-02-traits.html#using-trait-bounds-to-conditionally-implement-methods) -this feature is part of conditional conformance. Swift supports conditional -methods using -[conditional extensions](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID553) -or -[contextual where clauses](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID628). - ### Blanket impl declarations A _blanket impl declaration_ is an `impl` declaration that could apply to more @@ -6704,40 +6681,38 @@ impl forall [T:! IntLike] like T ## Parameterized types -**FIXME: left off here.** - Generic types may be defined by giving them compile-time parameters. Those parameters may be used to specify types in the declarations of its members, such as data fields, member functions, and even interfaces being implemented. For -example, a container type might be parameterized by the type of its elements: - -**FIXME: add a bit that member functions may have additional parameters.** +example, a container type might be parameterized by a facet describing the type +of its elements: ```carbon class HashMap( - KeyType:! Hashable & EqualityComparable & Movable, - ValueType:! Movable) { - // `Self` is `HashMap(KeyType, ValueType)`. + KeyT:! Hashable & Eq & Movable, + ValueT:! Movable) { + // `Self` is `HashMap(KeyT, ValueT)`. - // Parameters may be used in function signatures. - fn Insert[addr self: Self*](k: KeyType, v: ValueType); + // Class parameters may be used in function signatures. + fn Insert[addr self: Self*](k: KeyT, v: ValueT); - // Parameters may be used in field types. - private var buckets: Vector((KeyType, ValueType)); + // Class parameters may be used in field types. + private var buckets: DynArray((KeyT, ValueT)); - // Parameters may be used in interfaces implemented. - extend impl as Container where .ElementType = (KeyType, ValueType); - impl as OrderedWith(HashMap(KeyType, ValueType)); + // Class parameters may be used in interfaces implemented. + extend impl as Container where .ElementType = (KeyT, ValueT); + impl as OrderedWith(HashMap(KeyT, ValueT)); } ``` -Note that, unlike functions, every parameter to a type must be compile-time, -either symbolic using `:!` or template using `template...:!`, not dynamic, with -a plain `:`. +Note that, unlike functions, every parameter to a type must be a compile-time +binding, either symbolic using `:!` or template using `template`...`:!`, not +runtime, with a plain `:`. -Two types are the same if they have the same name and the same arguments. -Carbon's [manual type equality](#manual-type-equality) approach means that the -compiler may not always be able to tell when two +Two types are the same if they have the same name and the same arguments, after +applying aliases and [rewrite constraints](#rewrite-constraints). Carbon's +[manual type equality](#manual-type-equality) approach means that the compiler +may not always be able to tell when two [type expressions](terminology.md#type-expression) are equal without help from the user, in the form of [`observe` declarations](#observe-declarations). This means Carbon will not in general be able to determine when types are unequal. @@ -6746,19 +6721,66 @@ Unlike an [interface's parameters](#parameterized-interfaces), a type's parameters may be [deduced](terminology.md#deduced-parameter), as in: ```carbon -fn ContainsKey[KeyType:! Movable, ValueType:! Movable] - (haystack: HashMap(KeyType, ValueType), needle: KeyType) +fn ContainsKey[KeyT:! Movable, ValueT:! Movable] + (haystack: HashMap(KeyT, ValueT), needle: KeyT) -> bool { ... } fn MyMapContains(s: String) { var map: HashMap(String, i32) = (("foo", 3), ("bar", 5)); - // ✅ Deduces `KeyType` = `String` from the types of both arguments. - // Deduces `ValueType` = `i32` from the type of the first argument. + // ✅ Deduces `KeyT` = `String as Movable` from the types of both arguments. + // Deduces `ValueT` = `i32 as Movable` from the type of the first argument. return ContainsKey(map, s); } ``` Note that restrictions on the type's parameters from the type's declaration can -be [implied constraints](#implied-constraints) on the function's parameters. +be [implied constraints](#implied-constraints) on the function's parameters. In +the above example, the `KeyT` parameter to `ContainsKey` gets `Hashable & Eq` +implied constraints from the declaration of the corresponding parameter to +`HashMap`. + +> **Future work:** We may want to support optional deduced parameters in square +> brackets `[`...`]` before the explicit parameters in round parens `(`...`)`. + +> **References:** This feature is from +> [proposal #1146: Generic details 12: parameterized types](https://github.com/carbon-language/carbon-lang/pull/1146). + +### Generic methods + +A generic type may have methods with additional compile-time parameters. For +example, this `Set(T)` type may be compared to anything implementing the +`Container` interface as long as the element types match: + +```carbon +class Set(T:! Ordered) { + fn Less[U:! Container with .ElementType = T, self: Self](u: U) -> bool; + // ... +} +``` + +The `Less` method is parameterized both by the `T` parameter to the `Set` type +and its own `U` parameter deduced from the type of its first argument. + +### Conditional methods + +A method could be defined conditionally for a generic type by using a more +specific type in place of `Self` in the method declaration. For example, this is +how to define a dynamically sized array type that only has a `Sort` method if +its elements implement the `Ordered` interface: + +```carbon +class DynArray(T:! type) { + // `DynArray(T)` has a `Sort()` method if `T impls Ordered`. + fn Sort[C:! Ordered, addr self: DynArray(C)*](); +} +``` + +**Comparison with other languages:** In +[Rust](https://doc.rust-lang.org/book/ch10-02-traits.html#using-trait-bounds-to-conditionally-implement-methods) +this feature is part of conditional conformance. Swift supports conditional +methods using +[conditional extensions](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID553) +or +[contextual where clauses](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID628). ### Specialization @@ -6870,6 +6892,9 @@ class Optional(T:! Movable) { } ``` +> **Alternative considered:** Direct support for specialization of types was +> considered in [proposal #1146](/proposals/p1146.md#alternatives-considered). + ## Future work ### Dynamic types @@ -6958,23 +6983,18 @@ See details in [the goals document](goals.md#bridge-for-c-customization-points). Some facility for allowing a function to take a variable number of arguments, with the [definition checked](terminology.md#complete-definition-checking) -independent of calls. - -### Range constraints on symbolic integers - -We currently only support `where` clauses on facet types. We may want to also -support constraints on symbolic integers. The constraint with the most expected -value is the ability to do comparisons like `<`, or `>=`. For example, you might -constrain the `N` member of [`NSpacePoint`](#associated-constants) using an -expression like `PointT:! NSpacePoint where 2 <= .N and .N <= 3`. - -The concern here is supporting this at compile time with more benefit than -complexity. For example, we probably don't want to support integer-range based -types at runtime, and there are also concerns about reasoning about comparisons -between multiple symbolic integer parameters. For example, if `J < K` and -`K <= L`, can we call a function that requires `J < L`? There is also a -secondary syntactic concern about how to write this kind of constraint on a -parameter, as opposed to an associated facet, as in `N:! u32 where ___ >= 2`. +independent of calls. Open +[proposal #2240](https://github.com/carbon-language/carbon-lang/pull/2240) is +adding this feature. + +### Value constraints for template parameters + +We have planned support for predicates that constrain the value of non-facet +template parameters. For example, we might support a predicate that constrains +an integer to live inside a specified range. See +[question-for-leads issue #2153: Checked generics calling templates](https://github.com/carbon-language/carbon-lang/issues/2153) +and +[future work in proposal #2200: Template generics](/proposals/p2200.md#predicates-constraints-on-values). ## References From 20bc45fda551350ebbbdfd1b8c92fa3530331637 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 22 Sep 2023 21:07:00 +0000 Subject: [PATCH 79/83] Checkpoint progress. --- docs/design/generics/details.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 8cccd0d3a62e4..74f86e71e852f 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -7026,6 +7026,3 @@ and - [#2760: Consistent `class` and `interface` syntax](https://github.com/carbon-language/carbon-lang/pull/2760) - [#2964: Expression phase terminology](https://github.com/carbon-language/carbon-lang/pull/2964) - [#3162: Reduce ambiguity in terminology](https://github.com/carbon-language/carbon-lang/pull/3162) - -**FIXME: issue #2495 updated `impl` terminology, should be noted in PR summary. -Also: https://github.com/carbon-language/carbon-lang/pull/2687 ** From c11169869551656e0662ed2edb14a82916548733 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 22 Sep 2023 22:33:27 +0000 Subject: [PATCH 80/83] Checkpoint progress. --- docs/design/generics/details.md | 3632 +++++++++++-------------------- 1 file changed, 1281 insertions(+), 2351 deletions(-) diff --git a/docs/design/generics/details.md b/docs/design/generics/details.md index 74f86e71e852f..3a738703341f8 100644 --- a/docs/design/generics/details.md +++ b/docs/design/generics/details.md @@ -49,46 +49,39 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Parameterized interfaces](#parameterized-interfaces) - [Parameterized named constraints](#parameterized-named-constraints) - [Where constraints](#where-constraints) - - [Kinds of `where` constraints](#kinds-of-where-constraints) - - [Recursive constraints](#recursive-constraints) - - [Rewrite constraints](#rewrite-constraints) - - [Combining constraints with `&`](#combining-constraints-with-) - - [Combining constraints with `and`](#combining-constraints-with-and) - - [Combining constraints with `extends`](#combining-constraints-with-extends) - - [Combining constraints with `impl as` and `impls`](#combining-constraints-with-impl-as-and-impls) - - [Rewrite constraint resolution](#rewrite-constraint-resolution) - - [Precise rules and termination](#precise-rules-and-termination) - - [Qualified name lookup](#qualified-name-lookup) - - [Type substitution](#type-substitution) - - [Examples](#examples) - - [Termination](#termination) - - [Same-type constraints](#same-type-constraints) - - [Implementation of same-type `ImplicitAs`](#implementation-of-same-type-implicitas) - - [Manual type equality](#manual-type-equality) - - [Observe declarations](#observe-declarations) - - [Implements constraints](#implements-constraints) - - [Implied constraints](#implied-constraints) + - [Constraint use cases](#constraint-use-cases) + - [Set an associated constant to a specific value](#set-an-associated-constant-to-a-specific-value) + - [Same type constraints](#same-type-constraints) + - [Set an associated type to a specific value](#set-an-associated-type-to-a-specific-value) + - [Equal generic types](#equal-generic-types) + - [Satisfying both type-of-types](#satisfying-both-type-of-types) + - [Type bound for associated type](#type-bound-for-associated-type) + - [Type bounds on associated types in declarations](#type-bounds-on-associated-types-in-declarations) + - [Type bounds on associated types in interfaces](#type-bounds-on-associated-types-in-interfaces) - [Combining constraints](#combining-constraints) - - [Satisfying both facet types](#satisfying-both-facet-types) - - [Constraints must use a designator](#constraints-must-use-a-designator) - - [Referencing names in the interface being defined](#referencing-names-in-the-interface-being-defined) - - [Constraint examples and use cases](#constraint-examples-and-use-cases) + - [Recursive constraints](#recursive-constraints) - [Parameterized type implements interface](#parameterized-type-implements-interface) - [Another type implements parameterized interface](#another-type-implements-parameterized-interface) + - [Constraints must use a designator](#constraints-must-use-a-designator) + - [Implied constraints](#implied-constraints) - [Must be legal type argument constraints](#must-be-legal-type-argument-constraints) - - [Named constraint constants](#named-constraint-constants) -- [Other constraints as facet types](#other-constraints-as-facet-types) + - [Referencing names in the interface being defined](#referencing-names-in-the-interface-being-defined) + - [Manual type equality](#manual-type-equality) + - [`observe` declarations](#observe-declarations) +- [Other constraints as type-of-types](#other-constraints-as-type-of-types) - [Is a derived class](#is-a-derived-class) - [Type compatible with another type](#type-compatible-with-another-type) - [Same implementation restriction](#same-implementation-restriction) - [Example: Multiple implementations of the same interface](#example-multiple-implementations-of-the-same-interface) - [Example: Creating an impl out of other implementations](#example-creating-an-impl-out-of-other-implementations) - - [Sized types and facet types](#sized-types-and-facet-types) + - [Sized types and type-of-types](#sized-types-and-type-of-types) + - [`TypeId`](#typeid) - [Destructor constraints](#destructor-constraints) -- [Compile-time `let`](#compile-time-let) +- [Generic `let`](#generic-let) - [Parameterized impl declarations](#parameterized-impl-declarations) - [Impl for a parameterized type](#impl-for-a-parameterized-type) - [Conditional conformance](#conditional-conformance) + - [Conditional methods](#conditional-methods) - [Blanket impl declarations](#blanket-impl-declarations) - [Difference between a blanket impl and a named constraint](#difference-between-a-blanket-impl-and-a-named-constraint) - [Wildcard impl declarations](#wildcard-impl-declarations) @@ -100,7 +93,6 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Prioritization rule](#prioritization-rule) - [Acyclic rule](#acyclic-rule) - [Termination rule](#termination-rule) - - [Non-facet arguments](#non-facet-arguments) - [`final` impl declarations](#final-impl-declarations) - [Libraries that can contain a `final` impl](#libraries-that-can-contain-a-final-impl) - [Comparison to Rust](#comparison-to-rust) @@ -119,13 +111,10 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Observing a type implements an interface](#observing-a-type-implements-an-interface) - [Observing interface requirements](#observing-interface-requirements) - [Observing blanket impl declarations](#observing-blanket-impl-declarations) - - [Observing equal to a type implementing an interface](#observing-equal-to-a-type-implementing-an-interface) - [Operator overloading](#operator-overloading) - [Binary operators](#binary-operators) - [`like` operator for implicit conversions](#like-operator-for-implicit-conversions) - [Parameterized types](#parameterized-types) - - [Generic methods](#generic-methods) - - [Conditional methods](#conditional-methods) - [Specialization](#specialization) - [Future work](#future-work) - [Dynamic types](#dynamic-types) @@ -141,7 +130,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Field requirements](#field-requirements) - [Bridge for C++ customization points](#bridge-for-c-customization-points) - [Variadic arguments](#variadic-arguments) - - [Value constraints for template parameters](#value-constraints-for-template-parameters) + - [Range constraints on symbolic integers](#range-constraints-on-symbolic-integers) - [References](#references) @@ -240,7 +229,7 @@ An [interface](terminology.md#interface), defines an API that a given type can implement. For example, an interface capturing a linear-algebra vector API might have two methods: -```carbon +``` interface Vector { // Here the `Self` keyword means // "the type implementing this interface". @@ -277,7 +266,7 @@ what is associated with the implementing type for that interface. An impl may be defined inline inside the type definition: -```carbon +``` class Point_Inline { var x: f64; var y: f64; @@ -299,7 +288,7 @@ class Point_Inline { Interfaces that are implemented inline with the `extend` keyword contribute to the type's API: -```carbon +``` class Point_Extend { var x: f64; var y: f64; @@ -322,7 +311,7 @@ Assert(p1.Add(p1) == p2); Without `extend`, those methods may only be accessed with [qualified member names and compound member access](#qualified-member-names-and-compound-member-access): -```carbon +``` // Point_Inline did not use `extend` when // implementing `Vector`: var a: Point_Inline = {.x = 1.0, .y = 2.0}; @@ -351,7 +340,7 @@ This syntax was changed to use `extend` in An impl may also be defined after the type definition, by naming the type between `impl` and `as`: -```carbon +``` class Point_OutOfLine { var x: f64; var y: f64; @@ -436,7 +425,7 @@ done using [`extend` to add to the type's API](#extend-impl), only the declaration in the class definition will use the `extend` keyword, as in this example: -```carbon +``` class Point_ExtendForward { var x: f64; var y: f64; @@ -465,7 +454,7 @@ More about forward declaring implementations in To implement more than one interface when defining a type, simply include an `impl` block or forward declaration per interface. -```carbon +``` class Point_2Extend { var x: f64; var y: f64; @@ -492,7 +481,7 @@ names to be given the same implementation? It seems like that might be a common case, but we won't really know if this is an important case until we get more experience. -```carbon +``` class Player { var name: String; extend impl as Icon { @@ -514,7 +503,7 @@ class Player { To avoid name collisions, you can't extend implementations of two interfaces that have a name in common: -```carbon +``` class GameBoard { extend impl as Drawable { fn Draw[self: Self]() { ... } @@ -535,7 +524,7 @@ cluttering the API of that type or to avoid a name collision with another member of that type. A syntax for reusing method implementations allows us to include names from an implementation selectively: -```carbon +``` class Point_ReuseMethodInImpl { var x: f64; var y: f64; @@ -596,7 +585,7 @@ impl Point_ReuseByOutOfLine as Vector { ### Qualified member names and compound member access -```carbon +``` class Point_NoExtend { var x: f64; var y: f64; @@ -613,7 +602,7 @@ whether or not the implementation is done with an expression writes the member's _qualified name_ in the parentheses of the [compound member access syntax](/docs/design/expressions/member_access.md): -```carbon +``` var p1: Point_NoExtend = {.x = 1.0, .y = 2.0}; var p2: Point_NoExtend = {.x = 2.0, .y = 4.0}; Assert(p1.(Vector.Scale)(2.0) == p2); @@ -625,7 +614,7 @@ the names of members of `Point_NoExtend`. So if there was another interface `Drawable` with method `Draw` defined in the `Plot` package also implemented for `Point_NoExtend`, as in: -```carbon +``` package Plot; import Points; @@ -638,7 +627,7 @@ impl Points.Point_NoExtend as Drawable { ... } You could access `Draw` with a qualified name: -```carbon +``` import Plot; import Points; @@ -673,7 +662,7 @@ declaration of the `impl`. Here is a function that can accept values of any type that has implemented the `Vector` interface: -```carbon +``` fn AddAndScaleGeneric[T:! Vector](a: T, b: T, s: f64) -> T { return a.Add(b).Scale(s); } @@ -715,7 +704,7 @@ to adding a `Vector` [qualification](#qualified-member-names-and-compound-member-access) to replace all simple member accesses of `T`: -```carbon +``` fn AddAndScaleGeneric[T:! Vector](a: T, b: T, s: Double) -> T { return a.(Vector.Add)(b).(Vector.Scale)(s); } @@ -742,7 +731,7 @@ The behavior of calling `AddAndScaleGeneric` with a value of a specific type like `Point_Extend` is to set `T` to `Point_Extend` after all the names have been qualified. -```carbon +``` // AddAndScaleGeneric with T = Point_Extend fn AddAndScaleForPoint_Extend( a: Point_Extend, b: Point_Extend, s: Double) @@ -756,7 +745,7 @@ even when the type supplied by the caller does not [extend the implementation of the interface](terminology.md#extending-an-impl), like `Point_NoExtend`: -```carbon +``` // AddAndScaleGeneric with T = Point_NoExtend fn AddAndScaleForPoint_NoExtend( a: Point_NoExtend, b: Point_NoExtend, s: Double) @@ -788,7 +777,7 @@ substitution process to determine the return type, but the result may be a symbolic value. In this example of calling a checked generic from another checked generic, -```carbon +``` fn DoubleThreeTimes[U:! Vector](a: U) -> U { return AddAndScaleGeneric(a, a, 2.0).Scale(2.0); } @@ -804,7 +793,7 @@ capabilities of `U`. For example, given a parameterized type `GeneralPoint` implementing `Vector`, and a function that takes a `GeneralPoint` and calls `AddAndScaleGeneric` with it: -```carbon +``` class GeneralPoint(C:! Numeric) { impl as Vector { ... } fn Get[self: Self](i: i32) -> C; @@ -914,7 +903,7 @@ A named constraint definition can contain interface requirements using `require Self impls` declarations and names using `alias` declarations. Note that this allows us to declare the aspects of a facet type directly. -```carbon +``` constraint VectorLegoFish { // Interface implementation requirements require Self impls Vector; @@ -930,7 +919,7 @@ A `require Self impls` requirement may alternatively be on a named constraint, instead of an interface, to add all the requirements of another named constraint without adding any of the names: -```carbon +``` constraint DrawVectorLegoFish { // The same as requiring both `Vector` and `LegoFish`. require Self impls VectorLegoFish; @@ -959,14 +948,14 @@ constructs we do expect them to use will be defined in terms of them. For example, if `type` were not a keyword, we could define the Carbon builtin `type` as: -```carbon +``` constraint type { } ``` That is, `type` is the facet type with no requirements (so matches every type), and defines no names. -```carbon +``` fn Identity[T:! type](x: T) -> T { // Can accept values of any type. But, since we know nothing about the // type, we don't know about any operations on `x` inside this function. @@ -993,7 +982,7 @@ There is an analogy between declarations used in a `template constraint` and in an `interface` definition. If an `interface` `I` has (non-`alias`, non-`require`) declarations `X`, `Y`, and `Z`, like so: -```carbon +``` interface I { X; Y; @@ -1004,7 +993,7 @@ interface I { Then a type implementing `I` would have `impl as I` with definitions for `X`, `Y`, and `Z`, as in: -```carbon +``` class ImplementsI { // ... impl as I { @@ -1017,7 +1006,7 @@ class ImplementsI { But a `template constraint`, `S`: -```carbon +``` template constraint S { X; Y; @@ -1027,7 +1016,7 @@ template constraint S { would match any type with definitions for `X`, `Y`, and `Z` directly: -```carbon +``` class ImplementsS { // ... X { ... } @@ -1046,7 +1035,7 @@ type `I2` as long as the requirements of `I1` are a superset of the requirements of `I2`. This means a value `x: T` may be passed to functions requiring types to satisfy `I2`, as in this example: -```carbon +``` interface Printable { fn Print[self: Self](); } interface Renderable { fn Draw[self: Self](); } @@ -1078,7 +1067,7 @@ implemented, we provide a combination operator on facet types, written `&`. This operator gives the facet type with the union of all the requirements and the union of the names. -```carbon +``` interface Printable { fn Print[self: Self](); } @@ -1119,7 +1108,7 @@ PrintThenDraw(s); It is an error to use any names that conflict between the two interfaces. -```carbon +``` interface Renderable { fn Center[self: Self]() -> (i32, i32); fn Draw[self: Self](); @@ -1139,7 +1128,7 @@ Conflicts can be resolved at the call site using a [qualified member access expression](#qualified-member-names-and-compound-member-access), or by defining a named constraint explicitly and renaming the methods: -```carbon +``` constraint RenderableAndEndOfGame { require Self impls Renderable; require Self impls EndOfGame; @@ -1167,8 +1156,8 @@ gives itself, `MyTypeOfType & MyTypeOfType == MyTypeOfType`. Also, given two combination should not conflict on any names in the common base. To add to the requirements of a facet type without affecting the names, and so -avoid the possibility of name conflicts, names, use a -[`where .Self impls` clause](#implements-constraints). +avoid the possibility of name conflicts, names, use a `where .Self impls` +clause. ``` // `Printable where .Self impls Renderable` is equivalent to: @@ -1204,12 +1193,13 @@ Some interfaces will depend on other interfaces being implemented for the same type. For example, in C++, [the `Container` concept](https://en.cppreference.com/w/cpp/named_req/Container#Other_requirements) requires all containers to also satisfy the requirements of -`DefaultConstructible`, `CopyConstructible`, `Eq`, and `Swappable`. This is -already a capability for [facet types in general](#facet-types). For consistency -we will use the same semantics and `require Self impls` syntax as we do for +`DefaultConstructible`, `CopyConstructible`, `EqualityComparable`, and +`Swappable`. This is already a capability for +[facet types in general](#facet-types). For consistency we will use the same +semantics and `require Self impls` syntax as we do for [named constraints](#named-constraints): -```carbon +``` interface Equatable { fn Equals[self: Self](rhs: Self) -> bool; } interface Iterable { @@ -1237,7 +1227,7 @@ Like with named constraints, an interface implementation requirement doesn't by itself add any names to the interface, but again those can be added with `alias` declarations: -```carbon +``` interface Hashable { fn Hash[self: Self]() -> u64; require Self impls Equatable; @@ -1263,7 +1253,7 @@ When implementing an interface, we should allow implementing the aliased names as well. In the case of `Hashable` above, this includes all the members of `Equatable`, obviating the need to implement `Equatable` itself: -```carbon +``` class Song { extend impl as Hashable { fn Hash[self: Self]() -> u64 { ... } @@ -1286,7 +1276,7 @@ benefits: We expect this concept to be common enough to warrant dedicated `interface` syntax: -```carbon +``` interface Equatable { fn Equals[self: Self](rhs: Self) -> bool; } interface Hashable { @@ -1329,7 +1319,7 @@ The [`SetAlgebra` protocol](https://swiftdoc.org/v5.1/protocol/setalgebra/) extends `Equatable` and `ExpressibleByArrayLiteral`, which would be declared in Carbon: -```carbon +``` interface SetAlgebra { extend Equatable; extend ExpressibleByArrayLiteral; @@ -1341,7 +1331,7 @@ interface SetAlgebra { [associated constants](terminology.md#associated-entity) also defined in the body in parameters or constraints of the interface being extended. -```carbon +``` // A type can implement `ConvertibleTo` many times, // using different values of `T`. interface ConvertibleTo(T:! type) { ... } @@ -1361,7 +1351,7 @@ interface PreferredConversion { The `extend` declaration makes sense with the same meaning inside a [`constraint`](#named-constraints) definition, and so is also supported. -```carbon +``` interface Media { fn Play[self: Self](); } @@ -1378,7 +1368,7 @@ constraint Combined { This definition of `Combined` is equivalent to requiring both the `Media` and `Job` interfaces being implemented, and aliases their methods. -```carbon +``` // Equivalent constraint Combined { require Self impls Media; @@ -1392,7 +1382,7 @@ Notice how `Combined` has aliases for all the methods in the interfaces it requires. That condition is sufficient to allow a type to `impl` the named constraint: -```carbon +``` class Song { extend impl as Combined { fn Play[self: Self]() { ... } @@ -1403,7 +1393,7 @@ class Song { This is equivalent to implementing the required interfaces directly: -```carbon +``` class Song { extend impl as Media { fn Play[self: Self]() { ... } @@ -1420,7 +1410,7 @@ This is just like when you get an implementation of `Equatable` by implementing Conversely, an `interface` can extend a `constraint`: -```carbon +``` interface MovieCodec { extend Combined; @@ -1431,7 +1421,7 @@ interface MovieCodec { This gives `MovieCodec` the same requirements and names as `Combined`, and so is equivalent to: -```carbon +``` interface MovieCodec { require Self impls Media; alias Play = Media.Play; @@ -1447,7 +1437,7 @@ interface MovieCodec { Consider this set of interfaces, simplified from [this example generic graph library doc](https://docs.google.com/document/d/15Brjv8NO_96jseSesqer5HbghqSTJICJ_fTaZOH0Mg4/edit?usp=sharing&resourcekey=0-CYSbd6-xF8vYHv9m1rolEQ): -```carbon +``` interface Graph { fn Source[addr self: Self*](e: EdgeDescriptor) -> VertexDescriptor; fn Target[addr self: Self*](e: EdgeDescriptor) -> VertexDescriptor; @@ -1469,7 +1459,7 @@ We need to specify what happens when a graph type implements both `IncidenceGraph` and `EdgeListGraph`, since both interfaces extend the `Graph` interface. -```carbon +``` class MyEdgeListIncidenceGraph { extend impl as IncidenceGraph { ... } extend impl as EdgeListGraph { ... } @@ -1483,7 +1473,7 @@ though could be defined in the `impl` block of `IncidenceGraph`, - `IncidenceGraph` implements all methods of `Graph`, `EdgeListGraph` implements none of them. - ```carbon + ``` class MyEdgeListIncidenceGraph { extend impl as IncidenceGraph { fn Source[self: Self](e: EdgeDescriptor) -> VertexDescriptor { ... } @@ -1500,7 +1490,7 @@ though could be defined in the `impl` block of `IncidenceGraph`, - `IncidenceGraph` and `EdgeListGraph` implement all methods of `Graph` between them, but with no overlap. - ```carbon + ``` class MyEdgeListIncidenceGraph { extend impl as IncidenceGraph { fn Source[self: Self](e: EdgeDescriptor) -> VertexDescriptor { ... } @@ -1516,7 +1506,7 @@ though could be defined in the `impl` block of `IncidenceGraph`, - Explicitly implementing `Graph`. - ```carbon + ``` class MyEdgeListIncidenceGraph { extend impl as Graph { fn Source[self: Self](e: EdgeDescriptor) -> VertexDescriptor { ... } @@ -1529,7 +1519,7 @@ though could be defined in the `impl` block of `IncidenceGraph`, - Implementing `Graph` out-of-line. - ```carbon + ``` class MyEdgeListIncidenceGraph { extend impl as IncidenceGraph { ... } extend impl as EdgeListGraph { ... } @@ -1572,11 +1562,11 @@ therefore provides a way to create new types APIs, in particular with different interface implementations, by [adapting](terminology.md#adapting-a-type) them: -```carbon +``` interface Printable { fn Print[self: Self](); } -interface Ordered { +interface Comparable { fn Less[self: Self](rhs: Self) -> bool; } class Song { @@ -1584,7 +1574,7 @@ class Song { } class SongByTitle { adapt Song; - extend impl as Ordered { + extend impl as Comparable { fn Less[self: Self](rhs: Self) -> bool { ... } } } @@ -1595,7 +1585,7 @@ class FormattedSong { class FormattedSongByTitle { adapt Song; extend impl as Printable = FormattedSong; - extend impl as Ordered = SongByTitle; + extend impl as Comparable = SongByTitle; } ``` @@ -1620,10 +1610,10 @@ This allows developers to provide implementations of new interfaces (as in Inside an adapter, the `Self` type matches the adapter. Members of the original type may be accessed either by a cast: -```carbon +``` class SongByTitle { adapt Song; - extend impl as Ordered { + extend impl as Comparable { fn Less[self: Self](rhs: Self) -> bool { return (self as Song).Title() < (rhs as Song).Title(); } @@ -1633,10 +1623,10 @@ class SongByTitle { or using a qualified member access expression: -```carbon +``` class SongByTitle { adapt Song; - extend impl as Ordered { + extend impl as Comparable { fn Less[self: Self](rhs: Self) -> bool { return self.(Song.Title)() < rhs.(Song.Title)(); } @@ -1645,8 +1635,8 @@ class SongByTitle { ``` **Comparison with other languages:** This matches the Rust idiom called -"newtype", which is used to implement traits on types while avoiding -[coherence](terminology.md#coherence) problems, see +"newtype", which is used to implement traits on types while avoiding coherence +problems, see [here](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types) and [here](https://github.com/Ixrec/rust-orphan-rules#user-content-why-are-the-orphan-rules-controversial). @@ -1659,9 +1649,9 @@ compiler provides it as ### Adapter compatibility -Consider a [type with a facet parameter, like a hash map](#parameterized-types): +Consider a type with a facet parameter, like a hash map: -```carbon +``` interface Hashable { ... } class HashMap(KeyT:! Hashable, ValueT:! type) { fn Find[self: Self](key: KeyT) -> Optional(ValueT); @@ -1671,7 +1661,7 @@ class HashMap(KeyT:! Hashable, ValueT:! type) { A user of this type will provide specific values for the key and value types: -```carbon +``` class Song { extend impl as Hashable { ... } // ... @@ -1688,7 +1678,7 @@ specified as requirements. This allows us to evaluate when we can convert between two different arguments to a parameterized type. Consider two adapters of `Song` that implement `Hashable`: -```carbon +``` class PlayableSong { adapt Song; extend impl as Hashable = Song; @@ -1720,7 +1710,7 @@ replacing an interface implementation. Users would indicate that an adapter starts from the original type's existing API by using the `extend` keyword before `adapt`: -```carbon +``` class Song { extend impl as Hashable { ... } extend impl as Printable { ... } @@ -1730,7 +1720,7 @@ class SongByArtist { extend adapt Song; // Add an implementation of a new interface - extend impl as Ordered { ... } + extend impl as Comparable { ... } // Replace an existing implementation of an interface // with an alternative. @@ -1740,7 +1730,7 @@ class SongByArtist { The resulting type `SongByArtist` would: -- implement `Ordered`, unlike `Song`, +- implement `Comparable`, unlike `Song`, - implement `Hashable`, but differently than `Song`, and - implement `Printable`, inherited from `Song`. @@ -1760,7 +1750,7 @@ To avoid or resolve name conflicts between interfaces, an `impl` may be declared without [`extend`](#extend-impl). The names in that interface may then be pulled in individually or renamed using `alias` declarations. -```carbon +``` class SongRenderToPrintDriver { extend adapt Song; @@ -1792,7 +1782,7 @@ an adapter that provides an implementation of `CompareLib.Comparable` for [`extend` facility of adapters](#extending-adapter) to preserve the `SongLib.Song` API. -```carbon +``` import CompareLib; import SongLib; @@ -1815,7 +1805,7 @@ class Song { The caller can either convert `SongLib.Song` values to `Song` when calling `CompareLib.Sort` or just start with `Song` values in the first place. -```carbon +``` var lib_song: SongLib.Song = ...; CompareLib.Sort((lib_song as Song,)); @@ -1834,7 +1824,7 @@ syntax. For example, given an interface `Comparable` for deciding which value is smaller: -```carbon +``` interface Comparable { fn Less[self: Self](rhs: Self) -> bool; } @@ -1843,7 +1833,7 @@ interface Comparable { We might define an adapter that implements `Comparable` for types that define another interface `Difference`: -```carbon +``` interface Difference { fn Sub[self: Self](rhs: Self) -> i32; } @@ -1869,7 +1859,7 @@ class IntWrapper { **TODO:** If we support function types, we could potentially pass a function to use to the adapter instead: -```carbon +``` class ComparableFromDifferenceFn (T:! type, Difference:! fnty(T, T)->i32) { adapt T; @@ -1899,7 +1889,7 @@ implement the interface on that instead. Any member of the class can cast its `self` parameter to the adapter type when it wants to make use of the private impl. -```carbon +``` // Public, in API file class Complex64 { // ... @@ -1934,7 +1924,7 @@ Consider a case where a function will call several functions from an interface that the type does not [extend the implementation of](terminology.md#extending-an-impl). -```carbon +``` interface DrawingContext { fn SetPen[self: Self](...); fn SetFill[self: Self](...); @@ -1950,7 +1940,7 @@ extend the implementation of the interface. This avoids having to [qualify](terminology.md#qualified-member-access-expression) each call to methods in the interface. -```carbon +``` class DrawInWindow { adapt Window; extend impl as DrawingContext = Window; @@ -1965,7 +1955,7 @@ fn Render(w: Window) { ``` **Note:** Another way to achieve this is to use a -[local symbolic facet constant](#compile-time-let). +[local symbolic facet constant](#generic-let). ```carbon fn Render(w: Window) { @@ -2001,7 +1991,7 @@ without assigning a value. As constants, they are declared using the `let` introducer. For example, a fixed-dimensional point type could have the dimension as an associated constant. -```carbon +``` interface NSpacePoint { let N:! i32; // The following require: 0 <= i < N. @@ -2016,7 +2006,7 @@ 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`: -```carbon +``` class Point2D { extend impl as NSpacePoint where .N = 2 { fn Get[addr self: Self*](i: i32) -> f64 { ... } @@ -2045,7 +2035,7 @@ keyword. The list of assignments is subject to two restrictions: These values may be accessed as members of the type: -```carbon +``` Assert(Point2D.N == 2); Assert(Point3D.N == 3); @@ -2081,7 +2071,7 @@ To be consistent with normal [class function](/docs/design/classes.md#class-functions) declaration syntax, associated class functions are written using a `fn` declaration: -```carbon +``` interface DeserializeFromString { fn Deserialize(serialized: String) -> Self; } @@ -2122,7 +2112,7 @@ implementation. We already have one example of this: the `Self` type discussed interface declares that each implementation will provide a facet constant under a specified name. For example: -```carbon +``` interface StackAssociatedFacet { let ElementType:! type; fn Push[addr self: Self*](value: ElementType); @@ -2137,7 +2127,7 @@ accepting or returning values with the type `ElementType`, which any implementer of `StackAssociatedFacet` must also define. For example, maybe a `DynamicArray` [parameterized type](#parameterized-types) implements `StackAssociatedFacet`: -```carbon +``` class DynamicArray(T:! type) { class IteratorType { ... } fn Begin[addr self: Self*]() -> IteratorType; @@ -2169,7 +2159,7 @@ The keyword `Self` can be used after the `as` in an `impl` declaration as a shorthand for the type being implemented, including in the `where` clause specifying the values of associated facets, as in: -```carbon +``` impl VeryLongTypeName as Add // `Self` here means `VeryLongTypeName` where .Result == Self { @@ -2187,7 +2177,7 @@ The definition of the `StackAssociatedFacet` is sufficient for writing a checked-generic function that operates on anything implementing that interface, for example: -```carbon +``` fn PeekAtTopOfStack[StackType:! StackAssociatedFacet](s: StackType*) -> StackType.ElementType { var top: StackType.ElementType = s->Pop(); @@ -2207,7 +2197,7 @@ Outside the checked-generic, associated facets have the concrete facet values determined by impl lookup, rather than the erased version of that facet used inside a checked-generic. -```carbon +``` var my_array: DynamicArray(i32) = (1, 2, 3); // PeekAtTopOfStack's `StackType` is set to `DynamicArray(i32)` // with `StackType.ElementType` set to `i32`. @@ -2221,7 +2211,7 @@ discussed in the [return type section](#return-type). Associated facets can also be implemented using a [member type](/docs/design/classes.md#member-type). -```carbon +``` interface Container { let IteratorType:! Iterator; ... @@ -2259,7 +2249,7 @@ also known as _generic interfaces_. To write a parameterized version of the stack interface, instead of using associated constants, write a parameter list after the name of the interface: -```carbon +``` interface StackParameterized(ElementType:! type) { fn Push[addr self: Self*](value: ElementType); fn Pop[addr self: Self*]() -> ElementType; @@ -2270,7 +2260,7 @@ interface StackParameterized(ElementType:! type) { Then `StackParameterized(Fruit)` and `StackParameterized(Veggie)` would be considered different interfaces, with distinct implementations. -```carbon +``` class Produce { var fruit: DynamicArray(Fruit); var veggie: DynamicArray(Veggie); @@ -2304,7 +2294,7 @@ parameters can't be deduced. For example, if we were to rewrite [the `PeekAtTopOfStack` example in the "associated facets" section](#associated-facets) for `StackParameterized(T)` it would generate a compile error: -```carbon +``` // ❌ Error: can't deduce interface parameter `T`. fn BrokenPeekAtTopOfStackParameterized [T:! type, StackType:! StackParameterized(T)] @@ -2315,7 +2305,7 @@ This error is because the compiler can not determine if `T` should be `Fruit` or `Veggie` when passing in argument of type `Produce*`. Either `T` should be replaced by a concrete type, like `Fruit`: -```carbon +``` fn PeekAtTopOfFruitStack [StackType:! StackParameterized(Fruit)] (s: StackType*) -> T { ... } @@ -2328,7 +2318,7 @@ var top_fruit: Fruit = Or the value for `T` would be passed explicitly, using `where` constraints described [in this section](#another-type-implements-parameterized-interface): -```carbon +``` fn PeekAtTopOfStackParameterizedImpl (T:! type, StackType:! StackParameterized(T), s: StackType*) -> T { ... @@ -2354,7 +2344,7 @@ Parameterized interfaces are useful for `OrderedWith(T)` interfaces have a parameter that allows type to be comparable with multiple other types, as in: -```carbon +``` interface EqWith(T:! type) { fn Equal[self: Self](rhs: T) -> bool; ... @@ -2385,7 +2375,7 @@ vast majority of cases. As an example, if we had an interface that allowed a type to define how the tuple-member-read operator would work, the index of the member could be an interface parameter: -```carbon +``` interface ReadTupleMember(index:! u32) { let T:! type; // Returns self[index] @@ -2399,7 +2389,7 @@ indices to be associated with different values of `T`. **Caveat:** When implementing an interface twice for a type, the interface parameters are required to always be different. For example: -```carbon +``` interface Map(FromType:! type, ToType:! type) { fn Map[addr self: Self*](needle: FromType) -> Optional(ToType); } @@ -2416,7 +2406,7 @@ In this case, it would be better to have an [adapting type](#adapting-types) to contain the `impl` for the reverse map lookup, instead of implementing the `Map` interface twice: -```carbon +``` class Bijection(FromType:! type, ToType:! type) { extend impl as Map(FromType, ToType) { ... } } @@ -2444,16 +2434,16 @@ support parameters. Those parameters work the same way as for interfaces. ## Where constraints -So far, we have restricted a [symbolic facet binding](#symbolic-facet-bindings) -by saying it has to implement an interface or a set of interfaces. There are a -variety of other constraints we would like to be able to express, such as -applying restrictions to associated constants. This is done using the `where` -operator that adds constraints to a [facet type](#facet-types). +So far, we have restricted a generic type parameter by saying it has to +implement an interface or a set of interfaces. There are a variety of other +constraints we would like to be able to express, such as applying restrictions +to its associated types and associated constants. This is done using the `where` +operator that adds constraints to a type-of-type. -The where operator can be applied to a facet type in a declaration context: +The where operator can be applied to a type-of-type in a declaration context: -```carbon -// Constraints on generic function parameters: +``` +// Constraints on function parameters: fn F[V:! D where ...](v: V) { ... } // Constraints on a class parameter: @@ -2464,7 +2454,7 @@ class S(T:! B where ...) { // Constraints on an interface parameter: interface A(T:! B where ...) { - // Constraints on an associated facet: + // Constraints on an associated type: let U:! C where ...; // Constraints on an associated method: fn G[self: Self, V:! D where ...](v: V); @@ -2473,1280 +2463,549 @@ interface A(T:! B where ...) { We also allow you to name constraints using a `where` operator in a `let` or `constraint` definition. The expressions that can follow the `where` keyword are -described in the ["kinds of `where` constraints"](#kinds-of-where-constraints) -section, but generally look like boolean expressions that should evaluate to -`true`. - -The result of applying a `where` operator to a facet type is another facet type. -Note that this expands the kinds of requirements that facet types can have from -just interface requirements to also include the various kinds of constraints -discussed later in this section. In addition, it can introduce relationships -between different type variables, such as that a member of one is equal to the -member of another. The `where` operator is not associative, so a type expression -using multiple must use round parens `(`...`)` to specify grouping. - -> **Comparison with other languages:** Both Swift and Rust use `where` clauses -> on declarations instead of in the expression syntax. These happen after the -> type that is being constrained has been given a name and use that name to -> express the constraint. -> -> Rust also supports -> [directly passing in the values for associated types](https://rust-lang.github.io/rfcs/0195-associated-items.html#constraining-associated-types) -> when using a trait as a constraint. This is helpful when specifying concrete -> types for all associated types in a trait in order to -> [make it object safe so it can be used to define a trait object type](https://rust-lang.github.io/rfcs/0195-associated-items.html#trait-objects). -> -> Rust is adding trait aliases -> ([RFC](https://github.com/rust-lang/rfcs/blob/master/text/1733-trait-alias.md), -> [tracking issue](https://github.com/rust-lang/rust/issues/41517)) to support -> naming some classes of constraints. - -**References:** `where` constraints were added in proposal -[#818: Constraints for generics (generics details 3)](https://github.com/carbon-language/carbon-lang/pull/818). - -### Kinds of `where` constraints - -There are three kinds of `where` constraints, each of which uses a different -binary operator: - -- _Rewrite constraints_: `where`...`=`... -- _Same-type constraints_: `where`...`==`... -- _Implements constraints_: `where`...`impls`... - -A rewrite constraint is written `where .A = B`, where `A` is the name of an -[associated constant](#associated-constants) which is rewritten to `B`. - -The "dot followed by the name of a member" construct, like `.A`, is called a -_designator_. The name of the designator is looked up in the constraint, and -refers to the value of that member for whatever type is to satisfy this +described in the ["constraint use cases"](#constraint-use-cases) section, but +generally look like boolean expressions that should evaluate to `true`. + +The result of applying a `where` operator to a type-of-type is another +type-of-type. Note that this expands the kinds of requirements that +type-of-types can have from just interface requirements to also include the +various kinds of constraints discussed later in this section. In addition, it +can introduce relationships between different type variables, such as that a +member of one is equal to the member of another. The `where` operator is not +associative, so a type expression using multiple must use round parens `(`...`)` +to specify grouping. + +**Comparison with other languages:** Both Swift and Rust use `where` clauses on +declarations instead of in the expression syntax. These happen after the type +that is being constrained has been given a name and use that name to express the constraint. -> **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. +Rust also supports +[directly passing in the values for associated types](https://rust-lang.github.io/rfcs/0195-associated-items.html#constraining-associated-types) +when using a trait as a constraint. This is helpful when specifying concrete +types for all associated types in a trait in order to +[make it object safe so it can be used to define a trait object type](https://rust-lang.github.io/rfcs/0195-associated-items.html#trait-objects). -A same-type constraint is written `where X == Y`, where `X` and `Y` both name -facets. The constraint is that `X as type` must be the same as `Y as type`. In -cases where a constraint may be written in either form, prefer a rewrite -constraint over a same-type constraint. Note that switching between the two -forms does not change which types satisfies the constraint, and so is a -compatible change for callers. +Rust is adding trait aliases +([RFC](https://github.com/rust-lang/rfcs/blob/master/text/1733-trait-alias.md), +[tracking issue](https://github.com/rust-lang/rust/issues/41517)) to support +naming some classes of constraints. -An implements constraint is written `where T impls C`, where `T` is a facet and -`C` is a facet type. The constraint is that `T` satisfies the requirements of -`C`. +### Constraint use cases -**References:** The definition of rewrite and same-type constraints were -[proposal #2173](https://github.com/carbon-language/carbon-lang/pull/2173). -Implements constraints switched using the `impls` keyword in -[proposal #2483](https://github.com/carbon-language/carbon-lang/pull/2483). - -**Alternatives considered:** +#### Set an associated constant to a specific value -- [Different equality constraint operators for symbolic and constants](/proposals/p2173.md#status-quo) -- [Single one-step equality constraint operators that merges constraints](/proposals/p2173.md#equal-types-with-different-interfaces) -- [Restrict constraints to allow computable type equality](/proposals/p2173.md#restrict-constraints-to-allow-computable-type-equality) -- [Find a fully transitive approach to type equality](/proposals/p2173.md#find-a-fully-transitive-approach-to-type-equality) -- [Different syntax for rewrite constraint](/proposals/p2173.md#different-syntax-for-rewrite-constraint) -- [Different syntax for same-type constraint](/proposals/p2173.md#different-syntax-for-same-type-constraint) -- [Required ordering for rewrites](/proposals/p2173.md#required-ordering-for-rewrites) -- [Multi-constraint `where` clauses](/proposals/p2173.md#multi-constraint-where-clauses) -- [Rewrite constraints in `impl as` constraints](/proposals/p2173.md#rewrite-constraints-in-impl-as-constraints) +We might need to write a function that only works with a specific value of an +[associated constant](#associated-constants) `N`. In this case, the name of the +associated constant is written after a `.`, followed by an `=`, and then the +value: -#### Recursive constraints +``` +fn PrintPoint2D[PointT:! NSpacePoint where .N = 2](p: PointT) { + Print(p.Get(0), ", ", p.Get(1)); +} +``` -We sometimes need to constrain a type to equal one of its associated facets. In -this first example, we want to represent the function `Abs` which will return -`Self` for some but not all types, so we use an associated facet `MagnitudeType` -to encode the return type: +Similarly in an interface definition: -```carbon -interface HasAbs { - extend Numeric; - let MagnitudeType:! Numeric; - fn Abs[self: Self]() -> MagnitudeType; +``` +interface Has2DPoint { + let PointT:! NSpacePoint where .N = 2; } ``` -For types representing subsets of the real numbers, such as `i32` or `f32`, the -`MagnitudeType` will match `Self`, the type implementing an interface. For types -representing complex numbers, the types will be different. For example, the -`Abs()` applied to a `Complex64` value would produce a `f32` result. The goal is -to write a constraint to restrict to the first case. - -In a second example, when you take the slice of a type implementing `Container` -you get a type implementing `Container` which may or may not be the same type as -the original container type. However, taking the slice of a slice always gives -you the same type, and some functions want to only operate on containers whose -slice type is the same as the container type. +The "dot followed by the name of a member" construct, `.N` in the examples +above, is called a _designator_. A designator refers to the value of that member +for whatever type is to satisfy this constraint. -To solve this problem, we think of `Self` as an actual associated facet member -of every interface. We can then address it using `.Self` in a `where` clause, -like any other associated facet member. +To name such a constraint, you may use a `let` or a `constraint` declaration: -```carbon -fn Relu[T:! HasAbs where .MagnitudeType = .Self](x: T) { - // T.MagnitudeType == T so the following is allowed: - return (x.Abs() + x) / 2; -} -fn UseContainer[T:! Container where .SliceType = .Self](c: T) -> bool { - // T.SliceType == T so `c` and `c.Slice(...)` can be compared: - return c == c.Slice(...); +``` +let Point2DInterface:! auto = NSpacePoint where .N = 2; +constraint Point2DInterface { + extend NSpacePoint where .N = 2; } ``` -Notice that in an interface definition, `Self` refers to the type implementing -this interface while `.Self` refers to the associated facet currently being -defined. +This syntax is also used to specify the values of +[associated constants](#associated-constants) when implementing an interface for +a type. -```carbon -interface Container { - let ElementType:! 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. - let SliceType:! Container - where .ElementType = ElementType and - // `.Self` means `SliceType`. - .SliceType = .Self; +A constraint to say that two associated constants should have the same value +without specifying what specific value they should have must use `==` instead of +`=`: - // `Self` means the type implementing `Container`. - fn GetSlice[addr self: Self*] - (start: IteratorType, end: IteratorType) -> SliceType; +``` +interface PointCloud { + let Dim:! i32; + let PointT:! NSpacePoint where .N == Dim; } ``` -Note that [naming](#named-constraint-constants) a recursive constraint using the -[`constraint` introducer](#named-constraints) approach, we can name the -implementing type using `Self` instead of `.Self`, since they refer to the same -type: +#### Same type constraints -```carbon -constraint RealAbs { - extend HasAbs where .MagnitudeType = Self; - // Equivalent to: - extend HasAbs where .MagnitudeType = .Self; -} +##### Set an associated type to a specific value -constraint ContainerIsSlice { - extend Container where .SliceType = Self; - // Equivalent to: - extend Container where .SliceType = .Self; +Functions accepting a generic type might also want to constrain one of its +associated types to be a specific, concrete type. For example, we might want to +have a function only accept stacks containing integers: + +``` +fn SumIntStack[T:! Stack where .ElementType = i32](s: T*) -> i32 { + var sum: i32 = 0; + while (!s->IsEmpty()) { + // s->Pop() has type `T.ElementType` == i32: + sum += s->Pop(); + } + return sum; } ``` -The `.Self` construct follows these rules: - -- `X :!` introduces `.Self:! type`, where references to `.Self` are resolved. - to `X`. This allows you to use `.Self` as an interface parameter as in - `X:! I(.Self)`. -- `A where` introduces `.Self:! A` and `.Foo` _designator_ for each member - `Foo` of `A`. -- It's an error to reference `.Self` if it refers to more than one different - thing or isn't a facet. -- You get the innermost, most-specific type for `.Self` if it is introduced - twice in a scope. By the previous rule, it is only legal if they all refer - to the same facet binding. -- `.Self` may not be on the left side of the `=` in a rewrite constraint. - -So in `X:! A where ...`, `.Self` is introduced twice, after the `:!` and the -`where`. This is allowed since both times it means `X`. After the `:!`, `.Self` -has the type `type`, which gets refined to `A` after the `where`. In contrast, -it is an error if `.Self` could mean two different things, as in: +To name these sorts of constraints, we could use `let` declarations or +`constraint` definitions: -```carbon -// ❌ Illegal: `.Self` could mean `T` or `T.A`. -fn F[T:! InterfaceA where .A impls - (InterfaceB where .B = .Self)](x: T); +``` +let IntStack:! auto = Stack where .ElementType = i32; +constraint IntStack { + extend Stack where .ElementType = i32; +} ``` -#### Rewrite constraints +This syntax is also used to specify the values of +[associated constants](#associated-constants) when implementing an interface for +a type. -In a rewrite constraint, the left-hand operand of `=` must be a `.` followed by -the name of an associated constant. `.Self` is not permitted. +##### Equal generic types -```carbon -interface RewriteSelf { - // ❌ Error: `.Self` is not the name of an associated constant. - let Me:! type where .Self = Self; -} -interface HasAssoc { - let Assoc:! type; -} -interface RewriteSingleLevel { - // ✅ Uses of `A.Assoc` will be rewritten to `i32`. - let A:! HasAssoc where .Assoc = i32; +Alternatively, two generic types could be constrained to be equal to each other, +without specifying what that type is. This uses `==` instead of `=`. For +example, we could make the `ElementType` of an `Iterator` interface equal to the +`ElementType` of a `Container` interface as follows: + +``` +interface Iterator { + let ElementType:! type; + ... } -interface RewriteMultiLevel { - // ❌ Error: Only one level of associated constant is permitted. - let B:! RewriteSingleLevel where .A.Assoc = i32; +interface Container { + let ElementType:! type; + let IteratorType:! Iterator where .ElementType == ElementType; + ... } ``` -This notation is permitted anywhere a constraint can be written, and results in -a new constraint with a different interface: the named member effectively no -longer names an associated constant of the constrained type, and is instead -treated as a rewrite rule that expands to the right-hand side of the constraint, -with any mentioned parameters substituted into that type. +Given an interface with two associated types -```carbon -interface Container { - let Element:! type; - let Slice:! Container where .Element = Element; - fn Add[addr self: Self*](x: Element); -} -// `T.Slice.Element` rewritten to `T.Element` -// because type of `T.Slice` says `.Element = Element`. -// `T.Element` rewritten to `i32` -// because type of `T` says `.Element = i32`. -fn Add[T:! Container where .Element = i32](p: T*, y: T.Slice.Element) { - // ✅ Argument `y` has the same type `i32` as parameter `x` of - // `T.(Container.Add)`, which is also rewritten to `i32`. - p->Add(y); -} -``` - -Rewrites aren't performed on the left-hand side of such an `=`, so -`where .A = .B and .A = C` is not rewritten to `where .A = .B and .B = C`. -Instead, such a `where` clause is invalid when the constraint is -[resolved](#rewrite-constraint-resolution) unless each rule for `.A` specifies -the same rewrite. - -Note that `T:! C where .R = i32` can result in a type `T.R` whose behavior is -different from the behavior of `T.R` given `T:! C`. For example, member lookup -into `T.R` can find different results and operations can therefore have -different behavior. However, this does not violate -[coherence](/proposals/p2173.md#coherence) because the facet types `C` and -`C where .R = i32` don't differ by merely having more type information; rather, -they are different facet types that have an isomorphic set of values, somewhat -like `i32` and `u32`. An `=` constraint is not merely learning a new fact about -a type, it is requesting different behavior. - -This approach has some good properties that -[same-type constraints](#same-type-constraints) have problems with: - -- [Equal types with different interfaces](/proposals/p2173.md#equal-types-with-different-interfaces): - When an associated facet is constrained to be a concrete type, it is - desirable for the associated facet to behave like that concrete type. -- [Type canonicalization](/proposals/p2173.md#type-canonicalization): to - enable efficient type equality. -- [Transitivity of equality of types](/proposals/p2173.md#transitivity-of-equality) - -##### Combining constraints with `&` - -Suppose we have `X = C where .R = A` and `Y = C where .R = B`. What should -`C & X` produce? What should `X & Y` produce? - -- Combining two rewrite rules with different rewrite targets results in a - facet type where the associated constant is ambiguous. Given `T:! X & Y`, - the type expression `T.R` is ambiguous between a rewrite to `A` and a - rewrite to `B`. But given `T:! X & X`, `T.R` is unambiguously rewritten to - `A`. -- Combining a constraint with a rewrite rule with a constraint with no rewrite - rule preserves the rewrite rule, so `C & X` is the same as `X`. For example, - supposing that `interface Container` extends `interface Iterable`, and - `Iterable` has an associated constant `Element`, the constraint - `Container & (Iterable where .Element = i32)` is the same as the constraint - `(Container & Iterable) where .Element = i32` which is the same as the - constraint `Container where .Element = i32`. - -If the rewrite for an associated constant is ambiguous, the facet type is -rejected during [constraint resolution](#rewrite-constraint-resolution). - -> **Alternative considered:** We could perhaps say that `X & Y` results in a -> facet type where the type of `R` has the union of the interface of `A` and the -> interface of `B`, and that `C & X` similarly results in a facet type where the -> type of `R` has the union of the interface of `A` and the interface originally -> specified by `C`. - -##### Combining constraints with `and` - -It's possible for one `=` constraint in a `where` to refer to another. When this -happens, the facet type `C where A and B` is interpreted as -`(C where A) where B`, so rewrites in `A` are applied immediately to names in -`B`, but rewrites in `B` are not applied to names in `A` until the facet type is -[resolved](#rewrite-constraint-resolution): - -```carbon -interface C { - let T:! type; - let U:! type; - let V:! type; -} -class M { - alias Me = Self; -} -// ✅ Same as `C where .T = M and .U = M.Me`, which is -// the same as `C where .T = M and .U = M`. -fn F[A:! C where .T = M and .U = .T.Me]() {} -// ❌ No member `Me` in `A.T:! type`. -fn F[A:! C where .U = .T.Me and .T = M]() {} ``` - -##### Combining constraints with `extends` - -Within an interface or named constraint, `extends` can be used to extend a -constraint that has rewrites. - -```carbon -interface A { - let T:! type; - let U:! type; -} -interface B { - extends A where .T = .U and .U = i32; +interface PairInterface { + let Left:! type; + let Right:! type; } +``` -var n: i32; +we can constrain them to be equal in a function signature: -// ✅ Resolved constraint on `T` is -// `B where .(A.T) = i32 and .(A.U) = i32`. -// `T.(A.T)` is rewritten to `i32`. -fn F(T:! B) -> T.(A.T) { return n; } +``` +fn F[MatchedPairType:! PairInterface where .Left == .Right] + (x: MatchedPairType*); ``` -##### Combining constraints with `impl as` and `impls` - -Within an interface or named constraint, the `impl T as C` syntax does not -permit `=` constraints to be specified directly. However, such constraints can -be specified indirectly, for example if `C` is a named constraint that contains -rewrites. Because these constraints don't change the type of `T`, rewrite -constraints in this context will never result in a rewrite being performed, and -instead are equivalent to `==` constraints. +or in an interface definition: -```carbon -interface A { - let T:! type; - let U:! type; -} -constraint C { - extends A where .T = .U and .U = i32; -} -constraint D { - extends A where .T == .U and .U == i32; -} -interface B { - // OK, equivalent to `impl as D;` or - // `impl as A where .T == .U and .U == i32;`. - impl as C; +``` +interface HasEqualPair { + let P:! PairInterface where .Left == .Right; } - -var n: i32; - -// ❌ No implicit conversion from `i32` to `T.(A.T)`. -// Resolved constraint on `T` is -// `B where T.(A.T) == T.(A.U) and T.(A.U) = i32`. -// `T.(A.T)` is single-step equal to `T.(A.U)`, and -// `T.(A.U)` is single-step equal to `i32`, but -// `T.(A.T)` is not single-step equal to `i32`. -fn F(T:! B) -> T.(A.T) { return n; } ``` -A purely syntactic check is used to determine if an `=` is specified directly in -an expression: - -- An `=` constraint is specified directly in its enclosing `where` expression. -- If an `=` constraint is specified directly in an operand of an `&` or - `(`...`)`, then it is specified directly in that enclosing expression. +This kind of constraint can be named: -In an `impl as C` or `impl T as C` declaration in an interface or named -constraint, `C` is not allowed to directly specify any `=` constraints: - -```carbon -// Compile-time identity function. -fn Identity[T:! type](x:! T) -> T { return x; } - -interface E { - // ❌ Rewrite constraint specified directly. - impl as A where .T = .U and .U = i32; - // ❌ Rewrite constraint specified directly. - impl as type & (A where .T = .U and .U = i32); - // ✅ Not specified directly, but does not result - // in any rewrites being performed. - impl as Identity(A where .T = .U and .U = i32); +``` +let EqualPair:! auto = + PairInterface where .Left == .Right; +constraint EqualPair { + extend PairInterface where .Left == .Right; } ``` -The same rules apply to `impls` constraints. Note that `.T == U` constraints are -also not allowed in this context, because the reference to `.T` is rewritten to -`.Self.T`, and `.Self` is ambiguous. - -```carbon -// ❌ Rewrite constraint specified directly in `impls`. -fn F[T:! A where .U impls (A where .T = i32)](); - -// ❌ Reference to `.T` in same-type constraint is ambiguous: -// does this mean the outer or inner `.Self.T`? -fn G[T:! A where .U impls (A where .T == i32)](); - -// ✅ Not specified directly, but does not result -// in any rewrites being performed. Return type -// is not rewritten to `i32`. -fn H[T:! type where .Self impls C]() -> T.(A.U); +Another example of same type constraints is when associated types of two +different interfaces are constrained to be equal: -// ✅ Return type is rewritten to `i32`. -fn I[T:! C]() -> T.(A.U); ``` - -##### Rewrite constraint resolution - -**FIXME: Should the precise rules for constraints be moved into an appendix?** - -When a facet type is used as the declared type of a facet `T`, the constraints -that were specified within that facet type are _resolved_ to determine the -constraints that apply to `T`. This happens: - -- When the constraint is used explicitly, when declaring symbolic binding, - like a generic parameter or associated constant, of the form - `T:! Constraint`. -- When declaring that a type implements a constraint with an `impl` - declaration, such as `impl T as Constraint`. Note that this does not include - `impl ... as` constraints appearing in `interface` or `constraint` - declarations. - -In each case, the following steps are performed to resolve the facet type's -abstract constraints into a set of constraints on `T`: - -- If multiple rewrites are specified for the same associated constant, they - are required to be identical, and duplicates are discarded. -- Rewrites are performed on other rewrites in order to find a fixed point, - where no rewrite applies within any other rewrite. If no fixed point exists, - the generic parameter declaration or `impl` declaration is invalid. -- Rewrites are performed throughout the other constraints in the facet type -- - that is, in any `==` constraints and `impls` constraints -- and the type - `.Self` is replaced by `T` throughout the constraint. - -```carbon -// ✅ `.T` in `.U = .T` is rewritten to `i32` when initially -// forming the facet type. -// Nothing to do during constraint resolution. -fn InOrder[A:! C where .T = i32 and .U = .T]() {} -// ✅ Facet type has `.T = .U` before constraint resolution. -// That rewrite is resolved to `.T = i32`. -fn Reordered[A:! C where .T = .U and .U = i32]() {} -// ✅ Facet type has `.U = .T` before constraint resolution. -// That rewrite is resolved to `.U = i32`. -fn ReorderedIndirect[A:! (C where .T = i32) & (C where .U = .T)]() {} -// ❌ Constraint resolution fails because -// no fixed point of rewrites exists. -fn Cycle[A:! C where .T = .U and .U = .T]() {} -``` - -To find a fixed point, we can perform rewrites on other rewrites, cycling -between them until they don't change or until a rewrite would apply to itself. -In the latter case, we have found a cycle and can reject the constraint. Note -that it's not sufficient to expand only a single rewrite until we hit this -condition: - -```carbon -// ❌ Constraint resolution fails because -// no fixed point of rewrites exists. -// If we only expand the right-hand side of `.T`, -// we find `.U`, then `.U*`, then `.U**`, and so on, -// and never detect a cycle. -// If we alternate between them, we find -// `.T = .U*`, then `.U = .U**`, then `.V = .U***`, -// then `.T = .U**`, then detect that the `.U` rewrite -// would apply to itself. -fn IndirectCycle[A:! C where .T = .U and .U = .V* and .V = .U*](); +fn Map[CT:! Container, + FT:! Function where .InputType == CT.ElementType] + (c: CT, f: FT) -> Vector(FT.OutputType); ``` -After constraint resolution, no references to rewritten associated constants -remain in the constraints on `T`. - -If a facet type is never used to constrain a type, it is never subject to -constraint resolution, and it is possible for a facet type to be formed for -which constraint resolution would always fail. For example: +###### Satisfying both type-of-types -```carbon -package Broken api; +If the two types being constrained to be equal have been declared with different +type-of-types, then the actual type value they are set to will have to satisfy +both constraints. For example, if `SortedContainer.ElementType` is declared to +be `Comparable`, then in this declaration: -interface X { - let T:! type; - let U:! type; -} -let Bad:! auto = (X where .T = .U) & (X where .U = .T); -// Bad is not used here. +``` +fn Contains + [SC:! SortedContainer, + CT:! Container where .ElementType == SC.ElementType] + (haystack: SC, needles: CT) -> bool; ``` -In such cases, the facet type `Broken.Bad` is not usable: any attempt to use -that facet type to constrain a type would perform constraint resolution, which -would always fail because it would discover a cycle between the rewrites for -`.T` and for `.U`. In order to ensure that such cases are diagnosed, a trial -constraint resolution is performed for all facet types. Note that this trial -resolution can be skipped for facet types that are actually used, which is the -common case. - -##### Precise rules and termination - -This section explains the detailed rules used to implement rewrites. There are -two properties we aim to satisfy: +the `where` constraint means `CT.ElementType` must satisfy `Comparable` as well. +However, inside the body of `Contains`, `CT.ElementType` will act like the +implementation of `Comparable` is declared without [`extend`](#extend-impl). +That is, items from the `needles` container won't directly have a `Compare` +method member, but can still be implicitly converted to `Comparable` and can +still call `Compare` using the compound member access syntax, +`needle.(Comparable.Compare)(elt)`. The rule is that an `==` `where` constraint +between two type variables does not modify the set of member names of either +type. (If you write `where .ElementType = String` with a `=` and a concrete +type, then `.ElementType` is actually set to `String` including the complete +`String` API.) -1. After type-checking, no symbolic references to associated constants that - have an associated rewrite rule remain. -2. Type-checking always terminates in a reasonable amount of time, ideally - linear in the size of the problem. +Note that `==` constraints are symmetric, so the previous declaration of +`Contains` is equivalent to an alternative declaration where `CT` is declared +first and the `where` clause is attached to `SortedContainer`: -Rewrites are applied in two different phases of program analysis. +``` +fn Contains + [CT:! Container, + SC:! SortedContainer where .ElementType == CT.ElementType] + (haystack: SC, needles: CT) -> bool; +``` -- During qualified name lookup and type checking for qualified member access, - if a rewritten member is looked up, the right-hand side's value and type are - used for subsequent checks. -- During substitution, if the symbolic name of an associated constant is - substituted into, and substitution into the left-hand side results in a - value with a rewrite for that constant, that rewrite is applied. +#### Type bound for associated type -In each case, we always rewrite to a value that satisfies property 1 above, and -these two steps are the only places where we might form a symbolic reference to -an associated cosntant, so property 1 is recursively satisfied. Moreover, we -apply only one rewrite in each of the above cases, satisfying property 2. +A `where` clause can express that a type must implement an interface. This is +more flexible than the usual approach of including that interface in the type +since it can be applied to associated type members as well. -###### Qualified name lookup +##### Type bounds on associated types in declarations -Qualified name lookup into either a facet parameter or into an expression whose -type is a symbolic type `T` -- either a facet parameter or an associated facet --- considers names from the facet type `C` of `T`, that is, from `T`’s declared -type. +In the following example, normally the `ElementType` of a `Container` can be any +type. The `SortContainer` function, however, takes a pointer to a type +satisfying `Container` with the additional constraint that its `ElementType` +must satisfy the `Comparable` interface, using an `impls` constraint: -```carbon -interface C { - let M:! i32; - let U:! C; -} -fn F[T:! C](x: T) { - // Value is C.M in all four of these - let a: i32 = x.M; - let b: i32 = T.M; - let c: i32 = x.U.M; - let d: i32 = T.U.M; +``` +interface Container { + let ElementType:! type; + ... } + +fn SortContainer + [ContainerType:! Container where .ElementType impls Comparable] + (container_to_sort: ContainerType*); ``` -When looking up the name `N`, if `C` is an interface `I` and `N` is the name of -an associated constant in that interface, the result is a symbolic value -representing "the member `N` of `I`". If `C` is formed by combining interfaces -with `&`, all such results are required to find the same associated constant, -otherwise we reject for ambiguity. +In contrast to [a same type constraint](#same-type-constraints), this does not +say what type `ElementType` exactly is, just that it must satisfy some +type-of-type. -If a member of a class or interface is named in a qualified name lookup, the -type of the result is determined by performing a substitution. For an interface, -`Self` is substituted for the self type, and any parameters for that class or -interface (and enclosing classes or interfaces, if any) are substituted into the -declared type. +**Note:** `Container` defines `ElementType` as having type `type`, but +`ContainerType.ElementType` has type `Comparable`. This is because +`ContainerType` has type `Container where .ElementType impls Comparable`, not +`Container`. This means we need to be a bit careful when talking about the type +of `ContainerType` when there is a `where` clause modifying it. -```carbon -interface SelfIface { - fn Get[self: Self]() -> Self; -} -class UsesSelf(T:! type) { - // Equivalent to `fn Make() -> UsesSelf(T)*;` - fn Make() -> Self*; - impl as SelfIface; -} +##### Type bounds on associated types in interfaces -// ✅ `T = i32` is substituted into the type of `UsesSelf(T).Make`, -// so the type of `UsesSelf(i32).Make` is `fn () -> UsesSelf(i32)*`. -let x: UsesSelf(i32)* = UsesSelf(i32).Make(); +Given these definitions (omitting `ElementType` for brevity): -// ✅ `Self = UsesSelf(i32)` is substituted into the type -// of `SelfIface.Get`, so the type of `UsesSelf(i32).(SelfIface.Get)` -// is `fn [self: UsesSelf(i32)]() -> UsesSelf(i32)`. -let y: UsesSelf(i32) = x->Get(); ``` - -If a facet type `C` into which lookup is performed includes a `where` clause -saying `.N = U`, and the result of qualified name lookup is the associated -constant `N`, that result is replaced by `U`, and the type of the result is the -type of `U`. No substitution is performed in this step, not even a `Self` -substitution -- any necessary substitutions were already performed when forming -the facet type `C`, and we don’t consider either the declared type or value of -the associated constant at all for this kind of constraint. Going through an -example in detail: - -```carbon -interface A { - let T:! type; -} -interface B { - let U:! type; - // More explicitly, this is of type `A where .(A.T) = Self.(B.U)` - let V:! A where .T = U; +interface IteratorInterface { ... } +interface ContainerInterface { + let IteratorType:! IteratorInterface; + ... } -// Type of W is B. -fn F[W:! B](x: W) { - // The type of the expression `W` is `B`. - // `W.V` finds `B.V` with type `A where .(A.T) = Self.(B.U)`. - // We substitute `Self` = `W` giving the type of `u` as - // `A where .(A.T) = W.(B.U)`. - let u:! auto = W.V; - // The type of `u` is `A where .(A.T) = W.(B.U)`. - // Lookup for `u.T` resolves it to `u.(A.T)`. - // So the result of the qualified member access is `W.(B.U)`, - // and the type of `v` is the type of `W.(B.U)`, namely `type`. - // No substitution is performed in this step. - let v:! auto = u.T; +interface RandomAccessIterator { + extend IteratorInterface; + ... } ``` -The more complex case of +We can then define a function that only accepts types that implement +`ContainerInterface` where its `IteratorType` associated type implements +`RandomAccessIterator`: -```carbon -fn F2[Z:! B where .U = i32](x: Z); ``` - -is discussed later. - -###### Type substitution - -At various points during the type-checking of a Carbon program, we need to -substitute a set of (binding, value) pairs into a symbolic value. We saw an -example above: substituting `Self = W` into the type `A where .(A.T) = Self.U` -to produce the value `A where .(A.T) = W.U`. Another important case is the -substitution of inferred parameter values into the type of a function when -type-checking a function call: - -```carbon -fn F[T:! C](x: T) -> T; -fn G(n: i32) -> i32 { - // Deduces T = i32, which is substituted - // into the type `fn (x: T) -> T` to produce - // `fn (x: i32) -> i32`, giving `i32` as the type - // of the call expression. - return F(n); -} +fn F[ContainerType:! ContainerInterface + where .IteratorType impls RandomAccessIterator] + (c: ContainerType); ``` -Qualified name lookup is not re-done as a result of type substitution. For a -template, we imagine there’s a completely separate step that happens before type -substitution, where qualified name lookup is redone based on the actual value of -template arguments; this proceeds as described in the previous section. -Otherwise, we performed the qualified name lookup when type-checking symbolic -expressions, and do not do it again: - -```carbon -interface IfaceHasX { - let X:! type; -} -class ClassHasX { - class X {} -} -interface HasAssoc { - let Assoc:! IfaceHasX; -} - -// Qualified name lookup finds `T.(HasAssoc.Assoc).(IfaceHasX.X)`. -fn F(T:! HasAssoc) -> T.Assoc.X; +We would like to be able to name this constraint, defining a +`RandomAccessContainer` to be a type-of-type whose types satisfy +`ContainerInterface` with an `IteratorType` satisfying `RandomAccessIterator`. -fn G(T:! HasAssoc where .Assoc = ClassHasX) { - // `T.Assoc` rewritten to `ClassHasX` by qualified name lookup. - // Names `ClassHasX.X`. - var a: T.Assoc.X = {}; - // Substitution produces `ClassHasX.(IfaceHasX.X)`, - // not `ClassHasX.X`. - var b: auto = F(T); -} ``` - -During substitution, we might find a member access that named an opaque symbolic -associated constant in the original value can now be resolved to some specific -value. It’s important that we perform this resolution: - -```carbon -interface A { - let T:! type; -} -class K { fn Member(); } -fn H[U:! A](x: U) -> U.T; -fn J[V:! A where .T = K](y: V) { - // We need the interface of `H(y)` to include - // `K.Member` in order for this lookup to succeed. - H(y).Member(); -} -``` - -The values being substituted into the symbolic value are themselves already -fully substituted and resolved, and in particular, satisfy property 1 given -above. - -Substitution proceeds by recursively rebuilding each symbolic value, bottom-up, -replacing each substituted binding with its value. Doing this naively will -propagate values like `i32` in the `F`/`G` case earlier in this section, but -will not propagate rewrite constants like in the `H`/`J` case. The reason is -that the `.T = K` constraint is in the _type_ of the substituted value, rather -than in the substituted value itself: deducing `T = i32` and converting `i32` to -the type `C` of `T` preserves the value `i32`, but deducing `U = V` and -converting `V` to the type `A` of `U` discards the rewrite constraint. - -In order to apply rewrites during substitution, we associate a set of rewrites -with each value produced by the recursive rebuilding procedure. This is somewhat -like having substitution track a refined facet type for the type of each value, -but we don’t need -- or want -- for the type to change during this process, only -for the rewrites to be properly applied. For a substituted binding, this set of -rewrites is the rewrites found on the type of the corresponding value prior to -conversion to the type of the binding. When rebuilding a member access -expression, if we have a rewrite corresponding to the accessed member, then the -resulting value is the target of the rewrite, and its set of rewrites is that -found in the type of the target of the rewrite, if any. Because the target of -the rewrite is fully resolved already, we can ask for its type without -triggering additional work. In other cases, the rewrite set is empty; all -necessary rewrites were performed when type-checking the value we're -substituting into. - -Continuing an example from [qualified name lookup](#qualified-name-lookup): - -```carbon -interface A { - let T:! type; -} -interface B { - let U:! type; - let V:! A where .T = U; +let RandomAccessContainer:! auto = + ContainerInterface where .IteratorType impls RandomAccessIterator; +// or +constraint RandomAccessContainer { + extend ContainerInterface + where .IteratorType impls RandomAccessIterator; } -// Type of the expression `Z` is `B where .(B.U) = i32` -fn F2[Z:! B where .U = i32](x: Z) { - // The type of the expression `Z` is `B where .U = i32`. - // `Z.V` is looked up and finds the associated facet `(B.V)`. - // The declared type is `A where .(A.T) = Self.U`. - // We substitute `Self = Z` with rewrite `.U = i32`. - // The resulting type is `A where .(A.T) = i32`. - // So `u` is `Z.V` with type `A where .(A.T) = i32`. - let u:! auto = Z.V; - // The type of `u` is `A where .(A.T) = i32`. - // Lookup for `u.T` resolves it to `u.(A.T)`. - // So the result of the qualified member access is `i32`, - // and the type of `v` is the type of `i32`, namely `type`. - // No substitution is performed in this step. - let v:! auto = u.T; -} +// With the above definition: +fn F[ContainerType:! RandomAccessContainer](c: ContainerType); +// is equivalent to: +fn F[ContainerType:! ContainerInterface + where .IteratorType impls RandomAccessIterator] + (c: ContainerType); ``` -###### Examples +#### Combining constraints -```carbon -interface Container { - let Element:! type; -} -interface SliceableContainer { - extends Container; - let Slice:! Container where .Element = Self.(Container.Element); -} -// ❌ Qualified name lookup rewrites this facet type to -// `SliceableContainer where .(Container.Element) = .Self.(Container.Element)`. -// Constraint resolution rejects this because this rewrite forms a cycle. -fn Bad[T:! SliceableContainer where .Element = .Slice.Element](x: T.Element) {} -``` +Constraints can be combined by separating constraint clauses with the `and` +keyword. This example expresses a constraint that two associated types are equal +and satisfy an interface: -```carbon -interface Helper { - let D:! type; -} -interface Example { - let B:! type; - let C:! Helper where .D = B; -} -// ✅ `where .D = ...` by itself is fine. -// `T.C.D` is rewritten to `T.B`. -fn Allowed(T:! Example, x: T.C.D); -// ❌ But combined with another rewrite, creates an infinite loop. -// `.C.D` is rewritten to `.B`, resulting in `where .B = .B`, -// which causes an error during constraint resolution. -// Using `==` instead of `=` would make this constraint redundant, -// rather than it being an error. -fn Error(T:! Example where .B = .C.D, x: T.C.D); ``` - -```carbon -interface Allowed; -interface AllowedBase { - let A:! Allowed; -} -interface Allowed { - extends AllowedBase where .A = .Self; -} -// ✅ The final type of `x` is `T`. It is computed as follows: -// In `((T.A).A).A`, the inner `T.A` is rewritten to `T`, -// resulting in `((T).A).A`, which is then rewritten to -// `(T).A`, which is then rewritten to `T`. -fn F(T:! Allowed, x: ((T.A).A).A); +fn EqualContainers + [CT1:! Container, + CT2:! Container where .ElementType impls HasEquality + and .ElementType == CT1.ElementType] + (c1: CT1*, c2: CT2*) -> bool; ``` -```carbon -interface MoveYsRight; -constraint ForwardDeclaredConstraint(X:! MoveYsRight); -interface MoveYsRight { - let X:! MoveYsRight; - // Means `Y:! MoveYsRight where .X = X.Y` - let Y:! ForwardDeclaredConstraint(X); -} -constraint ForwardDeclaredConstraint(X:! MoveYsRight) { - extends MoveYsRight where .X = X.Y; -} -// ✅ The final type of `x` is `T.X.Y.Y`. It is computed as follows: -// The type of `T` is `MoveYsRight`. -// The type of `T.Y` is determined as follows: -// - Qualified name lookup finds `MoveYsRight.Y`. -// - The declared type is `ForwardDeclaredConstraint(Self.X)`. -// - That is a named constraint, for which we perform substitution. -// Substituting `X = Self.X` gives the type -// `MoveYsRight where .X = Self.X.Y`. -// - Substituting `Self = T` gives the type -// `MoveYsRight where .X = T.X.Y`. -// The type of `T.Y.Y` is determined as follows: -// - Qualified name lookup finds `MoveYsRight.Y`. -// - The declared type is `ForwardDeclaredConstraint(Self.X)`. -// - That is a named constraint, for which we perform substitution. -// Substituting `X = Self.X` gives the type -// `MoveYsRight where .X = Self.X.Y`. -// - Substituting `Self = T.Y` with -// rewrite `.X = T.X.Y` gives the type -// `MoveYsRight where .X = T.Y.X.Y`, but -// `T.Y.X` is replaced by `T.X.Y`, giving -// `MoveYsRight where .X = T.X.Y.Y`. -// The type of `T.Y.Y.X` is determined as follows: -// - Qualified name lookup finds `MoveYsRight.X`. -// - The type of `T.Y.Y` says to rewrite that to `T.X.Y.Y`. -// - The result is `T.X.Y.Y`, of type `MoveYsRight`. -fn F4(T:! MoveYsRight, x: T.Y.Y.X); -``` - -###### Termination - -Each of the above steps performs at most one rewrite, and doesn't introduce any -new recursive type-checking steps, so should not introduce any new forms of -non-termination. Rewrite constraints thereby give us a deterministic, -terminating type canonicalization mechanism for associated constants: in `A.B`, -if the type of `A` specifies that `.B = C`, then `A.B` is replaced by `C`. -Equality of types constrained in this way is transitive. - -However, some existing forms of non-termination may remain, such as template -instantiation triggering another template instantiation. Such cases will need to -be detected and handled in some way, such as by a depth limit, but doing so -doesn't compromise the soundness of the type system. - -#### Same-type constraints - -A same-type constraint describes that two type expressions are known to evaluate -to the same value. Unlike a [rewrite constraint](#rewrite-constraints), however, -the two type expressions are treated as distinct types when type-checking a -symbolic expression that refers to them. - -Same-type constraints are brought into scope, looked up, and resolved exactly as -if there were a `SameAs(U:! type)` interface and a `T == U` impl corresponded to -`T is SameAs(U)`, except that `==` is commutative. As such, it's not possible to -ask for a list of types that are the same as a given type, nor to ask whether -there exists a type that is the same as a given type and has some property. But -it is possible to ask whether two types are (non-transitively) known to be the -same. - -In order for same-type constraints to be useful, they must allow the two types -to be treated as actually being the same in some context. This can be -accomplished by the use of `==` constraints in an `impl`, such as in the -built-in implementation of `ImplicitAs`: +**Comparison with other languages:** Swift and Rust use commas `,` to separate +constraint clauses, but that only works because they place the `where` in a +different position in a declaration. In Carbon, the `where` is attached to a +type in a parameter list that is already using commas to separate parameters. -```carbon -final impl forall [T:! type, U:! type where .Self == T] T as ImplicitAs(U) { - fn Convert[self: Self](other: U) -> U { ... } -} -``` - -> **Alternative considered:** It superficially seems like it would be convenient -> if such implementations were made available implicitly –- for example, by -> writing `impl forall [T:! type] T as ImplicitAs(T)` -– but in more complex -> examples that turns out to be problematic. Consider: -> -> ```carbon -> interface CommonTypeWith(U:! type) { -> let Result:! type; -> } -> final impl forall [T:! type] T as CommonTypeWith(T) where .Result = T {} -> -> fn F[T:! Potato, U:! Hashable where .Self == T](x: T, y: U) -> auto { -> // What is T.CommonTypeWith(U).Result? Is it T or U? -> return (if cond then x else y).Hash(); -> } -> ``` -> -> With this alternative, `impl` validation for `T as CommonTypeWith(U)` fails: -> we cannot pick a common type when given two distinct type expressions, even if -> we know they evaluate to the same type, because we would not know which API -> the result should have. - -##### Implementation of same-type `ImplicitAs` - -It is possible to implement the above `impl` of `ImplicitAs` directly in Carbon, -without a compiler builtin, by taking advantage of the built-in conversion -between `C where .A = X` and `C where .A == X`: +#### Recursive constraints -```carbon -interface EqualConverter { - let T:! type; - fn Convert(t: T) -> Self; -} -fn EqualConvert[T:! type](t: T, U:! EqualConverter where .T = T) -> U { - return U.Convert(t); -} -impl forall [U:! type] U as EqualConverter where .T = U { - fn Convert(u: U) -> U { return u; } -} +We sometimes need to constrain a type to equal one of its associated types. In +this first example, we want to represent the function `Abs` which will return +`Self` for some but not all types, so we use an associated type `MagnitudeType` +to encode the return type: -impl forall [T:! type, U:! type where .Self == T] T as ImplicitAs(U) { - fn Convert[self: Self]() -> U { return EqualConvert(self, U); } -} ``` - -The transition from `(T as ImplicitAs(U)).Convert`, where we know that `U == T`, -to `EqualConverter.Convert`, where we know that `.T = U`, allows a same-type -constraint to be used to perform a rewrite. - -##### Manual type equality - -A same-type constraint establishes -[type expressions](terminology.md#type-expression) are equal, and allows -implicit conversions between them. However, determining whether two type -expressions are _transitively_ equal is in general undecidable, as -[has been shown in Swift](https://forums.swift.org/t/swift-type-checking-is-undecidable/39024). - -Carbon does not combine these equalities between type expressions. This means -that if two type expressions are only transitively equal, the user will need to -include a sequence of casts or use an -[`observe` declaration](#observe-declarations) to convert between them. - -Given this interface `Transitive` that has associated facets that are -constrained to all be equal, with interfaces `P`, `Q`, and `R`: - -```carbon -interface P { fn InP[self: Self](); } -interface Q { fn InQ[self: Self](); } -interface R { fn InR[self: Self](); } - -interface Transitive { - let A:! P; - let B:! Q where .Self == A; - let C:! R where .Self == B; - - fn GetA[self: Self]() -> A; - fn TakesC[self: Self](c: C); +interface HasAbs { + extend Numeric; + let MagnitudeType:! Numeric; + fn Abs[self: Self]() -> MagnitudeType; } ``` -A cast to `B` is needed to call `TakesC` with a value of type `A`, so each step -only relies on one equality: +For types representing subsets of the real numbers, such as `i32` or `f32`, the +`MagnitudeType` will match `Self`, the type implementing an interface. For types +representing complex numbers, the types will be different. For example, the +`Abs()` applied to a `Complex64` value would produce a `f32` result. The goal is +to write a constraint to restrict to the first case. -```carbon -fn F[T:! Transitive](t: T) { - // ✅ Allowed - t.TakesC(t.GetA() as T.B); +In a second example, when you take the slice of a type implementing `Container` +you get a type implementing `Container` which may or may not be the same type as +the original container type. However, taking the slice of a slice always gives +you the same type, and some functions want to only operate on containers whose +slice type is the same as the container type. - // ✅ Allowed - let b: T.B = t.GetA(); - t.TakesC(b); +To solve this problem, we think of `Self` as an actual associated type member of +every interface. We can then address it using `.Self` in a `where` clause, like +any other associated type member. - // ❌ Not allowed: t.TakesC(t.GetA()); -} ``` - -The compiler may have several different `where` clauses to consider, -particularly when an interface has associated facets that recursively satisfy -the same interface. For example, given this interface `Commute`: - -```carbon -interface Commute { - let X:! Commute; - // **FIXME: Not allowed (at least not by Explorer) - // since `Commute` is incomplete here.** - let Y:! Commute where .X == X.Y; - - fn GetX[self: Self]() -> X; - fn GetY[self: Self]() -> Y; - fn TakesXXY[self: Self](xxy: X.X.Y); -} - -// **FIXME: Maybe the following?** -interface Commute; -constraint Helper(T:! Commute); - -interface Commute { - let X:! Commute; - let Y:! Helper(X); - - fn GetX[self: Self]() -> X; - fn GetY[self: Self]() -> Y; - // **FIXME: Don't think it is legal to write `X.X.Y` here.** - fn TakesXXY[self: Self](xxy: X.X.Y); +fn Relu[T:! HasAbs where .MagnitudeType == .Self](x: T) { + // T.MagnitudeType == T so the following is allowed: + return (x.Abs() + x) / 2; } - -constraint Helper(T:! Commute) { - extend Commute where .X == T.Y; +fn UseContainer[T:! Container where .SliceType == .Self](c: T) -> bool { + // T.SliceType == T so `c` and `c.Slice(...)` can be compared: + return c == c.Slice(...); } ``` -and a function `H` taking a value with some type implementing this interface, -then the following would be legal statements in `H`: +Notice that in an interface definition, `Self` refers to the type implementing +this interface while `.Self` refers to the associated type currently being +defined. -```carbon -fn H[C: Commute](c: C) { - // ✅ Legal: argument has type `C.X.X.Y` - c.TakesXXY(c.GetX().GetX().GetY()); +``` +interface Container { + let ElementType:! type; - // ✅ Legal: argument has type `C.X.Y.X` which is equal - // to `C.X.X.Y` following only one `where` clause. - c.TakesXXY(c.GetX().GetY().GetX()); + let SliceType:! Container + where .ElementType == ElementType and + .SliceType == .Self; - // ✅ Legal: cast is legal since it matches a `where` - // clause, and produces an argument that has type - // `C.X.Y.X`. - c.TakesXXY(c.GetY().GetX().GetX() as C.X.Y.X); + fn GetSlice[addr self: Self*] + (start: IteratorType, end: IteratorType) -> SliceType; } ``` -That last call would not be legal without the cast, though. - -**Comparison with other languages:** Other languages such as Swift and Rust -instead perform automatic type equality. In practice this means that their -compiler can reject some legal programs based on heuristics simply to avoid -running for an unbounded length of time. +These recursive constraints can be named: -The benefits of the manual approach include: - -- fast compilation, since the compiler does not need to explore a potentially - large set of combinations of equality restrictions, supporting - [Carbon's goal of fast and scalable development](/docs/project/goals.md#fast-and-scalable-development); -- expressive and predictable semantics, since there are no limitations on how - complex a set of constraints can be supported; and -- simplicity. +``` +let RealAbs:! auto = HasAbs where .MagnitudeType == .Self; +constraint RealAbs { + extend HasAbs where .MagnitudeType == Self; +} +let ContainerIsSlice:! auto = + Container where .SliceType == .Self; +constraint ContainerIsSlice { + extend Container where .SliceType == Self; +} +``` -The main downsides are: +Note that using the `constraint` approach we can name these constraints using +`Self` instead of `.Self`, since they refer to the same type. -- manual work for the source code author to prove to the compiler that types - are equal; and -- verbosity. - -We expect that rich error messages and IDE tooling will be able to suggest -changes to the source code when a single equality constraint is not sufficient -to show two type expressions are equal, but a more extensive automated search -can find a sequence that prove they are equal. +The `.Self` construct follows these rules: -##### Observe declarations +- `X :!` introduces `.Self:! type`, where references to `.Self` are resolved + to `X`. This allows you to use `.Self` as an interface parameter as in + `X:! I(.Self)`. +- `A where` introduces `.Self:! A` and `.Foo` for each member `Foo` of `A` +- It's an error to reference `.Self` if it refers to more than one different + thing or isn't a type. +- You get the innermost, most-specific type for `.Self` if it is introduced + twice in a scope. By the previous rule, it is only legal if they all refer + to the same generic parameter. -Same-type constraints are non-transitive, just like `ImplicitAs`. The developer -can use an `observe` declaration to bring a new same-type constraint into scope: +So in `X:! A where ...`, `.Self` is introduced twice, after the `:!` and the +`where`. This is allowed since both times it means `X`. After the `:!`, `.Self` +has the type `type`, which gets refined to `A` after the `where`. In contrast, +it is an error if `.Self` could mean two different things, as in: -```carbon -observe A == B == C; ``` - -notionally does much the same thing as - -```carbon -impl A as SameAs(C) { ... } +// ❌ Illegal: `.Self` could mean `T` or `T.A`. +fn F[T:! InterfaceA where .A impls + (InterfaceB where .B == .Self)](x: T); ``` -where the `impl` makes use of `A is SameAs(B)` and `B is SameAs(C)`. - -In general, an `observe` declaration lists a sequence of -[type expressions](terminology.md#type-expression) that are equal by some -same-type `where` constraints. These `observe` declarations may be included in -an `interface` definition or a function body, as in: +#### Parameterized type implements interface -```carbon -// **FIXME: Not clear how to fix this example.** -interface Commute { - let X:! Commute; - let Y:! Commute where .X == X.Y; - ... - observe X.X.Y == X.Y.X == Y.X.X; -} +There are times when a function will pass a generic type parameter of the +function as an argument to a parameterized type, as in the previous case, and in +addition the function needs the result to implement a specific interface. -fn H[C: Commute](c: C) { - observe C.X.Y.Y == C.Y.X.Y == C.Y.Y.X; - ... -} ``` +// Some parameterized type. +class Vector(T:! type) { ... } -Every type expression after the first must be equal to some earlier type -expression in the sequence by a single `where` equality constraint. In this -example, +// Parameterized type implements interface only for some arguments. +impl Vector(String) as Printable { ... } -```carbon -// **FIXME: Not clear how to fix this example.** -interface Commute { - let X:! Commute; - let Y:! Commute where .X == X.Y; - ... - // ✅ Legal: - observe X.X.Y.Y == X.Y.X.Y == Y.X.X.Y == X.Y.Y.X; +// Constraint: `T` such that `Vector(T)` implements `Printable` +fn PrintThree + [T:! type where Vector(.Self) impls Printable] + (a: T, b: T, c: T) { + var v: Vector(T) = (a, b, c); + Print(v); } ``` -the expression `X.Y.Y.X` is one equality away from `X.Y.X.Y` and so it is -allowed. This is even though `X.Y.X.Y` isn't the type expression immediately -prior to `X.Y.Y.X`. - -After an `observe` declaration, all of the listed type expressions are -considered equal to each other using a single `where` equality. In this example, -the `observe` declaration in the `Transitive` interface definition provides the -link between associated facets `A` and `C` that allows function `F` to type -check. - -```carbon -interface P { fn InP[self: Self](); } -interface Q { fn InQ[self: Self](); } -interface R { fn InR[self: Self](); } +**Comparison with other languages:** This use case was part of the +[Rust rationale for adding support for `where` clauses](https://rust-lang.github.io/rfcs/0135-where.html#motivation). -interface Transitive { - let A:! P; - let B:! Q where .Self == A; - let C:! R where .Self == B; +#### Another type implements parameterized interface - fn GetA[self: Self]() -> A; - fn TakesC[self: Self](c: C); +In this case, we need some other type to implement an interface parameterized by +a generic type parameter. The syntax for this case follows the previous case, +except now the `.Self` parameter is on the interface to the right of the +`impls`. For example, we might need a type parameter `T` to support explicit +conversion from an integer type like `i32`: - // Without this `observe` declaration, the - // calls in `F` below would not be allowed. - observe A == B == C; +``` +interface As(T:! type) { + fn Convert[self: Self]() -> T; } -fn F[T:! Transitive](t: T) { - var a: T.A = t.GetA(); - - // ✅ Allowed: `T.A` values implicitly convert to - // `T.C` using `observe` in interface definition. - t.TakesC(a); - - // ✅ Allowed: `T.C` extends and implements `R`. - (a as T.C).InR(); +fn Double[T:! Mul where i32 impls As(.Self)](x: T) -> T { + return x * (2 as T); } ``` -Only the current type is searched for interface implementations, so the call to -`InR()` would be illegal without the cast. However, an -[`observe`...`==`...`impls` declaration](#observing-equal-to-a-type-implementing-an-interface) -can be used to identify interfaces that must be implemented through some equal -type. This does not [extending](terminology.md#extending-an-impl) the API of the -type, that is solely determined by the definition of the type. Continuing the -previous example: - -```carbon -fn TakesPQR[U:! P & Q & R](u: U); - -fn G[T:! Transitive](t: T) { - var a: T.A = t.GetA(); - - // ✅ Allowed: `T.A` implements `P` and - // includes its API, as if it extends `P`. - a.InP(); - - // ❌ Illegal: only the current type is - // searched for interface implementations. - a.(Q.InQ()); +### Constraints must use a designator - // ✅ Allowed: values of type `T.A` may be cast - // to `T.B`, which extends and implements `Q`. - (a as T.B).InQ(); +We don't allow a `where` constraint unless it applies a restriction to the +current type. This means referring to some +[designator](#set-an-associated-constant-to-a-specific-value), like +`.MemberName`, or [`.Self`](#recursive-constraints). Examples: - // ✅ Allowed: `T.A` == `T.B` that implements `Q`. - observe T.A == T.B impls Q; - a.(Q.InQ()); +- `Container where .ElementType = i32` +- `type where Vector(.Self) impls Sortable` +- `Addable where i32 impls AddableWith(.Result)` - // ❌ Illegal: `T.A` still does not extend `Q`. - a.InQ(); +Constraints that only refer to other types should be moved to the type that is +declared last. So: - // ✅ Allowed: `T.A` implements `P`, - // `T.A` == `T.B` that implements `Q` (observe above), - // and `T.A` == `T.C` that implements `R`. - observe T.A == T.C impls R; - TakesPQR(a); -} +```carbon +// ❌ Error: `where A == B` does not use `.Self` or a designator +fn F[A:! type, B:! type, C:! type where A == B](a: A, b: B, c: C); ``` -Since adding an `observe`...`impls` declaration only adds non-extending -implementations of interfaces to symbolic facets, they may be added without -breaking existing code. - -#### Implements constraints - -An _implements constraint_ is written `where T impls C`, and expresses that the -facet `T` must implement the requirements of facet type `C`. This is more -flexible than using -[`&` to add a constraint](#combining-interfaces-by-anding-facet-types) since it -can be applied to [associated facet](#associated-facets) members as well. - -In the following example, normally the `ElementType` of a `Container` can be any -type. The `SortContainer` function, however, takes a pointer to a type -satisfying `Container` with the additional constraint that its `ElementType` -must satisfy the `Ordered` interface, using an `impls` constraint: +must be replaced by: ```carbon -interface Container { - let ElementType:! type; - ... -} - -fn SortContainer - [ContainerType:! Container where .ElementType impls Ordered] - (container_to_sort: ContainerType*); +// ✅ Allowed +fn F[A:! type, B:! type where A == .Self, C:! type](a: A, b: B, c: C); ``` -In contrast to a [rewrite constraint](#rewrite-constraints) or a -[same-type constraint](#same-type-constraints), this does not say what type -`ElementType` exactly is, just that it must satisfy requirements of some facet -type. +This includes `where` clauses used in an `impl` declaration: + +``` +// ❌ Error: `where T impls B` does not use `.Self` or a designator +impl forall [T:! type] T as A where T impls B {} +// ✅ Allowed +impl forall [T:! type where .Self impls B] T as A {} +// ✅ Allowed +impl forall [T:! B] T as A {} +``` -> **Note:** `Container` defines `ElementType` as having type `type`, but -> `ContainerType.ElementType` has type `Ordered`. This is because -> `ContainerType` has type `Container where .ElementType impls Ordered`, not -> `Container`. This means we need to be a bit careful when talking about the -> type of `ContainerType` when there is a `where` clause modifying it. +This clarifies the meaning of the `where` clause and reduces the number of +redundant ways to express a restriction, following the +[one-way principle](/docs/project/principles/one_way.md). -An implements constraint can be applied to [`.Self`](#recursive-constraints), as -in `I where .Self impls C`. This has the same requirements as `I & C`, but that -`where` clause does not affect the API. This means that a -[symbolic facet binding](#symbolic-facet-bindings) with that facet type, so `T` -in `T:! I where .Self impls C`, is represented by an -[archetype](terminology.md#archetype) that implements both `I` and `C`, but only -[extends](terminology.md#extending-an-impl) `I`. +**Alternative considered:** This rule was added in proposal +[#2376](https://github.com/carbon-language/carbon-lang/pull/2376), which +[considered whether this rule should be added](/proposals/p2376.md#alternatives-considered). -##### Implied constraints +### Implied constraints -Imagine we have a checked-generic function that accepts an arbitrary -[`HashMap` parameterized type](#parameterized-types): +Imagine we have a generic function that accepts an arbitrary `HashMap`: -```carbon -fn LookUp[KeyT:! type](hm: HashMap(KeyT, i32)*, - k: KeyT) -> i32; +``` +fn LookUp[KeyType:! type](hm: HashMap(KeyType, i32)*, + k: KeyType) -> i32; -fn PrintValueOrDefault[KeyT:! Printable, +fn PrintValueOrDefault[KeyType:! Printable, ValueT:! Printable & HasDefault] - (map: HashMap(KeyT, ValueT), key: KeyT); + (map: HashMap(KeyType, ValueT), key: KeyT); ``` -The `KeyT` in these declarations does not visibly satisfy the requirements of +The `KeyType` in these declarations does not visibly satisfy the requirements of `HashMap`, which requires the type implement `Hashable` and other interfaces: -```carbon +``` class HashMap( - KeyT:! Hashable & Eq & Movable, + KeyType:! Hashable & EqualityComparable & Movable, ...) { ... } ``` -In this case, `KeyT` gets `Hashable` and so on as _implied constraints_. +In this case, `KeyType` gets `Hashable` and so on as _implied constraints_. Effectively that means that these functions are automatically rewritten to add a -`where .Self impls` constraint on `KeyT`: +`where` constraint on `KeyType` attached to the `HashMap` type: -```carbon -fn LookUp[ - KeyT:! type - where .Self impls Hashable & Eq & Movable] - (hm: HashMap(KeyT, i32)*, k: KeyT) -> i32; +``` +fn LookUp[KeyType:! type] + (hm: HashMap(KeyType, i32)* + where KeyType impls Hashable & EqualityComparable & Movable, + k: KeyType) -> i32; -fn PrintValueOrDefault[ - KeyT:! Printable - where .Self impls Hashable & Eq & Movable, - ValueT:! Printable & HasDefault] - (map: HashMap(KeyT, ValueT), key: KeyT); +fn PrintValueOrDefault[KeyType:! Printable, + ValueT:! Printable & HasDefault] + (map: HashMap(KeyType, ValueT) + where KeyType impls Hashable & EqualityComparable & Movable, + key: KeyT); ``` In this case, Carbon will accept the definition and infer the needed constraints -on the symbolic facet parameter. This is both more concise for the author of the +on the generic type parameter. This is both more concise for the author of the code and follows the ["don't repeat yourself" principle](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself). This redundancy is undesirable since it means if the needed constraints for @@ -3758,15 +3017,15 @@ will have already satisfied these constraints. This implied constraint is equivalent to the explicit constraint that each parameter and return type [is legal](#must-be-legal-type-argument-constraints). -> **Note:** These implied constraints affect the _requirements_ of a symbolic -> facet parameter, but not its _member names_. This way you can always look at -> the declaration to see how name resolution works, without having to look up -> the definitions of everything it is used as an argument to. +**Note:** These implied constraints affect the _requirements_ of a generic type +parameter, but not its _member names_. This way you can always look at the +declaration to see how name resolution works, without having to look up the +definitions of everything it is used as an argument to. **Limitation:** To limit readability concerns and ambiguity, this feature is limited to a single signature. Consider this interface declaration: -```carbon +``` interface GraphNode { let Edge:! type; fn EdgesFrom[self: Self]() -> HashSet(Edge); @@ -3780,7 +3039,7 @@ say that the `EdgesFrom` would only be conditionally available when `Edge` does satisfy the constraints on `HashSet` arguments. Instead, Carbon will reject this definition, requiring the user to include all the constraints required for the other declarations in the interface in the declaration of the `Edge` associated -facet. Similarly, a parameter to a class must be declared with all the +type. Similarly, a parameter to a class must be declared with all the constraints needed to declare the members of the class that depend on that parameter. @@ -3792,407 +3051,306 @@ and support some form of this feature as part of their type inference (and [the Rust community is considering expanding support](http://smallcultfollowing.com/babysteps//blog/2022/04/12/implied-bounds-and-perfect-derive/#expanded-implied-bounds)). -#### Combining constraints - -Constraints can be combined by separating constraint clauses with the `and` -keyword. This example expresses a constraint that two associated facets are -equal and satisfy an interface: - -```carbon -fn EqualContainers - [CT1:! Container, - CT2:! Container where .ElementType impls HasEquality - and .ElementType = CT1.ElementType] - (c1: CT1*, c2: CT2*) -> bool; -``` - -**Comparison with other languages:** Swift and Rust use commas `,` to separate -constraint clauses, but that only works because they place the `where` in a -different position in a declaration. In Carbon, the `where` is attached to a -type in a parameter list that is already using commas to separate parameters. - -### Satisfying both facet types +#### Must be legal type argument constraints -If the two facet bindings being constrained to be equal, using either a -[rewrite constraint](#rewrite-constraints) or a -[same-type constraint](#same-type-constraints), have been declared with -different facet types, then the actual type value they are set to will have to -satisfy the requirements of both facet types. For example, if -`SortedContainer.ElementType` is declared to have a `Ordered` requirement, then -in these declarations: +Now consider the case that the generic type parameter is going to be used as an +argument to a parameterized type in a function body, not in the signature. If +the parameterized type was explicitly mentioned in the signature, the implied +constraint feature would ensure all of its requirements were met. The developer +can create a trivial +[parameterized type implements interface](#parameterized-type-implements-interface) +`where` constraint to just say the type is a legal with this argument, by saying +that the parameterized type implements `type`, which all types do. -```carbon -// With `=` rewrite constraint: -fn Contains_Rewrite - [SC:! SortedContainer, - CT:! Container where .ElementType = SC.ElementType] - (haystack: SC, needles: CT) -> bool; +For example, a function that adds its parameters to a `HashSet` to deduplicate +them, needs them to be `Hashable` and so on. To say "`T` is a type where +`HashSet(T)` is legal," we can write: -// With `==` same-type constraint: -fn Contains_SameType - [SC:! SortedContainer, - CT:! Container where .ElementType == SC.ElementType] - (haystack: SC, needles: CT) -> bool; ``` - -the `where` constraints in both cases mean `CT.ElementType` must satisfy -`Ordered` as well. However, the behavior inside the body of these two inside the -body of the two functions is different. - -In `Contains_Rewrite`, `CT.ElementType` is rewritten to `SC.ElementType` and -uses the facet type of `SC.ElementType`. - -In `Contains_SameType`, the `where` clause does not affect the API of -`CT.ElementType`, and it would not even be considered to implement `Ordered` -unless there is some declaration like -`observe CT.ElementType == SC.ElementType impls Ordered`. Even then, the items -from the `needles` container won't directly have a `Compare` method member. - -The rule is that an same-type `where` constraint between two type variables does -not modify the set of member names of either type. This is in contrast to -rewrite constraints like `where .ElementType = String` with a `=`, then -`.ElementType` is actually set to `String` including the complete `String` API. - -Note that `==` constraints are symmetric, so the previous declaration of -`Contains_SameType` is equivalent to an alternative declaration where `CT` is -declared first and the `where` clause is attached to `SortedContainer`: - -```carbon -fn Contains_SameType_Equivalent - [CT:! Container, - SC:! SortedContainer where .ElementType == CT.ElementType] - (haystack: SC, needles: CT) -> bool; +fn NumDistinct[T:! type where HashSet(.Self) impls type] + (a: T, b: T, c: T) -> i32 { + var set: HashSet(T); + set.Add(a); + set.Add(b); + set.Add(c); + return set.Size(); +} ``` -### Constraints must use a designator - -We don't allow a `where` constraint unless it applies a restriction to the -current type. This means referring to some -[designator](#kinds-of-where-constraints), like `.MemberName`, or -[`.Self`](#recursive-constraints). Examples: +This has the same advantages over repeating the constraints on `HashSet` +arguments in the type of `T` as the general implied constraints above. -- `Container where .ElementType = i32` -- `type where Vector(.Self) impls Sortable` -- `Addable where i32 impls AddableWith(.Result)` +### Referencing names in the interface being defined -Constraints that only refer to other types should be moved to the type that is -declared last. So: +The constraint in a `where` clause is required to only reference earlier names +from this scope, as in this example: -```carbon -// ❌ Error: `where A == B` does not use `.Self` or a designator -fn F[A:! type, B:! type, C:! type where A == B](a: A, b: B, c: C); ``` - -must be replaced by: - -```carbon -// ✅ Allowed -fn F[A:! type, B:! type where A == .Self, C:! type](a: A, b: B, c: C); +interface Graph { + let E: Edge; + let V: Vert where .E == E and .Self == E.V; +} ``` -This includes `where` clauses used in an `impl` declaration: +### Manual type equality + +Imagine we have some function with generic parameters: -```carbon -// ❌ Error: `where T impls B` does not use `.Self` or a designator -impl forall [T:! type] T as A where T impls B {} -// ✅ Allowed -impl forall [T:! type where .Self impls B] T as A {} -// ✅ Allowed -impl forall [T:! B] T as A {} +``` +fn F[T:! SomeInterface](x: T) { + x.G(x.H()); +} ``` -This clarifies the meaning of the `where` clause and reduces the number of -redundant ways to express a restriction, following the -[one-way principle](/docs/project/principles/one_way.md). +We want to know if the return type of method `T.H` is the same as the parameter +type of `T.G` in order to typecheck the function. However, determining whether +two type expressions are transitively equal is in general undecidable, as +[has been shown in Swift](https://forums.swift.org/t/swift-type-checking-is-undecidable/39024). -**Alternative considered:** This rule was added in proposal -[#2376](https://github.com/carbon-language/carbon-lang/pull/2376), which -[considered whether this rule should be added](/proposals/p2376.md#alternatives-considered). +Carbon's approach is to only allow implicit conversions between two type +expressions that are constrained to be equal in a single where clause. This +means that if two type expressions are only transitively equal, the user will +need to include a sequence of casts or use an +[`observe` declaration](#observe-declarations) to convert between them. -### Referencing names in the interface being defined +Given this interface `Transitive` that has associated types that are constrained +to all be equal, with interfaces `P`, `Q`, and `R`: -The constraint in a `where` clause is required to only reference earlier names -from this scope, as in this example: +``` +interface P { fn InP[self: Self](); } +interface Q { fn InQ[self: Self](); } +interface R { fn InR[self: Self](); } -```carbon -// ❌ Illegal: `E` references `V` declared later. -interface Graph { - let E: Edge where .V = V; - let V: Vert where .E = E; -} +interface Transitive { + let A:! P; + let B:! Q where .Self == A; + let C:! R where .Self == B; -// ✅ Allowed: Only references to earlier names. -interface Graph { - let E: Edge; - let V: Vert where .E = E and .Self == E.V; + fn GetA[self: Self]() -> A; + fn TakesC[self: Self](c: C); } ``` -### Constraint examples and use cases +A cast to `B` is needed to call `TakesC` with a value of type `A`, so each step +only relies on one equality: -- **Set [associated constant](#associated-constants) to a constant:** For - example in `NSpacePoint where .N = 2`, the associated constant `N` of - `NSpacePoint` must be `2`. This syntax is also used to specify the values of - associated constants when implementing an interface for a type, as in - `impl MyPoint as NSpacePoint where .N = 2 {`...`}`. +``` +fn F[T:! Transitive](t: T) { + // ✅ Allowed + t.TakesC(t.GetA() as T.B); -- **Set an [associated facet](#associated-facets) to a specific value:** - Associated facets are treated like any other associated constant. So - `Stack where .ElementType = i32` is a facet type that restricts to types - that implement the `Stack` interface with integer elements, as in: + // ✅ Allowed + let b: T.B = t.GetA(); + t.TakesC(b); - ```carbon - fn SumIntStack[T:! Stack where .ElementType = i32] - (s: T*) -> i32 { - var sum: i32 = 0; - while (!s->IsEmpty()) { - // s->Pop() returns a value of type - // `T.ElementType` which is `i32`: - sum += s->Pop(); - } - return sum; - } - ``` + // ❌ Not allowed: t.TakesC(t.GetA()); +} +``` - Note that this is a case that can use an `==` same-type constraint instead - of an `=` rewrite constraint. +A value of type `A`, such as the return value of `GetA()`, has the API of `P`. +Any such value also implements `Q`, and since the compiler can see that by way +of a single `where` equality, values of type `A` are treated as if they +implement `Q` [externally](terminology.md#extending-an-impl). However, the +compiler will require a cast to `B` or `C` to see that the type implements `R`. -- **One [associated constant](#associated-constants) must equal another:** For - example with this definition of the interface `PointCloud`: +``` +fn TakesPQR[U:! P & Q & R](u: U); - ```carbon - interface PointCloud { - let Dim:! i32; - let PointT:! NSpacePoint where .N = Dim; - } - ``` +fn G[T:! Transitive](t: T) { + var a: T.A = t.GetA(); - an implementation of `PointCloud` for a type `T` will have - `T.PointT.N == T.Dim`. + // ✅ Allowed: `T.A` implements `P`. + a.InP(); -- **Equal facet bindings:** + // ✅ Allowed: `T.A` implements `Q` externally. + a.(Q.InQ)(); - For example, we could make the `ElementType` of an `Iterator` interface - equal to the `ElementType` of a `Container` interface as follows: + // ❌ Not allowed: a.InQ(); - ```carbon - interface Iterator { - let ElementType:! type; - ... - } - interface Container { - let ElementType:! type; - let IteratorType:! Iterator where .ElementType = ElementType; - ... - } - ``` + // ✅ Allowed: values of type `T.A` may be cast + // to `T.B`, which implements `Q` internally. + (a as T.B).InQ(); - In a function signature, this may be done by referencing an earlier - parameter: + // ✅ Allowed: `T.B` implements `R` externally. + (a as T.B).(R.InR)(); - ```carbon - fn Map[CT:! Container, - FT:! Function where .InputType = CT.ElementType] - (c: CT, f: FT) -> Vector(FT.OutputType); - ``` + // ❌ Not allowed: TakesPQR(a); - In that example, `FT.InputType` is constrained to equal `CT.InputType`. - Given an interface with two associated facets + // ✅ Allowed: `T.B` implements `P`, `Q`, and + // `R`, though the implementations of `P` + // and `R` are external. + TakesPQR(a as T.B); +} +``` - ```carbon - interface PairInterface { - let Left:! type; - let Right:! type; - } - ``` +The compiler may have several different `where` clauses to consider, +particularly when an interface has associated types that recursively satisfy the +same interface. For example, given this interface `Commute`: + +``` +interface Commute { + let X:! Commute; + let Y:! Commute where .X == X.Y; - we can constrain them to be equal using - `PairInterface where .Left = .Right`. + fn GetX[self: Self]() -> X; + fn GetY[self: Self]() -> Y; + fn TakesXXY[self: Self](xxy: X.X.Y); +} +``` - Note that this is a case that can use an `==` same-type constraint instead - of an `=` rewrite constraint. +and a function `H` taking a value with some type implementing this interface, +then the following would be legal statements in `H`: -- **[Associated facet](#associated-facets) implements interface:** Given these - definitions (omitting `ElementType` for brevity): +``` +fn H[C: Commute](c: C) { + // ✅ Legal: argument has type `C.X.X.Y` + c.TakesXXY(c.GetX().GetX().GetY()); - ```carbon - interface IteratorInterface { ... } - interface ContainerInterface { - let IteratorType:! IteratorInterface; - // ... - } - interface RandomAccessIterator { - extend IteratorInterface; - // ... - } - ``` + // ✅ Legal: argument has type `C.X.Y.X` which is equal + // to `C.X.X.Y` following only one `where` clause. + c.TakesXXY(c.GetX().GetY().GetX()); - We can then define a function that only accepts types that implement - `ContainerInterface` where its `IteratorType` associated facet implements - `RandomAccessIterator`: + // ✅ Legal: cast is legal since it matches a `where` + // clause, and produces an argument that has type + // `C.X.Y.X`. + c.TakesXXY(c.GetY().GetX().GetX() as C.X.Y.X); +} +``` - ```carbon - fn F[ContainerType:! ContainerInterface - where .IteratorType impls RandomAccessIterator] - (c: ContainerType); - ``` +That last call would not be legal without the cast, though. -#### Parameterized type implements interface +**Comparison with other languages:** Other languages such as Swift and Rust +instead perform automatic type equality. In practice this means that their +compiler can reject some legal programs based on heuristics simply to avoid +running for an unbounded length of time. -There are times when a function will pass a -[symbolic facet parameter](#symbolic-facet-bindings) of the function as an -argument to a [parameterized type](#parameterized-types), and the function needs -the result to implement a specific interface. +The benefits of the manual approach include: -```carbon -// A parameterized type -class Vector(T:! type) { ... } +- fast compilation, since the compiler does not need to explore a potentially + large set of combinations of equality restrictions, supporting + [Carbon's goal of fast and scalable development](/docs/project/goals.md#fast-and-scalable-development); +- expressive and predictable semantics, since there are no limitations on how + complex a set of constraints can be supported; and +- simplicity. -// The parameterized type `Vector` implements interface -// `Printable` only for some arguments. -impl Vector(String) as Printable { ... } +The main downsides are: -// Constraint: `T` such that `Vector(T)` implements `Printable` -fn PrintThree - [T:! type where Vector(.Self) impls Printable] - (a: T, b: T, c: T) { - var v: Vector(T) = (a, b, c); - Print(v); -} -``` +- manual work for the source code author to prove to the compiler that types + are equal; and +- verbosity. -**Comparison with other languages:** This use case was part of the -[Rust rationale for adding support for `where` clauses](https://rust-lang.github.io/rfcs/0135-where.html#motivation). +We expect that rich error messages and IDE tooling will be able to suggest +changes to the source code when a single equality constraint is not sufficient +to show two type expressions are equal, but a more extensive automated search +can find a sequence that prove they are equal. -#### Another type implements parameterized interface +#### `observe` declarations -In this case, we need some other type to implement an interface parameterized by -a [symbolic facet parameter](#symbolic-facet-bindings). The syntax for this case -follows the previous case, except now the `.Self` parameter is on the interface -to the right of the `impls`. For example, we might need a type parameter `T` to -support explicit conversion from an `i32`: +An `observe` declaration lists a sequence of type expressions that are equal by +some same-type `where` constraints. These `observe` declarations may be included +in an `interface` definition or a function body, as in: -```carbon -interface As(T:! type) { - fn Convert[self: Self]() -> T; +``` +interface Commute { + let X:! Commute; + let Y:! Commute where .X == X.Y; + ... + observe X.X.Y == X.Y.X == Y.X.X; } -fn Double[T:! Mul where i32 impls As(.Self)](x: T) -> T { - return x * ((2 as i32) as T); +fn H[C: Commute](c: C) { + observe C.X.Y.Y == C.Y.X.Y == C.Y.Y.X; + ... } ``` -#### Must be legal type argument constraints - -Now consider the case that the symbolic facet parameter is going to be used as -an argument to a [parameterized type](#parameterized-types) in a function body, -but not in the signature. If the parameterized type was explicitly mentioned in -the signature, the [implied constraint](#implied-constraints) feature would -ensure all of its requirements were met. To say a parameterized type is allowed -to be passed a specific argument, just write that it `impls type`, which all -types do. This is a trivial case of a -[parameterized type implements interface](#parameterized-type-implements-interface) -`where` constraint. - -For example, a function that adds its parameters to a `HashSet` to deduplicate -them, needs them to be `Hashable` and so on. To say "`T` is a type where -`HashSet(T)` is legal," we can write: +Every type expression after the first must be equal to some earlier type +expression in the sequence by a single `where` equality constraint. In this +example, -```carbon -fn NumDistinct[T:! type where HashSet(.Self) impls type] - (a: T, b: T, c: T) -> i32 { - var set: HashSet(T); - set.Add(a); - set.Add(b); - set.Add(c); - return set.Size(); +``` +interface Commute { + let X:! Commute; + let Y:! Commute where .X == X.Y; + ... + // ✅ Legal: + observe X.X.Y.Y == X.Y.X.Y == Y.X.X.Y == X.Y.Y.X; } ``` -This has the same advantages over repeating the constraints on `HashSet` -arguments in the type of `T` as other -[implied constraints](#implied-constraints). +the expression `X.Y.Y.X` is one equality away from `X.Y.X.Y` and so it is +allowed. This is even though `X.Y.X.Y` isn't the type expression immediately +prior to `X.Y.Y.X`. -### Named constraint constants +After an `observe` declaration, all of the listed type expressions are +considered equal to each other using a single `where` equality. In this example, +the `observe` declaration in the `Transitive` interface definition provides the +link between associated types `A` and `C` that allows function `F` to type +check. -A facet type with a `where` constraint, such as `C where `, can be -named two different ways: +``` +interface P { fn InP[self: Self](); } +interface Q { fn InQ[self: Self](); } +interface R { fn InR[self: Self](); } -- Using `let template` as in: +interface Transitive { + let A:! P; + let B:! Q where .Self == A; + let C:! R where .Self == B; - ```carbon - let template NameOfConstraint:! auto = C where ; - ``` + fn GetA[self: Self]() -> A; + fn TakesC[self: Self](c: C); - or, since the type of a facet type is `type`: + // Without this `observe` declaration, the + // calls in `F` below would not be allowed. + observe A == B == C; +} - ```carbon - let template NameOfConstraint:! type = C where ; - ``` +fn TakesPQR[U:! P & Q & R](u: U); -- Using a [named constraint](#named-constraints) with the `constraint` keyword - introducer: +fn F[T:! Transitive](t: T) { + var a: T.A = t.GetA(); - ```carbon - constraint NameOfConstraint { - extend C where ; - } - ``` + // ✅ Allowed: `T.A` == `T.C` + t.TakesC(a); + a.(R.InR()); -Whichever approach is used, the result is `NameOfConstraint` is a compile-time -constant that is equivalent to `C where `. + // ✅ Allowed: `T.A` implements `P`, + // `T.A` == `T.B` that implements `Q`, and + // `T.A` == `T.C` that implements `R`. + TakesPQR(a); +} +``` -## Other constraints as facet types +Since adding an `observe` declaration only adds external implementations of +interfaces to generic types, they may be added without breaking existing code. -There are some constraints that Carbon naturally represents as named facet -types. These can either be used directly to constrain a facet binding, or in a -`where ... impls ...` [implements constraint](#implements-constraints) to -constrain an associated facet. +## Other constraints as type-of-types -The compiler determines which types implement these interfaces, developers are -not permitted to explicitly implement these interfaces for their own types. +There are some constraints that we will naturally represent as named +type-of-types. These can either be used directly to constrain a generic type +parameter, or in a `where ... impls ...` clause to constrain an associated type. -These facet types extend the requirements that facet types are allowed to -include beyond [interfaces implemented](#facet-types) and -[`where` clauses](#where-constraints). +The compiler determines which types implement these interfaces, developers can +not explicitly implement these interfaces for their own types. **Open question:** Are these names part of the prelude or in a standard library? ### Is a derived class -Given a type `T`, `Extends(T)` is a facet type whose values are facets that are -(transitively) [derived from](/docs/design/classes.md#inheritance) `T`. That is, -`U:! Extends(T)` means `U` has an `extend base: T;` declaration, or there is a -chain of `extend base` declarations connecting `T` to `U`. - -```carbon -base class BaseType { ... } +Given a type `T`, `Extends(T)` is a type-of-type whose values are types that are +derived from `T`. That is, `Extends(T)` is the set of all types `U` that are +subtypes of `T`. +``` fn F[T:! Extends(BaseType)](p: T*); -fn UpCast[U:! type] - (p: U*, V:! type where U impls Extends(.Self)) -> V*; -fn DownCast[X:! type](p: X*, Y:! Extends(X)) -> Y*; - -class DerivedType { - extend base: BaseType; -} -var d: DerivedType = {}; -// `T` is set to `DerivedType` -// `DerivedType impls Extends(BaseType)` -F(&d); - -// `U` is set to `DerivedType` -let p: BaseType* = UpCast(&d, BaseType); - -// `X` is set to `BaseType` -// `Y` is set to facet `DerivedType as Extends(BaseType)`. -Assert(DownCast(p, DerivedType) == &d); +fn UpCast[T:! type](p: T*, U:! type where T impls Extends(.Self)) -> U*; +fn DownCast[T:! type](p: T*, U:! Extends(T)) -> U*; ``` -**Open question:** Alternatively, we could define a new `extends` operator for -use in `where` clauses: +**Open question:** Alternatively, we could define a new `extends` operator: -```carbon +``` fn F[T:! type where .Self extends BaseType](p: T*); fn UpCast[T:! type](p: T*, U:! type where T extends .Self) -> U*; fn DownCast[T:! type](p: T*, U:! type where .Self extends T) -> U*; @@ -4203,23 +3361,25 @@ fn DownCast[T:! type](p: T*, U:! type where .Self extends T) -> U*; ### Type compatible with another type -Given a type `U`, define the facet type `CompatibleWith(U)` as follows: +Given a type `U`, define the type-of-type `CompatibleWith(U)` as follows: + +> `CompatibleWith(U)` is a type whose values are types `T` such that `T` and `U` +> are [compatible](terminology.md#compatible-types). That is values of types `T` +> and `U` can be cast back and forth without any change in representation (for +> example `T` is an [adapter](#adapting-types) for `U`). -> `CompatibleWith(U)` is a facet type whose values are facets `T` such that -> `T as type` and `U as type` are -> [compatible types](terminology.md#compatible-types). That is values of `T` and -> `U` as types can be cast back and forth without any change in representation -> (for example `T` is an [adapter](#adapting-types) for `U`). +To support this, we extend the requirements that type-of-types are allowed to +have to include a "data representation requirement" option. `CompatibleWith` determines an equivalence relationship between types. Specifically, given two types `T1` and `T2`, they are equivalent if -`T1 impls CompatibleWith(T2)`, which is true if and only if -`T2 impls CompatibleWith(T1)`. +`T1 impls CompatibleWith(T2)`. That is, if `T1` has the type +`CompatibleWith(T2)`. -**Note:** Just like interface parameters, we require the user to supply `U`, it -may not be deduced. Specifically, this code would be illegal: +**Note:** Just like interface parameters, we require the user to supply `U`, +they may not be deduced. Specifically, this code would be illegal: -```carbon +``` fn Illegal[U:! type, T:! CompatibleWith(U)](x: T*) ... ``` @@ -4227,7 +3387,7 @@ In general there would be multiple choices for `U` given a specific `T` here, and no good way of picking one. However, similar code is allowed if there is another way of determining `U`: -```carbon +``` fn Allowed[U:! type, T:! CompatibleWith(U)](x: U*, y: T*) ... ``` @@ -4236,37 +3396,37 @@ fn Allowed[U:! type, T:! CompatibleWith(U)](x: U*, y: T*) ... In some cases, we need to restrict to types that implement certain interfaces the same way as the type `U`. -> The values of facet type `CompatibleWith(U, C)` are facets satisfying -> `CompatibleWith(U)` that have the same implementation of `C` as `U`. +> The values of type `CompatibleWith(U, TT)` are types satisfying +> `CompatibleWith(U)` that have the same implementation of `TT` as `U`. For example, if we have a type `HashSet(T)`: -```carbon +``` class HashSet(T:! Hashable) { ... } ``` Then `HashSet(T)` may be cast to `HashSet(U)` if `T impls CompatibleWith(U, Hashable)`. The one-parameter interpretation of -`CompatibleWith(U)` is recovered by letting the default for the second parameter -(`C`) be `type`. +`CompatibleWith(U)` is recovered by letting the default for the second `TT` +parameter be `type`. #### Example: Multiple implementations of the same interface This allows us to represent functions that accept multiple implementations of the same interface for a type. -```carbon -choice CompareResult { Less, Equal, Greater } -interface Ordered { +``` +enum CompareResult { Less, Equal, Greater } +interface Comparable { fn Compare[self: Self](rhs: Self) -> CompareResult; } fn CombinedLess[T:! type](a: T, b: T, - U:! CompatibleWith(T) & Ordered, - V:! CompatibleWith(T) & Ordered) -> bool { + U:! CompatibleWith(T) & Comparable, + V:! CompatibleWith(T) & Comparable) -> bool { match ((a as U).Compare(b as U)) { - case .Less => { return True; } - case .Greater => { return False; } - case .Equal => { + case CompareResult.Less => { return True; } + case CompareResult.Greater => { return False; } + case CompareResult.Equal => { return (a as V).Compare(b as V) == CompareResult.Less; } } @@ -4275,54 +3435,52 @@ fn CombinedLess[T:! type](a: T, b: T, Used as: -```carbon +``` class Song { ... } -class SongByArtist { adapt Song; impl as Ordered { ... } } -class SongByTitle { adapt Song; impl as Ordered { ... } } -let s1: Song = ...; -let s2: Song = ...; +class SongByArtist { adapt Song; impl as Comparable { ... } } +class SongByTitle { adapt Song; impl as Comparable { ... } } +var s1: Song = ...; +var s2: Song = ...; assert(CombinedLess(s1, s2, SongByArtist, SongByTitle) == True); ``` -> **Open question:** We might generalize this to a list of implementations using -> variadics: -> -> ```carbon -> fn CombinedCompare[T:! type] -> (a: T, b: T, ... each CompareT:! CompatibleWith(T) & Ordered) -> -> CompareResult { -> ... block { -> let result: CompareResult = -> (a as each CompareT).Compare(b as each CompareT); -> if (result != CompareResult.Equal) { -> return result; -> } -> } -> return CompareResult.Equal; -> } -> -> assert(CombinedCompare(s1, s2, SongByArtist, SongByTitle) -> == CompareResult.Less); -> ``` -> -> However, [variadic support](#variadic-arguments) is still future work. +We might generalize this to a list of implementations: + +``` +fn CombinedCompare[T:! type] + (a: T, b: T, CompareList:! List(CompatibleWith(T) & Comparable)) + -> CompareResult { + for (let U:! auto in CompareList) { + var result: CompareResult = (a as U).Compare(b); + if (result != CompareResult.Equal) { + return result; + } + } + return CompareResult.Equal; +} + +assert(CombinedCompare(Song(...), Song(...), (SongByArtist, SongByTitle)) == + CompareResult.Less); +``` + +**Open question:** How are compile-time lists of types declared and iterated +through? They will also be needed for +[variadic argument support](#variadic-arguments). #### Example: Creating an impl out of other implementations -And then to package this functionality as an implementation of `Ordered`, we -combine `CompatibleWith` with [type adaptation](#adapting-types) and -[variadics](#variadic-arguments): +And then to package this functionality as an implementation of `Comparable`, we +combine `CompatibleWith` with [type adaptation](#adapting-types): -```carbon +``` class ThenCompare( T:! type, - ... each CompareT:! CompatibleWith(T) & Ordered) { + CompareList:! List(CompatibleWith(T) & Comparable)) { adapt T; - extend impl as Ordered { + extend impl as Comparable { fn Compare[self: Self](rhs: Self) -> CompareResult { - ... block { - let result: CompareResult = - (self as each CompareT).Compare(rhs as each CompareT); + for (let U:! auto in CompareList) { + var result: CompareResult = (self as U).Compare(rhs as U); if (result != CompareResult.Equal) { return result; } @@ -4332,23 +3490,23 @@ class ThenCompare( } } -let template SongByArtistThenTitle:! auto = - ThenCompare(Song, SongByArtist, SongByTitle); +let SongByArtistThenTitle: auto = + ThenCompare(Song, (SongByArtist, SongByTitle)); var s1: Song = ...; var s2: SongByArtistThenTitle = - ({ ... } as Song) as SongByArtistThenTitle; + Song(...) as SongByArtistThenTitle; assert((s1 as SongByArtistThenTitle).Compare(s2) == CompareResult.Less); ``` -### Sized types and facet types +### Sized types and type-of-types What is the size of a type? - It could be fully known and fixed at compile time -- this is true of primitive types (`i32`, `f64`, and so on), most [classes](/docs/design/classes.md), and most other concrete types. -- It could be known symbolically. This means that it will be known at codegen +- It could be known generically. This means that it will be known at codegen time, but not at type-checking time. - It could be dynamic. For example, it could be a [dynamic type](#runtime-type-fields), a slice, variable-sized type (such as @@ -4359,11 +3517,11 @@ What is the size of a type? essentially equivalent to having dynamic size. A type is called _sized_ if it is in the first two categories, and _unsized_ -otherwise. Note: something with size 0 is still considered "sized". The facet -type `Sized` is defined as follows: +otherwise. Note: something with size 0 is still considered "sized". The +type-of-type `Sized` is defined as follows: > `Sized` is a type whose values are types `T` that are "sized" -- that is the -> size of `T` is known, though possibly only symbolically +> size of `T` is known, though possibly only generically. Knowing a type is sized is a precondition to declaring variables of that type, taking values of that type as parameters, returning values of that type, and @@ -4371,9 +3529,12 @@ defining arrays of that type. Users will not typically need to express the `Sized` constraint explicitly, though, since it will usually be a dependency of some other constraint the type will need such as `Movable` or `Concrete`. +**Note:** The compiler will determine which types are "sized", this is not +something types will implement explicitly like ordinary interfaces. + Example: -```carbon +``` // In the Carbon standard library interface DefaultConstructible { // Types must be sized to be default constructible. @@ -4396,7 +3557,7 @@ fn F[T:! type](x: T*) { // T is unsized. var z: T; } -// T is sized, but its size is only known symbolically. +// T is sized, but its size is only known generically. fn G[T: DefaultConstructible](x: T*) { // ✅ Allowed: T is default constructible, which means sized. var y: T = T.Default(); @@ -4407,15 +3568,46 @@ var z: Name = Name.Default();; G(&z); ``` -**Open question:** Should the `Sized` facet type expose an associated constant +**Open question:** Even if the size is fixed, it won't be known at the time of +compiling the generic function if we are using the dynamic strategy. Should we +automatically +[box]() +local variables when using the dynamic strategy? Or should we only allow +`MaybeBox` values to be instantiated locally? Or should this just be a case +where the compiler won't necessarily use the dynamic strategy? + +**Open question:** Should the `Sized` type-of-type expose an associated constant with the size? So you could say `T.ByteSize` in the above example to get a -symbolic integer value with the size of `T`. Similarly you might say -`T.ByteStride` to get the number of bytes used for each element of an array of -`T`. +generic int value with the size of `T`. Similarly you might say `T.ByteStride` +to get the number of bytes used for each element of an array of `T`. + +### `TypeId` + +There are some capabilities every type can provide. For example, every type +should be able to return its name or identify whether it is equal to another +type. It is rare, however, for code to need to access these capabilities, so we +relegate these capabilities to an interface called `TypeId` that all types +automatically implement. This way generic code can indicate that it needs those +capabilities by including `TypeId` in the list of requirements. In the case +where no type capabilities are needed, for example the code is only manipulating +pointers to the type, you would write `T:! type` and get the efficiency of +`void*` but without giving up type safety. + +``` +fn SortByAddress[T:! type](v: Vector(T*)*) { ... } +``` + +In particular, the compiler should in general avoid monomorphizing to generate +multiple instantiations of the function in this case. + +**Open question:** Should `TypeId` be +[implemented externally](terminology.md#extending-an-impl) for types to avoid +name pollution (`.TypeName`, `.TypeHash`, etc.) unless the function specifically +requests those capabilities? ### Destructor constraints -There are four facet types related to +There are four type-of-types related to [the destructors of types](/docs/design/classes.md#destructors): - `Concrete` types may be local or member variables. @@ -4425,7 +3617,7 @@ There are four facet types related to using the `UnsafeDelete` method on the correct `Allocator`, but it may be unsafe. The concerning case is deleting a pointer to a derived class through a pointer to its base class without a virtual destructor. -- `TrivialDestructor` types have empty destructors. This facet type may be +- `TrivialDestructor` types have empty destructors. This type-of-type may be used with [specialization](#lookup-resolution-and-specialization) to unlock specific optimizations. @@ -4440,86 +3632,34 @@ The facet types `Concrete`, `Deletable`, and `TrivialDestructor` all extend checked-generic function that both instantiates and deletes values of a type `T` would require `T` implement `Concrete & Deletable`. -Types are forbidden from explicitly implementing these facet types directly. +Types are forbidden from explicitly implementing these type-of-types directly. Instead they use [`destructor` declarations in their class definition](/docs/design/classes.md#destructors) -and the compiler uses them to determine which of these facet types are +and the compiler uses them to determine which of these type-of-types are implemented. -## Compile-time `let` +## Generic `let` A `let` statement inside a function body may be used to get the change in type -behavior of calling a checked-generic function without having to introduce a -function call. - -```carbon -fn SymbolicLet(...) { - ... - let T:! C = U; - X; - Y; - Z; -} -``` - -This introduces a symbolic constant `T` with type `C` and value `U`. This -implicitly includes an [`observe T == U;` declaration](#observe-declarations), -when `T` and `U` are facets, which allows values to implicitly convert from the -concrete type `U` to the erased type `T`, as in: - -```carbon -let x: i32 = 7; -let T:! Add = i32; -// ✅ Allowed to convert `i32` values to `T`. -let y: T = x; -``` +behavior of calling a generic function without having to introduce a function +call. -> **TODO:** The implied `observe` declaration is from question-for-leads issue -> [#996](https://github.com/carbon-language/carbon-lang/issues/996) and should -> be approved in a proposal. - -This makes the `SymbolicLet` function roughly equivalent to: - -```carbon -fn SymbolicLet(...) { - ... - fn Closure(T:! C where .Self == U) { - X; - Y; - Z; - } - Closure(U); -} ``` - -The `where .Self == U` modifier captures the `observe` declaration introduced by -the `let` (at the cost of changing the type of `T`). - -A symbolic `let` can be used to switch to the API of `C` when `U` does not -extend `C`, as an alternative to -[using an adapter](#use-case-accessing-interface-names), or to simplify inlining -of a generic function while preserving semantics. - -To get a template binding instead of symbolic binding, add the `template` -keyword before the binding pattern, as in: - -```carbon -fn TemplateLet(...) { +fn F(...) { ... - let template T:! C = U; + let T:! C = U; X; Y; Z; } ``` -which introduces a template constant `T` with type `C` and value `U`. This is -roughly equivalent to: +gets rewritten to: -```carbon -fn TemplateLet(...) { +``` +fn F(...) { ... - fn Closure(template T:! C) { + fn Closure(T:! C where .Self == U) { X; Y; Z; @@ -4528,52 +3668,37 @@ fn TemplateLet(...) { } ``` -In this case, the `where .Self == U` modifier is superfluous. - -> **References:** -> -> - Proposal -> [#950: Generics details 6: remove facets #950](https://github.com/carbon-language/carbon-lang/pull/950) -> - Question-for-leads issue -> [#996: Generic `let` with `auto`?](https://github.com/carbon-language/carbon-lang/issues/996) +The `where .Self == U` modifier allows values to implicitly convert between type +`T`, the erased type, and type `U`, the concrete type. Note that implicit +conversion is +[only performed across a single `where` equality](#manual-type-equality). This +can be used to switch to the API of `C` when `U` does not extend `C`, as an +alternative to [using an adapter](#use-case-accessing-interface-names), or to +simplify inlining of a generic function while preserving semantics. ## Parameterized impl declarations -There are cases where an `impl` definition should apply to more than a single -type and interface combination. The solution is to parameterize the `impl` -definition, so it applies to a family of types, interfaces, or both. This -includes: +There are cases where an impl definition should apply to more than a single type +and interface combination. The solution is to parameterize the impl definition, +so it applies to a family of types, interfaces, or both. This includes: -- Defining an `impl` that applies to multiple arguments to a - [parameterized type](#parameterized-types). -- _Conditional conformance_ where a parameterized type implements some +- Declare an impl for a parameterized type, which may be external or declared + out-of-line. +- "Conditional conformance" where a parameterized type implements some interface if the parameter to the type satisfies some criteria, like implementing the same interface. -- _Blanket_ `impl` declarations where an interface is implemented for all - types that implement another interface, or some other criteria beyond being - a specific type. -- _Wildcard_ `impl` declarations where a family of interfaces are implemented +- "Blanket" impl declarations where an interface is implemented for all types + that implement another interface, or some other criteria beyond being a + specific type. +- "Wildcard" impl declarations where a family of interfaces are implemented for single type. -The syntax for an out-of-line parameterized `impl` declaration is: - - - - - -> `impl forall [`__`]` __ `as` -> _ [_ `where` _ ]_ `;` - - - -This may also be called a _generic `impl` declaration_. - ### Impl for a parameterized type -Interfaces may be implemented for a [parameterized type](#parameterized-types). -This can be done lexically in the class' scope: +Interfaces may be implemented for a parameterized type. This can be done +lexically in the class' scope: -```carbon +``` class Vector(T:! type) { impl as Iterable where .ElementType = T { ... @@ -4584,7 +3709,7 @@ class Vector(T:! type) { This is equivalent to naming the implementing type between `impl` and `as`, though this form is not allowed after `extend`: -```carbon +``` class Vector(T:! type) { impl Vector(T) as Iterable where .ElementType = T { ... @@ -4595,7 +3720,7 @@ class Vector(T:! type) { An out-of-line `impl` declaration must declare all parameters in a `forall` clause: -```carbon +``` impl forall [T:! type] Vector(T) as Iterable where .ElementType = T { ... @@ -4603,23 +3728,23 @@ impl forall [T:! type] Vector(T) as Iterable ``` The parameter for the type can be used as an argument to the interface being -implemented, with or without `extend`: +implemented: -```carbon -class HashMap(KeyT:! Hashable, ValueT:! type) { - extend impl as Has(KeyT) { ... } - impl as Contains(HashSet(KeyT)) { ... } +``` +class HashMap(Key:! Hashable, Value:! type) { + extend impl as Has(Key) { ... } + extend impl as Contains(HashSet(Key)) { ... } } ``` -or out-of-line the same `forall` parameter can be passed to both: +or externally out-of-line: -```carbon -class HashMap(KeyT:! Hashable, ValueT:! type) { ... } -impl forall [KeyT:! Hashable, ValueT:! type] - HashMap(KeyT, ValueT) as Has(KeyT) { ... } -impl forall [KeyT:! Hashable, ValueT:! type] - HashMap(KeyT, ValueT) as Contains(HashSet(KeyT)) { ... } +``` +class HashMap(Key:! Hashable, Value:! type) { ... } +impl forall [Key:! Hashable, Value:! type] + HashMap(Key, Value) as Has(Key) { ... } +impl forall [Key:! Hashable, Value:! type] + HashMap(Key, Value) as Contains(HashSet(Key)) { ... } ``` ### Conditional conformance @@ -4640,7 +3765,7 @@ interface when its element type satisfies the same interface: This may be done by specifying a more specific implementing type to the left of the `as` in the declaration: -```carbon +``` interface Printable { fn Print[self: Self](); } @@ -4660,9 +3785,9 @@ impl forall [T:! Printable] Vector(T) as Printable { ``` Note that no `forall` clause or type may be specified when declaring an `impl` -with the [`extend`](#extend-impl) keyword: +with the `extend` keyword: -```carbon +``` class Array(T:! type, template N:! i64) { // ❌ Illegal: nothing allowed before `as` after `extend impl`: extend impl forall [P:! Printable] Array(P, N) as Printable { ... } @@ -4675,7 +3800,7 @@ class Array(T:! type, template N:! i64) { Instead, the class can declare aliases to members of the interface. Those aliases will only be usable when the type implements the interface. -```carbon +``` class Array(T:! type, template N:! i64) { alias Print = Printable.Print; } @@ -4697,31 +3822,29 @@ var no_print: Array(Unprintable, 2) = ...; no_print.Print(); ``` -It is legal to declare or define a conditional impl lexically inside the class -scope without `extend`, as in: +It is still legal to declare or define an external impl lexically inside the +class scope, as in: -```carbon +``` class Array(T:! type, template N:! i64) { - // ✅ Allowed: non-extending impl defined in class scope may - // use `forall` and may specify a type. + // ✅ Allowed: external impl defined in class scope may use `forall` + // and may specify a type. impl forall [P:! Printable] Array(P, N) as Printable { ... } } ``` Inside the scope of this `impl` definition, both `P` and `T` refer to the same -type, but `P` has the facet type of `Printable` and so has a `Print` member. The -relationship between `T` and `P` is as if there was a -[`where P == T` clause](#same-type-constraints). +type, but `P` has the type-of-type of `Printable` and so has a `Print` member. +The relationship between `T` and `P` is as if there was a `where P == T` clause. -**Open question:** Need to resolve whether the `T` name can be reused, or if we -require that you need to use new names, like `P`, when creating new type -variables. +**TODO:** Need to resolve whether the `T` name can be reused, or if we require +that you need to use new names, like `P`, when creating new type variables. **Example:** Consider a type with two parameters, like `Pair(T, U)`. In this example, the interface `Foo(T)` is only implemented when the two types are equal. -```carbon +``` interface Foo(T:! type) { ... } class Pair(T:! type, U:! type) { ... } impl forall [T:! type] Pair(T, T) as Foo(T) { ... } @@ -4730,7 +3853,7 @@ impl forall [T:! type] Pair(T, T) as Foo(T) { ... } As before, you may also define the `impl` inline, but it may not be combined with the `extend` keyword: -```carbon +``` class Pair(T:! type, U:! type) { impl Pair(T, T) as Foo(T) { ... } } @@ -4739,7 +3862,7 @@ class Pair(T:! type, U:! type) { **Clarification:** The same interface may be implemented multiple times as long as there is no overlap in the conditions: -```carbon +``` class X(T:! type) { // ✅ Allowed: `X(T).F` consistently means `X(T).(Foo.F)` // even though that may have different definitions for @@ -4763,6 +3886,28 @@ can only mean one thing, regardless of `T`. but bans cases where there could be ambiguity from overlap. [Rust also supports conditional conformance](https://doc.rust-lang.org/rust-by-example/generics/where.html). +#### Conditional methods + +A method could be defined conditionally for a type by using a more specific type +in place of `Self` in the method declaration. For example, this is how to define +a vector type that only has a `Sort` method if its elements implement the +`Comparable` interface: + +``` +class Vector(T:! type) { + // `Vector(T)` has a `Sort()` method if `T` impls `Comparable`. + fn Sort[C:! Comparable, addr self: Vector(C)*](); +} +``` + +**Comparison with other languages:** In +[Rust](https://doc.rust-lang.org/book/ch10-02-traits.html#using-trait-bounds-to-conditionally-implement-methods) +this feature is part of conditional conformance. Swift supports conditional +methods using +[conditional extensions](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID553) +or +[contextual where clauses](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID628). + ### Blanket impl declarations A _blanket impl declaration_ is an `impl` declaration that could apply to more @@ -4772,13 +3917,13 @@ than one root type, so the `impl` declaration will use a type variable for the - Any type implementing `Ordered` should get an implementation of `PartiallyOrdered`. - ```carbon + ``` impl forall [T:! Ordered] T as PartiallyOrdered { ... } ``` - `T` implements `CommonType(T)` for all `T` - ```carbon + ``` impl forall [T:! type] T as CommonType(T) where .Result = T { } ``` @@ -4809,7 +3954,7 @@ of interfaces are implemented for a single `Self` type. For example, the `ImplicitAs(i32)`. The implementation would first convert `T` to `i32` and then add the `i32` to the `BigInt` value. -```carbon +``` class BigInt { impl forall [T:! ImplicitAs(i32)] as AddTo(T) { ... } } @@ -4826,7 +3971,7 @@ The different kinds of parameters to an `impl` declarations may be combined. For example, if `T` implements `As(U)`, then this implements `As(Optional(U))` for `Optional(T)`: -```carbon +``` impl forall [U:! type, T:! As(U)] Optional(T) as As(Optional(U)) { ... } ``` @@ -4835,22 +3980,22 @@ This has a wildcard parameter `U`, and a condition on parameter `T`. ### Lookup resolution and specialization -As much as possible, we want rules for where an `impl` is allowed to be defined -and for selecting which `impl` definition to use that achieve these three goals: +As much as possible, we want rules for where an impl is allowed to be defined +and for selecting which impl to use that achieve these three goals: - Implementations have coherence, as [defined in terminology](terminology.md#coherence). This is [a goal for Carbon](goals.md#coherence). More detail can be found in [this appendix with the rationale and alternatives considered](appendix-coherence.md). - Libraries will work together as long as they pass their separate checks. -- A checked-generic function can assume that some `impl` definition will be - successfully selected if it can see an `impl` declaration that applies, even - though another more specific `impl` definition may be selected. +- A generic function can assume that some impl will be successfully selected + if it can see an impl that applies, even though another more specific impl + may be selected. -For this to work, we need a rule that picks a single `impl` definition in the -case where there are multiple `impl` definitions that match a particular type -and interface combination. This is called _specialization_ when the rule is that -most specific implementation is chosen, for some definition of "specific." +For this to work, we need a rule that picks a single `impl` in the case where +there are multiple `impl` definitions that match a particular type and interface +combination. This is called _specialization_ when the rule is that most specific +implementation is chosen, for some definition of specific. #### Type structure of an impl declaration @@ -4858,21 +4003,21 @@ Given an impl declaration, find the type structure by deleting deduced parameters and replacing type parameters by a `?`. The type structure of this declaration: -```carbon +``` impl forall [T:! ..., U:! ...] Foo(T, i32) as Bar(String, U) { ... } ``` is: -```carbon +``` impl Foo(?, i32) as Bar(String, ?) ``` To get a uniform representation across different `impl` definitions, before type parameters are replaced the declarations are normalized as follows: -- For `impl` declarations that are lexically inline in a class definition, the - type is added between the `impl` and `as` keywords if the type is left out. +- For impl declarations lexically inline in a class definition, the type is + added between the `impl` and `as` keywords if the type is left out. - Pointer types `T*` are replaced with `Ptr(T)`. - The `extend` keyword is removed, if present. - The `forall` clause introducing type parameters is removed, if present. @@ -4888,12 +4033,11 @@ library depends on. #### Orphan rule -To achieve [coherence](terminology.md#coherence), we need to ensure that any -given impl can only be defined in a library that must be imported for it to -apply. Specifically, given a specific type and specific interface, `impl` -declarations that can match can only be in libraries that must have been -imported to name that type or interface. This is achieved with the _orphan -rule_. +To achieve coherence, we need to ensure that any given impl can only be defined +in a library that must be imported for it to apply. Specifically, given a +specific type and specific interface, `impl` declarations that can match can +only be in libraries that must have been imported to name that type or +interface. This is achieved with the _orphan rule_. **Orphan rule:** Some name from the type structure of an `impl` declaration must be defined in the same library as the `impl`, that is some name must be _local_. @@ -4914,9 +4058,9 @@ few goals: is actually used, avoiding [One Definition Rule (ODR)](https://en.wikipedia.org/wiki/One_Definition_Rule) problems. -- Every attempt to use an `impl` will see the exact same `impl` definition, - making the interpretation and semantics of code consistent no matter its - context, in accordance with the +- Every attempt to use an `impl` will see the exact same `impl`, making the + interpretation and semantics of code consistent no matter its context, in + accordance with the [low context-sensitivity principle](/docs/project/principles/low_context_sensitivity.md). - Allowing the `impl` to be defined with either the interface or the type partially addresses the @@ -4950,7 +4094,7 @@ implementation of that type for that interface. Given two different type structures of impl declarations matching a query, for example: -```carbon +``` impl Foo(?, i32) as Bar(String, ?) impl Foo(?, ?) as Bar(String, f32) ``` @@ -4968,80 +4112,66 @@ difference. Since at most one library can contain `impl` definitions with a given type structure, all `impl` definitions with a given type structure must be in the -same library. Furthermore by the [`impl` declaration access rules](#access), -they will be defined in the API file for the library if they could match any -query from outside the library. If there is more than one `impl` with that type +same library. Furthermore by the [impl declaration access rules](#access), they +will be defined in the API file for the library if they could match any query +from outside the library. If there is more than one impl with that type structure, they must be [defined](#implementing-interfaces) or [declared](#declaring-implementations) together in a prioritization block. Once -a type structure is selected for a query, the first `impl` declaration in the -prioritization block that matches is selected. - -> **Open question:** How are prioritization blocks written? A block starts with -> a keyword like `match_first` or `impl_priority` and then a sequence of impl -> declarations inside matching curly braces `{` ... `}`. -> -> ```carbon -> match_first { -> // If T is Foo prioritized ahead of T is Bar -> impl forall [T:! Foo] T as Bar { ... } -> impl forall [T:! Baz] T as Bar { ... } -> } -> ``` - -To increase expressivity, Carbon allows prioritization blocks to contain a mix -of type structures, which is resolved using this rule: - -> The compiler first picks the `impl` declaration with the type structure most -> favored for the query, and then picks the highest priority (earliest) matching -> `impl` declaration in the same prioritization block. - -> **Alternatives considered:** We considered two other options: -> -> - "Intersection rule:" Prioritization blocks implicitly define all non-empty -> intersections of contained `impl` declarations, which are then selected by -> their type structure. -> - "Same type structure rule:" All the `impl` declarations in a -> prioritization block are required to have the same type structure, at a -> cost in expressivity. This option was not chosen since it wouldn't support -> the different type structures created by the -> [`like` operator](#like-operator-for-implicit-conversions). -> -> To see the difference from the first option, consider two libraries with type -> structures as follows: -> -> - Library B has `impl (A, ?, ?, D) as I` and `impl (?, B, ?, D) as I` in the -> same prioritization block. -> - Library C has `impl (A, ?, C, ?) as I`. -> -> For the query `(A, B, C, D) as I`, using the intersection rule, library B is -> considered to have the intersection impl with type structure -> `impl (A, B, ?, D) as I` which is the most specific. If we instead just -> considered the rules mentioned explicitly, then `impl (A, ?, C, ?) as I` from -> library C is the most specific. The advantage of the implicit intersection -> rule is that if library B is changed to add an impl with type structure -> `impl (A, B, ?, D) as I`, it won't shift which library is serving that query. -> Ultimately we decided that it was too surprising to prioritize based on the -> implicit intersection of `impl` declarations, rather than something explicitly -> written in the code. -> -> We chose between these alternatives in -> [the open discussion on 2023-07-18](https://docs.google.com/document/d/1gnJBTfY81fZYvI_QXjwKk1uQHYBNHGqRLI2BS_cYYNQ/edit?resourcekey=0-ql1Q1WvTcDvhycf8LbA9DQ#heading=h.7jxges9ojgy3). -> **TODO:** This decision needs to be approved in a proposal. +a type structure is selected for a query, the first impl in the prioritization +block that matches is selected. + +**Open question:** How are prioritization blocks written? A block starts with a +keyword like `match_first` or `impl_priority` and then a sequence of impl +declarations inside matching curly braces `{` ... `}`. + +``` +match_first { + // If T is Foo prioritized ahead of T is Bar + impl forall [T:! Foo] T as Bar { ... } + impl forall [T:! Baz] T as Bar { ... } +} +``` + +**Open question:** How do we pick between two different prioritization blocks +when they contain a mixture of type structures? There are three options: + +- Prioritization blocks implicitly define all non-empty intersections of + contained `impl` declarations, which are then selected by their type + structure. +- The compiler first picks the impl with the type pattern most favored for the + query, and then picks the definition of the highest priority matching impl + in the same prioritization block. +- All the `impl` declarations in a prioritization block are required to have + the same type structure, at a cost in expressivity. + +To see the difference between the first two options, consider two libraries with +type structures as follows: + +- Library B has `impl (A, ?, ?, D) as I` and `impl (?, B, ?, D) as I` in the + same prioritization block. +- Library C has `impl (A, ?, C, ?) as I`. + +For the query `(A, B, C, D) as I`, using the intersection rule, library B is +considered to have the intersection impl with type structure +`impl (A, B, ?, D) as I` which is the most specific. If we instead just +considered the rules mentioned explicitly, then `impl (A, ?, C, ?) as I` from +library C is the most specific. The advantage of the implicit intersection rule +is that if library B is changed to add an impl with type structure +`impl (A, B, ?, D) as I`, it won't shift which library is serving that query. #### Acyclic rule A cycle is when a query, such as "does type `T` implement interface `I`?", -considers an `impl` declaration that might match, and whether that `impl` -declaration matches is ultimately dependent on whether that query is true. These -are cycles in the graph of (type, interface) pairs where there is an edge from -pair A to pair B if whether type A implements interface A determines whether -type B implements interface B. +considers an impl that might match, and whether that impl matches is ultimately +dependent on whether that query is true. These are cycles in the graph of (type, +interface) pairs where there is an edge from pair A to pair B if whether type A +implements interface A determines whether type B implements interface B. The test for whether something forms a cycle needs to be precise enough, and not erase too much information when considering this graph, that these `impl` declarations are not considered to form cycles with themselves: -```carbon +``` impl forall [T:! Printable] Optional(T) as Printable; impl forall [T:! type, U:! ComparableTo(T)] U as ComparableTo(Optional(T)); ``` @@ -5049,7 +4179,7 @@ impl forall [T:! type, U:! ComparableTo(T)] U as ComparableTo(Optional(T)); **Example:** If `T` implements `ComparableWith(U)`, then `U` should implement `ComparableWith(T)`. -```carbon +``` impl forall [U:! type, T:! ComparableWith(U)] U as ComparableWith(T); ``` @@ -5061,7 +4191,7 @@ types implement the same interface. selecting `impl` declarations that are inconsistent with each other. Consider an interface with two blanket `impl` declarations: -```carbon +``` class Y {} class N {} interface True {} @@ -5080,18 +4210,18 @@ declarations are selected. - An implementation of `Z(i16)` for `i8` could come from the first blanket impl with `T == i8` and `U == i16` if `i16 impls Z(i8)` and - `(i16 as Z(i8)).Cond == Y`. This condition is satisfied if `i16` implements + `i16.(Z(i8).Cond) == Y`. This condition is satisfied if `i16` implements `Z(i8)` using the second blanket impl. In this case, - `(i8 as Z(i16)).Cond == N`. + `i8.(Z(i16).Cond) == N`. - Equally well `Z(i8)` could be implemented for `i16` using the first blanket impl and `Z(i16)` for `i8` using the second. In this case, - `(i8 as Z(i16)).Cond == Y`. + `i8.(Z(i16).Cond) == Y`. There is no reason to to prefer one of these outcomes over the other. **Example:** Further, cycles can create contradictions in the type system: -```carbon +``` class A {} class B {} class C {} @@ -5106,20 +4236,18 @@ match_first { } ``` -What is `(i8 as D(i16)).Cond`? The answer is determined by which blanket impl is +What is `i8.(D(i16).Cond)`? The answer is determined by which blanket impl is selected to implement `D(i16)` for `i8`: -- If the third blanket impl is selected, then `(i8 as D(i16)).Cond == A`. This - implies that `(i16 as D(i8)).Cond == B` using the second blanket impl. If - that is true, though, then our first impl choice was incorrect, since the - first blanket impl applies and is higher priority. So - `(i8 as D(i16)).Cond == C`. But that means that `i16 as D(i8)` can't use the - second blanket impl. -- For the second blanket impl to be selected, so `(i8 as D(i16)).Cond == B`, - `(i16 as D(i8)).Cond` would have to be `A`. This happens when `i16` - implements `D(i8)` using the third blanket impl. However, - `(i8 as D(i16)).Cond == B` means that there is a higher priority - implementation of `D(i8).Cond` for `i16`. +- If the third blanket impl is selected, then `i8.(D(i16).Cond) == A`. This + implies that `i16.(D(i8).Cond) == B` using the second blanket impl. If that + is true, though, then our first impl choice was incorrect, since the first + blanket impl applies and is higher priority. So `i8.(D(i16).Cond) == C`. But + that means that `i16 as D(i8)` can't use the second blanket impl. +- For the second blanket impl to be selected, so `i8.(D(i16).Cond) == B`, + `i16.(D(i8).Cond)` would have to be `A`. This happens when `i16` implements + `D(i8)` using the third blanket impl. However, `i8.(D(i16).Cond) == B` means + that there is a higher priority implementation of `D(i8).Cond` for `i16`. In either case, we arrive at a contradiction. @@ -5153,142 +4281,36 @@ forever. `Optional(A) impls B`, if `Optional(Optional(A)) impls B`, and so on. This could be the result of a single impl: -```carbon +``` impl forall [A:! type where Optional(.Self) impls B] A as B { ... } ``` This problem can also result from a chain of `impl` declarations, as in `A impls B` if `A* impls C`, if `Optional(A) impls B`, and so on. -Determining whether a particular set of `impl` declarations terminates is -[equivalent to the halting problem](https://sdleffler.github.io/RustTypeSystemTuringComplete/) -(content warning: contains many instances of an obscene word as part of a -programming language name), and so is undecidable in general. Carbon adopts an -approximation that guarantees termination, but may mistakenly report an error -when the query would terminate if left to run long enough. The hope is that this -criteria is accurate on code that occurs in practice. - -Rule: the types in the `impl` query must never get strictly more complicated -when considering the same `impl` declaration again. The way we measure the -complexity of a set of types is by counting how many of each base type appears. -A base type is the name of a type without its parameters. For example, the base -types in this query `Pair(Optional(i32), bool) impls AddWith(Optional(i32))` -are: - -- `Pair` -- `Optional` twice -- `i32` twice -- `bool` -- `AddWith` - -A query is strictly more complicated if at least one count increases, and no -count decreases. So `Optional(Optional(i32))` is strictly more complicated than -`Optional(i32)` but not strictly more complicated than `Optional(bool)`. - -This rule, when combined with [the acyclic rule](#acyclic-rule) that a query -can't repeat exactly, -[guarantees termination](/proposals/p2687.md#proof-of-termination). - -Consider the example from before, - -```carbon -impl forall [A:! type where Optional(.Self) impls B] A as B; -``` - -This `impl` declaration matches the query `i32 impls B` as long as -`Optional(i32) impls B`. That is a strictly more complicated query, though, -since it contains all the base types of the starting query (`i32` and `B`), plus -one more (`Optional`). As a result, an error can be given after one step, rather -than after hitting a large recursion limit. And that error can state explicitly -what went wrong: we went from a query with no `Optional` to one with one, -without anything else decreasing. - -Note this only triggers a failure when the same `impl` declaration is considered -with the strictly more complicated query. For example, if the declaration is not -considered since there is a more specialized `impl` declaration that is -preferred by the [type-structure overlap rule](#overlap-rule), as in: - -``` -impl forall [A:! type where Optional(.Self) impls B] A as B; -impl Optional(bool) as B; -// OK, because we never consider the first `impl` -// declaration when looking for `Optional(bool) impls I`. -let U:! B = bool; -// Error: cycle with `i32 impls B` depending on -// `Optional(i32) impls B`, using the same `impl` -// declaration, as before. -let V:! B = i32; -``` - -> **Comparison with other languages:** Rust solves this problem by imposing a -> recursion limit, much like C++ compilers use to terminate template recursion. -> This goes against -> [Carbon's goal of predictability in generics](goals.md#predictability), -> because of the concern that increasing the number of steps needed to resolve -> an `impl` query could cause far away code to hit the recursion limit. -> -> Carbon's approach is robust in the face of refactoring: -> -> - It does not depend on the specifics of how an `impl` declaration is -> parameterized, only on the query. -> - It does not depend on the length of the chain of queries. -> - It does not depend on a measure of type-expression complexity, like depth. -> -> Carbon's approach also results in identifying the minimal steps in the loop, -> which makes error messages as short and understandable as possible. - -> **Alternatives considered:** -> -> - [Recursion limit](/proposals/p2687.md#problem) -> - [Measure complexity using type tree depth](/proposals/p2687.md#measure-complexity-using-type-tree-depth) -> - [Consider each type parameter in an `impl` declaration separately](/proposals/p2687.md#consider-each-type-parameter-in-an-impl-declaration-separately) -> - [Consider types in the interface being implemented as distinct](/proposals/p2687.md#consider-types-in-the-interface-being-implemented-as-distinct) -> - [Require some count to decrease](/proposals/p2687.md#require-some-count-to-decrease) -> - [Require non-type values to stay the same](/proposals/p2687.md#require-non-type-values-to-stay-the-same) - -> **References:** This algorithm is from proposal -> [#2687: Termination algorithm for impl selection](https://github.com/carbon-language/carbon-lang/pull/2687), -> replacing the recursion limit originally proposed in -> [#920: Generic parameterized impls (details 5)](https://github.com/carbon-language/carbon-lang/pull/920) -> before we came up with this algorithm. - -##### Non-facet arguments - -For non-facet arguments we have to expand beyond base types to consider other -kinds of keys. These other keys are in a separate namespace from base types. - -- Values with an integral type use the name of the type as the key and the - absolute value as a count. This means integer arguments are considered more - complicated if they increase in absolute value. For example, if the values - `2` and `-3` are used as arguments to parameters with type `i32`, then the - `i32` key will have count `5`. -- Every option of a choice type is its own key, counting how many times a - value using that option occurs. Any parameters to the option are recorded as - separate keys. For example, the `Optional(i32)` value of `.Some(7)` is - recorded as keys `.Some` (with a count of `1`) and `i32` (with a count of - `7`). -- Yet another namespace of keys is used to track counts of variadic arguments, - under the base type. This is to defend against having a variadic type `V` - that takes any number of `i32` arguments, with an infinite set of distinct - instantiations: `V(0)`, `V(0, 0)`, `V(0, 0, 0)`, ... - - A `tuple` key in this namespace is used to track the total number of - components of tuple values. The values of those elements will be tracked - using their own keys. - -Non-facet argument values not covered by these cases are deleted from the query -entirely for purposes of the termination algorithm. This requires that two -queries that only differ by non-facet arguments are considered identical and -therefore are rejected by the acyclic rule. Otherwise, we could construct an -infinite family of non-facet argument values that could be used to avoid -termination. +Rust solves this problem by imposing a recursion limit, much like C++ compilers +use to terminate template recursion. This goes against +[Carbon's goal of predictability in generics](goals.md#predictability), but at +this time there are no known alternatives. Unfortunately, the approach Carbon +uses to avoid undecidability for type equality, +[providing an explicit proof in the source](#manual-type-equality), can't be +used here. The code triggering the query asking whether some type implements an +interface will typically be checked-generic code with no specific knowledge +about the types involved, and won't be in a position to provide a manual proof +that the implementation should exist. + +**Open question:** Is there some restriction on `impl` declarations that would +allow our desired use cases, but allow the compiler to detect non-terminating +cases? Perhaps there is some sort of complexity measure Carbon can require +doesn't increase when recursing? ### `final` impl declarations There are cases where knowing that a parameterized impl won't be specialized is particularly valuable. This could let the compiler know the return type of a -call to a generic function, such as using an operator: +generic function call, such as using an operator: -```carbon +``` // Interface defining the behavior of the prefix-* operator interface Deref { let Result:! type; @@ -5320,7 +4342,7 @@ anything about the return type of `Deref.Op` calls. This means `F` would in practice have to add a constraint, which is both verbose and exposes what should be implementation details: -```carbon +``` fn F[T:! type where Optional(T).(Deref.Result) == .Self and Ptr(T).(Deref.Result) == .Self](x: T) { // uses Ptr(T) and Optional(T) in implementation @@ -5330,7 +4352,7 @@ fn F[T:! type where Optional(T).(Deref.Result) == .Self To mark an impl as not able to be specialized, prefix it with the keyword `final`: -```carbon +``` class Ptr(T:! type) { ... // Note: added `final` @@ -5378,7 +4400,7 @@ If the Carbon compiler sees a matching `final` impl, it can assume it won't be specialized so it can use the assignments of the associated constants in that impl definition. -```carbon +``` fn F[T:! type](x: T) { var p: Ptr(T) = ...; // *p has type `T` @@ -5387,21 +4409,21 @@ fn F[T:! type](x: T) { } ``` -> **Alternatives considered:** -> -> - [Allow interfaces with member functions to compare equal](/proposals/p2868.md#allow-interfaces-with-member-functions-to-compare-equal) -> - Mark associated constants as `final` instead of an `impl` declaration, in -> proposals -> [#983](/proposals/p0983.md#final-associated-constants-instead-of-final-impls) -> and -> [#2868](/proposals/p2868.md#mark-associated-constants-as-final-instead-of-an-impl-declaration) -> - [Prioritize a `final impl` over a more specific `impl` on the overlap](/proposals/p2868.md#prioritize-a-final-impl-over-a-more-specific-impl-on-the-overlap) +**Alternatives considered:** + +- [Allow interfaces with member functions to compare equal](/proposals/p2868.md#allow-interfaces-with-member-functions-to-compare-equal) +- Mark associated constants as `final` instead of an `impl` declaration, in + proposals + [#983](/proposals/p0983.md#final-associated-constants-instead-of-final-impls) + and + [#2868](/proposals/p2868.md#mark-associated-constants-as-final-instead-of-an-impl-declaration) +- [Prioritize a `final impl` over a more specific `impl` on the overlap](/proposals/p2868.md#prioritize-a-final-impl-over-a-more-specific-impl-on-the-overlap) #### Libraries that can contain a `final` impl -To prevent the possibility of two unrelated libraries defining conflicting -`impl` declarations, Carbon restricts which libraries may declare an impl as -`final` to only: +To prevent the possibility of two unrelated libraries defining conflicting impl +declarations, Carbon restricts which libraries may declare an impl as `final` to +only: - the library declaring the impl's interface and - the library declaring the root of the `Self` type. @@ -5440,9 +4462,9 @@ process, so Carbon can benefit from the work they have done. However, getting specialization to work for Rust is complicated by the need to maintain compatibility with existing Rust code. This motivates a number of Rust rules where Carbon can be simpler. As a result there are both similarities and -differences between the Carbon design and Rust plans: +differences between the Carbon and Rust plans: -- A Rust `impl` defaults to not being able to be specialized, with a `default` +- A Rust impl defaults to not being able to be specialized, with a `default` keyword used to opt-in to allowing specialization, reflecting the existing code base developed without specialization. Carbon `impl` declarations default to allowing specialization, with restrictions on which may be @@ -5466,9 +4488,9 @@ differences between the Carbon design and Rust plans: [Little Orphan Impls: The ordered rule](http://smallcultfollowing.com/babysteps/blog/2015/01/14/little-orphan-impls/#the-ordered-rule), but the specifics are different. - Carbon is not planning to support any inheritance of implementation between - `impl` definitions. This is more important to Rust since Rust does not - support class inheritance for implementation reuse. Rust has considered - multiple approaches here, see + impl definitions. This is more important to Rust since Rust does not support + class inheritance for implementation reuse. Rust has considered multiple + approaches here, see [Aaron Turon: "Specialize to Reuse"](http://aturon.github.io/tech/2015/09/18/reuse/) and [Supporting blanket impls in specialization](http://smallcultfollowing.com/babysteps/blog/2016/10/24/supporting-blanket-impls-in-specialization/). @@ -5514,7 +4536,10 @@ interface in its parameter list. There is a the use cases when this would come up. An expression forming a constraint, such as `C & D`, is incomplete if any of the -interfaces or constraints used in the expression are incomplete. +interfaces or constraints used in the expression are incomplete. A constraint +expression using a [`where` clause](#where-constraints), like `C where ...`, is +invalid if `C` is incomplete, since there is no way to look up member names of +`C` that appear after `where`. An interface or named constraint may be forward declared subject to these rules: @@ -5522,17 +4547,17 @@ An interface or named constraint may be forward declared subject to these rules: - Only the first declaration may have an access-control keyword. - An incomplete interface or named constraint may be used as constraints in declarations of types, functions, interfaces, or named constraints. This - includes an `require` or `extend` declaration inside an interface or named + includes an `impl as` or `extend` declaration inside an interface or named constraint, but excludes specifying the values for associated constants because that would involve name lookup into the incomplete constraint. - An attempt to define the body of a generic function using an incomplete - interface or named constraint in its signature is illegal. + interface or named constraint is illegal. - An attempt to call a generic function using an incomplete interface or named constraint in its signature is illegal. - Any name lookup into an incomplete interface or named constraint is an error. For example, it is illegal to attempt to access a member of an interface using `MyInterface.MemberName` or constrain a member using a - [`where` clause](#where-constraints). + `where` clause. If `C` is the name of an incomplete interface or named constraint, then it can be used in the following contexts: @@ -5541,8 +4566,8 @@ be used in the following contexts: - ✅ `C & D` - There may be conflicts between `C` and `D` making this invalid that will only be discovered once they are both complete. -- ✅ `interface `...` { require` ... `impls C; }` or - `constraint `...` { require` ... `impls C; }` +- ✅ `interface `...` { impl` ... `as C; }` or `constraint `...` { impl` ... + `as C; }` - Nothing implied by implementing `C` will be visible until `C` is complete. - ✅ `T:! C` ... `T impls C` @@ -5565,24 +4590,24 @@ An incomplete `C` cannot be used in the following contexts: - Need to see the definition of `C` to see if it implies `A`. - ❌ `impl` ... `as C {` ... `}` -> **Future work:** It is currently undecided whether an interface needs to be -> complete to be extended, as in: -> -> ```carbon -> interface I { extend C; } -> ``` -> -> There are three different approaches being considered: -> -> - If we detect name collisions between the members of the interface `I` and -> `C` when the interface `I` is defined, then we need `C` to be complete. -> - If we instead only generate errors on ambiguous use of members with the -> same name, as we do with `A & B`, then we don't need to require `C` to be -> complete. -> - Another option, being discussed in -> [#2355](https://github.com/carbon-language/carbon-lang/issues/2355), is -> that names in interface `I` shadow the names in any interface being -> extended, then `C` would not be required to be complete. +**Future work:** It is currently undecided whether an interface needs to be +complete to be extended, as in: + +``` +interface I { extend C; } +``` + +There are three different approaches being considered: + +- If we detect name collisions between the members of the interface `I` and + `C` when the interface `I` is defined, then we need `C` to be complete. +- If we instead only generate errors on ambiguous use of members with the same + name, as we do with `A & B`, then we don't need to require `C` to be + complete. +- Another option, being discussed in + [#2355](https://github.com/carbon-language/carbon-lang/issues/2355), is that + names in interface `I` shadow the names in any interface being extended, + then `C` would not be required to be complete. ### Declaring implementations @@ -5592,43 +4617,46 @@ The declaration of an interface implementation consists of: - the keyword introducer `impl`, - an optional `forall` followed by a deduced parameter list in square brackets `[`...`]`, -- a type, including an optional [argument list](#parameterized-types), +- a type, including an optional parameter pattern, - the keyword `as`, and - a [facet type](#facet-types), including an optional - [argument list](#parameterized-interfaces) and + [parameter pattern](#parameterized-interfaces) and [`where` clause](#where-constraints) assigning [associated constants](#associated-constants) including [associated facets](#associated-facets). -**Note:** The `extend` keyword, when present, is not part of the `impl` -declaration. It precedes the `impl` declaration in class scope. +**Note:** The `extend` keyword, when present, is not part of the declaration. It +precedes the `impl` declaration in class scope. -An implementation of an interface for a type may be forward declared, subject to +An implementation of an interface for a type may be forward declared subject to these rules: - The definition must be in the same library as the declaration. They must either be in the same file, or the declaration can be in the API file and the definition in an impl file. **Future work:** Carbon may require - [parameterized `impl` definitions](#parameterized-impl-declarations) to be - in the API file, to support separate compilation. + [parameterized impl definitions](#parameterized-impl-declarations) to be in + the API file, to support separate compilation. - If there is both a forward declaration and a definition, only the first declaration must specify the assignment of associated constants with a `where` clause. Later declarations may omit the `where` clause by writing `where _` instead. -- You can't forward declare an implementation of an incomplete interface. This - allows the assignment of associated constants in the `impl` declaration to - be verified with the declaration. An `impl` forward declaration may be for - any declared type, whether it is incomplete or defined. -- Every [extending implementation](#extend-impl) must be declared (or defined) - inside the scope of the class definition. It may also be declared before the - class definition or defined afterwards. Note that the class itself is - incomplete in the scope of the class definition, but member function bodies - defined inline are processed +- You may forward declare an implementation of a defined interface but not an + incomplete interface. This allows the assignment of associated constants in + the `impl` declaration to be verified. An impl forward declaration may be + for any declared type, whether it is incomplete or defined. Note that this + does not apply to `impl as` declarations in an interface or named constraint + definition, as those are considered interface requirements not forward + declarations. +- Every extending implementation must be declared (or defined) inside the + scope of the class definition. It may also be declared before the class + definition or defined afterwards. Note that the class itself is incomplete + in the scope of the class definition, but member function bodies defined + inline are processed [as if they appeared immediately after the end of the outermost enclosing class](/docs/project/principles/information_accumulation.md#exceptions). -- For [coherence](terminology.md#coherence), we require that any `impl` - declaration that matches an impl lookup query in the same file, must be - declared before the query. This can be done with a definition or a forward - declaration. This matches the +- For [coherence](goals.md#coherence), we require that any `impl` declaration + that matches an impl lookup query in the same file, must be declared before + the query. This can be done with a definition or a forward declaration. This + matches the [information accumulation principle](/docs/project/principles/information_accumulation.md). ### Matching and agreeing @@ -5656,10 +4684,9 @@ expressions match along with - If the type part is omitted, it is rewritten to `Self` in the context of the declaration. - `Self` is rewritten to its meaning in the scope it is used. In a class - scope, this should match the type name and - [optional parameter expression](#parameterized-types) after `class`. So in - `class MyClass { ... }`, `Self` is rewritten to `MyClass`. In - `class Vector(T:! Movable) { ... }`, `Self` is rewritten to + scope, this should match the type name and optional parameter expression + after `class`. So in `class MyClass { ... }`, `Self` is rewritten to + `MyClass`. In `class Vector(T:! Movable) { ... }`, `Self` is rewritten to `forall [T:! Movable] Vector(T)`. - Types match if they have the same name after name and alias resolution and the same parameters, or are the same type parameter. @@ -5678,7 +4705,7 @@ For implementations to agree: ### Declaration examples -```carbon +``` // Forward declaration of interfaces interface Interface1; interface Interface2; @@ -5776,7 +4803,7 @@ constrained to implement `Node`. Furthermore, the `NodeType` of an `EdgeType` is the original type, and the other way around. This is accomplished by naming and then forward declaring the constraints that can't be stated directly: -```carbon +``` // Forward declare interfaces used in // parameter lists of constraints. interface Edge; @@ -5814,7 +4841,7 @@ To work around [the restriction about not being able to name an interface in its parameter list](#declaring-interfaces-and-named-constraints), instead include that requirement in the body of the interface. -```carbon +``` // Want to require that `T` satisfies `CommonType(Self)`, // but that can't be done in the parameter list. interface CommonType(T:! type) { @@ -5829,7 +4856,7 @@ constraints on members of `CommonType` are allowed, and that this `require T impls` declaration [must involve `Self`](#interface-requiring-other-interfaces-revisited). -```carbon +``` interface CommonType(T:! type) { let Result:! type; // ❌ Illegal: `CommonType` is incomplete @@ -5841,7 +4868,7 @@ Instead, a forward-declared named constraint can be used in place of the constraint that can only be defined later. This is [the same strategy used to work around cyclic references](#example-of-declaring-interfaces-with-cyclic-references). -```carbon +``` private constraint CommonTypeResult(T:! type, R:! type); interface CommonType(T:! type) { @@ -5869,7 +4896,7 @@ and prefixed with the `final` keyword. An interface may provide a default implementation of methods in terms of other methods in the interface. -```carbon +``` interface Vector { fn Add[self: Self](b: Self) -> Self; fn Scale[self: Self](v: f64) -> Self; @@ -5883,7 +4910,7 @@ interface Vector { A default function or method may also be defined out of line, later in the same file as the interface definition: -```carbon +``` interface Vector { fn Add[self: Self](b: Self) -> Self; fn Scale[self: Self](v: f64) -> Self; @@ -5909,7 +4936,7 @@ has one required method but dozens of "provided methods" with defaults. Defaults may also be provided for associated constants, such as associated facets, and interface parameters, using the `= ` syntax. -```carbon +``` interface Add(Right:! type = Self) { default let Result:! type = Self; fn DoAdd[self: Self](right: Right) -> Result; @@ -5925,7 +4952,7 @@ Note that `Self` is a legal default value for an associated facet or facet parameter. In this case the value of those names is not determined until `Self` is, so `Add()` is equivalent to the constraint: -```carbon +``` // Equivalent to Add() constraint AddDefault { extend Add(Self); @@ -5938,7 +4965,7 @@ parameters are left as their default values. More generally, default expressions may reference other associated constants or `Self` as parameters to type constructors. For example: -```carbon +``` interface Iterator { let Element:! type; default let Pointer:! type = Element*; @@ -5948,7 +4975,7 @@ interface Iterator { Carbon does **not** support providing a default implementation of a required interface. -```carbon +``` interface TotalOrder { fn TotalLess[self: Self](right: Self) -> bool; // ❌ Illegal: May not provide definition @@ -5964,11 +4991,7 @@ interface TotalOrder { The workaround for this restriction is to use a [blanket impl declaration](#blanket-impl-declarations) instead: -**FIXME: Is it sensible to have both a `require` and a blanket implementation? -Does the blanket implementation satisfy the requirement so you only need to -implement `TotalOrder`?** - -```carbon +``` interface TotalOrder { fn TotalLess[self: Self](right: Self) -> bool; require Self impls PartialOrder; @@ -5997,7 +5020,7 @@ As an alternative to providing a definition of an interface member as a default, members marked with the `final` keyword will not allow that definition to be overridden in `impl` definitions. -```carbon +``` interface TotalOrder { fn TotalLess[self: Self](right: Self) -> bool; final fn TotalGreater[self: Self](right: Self) -> bool { @@ -6018,14 +5041,14 @@ interface Add(T:! type = Self) { // `AddWith` *always* equals `T` final let AddWith:! type = T; // Has a *default* of `Self` - default let Result:! type = Self; + let Result:! type = Self; fn DoAdd[self: Self](right: AddWith) -> Result; } ``` Final members may also be defined out-of-line: -```carbon +``` interface TotalOrder { fn TotalLess[self: Self](right: Self) -> bool; final fn TotalGreater[self: Self](right: Self) -> bool; @@ -6053,7 +5076,7 @@ Recall that an [interface can require another interface be implemented for the type](#interface-requiring-other-interfaces), as in: -```carbon +``` interface Iterable { require Self impls Equatable; // ... @@ -6066,7 +5089,7 @@ done with [conditional conformance](#conditional-conformance), we allow another type to be specified between `require` and `impls` to say some type other than `Self` must implement an interface. For example, -```carbon +``` interface IntLike { require i32 impls As(Self); // ... @@ -6076,7 +5099,7 @@ interface IntLike { says that if `Self` implements `IntLike`, then `i32` must implement `As(Self)`. Similarly, -```carbon +``` interface CommonTypeWith(T:! type) { require T impls CommonTypeWith(Self); // ... @@ -6087,9 +5110,8 @@ says that if `Self` implements `CommonTypeWith(T)`, then `T` must implement `CommonTypeWith(Self)`. An `require`...`impls` constraint in an `interface`, or `constraint`, definition -must still use `Self` in some way. It can be an argument to either the -[type](#parameterized-types) or [interface](#parameterized-interfaces). For -example: +must still use `Self` in some way. It can be an argument to either the type or +interface. For example: - ✅ Allowed: `require Self impls Equatable` - ✅ Allowed: `require Vector(Self) impls Equatable` @@ -6102,9 +5124,9 @@ example: This restriction allows the Carbon compiler to know where to look for facts about a type. If `require i32 impls Equatable` could appear in any `interface` definition, that implies having to search all of them when considering what -interfaces `i32` implements. This would create a -[coherence](terminology.md#coherence) problem, since then the set of facts true -for a type would depend on which interfaces have been imported. +interfaces `i32` implements. This creates a coherence problem, since then the +set of facts true for a type would depend on which interfaces have been +imported. When implementing an interface with an `require`...`impls` requirement, that requirement must be satisfied by an implementation in an imported library, an @@ -6114,7 +5136,7 @@ requirement will be implemented. This is like a [forward declaration of an impl](#declaring-implementations) except that the definition can be broader instead of being required to match exactly. -```carbon +``` // `Iterable` requires `Equatable`, so there must be some // impl of `Equatable` for `Vector(i32)` in this file. impl Vector(i32) as Iterable { ... } @@ -6127,14 +5149,14 @@ fn ProcessVector(v: Vector(i32)) { } // Satisfies the requirement that `Vector(i32)` must -// implement `Equatable` since `i32 impls Equatable`. +// implement `Equatable` since `i32` impls `Equatable`. 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: -```carbon +``` impl forall [T:! type] T as CommonTypeWith(T) { ... } ``` @@ -6142,7 +5164,7 @@ Here is an example where the requirement of interface `Iterable` that the type implements interface `Equatable` is satisfied by a constraint in the `impl` declaration: -```carbon +``` 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 @@ -6154,7 +5176,7 @@ impl forall [T:! type where Foo(T) impls Equatable] This might be used to provide an implementation of `Equatable` for types that already satisfy the requirement of implementing `Iterable`: -```carbon +``` class Bar {} impl Foo(Bar) as Equatable {} // Gives `Foo(Bar) impls Iterable` using the blanket impl of @@ -6167,7 +5189,7 @@ An interface implementation requirement with a `where` clause is harder to satisfy. Consider an interface `B` that has a requirement that interface `A` is also implemented. -```carbon +``` interface A(T:! type) { let Result:! type; } @@ -6190,10 +5212,9 @@ to have a different assignment. An [`observe` declaration](#observe-declarations) can be used to show that two types are equal so code can pass type checking without explicitly writing casts, -and without requiring the compiler to do a unbounded search that may not -terminate. An `observe` declaration can also be used to show that a type -implements an interface, in cases where the compiler will not work this out for -itself. +without requiring the compiler to do a unbounded search that may not terminate. +An `observe` declaration can also be used to show that a type implements an +interface, in cases where the compiler will not work this out for itself. ### Observing interface requirements @@ -6201,13 +5222,13 @@ One situation where this occurs is when there is a chain of [interfaces requiring other interfaces](#interface-requiring-other-interfaces-revisited). During the `impl` validation done during type checking, Carbon will only consider the interfaces that are direct requirements of the interfaces the type -is known to implement. An `observe`...`impls` declaration can be used to add an +is known to implement. An `observe...impls` declaration can be used to add an interface that is a direct requirement to the set of interfaces whose direct requirements will be considered for that type. This allows a developer to provide a proof that there is a sequence of requirements that demonstrate that a type implements an interface, as in this example: -```carbon +``` interface A { } interface B { require Self impls A; } interface C { require Self impls B; } @@ -6222,35 +5243,35 @@ fn RequiresD[T:! D](x: T) { // ❌ Illegal: No direct connection between `D` and `A`. // RequiresA(x); - // `T impls D` and `D` directly requires `C` to be + // `T` impls `D` and `D` directly requires `C` to be // implemented. observe T impls C; - // `T impls C` and `C` directly requires `B` to be + // `T` impls `C` and `C` directly requires `B` to be // implemented. observe T impls B; - // ✅ Allowed: `T impls B` and `B` directly requires + // ✅ Allowed: `T` impls `B` and `B` directly requires // `A` to be implemented. RequiresA(x); } ``` Note that `observe` statements do not affect which impl is selected during code -generation. For [coherence](terminology.md#coherence), the impl used for a -(type, interface) pair must always be the same, independent of context. The +generation. For coherence, the impl used for a (type, interface) pair must +always be the same, independent of context. The [termination rule](#termination-rule) governs when compilation may fail when the -compiler can't determine the `impl` definition to select. +compiler can't determine the impl to select. ### Observing blanket impl declarations -An `observe`...`impls` declaration can also be used to observe that a type +An `observe...impls` declaration can also be used to observe that a type implements an interface because there is a [blanket impl declaration](#blanket-impl-declarations) in terms of requirements a type is already known to satisfy. Without an `observe` declaration, Carbon will only use blanket impl declarations that are directly satisfied. -```carbon +``` interface A { } interface B { } interface C { } @@ -6290,57 +5311,27 @@ In the case of an error, a quality Carbon implementation will do a deeper search for chains of requirements and blanket impl declarations and suggest `observe` declarations that would make the code compile if any solution is found. -### Observing equal to a type implementing an interface - -The [`observe`...`==` form](#observe-declarations) can be combined with the -`observe`...`impls` form to show that a type implements an interface because it -is equal to another type that is known to implement that interface. - -```carbon -interface I { - fn F(); -} - -fn G(T:! I, U:! type where .Self == T) { - // ❌ Illegal: No implementation of `I` for `U`. - U.(I.F)(); - - // ✅ Allowed: Implementation of `I` for `U` - // through `T`. - observe U == T impls I; - U.(I.F)(); - - // ❌ Illegal: `U` does not extend `I`. - U.F(); -} -``` - -Multiple `==` clauses are allowed in an `observe` declaration, so you may write -`observe A == B == C impls I;`. - ## Operator overloading Operations are overloaded for a type by implementing an interface specific to -that interface for that type. For example, types implement -[the `Negate` interface](/docs/design/expressions/arithmetic.md#extensibility) -to overload the unary `-` operator: +that interface for that type. For example, types implement the `Negatable` +interface to overload the unary `-` operator: -```carbon +``` // Unary `-`. -interface Negate { - default let Result:! type = Self; - fn Op[self: Self]() -> Result; +interface Negatable { + let Result:! type = Self; + fn Negate[self: Self]() -> Result; } ``` Expressions using operators are rewritten into calls to these interface methods. -For example, `-x` would be rewritten to `x.(Negate.Op)()`. +For example, `-x` would be rewritten to `x.(Negatable.Negate)()`. The interfaces and rewrites used for a given operator may be found in the [expressions design](/docs/design/expressions/README.md). [Question-for-leads issue #1058](https://github.com/carbon-language/carbon-lang/issues/1058) -defines the naming scheme for these interfaces, which was implemented in -[proposal #1178](https://github.com/carbon-language/carbon-lang/pull/1178). +defines the naming scheme for these interfaces. ### Binary operators @@ -6350,7 +5341,7 @@ example, to say a type may be converted to another type using an `as` expression, implement the [`As` interface](/docs/design/expressions/as_expressions.md#extensibility): -```carbon +``` interface As(Dest:! type) { fn Convert[self: Self]() -> Dest; } @@ -6361,26 +5352,26 @@ parameterization of the interface means it can be implemented multiple times to support multiple operand types. Unlike `as`, for most binary operators the interface's argument will be the -_type_ of the right-hand operand instead of its _value_. Consider -[the interface for a binary operator like `*`](/docs/design/expressions/arithmetic.md#extensibility): +_type_ of the right-hand operand instead of its _value_. Consider an interface +for a binary operator like `*`: -```carbon +``` // Binary `*`. -interface MulWith(U:! type) { - default let Result:! type = Self; - fn Op[self: Self](other: U) -> Result; +interface MultipliableWith(U:! type) { + let Result:! type = Self; + fn Multiply[self: Self](other: U) -> Result; } ``` A use of binary `*` in source code will be rewritten to use this interface: -```carbon +``` var left: Meters = ...; var right: f64 = ...; var result: auto = left * right; // Equivalent to: -var equivalent: left.(MulWith(f64).Result) - = left.(MulWith(f64).Op)(right); +var equivalent: left.(MultipliableWith(f64).Result) + = left.(MultipliableWith(f64).Multiply)(right); ``` Note that if the types of the two operands are different, then swapping the @@ -6389,28 +5380,23 @@ It is up to the developer to make those consistent when that is appropriate. The standard library will provide [adapters](#adapting-types) for defining the second implementation from the first, as in: -```carbon -interface OrderedWith(U:! type) { - fn Compare[self: Self](u: U) -> Ordering; - // ... +``` +interface ComparableWith(RHS:! type) { + fn Compare[self: Self](right: RHS) -> CompareResult; } -class ReverseComparison(T:! type, U:! OrderedWith(T)) { +class ReverseComparison + (T:! type, U:! ComparableWith(RHS)) { adapt T; - extend impl as OrderedWith(U) { - fn Compare[self: Self](u: U) -> Ordering { - match (u.Compare(self)) { - case .Less => return .Greater; - case .Equivalent => return .Equivalent; - case .Greater => return .Less; - case .Incomparable => return .Incomparable; - } + extend impl as ComparableWith(U) { + fn Compare[self: Self](right: RHS) -> CompareResult { + return ReverseCompareResult(right.Compare(self)); } } } -impl SongByTitle as OrderedWith(SongTitle) { ... } -impl SongTitle as OrderedWith(SongByTitle) +impl SongByTitle as ComparableWith(SongTitle); +impl SongTitle as ComparableWith(SongByTitle) = ReverseComparison(SongTitle, SongByTitle); ``` @@ -6418,59 +5404,59 @@ In some cases the reverse operation may not be defined. For example, a library might support subtracting a vector from a point, but not the other way around. Further note that even if the reverse implementation exists, -[the `impl` prioritization rule](#prioritization-rule) might not pick it. For +[the impl prioritization rule](#prioritization-rule) might not pick it. For example, if we have two types that support comparison with anything implementing an interface that the other implements: -```carbon +``` interface IntLike { fn AsInt[self: Self]() -> i64; } class EvenInt { ... } impl EvenInt as IntLike; -impl EvenInt as OrderedWith(EvenInt); +impl EvenInt as ComparableWith(EvenInt); // Allow `EvenInt` to be compared with anything that // implements `IntLike`, in either order. -impl forall [T:! IntLike] EvenInt as OrderedWith(T); -impl forall [T:! IntLike] T as OrderedWith(EvenInt); +impl forall [T:! IntLike] EvenInt as ComparableWith(T); +impl forall [T:! IntLike] T as ComparableWith(EvenInt); class PositiveInt { ... } impl PositiveInt as IntLike; -impl PositiveInt as OrderedWith(PositiveInt); +impl PositiveInt as ComparableWith(PositiveInt); // Allow `PositiveInt` to be compared with anything that // implements `IntLike`, in either order. -impl forall [T:! IntLike] PositiveInt as OrderedWith(T); -impl forall [T:! IntLike] T as OrderedWith(PositiveInt); +impl forall [T:! IntLike] PositiveInt as ComparableWith(T); +impl forall [T:! IntLike] T as ComparableWith(PositiveInt); ``` -Then the compiler will favor selecting the implementation based on the type of -the left-hand operand: +Then it will favor selecting the implementation based on the type of the +left-hand operand: -```carbon +``` var even: EvenInt = ...; var positive: PositiveInt = ...; -// Uses `EvenInt as OrderedWith(T)` impl +// Uses `EvenInt as ComparableWith(T)` impl if (even < positive) { ... } -// Uses `PositiveInt as OrderedWith(T)` impl +// Uses `PositiveInt as ComparableWith(T)` impl if (positive > even) { ... } ``` ### `like` operator for implicit conversions -Because the type of the operands is directly used to select the operator -interface implementation, there are no automatic implicit conversions, unlike -with function or method calls. Given both a method and an interface -implementation for multiplying by a value of type `f64`: +Because the type of the operands is directly used to select the implementation +to use, there are no automatic implicit conversions, unlike with function or +method calls. Given both a method and an interface implementation for +multiplying by a value of type `f64`: -```carbon +``` class Meters { fn Scale[self: Self](s: f64) -> Self; } // "Implementation One" -impl Meters as MulWith(f64) +impl Meters as MultipliableWith(f64) where .Result = Meters { - fn Op[self: Self](other: f64) -> Result { + fn Multiply[self: Self](other: f64) -> Result { return self.Scale(other); } } @@ -6480,14 +5466,14 @@ the method will work with any argument that can be implicitly converted to `f64` but the operator overload will only work with values that have the specific type of `f64`: -```carbon +``` var height: Meters = ...; var scale: f32 = 1.25; // ✅ Allowed: `scale` implicitly converted // from `f32` to `f64`. var allowed: Meters = height.Scale(scale); // ❌ Illegal: `Meters` doesn't implement -// `MulWith(f32)`. +// `MultipliableWith(f32)`. var illegal: Meters = height * scale; ``` @@ -6495,17 +5481,17 @@ The workaround is to define a parameterized implementation that performs the conversion. The implementation is for types that implement the [`ImplicitAs` interface](/docs/design/expressions/implicit_conversions.md#extensibility). -```carbon +``` // "Implementation Two" impl forall [T:! ImplicitAs(f64)] - Meters as MulWith(T) where .Result = Meters { - fn Op[self: Self](other: T) -> Result { + Meters as MultipliableWith(T) where .Result = Meters { + fn Multiply[self: Self](other: T) -> Result { // Carbon will implicitly convert `other` from type // `T` to `f64` to perform this call. - return self.((Meters as MulWith(f64)).Op)(other); + return self.(Meters.(MultipliableWith(f64).Multiply))(other); } } -// ✅ Allowed: uses `Meters as MulWith(T)` impl +// ✅ Allowed: uses `Meters as MultipliableWith(T)` impl // with `T == f32` since `f32 impls ImplicitAs(f64)`. var now_allowed: Meters = height * scale; ``` @@ -6518,12 +5504,12 @@ defining operator overloads, Carbon has the `like` operator. This operator can only be used in the type or facet type part of an `impl` declaration, as part of a forward declaration or definition, in a place of a type. -```carbon +``` // Notice `f64` has been replaced by `like f64` // compared to "implementation one" above. -impl Meters as MulWith(like f64) +impl Meters as MultipliableWith(like f64) where .Result = Meters { - fn Op[self: Self](other: f64) -> Result { + fn Multiply[self: Self](other: f64) -> Result { return self.Scale(other); } } @@ -6535,30 +5521,19 @@ equivalent to "implementation one". The second implementation replaces the `like f64` with a parameter that ranges over types that can be implicitly converted to `f64`, equivalent to "implementation two". -> **Note:** We have decided to change the following in -> [a discussion on 2023-07-13](https://docs.google.com/document/d/1gnJBTfY81fZYvI_QXjwKk1uQHYBNHGqRLI2BS_cYYNQ/edit?resourcekey=0-ql1Q1WvTcDvhycf8LbA9DQ#heading=h.rs7m0kytcl4t). -> The new approach is to have one parameterized implementation replacing all of -> the `like` expressions on the left of the `as`, and another replacing all of -> the `like` expressions on the right of the `as`. However, in -> [a discussion on 2023-07-20](https://docs.google.com/document/d/1gnJBTfY81fZYvI_QXjwKk1uQHYBNHGqRLI2BS_cYYNQ/edit?resourcekey=0-ql1Q1WvTcDvhycf8LbA9DQ#heading=h.msdqbemd6axi), -> we decided that this change would not affect how we handle nested `like` -> expressions: `like Vector(like i32)` is still `like Vector(i32)` plus -> `Vector(like i32)`. These changes have not yet gone through the proposal -> process. - In general, each `like` adds one additional parameterized implementation. There is always the impl defined with all of the `like` expressions replaced by their arguments with the definition supplied in the source code. In addition, for each `like` expression, there is an automatic `impl` definition with it replaced by a new parameter. These additional automatic implementations will delegate to the -main `impl` definition, which will trigger implicit conversions according to +main impl, which will trigger implicit conversions according to [Carbon's ordinary implicit conversion rules](/docs/design/expressions/implicit_conversions.md). In this example, there are two uses of `like`, producing three implementations -```carbon -impl like Meters as MulWith(like f64) +``` +impl like Meters as MultipliableWith(like f64) where .Result = Meters { - fn Op[self: Self](other: f64) -> Result { + fn Multiply[self: Self](other: f64) -> Result { return self.Scale(other); } } @@ -6566,61 +5541,63 @@ impl like Meters as MulWith(like f64) is equivalent to "implementation one", "implementation two", and: -```carbon +``` impl forall [T:! ImplicitAs(Meters)] - T as MulWith(f64) where .Result = Meters { - fn Op[self: Self](other: f64) -> Result { - // Will implicitly convert `self` to `Meters` in - // order to match the signature of this `Op` method. - return self.((Meters as MulWith(f64)).Op)(other); + T as MultipliableWith(f64) where .Result = Meters { + fn Multiply[self: Self](other: f64) -> Result { + // Will implicitly convert `self` to `Meters` in order to + // match the signature of this `Multiply` method. + return self.(Meters.(MultipliableWith(f64).Multiply))(other); } } ``` -`like` may be used in `impl` forward declarations in a way analogous to `impl` +`like` may be used in forward declarations in a way analogous to impl definitions. -```carbon -impl like Meters as MulWith(like f64) +``` +impl like Meters as MultipliableWith(like f64) where .Result = Meters; } ``` is equivalent to: -```carbon +``` // All `like`s removed. Same as the declaration part of // "implementation one", without the body of the definition. -impl Meters as MulWith(f64) where .Result = Meters; +impl Meters as MultipliableWith(f64) + where .Result = Meters; // First `like` replaced with a wildcard. impl forall [T:! ImplicitAs(Meters)] - T as MulWith(f64) where .Result = 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. impl forall [T:! ImplicitAs(f64)] - Meters as MulWith(T) where .Result = Meters; + Meters as MultipliableWith(T) where .Result = Meters; ``` -In addition, the generated `impl` definition for a `like` is implicitly injected -at the end of the (unique) source file in which the `impl` is defined. That is, -it is injected in the API file if the `impl` definition is in an API file, and -in the sole impl file with the `impl` definition otherwise. +In addition, the generated impl definition for a `like` is implicitly injected +at the end of the (unique) source file in which the impl is first declared. That +is, it is injected in the API file if the impl is declared in an API file, and +in the sole impl file declaring the impl otherwise. This means an `impl` +declaration using `like` in an API file also makes the parameterized definition If one `impl` declaration uses `like`, other declarations must use `like` in the same way to match. The `like` operator may be nested, as in: -```carbon +``` impl like Vector(like String) as Printable; ``` Which will generate implementations with declarations: -```carbon +``` impl Vector(String) as Printable; impl forall [T:! ImplicitAs(Vector(String))] T as Printable; impl forall [T:! ImplicitAs(String)] Vector(T) as Printable; @@ -6634,7 +5611,7 @@ example, there existing an implicit conversion from `T` to `String` does not imply that there is one from `Vector(T)` to `Vector(String)`, so the following use of `like` is illegal: -```carbon +``` // ❌ Illegal: Can't convert a value with type // `Vector(T:! ImplicitAs(String))` // to `Vector(String)` for `self` @@ -6649,7 +5626,7 @@ The argument to `like` must either not mention any type parameters, or those parameters must be able to be determined due to being repeated outside of the `like` expression. -```carbon +``` // ✅ Allowed: no parameters impl like Meters as Printable; @@ -6659,24 +5636,24 @@ impl forall [T:! IntLike] like T as Printable; // ❌ Illegal: `T` being used in a `where` clause // is insufficient. impl forall [T:! IntLike] like T - as MulWith(i64) where .Result = T; + as MultipliableWith(i64) where .Result = T; // ❌ Illegal: `like` can't be used in a `where` // clause. -impl Meters as MulWith(f64) +impl Meters as MultipliableWith(f64) where .Result = like Meters; // ✅ Allowed: `T` can be determined by another // part of the query. impl forall [T:! IntLike] like T - as MulWith(T) where .Result = T; + as MultipliableWith(T) where .Result = T; impl forall [T:! IntLike] T - as MulWith(like T) where .Result = 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. impl forall [T:! IntLike] like T - as MulWith(like T) where .Result = T; + as MultipliableWith(like T) where .Result = T; ``` ## Parameterized types @@ -6684,35 +5661,35 @@ impl forall [T:! IntLike] like T Generic types may be defined by giving them compile-time parameters. Those parameters may be used to specify types in the declarations of its members, such as data fields, member functions, and even interfaces being implemented. For -example, a container type might be parameterized by a facet describing the type -of its elements: +example, a container type might be parameterized by the type of its elements: -```carbon +**FIXME: member functions may have additional parameters.** + +``` class HashMap( - KeyT:! Hashable & Eq & Movable, - ValueT:! Movable) { - // `Self` is `HashMap(KeyT, ValueT)`. + KeyType:! Hashable & EqualityComparable & Movable, + ValueType:! Movable) { + // `Self` is `HashMap(KeyType, ValueType)`. - // Class parameters may be used in function signatures. - fn Insert[addr self: Self*](k: KeyT, v: ValueT); + // Parameters may be used in function signatures. + fn Insert[addr self: Self*](k: KeyType, v: ValueType); - // Class parameters may be used in field types. - private var buckets: DynArray((KeyT, ValueT)); + // Parameters may be used in field types. + private var buckets: Vector((KeyType, ValueType)); - // Class parameters may be used in interfaces implemented. - extend impl as Container where .ElementType = (KeyT, ValueT); - impl as OrderedWith(HashMap(KeyT, ValueT)); + // Parameters may be used in interfaces implemented. + extend impl as Container where .ElementType = (KeyType, ValueType); + extend impl as ComparableWith(HashMap(KeyType, ValueType)); } ``` -Note that, unlike functions, every parameter to a type must be a compile-time -binding, either symbolic using `:!` or template using `template`...`:!`, not -runtime, with a plain `:`. +Note that, unlike functions, every parameter to a type must be compile-time, +either symbolic using `:!` or template using `template...:!`, not dynamic, with +a plain `:`. -Two types are the same if they have the same name and the same arguments, after -applying aliases and [rewrite constraints](#rewrite-constraints). Carbon's -[manual type equality](#manual-type-equality) approach means that the compiler -may not always be able to tell when two +Two types are the same if they have the same name and the same arguments. +Carbon's [manual type equality](#manual-type-equality) approach means that the +compiler may not always be able to tell when two [type expressions](terminology.md#type-expression) are equal without help from the user, in the form of [`observe` declarations](#observe-declarations). This means Carbon will not in general be able to determine when types are unequal. @@ -6720,67 +5697,20 @@ means Carbon will not in general be able to determine when types are unequal. Unlike an [interface's parameters](#parameterized-interfaces), a type's parameters may be [deduced](terminology.md#deduced-parameter), as in: -```carbon -fn ContainsKey[KeyT:! Movable, ValueT:! Movable] - (haystack: HashMap(KeyT, ValueT), needle: KeyT) +``` +fn ContainsKey[KeyType:! Movable, ValueType:! Movable] + (haystack: HashMap(KeyType, ValueType), needle: KeyType) -> bool { ... } fn MyMapContains(s: String) { var map: HashMap(String, i32) = (("foo", 3), ("bar", 5)); - // ✅ Deduces `KeyT` = `String as Movable` from the types of both arguments. - // Deduces `ValueT` = `i32 as Movable` from the type of the first argument. + // ✅ Deduces `KeyType` = `String` from the types of both arguments. + // Deduces `ValueType` = `i32` from the type of the first argument. return ContainsKey(map, s); } ``` Note that restrictions on the type's parameters from the type's declaration can -be [implied constraints](#implied-constraints) on the function's parameters. In -the above example, the `KeyT` parameter to `ContainsKey` gets `Hashable & Eq` -implied constraints from the declaration of the corresponding parameter to -`HashMap`. - -> **Future work:** We may want to support optional deduced parameters in square -> brackets `[`...`]` before the explicit parameters in round parens `(`...`)`. - -> **References:** This feature is from -> [proposal #1146: Generic details 12: parameterized types](https://github.com/carbon-language/carbon-lang/pull/1146). - -### Generic methods - -A generic type may have methods with additional compile-time parameters. For -example, this `Set(T)` type may be compared to anything implementing the -`Container` interface as long as the element types match: - -```carbon -class Set(T:! Ordered) { - fn Less[U:! Container with .ElementType = T, self: Self](u: U) -> bool; - // ... -} -``` - -The `Less` method is parameterized both by the `T` parameter to the `Set` type -and its own `U` parameter deduced from the type of its first argument. - -### Conditional methods - -A method could be defined conditionally for a generic type by using a more -specific type in place of `Self` in the method declaration. For example, this is -how to define a dynamically sized array type that only has a `Sort` method if -its elements implement the `Ordered` interface: - -```carbon -class DynArray(T:! type) { - // `DynArray(T)` has a `Sort()` method if `T impls Ordered`. - fn Sort[C:! Ordered, addr self: DynArray(C)*](); -} -``` - -**Comparison with other languages:** In -[Rust](https://doc.rust-lang.org/book/ch10-02-traits.html#using-trait-bounds-to-conditionally-implement-methods) -this feature is part of conditional conformance. Swift supports conditional -methods using -[conditional extensions](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID553) -or -[contextual where clauses](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID628). +be [implied constraints](#implied-constraints) on the function's parameters. ### Specialization @@ -6802,7 +5732,7 @@ single byte. Clients of the optional library may want to add additional specializations for their own types. We make an interface that represents "the storage of `Optional(T)` for type `T`," written here as `OptionalStorage`: -```carbon +``` interface OptionalStorage { let Storage:! type; fn MakeNone() -> Storage; @@ -6815,7 +5745,7 @@ interface OptionalStorage { The default implementation of this interface is provided by a [blanket implementation](#blanket-impl-declarations): -```carbon +``` // Default blanket implementation impl forall [T:! Movable] T as OptionalStorage where .Storage = (bool, T) { @@ -6827,7 +5757,7 @@ This implementation can then be [specialized](#lookup-resolution-and-specialization) for more specific type patterns: -```carbon +``` // Specialization for pointers, using nullptr == None final impl forall [T:! type] T* as OptionalStorage where .Storage = Array(Byte, sizeof(T*)) { @@ -6844,7 +5774,7 @@ Further, libraries can implement `OptionalStorage` for their own types, assuming the interface is not marked `private`. Then the implementation of `Optional(T)` can delegate to `OptionalStorage` for anything that can vary with `T`: -```carbon +``` class Optional(T:! Movable) { fn None() -> Self { return {.storage = T.(OptionalStorage.MakeNone)()}; @@ -6864,7 +5794,7 @@ implementation of `OptionalStorage` exists for `T`. Carbon does not require callers of `Optional`, even checked-generic callers, to specify that the argument type implements `OptionalStorage`: -```carbon +``` // ✅ Allowed: `T` just needs to be `Movable` to form `Optional(T)`. // A `T:! OptionalStorage` constraint is not required. fn First[T:! Movable & Eq](v: Vector(T)) -> Optional(T); @@ -6878,7 +5808,7 @@ In this example, a `let` is used to avoid repeating `OptionalStorage` in the definition of `Optional`, since it has no name conflicts with the members of `Movable`: -```carbon +``` class Optional(T:! Movable) { private let U:! Movable & OptionalStorage = T; fn None() -> Self { @@ -6892,9 +5822,6 @@ class Optional(T:! Movable) { } ``` -> **Alternative considered:** Direct support for specialization of types was -> considered in [proposal #1146](/proposals/p1146.md#alternatives-considered). - ## Future work ### Dynamic types @@ -6983,18 +5910,23 @@ See details in [the goals document](goals.md#bridge-for-c-customization-points). Some facility for allowing a function to take a variable number of arguments, with the [definition checked](terminology.md#complete-definition-checking) -independent of calls. Open -[proposal #2240](https://github.com/carbon-language/carbon-lang/pull/2240) is -adding this feature. +independent of calls. -### Value constraints for template parameters +### Range constraints on symbolic integers -We have planned support for predicates that constrain the value of non-facet -template parameters. For example, we might support a predicate that constrains -an integer to live inside a specified range. See -[question-for-leads issue #2153: Checked generics calling templates](https://github.com/carbon-language/carbon-lang/issues/2153) -and -[future work in proposal #2200: Template generics](/proposals/p2200.md#predicates-constraints-on-values). +We currently only support `where` clauses on facet types. We may want to also +support constraints on symbolic integers. The constraint with the most expected +value is the ability to do comparisons like `<`, or `>=`. For example, you might +constrain the `N` member of [`NSpacePoint`](#associated-constants) using an +expression like `PointT:! NSpacePoint where 2 <= .N and .N <= 3`. + +The concern here is supporting this at compile time with more benefit than +complexity. For example, we probably don't want to support integer-range based +types at runtime, and there are also concerns about reasoning about comparisons +between multiple symbolic integer parameters. For example, if `J < K` and +`K <= L`, can we call a function that requires `J < L`? There is also a +secondary syntactic concern about how to write this kind of constraint on a +parameter, as opposed to an associated facet, as in `N:! u32 where ___ >= 2`. ## References @@ -7015,14 +5947,12 @@ and - [#1327: Generics: `impl forall`](https://github.com/carbon-language/carbon-lang/pull/1327) - [#2107: Clarify rules around `Self` and `.Self`](https://github.com/carbon-language/carbon-lang/pull/2107) - [#2138: Checked and template generic terminology](https://github.com/carbon-language/carbon-lang/pull/2138) -- [Issue #2153: Checked generics calling templates](https://github.com/carbon-language/carbon-lang/issues/2153) - [#2173: Associated constant assignment versus equality](https://github.com/carbon-language/carbon-lang/pull/2173) - [#2200: Template generics](https://github.com/carbon-language/carbon-lang/pull/2200) - [#2347: What can be done with an incomplete interface](https://github.com/carbon-language/carbon-lang/pull/2347) - [#2360: Types are values of type `type`](https://github.com/carbon-language/carbon-lang/pull/2360) - [#2376: Constraints must use `Self`](https://github.com/carbon-language/carbon-lang/pull/2376) - [#2483: Replace keyword `is` with `impls`](https://github.com/carbon-language/carbon-lang/pull/2483) -- [#2687: Termination algorithm for impl selection](https://github.com/carbon-language/carbon-lang/pull/2687) - [#2760: Consistent `class` and `interface` syntax](https://github.com/carbon-language/carbon-lang/pull/2760) - [#2964: Expression phase terminology](https://github.com/carbon-language/carbon-lang/pull/2964) - [#3162: Reduce ambiguity in terminology](https://github.com/carbon-language/carbon-lang/pull/3162) From 115703a2b48a8b6d6b44175378dc98d0ce625bf6 Mon Sep 17 00:00:00 2001 From: Josh L Date: Fri, 22 Sep 2023 22:53:01 +0000 Subject: [PATCH 81/83] Checkpoint progress. --- docs/design/generics/appendix-witness.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/design/generics/appendix-witness.md b/docs/design/generics/appendix-witness.md index 233e3da0ff14d..f70566f3b810c 100644 --- a/docs/design/generics/appendix-witness.md +++ b/docs/design/generics/appendix-witness.md @@ -213,8 +213,8 @@ class Vector { } ``` -The [`impl` of `Vector` for `Point_Inline`](details.md#inline-impl) would be a -value of this type: +The [`impl` definition of `Vector` for `Point_Inline`](details.md#inline-impl) +would be a value of this type: ``` var VectorForPoint_Inline: Vector = { From 58b96112f8a4def6cd0a2e95fb540186942109a7 Mon Sep 17 00:00:00 2001 From: josh11b Date: Sat, 23 Sep 2023 08:46:21 -0700 Subject: [PATCH 82/83] Apply suggestions from code review Co-authored-by: Chandler Carruth --- docs/design/classes.md | 2 +- docs/design/generics/README.md | 4 ++-- docs/project/faq.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/design/classes.md b/docs/design/classes.md index c384acf1226ab..28ec1b236cb66 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -1625,7 +1625,7 @@ conform to the decision on The compiler automatically determines which of these [facet types](/docs/design/generics/terminology.md#facet-type) a given type satisfies. It is illegal to directly implement `Concrete`, `Deletable`, or -`Destructible` directly. For more about these constraints, see +`Destructible`. For more about these constraints, see ["destructor constraints" in the detailed generics design](/docs/design/generics/details.md#destructor-constraints). A pointer to `Deletable` types may be passed to the `Delete` method of the diff --git a/docs/design/generics/README.md b/docs/design/generics/README.md index 46c1bd1af9076..999bea92b4624 100644 --- a/docs/design/generics/README.md +++ b/docs/design/generics/README.md @@ -19,6 +19,6 @@ feature of Carbon: - [Detailed design](details.md) - In-depth description - [Appendix: Witness tables](appendix-witness.md) - Describes an implementation strategy for checked generics, and Carbon's rationale for - only using it for dynamic dispatch + only using it for dynamic dispatch. - [Appendix: Coherence](appendix-coherence.md) - Describes the rationale - for Carbon's choice to have coherent generics, and the alternatives + for Carbon's choice to have coherent generics, and the alternatives. diff --git a/docs/project/faq.md b/docs/project/faq.md index 6248d704be490..1472cd549b1d3 100644 --- a/docs/project/faq.md +++ b/docs/project/faq.md @@ -336,7 +336,7 @@ in scope. It's also worth noting that Carbon [doesn't use _any_ kind of brackets](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/README.md#checked-and-template-parameters) -to mark template or checked-generic parameters, so if Carbon had angle brackets, +to mark template- or checked-generic parameters, so if Carbon had angle brackets, they would mean something different than they do in C++, which could cause confusion. We do use square brackets to mark _deduced_ parameters, as in: From 538ef71b4ca6c1d9ac1e69ea4b9bb85929dcd05e Mon Sep 17 00:00:00 2001 From: Josh L Date: Sat, 23 Sep 2023 15:50:01 +0000 Subject: [PATCH 83/83] Fix formatting --- docs/project/faq.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/project/faq.md b/docs/project/faq.md index 1472cd549b1d3..25eaeb546fc4d 100644 --- a/docs/project/faq.md +++ b/docs/project/faq.md @@ -336,9 +336,9 @@ in scope. It's also worth noting that Carbon [doesn't use _any_ kind of brackets](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/README.md#checked-and-template-parameters) -to mark template- or checked-generic parameters, so if Carbon had angle brackets, -they would mean something different than they do in C++, which could cause -confusion. We do use square brackets to mark _deduced_ parameters, as in: +to mark template- or checked-generic parameters, so if Carbon had angle +brackets, they would mean something different than they do in C++, which could +cause confusion. We do use square brackets to mark _deduced_ parameters, as in: ``` fn Sort[T:! Comparable](a: Vector(T)*)
Generics + Checked Templates + Template
name lookup resolved for definitions in isolation ("early") some name lookup may require information from calls (name lookup may be "late") + name lookup can use information from calls (name lookup may be "late")
supports separate type checking; may also support separate compilation, for example when implemented using dynamic witness tables + supports separate type checking; may also support separate compilation separate compilation only to the extent that C++ supports it