|
@@ -0,0 +1,650 @@
|
|
|
|
|
+# Types are values of type `type`
|
|
|
|
|
+
|
|
|
|
|
+<!--
|
|
|
|
|
+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/2360)
|
|
|
|
|
+
|
|
|
|
|
+<!-- toc -->
|
|
|
|
|
+
|
|
|
|
|
+## Table of contents
|
|
|
|
|
+
|
|
|
|
|
+- [Abstract](#abstract)
|
|
|
|
|
+- [Problem](#problem)
|
|
|
|
|
+ - [Tuples behave inconsistently](#tuples-behave-inconsistently)
|
|
|
|
|
+ - [Qualified access to constraint members is inconsistent](#qualified-access-to-constraint-members-is-inconsistent)
|
|
|
|
|
+- [Background](#background)
|
|
|
|
|
+- [Proposal](#proposal)
|
|
|
|
|
+- [Details](#details)
|
|
|
|
|
+ - [Terminology](#terminology)
|
|
|
|
|
+ - [`type` and `Core.Type`](#type-and-coretype)
|
|
|
|
|
+ - [Compound member access](#compound-member-access)
|
|
|
|
|
+ - [Instance binding](#instance-binding)
|
|
|
|
|
+ - [Impl lookup](#impl-lookup)
|
|
|
|
|
+ - [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)
|
|
|
|
|
+ - [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)
|
|
|
|
|
+
|
|
|
|
|
+<!-- tocstop -->
|
|
|
|
|
+
|
|
|
|
|
+## Abstract
|
|
|
|
|
+
|
|
|
|
|
+Define a "type" to be a value of type `type`. Contexts expecting a type perform
|
|
|
|
|
+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 "facets") are similarly not formally types but
|
|
|
|
|
+implicitly convert to `type`.
|
|
|
|
|
+
|
|
|
|
|
+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
|
|
|
|
|
+
|
|
|
|
|
+### 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
|
|
|
|
|
+
|
|
|
|
|
+- [#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.
|
|
|
|
|
+- [Issue #2113](https://github.com/carbon-language/carbon-lang/issues/2113)
|
|
|
|
|
+ gave us spelling and behavior guidelines for predefined names like `type`.
|
|
|
|
|
+
|
|
|
|
|
+## Proposal
|
|
|
|
|
+
|
|
|
|
|
+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`.
|
|
|
|
|
+
|
|
|
|
|
+This has the following consequences:
|
|
|
|
|
+
|
|
|
|
|
+- `()` 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
|
|
|
|
|
+
|
|
|
|
|
+### Terminology
|
|
|
|
|
+
|
|
|
|
|
+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 _facet type_ is a type whose values are some subset of the values of
|
|
|
|
|
+ `type`, determined by a set of constraints.
|
|
|
|
|
+
|
|
|
|
|
+ - Interface types and named constraint types are facet types whose
|
|
|
|
|
+ constraints are that the interface or named constraint is satisfied by
|
|
|
|
|
+ the type.
|
|
|
|
|
+ - The values produced by `&` operations between facet types and by `where`
|
|
|
|
|
+ expressions are facet types, whose set of constraints are determined by
|
|
|
|
|
+ the `&` or `where` expression.
|
|
|
|
|
+ - `type` is a facet type whose set of constraints is empty.
|
|
|
|
|
+
|
|
|
|
|
+ We previously referred to a facet type as a type-of-type, but that is no
|
|
|
|
|
+ longer accurate, as the type of any type is now by definition `type`.
|
|
|
|
|
+
|
|
|
|
|
+- A _facet_ is a value of a facet type. For example, `i32 as Hashable` is a
|
|
|
|
|
+ facet, and `Hashable` is a facet type. Note that all types are facets.
|
|
|
|
|
+ - A facet value can be thought of as a tuple of a possibly-symbolic
|
|
|
|
|
+ identity for a type, plus any witnesses needed to demonstrate that the
|
|
|
|
|
+ type satisfies the constraints of the facet type. A type is then a facet
|
|
|
|
|
+ value that has no witnesses.
|
|
|
|
|
+- 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. It may
|
|
|
|
|
+ be a facet, and its type may be a facet type other than `type`.
|
|
|
|
|
+
|
|
|
|
|
+### `type` and `Core.Type`
|
|
|
|
|
+
|
|
|
|
|
+This proposal introduces the name `type` as a keyword -- a type literal -- that
|
|
|
|
|
+names the facet type with an empty set of constraints. This keyword replaces the
|
|
|
|
|
+provisional name `Type` for this functionality.
|
|
|
|
|
+
|
|
|
|
|
+In [issue #2113](https://github.com/carbon-language/carbon-lang/issues/2113), it
|
|
|
|
|
+was decided that such functionality may be an alias for a name in the prelude,
|
|
|
|
|
+such as `Core.Type`. This proposal does not introduce a corresponding prelude
|
|
|
|
|
+name. The facet type `type` is primitive and provided by the compiler, and not
|
|
|
|
|
+an alias.
|
|
|
|
|
+
|
|
|
|
|
+[As noted in the generics design](/docs/design/generics/details.md#named-constraints),
|
|
|
|
|
+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. 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
|
|
|
|
|
+
|
|
|
|
|
+#### Instance binding
|
|
|
|
|
+
|
|
|
|
|
+For the syntax `x.y`, if `x` is an entity that has member names, such as a
|
|
|
|
|
+namespace or a type, then `y` is looked up within `x`, and instance binding is
|
|
|
|
|
+not performed. Otherwise, `y` is looked up within the type of `x` and instance
|
|
|
|
|
+binding is performed if an instance member is found.
|
|
|
|
|
+
|
|
|
|
|
+The syntax `x.(Y)`, where `Y` names an instance member, 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: MyType) -> 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 `MyType` and
|
|
|
|
|
+`MyType as MyInterface` are equivalent.
|
|
|
|
|
+
|
|
|
|
|
+#### Impl lookup
|
|
|
|
|
+
|
|
|
|
|
+Prior to this proposal, when a member access names a member of interface `I`,
|
|
|
|
|
+the rules for `impl` lookup also depend on whether the left hand operand is a
|
|
|
|
|
+type:
|
|
|
|
|
+
|
|
|
|
|
+- If the left hand operand is a type-of-type (now, facet type), `impl` lookup
|
|
|
|
|
+ is not performed.
|
|
|
|
|
+- If the left hand operand evaluates to a type `T`, then `impl` lookup is
|
|
|
|
|
+ performed for `T as I`.
|
|
|
|
|
+- Otherwise, the first operand is an expression of some type `T`, and `impl`
|
|
|
|
|
+ lookup is performed for `T as I`.
|
|
|
|
|
+
|
|
|
|
|
+Due to the non-uniform treatment of types and non-types, this rule is also
|
|
|
|
|
+changed to the following:
|
|
|
|
|
+
|
|
|
|
|
+- For a simple member access `a.b` where `b` names a member of an interface
|
|
|
|
|
+ `I`:
|
|
|
|
|
+ - If the interface member was found by searching a non-facet-type scope
|
|
|
|
|
+ `T`, for example a class or an adapter, then `impl` lookup is performed
|
|
|
|
|
+ for `T as I`. For example:
|
|
|
|
|
+ - `MyClass.AliasForInterfaceMember` finds the member of the
|
|
|
|
|
+ `impl MyClass as I`, not the interface member.
|
|
|
|
|
+ - `my_value.AliasForInterfaceMember`, with `my_value: MyClass`, finds
|
|
|
|
|
+ the member of the `impl MyClass as I`, and performs instance binding
|
|
|
|
|
+ if the member is an instance member.
|
|
|
|
|
+ - In the case where the member was found in a base class of the class
|
|
|
|
|
+ that was searched, `T` is the derived class that was searched, not
|
|
|
|
|
+ the base class in which the name was declared.
|
|
|
|
|
+ - Otherwise, `impl` lookup is not performed:
|
|
|
|
|
+ - `MyInterface.AliasForInterfaceMember` finds the member in the
|
|
|
|
|
+ interface `MyInterface` and does not perform `impl` lookup.
|
|
|
|
|
+- For a compound member access `a.(b)` where `b` names a member of an
|
|
|
|
|
+ interface `I`, `impl` lookup is performed for `T as I`, where:
|
|
|
|
|
+ - If `b` is an instance member, `T` is the type of `a`.
|
|
|
|
|
+ - `my_value.(Interface.InstanceInterfaceMember)()`, where `my_value`
|
|
|
|
|
+ is of type `MyClass`, uses `MyClass as Interface`.
|
|
|
|
|
+ - `MyClass.(Interface.InstanceInterfaceMember)()` uses
|
|
|
|
|
+ `type as Interface`.
|
|
|
|
|
+ - Otherwise, `a` is implicitly converted to `I`, and `T` is the result of
|
|
|
|
|
+ symbolically evaluating the converted expression.
|
|
|
|
|
+ - `MyClass.(Interface.NonInstanceInterfaceMember)()` uses
|
|
|
|
|
+ `MyClass as Interface`.
|
|
|
|
|
+ - `my_value.(Interface.NonInstanceInterfaceMember)()` is an error
|
|
|
|
|
+ unless `my_value` implicitly converts to a type.
|
|
|
|
|
+
|
|
|
|
|
+This approach aims to change as little as possible of the existing `impl` lookup
|
|
|
|
|
+behavior while removing inconsistencies and behavioral differences based on
|
|
|
|
|
+whether a value is a type.
|
|
|
|
|
+
|
|
|
|
|
+#### Examples
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+interface Iface {
|
|
|
|
|
+ fn Static();
|
|
|
|
|
+ fn Method[me: Self]();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+impl type as Iface { ... }
|
|
|
|
|
+
|
|
|
|
|
+fn F[T:! Iface](x: T) {
|
|
|
|
|
+ // ✅ Uses `T as Iface`.
|
|
|
|
|
+ x.Static();
|
|
|
|
|
+ T.Static();
|
|
|
|
|
+ // ❌ Uses `T as Iface`, but method call is missing an instance.
|
|
|
|
|
+ T.Method();
|
|
|
|
|
+ // ✅ OK, instance is provided. Uses `T as Iface`.
|
|
|
|
|
+ x.Method();
|
|
|
|
|
+ x.(T.Method)();
|
|
|
|
|
+
|
|
|
|
|
+ // ❌ Would use `(x as Iface).Static`.
|
|
|
|
|
+ // Error because `x` is not a symbolic constant.
|
|
|
|
|
+ x.(Iface.Static)();
|
|
|
|
|
+ // ✅ Uses `(T as Iface).Static`.
|
|
|
|
|
+ T.(Iface.Static)();
|
|
|
|
|
+ // ✅ Uses `(type as Iface).Static`.
|
|
|
|
|
+ type.(Iface.Static)();
|
|
|
|
|
+
|
|
|
|
|
+ // ✅ Uses `(T as Iface).Method`, with `me = x`.
|
|
|
|
|
+ x.(Iface.Method)();
|
|
|
|
|
+ // ✅ Uses `(type as Iface).Method`, with `me = T`.
|
|
|
|
|
+ T.(Iface.Method)();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+base class Base {
|
|
|
|
|
+ fn Static();
|
|
|
|
|
+ fn Method[me: Self]();
|
|
|
|
|
+ alias IfaceStatic = Iface.Static;
|
|
|
|
|
+ alias IfaceMethod = Iface.Method;
|
|
|
|
|
+}
|
|
|
|
|
+external impl Base as Iface { ... }
|
|
|
|
|
+
|
|
|
|
|
+fn G(b: Base) {
|
|
|
|
|
+ // ✅ OK
|
|
|
|
|
+ b.Static();
|
|
|
|
|
+ Base.Static();
|
|
|
|
|
+ // ❌ Uses `Base as Iface`, but method call is missing an instance.
|
|
|
|
|
+ Base.Method();
|
|
|
|
|
+ // ✅ OK
|
|
|
|
|
+ b.Method();
|
|
|
|
|
+ b.(Base.Method)();
|
|
|
|
|
+
|
|
|
|
|
+ // ✅ OK, same as `(Base as Iface).Static()`.
|
|
|
|
|
+ b.IfaceStatic();
|
|
|
|
|
+ Base.IfaceStatic();
|
|
|
|
|
+ // ❌ Uses `Base as Iface`, but method call is missing an instance.
|
|
|
|
|
+ Base.IfaceMethod();
|
|
|
|
|
+ // ✅ OK, uses `Base as Iface`.
|
|
|
|
|
+ b.IfaceMethod();
|
|
|
|
|
+ b.(Base.IfaceMethod)();
|
|
|
|
|
+ b.((Base as Iface).Method)();
|
|
|
|
|
+ b.(Iface.Method)();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+class Derived extends Base {}
|
|
|
|
|
+external impl Derived as Iface { ... }
|
|
|
|
|
+
|
|
|
|
|
+fn H(d: Derived) {
|
|
|
|
|
+ // ✅ OK, calls `Base.Static`.
|
|
|
|
|
+ d.Static();
|
|
|
|
|
+ Derived.Static();
|
|
|
|
|
+ // ❌ Uses `Derived as Iface`, but method call is missing an instance.
|
|
|
|
|
+ Derived.Method();
|
|
|
|
|
+ // ✅ OK, calls `Base.Method`.
|
|
|
|
|
+ d.Method();
|
|
|
|
|
+ d.(Base.Method)();
|
|
|
|
|
+ d.(Derived.Method)();
|
|
|
|
|
+
|
|
|
|
|
+ // ✅ OK, same as `(Derived as Iface).Static()`.
|
|
|
|
|
+ d.IfaceStatic();
|
|
|
|
|
+ Derived.IfaceStatic();
|
|
|
|
|
+ // ❌ Uses `Derived as Iface`, but method call is missing an instance.
|
|
|
|
|
+ Derived.IfaceMethod();
|
|
|
|
|
+ // ✅ OK, uses `Derived as Iface`.
|
|
|
|
|
+ d.IfaceMethod();
|
|
|
|
|
+ d.(Derived.IfaceMethod)();
|
|
|
|
|
+ d.((Derived as Iface).Method)();
|
|
|
|
|
+ d.(Iface.Method)();
|
|
|
|
|
+ // ✅ OK, uses `Base as Iface`.
|
|
|
|
|
+ d.(Base.IfaceMethod)();
|
|
|
|
|
+ d.((Base as Iface).Method)();
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Tuple and struct literals
|
|
|
|
|
+
|
|
|
|
|
+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 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`.
|
|
|
|
|
+
|
|
|
|
|
+### 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)
|
|
|
|
|
+ - 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)
|
|
|
|
|
+ - 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.
|
|
|
|
|
+
|
|
|
|
|
+### 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++.
|
|
|
|
|
+
|
|
|
|
|
+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.
|
|
|
|
|
+
|
|
|
|
|
+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
|
|
|
|
|
+
|
|
|
|
|
+### 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.
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+interface A {
|
|
|
|
|
+ fn F[me: Self]();
|
|
|
|
|
+ fn S();
|
|
|
|
|
+}
|
|
|
|
|
+class B {
|
|
|
|
|
+ external impl as A;
|
|
|
|
|
+ alias G = A.F;
|
|
|
|
|
+ fn H[me: Self]();
|
|
|
|
|
+ alias T = A.S;
|
|
|
|
|
+ fn U();
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+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
|
|
|
|
|
+[open discussion on 2022-10-27](https://docs.google.com/document/d/1tEt4iM6vfcY0O0DG0uOEMIbaXcZXlNREc2ChNiEtn_w/edit#heading=h.lecxe8qywdxa).
|
|
|
|
|
+
|
|
|
|
|
+Instead of "facet type" 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
|
|
|
|
|
+ facet types.
|
|
|
|
|
+- constraint
|
|
|
|
|
+ - Concern: this may sound like it should refer to a boolean predicate,
|
|
|
|
|
+ `T is C`, not `C` itself.
|
|
|
|
|
+- kind
|
|
|
|
|
+ - Concern: while this terminology is used to describe the type of a type
|
|
|
|
|
+ in computer science literature, that usage is different from this one in
|
|
|
|
|
+ that it generally refers to the arity of type constructors rather than
|
|
|
|
|
+ classifying subsets of the set of types.
|
|
|
|
|
+
|
|
|
|
|
+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 facet types 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:
|
|
|
|
|
+
|
|
|
|
|
+- 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
|
|
|
|
|
+ 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.
|
|
|
|
|
+
|
|
|
|
|
+### `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
|
|
|
|
|
+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:
|
|
|
|
|
+
|
|
|
|
|
+- Type-checking the prelude itself becomes more challenging if `type` is a
|
|
|
|
|
+ named constraint, due to the ubiquity of its usage.
|
|
|
|
|
+- If `type` is an alias to `Core.Type`, then either the implementation would
|
|
|
|
|
+ need to ensure that it introduces no member names and no constraints, or it
|
|
|
|
|
+ would need to faithfully look into `Core.Type` every type a property of
|
|
|
|
|
+ `type` is consulted, adding either compile-time cost or complexity to the
|
|
|
|
|
+ implementation.
|
|
|
|
|
+
|
|
|
|
|
+Given that we have no current desire to add member names or constraints to
|
|
|
|
|
+`type`, we do not get any benefit from defining it in the Carbon library. If we
|
|
|
|
|
+later choose to make `type` imply some other constraints by default, such as
|
|
|
|
|
+`Sized` or `Movable`, this decision should be revisited.
|
|
|
|
|
+
|
|
|
|
|
+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 types 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);
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+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.
|
|
|
|
|
+- 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
|
|
|
|
|
+ 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.
|
|
|
|
|
+ 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)
|
|
|
|
|
+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).
|