From 7cf53a2a70150b5305d2239375ea848ba18390d4 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 28 Oct 2022 16:25:23 -0700 Subject: [PATCH 01/25] Filling out template with PR 2360 --- proposals/p2360.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 proposals/p2360.md diff --git a/proposals/p2360.md b/proposals/p2360.md new file mode 100644 index 0000000000000..c2632cf7606e1 --- /dev/null +++ b/proposals/p2360.md @@ -0,0 +1,70 @@ +# Types are values of type Type + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/2360) + + + +## Table of contents + +- [Abstract](#abstract) +- [Problem](#problem) +- [Background](#background) +- [Proposal](#proposal) +- [Details](#details) +- [Rationale](#rationale) +- [Alternatives considered](#alternatives-considered) + + + +## Abstract + +TODO: Describe, in a succinct paragraph, the gist of this document. This +paragraph should be reproduced verbatim in the PR summary. + +## Problem + +TODO: What problem are you trying to solve? How important is that problem? Who +is impacted by it? + +## Background + +TODO: Is there any background that readers should consider to fully understand +this problem and your approach to solving it? + +## Proposal + +TODO: Briefly and at a high level, how do you propose to solve the problem? Why +will that in fact solve it? + +## Details + +TODO: Fully explain the details of the proposed solution. + +## Rationale + +TODO: How does this proposal effectively advance Carbon's goals? Rather than +re-stating the full motivation, this should connect that motivation back to +Carbon's stated goals and principles. This may evolve during review. Use links +to appropriate sections of [`/docs/project/goals.md`](/docs/project/goals.md), +and/or to documents in [`/docs/project/principles`](/docs/project/principles). +For example: + +- [Community and culture](/docs/project/goals.md#community-and-culture) +- [Language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem) +- [Performance-critical software](/docs/project/goals.md#performance-critical-software) +- [Software and language evolution](/docs/project/goals.md#software-and-language-evolution) +- [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write) +- [Practical safety and testing mechanisms](/docs/project/goals.md#practical-safety-and-testing-mechanisms) +- [Fast and scalable development](/docs/project/goals.md#fast-and-scalable-development) +- [Modern OS platforms, hardware architectures, and environments](/docs/project/goals.md#modern-os-platforms-hardware-architectures-and-environments) +- [Interoperability with and migration from existing C++ code](/docs/project/goals.md#interoperability-with-and-migration-from-existing-c-code) + +## Alternatives considered + +TODO: What alternative solutions have you considered? From a7a6ad18758b619377f9dd52b2ccdbfb5eae70a3 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 28 Oct 2022 17:58:58 -0700 Subject: [PATCH 02/25] Initial proposal draft. --- proposals/p2360.md | 269 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 245 insertions(+), 24 deletions(-) diff --git a/proposals/p2360.md b/proposals/p2360.md index c2632cf7606e1..0b3d676d406d1 100644 --- a/proposals/p2360.md +++ b/proposals/p2360.md @@ -1,4 +1,4 @@ -# Types are values of type Type +# Types are values of type `Type` ## Abstract -TODO: Describe, in a succinct paragraph, the gist of this document. This -paragraph should be reproduced verbatim in the PR summary. +Define a "type" to be a value of type `Type`. Contexts expecting a type perform +an implicit conversion to `Type`. Values like `()` and `(i32, i32)` and `{}` are +no longer types, but instead implicitly convert to `Type`. Values of interface +or constraint type (now called "kinds") are similarly not formally types but +implicitly convert to `Type`. ## Problem -TODO: What problem are you trying to solve? How important is that problem? Who -is impacted by it? +### Tuples behave inconsistently + +Currently, the type of `(i32, i32)` is `(Type, Type)`, and similarly the type of +`(Type, Type)` is also `(Type, Type)`. This leads to an inconsistency: for most +values, asking for the type, then the type-of-type, and so on, quickly leads to +`Type`, whose type is `Type`. But for a few values, notably tuples and empty +structs, we instead bottom out at a different value. + +This leads to implementation complexity and a lack of clarity around what is or +is not a type. It is easy to find examples where explorer misbehaves or crashes +due to getting tuple values and types mixed up. + +Moreover, this inconsistency is limited to a few built-in types. There is no way +to design a class type so it behaves like a tuple type. + +### Qualified access to constraint members is inconsistent + +Given the expression `x.(Interface.Function)`, where the function is an instance +method, the behavior depends on whether `x` is a type. + +- If `x` is not a type, but a value of type `T`, this requires + `T is Interface` and looks for `Function` in the corresponding `impl`. + Instance binding is performed. +- If `x` is a type, this requires `x` to have symbolic value phase and + requires that `x is Interface`. The result is an unbound member name. + +This dual meaning has some problematic consequences: + +- Even if `Interface.Function` is non-dependent, if `x` depends on a template + parameter then we must treat the compound member access as being dependent. + Qualification cannot be used to avoid dependence in templates. +- Worse, if `x` is a template parameter, the interpretation will differ + between instantiations, meaning that a template and a checked generic will + have a discontinuity in behavior when `x` happens to be a type. +- While it is possible to `impl Type as ...`, this rule means it is not + possible to directly invoke methods that have been implemented this way. For + example, given an `impl Type as DebugPrintable`, + `i32.(DebugPrintable.Print)()` will look for `i32 as DebugPrintable` not + `Type as DebugPrintable`. ## Background -TODO: Is there any background that readers should consider to fully understand -this problem and your approach to solving it? +- [#989: Member access expressions](/proposals/p0989.md) introduced the + current compound member access rules. +- [Issue #495](https://github.com/carbon-language/carbon-lang/issues/495) + suggested adding an implicit conversion from tuples of types to type `Type`. +- [Issue #508](https://github.com/carbon-language/carbon-lang/issues/508) + raised the question of whether we want tuples like `((), (), ())` to be + self-typed. ## Proposal -TODO: Briefly and at a high level, how do you propose to solve the problem? Why -will that in fact solve it? +Instead of treating type-like values as being types regardless of the type of +those values, we define a _type_ as being a value of type `Type`. Therefore: + +- `()` is a value of type `() as Type`, not a value of type `()` (because the + latter is not a type). + - `() as Type` is a value of type `Type`. + - Similarly, `(i32, i32)` is of type `(Type, Type) as Type`, which is of + type `Type`. + - Similarly, `{}` is of type `{} as Type`, which is of type `Type`. + - More generally, _the type of any type is `Type`_. +- Given `T:! Hashable`, `T` is formally not a type, because it is not of type + `Type`. Indeed, member lookup into `Type`, and hence into types, finds + nothing, but member lookup into `T` finds members of `Hashable`. +- Contexts in which a type is required, such as on the right hand side of a + `:` or `:!` binding or the return type of a function, perform an implicit + conversion to type `Type`. + - This means that a user-defined type can implement `ImplicitAs(Type)`, + annotated in whatever way we eventually decide to annotate functions + that are usable at compile time, and values of that type will be usable + as types, in the same way that `()` and `{}` are. +- There are no singleton type-of-types. It's still possible to have, for + example, the type of `i32` be a type whose only value is `i32`. The value + `i32` would then not formally be a type, as its type would not be `Type`, + but it could still implicitly convert to `Type`, as `()` does, if we so + desire. + - This extends to other cases: we could say that for integer literals, + which we have recently been describing as having type `IntLiteral(n)`, + there is an `impl IntLiteral(n) as ImplicitAs(Type)` which converts a + value of type `IntLiteral(n)` to the `Type` value `IntLiteral(n)`. This + would then permit `let v: 0 = 0;`. + - Such changes to `i32` or integer literals are not part of this proposal. ## Details -TODO: Fully explain the details of the proposed solution. +### Terminology -## Rationale +The changes to the language rules in this proposal are fairly small, but there +is a significant change in how we describe those rules. We now use the following +terminology: + +- A _type_ is a value of type `Type`. +- A _kind_ is a constrained or unconstrained version of `Type`, such as an + interface type or a named constraint type. We previously referred to a kind + as a type-of-type, but that is no longer accurate, as the type of any type + is now by definition `Type`. + - When one kind has a superset of the constraints of another kind, we say + it is a _subkind_. For example, `Hashable & Movable` is a subkind of + `Hashable`, which is a subkind of `Type`. +- A _facet_ is a value of a kind other than `Type`. For example, + `i32 as Hashable` is a facet. Note that facets are not types. +- We use the term _generic type_ to refer to a type or facet introduced by a + `:!` binding, such as in a generic parameter or associated constant. + - This gives rise to terms such as _generic type parameter_ and + _associated generic type_. + - It is worth noting that a generic type is not necessarily a type, as its + type may be an interface or constraint rather than `Type`. + +### Compound member access + +The syntax `x.(Y)`, where `Y` names an instance member, now always performs +instance binding. Therefore, for a suitable `DebugPrintable`: + +- `1.(DebugPrintable.Print)()` still prints `1`, as before. +- `i32.(DebugPrintable.Print)()` now prints `i32`, rather than forming a + member name. +- `1.(i32.(DebugPrintable.Print))()` is now an error, rather than printing + `1`. + +In order to get the old effect of `x.(MyType.(MyInterface.InstanceMember))()`, +one can now write `x.((MyType as MyInterface).InstanceMember)()`. This behaves +the same as the member access in: + +``` +fn F[MyType:! MyInterface, X:! Type](x: X) -> auto { + return x.(MyType.InstanceMember)(); +} +``` + +... because it is the same thing. `MyType as InstanceMember` is a facet, which +roughly corresponds to a fully-instanstiated `impl`, and lookup into a facet +finds members of that corresponding `impl`. And if `MyType` is declared as +`MyType:! MyInterface` instead of as `MyType:! Type`, then of course `MyType` +and `MyType as MyInterface` are equivalent. + +### Tuple and struct literals -TODO: How does this proposal effectively advance Carbon's goals? Rather than -re-stating the full motivation, this should connect that motivation back to -Carbon's stated goals and principles. This may evolve during review. Use links -to appropriate sections of [`/docs/project/goals.md`](/docs/project/goals.md), -and/or to documents in [`/docs/project/principles`](/docs/project/principles). -For example: +A tuple literal such as `(1, 2, 3)` produces a tuple value. Its type cannot be +written directly as a literal: the literal `(i32, i32, i32)` also produces a +tuple value, not a type value. However, the type of `(1, 2, 3)` is easy to +express: it is the result of implicitly converting `(i32, i32, i32)` to a type, +so for example `(i32, i32, i32) as Type` evaluates to the type of `(1, 2, 3)`. + +There is no conversion from a tuple type to a tuple value, because tuple types +are values of type `Type`, and the type `Type` does not implicitly convert to a +tuple type. For metaprogramming, it is likely that we will benefit from having a +mechanism to convert a tuple type into a tuple of types, but this is likely to +be possible using a variadic: + +``` +@''' +Takes a tuple of values. Returns a tuple containing the types of those values. +''' +fn TupleTypeToTupleOfTypes[Types:! Type,...](x: (Types,...)) -> auto { + return (Types,...); +} +``` + +### Name lookup into values with constrained types + +Given a generic function with a generic type parameter, uses of that parameter +will be implicitly converted to type `Type`. For example: + +``` +fn F[T:! Printable](x: T) { x.Print(); } +``` + +... is equivalent to ... + +``` +fn F[T:! Printable](x: T as Type) { x.Print(); } +``` + +As a result, we can no longer describe lookup for `x` as looking into the +type-of-type, that is, the type of the expression after `x:`, because after +implicit conversion, that expression is of type `Type`. Instead, lookup will +look into the type of `x`, which symbolically evaluates to `T`, and will look at +the value `T` to determine where else to look. Because `T` is symbolically the +name of a generic type parameter, we will look in the type of `T`, which is +`Printable`. + +## Rationale -- [Community and culture](/docs/project/goals.md#community-and-culture) - [Language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem) -- [Performance-critical software](/docs/project/goals.md#performance-critical-software) + - Making the behavior of tuples and empty structs more like other types is + easier for tooling to handle. - [Software and language evolution](/docs/project/goals.md#software-and-language-evolution) + - The transition between templates and generics is made smoother by + changing compound member access to behave consistently regardless of + whether the left-hand operand is a type. - [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write) -- [Practical safety and testing mechanisms](/docs/project/goals.md#practical-safety-and-testing-mechanisms) -- [Fast and scalable development](/docs/project/goals.md#fast-and-scalable-development) -- [Modern OS platforms, hardware architectures, and environments](/docs/project/goals.md#modern-os-platforms-hardware-architectures-and-environments) -- [Interoperability with and migration from existing C++ code](/docs/project/goals.md#interoperability-with-and-migration-from-existing-c-code) + - Bottoming out at the same type, `Type`, for all types is likely to be + easier to understand. However, having `(i32, i32)` not be the type of a + pair of integers, but instead be _implicitly convertible to_ the type of + a pair of integers, is likely to be harder to understand. It's unclear + which of these will be more dominant, but the new behavior is more + consistent. ## Alternatives considered -TODO: What alternative solutions have you considered? +### Alternative terminology + +We considered various alternatives for the terminology choices listed above. + +Instead of "kind" we considered: + +- constraint type + - Concern: a "foo type" can mean "a type that is also a foo" like "class + type" or it can mean "a type that holds foos" like "integer type". +- constrained type + - Concern: for some of us, this name seemed to better describe facets than + kinds. +- constraint + - Concern: this may sound like it should refer to a boolean predicate, + `T is C`, not `C` itself. + +Instead of "generic type" we considered: + +- archetype + - Concern: the usage here didn't match some of our intuitions based on + existing usage of this term. + - Concern: while this might fit usage within the scope of the parameter, + it fits usage from outside the scope less well. It seemed awkward to say + that a class has an archetype parameter, as opposed to our chosen + terminology of a generic type parameter. +- constrained type variable + - Concern: for some of us, the name "constrained type" seemed to better + describe kinds than facets. +- type-like + - Concern: derived terms like "generic type-like parameter" and + "associated type-like constant" are awkward. +- kinded + - Concern: derived terms like "generic kinded parameter" and "associated + kinded constant" are awkward. + +Instead of "facet" we considered: + +- impl + - Concern: facets are analogous to `impl`s but are not in direct + correspondence, because a facet can be for a constraint that involves + multiple interfaces and hence potentially multiple `impl`s, and an + `impl` can be for a constraint that involves multiple interfaces and + hence a facet can refer to only _part of_ an `impl`. +- archetype + - Concern: this seems inappropriate for non-generic facets, such as + `i32 as Hashable`. +- witness + - Concern: doesn't quite represent what we mean: a facet is more like a + pair of a type and a witness that that type implements the facet's type. From 4e47068fb2f5775c0af208e505e86d5c5c64d333 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Mon, 31 Oct 2022 14:04:43 -0700 Subject: [PATCH 03/25] Update proposals/p2360.md Co-authored-by: Chandler Carruth --- proposals/p2360.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/proposals/p2360.md b/proposals/p2360.md index 0b3d676d406d1..39e34a5fa3432 100644 --- a/proposals/p2360.md +++ b/proposals/p2360.md @@ -37,6 +37,10 @@ no longer types, but instead implicitly convert to `Type`. Values of interface or constraint type (now called "kinds") are similarly not formally types but implicitly convert to `Type`. +The practical impact to the semantics are relatively small. This is primarily +establishing more principled and clear terminology and concepts in the design +with a few more narrow fixes and improvements to the design's consistency. + ## Problem ### Tuples behave inconsistently From 3196819ce6b14ba5bc064339b2f54386cad1b2d1 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Mon, 31 Oct 2022 14:10:19 -0700 Subject: [PATCH 04/25] Update proposals/p2360.md Co-authored-by: josh11b --- proposals/p2360.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/p2360.md b/proposals/p2360.md index 39e34a5fa3432..1a5db48faab4a 100644 --- a/proposals/p2360.md +++ b/proposals/p2360.md @@ -168,7 +168,7 @@ one can now write `x.((MyType as MyInterface).InstanceMember)()`. This behaves the same as the member access in: ``` -fn F[MyType:! MyInterface, X:! Type](x: X) -> auto { +fn F(MyType:! MyInterface, x: MyType) -> auto { return x.(MyType.InstanceMember)(); } ``` From 68dcd2281e821230ed92e862eec9bc6f954af339 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Mon, 31 Oct 2022 14:10:39 -0700 Subject: [PATCH 05/25] Update proposals/p2360.md Co-authored-by: Jon Ross-Perkins --- proposals/p2360.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/p2360.md b/proposals/p2360.md index 1a5db48faab4a..b9e5ffdc04eeb 100644 --- a/proposals/p2360.md +++ b/proposals/p2360.md @@ -32,7 +32,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ## Abstract Define a "type" to be a value of type `Type`. Contexts expecting a type perform -an implicit conversion to `Type`. Values like `()` and `(i32, i32)` and `{}` are +an implicit conversion to `Type`. Values like `()`, `(i32, i32)`, and `{}` are no longer types, but instead implicitly convert to `Type`. Values of interface or constraint type (now called "kinds") are similarly not formally types but implicitly convert to `Type`. From 0f73ca8341efa10fb00323298e4a5a37e7c25c24 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Mon, 31 Oct 2022 14:32:26 -0700 Subject: [PATCH 06/25] Responses to review comments. --- proposals/p2360.md | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/proposals/p2360.md b/proposals/p2360.md index b9e5ffdc04eeb..f78046f8cff48 100644 --- a/proposals/p2360.md +++ b/proposals/p2360.md @@ -23,6 +23,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Compound member access](#compound-member-access) - [Tuple and struct literals](#tuple-and-struct-literals) - [Name lookup into values with constrained types](#name-lookup-into-values-with-constrained-types) + - [Things not being changed](#things-not-being-changed) - [Rationale](#rationale) - [Alternatives considered](#alternatives-considered) - [Alternative terminology](#alternative-terminology) @@ -37,9 +38,9 @@ no longer types, but instead implicitly convert to `Type`. Values of interface or constraint type (now called "kinds") are similarly not formally types but implicitly convert to `Type`. -The practical impact to the semantics are relatively small. This is primarily -establishing more principled and clear terminology and concepts in the design -with a few more narrow fixes and improvements to the design's consistency. +The practical impact to the language semantics is relatively small. This +proposal primarily establishes clearer, more principled terminology and design +concepts with a few narrow improvements to the design's consistency. ## Problem @@ -220,10 +221,24 @@ fn F[T:! Printable](x: T as Type) { x.Print(); } As a result, we can no longer describe lookup for `x` as looking into the type-of-type, that is, the type of the expression after `x:`, because after implicit conversion, that expression is of type `Type`. Instead, lookup will -look into the type of `x`, which symbolically evaluates to `T`, and will look at -the value `T` to determine where else to look. Because `T` is symbolically the -name of a generic type parameter, we will look in the type of `T`, which is -`Printable`. +look into the type of `x`, which symbolically evaluates to the value `T` of type +`Type`, and will look at that value `T` to determine where else to look. Because +`T` is symbolically the name of a generic type parameter, we will look in the +type of `T`, which is `Printable`. + +### Things not being changed + +This proposal does not introduce the name `Type`, which remains provisional. See +[issue #2113](https://github.com/carbon-language/carbon-lang/issues/2113) for +discussion of how things like `Type` should be named. At this time, that issue +is still open, but it might conclude that `Type` is renamed `Core.Type`, with a +keyword alias `type`. + +Under the [current design](/docs/design/generics/details.md#named-constraints), +`Type` is the kind with no requirements, and an equivalent kind can be declared +with `constraint MyVersionOfType {}`. This proposal does not change this +decision. Because such a constraint `MyVersionOfType` is the same constraint as +`Type`, a value `T:! MyVersionOfType` is a type under this proposal. ## Rationale @@ -246,7 +261,8 @@ name of a generic type parameter, we will look in the type of `T`, which is ### Alternative terminology -We considered various alternatives for the terminology choices listed above. +We considered various alternatives for the terminology choices listed above in +[open discussion](https://docs.google.com/document/d/1tEt4iM6vfcY0O0DG0uOEMIbaXcZXlNREc2ChNiEtn_w/edit#heading=h.lecxe8qywdxa). Instead of "kind" we considered: From fa708802c126ce95692710aef6adc38f9f65f815 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Mon, 31 Oct 2022 14:41:56 -0700 Subject: [PATCH 07/25] Rephrase a little more. --- proposals/p2360.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proposals/p2360.md b/proposals/p2360.md index f78046f8cff48..cec48997bfa75 100644 --- a/proposals/p2360.md +++ b/proposals/p2360.md @@ -221,10 +221,10 @@ fn F[T:! Printable](x: T as Type) { x.Print(); } As a result, we can no longer describe lookup for `x` as looking into the type-of-type, that is, the type of the expression after `x:`, because after implicit conversion, that expression is of type `Type`. Instead, lookup will -look into the type of `x`, which symbolically evaluates to the value `T` of type -`Type`, and will look at that value `T` to determine where else to look. Because -`T` is symbolically the name of a generic type parameter, we will look in the -type of `T`, which is `Printable`. +look into the type of `x`, which symbolically evaluates to the value +corresponding to `T` in type `Type`, and will look at that value to determine +where else to look. Because that value is symbolically the name of a generic +type parameter, we will look in the type of `T`, which is `Printable`. ### Things not being changed From 5ba05bc5136afa299439b14e860330bbfa12dd6b Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Mon, 31 Oct 2022 14:48:25 -0700 Subject: [PATCH 08/25] Improve terminology. --- proposals/p2360.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/proposals/p2360.md b/proposals/p2360.md index cec48997bfa75..8135dc65e092f 100644 --- a/proposals/p2360.md +++ b/proposals/p2360.md @@ -237,8 +237,9 @@ keyword alias `type`. Under the [current design](/docs/design/generics/details.md#named-constraints), `Type` is the kind with no requirements, and an equivalent kind can be declared with `constraint MyVersionOfType {}`. This proposal does not change this -decision. Because such a constraint `MyVersionOfType` is the same constraint as -`Type`, a value `T:! MyVersionOfType` is a type under this proposal. +decision. Because such a named constraint `MyVersionOfType` is equivalent to +`Type`, that is, because they are the same kind, a value `T:! MyVersionOfType` +is a type under this proposal. ## Rationale From 496b8c9e385b973b823eb552c1c25087fa742cd7 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 9 Nov 2022 17:24:41 -0800 Subject: [PATCH 09/25] Updates based on design discussion and feedback. --- proposals/p2360.md | 194 +++++++++++++++++++++++++++++++-------------- 1 file changed, 134 insertions(+), 60 deletions(-) diff --git a/proposals/p2360.md b/proposals/p2360.md index 8135dc65e092f..7474436799f49 100644 --- a/proposals/p2360.md +++ b/proposals/p2360.md @@ -1,4 +1,4 @@ -# Types are values of type `Type` +# Types are values of type `type` @@ -434,10 +436,10 @@ type parameter, we will look in the type of `T`, which is `Printable`. ### Alternative member access rules We considered rules where `T.(Interface.Member)` would either always mean -`(T as Interface).Member` or always mean `(typeof(T) as Interface).Member`, but that -did not allow aliases to work as expected. We wanted an alias to an interface method or -class function defined inside of a class to be usable as a method or class function -member of the class. +`(T as Interface).Member` or always mean `(typeof(T) as Interface).Member`, but +that did not allow aliases to work as expected. We wanted an alias to an +interface method or class function defined inside of a class to be usable as a +method or class function member of the class. ``` interface A { @@ -453,17 +455,19 @@ class B { } ``` -We want `B.G` to act like a method of `B`, like `B.H`, and `B.T` to act like a static -class function of `B`, like `B.U`. We could have accomplished this by requiring those -aliases to be written in a different way, for example `alias G = (Self as A).F`, but -needing to do that was surprising. We made it work by making the interpretation of -`T.(Interface.Member)` depend on whether `Member` was an _instance_ member of -`Interface`, with an implicit `me` parameter, or not. +We want `B.G` to act like a method of `B`, like `B.H`, and `B.T` to act like a +static class function of `B`, like `B.U`. We could have accomplished this by +requiring those aliases to be written in a different way, for example +`alias G = (Self as A).F`, but needing to do that was surprising. We made it +work by making the interpretation of `T.(Interface.Member)` depend on whether +`Member` was an _instance_ member of `Interface`, with an implicit `me` +parameter, or not. This was discussed in [the #syntax channel on discord on 2022-11-07](https://discord.com/channels/655572317891461132/709488742942900284/1039298322625744987) and in [open discussion on 2022-11-10](https://docs.google.com/document/d/1tEt4iM6vfcY0O0DG0uOEMIbaXcZXlNREc2ChNiEtn_w/edit#heading=h.1y65biik2vr). + ### Alternative terminology We considered various alternatives for the terminology choices listed above in @@ -522,8 +526,8 @@ Instead of "facet" we considered: ### `Core.Type` is an empty named constraint -We considered making `type` an alias for an empty type constraint defined in -the prelude, `Core.Type`. While it is possible to define a constraint that is +We considered making `type` an alias for an empty type constraint defined in the +prelude, `Core.Type`. While it is possible to define a constraint that is equivalent to `type` as a named constraint, we decided not to define `type` as an alias to such a named constraint in order to reduce complexity: From 3890809a3de05241bf75a937ba64ecf9f57c0708 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 16 Nov 2022 16:13:27 -0800 Subject: [PATCH 17/25] Say that it's an open question whether `constraint Foo {}` should mean that `Foo` is the same thing as `type`. --- proposals/p2360.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/p2360.md b/proposals/p2360.md index 7187ed606fec2..63e67cb317b53 100644 --- a/proposals/p2360.md +++ b/proposals/p2360.md @@ -190,7 +190,7 @@ a declaration such as `constraint MyVersionOfType {}` would introduce a facet type that is equivalent to `type`. This proposal does not change this decision. Because such a named constraint `MyVersionOfType` is equivalent to `type`, that is, because they are the same facet type, a value `T:! MyVersionOfType` is a -type under this proposal. +type under this proposal. Whether this is the right choice is an open question. ### Compound member access From e1cbd51fc268df3b5281d620888c5f6b9049ee1b Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 16 Nov 2022 16:30:12 -0800 Subject: [PATCH 18/25] Mention issue for leads #2409. --- proposals/p2360.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/proposals/p2360.md b/proposals/p2360.md index 63e67cb317b53..e717bcd17ae0f 100644 --- a/proposals/p2360.md +++ b/proposals/p2360.md @@ -190,7 +190,9 @@ a declaration such as `constraint MyVersionOfType {}` would introduce a facet type that is equivalent to `type`. This proposal does not change this decision. Because such a named constraint `MyVersionOfType` is equivalent to `type`, that is, because they are the same facet type, a value `T:! MyVersionOfType` is a -type under this proposal. Whether this is the right choice is an open question. +type under this proposal. There is an open +[issue for leads](https://github.com/carbon-language/carbon-lang/issues/2409) on +whether this is the right behavior or if we'd prefer a different rule. ### Compound member access From 132acc6557028fcc61764c0c505bf1a56c662750 Mon Sep 17 00:00:00 2001 From: Josh L Date: Sat, 19 Nov 2022 00:27:03 +0000 Subject: [PATCH 19/25] Checkpoint progress. --- proposals/p2360.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/proposals/p2360.md b/proposals/p2360.md index e717bcd17ae0f..b39f22a917b4b 100644 --- a/proposals/p2360.md +++ b/proposals/p2360.md @@ -551,3 +551,47 @@ This was discussed in: - [#naming on 2022-10-31](https://discord.com/channels/655572317891461132/963846118964350976/1036750048899366954) - [#syntax on 2022-10-27](https://discord.com/channels/655572317891461132/709488742942900284/1035222720864071740) - [open discussion on 2022-10-31](https://docs.google.com/document/d/1tEt4iM6vfcY0O0DG0uOEMIbaXcZXlNREc2ChNiEtn_w/edit#heading=h.9x8m0qwhuux) + +### Write tuple types differently than tuples + +We considered having the way to write tuple tuples be different than tuples. So +instead of `var x: (i32, i32) = (1, 2);` you might write: + +```carbon +var x: TupleType(i32, i32) = (1, 2); + +// Or, with dedicated syntax: +var y: (|i32, i32|) = (1, 2); +``` + +- Cost to having more balanced delimiters + + > [chandlerc] Personally, my bigger worry is not so much the desire to specifically use `()` as the cost of either further overloading other balanced delimeters or the cost of acquiring more balanced delimiters. + + > [chandlerc] writing `let p: (|i32, i32|) = (1, 2);` (or any of the other easy other syntaxes) is going to be a frustrating ergonomic experience + +- Since types are values in Carbon, `(i32, i32)` is a legal expression and + users would be surprised if they can use it as a type. + + > [chandlerc] A further thing to think about is that as long as we have types available to program in the expression grammar, people would be able to form a tuple of them with (i32, i32) and it seems very unfortunate for that syntax to be valid, meaningful, but not usable in the context of a tuple type. + + > I think telling folks that you can write `(i32, i32)` but you can't write: `let p: (i32, i32) = (1, 2);` is also going to be a pretty big stumbling point. + +- Expect that when teaching or speaking casually, would not need to explain + that `(i32, i32)` was not a type but was usable as a type. That distinction + would generally not affect users, who would generally be able to treat the + type of a tuple as the tuple of the types of its elements. + + > [chandlerc] agree that trying to dive into a detail like this isn't a good teachability thing. But it isn't clear if its needed + > "how do you get the type for a tuple of two types? just build a tuple of the types" + > or saying casually that the type of (1, 2) is (i32, i32) I think is fine + > the fact that there is an implicit conversion I think wouldn't be something you'd teach at all until getting into super pedantic parts of "ok, but how does this low-level thing work in the type system" + +- This would prevent operations from working generically on both tuples values + and tuple types. We would need to instead duplicate the set of operations. + + > [chandlerc] I think it also leaves us with a genericity problem: when using tuples, you cannot use them generically for both type values and non-type values... either on one end or the other, you have to explicitly separate those + > so stuff like let p: TupleCat((i32, i32), (i64, i64)) = (1, 2, 3, 4); seems like it will need quite a bit of machinery to deal with type-tuples being different + +This was discussed +[in the #syntax channel on 2022-11-03](https://discord.com/channels/655572317891461132/708431657849585705/1037793379813167166). From 1a70443a9814a80ccec4e9935d7fd32b0c9276cd Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 22 Nov 2022 20:45:46 +0000 Subject: [PATCH 20/25] Fix formatting --- proposals/p2360.md | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/proposals/p2360.md b/proposals/p2360.md index b39f22a917b4b..e442f5c7bdf42 100644 --- a/proposals/p2360.md +++ b/proposals/p2360.md @@ -32,6 +32,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Alternative member access rules](#alternative-member-access-rules) - [Alternative terminology](#alternative-terminology) - [`Core.Type` is an empty named constraint](#coretype-is-an-empty-named-constraint) + - [Write tuple types differently than tuples](#write-tuple-types-differently-than-tuples) @@ -566,32 +567,48 @@ var y: (|i32, i32|) = (1, 2); - Cost to having more balanced delimiters - > [chandlerc] Personally, my bigger worry is not so much the desire to specifically use `()` as the cost of either further overloading other balanced delimeters or the cost of acquiring more balanced delimiters. - - > [chandlerc] writing `let p: (|i32, i32|) = (1, 2);` (or any of the other easy other syntaxes) is going to be a frustrating ergonomic experience + > [chandlerc] Personally, my bigger worry is not so much the desire to + > specifically use `()` as the cost of either further overloading other + > balanced delimiters or the cost of acquiring more balanced delimiters. + + > [chandlerc] writing `let p: (|i32, i32|) = (1, 2);` (or any of the other + > easy other syntaxes) is going to be a frustrating ergonomic experience - Since types are values in Carbon, `(i32, i32)` is a legal expression and users would be surprised if they can use it as a type. - > [chandlerc] A further thing to think about is that as long as we have types available to program in the expression grammar, people would be able to form a tuple of them with (i32, i32) and it seems very unfortunate for that syntax to be valid, meaningful, but not usable in the context of a tuple type. + > [chandlerc] A further thing to think about is that as long as we have + > types available to program in the expression grammar, people would be able + > to form a tuple of them with (i32, i32) and it seems very unfortunate for + > that syntax to be valid, meaningful, but not usable in the context of a + > tuple type. - > I think telling folks that you can write `(i32, i32)` but you can't write: `let p: (i32, i32) = (1, 2);` is also going to be a pretty big stumbling point. + > I think telling folks that you can write `(i32, i32)` but you can't write: + > `let p: (i32, i32) = (1, 2);` is also going to be a pretty big stumbling + > point. - Expect that when teaching or speaking casually, would not need to explain that `(i32, i32)` was not a type but was usable as a type. That distinction would generally not affect users, who would generally be able to treat the type of a tuple as the tuple of the types of its elements. - - > [chandlerc] agree that trying to dive into a detail like this isn't a good teachability thing. But it isn't clear if its needed - > "how do you get the type for a tuple of two types? just build a tuple of the types" - > or saying casually that the type of (1, 2) is (i32, i32) I think is fine - > the fact that there is an implicit conversion I think wouldn't be something you'd teach at all until getting into super pedantic parts of "ok, but how does this low-level thing work in the type system" + + > [chandlerc] agree that trying to dive into a detail like this isn't a good + > teachability thing. But it isn't clear if its needed "how do you get the + > type for a tuple of two types? just build a tuple of the types" or saying + > casually that the type of (1, 2) is (i32, i32) I think is fine the fact + > that there is an implicit conversion I think wouldn't be something you'd + > teach at all until getting into super pedantic parts of "ok, but how does + > this low-level thing work in the type system" - This would prevent operations from working generically on both tuples values and tuple types. We would need to instead duplicate the set of operations. - - > [chandlerc] I think it also leaves us with a genericity problem: when using tuples, you cannot use them generically for both type values and non-type values... either on one end or the other, you have to explicitly separate those - > so stuff like let p: TupleCat((i32, i32), (i64, i64)) = (1, 2, 3, 4); seems like it will need quite a bit of machinery to deal with type-tuples being different + + > [chandlerc] I think it also leaves us with a genericity problem: when + > using tuples, you cannot use them generically for both type values and + > non-type values... either on one end or the other, you have to explicitly + > separate those so stuff like let p: TupleCat((i32, i32), (i64, i64)) = (1, + > 2, 3, 4); seems like it will need quite a bit of machinery to deal with + > type-tuples being different This was discussed [in the #syntax channel on 2022-11-03](https://discord.com/channels/655572317891461132/708431657849585705/1037793379813167166). From 35bd2edf6bd303d08dadcf988afa0e54c3f8a8bb Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 22 Nov 2022 22:57:24 +0000 Subject: [PATCH 21/25] Clean up rationale in alternative --- proposals/p2360.md | 55 +++++++++++----------------------------------- 1 file changed, 13 insertions(+), 42 deletions(-) diff --git a/proposals/p2360.md b/proposals/p2360.md index e442f5c7bdf42..8da95ec2cda1c 100644 --- a/proposals/p2360.md +++ b/proposals/p2360.md @@ -565,50 +565,21 @@ var x: TupleType(i32, i32) = (1, 2); var y: (|i32, i32|) = (1, 2); ``` -- Cost to having more balanced delimiters - - > [chandlerc] Personally, my bigger worry is not so much the desire to - > specifically use `()` as the cost of either further overloading other - > balanced delimiters or the cost of acquiring more balanced delimiters. - - > [chandlerc] writing `let p: (|i32, i32|) = (1, 2);` (or any of the other - > easy other syntaxes) is going to be a frustrating ergonomic experience - -- Since types are values in Carbon, `(i32, i32)` is a legal expression and - users would be surprised if they can use it as a type. - - > [chandlerc] A further thing to think about is that as long as we have - > types available to program in the expression grammar, people would be able - > to form a tuple of them with (i32, i32) and it seems very unfortunate for - > that syntax to be valid, meaningful, but not usable in the context of a - > tuple type. - - > I think telling folks that you can write `(i32, i32)` but you can't write: - > `let p: (i32, i32) = (1, 2);` is also going to be a pretty big stumbling - > point. - -- Expect that when teaching or speaking casually, would not need to explain - that `(i32, i32)` was not a type but was usable as a type. That distinction - would generally not affect users, who would generally be able to treat the - type of a tuple as the tuple of the types of its elements. - - > [chandlerc] agree that trying to dive into a detail like this isn't a good - > teachability thing. But it isn't clear if its needed "how do you get the - > type for a tuple of two types? just build a tuple of the types" or saying - > casually that the type of (1, 2) is (i32, i32) I think is fine the fact - > that there is an implicit conversion I think wouldn't be something you'd - > teach at all until getting into super pedantic parts of "ok, but how does - > this low-level thing work in the type system" - +There are a few reasons we decided not to go with this approach: + +- There is a significant cost to having more balanced delimiters, particularly + ones consisting of multiple characters. +- Since types are values in Carbon, `(i32, i32)` is a valid and meaningful + expression. Users would be surprised if they cannot use it as a type. +- We expect that, when teaching or speaking casually, we would not need to + explain that `(i32, i32)` was not a type but was usable as a type. That + distinction would generally not affect users, who would generally be able to + treat the type of a tuple as the tuple of the types of its elements. - This would prevent operations from working generically on both tuples values and tuple types. We would need to instead duplicate the set of operations. - - > [chandlerc] I think it also leaves us with a genericity problem: when - > using tuples, you cannot use them generically for both type values and - > non-type values... either on one end or the other, you have to explicitly - > separate those so stuff like let p: TupleCat((i32, i32), (i64, i64)) = (1, - > 2, 3, 4); seems like it will need quite a bit of machinery to deal with - > type-tuples being different + For example, there would need to be separate `TupleCat` and `TupleTypeCat` + operations to perform concatenation. This would be extra machinery, and a + burden for users to know about all of the functions and call the right one. This was discussed [in the #syntax channel on 2022-11-03](https://discord.com/channels/655572317891461132/708431657849585705/1037793379813167166). From 1d65c9a62409fcac22b08c5256ebcf3676d07e2a Mon Sep 17 00:00:00 2001 From: Josh L Date: Tue, 22 Nov 2022 23:05:36 +0000 Subject: [PATCH 22/25] Start on teachability --- proposals/p2360.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/proposals/p2360.md b/proposals/p2360.md index 8da95ec2cda1c..a4ab446078658 100644 --- a/proposals/p2360.md +++ b/proposals/p2360.md @@ -28,6 +28,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Tuple and struct literals](#tuple-and-struct-literals) - [Name lookup into values with constrained types](#name-lookup-into-values-with-constrained-types) - [Rationale](#rationale) + - [Teachability](#teachability) - [Alternatives considered](#alternatives-considered) - [Alternative member access rules](#alternative-member-access-rules) - [Alternative terminology](#alternative-terminology) @@ -434,6 +435,18 @@ type parameter, we will look in the type of `T`, which is `Printable`. which of these will be more dominant, but the new behavior is more consistent. +### Teachability + +One of the bigger concerns with this proposal is how teachable it is. The +details of this model are likely to be challenging for new-comers to Carbon, +even ones with experience in C++. + +FIXME: ideas around how to approach explaining or teaching it + +FIXME: this is a relatively small change in practice from where Carbon's design +already is today, and we can and should still be open to more significant +changes if this problem ends up being severe and needing it. + ## Alternatives considered ### Alternative member access rules From 6d690a18288f02ab8e52a31f99dc5925263dbbb3 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 16 Dec 2022 17:57:10 -0800 Subject: [PATCH 23/25] Responses to review comments. --- proposals/p2360.md | 58 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/proposals/p2360.md b/proposals/p2360.md index a4ab446078658..c2d51b0cec9b6 100644 --- a/proposals/p2360.md +++ b/proposals/p2360.md @@ -27,6 +27,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - [Examples](#examples) - [Tuple and struct literals](#tuple-and-struct-literals) - [Name lookup into values with constrained types](#name-lookup-into-values-with-constrained-types) + - [Types in diagnostics](#types-in-diagnostics) - [Rationale](#rationale) - [Teachability](#teachability) - [Alternatives considered](#alternatives-considered) @@ -418,6 +419,27 @@ corresponding to `T` in type `type`, and will look at that value to determine where else to look. Because that value is symbolically the name of a generic type parameter, we will look in the type of `T`, which is `Printable`. +### Types in diagnostics + +Concern has been raised that this proposal could lead to `as type` appearing +frequently in diagnostics when types are printed. This is not expected to be the +case. When a type appears in a diagnostic, it will generally be in one of two +forms: + +- A reflection of source-level syntax that the programmer wrote. +- A string generated by the compiler to describe the type to the programmer. + +In both cases, we expect the type of a variable such as `var v: (i32, i32)` to +be displayed as `(i32, i32)`, not as `(i32, i32) as type`, unless either the +context requires the `as type` suffix for disambiguation or the programmer wrote +it in the source code. + +This is analogous to how integer literals behave: when describing the value `1` +of type `i32` in a diagnostic, the type is usually treated as assumed context, +so the diagnostic would say simply `1` rather than `1 as i32`. Similarly, C++ +compilers typically avoid printing things like `(short)5` when including values +of type `short`, for which there is no literal syntax, in diagnostics. + ## Rationale - [Language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem) @@ -441,11 +463,26 @@ One of the bigger concerns with this proposal is how teachable it is. The details of this model are likely to be challenging for new-comers to Carbon, even ones with experience in C++. -FIXME: ideas around how to approach explaining or teaching it +When explaining this model, we suggest focusing on the goal and how it is +achieved, rather than what happens under the covers. For example, we can say + +> You can use `var n: (i32, i32)` to declare that `n` is a pair of `i32`s + +without mentioning that the actual type of `(i32, i32)` is `(type, type)`, and +that an implicit conversion is performed here. This is analogous to how we can +say + +> You can use `n = (1, 2);` to assign `1` to the first element of `n` and `2` to +> the second element + +without mentioning that again an implicit conversion is performed. -FIXME: this is a relatively small change in practice from where Carbon's design -already is today, and we can and should still be open to more significant -changes if this problem ends up being severe and needing it. +In practice, this is a relatively small change from Carbon's design prior to +this proposal, and there are also teachability concerns with the ability to +treat a tuple of types as a type despite it _not_ being a value of type `type`. +Should there prove to be sustained problems with teachability, we should +consider making more significant changes, such as adopting a distinct syntax for +tuple types and empty structs. ## Alternatives considered @@ -527,6 +564,16 @@ Instead of "generic type" we considered: Instead of "facet" we considered: +- type + - The idea here is that, because facets can be used in type contexts, + calling them types is reasonable. + - Concern: we want `i32 as Sortable` and `i32 as Hashable` to be + different, but we do not want to say that they are "different types". + - Concern: we use `type` to refer to types, not facets, and it's important + that conversion to type `type` erases the facet type from a facet. If we + call facets "types" then we mean different things by "type" and + "`type`", and for example conversion of a type to type `type` would + change its meaning, which seems surprising. - impl - Concern: facets are analogous to `impl`s but are not in direct correspondence, because a facet can be for a constraint that involves @@ -584,6 +631,9 @@ There are a few reasons we decided not to go with this approach: ones consisting of multiple characters. - Since types are values in Carbon, `(i32, i32)` is a valid and meaningful expression. Users would be surprised if they cannot use it as a type. +- Allowing tuples in pattern syntax but not in type syntax may be surprising. + For example, given that `var (x: i32, y: i32);` is valid, it may be + surprising if `var p: (i32, i32);` is not valid. - We expect that, when teaching or speaking casually, we would not need to explain that `(i32, i32)` was not a type but was usable as a type. That distinction would generally not affect users, who would generally be able to From 60797490a24c5c826f4ffb8e4e82b8894b2b9e72 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 16 Dec 2022 17:58:19 -0800 Subject: [PATCH 24/25] Add additional discussion link Co-authored-by: josh11b --- proposals/p2360.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/proposals/p2360.md b/proposals/p2360.md index c2d51b0cec9b6..15883951e68b0 100644 --- a/proposals/p2360.md +++ b/proposals/p2360.md @@ -645,4 +645,6 @@ There are a few reasons we decided not to go with this approach: burden for users to know about all of the functions and call the right one. This was discussed -[in the #syntax channel on 2022-11-03](https://discord.com/channels/655572317891461132/708431657849585705/1037793379813167166). +[in the #syntax channel on 2022-11-03](https://discord.com/channels/655572317891461132/708431657849585705/1037793379813167166) +and +[in open discussion on on 2022-12-07](https://docs.google.com/document/d/1gnJBTfY81fZYvI_QXjwKk1uQHYBNHGqRLI2BS_cYYNQ/edit?resourcekey=0-ql1Q1WvTcDvhycf8LbA9DQ#heading=h.qu6e01wbzrhz). From 7a30d2df04d38b1b49ece60fe172a4cd339975ff Mon Sep 17 00:00:00 2001 From: josh11b Date: Thu, 22 Dec 2022 13:43:33 -0800 Subject: [PATCH 25/25] Update proposals/p2360.md Co-authored-by: Chandler Carruth --- proposals/p2360.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/p2360.md b/proposals/p2360.md index 15883951e68b0..89a424ade622d 100644 --- a/proposals/p2360.md +++ b/proposals/p2360.md @@ -615,7 +615,7 @@ This was discussed in: ### Write tuple types differently than tuples -We considered having the way to write tuple tuples be different than tuples. So +We considered having the way to write tuple types be different than tuples. So instead of `var x: (i32, i32) = (1, 2);` you might write: ```carbon