Просмотр исходного кода

Clarify rules around `Self` and `.Self` (#2107)

A number of smaller changes grouped together in one proposal:

-   Make `Self` a keyword.
-   Clarify that `Self` refers to the current type in a base class and in impl declarations.
-   Clarify when `.Self` is legal, and what type it has.
-   Also specify that `where` is not an associative operator.
josh11b 3 лет назад
Родитель
Сommit
e4595deb4b
4 измененных файлов с 275 добавлено и 10 удалено
  1. 56 4
      docs/design/classes.md
  2. 49 6
      docs/design/generics/details.md
  3. 1 0
      docs/design/lexical_conventions/words.md
  4. 169 0
      proposals/p2107.md

+ 56 - 4
docs/design/classes.md

@@ -31,7 +31,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
     -   [Operations performed field-wise](#operations-performed-field-wise)
 -   [Nominal class types](#nominal-class-types)
     -   [Forward declaration](#forward-declaration)
-    -   [Self](#self)
+    -   [`Self`](#self)
     -   [Construction](#construction)
         -   [Assignment](#assignment)
     -   [Member functions](#member-functions)
@@ -46,6 +46,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
         -   [Virtual methods](#virtual-methods)
             -   [Virtual override keywords](#virtual-override-keywords)
         -   [Subtyping](#subtyping)
+        -   [`Self` refers to the current type](#self-refers-to-the-current-type)
         -   [Constructors](#constructors)
             -   [Partial facet](#partial-facet)
             -   [Usage](#usage)
@@ -730,7 +731,7 @@ class GraphNode {
 **Open question:** What is specifically allowed and forbidden with an incomplete
 type has not yet been decided.
 
-### Self
+### `Self`
 
 A `class` definition may provisionally include references to its own name in
 limited ways. These limitations arise from the type not being complete until the
@@ -743,8 +744,8 @@ class IntListNode {
 }
 ```
 
-An equivalent definition of `IntListNode`, since `Self` is an alias for the
-current type, is:
+An equivalent definition of `IntListNode`, since the `Self` keyword is an alias
+for the current type, is:
 
 ```
 class IntListNode {
@@ -759,6 +760,7 @@ class IntListNode {
 class IntList {
   class IntListNode {
     var data: i32;
+    // `Self` is `IntListNode`, not `IntList`.
     var next: Self*;
   }
   var first: IntListNode*;
@@ -1199,6 +1201,55 @@ abstract class ExtensibleBase { ... }
 class ExactlyExtensible extends ExtensibleBase { ... }
 ```
 
+#### `Self` refers to the current type
+
+Note that `Self` in a class definition means "the current type being defined"
+not "the type implementing this method." To implement a method in a derived
+class that uses `Self` in the declaration in the base class, only the type of
+`me` should change:
+
+```
+base class B1 {
+  virtual fn F[me: Self](x: Self) -> Self;
+  // Means exactly the same thing as:
+  //   virtual fn F[me: B1](x: B1) -> B1;
+}
+
+class D1 extends B1 {
+  // ❌ Illegal:
+  //   impl fn F[me: Self](x: Self) -> Self;
+  // since that would mean the same thing as:
+  //   impl fn F[me: Self](x: D1) -> D1;
+  // and `D1` is a different type than `B1`.
+
+  // ✅ Allowed: Parameter and return types
+  //  of `F` match declaration in `B1`.
+  impl fn F[me: Self](x: B1) -> B1;
+  // Or: impl fn F[me: D1](x: B1) -> B1;
+}
+```
+
+The exception is when there is a [subtyping relationship](#subtyping) such that
+it would be legal for a caller using the base classes signature to actually be
+calling the derived implementation, as in:
+
+```
+base class B2 {
+  virtual fn Clone[me: Self]() -> Self*;
+  // Means exactly the same thing as:
+  //   virtual fn Clone[me: B2]() -> B2*;
+}
+
+class D2 extends B2 {
+  // ✅ Allowed
+  impl fn Clone[me: Self]() -> Self*;
+  // Means the same thing as:
+  //   impl fn Clone[me: D2]() -> D2*;
+  // which is allowed since `D2*` is a
+  // subtype of `B2*`.
+}
+```
+
 #### Constructors
 
 Like for classes without inheritance, constructors for a derived class are
@@ -2057,3 +2108,4 @@ the type of `U.x`."
 -   [#777: Inheritance](https://github.com/carbon-language/carbon-lang/pull/777)
 -   [#981: Implicit conversions for aggregates](https://github.com/carbon-language/carbon-lang/pull/981)
 -   [#1154: Destructors](https://github.com/carbon-language/carbon-lang/pull/1154)
+-   [#2107: Clarify rules around `Self` and `.Self`](https://github.com/carbon-language/carbon-lang/pull/2107)

+ 49 - 6
docs/design/generics/details.md

@@ -223,7 +223,8 @@ have two methods:
 
 ```
 interface Vector {
-  // Here `Self` means "the type implementing this interface".
+  // Here the `Self` keyword means
+  // "the type implementing this interface".
   fn Add[me: Self](b: Self) -> Self;
   fn Scale[me: Self](v: f64) -> Self;
 }
@@ -257,7 +258,8 @@ class Point {
   var x: f64;
   var y: f64;
   impl as Vector {
-    // In this scope, "Self" is an alias for "Point".
+    // In this scope, the `Self` keyword is an
+    // alias for `Point`.
     fn Add[me: Self](b: Self) -> Self {
       return {.x = a.x + b.x, .y = a.y + b.y};
     }
@@ -364,7 +366,8 @@ class Point2 {
   var y: f64;
 
   external impl as Vector {
-    // In this scope, `Self` is an alias for `Point2`.
+    // In this scope, the `Self` keyword is an
+    // alias for `Point2`.
     fn Add[me: Self](b: Self) -> Self {
       return {.x = a.x + b.x, .y = a.y + b.y};
     }
@@ -389,7 +392,8 @@ class Point3 {
 }
 
 external impl Point3 as Vector {
-  // In this scope, `Self` is an alias for `Point3`.
+  // In this scope, the `Self` keyword is an
+  // alias for `Point3`.
   fn Add[me: Self](b: Self) -> Self {
     return {.x = a.x + b.x, .y = a.y + b.y};
   }
@@ -2065,6 +2069,18 @@ 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:
+
+```
+external impl VeryLongTypeName as Add
+    // `Self` here means `VeryLongTypeName`
+    where .Result == Self {
+  ...
+}
+```
+
 **Alternatives considered:** See
 [other syntax options considered in #731 for specifying associated types](/proposals/p0731.md#syntax-for-associated-constants).
 In particular, it was deemed that
@@ -2269,8 +2285,9 @@ class Complex {
   var imag: f64;
   // Can implement this interface more than once
   // as long as it has different arguments.
-  impl as EquatableWith(Complex) { ... }
   impl as EquatableWith(f64) { ... }
+  // Same as: impl as EquatableWith(Complex) { ... }
+  impl as EquatableWith(Self) { ... }
 }
 ```
 
@@ -2416,7 +2433,9 @@ 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.
+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
@@ -2794,6 +2813,29 @@ constraint ContainerIsSlice {
 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
+    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.
+
+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 is
+           (InterfaceB where .B == .Self)](x: T);
+```
+
 #### Parameterized type implements interface
 
 There are times when a function will pass a generic type parameter of the
@@ -5643,3 +5685,4 @@ parameter, as opposed to an associated type, as in `N:! u32 where ___ >= 2`.
 -   [#1144: Generic details 11: operator overloading](https://github.com/carbon-language/carbon-lang/pull/1144)
 -   [#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)

+ 1 - 0
docs/design/lexical_conventions/words.md

@@ -76,6 +76,7 @@ The following words are interpreted as keywords:
 -   `protected`
 -   `return`
 -   `returned`
+-   `Self`
 -   `then`
 -   `var`
 -   `virtual`

+ 169 - 0
proposals/p2107.md

@@ -0,0 +1,169 @@
+# Clarify rules around `Self` and `.Self`
+
+<!--
+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/2107)
+
+<!-- toc -->
+
+## Table of contents
+
+-   [Abstract](#abstract)
+-   [Problem](#problem)
+-   [Background](#background)
+-   [Proposal](#proposal)
+-   [Details](#details)
+-   [Rationale](#rationale)
+-   [Alternatives considered](#alternatives-considered)
+    -   [`Self` not a keyword](#self-not-a-keyword)
+    -   [Make `Self` a member of all types](#make-self-a-member-of-all-types)
+    -   [`where` operator could be associative](#where-operator-could-be-associative)
+
+<!-- tocstop -->
+
+## Abstract
+
+A number of smaller changes grouped together in one proposal:
+
+-   Make `Self` a keyword.
+-   Clarify that `Self` refers to the current type in a base class and in impl
+    declarations.
+-   Clarify when `.Self` is legal, and what type it has.
+-   Also specify that `where` is not an associative operator.
+
+## Problem
+
+There were a number of gaps found in the design when @zygoloid went to implement
+these features in [the explorer codebase](/explorer), for example
+[#1311: Basic support for `.Self` within `:!` bindings and `where` expressions](https://github.com/carbon-language/carbon-lang/pull/1311).
+
+## Background
+
+`Self` was introduced for interfaces and implementations in
+[#524: Generics overview](https://github.com/carbon-language/carbon-lang/pull/524)
+and
+[#553: Generics details part 1](https://github.com/carbon-language/carbon-lang/pull/553).
+`Self` was introduced for class types and methods in
+[#494: Method syntax](https://github.com/carbon-language/carbon-lang/issues/494)
+and
+[#722: Nominal classes and methods](https://github.com/carbon-language/carbon-lang/pull/722).
+
+Constraints using `where` and `.Self` were introduced in
+[#818: Constraints for generics (generics details 3)](https://github.com/carbon-language/carbon-lang/pull/818).
+The use of `where` to set associated types was introduced in
+[#1013: Generics: Set associated constants using `where` constraints](https://github.com/carbon-language/carbon-lang/pull/1013).
+
+The type of `.Self` and where it would be introduced grammatically was discussed
+[#generics channel on Discord on 2022-06-07](https://discord.com/channels/655572317891461132/708431657849585705/1013904969335836732).
+
+## Proposal
+
+This proposal implements a number of changes and clarifications about the use of
+`Self` and `.Self` with generics:
+
+-   `Self` is now a keyword, and may not be used as an identifier even in
+    contexts where `Self` has no meaning. If `Self` is used in a C++ API, Carbon
+    will use the same mechanism for interop as other Carbon keywords.
+-   Clarify that `Self` always refers to the current type, even for virtual
+    functions implemented in a derived class, not the type implementing the
+    method.
+-   `Self` in an `impl` declaration may be used after the `as` to refer to the
+    type being implemented. This could be the type named before `as` when
+    specified, otherwise it is the enclosing type.
+-   Clarify that `.Self` is legal after `:!` and `where`, as long as it only
+    refers to a single type variable.
+-   Specify the type of `.Self` as `Type` after `:!`, or `MyConstraint` after
+    `MyConstraint where`.
+
+In addition, this proposal specifies that `where` is not an associative
+operator.
+
+## Details
+
+`Self` was added as a keyword to
+[`docs/design/lexical_conventions/words.md`](/docs/design/lexical_conventions/words.md).
+The other rules were added to
+[`docs/design/classes.md`](/docs/design/classes.md) and
+[`docs/design/generics/details.md`](/docs/design/generics/details.md).
+
+## Rationale
+
+This proposal fills in gaps with an aim to make things consistent and simplify
+by having fewer rules where possible. For example, the rule saying that it is an
+error if `.Self` could mean two different things is consistent with other name
+lookup rules, such as those from
+[#989: Member access expressions](https://github.com/carbon-language/carbon-lang/pull/989)
+and
+[#2070: Always `==` not `=` in `where` clauses](https://github.com/carbon-language/carbon-lang/pull/2070).
+Simplicity benefits Carbon's
+[language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem)
+and consistency leads to
+[code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write).
+
+## Alternatives considered
+
+### `Self` not a keyword
+
+An alternative considered was forbidding identifiers to be equal to `Self`. The
+big concern with that approach is that we would need some other way to
+interoperate with C++ code, particularly classes, that had a `Self` member. If
+we were adding `Self` to the language later as part of evolution, we would make
+it a keyword. That would allow us to use the same evolution strategy as other
+keywords -- we would automatically update the code to change existing uses of
+`Self` to the raw identifier syntax.
+
+Note that at this time
+[no raw identifier syntax has been approved](https://github.com/carbon-language/carbon-lang/pull/93),
+but Rust uses a `r#` prefix. If Carbon used the same syntax, existing uses of
+`Self` would be changed to `r#Self`, and so `r#Self` should still be a legal
+identifier.
+
+### Make `Self` a member of all types
+
+We considered making `Self` a member of all types. From this uses of `Self` and
+`.Self` would follow naturally. It would have other consequences as well:
+
+-   `T.Self == T` for all types `T`.
+-   `x.Self`, where `x` is a non-type value with type `T`, would be `T`. This is
+    because under the normal member-access rules, since `Self` is not a member
+    of `x`, it would look in `T` and find `T.Self`.
+
+This raised the question of whether `Self` is a member of type-of-types like
+`Type`. That would seem to introduce an ambiguity for `i32.Self`. Furthermore,
+using `x.Self` to get the type of `x` seemed tricky, it would be better to have
+something that used the word "type" to do that.
+
+Since [`Self` is a keyword](#self-not-a-keyword), we don't need to make it
+follow the normal member-access rules. So we instead only define what it means
+in places where we have a use case.
+
+This was discussed
+[on 2022-08-29 in the #typesystem channel in Discord](https://discord.com/channels/655572317891461132/708431657849585705/1013904969335836732).
+
+### `where` operator could be associative
+
+We considered making the `where` operator associative, since an expression like
+
+```
+Interface1 where .AssocType1 is Interface2 where .AssocType2 == i32
+```
+
+would more usefully be interpreted as:
+
+```
+Interface1 where .AssocType1 is (Interface2 where .AssocType2 == i32)
+```
+
+than the alternative. However, this is expected to be a rare case and so it
+seemed much friendlier to humans to require parentheses or a separate named
+constraint declaration. This way they can easily visually disambiguate how it is
+interpreted without having to remember a rule that won't commonly be relevant.
+
+This was discussed in
+[the #syntax channel on Discord on 2022-05-27](https://discord.com/channels/655572317891461132/709488742942900284/979869282903130153)
+and
+[the weekly sync meeting on 2022-06-01](https://docs.google.com/document/d/1dwS2sJ8tsN3LwxqmZSv9OvqutYhP71dK9Dmr1IXQFTs/edit?resourcekey=0-NxBWgL9h05yD2GOR3wUisg#heading=h.qarzfirrcrgf).