Преглед изворни кода

Generic parameterized impls (details 5) (#920)

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.
    ```
    external impl [T:! Type] Vector(T) as Iterable { ... }
    external impl Vector(T:! Type) as Iterable { ... }
    ```
-   "Conditional conformance" where a parameterized type implements some interface if the parameter to the type satisfies some criteria, like implementing the same interface.
    ```
    external impl [T:! Type] Pair(T, T) as Foo(T) { ... }
    class Array(T:! Type, template N:! Int) {
      impl [P:! Printable] Array(P, N) as Printable { ... }
      impl Array(P:! Printable, N) as Printable { ... }
    }
    ```
-   "Blanket" impls where an interface is implemented for all types that implement another interface, or some other criteria beyond being a specific type.
    ```
    external impl [T:! Ordered] T as PartiallyOrdered { ... }
    ```
-   "Wildcard" impls where a family of interfaces are implemented for single type.
    ```
    class BigInt {
      external impl [T:! ImplicitAs(i32)] as AddTo(T) { ... }
      external impl as AddTo(T:! ImplicitAs(i32)) { ... }
    }
    external impl [T:! ImplicitAs(i32)] BigInt as AddTo(T) { ... }
    external impl BigInt as AddTo(T:! ImplicitAs(i32)) { ... }
    ```

In addition to a syntax for defining parameterized impls, we need rules for coherence:

-   Orphan rules that ensure that impls are imported in any code that might use it.
-   We need overlap rules that pick a specific impl when more than one impl declaration matches a specific query about whether a type implements an interface.

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
josh11b пре 4 година
родитељ
комит
dcc287cb6e
3 измењених фајлова са 1043 додато и 29 уклоњено
  1. 663 25
      docs/design/generics/details.md
  2. 22 4
      docs/design/generics/overview.md
  3. 358 0
      proposals/p0920.md

+ 663 - 25
docs/design/generics/details.md

@@ -74,10 +74,23 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
     -   [Sized types and type-of-types](#sized-types-and-type-of-types)
         -   [Implementation model](#implementation-model-2)
     -   [`TypeId`](#typeid)
--   [Future work](#future-work)
+-   [Parameterized impls](#parameterized-impls)
+    -   [Impl for a parameterized type](#impl-for-a-parameterized-type)
     -   [Conditional conformance](#conditional-conformance)
-    -   [Parameterized impls](#parameterized-impls)
-        -   [Lookup resolution and specialization](#lookup-resolution-and-specialization)
+        -   [Conditional methods](#conditional-methods)
+    -   [Blanket impls](#blanket-impls)
+        -   [Difference between blanket impls and named constraints](#difference-between-blanket-impls-and-named-constraints)
+    -   [Wildcard impls](#wildcard-impls)
+    -   [Combinations](#combinations)
+    -   [Lookup resolution and specialization](#lookup-resolution-and-specialization)
+        -   [Type structure of an impl declaration](#type-structure-of-an-impl-declaration)
+        -   [Orphan rule](#orphan-rule)
+        -   [Overlap rule](#overlap-rule)
+        -   [Prioritization rule](#prioritization-rule)
+        -   [Acyclic rule](#acyclic-rule)
+        -   [Termination rule](#termination-rule)
+        -   [Comparison to Rust](#comparison-to-rust)
+-   [Future work](#future-work)
     -   [Dynamic types](#dynamic-types)
         -   [Runtime type parameters](#runtime-type-parameters)
         -   [Runtime type fields](#runtime-type-fields)
@@ -2341,16 +2354,16 @@ 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, 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:
+`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
@@ -2365,8 +2378,8 @@ few goals:
     [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, as long as
-one can unambiguously be picked as most specific.
+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
@@ -3503,25 +3516,649 @@ value of type `T` in this case.
 pollution (`.TypeName`, `.TypeHash`, etc.) unless the function specifically
 requests those capabilities?
 
-## Future work
+## Parameterized impls
+
+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.
+-   "Conditional conformance" where a parameterized type implements some
+    interface if the parameter to the type satisfies some criteria, like
+    implementing the same interface.
+-   "Blanket" impls where an interface is implemented for all types that
+    implement another interface, or some other criteria beyond being a specific
+    type.
+-   "Wildcard" impls where a family of interfaces are implemented for single
+    type.
+
+### Impl for a parameterized type
+
+Interfaces may be implemented for a parameterized type. This can be done
+lexically in the class' scope:
+
+```
+class Vector(T:! Type) {
+  impl as Iterable {
+    let ElementType:! Type = T;
+    ...
+  }
+}
+```
+
+This is equivalent to naming the type between `impl` and `as`:
+
+```
+class Vector(T:! Type) {
+  impl Vector(T) as Iterable {
+    let ElementType:! Type = T;
+    ...
+  }
+}
+```
+
+An impl may be declared [external](#external-impl) by adding an `external`
+keyword before `impl`. External impls may also be declared out-of-line:
+
+```
+external impl [T:! Type] Vector(T) as Iterable {
+  let ElementType:! Type = T;
+  ...
+}
+// This syntax is also allowed:
+external impl Vector(T:! Type) as Iterable {
+  let ElementType:! Type = T;
+  ...
+}
+```
+
+The parameter for the type can be used as an argument to the interface being
+implemented:
+
+```
+class HashMap(Key:! Hashable, Value:! Type) {
+  impl as Has(Key) { ... }
+  impl as Contains(HashSet(Key)) { ... }
+}
+```
+
+or externally out-of-line:
+
+```
+class HashMap(Key:! Hashable, Value:! Type) { ... }
+external impl [Key:! Hashable, Value:! Type]
+    HashMap(Key, Value) as Has(Key) { ... }
+external impl [Key:! Hashable, Value:! Type]
+    HashMap(Key, Value) as Contains(HashSet(Key)) { ... }
+
+// This syntax is also allowed:
+external impl HashMap(Key:! Hashable, Value:! Type)
+    as Has(Key) { ... }
+external impl HashMap(Key:! Hashable, Value:! Type)
+    as Contains(HashSet(Key)) { ... }
+```
 
 ### Conditional conformance
 
-[The problem](terminology.md#conditional-conformance) we are trying to solve
-here is expressing that we have an `impl` of some interface for some type, but
-only if some additional type restrictions are met.
+[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
+include being able to say that a container type, like `Vector`, implements some
+interface when its element type satisfies the same interface:
 
-### Parameterized impls
+-   A container is printable if its elements are.
+-   A container could be compared to another container with the same element
+    type using a
+    [lexicographic comparison](https://en.wikipedia.org/wiki/Lexicographic_order)
+    if the element type is comparable.
+-   A container is copyable if its elements are.
 
-Also known as "blanket `impl`s", these are when you have an `impl` definition
-that is parameterized so it applies to more than a single type and interface
-combination.
+To do this with an [`external impl`](#external-impl), specify a more-specific
+`Self` type to the left of the `as` in the declaration:
 
-#### Lookup resolution and specialization
+```
+interface Printable {
+  fn Print[me: Self]();
+}
+class Vector(T:! Type) { ... }
+
+// By saying "T:! Printable" instead of "T:! Type" here,
+// we constrain T to be Printable for this impl.
+external impl [T:! Printable] Vector(T) as Printable {
+  fn Print[me: Self]() {
+    for (let a: T in me) {
+      // Can call `Print` on `a` since the constraint
+      // on `T` ensures it implements `Printable`.
+      a.Print();
+    }
+  }
+}
+// This syntax is also allowed:
+external impl Vector(T:! Printable) as Printable { ... }
+```
+
+To define these `impl`s inline in a `class` definition, include a more-specific
+type between the `impl` and `as` keywords.
+
+```
+class Array(T:! Type, template N:! Int) {
+  // These are both allowed:
+  impl [P:! Printable] Array(P, N) as Printable { ... }
+  impl Array(P:! Printable, N) as Printable { ... }
+}
+```
+
+It is legal to add the keyword `external` before the `impl` keyword to switch to
+an external impl defined lexically within the class scope. Inside the scope,
+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.
+
+**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.
+
+```
+interface Foo(T:! Type) { ... }
+class Pair(T:! Type, U:! Type) { ... }
+external impl [T:! Type] Pair(T, T) as Foo(T) { ... }
+```
+
+You may also define the `impl` inline, in which case it can be internal:
+
+```
+class Pair(T:! Type, U:! Type) {
+  impl Pair(T, T) as Foo(T) { ... }
+}
+```
+
+**Clarification:** Method lookup will look at all internal implementations,
+whether or not the conditions on those implementations hold for the `Self` type.
+If the conditions don't hold, then the call will be rejected because `Self` has
+the wrong type, just like any other argument/parameter type mismatch. This means
+types may not implement two different interfaces internally if they share a
+member name, even if their conditions are mutually exclusive:
+
+```
+class X(T:! Type) {
+  impl X(i32) as Foo {
+    fn F[me: Self]();
+  }
+  impl X(i64) as Bar {
+    // ❌ Illegal: name conflict between `Foo.F` and `Bar.F`
+    fn F[me: Self](n: i64);
+  }
+}
+```
+
+However, the same interface may be implemented multiple times as long as there
+is no overlap in the conditions:
+
+```
+class X(T:! Type) {
+  impl X(i32) as Foo {
+    fn F[me: Self]();
+  }
+  impl X(i64) as Foo {
+    // ✅ Allowed: `X(T).F` consistently means `X(T).(Foo.F)`
+    fn F[me: Self]();
+  }
+}
+```
+
+This allows a type to express that it implements an interface for a list of
+types, possibly with different implementations.
+
+In general, `X(T).F` can only mean one thing, regardless of `T`.
+
+**Concern:** The conditional conformance feature makes the question "is this
+interface implemented for this type" undecidable in general.
+[This feature in Rust has been shown to allow implementing a Turing machine](https://sdleffler.github.io/RustTypeSystemTuringComplete/).
+The acyclic restriction may eliminate this issue, otherwise we will likely need
+some heuristic like a limit on how many steps of recursion are allowed.
+
+**Comparison with other languages:**
+[Swift supports conditional conformance](https://github.com/apple/swift-evolution/blob/master/proposals/0143-conditional-conformances.md),
+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` is `Comparable`.
+  fn Sort[C:! Comparable, addr me: 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 impls
+
+A _blanket impl_ is an `impl` that could apply to more than one type, so the
+`impl` will use a type variable for the `Self` type. Here are some examples
+where blanket impls arise:
+
+-   Any type implementing `Ordered` should get an implementation of
+    `PartiallyOrdered`.
+
+    ```
+    external impl [T:! Ordered] T as PartiallyOrdered { ... }
+    ```
+
+-   `T` implements `CommonType(T)` for all `T`
+
+    ```
+    external impl [T:! Type] T as CommonType(T) {
+      let Result:! auto = T;
+    }
+    ```
+
+    This means that every type is the common type with itself.
+
+Blanket impls must always be [external](#external-impl) and defined lexically
+out-of-line.
+
+#### Difference between blanket impls and named constraints
+
+A blanket interface can be used to say "any type implementing `interface I` also
+implements `interface B`." Compare this with defining a `constraint C` that
+requires `I`. In that case, `C` will also be implemented any time `I` is. There
+are differences though:
+
+-   There can be other implementations of `interface B` without a corresponding
+    implementation of `I`, unless `B` has a requirement on `I`. However, the
+    types implementing `C` will be the same as the types implementing `I`.
+-   More specialized implementations of `B` can override the blanket
+    implementation.
+
+### Wildcard impls
+
+A _wildcard impl_ is an impl that defines a family of interfaces for a single
+`Self` type. For example, the `BigInt` type might implement `AddTo(T)` for all
+`T` that implement `ImplicitAs(i32)`. The implementation would first convert `T`
+to `i32` and then add the `i32` to the `BigInt` value.
+
+```
+class BigInt {
+  extern impl [T:! ImplicitAs(i32)] as AddTo(T) { ... }
+  // Or:
+  extern impl as AddTo(T:! ImplicitAs(i32)) { ... }
+}
+// Or out-of-line:
+extern impl [T:! ImplicitAs(i32)] BigInt as AddTo(T) { ... }
+// Or:
+extern impl BigInt as AddTo(T:! ImplicitAs(i32)) { ... }
+```
+
+Wildcard impls must always be [external](#external-impl), to avoid having the
+names in the interface defined for the type multiple times.
+
+### Combinations
+
+The different kinds of parameters to impls may be combined. For example, if `T`
+implements `As(U)`, then this implements `As(Optional(U))` for `Optional(T)`:
+
+```
+external impl [U:! Type, T:! As(U)]
+  Optional(T) as As(Optional(U)) { ... }
+```
+
+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:
+
+-   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 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
-combination.
+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
+
+Given an impl declaration, find the type structure by deleting deduced
+parameters and replacing type parameters by a `?`. The type structure of this
+declaration:
+
+```
+impl [T:! ..., U:! ...] Foo(T, i32) as Bar(String, U) { ... }
+```
+
+is:
+
+```
+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 impls declared 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 `external` keyword is removed, if present.
+
+The type structure will always contain a single interface name, which is the
+name of the interface being implemented, and some number of type names. Type
+names can be in the `Self` type to the left of the `as` keyword, or as
+parameters to other types or the interface. These names must always be defined
+either in the current library or be publicly defined in some library this
+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, impls 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_.
+
+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
+[need not be imported](/proposals/p0920.md#orphan-rule-could-consider-interface-requirements-in-blanket-impls).
+
+Since Carbon in addition requires there be no cyclic library dependencies, we
+conclude that there is at most one library that can define impls with a
+particular type structure.
+
+#### Overlap rule
+
+Given a specific concrete type, say `Foo(bool, i32)`, and an interface, say
+`Bar(String, f32)`, the overlap rule picks, among all the matching impls, which
+type structure is considered "most specific" to use as the implementation of
+that type for that interface.
+
+Given two different type structures of impls matching a query, for example:
+
+```
+impl Foo(?, i32) as Bar(String, ?)
+impl Foo(?, ?) as Bar(String, f32)
+```
+
+We pick the type structure with a non-`?` at the first difference as most
+specific. Here we see a difference between `Foo(?, i32)` and `Foo(?, ?)`, so we
+select the one with `Foo(?, i32)`, ignoring the fact that it has another `?`
+later in its type structure
+
+This rule corresponds to a depth-first traversal of the type tree to identify
+the first difference, and then picking the most specific choice at that
+difference.
+
+#### Prioritization rule
+
+Since at most one library can define impls with a given type structure, all
+impls 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 structure, they must be written
+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 `{` ... `}`.
+
+```
+match_first {
+  // If T is Foo prioritized ahead of T is Bar
+  impl [T:! Foo] T as Bar { ... }
+  impl [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 impls, 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 block.
+-   All the impls 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 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 impls are not
+considered to form cycles with themselves:
+
+```
+impl [T:! Printable] Optional(T) as Printable;
+impl [T:! Type, U:! ComparableTo(T)] U as ComparableTo(Optional(T));
+```
+
+**Example:** If `T` implements `ComparableWith(U)`, then `U` should implement
+`ComparableWith(T)`.
+
+```
+external impl [U:! Type, T:! ComparableWith(U)]
+    U as ComparableWith(T);
+```
+
+This is a cycle where which types implement `ComparableWith` determines which
+types implement the same interface.
+
+**Example:** Cycles can create situations where there are multiple ways of
+selecting impls that are inconsistent with each other. Consider an interface
+with two blanket `impl` declarations:
+
+```
+class Y {}
+class N {}
+interface True {}
+impl Y as True {}
+interface Z(T:! Type) { let Cond:! Type; }
+match_first {
+  impl [T:! Type, U:! Z(T) where .Cond is True] T as Z(U) {
+    let Cond:! Type = N;
+  }
+  impl [T:! Type, U:! Type] T as Z(U) {
+    let Cond:! Type = Y;
+  }
+}
+```
+
+What is `i8.(Z(i16).Cond)`? It depends on which of the two blanket impls are
+selected.
+
+-   An implementation of `Z(i16)` for `i8` could come from the first blanket
+    impl with `T == i8` and `U == i16` if `i16 is Z(i8)` and
+    `i16.(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`.
+-   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`.
+
+There is no reason to to prefer one of these outcomes over the other.
+
+**Example:** Further, cycles can create contradictions in the type system:
+
+```
+class A {}
+class B {}
+class C {}
+interface D(T:! Type) { let Cond:! Type; }
+match_first {
+  impl [T:! Type, U:! D(T) where .Cond = B] T as D(U) {
+    let Cond:! Type = C;
+  }
+  impl [T:! Type, U:! D(T) where .Cond = A] T as D(U) {
+    let Cond:! Type = B;
+  }
+  impl [T:! Type, U:! Type] T as D(U) {
+    let Cond:! Type = A;
+  }
+}
+```
+
+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.(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.
+
+The workaround for this problem is to either split an interface in the cycle in
+two, with a blanket implementation of one from the other, or move some of the
+criteria into a [named constraint](#named-constraints).
+
+**Concern:** Cycles could be spread out across libraries with no dependencies
+between them. This means there can be problems created by a library that are
+only detected by its users.
+
+**Open question:** Should Carbon reject cycles in the absence of a query? The
+two options here are:
+
+-   Combining impls gives you an immediate error if there exists queries using
+    those impls that have cycles.
+-   Only when a query reveals a cyclic dependency is an error reported.
+
+**Open question:** In the second case, should we ignore cycles if they don't
+affect the result of the query? For example, the cycle might be among
+implementations that are lower priority.
+
+#### Termination rule
+
+It is possible to define a set of impls where there isn't a cycle, but the graph
+is infinite. Without some rule to prevent exhaustive exploration of the graph,
+determining whether a type implements an interface could run forever.
+
+**Example:** It could be that `A` implements `B`, so `A is B` if
+`Optional(A) is B`, if `Optional(Optional(A)) is B`, and so on. This could be
+the result of a single impl:
+
+```
+impl [A:! Type where Optional(.Self) is B] A as B { ... }
+```
+
+This problem can also result from a chain of impls, as in `A is B` if `A* is C`,
+if `Optional(A) is 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 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.
+
+**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?
+
+#### Comparison to Rust
+
+Rust has been designing a specialization feature, but it has not been completed.
+Luckily, Rust team members have done a lot of blogging during their design
+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 similarites and
+differences between the Carbon and Rust plans:
+
+-   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 impls may always be
+    specialized.
+-   Since Rust impls are not specializable by default, generic functions can
+    assume that if a matching blanket impl 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 needs 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).
+-   Carbon will not use "covering" rules, as described in
+    [Rust RFC 2451: "Re-Rebalancing Coherence"](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html)
+    and
+    [Little Orphan Impls: The covered rule](http://smallcultfollowing.com/babysteps/blog/2015/01/14/little-orphan-impls/#the-covered-rule).
+-   Like Rust, Carbon does use ordering, favoring the `Self` type and then the
+    parameters to the interface in left-to-right order, see
+    [Rust RFC 1023: "Rebalancing Coherence"](https://rust-lang.github.io/rfcs/1023-rebalancing-coherence.html)
+    and
+    [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
+    impls. 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/).
+-   [Supporting blanket impls in specialization](http://smallcultfollowing.com/babysteps/blog/2016/10/24/supporting-blanket-impls-in-specialization/)
+    proposes a specialization rule for Rust that considers type structure before
+    other constraints, as in Carbon, though the details differ.
+-   Rust has more orphan restrictions to avoid there being cases where it is
+    ambiguous which impl should be selected. Carbon instead has picked a total
+    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.
+
+## Future work
 
 ### Dynamic types
 
@@ -3649,3 +4286,4 @@ parameter, as opposed to an associated type, as in `N:! u32 where ___ >= 2`.
 -   [#731: Generics details 2: adapters, associated types, parameterized interfaces](https://github.com/carbon-language/carbon-lang/pull/731)
 -   [#818: Constraints for generics (generics details 3)](https://github.com/carbon-language/carbon-lang/pull/818)
 -   [#931: Generic impls access (details 4)](https://github.com/carbon-language/carbon-lang/pull/931)
+-   [#920: Generic parameterized impls (details 5)](https://github.com/carbon-language/carbon-lang/pull/920)

+ 22 - 4
docs/design/generics/overview.md

@@ -34,6 +34,7 @@ pointers to other design documents that dive deeper into individual topics.
         -   [Associated types](#associated-types)
         -   [Parameterized interfaces](#parameterized-interfaces)
     -   [Constraints](#constraints)
+    -   [Parameterized impls](#parameterized-impls)
 -   [Future work](#future-work)
 -   [References](#references)
 
@@ -589,12 +590,28 @@ Constraints limit the types that the generic function can operate on, but
 increase the knowledge that may be used in the body of the function to operate
 on values of those types.
 
+### Parameterized impls
+
+Implementations can be parameterized to apply to multiple types. Those
+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.
+-   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.
+-   If there is more than one `impl` declaration with the most specific type
+    structure, pick the one listed first in the priority ordering.
+
+To ensure [coherence](goals.md#coherence), an `impl` may only be declared in a
+library defining some name from its type structure. If a library defines
+multiple implementations with the same type structure, they must be listed in
+priority order in a prioritization block.
+
 ## Future work
 
--   Implementations can be parameterized to apply to multiple types. These
-    implementations would be restricted to various conditions are true for the
-    parameters. When there are two implementations that can apply, there is a
-    specialization rule that picks the more specific one.
 -   Support functions should have a way to accept types that types that vary at
     runtime.
 -   You should have the ability to mark entities as `upcoming` or `deprecated`
@@ -611,3 +628,4 @@ on values of those types.
 -   [#524: Generics overview](https://github.com/carbon-language/carbon-lang/pull/524)
 -   [#731: Generics details 2: adapters, associated types, parameterized interfaces](https://github.com/carbon-language/carbon-lang/pull/731)
 -   [#818: Constraints for generics (generics details 3)](https://github.com/carbon-language/carbon-lang/pull/818)
+-   [#920: Generic parameterized impls (details 5)](https://github.com/carbon-language/carbon-lang/pull/920)

+ 358 - 0
proposals/p0920.md

@@ -0,0 +1,358 @@
+# Generic blanket impls (details 5)
+
+<!--
+Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+Exceptions. See /LICENSE for license information.
+SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+-->
+
+[Pull request](https://github.com/carbon-language/carbon-lang/pull/920)
+
+<!-- toc -->
+
+## Table of contents
+
+-   [Problem](#problem)
+-   [Background](#background)
+-   [Proposal](#proposal)
+-   [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals)
+-   [Alternatives considered](#alternatives-considered)
+    -   [Different syntax](#different-syntax)
+        -   [Inconsistent syntax](#inconsistent-syntax)
+        -   [Members defined out-of-line](#members-defined-out-of-line)
+        -   [Context sensitivity](#context-sensitivity)
+    -   [Orphan rule could consider interface requirements in blanket impls](#orphan-rule-could-consider-interface-requirements-in-blanket-impls)
+    -   [Child trumps parent rule](#child-trumps-parent-rule)
+    -   [Impls could limit specialization](#impls-could-limit-specialization)
+    -   [Libraries for orphan impls](#libraries-for-orphan-impls)
+    -   [Different traversal order for overlap rule](#different-traversal-order-for-overlap-rule)
+    -   [Intersection rule in addition to prioritization](#intersection-rule-in-addition-to-prioritization)
+    -   [Name lookup into conditional impls](#name-lookup-into-conditional-impls)
+    -   [Automatic implicit conversions](#automatic-implicit-conversions)
+
+<!-- tocstop -->
+
+## Problem
+
+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:
+
+-   Declaring 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" impls where an interface is implemented for all types that
+    implement another interface, or some other criteria beyond being a specific
+    type.
+-   "Wildcard" impls where a family of interfaces are implemented for single
+    type.
+
+In addition to a syntax for defining parameterized impls, we need rules for
+coherence:
+
+-   Orphan rules ensure that an impl is imported in any code that might use it.
+-   Overlap rules pick a specific impl when more than one impl declaration
+    matches a specific query about whether a type implements an interface.
+
+## Background
+
+Rust supports parameterized impls, but has not stabilized the specialization
+feature that resolves multiple impls applying.
+
+## Proposal
+
+This is a proposal to add
+[a "parameterized impls" section to the generics design details](/docs/design/generics/details.md#parameterized-impls).
+
+## Rationale based on Carbon's goals
+
+Much of this rationale for generics was captured in the
+[Generics goals proposal](https://github.com/carbon-language/carbon-lang/pull/24).
+This proposal is particularly concerned with achieving the goal of making
+generics [coherent](/docs/design/generics/goals.md#coherence).
+
+## Alternatives considered
+
+### Different syntax
+
+This conditional interface implementation syntax was decided in
+[issue #575](https://github.com/carbon-language/carbon-lang/issues/575), and
+clarified in
+[a Discord discussion in #syntax](https://discord.com/channels/655572317891461132/709488742942900284/903036676371259432).
+A large part of what was decided in that issue is the syntax used for
+non-parameterized impls, and incorporated into the Carbon generics design in
+[proposal #553: Generics details part 1](https://github.com/carbon-language/carbon-lang/pull/553).
+
+#### Inconsistent syntax
+
+Our primary concern with the syntax for parameterized impls was that it be
+consistent with non-parameterized impls, and consistent between lexically inline
+and out-of-line definitions. Consistency was the basis for rejecting a number of
+conditional conformance options:
+
+-   The `extend` syntax inline in a class definition to establish a more
+    specific `Self` type for conditional conformance choice was eliminated along
+    with [the `extend` syntax for external impls](p0553.md#extend-blocks).
+-   Consistency excluded using deduced arguments in square brackets after the
+    `impl` keyword, and an `if` or `where` clause to add constraints:
+
+    ```
+    class FixedArray(T:! Type, N:! Int) {
+      impl as [U:! Printable] Printable if T == U {
+        // Here `T` and `U` have the same value and so you can freely
+        // cast between them. The difference is that you can call the
+        // `Print` method on values of type `U`.
+      }
+    }
+
+    class Pair(T:! Type, U:! Type) {
+      impl as Foo(T) if T == U {
+        // Can cast between `Pair(T, U)` and `Pair(T, T)` since `T == U`.
+      }
+    }
+    ```
+
+    This was too different from how those same impls would be declared
+    out-of-line.
+
+-   Another approach that was too different between inline and out-of-line, is
+    to use pattern matching instead of boolean conditions. This might look like:
+
+    ```
+    class FixedArray(T:! Type, N:! Int) {
+      @if let P:! Printable = T {
+        impl as Printable { ... }
+      }
+    }
+
+    interface Foo(T:! Type) { ... }
+    class Pair(T:! Type, U:! Type) {
+      @if let Pair(T, T) = Self {
+        impl as Foo(T) { ... }
+      }
+    }
+    ```
+
+#### Members defined out-of-line
+
+We rejected options that declared unqualified member names outside of the scope
+defining the class. This would have allowed us to consistently use an
+out-of-line syntax, but starting with something other than `external` when it
+affected the names in the class.
+
+#### Context sensitivity
+
+We rejected options for conditional conformance where the meaning of names
+declared in the class scope would have a more specific meaning in an inner
+scope. This would be much like how a language with
+[flow-sensitive typing](https://en.wikipedia.org/wiki/Flow-sensitive_typing)
+might affect the types in an inner scope as part of control flow. For example, a
+`where` clause on an `impl` declaration might add constraints to a class
+parameter only inside the scope of the `impl` it was applied to:
+
+```
+class FixedArray(T:! Type, N:! Int) {
+  impl as Printable where T is Printable {
+    // Inside this scope, `T` has type `Printable` instead of `Type`.
+  }
+}
+```
+
+These options were rejected based on the
+[low-context-sensitivity principle](/docs/project/principles/low_context_sensitivity.md).
+
+### Orphan rule could consider interface requirements in blanket impls
+
+The orphan rule states that a rule like
+
+```
+impl [T:! Interface1] T as Interface2 { ... }
+```
+
+can only live in the library that defines `Interface2`, not the library that
+defines `Interface1`. To see that the alternative is not coherent, consider this
+example where we have three libraries, with one a common library imported by the
+other two:
+
+-   Library `Common`
+
+    ```
+    interface ICommon { ... }
+    class S { ... }
+    ```
+
+-   Library `A`:
+
+    ```
+    import Common
+    interface IA { ... }
+    // Local interface only used as a constraint
+    impl [T:! IA] T as ICommon { ... }
+    // Fine: implementation of a local interface
+    impl S as IA { ... }
+    ```
+
+-   Library `B`:
+
+    ```
+    import Common
+    interface IB { ... }
+    // Local interface only used as a constraint
+    impl [T:! IB] T as ICommon { ... }
+    // Fine: implementation of a local interface
+    impl S as IB { ... }
+    ```
+
+Inside another library that imports the `Common` library:
+
+-   Does `S` implement `ICommon`? If you just import `ICommon`, no
+    implementations are visible
+-   Does the answer change if you import libraries `A` or `B`?
+-   Which implementation of `ICommon` should `S` use if you import both?
+
+We avoid these problems by requiring the use of a local type or interface
+**outside** of constraints.
+
+### Child trumps parent rule
+
+[Rust has considered a "child trumps parent" rule](http://aturon.github.io/tech/2017/02/06/specialization-and-coherence/).
+This rule would say that a library `Child` importing library `Parent` is enough
+to prioritize `impl` definitions in `Child` over `Parent` when they would
+otherwise overlap without one matching a strict subset of the other. The goal
+would be to resolve overlaps in a way that is both easy to understand and more
+often matches what implementations users actually want prioritized.
+
+One caveat of this rule is that a simple interpretation is not transitive. If we
+define three impls in three different libraries, with these type structures:
+
+-   `impl (A, ?, ?) as I`
+-   `impl (?, B, ?) as I`
+-   `impl (?, ?, C) as I`
+
+then the type structure rule would prioritize `A` over `B` over `C`. If the
+library with `C` had a dependency on the one with `A`, though, then `C` would
+have priority over `A`, and we would not be able to decide which impl to use for
+`(A, B, C)`.
+
+The fix is to change the rule to be "Child trumps parent on their intersection".
+With that rule, it would be as if there was another implementation defined on
+the intersection of `(A, ?, ?)` and `(?, ?, C)`, that is it would match
+`(A, ?, C)`, that had the highest priority and delegated to the `(?, ?, C)` impl
+for the definition of the body.
+
+Without some child trumps parent rule: If I define a new type, then all impl
+lookup for interfaces implemented by that type as `Self` will consider impl from
+my library first, at the time I define it until some other library adds an impl
+of that type as `Self`. However, adding the "child trumps parent on their
+intersection" rule removes this property.
+
+This is something we would consider in the future once we have more experience.
+Note that this rule has not yet been implemented in Rust, so we don't know how
+it works out in practice.
+
+### Impls could limit specialization
+
+This would allow greater reasoning in generic functions, requiring less
+specification. For example, there may be a blanket implementation of
+`PartiallyOrdered` provided for types that implement `Ordered`. If that blanket
+implementation could not be specialized, a generic function could rely on the
+implementations of the two interfaces being consistent with each other.
+
+This feature has been left for a future proposal. Note this feature needs to be
+limited to those cases where the implementation that limits specialization will
+be guaranteed to be a dependency of all implementations that are prioritized as
+more specific.
+
+### Libraries for orphan impls
+
+The [orphan rule](/docs/design/generics/details.md#orphan-rule) means that there
+may be no library that can define an impl with a particular type structure. Rust
+has encountered this problem already, see
+[Rust RFC #1856: "Orphan rules are stricter than we would like"](https://github.com/rust-lang/rfcs/issues/1856).
+Carbon does not currently address this problem, but we would consider future
+changes that do as long as coherence was maintained. For example, we'd consider
+a mechanism that allowed libraries to be defined that must be imported, either
+implicitly or explicitly, depending on whether specific other libraries are
+imported or linked into the project.
+
+### Different traversal order for overlap rule
+
+The [overlap rule](/docs/design/generics/details.md#overlap-rule) uses a
+depth-first traversal of the type tree to identify the first difference between
+two different type structures. We could also do another order, such as
+breadth-first, but this order is both simple and reflects some experience from
+the Rust community that the `Self` type is particularly important to prioritize.
+
+### Intersection rule in addition to prioritization
+
+Another approach for
+[selecting between impls with the same type structure](/docs/design/generics/details.md#prioritization-rule)
+is to require that there is an impl matching the intersection of any two impls
+with the same type structure, and then prioritize by containment. This approach
+is being considered for Rust, see
+[Baby Steps: "Intersection Impls"](http://smallcultfollowing.com/babysteps/blog/2016/09/24/intersection-impls/)
+and
+[Aaron Turon: "Specialization, coherence, and API evolution](http://aturon.github.io/tech/2017/02/06/specialization-and-coherence/).
+Instead of requiring that impls with the same type structure are always in the
+same prioritization block, that requirement could be only when the intersection
+isn't defined. The advantage of the prioritization block is that it can express
+everything that intersection impls can express, and generally can do so without
+having to write an exponential number of impl declarations. Prioritization
+blocks do require you to write all the impls together in a block, though, so we
+might want to support the option of allowing developers to explicitly write out
+intersections instead. This might be convenient in cases where the intersection
+is defined anyway, such as when there is already strict subset relationship
+between the impls.
+
+### Name lookup into conditional impls
+
+We considered the possibility that name lookup would fail to find the members of
+that impl when a conditional impl's condition does not apply. This would allow
+
+```
+class X(T:! Type) {
+  impl X(i32) as Foo {
+    fn F[me: Self]();
+  }
+  impl X(i64) as Bar {
+    fn F[me: Self](n: i64);
+  }
+}
+```
+
+where `X(T).F` means different things for different `T`, which could be an
+unpleasant surprise. This seemed against the Carbon philosophy of making the
+code behave consistently and predictably, and didn't have a motivating use case.
+
+### Automatic implicit conversions
+
+We considered adding two features to support operator overloading use cases,
+where the language would select the interface parameter using the type of the
+second argument without considering implicit conversions. The intent of these
+features was to, for example, make it convenient to support addition with
+anything that implicitly converted to `i32` by implementing addition with just
+`i32`.
+
+One feature was to support implementing an interface using a function with a
+different signature, as long as the compiler could automatically generate a
+wrapper that bridged the two signatures using implicit conversions. This raised
+concerns about accidentally implementing interfaces with functions that had the
+wrong signature, evolution problems when new overloads were introduced, and
+surprises and confusing errors from a single function considered to have two
+different signatures.
+
+The other feature was to allow a wildcard implementation as long as it could be
+implemented using functions that did not vary with the wildcard parameter, by
+implicitly converting to a common type. This introduced concerns about what to
+do with other members of the interface, particularly those with defaults.
+Perhaps we would allow functions as long as the interface parameters could be
+deduced from the types of the arguments? Perhaps we would require explicit
+qualification to call functions where the interface parameter couldn't be
+deduced? In particular we were worried about evolution, and allowing users to
+add functions to an interface as long as they had defaults.
+
+These problems were discussed in
+[2021-12-08 open discussion](https://docs.google.com/document/d/1cRrhRrmaUf2hVi2lFcHsYo2j0jI6t9RGZoYjWhRxp14/edit?resourcekey=0-xWHBEZ8zIqnJiB4yfBSLfA#heading=h.njnqpdcjp5h0).
+We concluded that we will later find other mechanisms to support this use case.