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

Implicit conversions for aggregates (#981)

Define initialization, assignment, comparison, and implicit conversion between data classes with different field orders and/or types.

Implements the decision made in #710 .

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
josh11b 4 лет назад
Родитель
Сommit
1534970146
3 измененных файлов с 226 добавлено и 50 удалено
  1. 62 41
      docs/design/classes.md
  2. 31 9
      docs/design/tuples.md
  3. 133 0
      proposals/p0981.md

+ 62 - 41
docs/design/classes.md

@@ -521,12 +521,10 @@ _Structural data classes_, or _struct types_, are convenient for defining
 -   as an initializer for other `class` variables or values
 -   as a type parameter to a container
 
-Note that struct types are examples of _data class types_ and are still classes,
-but we expect later to support more ways to define data class types. Also note
-that there is no `struct` keyword, "struct" is just convenient shorthand
-terminology for a structural data class.
-
-[Nominal data classes](#nominal-data-classes) are also supported by Carbon.
+Note that struct types are examples of _data class types_ and are still classes.
+The ["nominal data classes" section](#nominal-data-classes) describes another
+way to define a data class type. Also note that there is no `struct` keyword,
+"struct" is just convenient shorthand terminology for a structural data class.
 
 ### Literals
 
@@ -542,7 +540,7 @@ This produces a struct value with two fields:
 -   The first field is named "`key`" and has the value `"the"`. The type of the
     field is set to the type of the value, and so is `String`.
 -   The second field is named "`value`" and has the value `27`. The type of the
-    field is set to the type of the value, and so is `Int`.
+    field is set to the type of the value, and so is `i32`.
 
 Note: A comma `,` may optionally be included after the last field:
 
@@ -565,7 +563,7 @@ The type of `kvpair` in the last example would be represented by this
 expression:
 
 ```
-{.key: String, .value: Int}
+{.key: String, .value: i32}
 ```
 
 This syntax is intended to parallel the literal syntax, and so uses commas (`,`)
@@ -580,7 +578,7 @@ Note: Like with struct literal expressions, a comma `,` may optionally be
 included after the last field:
 
 ```
-{.key: String, .value: Int,}
+{.key: String, .value: i32,}
 ```
 
 Also note that `{}` represents both the empty struct literal and its type.
@@ -592,11 +590,15 @@ type to a struct value on the right hand side, the order of the fields does not
 have to match, just the names.
 
 ```
-var different_order: {.x: Int, .y: Int} = {.y = 2, .x = 3};
+var different_order: {.x: i32, .y: i32} = {.y = 2, .x = 3};
 Assert(different_order.x == 3);
 Assert(different_order.y == 2);
 ```
 
+Initialization and assignment occur field-by-field. The order of fields is
+determined from the target on the left side of the `=`. This rule matches what
+we expect for classes with encapsulation more generally.
+
 **Open question:** What operations and in what order happen for assignment and
 initialization?
 
@@ -608,10 +610,6 @@ initialization?
     approach supports types that can't be moved or copied, such as mutex.
 -   Perhaps some operations are _not_ ordered with respect to each other?
 
-When initializing or assigning, the order of fields is determined from the
-target on the left side of the `=`. This rule matches what we expect for classes
-with encapsulation more generally.
-
 ### Operations performed field-wise
 
 Generally speaking, the operations that are available on a data class value,
@@ -619,38 +617,61 @@ such as a value with a struct type, are dependent on those operations being
 available for all the types of the fields.
 
 For example, two values of the same data class type may be compared for equality
-if equality is supported for every member of the type:
+or inequality if equality is supported for every member of the type:
 
 ```
 var p: auto = {.x = 2, .y = 3};
 Assert(p == {.x = 2, .y = 3});
 Assert(p != {.x = 2, .y = 4});
-```
-
-Similarly, a data class has an unformed state if all its members do. Treatment
-of unformed state follows
-[#257](https://github.com/carbon-language/carbon-lang/pull/257).
-
-`==` and `!=` are defined on a data class type if all its field types support
-it:
-
-```
 Assert({.x = 2, .y = 4} != {.x = 5, .y = 3});
 ```
 
-**Open question:** Which other comparisons are supported is the subject of
-[question-for-leads issue #710](https://github.com/carbon-language/carbon-lang/issues/710).
-
-```
-// Illegal
-Assert({.x = 2, .y = 3} != {.y = 4, .x = 5});
-```
+Equality and inequality comparisons are also allowed between different data
+class types when:
+
+-   At least one is a struct type.
+-   They have the same set of field names, though the order may be different.
+-   Equality comparison is defined between the pairs of member types with the
+    same field names.
+
+For example, since
+[comparison between `i32` and `u32` is defined](/proposals/p0702.md#built-in-comparisons-and-implicit-conversions),
+equality comparison between values of types `{.x: i32, .y: i32}` and
+`{.y: u32, .x: u32}` is as well. Equality and inequality comparisons compare
+fields using the field order of the left-hand operand and stop once the outcome
+of the comparison is determined. However, the comparison order and
+short-circuiting are generally expected to affect only the performance
+characteristics of the comparison and not its meaning.
+
+Ordering comparisons, such as `<` and `<=`, use the order of the fields to do a
+[lexicographical comparison](https://en.wikipedia.org/wiki/Lexicographic_order).
+The argument types must have a matching order of the field names. Otherwise, the
+restrictions on ordering comparisons between different data class types are
+analogous to equality comparisons:
+
+-   At least one is a struct type.
+-   Ordering comparison is defined between the pairs of member types with the
+    same field names.
+
+Implicit conversion from a struct type to a data class type is allowed when the
+set of field names is the same and implicit conversion is defined between the
+pairs of member types with the same field names. So calling a function
+effectively performs an assignment from each of the caller's arguments to the
+function's parameters, and will be valid when those assignments are all valid.
+
+A data class has an unformed state if all its members do. Treatment of unformed
+state follows proposal
+[#257](https://github.com/carbon-language/carbon-lang/pull/257).
 
 Destruction is performed field-wise in reverse order.
 
 Extending user-defined operations on the fields to an operation on an entire
 data class is [future work](#interfaces-implemented-for-data-classes).
 
+**References:** The rules for assignment, comparison, and implicit conversion
+for argument passing were decided in
+[question-for-leads issue #710](https://github.com/carbon-language/carbon-lang/issues/710).
+
 ## Nominal class types
 
 The declarations for nominal class types will have:
@@ -1546,7 +1567,7 @@ declaration with all of the optional parameters in an option struct:
 
 ```
 fn SortIntVector(
-    v: Vector(Int)*,
+    v: Vector(i32)*,
     options: {.stable: Bool = false,
               .descending: Bool = false} = {}) {
   // Code using `options.stable` and `options.descending`.
@@ -1571,7 +1592,7 @@ We might instead support destructuring struct patterns with defaults:
 
 ```
 fn SortIntVector(
-    v: Vector(Int)*,
+    v: Vector(i32)*,
     {stable: Bool = false, descending: Bool = false}) {
   // Code using `stable` and `descending`.
 }
@@ -1586,16 +1607,16 @@ and allows some other use cases such as destructuring return values.
 We might support destructuring directly:
 
 ```
-var {key: String, value: Int} = ReturnKeyValue();
+var {key: String, value: i32} = ReturnKeyValue();
 ```
 
 or by way of a mechanism that converts a struct into a tuple:
 
 ```
-var (key: String, value: Int) =
+var (key: String, value: i32) =
     ReturnKeyValue().extract(.key, .value);
 // or maybe:
-var (key: String, value: Int) =
+var (key: String, value: i32) =
     ReturnKeyValue()[(.key, .value)];
 ```
 
@@ -1876,7 +1897,7 @@ interface ConstructWidgetFrom {
   fn Construct(Self) -> Widget;
 }
 
-external impl {.kind: WidgetKind, .size: Int}
+external impl {.kind: WidgetKind, .size: i32}
     as ConstructWidgetFrom { ... }
 ```
 
@@ -1891,9 +1912,9 @@ 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
 the criteria.
 
-It is an open question how define implementations for binary operators. For
-example, if `Int` is comparable to `Float32`, then `{.x = 3, .y = 2.72}` should
-be comparable to `{.x = 3.14, .y = 2}`. The trick is how to declare the criteria
+It is an open question how to define implementations for binary operators. For
+example, if `i32` is comparable to `f64`, then `{.x = 3, .y = 2.72}` should be
+comparable to `{.x = 3.14, .y = 2}`. The trick is how to declare the criteria
 that "`T` is comparable to `U` if they have the same field names in the same
 order, and for every field `x`, the type of `T.x` implements `ComparableTo` for
 the type of `U.x`."

+ 31 - 9
docs/design/tuples.md

@@ -13,6 +13,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 -   [TODO](#todo)
 -   [Overview](#overview)
     -   [Indices as compile-time constants](#indices-as-compile-time-constants)
+    -   [Operations performed field-wise](#operations-performed-field-wise)
 -   [Open questions](#open-questions)
     -   [Slicing ranges](#slicing-ranges)
     -   [Single-value tuples](#single-value-tuples)
@@ -34,24 +35,24 @@ The primary composite type involves simple aggregation of other types as a tuple
 (called a "product type" in formal type theory):
 
 ```
-fn DoubleBoth(Int x, Int y) -> (Int, Int) {
+fn DoubleBoth(x: i32, y: i32) -> (i32, i32) {
   return (2 * x, 2 * y);
 }
 ```
 
 This function returns a tuple of two integers represented by the type
-`(Int, Int)`. The expression to return it uses a special tuple syntax to build a
+`(i32, i32)`. The expression to return it uses a special tuple syntax to build a
 tuple within an expression: `(<expression>, <expression>)`. This is actually the
 same syntax in both cases. The return type is a tuple expression, and the first
-and second elements are expressions referring to the `Int` type. The only
+and second elements are expressions referring to the `i32` type. The only
 difference is the type of these expressions. Both are tuples, but one is a tuple
 of types.
 
 Element access uses subscript syntax:
 
 ```
-fn Bar(Int x, Int y) -> Int {
-  var (Int, Int) t = (x, y);
+fn Bar(x: i32, y: i32) -> i32 {
+  var t: (i32, i32) = (x, y);
   return t[0] + t[1];
 }
 ```
@@ -59,9 +60,9 @@ fn Bar(Int x, Int y) -> Int {
 Tuples also support multiple indices and slicing to restructure tuple elements:
 
 ```
-fn Baz(Int x, Int y, Int z) -> (Int, Int) {
-  var (Int, Int, Int) t1 = (x, y, z);
-  var (Int, Int, Int) t2 = t1[(2, 1, 0)];
+fn Baz(x: i32, y: i32, z: i32) -> (i32, i32) {
+  var t1: (i32, i32, i32) = (x, y, z);
+  var t2: (i32, i32, i32) = t1[(2, 1, 0)];
   return t2[0 .. 2];
 }
 ```
@@ -76,6 +77,27 @@ compile-time constants. Without that, run-time indexing would need to suddenly
 switch to a variant-style return type to handle heterogeneous tuples. This would
 both be surprising and complex for little or no value.
 
+### Operations performed field-wise
+
+Like some other aggregate data types like
+[struct types](classes.md#struct-types), there are some operations are defined
+for tuples field-wise:
+
+-   initialization
+-   assignment
+-   equality and inequality comparison
+-   ordered comparison
+-   implicit conversion for argument passing
+-   destruction
+
+For binary operations, the two tuples must have the same number of components
+and the operation must be defined for the corresponding component types of the
+two tuples.
+
+**References:** The rules for assignment, comparison, and implicit conversion
+for argument passing were decided in
+[question-for-leads issue #710](https://github.com/carbon-language/carbon-lang/issues/710).
+
 ## Open questions
 
 ### Slicing ranges
@@ -120,5 +142,5 @@ C++ variadics.
 
 ### Type vs tuple of types
 
-Is `(Int, Int)` a type, a tuple of types, or is there even a difference between
+Is `(i32, i32)` a type, a tuple of types, or is there even a difference between
 the two? Is different syntax needed for these cases?

+ 133 - 0
proposals/p0981.md

@@ -0,0 +1,133 @@
+# Implicit conversions for aggregates
+
+<!--
+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/981)
+
+<!-- toc -->
+
+## Table of contents
+
+-   [Problem](#problem)
+-   [Background](#background)
+-   [Proposal](#proposal)
+-   [Details](#details)
+-   [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals)
+-   [Alternatives considered](#alternatives-considered)
+    -   [Field order is not significant](#field-order-is-not-significant)
+    -   [Different field orders are incompatible](#different-field-orders-are-incompatible)
+    -   [Explicit instead of implicit conversions](#explicit-instead-of-implicit-conversions)
+
+<!-- tocstop -->
+
+## Problem
+
+What operations are supported by default between two data classes with the same
+fields in different orders? What implicit conversions are allowed between
+aggregates, such as arrays, tuples, and data classes?
+
+## Background
+
+-   Comparison operators more generally were added to Carbon in
+    [proposal #702: Comparison operators](https://github.com/carbon-language/carbon-lang/pull/702)
+-   An initial take on these questions was originally in proposal
+    [proposal #561: Basic classes: use cases, struct literals, struct types, and future work](https://github.com/carbon-language/carbon-lang/pull/561)
+    but postponed until we had agreement on the right approach.
+-   The discussion that eventually reached an agreement took place in
+    [question-for-leads issue #710: Default comparison for data classes](https://github.com/carbon-language/carbon-lang/issues/710)
+
+## Proposal
+
+We propose that we should permissively allow operations between data classes and
+other aggregates with different field orders and types where we can. Field order
+in data classes is salient, but mostly determines the order that operations are
+performed. The only case where different field orders will forbid an operation
+is with ordering comparisons, where the field order determines the answer
+returned, not just the order of execution.
+
+## Details
+
+Changes have been made to:
+
+-   [docs/design/classes.md](/docs/design/classes.md),
+-   [docs/design/tuples.md](/docs/design/tuples.md).
+
+These changes are intended to apply to all aggregate types, including arrays.
+
+## Rationale based on Carbon's goals
+
+This proposal advances Carbon's goals:
+
+-   [Software and language evolution](/docs/project/goals.md#software-and-language-evolution):
+    This proposal allows changes to field order and type to be made
+    incrementally.
+-   [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write):
+    This proposal provides useful and expected facilities for data classes and
+    other aggregate types by default.
+
+## Alternatives considered
+
+Alternatives were considered in:
+
+-   [question-for-leads issue #710: Default comparison for data classes](https://github.com/carbon-language/carbon-lang/issues/710)
+-   [open discussion on 2021-11-29](https://docs.google.com/document/d/1cRrhRrmaUf2hVi2lFcHsYo2j0jI6t9RGZoYjWhRxp14/edit?resourcekey=0-xWHBEZ8zIqnJiB4yfBSLfA#heading=h.6komy889g3hc)
+
+### Field order is not significant
+
+We considered not making field order significant in struct types, making them
+into unordered collections of named fields. This view is consistent with order
+not being significant in initializers in prior class proposal
+[#561](https://github.com/carbon-language/carbon-lang/pull/561). This had a
+number of consequences:
+
+-   Users would not have control over the order of operations performed
+    field-by-field, such as comparisons, unless they use a nominal class type
+    and implement those operations explicitly.
+-   Order comparison operators, like `<` and `<=`, would not be supported. We
+    considered defining an unspecified-but-fixed ordering, for use in things
+    like binary search, accessed in some other way than the ordinary comparison
+    operators.
+
+Ultimately, we decided that field order was a salient property of struct types,
+at least determining the layout of the data in memory, and we should use it to
+determine the order of operations and how to compare lexicographically.
+
+**Reference:** See
+[this comment by `geoffromer` on #710](https://github.com/carbon-language/carbon-lang/issues/710#issuecomment-893866801).
+
+### Different field orders are incompatible
+
+Rather than picking the left-hand argument's field order when the orders were
+different, we considered requiring the field order to match when performing all
+comparisons, including equality comparisons. An explicit conversion to a common
+type would be required to perform a comparison when the field orders did not
+match.
+
+The current proposal is more convenient for users, and has the property that
+executing `a = b` results in the condition `a == b` being true, even when `a`
+and `b` have different field orders. We also believed that operations like
+assignment between structs with different field orders would be more efficiently
+implemented using field-by-field assignment rather than a conversion to the
+left-hand type followed by assignment, and so it was natural to support the
+former directly.
+
+### Explicit instead of implicit conversions
+
+We expected a lot of code trying to pass values between functions using
+different field orders would use destructuring instead of direct conversion. As
+a result, we thought it might be safer to require explicit conversions to avoid
+silently converting, say, 10,000 `i8` values to `i64`.
+
+However, there were some important use cases for performing the conversion
+implicitly, such as `(1, 1)` converting to an `(i8, i8)` value. We did not want
+rules that distinguish this case from other implicit conversions, for
+simplicity. Similarly, we wanted the set of conversions to be consistent across
+aggregate types, including tuples, arrays, and data classes. We can amend these
+rules in the future to address cases that are surprising to users in practice.
+
+**Reference:** See
+[this comment by `chandlerc` on #710](https://github.com/carbon-language/carbon-lang/issues/710#issuecomment-983579560).