mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 21:20:11 +01:00
Replace :! and :? with keywords and contextual defaults (#7254)
This proposal removes the `:!` syntax for generics and templates in favor of keywords (`generic`, `template`, `runtime`) and contextual defaults for phase. It also replaces `:?` with `fwd` and introduces `exttype` for extended types. Assisted-by: Antigravity with Gemini, and Claude --------- Co-authored-by: Geoff Romer <gromer@google.com>
This commit is contained in:
co-authored by
Geoff Romer
parent
366cb1010b
commit
6e6405c0bb
+43
-23
@@ -738,7 +738,7 @@ Value expressions are further broken down into three _expression phases_:
|
||||
- A _runtime value_ has a dynamic value only known at runtime.
|
||||
|
||||
Template constants and symbolic constants are collectively called _compile-time
|
||||
constants_ and correspond to declarations using `:!`.
|
||||
constants_ and correspond to declarations of compile-time parameters.
|
||||
|
||||
Carbon will automatically convert a template constant to a symbolic constant, or
|
||||
any value to a runtime value:
|
||||
@@ -1049,6 +1049,25 @@ name. It can only match values that may be
|
||||
underscore (`_`) may be used instead of the name to match a value but without
|
||||
binding any name to it.
|
||||
|
||||
Every binding pattern has a _phase_ (either compile-time or runtime). A
|
||||
[compile-time binding](#checked-and-template-parameters) can only match
|
||||
[compile-time constants](#expression-phases), not run-time values.
|
||||
|
||||
To minimize keyword noise, Carbon uses contextual defaults to determine the
|
||||
phase (compile-time vs runtime) of a binding in parameter lists:
|
||||
|
||||
- Parameters to compile-time entities (such as `interface`, `impl`, and
|
||||
`class`) are checked generics by default.
|
||||
- Deduced function parameters (declared in `[]`) are checked generics by
|
||||
default.
|
||||
- Explicit function parameters and local bindings (declared in `()`) are
|
||||
runtime by default.
|
||||
|
||||
These defaults can be overridden by using the `template`, `generic`, or
|
||||
`runtime` keywords. However, using a keyword that matches the contextual default
|
||||
is disallowed to maintain consistency. A `template` keyword before the binding
|
||||
selects a template binding instead of a symbolic binding.
|
||||
|
||||
Binding patterns default to _`let` bindings_. The `var` keyword is used to make
|
||||
it a _`var` binding_.
|
||||
|
||||
@@ -1069,11 +1088,6 @@ example through side effects of the destructor, copy, and move operations, but
|
||||
the program's correctness must not depend on which option the Carbon
|
||||
implementation chooses.
|
||||
|
||||
A [compile-time binding](#checked-and-template-parameters) uses `:!` instead of
|
||||
a colon (`:`) and can only match [compile-time constants](#expression-phases),
|
||||
not run-time values. A `template` keyword before the binding selects a template
|
||||
binding instead of a symbolic binding.
|
||||
|
||||
The keyword `auto` may be used in place of the type in a binding pattern, as
|
||||
long as the type can be deduced from the type of a value in the same
|
||||
declaration.
|
||||
@@ -2729,9 +2743,13 @@ not itself a type.
|
||||
|
||||
### Checked and template parameters
|
||||
|
||||
The `:!` marks it as a compile-time binding pattern, and so `T` is a
|
||||
compile-time parameter. Compile-time parameters may either be _checked_ or
|
||||
_template_, and default to checked.
|
||||
Compile-time bindings may either be _checked_ or _template_ bindings and are
|
||||
often used as _parameters_ to generic entities. A binding pattern declares a
|
||||
checked binding if it's marked `generic`, or appears in a context that only
|
||||
supports compile-time bindings, such as the deduced parameter list `[]` of a
|
||||
function, the parameter list of a `class` or `interface`, or an associated
|
||||
constant declaration. A binding pattern declares a template binding if it's
|
||||
marked `template`.
|
||||
|
||||
"Checked" here means that the body of `Min` is type checked when the function is
|
||||
defined, independent of the specific values `T` is instantiated with, and name
|
||||
@@ -2741,7 +2759,8 @@ type `T` that implements the `Ordered` interface. Subsequent calls to `Min` only
|
||||
need to check that the deduced value of `T` implements `Ordered`.
|
||||
|
||||
The parameter could alternatively be declared to be a _template_ generic
|
||||
parameter by prefixing with the `template` keyword, as in `template T:! type`.
|
||||
parameter by prefixing it with the `template` keyword. Keywords matching the
|
||||
contextual default are disallowed to ensure consistency.
|
||||
|
||||
```carbon
|
||||
fn Convert[template T:! type](source: T, template U:! type) -> U {
|
||||
@@ -2780,9 +2799,10 @@ constraints declared in the function signature and evaluated at compile-time.
|
||||
|
||||
The [expression phase](#expression-phases) of a checked parameter is a symbolic
|
||||
constant whereas the expression phase of a template parameter is template
|
||||
constant. A binding pattern using `:!` is a _compile-time binding pattern_; more
|
||||
specifically a _template binding pattern_ if it uses `template`, and a _symbolic
|
||||
binding pattern_ if it does not.
|
||||
constant. A binding pattern for a compile-time parameter is a _compile-time
|
||||
binding pattern_; more specifically a _template binding pattern_ if it uses
|
||||
`template`, and a _symbolic binding pattern_ if it uses `generic` or defaults to
|
||||
it.
|
||||
|
||||
Although checked generics are generally preferred, templates enable translation
|
||||
of code between C++ and Carbon, and address some cases where the type checking
|
||||
@@ -3003,13 +3023,13 @@ to a checked parameter.
|
||||
|
||||
An associated constant is a member of an interface whose value is determined by
|
||||
the implementation of that interface for a specific type. These values are set
|
||||
to compile-time values in implementations, and so use the
|
||||
[`:!` compile-time binding pattern syntax](#checked-and-template-parameters)
|
||||
inside a [`let` declaration](#constant-let-declarations) without an initializer.
|
||||
This allows types in the signatures of functions in the interface to vary. For
|
||||
example, an interface describing a
|
||||
[stack](<https://en.wikipedia.org/wiki/Stack_(abstract_data_type)>) might use an
|
||||
associated constant to represent the type of elements stored in the stack.
|
||||
to compile-time values in implementations, and so are defined using a
|
||||
[`let` declaration](#constant-let-declarations) without an initializer, which
|
||||
defines an associated constant in this context. This allows types in the
|
||||
signatures of functions in the interface to vary. For example, an interface
|
||||
describing a [stack](<https://en.wikipedia.org/wiki/Stack_(abstract_data_type)>)
|
||||
might use an associated constant to represent the type of elements stored in the
|
||||
stack.
|
||||
|
||||
```
|
||||
interface StackInterface {
|
||||
@@ -3055,9 +3075,9 @@ Many Carbon entities, not just functions, may be made generic by adding
|
||||
#### Generic Classes
|
||||
|
||||
Classes may be defined with an optional explicit parameter list. All parameters
|
||||
to a class must be compile-time, and so defined with `:!`, either with or
|
||||
without the `template` prefix. For example, to define a stack that can hold
|
||||
values of any type `T`:
|
||||
to a class must be compile-time, and are checked generic parameters by default,
|
||||
or can be marked with the `template` keyword. For example, to define a stack
|
||||
that can hold values of any type `T`:
|
||||
|
||||
```carbon
|
||||
class Stack(T:! type) {
|
||||
|
||||
@@ -13,7 +13,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
- [Overview](#overview)
|
||||
- [Member resolution](#member-resolution)
|
||||
- [Package and namespace members](#package-and-namespace-members)
|
||||
- [Types, forms, and facets](#types-forms-and-facets)
|
||||
- [Types, extended types, and facets](#types-extended-types-and-facets)
|
||||
- [`extend`](#extend)
|
||||
- [Tuple indexing](#tuple-indexing)
|
||||
- [Values](#values)
|
||||
@@ -133,17 +133,17 @@ A member access expression is processed using the following steps:
|
||||
The process of _member resolution_ determines which member `M` a member access
|
||||
expression is referring to.
|
||||
|
||||
For a simple member access, if the first operand is a type, form, facet,
|
||||
package, or namespace, a search for the member name is performed in the first
|
||||
operand. Otherwise, a search for the member name is performed in the type of the
|
||||
first operand. In either case, the search must succeed. In the latter case, if
|
||||
the result is an instance member, then [instance binding](#instance-binding) is
|
||||
performed on the first operand.
|
||||
For a simple member access, if the first operand is a type, extended type,
|
||||
facet, package, or namespace, a search for the member name is performed in the
|
||||
first operand. Otherwise, a search for the member name is performed in the type
|
||||
of the first operand. In either case, the search must succeed. In the latter
|
||||
case, if the result is an instance member, then
|
||||
[instance binding](#instance-binding) is performed on the first operand.
|
||||
|
||||
A search for a name within a form searches for the name in its
|
||||
[type component](/docs/design/values.md#expression-forms). Note that this means
|
||||
that the form of an expression never affects simple member access into that
|
||||
expression, except through its type component.
|
||||
A search for a name within an extended type searches for the name in its
|
||||
[type component](/docs/design/values.md#extended-types). Note that this means
|
||||
that the extended type of an expression never affects simple member access into
|
||||
that expression, except through its type component.
|
||||
|
||||
For a compound member access, the second operand is evaluated as a compile-time
|
||||
constant to determine the member being accessed. The evaluation is required to
|
||||
@@ -200,11 +200,11 @@ class Bar {
|
||||
}
|
||||
```
|
||||
|
||||
### Types, forms, and facets
|
||||
### Types, extended types, and facets
|
||||
|
||||
If the first operand is a type, form, or facet, it must be a compile-time
|
||||
constant. This disallows member access into a type except during compile-time,
|
||||
see leads issue
|
||||
If the first operand is a type, extended type, or facet, it must be a
|
||||
compile-time constant. This disallows member access into a type except during
|
||||
compile-time, see leads issue
|
||||
[#1293](https://github.com/carbon-language/carbon-lang/issues/1293).
|
||||
|
||||
Like the previous case, types (including
|
||||
@@ -240,8 +240,8 @@ class Avatar {
|
||||
Simple member access `(Avatar as Cowboy).Draw` finds the `Cowboy.Draw`
|
||||
implementation for `Avatar`, ignoring `Renderable.Draw`.
|
||||
|
||||
Similarly, a form has members, specifically the members of the form's type
|
||||
component.
|
||||
Similarly, an extended type has members, specifically the members of the
|
||||
extended type's type component.
|
||||
|
||||
#### `extend`
|
||||
|
||||
@@ -367,9 +367,9 @@ let n: i32 = p->(e);
|
||||
|
||||
### Values
|
||||
|
||||
If the first operand is not a type, form, package, namespace, or facet, it does
|
||||
not have member names, and a search is performed into the type of the first
|
||||
operand instead.
|
||||
If the first operand is not a type, extended type, facet, package, or namespace,
|
||||
it does not have member names, and a search is performed into the type of the
|
||||
first operand instead.
|
||||
|
||||
```carbon
|
||||
interface Printable {
|
||||
@@ -829,10 +829,11 @@ If instance binding is to be performed, the result of instance binding depends
|
||||
on what instance member `M` was found:
|
||||
|
||||
- For a field member of a struct type or tuple type, `x` is converted to a
|
||||
struct or tuple form by
|
||||
[form decomposition](/docs/design/values.md#category-conversions), and the
|
||||
`.f` element of the result of that conversion becomes the result of `x.f`.
|
||||
All other elements are [discarded](/docs/design/values.md#form-conversions).
|
||||
struct or tuple extended type by
|
||||
[extended type decomposition](/docs/design/values.md#extended-type-conversions),
|
||||
and the `.f` element of the result of that conversion becomes the result of
|
||||
`x.f`. All other elements are
|
||||
[discarded](/docs/design/values.md#extended-type-conversions).
|
||||
- For a field member in class `C`, `x` is required to be of type `C` or of a
|
||||
type derived from `C`. The result is the corresponding subobject within `x`.
|
||||
If `x` is an
|
||||
|
||||
@@ -184,6 +184,9 @@ The return type of a function or lambda can be specified using a return clause
|
||||
- Because the return type is deduced and not explicitly known, functions
|
||||
defined using `=>` cannot have a separate forward declaration.
|
||||
|
||||
> **TODO:** Update this section to cover extended return types, as discussed
|
||||
> [here](values.md#function-calls-and-returns).
|
||||
|
||||
#### Unused parameters
|
||||
|
||||
When a parameter introduced in a function definition is not used in the function
|
||||
@@ -889,6 +892,13 @@ Other designs build upon basic function syntax to add advanced features:
|
||||
- [Sigil for lambdas](/proposals/p003848-lambdas.md#alternative-considered-sigil)
|
||||
- [Additional Positional Parameter Restriction](/proposals/p003848-lambdas.md#alternative-considered-additional-positional-parameter-restriction)
|
||||
- [Recursive Self in lambdas](/proposals/p003848-lambdas.md#alternative-considered-recursive-self)
|
||||
- [Keep the `:!` syntax](/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md#keep-the--syntax)
|
||||
- [Alternative keyword names](/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md#alternative-keyword-names)
|
||||
- [Use `template generic` instead of just `template`](/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md#use-template-generic-instead-of-just-template)
|
||||
- [Context-independent syntax](/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md#context-independent-syntax)
|
||||
- [Erased model for generics](/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md#erased-model-for-generics)
|
||||
- [Context-sensitive defaults based on parameter type](/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md#context-sensitive-defaults-based-on-parameter-type)
|
||||
- [Allow redundant phase keywords](/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md#allow-redundant-phase-keywords)
|
||||
|
||||
## References
|
||||
|
||||
@@ -908,3 +918,5 @@ Other designs build upon basic function syntax to add advanced features:
|
||||
[#3848: Lambdas](https://github.com/carbon-language/carbon-lang/pull/3848)
|
||||
- Proposal
|
||||
[#5434: `ref` parameters, arguments, returns and `val` returns](https://github.com/carbon-language/carbon-lang/pull/5434)
|
||||
- Proposal
|
||||
[#7254: Replace `:!` and `:?` with keywords and contextual defaults](https://github.com/carbon-language/carbon-lang/pull/7254)
|
||||
|
||||
@@ -53,6 +53,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
- [Conditional conformance](#conditional-conformance)
|
||||
- [Interface parameters and associated constants](#interface-parameters-and-associated-constants)
|
||||
- [Type constraints](#type-constraints)
|
||||
- [Alternatives considered](#alternatives-considered)
|
||||
- [References](#references)
|
||||
|
||||
<!-- tocstop -->
|
||||
@@ -83,18 +84,23 @@ example, Rust supports
|
||||
## Checked versus template parameters
|
||||
|
||||
When we distinguish between checked and template generics in Carbon, it is on a
|
||||
parameter by parameter basis. A single function can take a mix of regular,
|
||||
checked, and template parameters.
|
||||
parameter by parameter basis. A single function can take a mix of runtime,
|
||||
checked generic, and template generic parameters.
|
||||
|
||||
- **Regular parameters**, or "dynamic parameters", are designated using the
|
||||
"\<name>`:` \<type>" syntax (or "\<value>").
|
||||
- **Checked parameters** are designated using `:!` between the name and the
|
||||
type (so it is "\<name>`:!` \<type>").
|
||||
- **Template parameters** are designated using "`template` \<name>`:!`
|
||||
\<type>".
|
||||
- **Runtime parameters** are the default for explicit function parameter lists
|
||||
(`()`) and locals. They can be explicitly marked with the `runtime` keyword
|
||||
in a context where they are not the default.
|
||||
- **Checked generic parameters** are the default in deduced parameter lists
|
||||
(`[]`) and parameters to compile-time entities (like `interface` or
|
||||
`class`). They can be explicitly marked with the `generic` keyword when used
|
||||
in explicit parameter lists (`()`).
|
||||
- **Template generic parameters** are designated by prefixing the parameter
|
||||
with the `template` keyword and are never the default.
|
||||
|
||||
Keywords matching the contextual default are disallowed to ensure consistency.
|
||||
|
||||
The syntax for checked and template parameters was decided in
|
||||
[questions-for-leads issue #565](https://github.com/carbon-language/carbon-lang/issues/565).
|
||||
[leads issue #6932](https://github.com/carbon-language/carbon-lang/issues/6932).
|
||||
|
||||
Expected difference between checked and template parameters:
|
||||
|
||||
@@ -288,27 +294,23 @@ classes, interfaces, and so on. There are three kinds of binding patterns,
|
||||
corresponding to
|
||||
[the three expression phases](/docs/design/README.md#expression-phases):
|
||||
|
||||
- A _runtime binding pattern_ binds to a dynamic value at runtime, and is
|
||||
written using a `:`, as in `x: i32`.
|
||||
- A _symbolic binding pattern_ binds to a compile-time value that is not known
|
||||
when type checking, and is used to declare
|
||||
[checked generic](#checked-versus-template-parameters) parameters. These
|
||||
binding use `:!`, as in `T:! type`.
|
||||
- A _runtime binding pattern_ binds to a dynamic value at runtime. It is the
|
||||
default for explicit function parameters.
|
||||
- A _symbolic binding pattern_ (or generic binding) binds to a compile-time
|
||||
value that is not known when type checking. It is the default for deduced
|
||||
function parameters and parameters to compile-time entities.
|
||||
- A _template binding pattern_ binds to a compile-time value that is known
|
||||
when type checking, and is used to declare
|
||||
[template](#checked-versus-template-parameters) parameters. These bindings
|
||||
use the keyword `template` in addition to `:!`, as in `template T:! type`.
|
||||
when type checking. It is indicated by the `template` keyword.
|
||||
|
||||
The last two binding patterns, which are about binding a compile-time value, are
|
||||
called _compile-time binding patterns_, and correspond to those binding patterns
|
||||
that use `:!`.
|
||||
These patterns use the keywords `runtime`, `generic`, and `template` to override
|
||||
the contextual defaults when necessary.
|
||||
|
||||
The name being declared, which is the identifier to the left of the `:` or `:!`,
|
||||
is called a _binding_, or more specifically a _runtime binding_, _compile-time
|
||||
The name being declared, which is the identifier to the left of the `:` is
|
||||
called a _binding_, or more specifically a _runtime binding_, _compile-time
|
||||
binding_, _symbolic binding_, or _template binding_. The expression to the right
|
||||
defining the type of the binding pattern is called the _binding type
|
||||
expression_, a kind of [type expression](#type-expression). For example, in
|
||||
`T:! Hashable`, `T` is the binding (a symbolic binding in this case), and
|
||||
`generic T: Hashable`, `T` is the binding (a symbolic binding in this case), and
|
||||
`Hashable` is the binding type expression.
|
||||
|
||||
## Types and `type`
|
||||
@@ -363,10 +365,9 @@ cases, we are concerned with the type value after the implicit conversion.
|
||||
## Facet binding
|
||||
|
||||
We use the term _facet binding_ to refer to the name introduced by a
|
||||
[compile-time binding pattern](#bindings) (using `:!` with or without the
|
||||
`template` modifier) where the declared type is a [facet type](#facet-type). In
|
||||
the binding pattern `T:! Hashable`, `T` is a facet binding, and the value of `T`
|
||||
is a [facet](#facet).
|
||||
[compile-time binding pattern](#bindings) where the declared type is a
|
||||
[facet type](#facet-type). In the binding pattern `generic T: Hashable`, `T` is
|
||||
a facet binding, and the value of `T` is a [facet](#facet).
|
||||
|
||||
## Deduced parameter
|
||||
|
||||
@@ -857,6 +858,16 @@ express, for example:
|
||||
Note that type constraints can be a restriction on one facet parameter or
|
||||
associated facet, or can define a relationship between multiple facets.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- [Keep the `:!` syntax](/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md#keep-the--syntax)
|
||||
- [Alternative keyword names](/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md#alternative-keyword-names)
|
||||
- [Use `template generic` instead of just `template`](/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md#use-template-generic-instead-of-just-template)
|
||||
- [Context-independent syntax](/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md#context-independent-syntax)
|
||||
- [Erased model for generics](/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md#erased-model-for-generics)
|
||||
- [Context-sensitive defaults based on parameter type](/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md#context-sensitive-defaults-based-on-parameter-type)
|
||||
- [Allow redundant phase keywords](/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md#allow-redundant-phase-keywords)
|
||||
|
||||
## References
|
||||
|
||||
- [#447: Generics terminology](https://github.com/carbon-language/carbon-lang/pull/447)
|
||||
@@ -866,3 +877,4 @@ associated facet, or can define a relationship between multiple facets.
|
||||
- [#2138: Checked and template generic terminology](https://github.com/carbon-language/carbon-lang/pull/2138)
|
||||
- [#2360: Types are values of type `type`](https://github.com/carbon-language/carbon-lang/pull/2360)
|
||||
- [#2760: Consistent `class` and `interface` syntax](https://github.com/carbon-language/carbon-lang/pull/2760)
|
||||
- [#7254: Replace `:!` and `:?` with keywords and contextual defaults](https://github.com/carbon-language/carbon-lang/pull/7254)
|
||||
|
||||
@@ -108,6 +108,10 @@ source file:
|
||||
- support an extensible operator set
|
||||
- different whitespace restrictions or no whitespace restrictions
|
||||
|
||||
[Alternatives from proposal #7254](/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md#alternatives-considered):
|
||||
|
||||
- [Keep the `:!` syntax](/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md#keep-the--syntax)
|
||||
|
||||
## References
|
||||
|
||||
- Proposal
|
||||
@@ -138,3 +142,5 @@ source file:
|
||||
[#2511: Assignment statements](https://github.com/carbon-language/carbon-lang/pull/2511)
|
||||
- Proposal
|
||||
[#2665: Semicolons terminate statements](https://github.com/carbon-language/carbon-lang/pull/2665)
|
||||
- Proposal
|
||||
[#7254: Replace `:!` and `:?` with keywords and contextual defaults](https://github.com/carbon-language/carbon-lang/pull/7254)
|
||||
|
||||
@@ -133,7 +133,8 @@ A name binding pattern is a pattern.
|
||||
|
||||
- _binding-pattern_ ::= `ref`? (_identifier_ `:` _expression_ | `self` (`:`
|
||||
_expression_)?)
|
||||
- _binding-pattern_ ::= `template`? _identifier_ `:!` _expression_
|
||||
- _binding-pattern_ ::= (`generic` | `template`)? _identifier_ `:`
|
||||
_expression_
|
||||
- _pattern_ ::= _binding-pattern_
|
||||
|
||||
A name binding pattern declares a _binding_ with a name specified by the
|
||||
@@ -150,14 +151,30 @@ which is the immediate subpattern of its enclosing `var` pattern.
|
||||
> expected to be the only difference between variable binding patterns and other
|
||||
> reference binding patterns.
|
||||
|
||||
If the pattern syntax uses `:` it is a _runtime binding pattern_. If it uses
|
||||
`:!`, it is a _compile-time binding pattern_, and it cannot appear inside a
|
||||
`var` pattern. A compile-time binding pattern is either a _symbolic binding
|
||||
pattern_ or a _template binding pattern_, depending on whether it is prefixed
|
||||
with `template`.
|
||||
A binding pattern has a phase, which is either runtime, symbolic compile-time,
|
||||
or template compile-time:
|
||||
|
||||
- A _runtime binding pattern_ binds to a dynamic value at runtime. It is the
|
||||
default for explicit function parameters and local bindings.
|
||||
- A _symbolic binding pattern_ (or generic binding pattern) binds to a
|
||||
compile-time value that is not known when type checking. It is the default
|
||||
for deduced function parameters and parameters to compile-time entities.
|
||||
Explicit function parameters are only symbolic binding patterns if they are
|
||||
declared using the `generic` keyword.
|
||||
- A _template binding pattern_ binds to a compile-time value that is known
|
||||
when type checking. It is declared using the `template` keyword.
|
||||
|
||||
> **Future work:** If Carbon supports deduced runtime parameters in the future,
|
||||
> the `runtime` keyword will be used to explicitly declare those runtime binding
|
||||
> patterns.
|
||||
|
||||
A symbolic or template binding pattern is collectively called a _compile-time
|
||||
binding pattern_. A compile-time binding pattern cannot appear inside a `var`
|
||||
pattern.
|
||||
|
||||
The binding declared by a binding pattern has a
|
||||
[primitive form](values.md#expression-forms) with the following components:
|
||||
[primitive extended type](values.md#extended-types) with the following
|
||||
components:
|
||||
|
||||
- The type is _expression_.
|
||||
- The category is "value" if the pattern is a value binding pattern, "durable
|
||||
@@ -167,13 +184,13 @@ The binding declared by a binding pattern has a
|
||||
pattern is a runtime, symbolic, or template binding pattern.
|
||||
|
||||
During pattern matching, the scrutinee is implicitly converted as needed to have
|
||||
the same form, and the binding is _bound_ to (and consumes) the result of these
|
||||
conversions. This makes a runtime or template binding a kind of reusable alias
|
||||
for the converted scrutinee expression, with the same form and value. Symbolic
|
||||
bindings are more complex: the binding will have the same type, category, and
|
||||
phase as the converted scrutinee expression, but its constant value is an opaque
|
||||
symbol introduced by the binding, which the type system knows to be equal to the
|
||||
converted scrutinee expression.
|
||||
the same extended type, and the binding is _bound_ to (and consumes) the result
|
||||
of these conversions. This makes a runtime or template binding a kind of
|
||||
reusable alias for the converted scrutinee expression, with the same extended
|
||||
type and value. Symbolic bindings are more complex: the binding will have the
|
||||
same type, category, and phase as the converted scrutinee expression, but its
|
||||
constant value is an opaque symbol introduced by the binding, which the type
|
||||
system knows to be equal to the converted scrutinee expression.
|
||||
|
||||
Note that there is no way to implicitly convert to a durable reference
|
||||
expression from any other category, so the scrutinee of a reference binding
|
||||
@@ -212,7 +229,7 @@ patterns in the same scope), and in all other respects it behaves as if it were
|
||||
wrapped in an [`unused` pattern](#unused).
|
||||
|
||||
- _binding-pattern_ ::= `_` `:` _expression_
|
||||
- _binding-pattern_ ::= `template`? `_` `:!` _expression_
|
||||
- _binding-pattern_ ::= (`generic` | `template`)? `_` `:` _expression_
|
||||
|
||||
```carbon
|
||||
fn F(n: i32) {
|
||||
@@ -301,13 +318,13 @@ scrutinee.
|
||||
|
||||
- _pattern_ ::= `var` _pattern_
|
||||
|
||||
The scrutinee is expected to have the same type as the resolved type of the
|
||||
nested _pattern_, and it is expected to be a runtime-phase ephemeral entire
|
||||
reference expression, which therefore refers to a newly-allocated temporary
|
||||
object. The scrutinee expression is converted as needed to satisfy those
|
||||
expectations, and the `var` pattern takes ownership of the referenced object,
|
||||
promotes it to a _durable_ entire reference expression, and matches the nested
|
||||
_pattern_ with it.
|
||||
The scrutinee is expected to have the same type component as the resolved type
|
||||
component of the nested _pattern_, and it is expected to be a runtime-phase
|
||||
ephemeral entire reference expression, which therefore refers to a
|
||||
newly-allocated temporary object. The scrutinee expression is converted as
|
||||
needed to satisfy those expectations, and the `var` pattern takes ownership of
|
||||
the referenced object, promotes it to a _durable_ entire reference expression,
|
||||
and matches the nested _pattern_ with it.
|
||||
|
||||
The lifetime of the allocated object extends to the end of scope of the `var`
|
||||
pattern (that is the scope that any bindings declared within it would have).
|
||||
@@ -381,12 +398,13 @@ A tuple of patterns can be used as a pattern.
|
||||
`)`
|
||||
- _pattern_ ::= _tuple-pattern_
|
||||
|
||||
The scrutinee is required to be of tuple type, with the same arity as the number
|
||||
of nested _patterns_. It is converted to a tuple form by
|
||||
[form decomposition](values.md#form-conversions), and then each nested _pattern_
|
||||
is matched against the corresponding element of the converted scrutinee's
|
||||
[result](values.md#expression-forms). The tuple pattern matches if all of these
|
||||
sub-matches succeed.
|
||||
The scrutinee is required to have a type component that is a tuple type, with
|
||||
the same arity as the number of nested _patterns_. It is converted to a tuple
|
||||
extended type by
|
||||
[extended type decomposition](values.md#extended-type-conversions), and then
|
||||
each nested _pattern_ is matched against the corresponding element of the
|
||||
converted scrutinee's [result](values.md#extended-types). The tuple pattern
|
||||
matches if all of these sub-matches succeed.
|
||||
|
||||
### Struct patterns
|
||||
|
||||
@@ -410,15 +428,16 @@ match ({.a = 1, .b = 2}) {
|
||||
}
|
||||
```
|
||||
|
||||
The scrutinee is required to be of struct type, and every field name in the
|
||||
pattern must be a field name in the scrutinee. It is converted to a struct form
|
||||
by [form decomposition](values.md#form-conversions) and then each
|
||||
The scrutinee is required to have a type component that is a struct type, and
|
||||
every field name in the pattern must be a field name in the scrutinee. It is
|
||||
converted to a struct extended type by
|
||||
[extended type decomposition](values.md#extended-type-conversions) and then each
|
||||
_field-pattern_ is matched with the same-named element of the converted
|
||||
scrutinee's [result](values.md#expression-forms). If the scrutinee result has
|
||||
any field names not present in the pattern, those sub-results are
|
||||
[discarded](values.md#form-conversions) in lexical order if the pattern has a
|
||||
trailing `_` (as in `{.a = 1, _}`), or diagnosed as an error if it does not. The
|
||||
struct pattern matches if all of these sub-matches succeed.
|
||||
scrutinee's [result](values.md#extended-types). If the scrutinee result has any
|
||||
field names not present in the pattern, those sub-results are
|
||||
[discarded](values.md#extended-type-conversions) in lexical order if the pattern
|
||||
has a trailing `_` (as in `{.a = 1, _}`), or diagnosed as an error if it does
|
||||
not. The struct pattern matches if all of these sub-matches succeed.
|
||||
|
||||
In the case where a field will be bound to an identifier with the same name, a
|
||||
shorthand syntax is available: `a: T` is synonymous with `.a = a: T`.
|
||||
@@ -505,12 +524,12 @@ is compared using `==`.
|
||||
|
||||
### Templates
|
||||
|
||||
Any checking of the type of the scrutinee against the type of the pattern that
|
||||
cannot be performed because the type of the scrutinee involves a template
|
||||
parameter is deferred until the template parameter's value is known. During
|
||||
instantiation, patterns that are not meaningful due to a type error are instead
|
||||
treated as not matching. This includes cases where an `==` fails because of a
|
||||
missing `EqWith` implementation.
|
||||
Any checking of the type component of the scrutinee against the type component
|
||||
of the pattern that cannot be performed because the type component of the
|
||||
scrutinee involves a template parameter is deferred until the template
|
||||
parameter's value is known. During instantiation, patterns that are not
|
||||
meaningful due to a type error are instead treated as not matching. This
|
||||
includes cases where an `==` fails because of a missing `EqWith` implementation.
|
||||
|
||||
```carbon
|
||||
fn TypeName[template T:! Type](x: T) -> String {
|
||||
@@ -743,9 +762,9 @@ In order to match a value, whatever is specified in the pattern must match.
|
||||
Using `auto` for a type will always match, making `_: auto` the wildcard
|
||||
pattern.
|
||||
|
||||
If the scrutinee expression's [form](values.md#expression-forms) contains any
|
||||
primitive forms with category "initializing", they are converted to ephemeral
|
||||
non-entire reference expressions by
|
||||
If the scrutinee expression's [extended type](values.md#extended-types) contains
|
||||
any primitive extended types with category "initializing", they are converted to
|
||||
ephemeral non-entire reference expressions by
|
||||
[materialization](values.md#temporary-materialization) before pattern matching
|
||||
begins, so that the result can be reused by multiple `case`s. However, the
|
||||
objects created by `var` patterns are not reused by multiple `case`s:
|
||||
@@ -949,6 +968,10 @@ pattern matching machinery, what (if any) restrictions are imposed, etc.
|
||||
|
||||
- [Type pattern matching](/proposals/p002188-pattern-matching-syntax-and-semantics.md#type-pattern-matching)
|
||||
- [Allow guards on arbitrary patterns](/proposals/p002188-pattern-matching-syntax-and-semantics.md#allow-guards-on-arbitrary-patterns)
|
||||
- [Keep the `:!` syntax](/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md#keep-the--syntax)
|
||||
- [Alternative keyword names](/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md#alternative-keyword-names)
|
||||
- [Use `template generic` instead of just `template`](/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md#use-template-generic-instead-of-just-template)
|
||||
- [Allow redundant phase keywords](/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md#allow-redundant-phase-keywords)
|
||||
|
||||
## References
|
||||
|
||||
@@ -956,3 +979,5 @@ pattern matching machinery, what (if any) restrictions are imposed, etc.
|
||||
[#2022: Unused Pattern Bindings (Unused Function Parameters)](https://github.com/carbon-language/carbon-lang/pull/2022)
|
||||
- Proposal
|
||||
[#2188: Pattern matching syntax and semantics](https://github.com/carbon-language/carbon-lang/pull/2188)
|
||||
- Proposal
|
||||
[#7254: Replace `:!` and `:?` with keywords and contextual defaults](https://github.com/carbon-language/carbon-lang/pull/7254)
|
||||
|
||||
+205
-176
@@ -32,9 +32,9 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
- [Function calls and returns](#function-calls-and-returns)
|
||||
- [Deferred initialization from values and references](#deferred-initialization-from-values-and-references)
|
||||
- [Declared `returned` variable](#declared-returned-variable)
|
||||
- [Expression forms](#expression-forms)
|
||||
- [Extended types](#extended-types)
|
||||
- [Initializing results](#initializing-results)
|
||||
- [Form conversions](#form-conversions)
|
||||
- [Extended type conversions](#extended-type-conversions)
|
||||
- [Type conversions](#type-conversions)
|
||||
- [Category conversions](#category-conversions)
|
||||
- [Pointers](#pointers)
|
||||
@@ -380,9 +380,9 @@ example:
|
||||
durable reference and compute the address of the referenced object.
|
||||
- [`ref` binding patterns](pattern_matching.md#name-binding-patterns) require
|
||||
their scrutinee to be a durable reference.
|
||||
- If a function's [return form](#function-calls-and-returns) contains `ref`
|
||||
tags, `return` statements require the corresponding parts of the operand to
|
||||
be durable reference expressions.
|
||||
- If a function's [extended return type](#function-calls-and-returns) contains
|
||||
`ref` tags, `return` statements require the corresponding parts of the
|
||||
operand to be durable reference expressions.
|
||||
|
||||
There are also several kinds of expressions that produce durable references. For
|
||||
example:
|
||||
@@ -396,8 +396,8 @@ example:
|
||||
- [Indexing](/docs/design/expressions/indexing.md) into a type similar to
|
||||
C++'s `std::span` that implements `IndirectIndexWith`, or indexing into any
|
||||
type with a durable reference expression such as `local_array[i]`.
|
||||
- Calls to functions whose [return forms](#function-calls-and-returns) contain
|
||||
`ref`.
|
||||
- Calls to functions whose
|
||||
[extended return types](#function-calls-and-returns) contain `ref`.
|
||||
|
||||
Durable reference expressions can only be produced _directly_ by one of these
|
||||
expressions. They are never produced by converting one of the other expression
|
||||
@@ -638,21 +638,21 @@ expression.
|
||||
|
||||
### Function calls and returns
|
||||
|
||||
The [result](#expression-forms) of a function call can have an almost arbitrary
|
||||
form. The return clause of a function signature consists of `->` followed by a
|
||||
_return form_, an expression-like syntax that specifies not only the type but
|
||||
also the form of the function call's result. `return` expressions in the
|
||||
function body are expected to have that form, and are converted to it if
|
||||
necessary. When a function is declared without a return clause, it behaves from
|
||||
the caller's point of view as if the return clause were `-> ()`, but `return`
|
||||
statements in the function body don't take operands (and can be omitted at the
|
||||
end of the function).
|
||||
The [result](#extended-types) of a function call can have an almost arbitrary
|
||||
extended type. The return clause of a function signature consists of `->`
|
||||
followed by an _extended return type_, an expression-like syntax that specifies
|
||||
not only the type component but also the extended type of the function call's
|
||||
result. `return` expressions in the function body are expected to have that
|
||||
extended type, and are converted to it if necessary. When a function is declared
|
||||
without a return clause, it behaves from the caller's point of view as if the
|
||||
return clause were `-> ()`, but `return` statements in the function body don't
|
||||
take operands (and can be omitted at the end of the function).
|
||||
|
||||
In the common case, the return form is a type expression, in which case calls
|
||||
are modeled directly as initializing expressions -- they require storage as an
|
||||
input and when evaluated cause that storage to be initialized with an object.
|
||||
This means that when a function call is used to initialize some variable pattern
|
||||
as here:
|
||||
In the common case, the extended return type is a type expression, in which case
|
||||
calls are modeled directly as initializing expressions -- they require storage
|
||||
as an input and when evaluated cause that storage to be initialized with an
|
||||
object. This means that when a function call is used to initialize some variable
|
||||
pattern as here:
|
||||
|
||||
```carbon
|
||||
fn CreateMyObject() -> MyType {
|
||||
@@ -671,58 +671,67 @@ the function's call expression. This in turn causes the property to hold
|
||||
_transitively_ across an arbitrary number of function calls and returns. The
|
||||
storage is forwarded at each stage and initialized exactly once.
|
||||
|
||||
More generally, the syntax and semantics of a return form are as follows:
|
||||
More generally, the syntax and semantics of an extended return type are as
|
||||
follows:
|
||||
|
||||
- _return-clause_ ::= `->` _return-form_
|
||||
- _return-form_ ::= _nesting-return-form_ | _auto-return-form_
|
||||
- _nesting-return-form_ ::= _expression-return-form_ | _proper-return-form_
|
||||
- _return-clause_ ::= `->` _extended-return-type_
|
||||
- _extended-return-type_ ::= _nesting-extended-return-type_ |
|
||||
_auto-extended-return-type_
|
||||
- _nesting-extended-return-type_ ::= _expression-extended-return-type_ |
|
||||
_proper-extended-return-type_
|
||||
|
||||
Return forms can usually be nested, but syntaxes involving `auto` can only occur
|
||||
at top level. We further divide nesting return forms into expressions and
|
||||
"proper" return forms, but this is just a technical means of avoiding formal
|
||||
ambiguity in the grammar; it has no greater significance.
|
||||
Extended return types can usually be nested, but syntaxes involving `auto` can
|
||||
only occur at top level. We further divide nesting extended return types into
|
||||
expressions and "proper" extended return types, but this is just a technical
|
||||
means of avoiding formal ambiguity in the grammar; it has no greater
|
||||
significance.
|
||||
|
||||
- _category-tag_ ::= `val` | `ref` | `var`
|
||||
|
||||
These tags are used to specify "value", "non-entire durable reference", or
|
||||
"initializing" expression category (respectively). Note that there is no way to
|
||||
express an entire or ephemeral reference category in a return form.
|
||||
express an entire or ephemeral reference category in an extended return type.
|
||||
|
||||
- _auto-return-form_ ::= _category-tag_? `auto`
|
||||
- _auto-extended-return-type_ ::= _category-tag_? `auto`
|
||||
|
||||
This denotes a primitive form with runtime phase and deduced type. The category
|
||||
is determined by _category-tag_ if present, or "initializing" otherwise.
|
||||
This denotes a primitive extended type with runtime phase and a deduced type
|
||||
component. The category is determined by _category-tag_ if present, or
|
||||
"initializing" otherwise.
|
||||
|
||||
- _proper-return-form_ ::= _category-tag_ _expression_
|
||||
- _proper-extended-return-type_ ::= _category-tag_ _expression_
|
||||
|
||||
This denotes a primitive form with runtime phase, category _category-tag_, and
|
||||
type "_expression_ `as type`".
|
||||
This denotes a primitive extended type with runtime phase, category
|
||||
_category-tag_, and type "_expression_ `as type`".
|
||||
|
||||
- _expression-return-form_ ::= _expression_
|
||||
- _expression-extended-return-type_ ::= _expression_
|
||||
|
||||
An expression with no _category-tag_ is equivalent to "`var` _expression_".
|
||||
|
||||
- _proper-return-form_ ::= `(` [_expression-return-form_ `,`]\* _proper-return-form_
|
||||
[`,` _nesting-return-form_]\* `,`? `)`
|
||||
- _proper-extended-return-type_ ::= `(` [_expression-extended-return-type_
|
||||
`,`]\* _proper-extended-return-type_ [`,` _nesting-extended-return-type_]\*
|
||||
`,`? `)`
|
||||
|
||||
A tuple literal of return forms denotes a tuple form whose sub-forms are
|
||||
specified by the comma-separated elements. To avoid formal ambiguity, this
|
||||
grammar rule requires at least one of the sub-forms to be proper.
|
||||
A tuple literal of extended return types denotes a tuple extended type whose
|
||||
sub-extended-types are specified by the comma-separated elements. To avoid
|
||||
formal ambiguity, this grammar rule requires at least one of the
|
||||
sub-extended-types to be proper.
|
||||
|
||||
- _expression-field-form_ ::= _designator_ `:` _expression-return-form_
|
||||
- _proper-field-form_ ::= _designator_ `:` _proper-return-form_
|
||||
- _field-form_ ::= _field-decl_
|
||||
- _field-form_ ::= _proper-field-form_
|
||||
- _proper-return-form_ ::= `{` [_expression-field-form_ `,`]\* _proper-field-form_
|
||||
[`,` _field-form_]\* `}`
|
||||
- _expression-field-extended-type_ ::= _designator_ `:`
|
||||
_expression-extended-return-type_
|
||||
- _proper-field-extended-type_ ::= _designator_ `:`
|
||||
_proper-extended-return-type_
|
||||
- _field-extended-type_ ::= _field-decl_
|
||||
- _field-extended-type_ ::= _proper-field-extended-type_
|
||||
- _proper-extended-return-type_ ::= `{` [_expression-field-extended-type_
|
||||
`,`]\* _proper-extended-type_ [`,` _field-extended-type_]\* `}`
|
||||
|
||||
A struct literal of return forms denotes a struct form whose field names and
|
||||
their forms are specified by the comma-separated field forms. To avoid formal
|
||||
ambiguity, this grammar rule requires at least one of the field forms to be
|
||||
proper.
|
||||
A struct literal of extended return types denotes a struct extended type whose
|
||||
field names and their extended types are specified by the comma-separated field
|
||||
extended types. To avoid formal ambiguity, this grammar rule requires at least
|
||||
one of the field extended types to be proper.
|
||||
|
||||
> **Open question:** Should there be a way to specify symbolic or template phase
|
||||
> in return forms?
|
||||
> in extended return types?
|
||||
|
||||
#### Deferred initialization from values and references
|
||||
|
||||
@@ -796,73 +805,81 @@ The model of initialization of returns also facilitates the use of
|
||||
[`returned var` declarations](control_flow/return.md#returned-var). These
|
||||
directly observe the storage provided for initialization of a function's return.
|
||||
|
||||
## Expression forms
|
||||
## Extended types
|
||||
|
||||
We typically treat the category and type of an expression as independent
|
||||
properties. However, in some cases we need to deal with them as an integrated
|
||||
whole. The _form_ of an expression captures all of the information about it that
|
||||
is visible to the type system, while abstracting away all other information
|
||||
about it. Thus, forms are a generalization of types: what we conventionally call
|
||||
"types" are really the types of objects and values, whereas forms are the types
|
||||
of expressions and patterns.
|
||||
whole. The _extended type_ of an expression captures all of the information
|
||||
about it that is visible to the type system, while abstracting away all other
|
||||
information about it. Thus, extended types are a generalization of types: what
|
||||
we conventionally call "types" are really the types of objects and values,
|
||||
whereas extended types are the types of expressions and patterns. The type
|
||||
`Core.ExtType` represents the type of an extended type constant, just as `type`
|
||||
represents the type of an object type constant.
|
||||
|
||||
A _primitive form_ currently consists of a type, an expression category, an
|
||||
expression phase, and optionally a constant value (which is present if and only
|
||||
if the expression phase is not "runtime"). When dealing with primitive forms,
|
||||
which is the common case, we can treat each of those properties as independent.
|
||||
For convenience, in this section we will use the notation `<T, C, P, V>` to
|
||||
represent a primitive form with type `T`, category `C`, phase `P` and value `V`,
|
||||
but this is not Carbon syntax.
|
||||
A _primitive extended type_ currently consists of a type, an expression
|
||||
category, an expression phase, and optionally a constant value (which is present
|
||||
if and only if the expression phase is not "runtime"). When dealing with
|
||||
primitive extended types, which is the common case, we can treat each of those
|
||||
properties as independent. For convenience, in this section we will use the
|
||||
notation `<T, C, P, V>` to represent a primitive extended type with type `T`,
|
||||
category `C`, phase `P` and value `V`, but this is not Carbon syntax.
|
||||
|
||||
Other forms are called _composite forms_, and there are two kinds:
|
||||
Other extended types are called _composite extended types_, and there are two
|
||||
kinds:
|
||||
|
||||
A _tuple form_ can be thought of as a tuple of forms, just as a tuple type can
|
||||
be thought of as a tuple of types. The form of a tuple literal is a tuple form,
|
||||
whose elements are the forms of the literal elements.
|
||||
A _tuple extended type_ can be thought of as a tuple of extended types, just as
|
||||
a tuple type can be thought of as a tuple of types. The extended type of a tuple
|
||||
literal is a tuple extended type, whose elements are the extended types of the
|
||||
literal elements.
|
||||
|
||||
> **TODO:** Extend this to support variadic forms.
|
||||
> **TODO:** Extend this to support variadic extended types.
|
||||
|
||||
A _struct form_ can be thought of as a struct whose fields are forms, just as a
|
||||
struct type can be thought of as a struct whose fields are types. The form of a
|
||||
struct literal is a struct form with the same field names, whose values are the
|
||||
forms of the corresponding fields of the struct literal.
|
||||
A _struct extended type_ can be thought of as a struct whose fields are extended
|
||||
types, just as a struct type can be thought of as a struct whose fields are
|
||||
types. The extended type of a struct literal is a struct extended type with the
|
||||
same field names, whose values are the extended types of the corresponding
|
||||
fields of the struct literal.
|
||||
|
||||
The _type component_ of a form is defined as follows:
|
||||
The _type component_ of an extended type is defined as follows:
|
||||
|
||||
- The type component of a primitive form `<T, C, P, V>` is `T`.
|
||||
- The type component of a tuple form is a tuple of the type components of its
|
||||
elements.
|
||||
- The type component of a struct form is a struct whose field names are the
|
||||
field names of the struct form and whose field types are the type components
|
||||
of the corresponding elements.
|
||||
- The type component of a primitive extended type `<T, C, P, V>` is `T`.
|
||||
- The type component of a tuple extended type is a tuple of the type
|
||||
components of its elements.
|
||||
- The type component of a struct extended type is a struct whose field names
|
||||
are the field names of the struct extended type and whose field types are
|
||||
the type components of the corresponding elements.
|
||||
|
||||
The _category component_ and _phase component_ of a form are defined likewise.
|
||||
The category component of a struct form is called a _struct category_, and the
|
||||
category component of a tuple form is called a _tuple category_.
|
||||
The _category component_ and _phase component_ of an extended type are defined
|
||||
likewise. The category component of a struct extended type is called a _struct
|
||||
category_, and the category component of a tuple extended type is called a
|
||||
_tuple category_.
|
||||
|
||||
The type of an expression is the type component of the expression's form.
|
||||
The type of an expression is the type component of the expression's extended
|
||||
type.
|
||||
|
||||
Evaluating an expression produces a _result_. It can be defined recursively in
|
||||
terms of the expression's form:
|
||||
terms of the expression's extended type:
|
||||
|
||||
- The result of an initializing expression is an
|
||||
[initializing result](#initializing-results).
|
||||
- The result of a value expression is a value.
|
||||
- The result of a reference expression is a reference of the same kind.
|
||||
- The result of an expression with tuple form is a tuple of results.
|
||||
- The result of an expression with struct form is a struct of results.
|
||||
- The result of an expression with tuple extended type is a tuple of results.
|
||||
- The result of an expression with struct extended type is a struct of
|
||||
results.
|
||||
|
||||
An expression and its result always have the same form.
|
||||
An expression and its result always have the same extended type.
|
||||
|
||||
The code that accesses the result of an expression is said to _consume_ that
|
||||
result, and every primitive-form result is consumed exactly once (except in
|
||||
certain narrow contexts where the result is known not to be initializing). If a
|
||||
result isn't explicitly accessed, such as when the expression is used as a
|
||||
statement, it is said to be _discarded_, which consumes it in the absence of an
|
||||
explicit consumer. Discarding an initializing result materializes and then
|
||||
immediately destroys it. Discarding an entire ephemeral reference destroys the
|
||||
object it refers to. Discarding a value or any other kind of reference is a
|
||||
no-op.
|
||||
result, and every primitive-extended-type result is consumed exactly once
|
||||
(except in certain narrow contexts where the result is known not to be
|
||||
initializing). If a result isn't explicitly accessed, such as when the
|
||||
expression is used as a statement, it is said to be _discarded_, which consumes
|
||||
it in the absence of an explicit consumer. Discarding an initializing result
|
||||
materializes and then immediately destroys it. Discarding an entire ephemeral
|
||||
reference destroys the object it refers to. Discarding a value or any other kind
|
||||
of reference is a no-op.
|
||||
|
||||
### Initializing results
|
||||
|
||||
@@ -916,62 +933,64 @@ expression. The source of that location depends on the consumer:
|
||||
location is newly-allocated temporary storage (which the consumer may
|
||||
subsequently lifetime-extend to durable storage).
|
||||
- If the consumer is a `return` statement, and the initializing result
|
||||
corresponds to an initializing sub-form of the function's return form, the
|
||||
result location is the implicit output parameter corresponding to that
|
||||
initializing sub-form.
|
||||
corresponds to an initializing sub-extended-type of the function's return
|
||||
extended type, the result location is the implicit output parameter
|
||||
corresponding to that initializing sub-extended-type.
|
||||
|
||||
### Form conversions
|
||||
### Extended type conversions
|
||||
|
||||
A conversion between forms can be broken down into up to three steps: type
|
||||
conversion, category conversion, and phase conversion. These convert the form to
|
||||
a particular target type, category, and phase component (respectively). These
|
||||
steps aren't fully orthogonal: type conversions can change the category and
|
||||
phase components as a byproduct, and category conversions can change the phase
|
||||
component. However, category conversions can't change the type component, and
|
||||
phase conversions can't change either of the other two, so converting the type,
|
||||
then category, then phase, ensures that we converge on the desired result.
|
||||
A conversion between extended types can be broken down into up to three steps:
|
||||
type conversion, category conversion, and phase conversion. These convert the
|
||||
extended type to a particular target type, category, and phase component
|
||||
(respectively). These steps aren't fully orthogonal: type conversions can change
|
||||
the category and phase components as a byproduct, and category conversions can
|
||||
change the phase component. However, category conversions can't change the type
|
||||
component, and phase conversions can't change either of the other two, so
|
||||
converting the type, then category, then phase, ensures that we converge on the
|
||||
desired result.
|
||||
|
||||
Any of these steps may be omitted, depending on whether the context imposes
|
||||
requirements on the corresponding component. Most commonly, an operand position
|
||||
requires its operand to have a primitive form with a particular category,
|
||||
usually with a particular type, and sometimes with a particular phase.
|
||||
requires its operand to have a primitive extended type with a particular
|
||||
category, usually with a particular type, and sometimes with a particular phase.
|
||||
|
||||
Phase conversions cannot change the form structure; they can only apply
|
||||
primitive phase conversions to primitive sub-forms. Type and category
|
||||
Phase conversions cannot change the extended type structure; they can only apply
|
||||
primitive phase conversions to primitive sub-extended-types. Type and category
|
||||
conversions are more complex, and are covered in the next two sections.
|
||||
|
||||
Note that these rules will implicitly convert between primitive and composite
|
||||
forms in both directions (except that a composite containing references cannot
|
||||
be converted to a primitive form). As a result, although the difference between
|
||||
primitive and composite forms is observable by way of overloading, it can't
|
||||
reliably carry any higher-level meaning, and should be used only as an
|
||||
optimization tool.
|
||||
extended types in both directions (except that a composite containing references
|
||||
cannot be converted to a primitive extended type). As a result, although the
|
||||
difference between primitive and composite extended types is observable by way
|
||||
of overloading, it can't reliably carry any higher-level meaning, and should be
|
||||
used only as an optimization tool.
|
||||
|
||||
Note that this section describes the _logical structure_ of form conversions. As
|
||||
such, it primarily describes them "breadth-first", as a sequence of operations
|
||||
that each applies to the whole expression by recursively operating on its parts.
|
||||
However, the _physical execution_ of these conversions is actually depth-first,
|
||||
applying as many operations as possible to a minimal subexpression before moving
|
||||
on to the next one. The details of that process are described
|
||||
Note that this section describes the _logical structure_ of extended type
|
||||
conversions. As such, it primarily describes them "breadth-first", as a sequence
|
||||
of operations that each applies to the whole expression by recursively operating
|
||||
on its parts. However, the _physical execution_ of these conversions is actually
|
||||
depth-first, applying as many operations as possible to a minimal subexpression
|
||||
before moving on to the next one. The details of that process are described
|
||||
[here](pattern_matching.md#evaluation-order).
|
||||
|
||||
#### Type conversions
|
||||
|
||||
See [here](expressions/implicit_conversions.md) for overall information about
|
||||
type conversions. Conversions involving struct, tuple, and array types are
|
||||
described here because of their unique interactions with expression forms.
|
||||
described here because of their unique interactions with extended types of
|
||||
expressions.
|
||||
|
||||
> **TODO:** A forthcoming proposal is expected to update the type conversion
|
||||
> interfaces to permit user-defined conversions to depend on the form of the
|
||||
> input, and customize the form of the output. Once that is done, these "built
|
||||
> in" conversions should be presented as implementations of those interfaces,
|
||||
> possibly with some "magic" for things like introspecting on struct field
|
||||
> names.
|
||||
> interfaces to permit user-defined conversions to depend on the extended type
|
||||
> of the input, and customize the extended type of the output. Once that is
|
||||
> done, these "built in" conversions should be presented as implementations of
|
||||
> those interfaces, possibly with some "magic" for things like introspecting on
|
||||
> struct field names.
|
||||
|
||||
Each of the conversions described in this section is explicit if and only if it
|
||||
invokes another explicit type conversion. Otherwise, it is implicit.
|
||||
|
||||
A type conversion of a primitive-form expression to a
|
||||
A type conversion of an expression with primitive extended type to a
|
||||
[compatible type](generics/terminology.md#compatible-types) just re-interprets
|
||||
the expression's result with a new type, so it requires no run-time work, and
|
||||
has the same category as the input expression.
|
||||
@@ -984,8 +1003,8 @@ A result `source` that has a struct type can be converted to a struct type
|
||||
type-convert `source.F` to `Dest.F`. Return a struct result where each field
|
||||
`F` is set to the result of the corresponding conversion.
|
||||
- If `source` is a primitive result, convert it to a struct result by
|
||||
[form decomposition](#category-conversions), and then type-convert the
|
||||
result to `Dest` and return the result.
|
||||
[extended type decomposition](#category-conversions), and then type-convert
|
||||
the result to `Dest` and return the result.
|
||||
|
||||
Note that the sub-conversions invoked here are not necessarily defined; if so,
|
||||
the conversion itself is not defined.
|
||||
@@ -1009,25 +1028,25 @@ of an object are not necessarily initialized in declaration order.
|
||||
Conversions between tuple types are defined in the same way, treating tuples as
|
||||
structs that have fields named `.0`, `.1`, etc, in numerical order.
|
||||
|
||||
There is a conversion to `array(T, N)` from any expression with a tuple form of
|
||||
exactly `N` elements, whose type components are convertible to `T`. The
|
||||
There is a conversion to `array(T, N)` from any expression with a tuple extended
|
||||
type of exactly `N` elements, whose type components are convertible to `T`. The
|
||||
conversion is an initializing expression, which type-converts each source
|
||||
element to `T`, and initializes the corresponding array element from the result
|
||||
of that conversion.
|
||||
|
||||
#### Category conversions
|
||||
|
||||
_Form composition_ converts an expression of composite form with consistent
|
||||
category to a primitive form as follows (where `min` as applied to phases uses
|
||||
the ordering "runtime" < "symbolic" < "template"):
|
||||
_Extended type composition_ converts an expression of composite extended type
|
||||
with consistent category to a primitive extended type as follows (where `min` as
|
||||
applied to phases uses the ordering "runtime" < "symbolic" < "template"):
|
||||
|
||||
- An expression of tuple form
|
||||
- An expression of tuple extended type
|
||||
`(<T1, C, P1, V1>, <T2, C, P2, V2>, ... <TN, C, PN, VN>)` can be converted
|
||||
to a primitive form
|
||||
to a primitive extended type
|
||||
`<(T1, T2, ..., TN), C, min(P1, P2, ..., PN), (V1, V2, ... VN)>`.
|
||||
- An expression of struct form
|
||||
- An expression of struct extended type
|
||||
`{.a = <Ta, C, Pa, Va>, .b = <Tb, C, Pb, Vb>, ... .z = <Tz, C, Pz, Vz>}` can
|
||||
be converted to a primitive form
|
||||
be converted to a primitive extended type
|
||||
`<{.a = Ta, .b = Tb, ... .z = Tz}, C, min(Pa, Pb, ... Pz), {.a = Va, .b = Vb, ... .z = Vz}>`.
|
||||
|
||||
When `C` is "value", composition forms a value representation of the aggregate
|
||||
@@ -1037,53 +1056,59 @@ expression that initializes the whole aggregate. `C` cannot be a reference
|
||||
category, because an aggregate of references to independent objects can't be
|
||||
replaced by a reference to a single aggregate object in a single step.
|
||||
|
||||
_Form decomposition_ is the inverse of form composition. It converts a
|
||||
primitive-form expression to a composite form as follows:
|
||||
_Extended type decomposition_ is the inverse of extended type composition. It
|
||||
converts an expression with primitive extended type to a composite extended type
|
||||
as follows:
|
||||
|
||||
- An expression with primitive form `<(T0, T1, ..., TN), C, P, V>` can be
|
||||
converted to a tuple form
|
||||
- An expression with primitive extended type `<(T0, T1, ..., TN), C, P, V>`
|
||||
can be converted to a tuple extended type
|
||||
`(<T0, CC, P, V.0>, <T1, CC, P, V.1>, ... <TN, CC, P, V.N>)`.
|
||||
- An expression with primitive form
|
||||
- An expression with primitive extended type
|
||||
`<{.a = Ta, .b = Tb, ... .z = Tz}, C, P, V>` can be converted to a struct
|
||||
form
|
||||
extended type
|
||||
`{.a = <Ta, CC, P, V.a>, .b = <Tb, CC, P, V.b>, ... .z = <Tz, CC, P, V.z>}`.
|
||||
|
||||
The category `CC` of the resulting sub-forms is the same as `C`, with two
|
||||
exceptions:
|
||||
The category `CC` of the resulting sub-extended-types is the same as `C`, with
|
||||
two exceptions:
|
||||
|
||||
- If `C` is "durable entire reference", `CC` will be "durable non-entire
|
||||
reference", because the sub-forms don't refer to complete objects. This
|
||||
doesn't apply to ephemeral entire references, because in that case form
|
||||
decomposition implicitly ends the lifetime of the original aggregate,
|
||||
promoting its elements to complete objects with independent lifetimes.
|
||||
reference", because the sub-extended-types don't refer to complete objects.
|
||||
This doesn't apply to ephemeral entire references, because in that case
|
||||
extended type decomposition implicitly ends the lifetime of the original
|
||||
aggregate, promoting its elements to complete objects with independent
|
||||
lifetimes.
|
||||
- If `C` is "initializing", the original expression is materialized before it
|
||||
is decomposed, so `CC` will be "ephemeral entire reference".
|
||||
|
||||
By convention, form decomposition is a no-op when applied to an expression with
|
||||
struct or tuple form.
|
||||
By convention, extended type decomposition is a no-op when applied to an
|
||||
expression with struct or tuple extended type.
|
||||
|
||||
_Category conversion_ converts an expression to have a given category component
|
||||
without changing its type. The conversion works by combining form composition
|
||||
and decomposition with primitive category conversions, and is defined
|
||||
recursively:
|
||||
without changing its type component. The conversion works by combining extended
|
||||
type composition and decomposition with primitive category conversions, and is
|
||||
defined recursively:
|
||||
|
||||
- If the target category component is a tuple, the source form must have a
|
||||
tuple type with the same arity. Convert the source to a tuple form by form
|
||||
decomposition, and then category-convert each source sub-form to the
|
||||
corresponding target sub-category.
|
||||
- If the target category component is a struct, the source form must have a
|
||||
struct type with the same set of field names in the same order. Convert the
|
||||
source to a struct form by form decomposition, and then category-convert
|
||||
each source sub-form to the corresponding target sub-category.
|
||||
- If the target category component is a tuple, the source extended type must
|
||||
have a type component that is a tuple type with the same arity. Convert the
|
||||
source to a tuple extended type by extended type decomposition, and then
|
||||
category-convert each source sub-extended-type to the corresponding target
|
||||
sub-category.
|
||||
- If the target category component is a struct, the source extended type must
|
||||
have a type component that is a struct type with the same set of field names
|
||||
in the same order. Convert the source to a struct extended type by extended
|
||||
type decomposition, and then category-convert each source sub-extended-type
|
||||
to the corresponding target sub-category.
|
||||
- If the target category is a primitive category `C`:
|
||||
- If the source form is primitive, convert to `C` by applying primitive
|
||||
category conversions.
|
||||
- If the source form is composite and `C` is a reference category,
|
||||
category-convert the source form to "initializing", and then convert the
|
||||
result to `C` by applying primitive category conversions.
|
||||
- If the source form is composite and `C` is not a reference category,
|
||||
category-convert each source sub-form to `C`, and then convert the
|
||||
aggregate result of these conversions to `C` by form composition.
|
||||
- If the source extended type is primitive, convert to `C` by applying
|
||||
primitive category conversions.
|
||||
- If the source extended type is composite and `C` is a reference
|
||||
category, category-convert the source extended type to "initializing",
|
||||
and then convert the result to `C` by applying primitive category
|
||||
conversions.
|
||||
- If the source extended type is composite and `C` is not a reference
|
||||
category, category-convert each source sub-extended-type to `C`, and
|
||||
then convert the aggregate result of these conversions to `C` by
|
||||
extended type composition.
|
||||
|
||||
## Pointers
|
||||
|
||||
@@ -1501,6 +1526,7 @@ itself.
|
||||
- [Alternative syntaxes for locals](/proposals/p002006-values-variables-pointers-and-references.md#alternative-syntaxes-for-locals)
|
||||
- [Mixed expression categories](/proposals/p005545-expression-form-basics.md#mixed-expression-categories)
|
||||
- [Don't implicitly convert to less-primitive forms](/proposals/p005545-expression-form-basics.md#dont-implicitly-convert-to-less-primitive-forms)
|
||||
- [Use `exprtype` and `expr` keywords](/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md#use-exprtype-and-expr-keywords)
|
||||
|
||||
## References
|
||||
|
||||
@@ -1510,6 +1536,8 @@ itself.
|
||||
- [Proposal #851: auto keyword for vars][#851]
|
||||
- [Proposal #2006: Values, variables, and pointers][#2006]
|
||||
- [Proposal #5545: Expression form basics][#5545]
|
||||
- [Proposal #7254: Replace `:!` and `:?` with keywords and contextual
|
||||
defaults][#7254]
|
||||
|
||||
[#257]: /proposals/p000257-initialization-of-memory-and-variables.md
|
||||
[#339]: /proposals/p000339-var-statement.md
|
||||
@@ -1517,3 +1545,4 @@ itself.
|
||||
[#851]: /proposals/p000851-variable-type-inference.md
|
||||
[#2006]: /proposals/p002006-values-variables-pointers-and-references.md
|
||||
[#5545]: /proposals/p005545-expression-form-basics.md
|
||||
[#7254]: /proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md
|
||||
|
||||
@@ -219,6 +219,19 @@ the named pack from the Nth scrutinee. The binding pattern's type expression may
|
||||
contain an each-name (such as `each ElementType` in the `Zip` example), but if
|
||||
so, it must be a deduced parameter of the enclosing pattern.
|
||||
|
||||
When a phase keyword (like `generic` or `template`) and/or a modifier (like
|
||||
`ref`) are used in a pack expansion pattern, the order is: `...` _phase_
|
||||
_modifier_ `each` _name_.
|
||||
|
||||
For example:
|
||||
|
||||
- `... generic each T: type`
|
||||
- `... runtime ref each x: i32` (Note: runtime parameters are the default for
|
||||
explicit parameters so this would only come up in a hypothetical case where
|
||||
we allow this in deduced parameters).
|
||||
|
||||
This ensures that `each` remains most tightly attached to the binding name.
|
||||
|
||||
> **Future work:** That restriction can probably be relaxed, but we currently
|
||||
> don't have motivating use cases to constrain the design.
|
||||
|
||||
|
||||
+31
-13
@@ -319,23 +319,41 @@ comparison `(a < b) > (c)`. In order to resolve the ambiguity, the compiler has
|
||||
to perform name lookup on `a` to determine whether there's a function named `a`
|
||||
in scope.
|
||||
|
||||
It's also worth noting that Carbon
|
||||
[doesn't use _any_ kind of brackets](/docs/design/README.md#checked-and-template-parameters)
|
||||
to mark template- or checked-generic parameters, so if Carbon had angle
|
||||
brackets, they would mean something different than they do in C++, which could
|
||||
cause confusion. We do use square brackets to mark _deduced_ parameters, as in:
|
||||
It's also worth noting that Carbon distinguishes between parameters based on
|
||||
whether they are _deduced_ or _explicit_, rather than using different brackets
|
||||
for checked or template generic parameters.
|
||||
|
||||
```
|
||||
fn Sort[T:! Comparable](a: Vector(T)*)
|
||||
We use square brackets `[]` to mark _deduced_ parameters, as in:
|
||||
|
||||
```carbon
|
||||
fn Sort[T: Comparable](a: Vector(T)*)
|
||||
```
|
||||
|
||||
But deduced parameters aren't the same thing as template parameters. In
|
||||
particular, deduced parameters are never mentioned at the callsite, so those
|
||||
square brackets are never part of the expression syntax.
|
||||
Deduced parameters are inferred by the compiler and are not mentioned at the
|
||||
call site, so those square brackets are never part of the expression syntax.
|
||||
This is a key distinction from C++ template parameters, which are often
|
||||
specified at the call site using angle brackets (for example,
|
||||
`std::make_shared<T>()`). In Carbon, if a compile-time parameter needs to be
|
||||
specified at the call site, it must be an explicit parameter in `()`. As a
|
||||
consequence, even if we used `<` / `>` delimiters, they would mean something
|
||||
different from their meaning in C++.
|
||||
|
||||
See [Proposal #676: `:!` generic syntax](/proposals/p000676-generic-syntax.md)
|
||||
for more background on how and why we chose our current compile-time parameter
|
||||
syntax.
|
||||
By default, deduced parameters are checked generics and explicit parameters (in
|
||||
`()`) are runtime. We can use keywords to override these defaults:
|
||||
|
||||
- Use `template` in `[]` for deduced template generic parameters (for example,
|
||||
`fn F[template T: type](...)`).
|
||||
- Use `generic` in `()` for explicit checked generic parameters (for example,
|
||||
`fn G(generic T: type, ...)`).
|
||||
|
||||
In all cases, the brackets indicate deduction, not the compile-time versus
|
||||
runtime phase.
|
||||
|
||||
See
|
||||
[proposal #7254](/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md)
|
||||
for details on the syntax using keywords and defaults, and
|
||||
[proposal #676](/proposals/p000676-generic-syntax.md) for the original
|
||||
background on avoiding angle brackets.
|
||||
|
||||
### Why do variable declarations have to start with `var` or `let`?
|
||||
|
||||
|
||||
@@ -0,0 +1,535 @@
|
||||
# Replace `:!` and `:?` with keywords and contextual defaults
|
||||
|
||||
<!--
|
||||
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/7254)
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [Abstract](#abstract)
|
||||
- [Problem](#problem)
|
||||
- [Background](#background)
|
||||
- [Proposal](#proposal)
|
||||
- [Details](#details)
|
||||
- [Phase Keywords and Contextual Defaults](#phase-keywords-and-contextual-defaults)
|
||||
- [Contextual Defaults](#contextual-defaults)
|
||||
- [Associated Constants](#associated-constants)
|
||||
- [Extended Types](#extended-types)
|
||||
- [Future Work on Extended Types](#future-work-on-extended-types)
|
||||
- [Rationale](#rationale)
|
||||
- [Alternatives considered](#alternatives-considered)
|
||||
- [Keep the `:!` syntax](#keep-the--syntax)
|
||||
- [Alternative keyword names](#alternative-keyword-names)
|
||||
- [Use `template generic` instead of just `template`](#use-template-generic-instead-of-just-template)
|
||||
- [Context-independent syntax](#context-independent-syntax)
|
||||
- [Erased model for generics](#erased-model-for-generics)
|
||||
- [Context-sensitive defaults based on parameter type](#context-sensitive-defaults-based-on-parameter-type)
|
||||
- [Allow redundant phase keywords](#allow-redundant-phase-keywords)
|
||||
- [Use `exprtype` and `expr` keywords](#use-exprtype-and-expr-keywords)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
## Abstract
|
||||
|
||||
This proposal removes the `:!` syntax for generics and templates in favor of
|
||||
keywords (`generic`, `template`, `runtime`) and contextual defaults for phase.
|
||||
It also suggests replacing `:?` from proposal #5389 with `fwd` and renaming
|
||||
"forms" to "extended types".
|
||||
|
||||
## Problem
|
||||
|
||||
The `:!` syntax for generics and templates has several issues:
|
||||
|
||||
- It doesn't work well for controlling the phase for functions.
|
||||
- The connection between generics/templates and `!` is tenuous and not an
|
||||
effective mnemonic.
|
||||
- It is very inventive syntax with little familiarity from other languages.
|
||||
- It makes Carbon code using generics start to look like ASCII-art due to
|
||||
dense punctuation.
|
||||
- It is in tension with more compelling use cases for `!`, such as for
|
||||
operations that are required to succeed or terminate (for example,
|
||||
unwrapping optionals).
|
||||
|
||||
## Background
|
||||
|
||||
The `:!` syntax was originally chosen to evoke the idea of "phase", using `!` to
|
||||
mark parameters that belong to an earlier (compile-time) phase of evaluation.
|
||||
Similarly, the `:?` syntax in the current revision of proposal #5389 is intended
|
||||
to mark deduced _form bindings_: parameters that capture extended type
|
||||
information (what was called a "form") about an expression, such as its value
|
||||
category and phase, rather than just its object type.
|
||||
|
||||
These issues were discussed in leads issue #6932, and a direction was decided to
|
||||
move away from punctuation and towards keywords and contextual defaults.
|
||||
|
||||
## Proposal
|
||||
|
||||
We propose to:
|
||||
|
||||
1. Remove `:!` syntax for generics and templates.
|
||||
2. Introduce contextual defaults for phase:
|
||||
- Parameters to compile-time entities (interfaces, impls, classes) are
|
||||
checked generic parameters by default.
|
||||
- Deduced function parameters are checked generic parameters by default.
|
||||
- Explicit function parameters are runtime by default.
|
||||
3. Allow overriding defaults with keywords `template`, `generic`, and
|
||||
`runtime`.
|
||||
4. Disallow keywords that match the contextual default to ensure consistency.
|
||||
5. Change the underlying terminology from "forms" to "extended types" and
|
||||
introduce `exttype`. Also suggest replacing the `:?` and `->?` syntax from
|
||||
pending proposal #5389 with a binding modifier `fwd` and corresponding
|
||||
return syntax.
|
||||
|
||||
## Details
|
||||
|
||||
### Phase Keywords and Contextual Defaults
|
||||
|
||||
Parameter phase is primarily determined by the context of the parameter:
|
||||
|
||||
- Parameters to compile-time entities (interfaces, impls, classes) are checked
|
||||
generic parameters by default.
|
||||
- Deduced function parameters are checked generic parameters by default.
|
||||
- Explicit function parameters are runtime by default.
|
||||
|
||||
These defaults can be overridden where meaningful by using one of the following
|
||||
keywords:
|
||||
|
||||
- `runtime`: Causes a parameter to be a runtime parameter in the deduced
|
||||
parameter context, if we ever decide to support runtime deduced parameters.
|
||||
- `generic`: Causes a parameter to be a checked generic when in an explicit
|
||||
function parameter context.
|
||||
- `template`: Causes a parameter to be a template generic in any of the three
|
||||
contexts.
|
||||
|
||||
#### Contextual Defaults
|
||||
|
||||
- **Compile-time entities**: Parameters to entities like `interface`, `impl`,
|
||||
and `class` are checked generic parameters by default.
|
||||
|
||||
```carbon
|
||||
interface I(T: type) { ... } // T is a checked generic parameter
|
||||
```
|
||||
|
||||
They can be marked as `template`:
|
||||
|
||||
```carbon
|
||||
class C(template T: type) { ... } // T is a template generic parameter
|
||||
```
|
||||
|
||||
- **Deduced function parameters**: Parameters in `[]` for functions default to
|
||||
checked generic parameters.
|
||||
|
||||
```carbon
|
||||
fn F[T: type](arg: T); // T is a checked generic parameter
|
||||
```
|
||||
|
||||
They can be marked as `template`:
|
||||
|
||||
```carbon
|
||||
fn F[template T: type](arg: T); // T is a template generic parameter
|
||||
```
|
||||
|
||||
If we ever add deduced runtime parameters (anticipated for scoped parameters
|
||||
like allocators), they would be marked with the `runtime` keyword:
|
||||
|
||||
```carbon
|
||||
fn F[runtime heap: Heap](T: type, arg: T) -> T*; // heap is a runtime parameter
|
||||
```
|
||||
|
||||
- **Explicit function parameters**: Parameters in `()` for functions default
|
||||
to runtime parameters.
|
||||
|
||||
```carbon
|
||||
fn F(arg: i32); // arg is a runtime parameter
|
||||
```
|
||||
|
||||
They can be marked as `generic` or `template`:
|
||||
|
||||
```carbon
|
||||
fn F(generic T: type, arg: T); // T is a checked generic parameter
|
||||
```
|
||||
|
||||
Keywords are only allowed where needed to override the contextual default. This
|
||||
avoids confusion and ensures consistency.
|
||||
|
||||
The checked generic default for deduced parameters applies only to declared
|
||||
parameters in the `[]` list. Lambda captures, which also appear in `[]` but are
|
||||
syntactically distinguished (they are not declared names), are not affected by
|
||||
this default. Instead, a capture retains the phase of the expression being
|
||||
captured, which we expect to be important for the usability of lambdas.
|
||||
|
||||
### Associated Constants
|
||||
|
||||
Associated constants in interfaces require no extra keywords. Their meaning is
|
||||
guided by the context of the interface definition itself.
|
||||
|
||||
The conceptual model is that an interface is essentially a class whose phase is
|
||||
inherently the symbolic compile-time (generic) phase. As a consequence, its
|
||||
fields (the associated constants) act as generic constants naturally, and
|
||||
placing an additional phase keyword on them would be redundant and disallowed.
|
||||
The same logic applies when implementing those constants in an `impl`, which
|
||||
already uses distinct syntax to assign them.
|
||||
|
||||
### Extended Types
|
||||
|
||||
This proposal replaces the concept of "forms" (as described in
|
||||
[`/docs/design/values.md`](/docs/design/values.md)) with **extended types**.
|
||||
|
||||
The term "forms" was originally used to generalize types to include expression
|
||||
category, phase, and value. However, this terminology was found to conflict
|
||||
confusingly with the concept of "unformed state". To resolve this, we move to a
|
||||
model where these are considered "extended types", connecting them more directly
|
||||
to the type system while preserving `type` for standard object types.
|
||||
|
||||
Under this new design:
|
||||
|
||||
- The literal expression `form(expr)` is renamed to `exttype(expr)`.
|
||||
- The type of extended types is `Core.ExtType` (replacing `Core.Form`).
|
||||
- The previous `:?` syntax for deduced form bindings is suggested to be
|
||||
replaced by a binding modifier `fwd`. This modifier causes the
|
||||
right-hand-side of the binding's `:` to be converted to `Core.ExtType`
|
||||
rather than `type`. This is a suggested (but not fully decided) direction
|
||||
for pending proposal #5389 to go with the syntax.
|
||||
- `fwd` is also suggested for use in the return signature (for example,
|
||||
`-> fwd T`) to forward the extended type information. Note that this may end
|
||||
up being more significant than just a syntactic replacement: it remains to
|
||||
be decided in proposal #5389 whether `fwd` must appear directly after the
|
||||
`->` (matching how `->?` works in that proposal currently) or if it can be
|
||||
used within tuple syntax in the return type, similar to how `ref` is
|
||||
allowed.
|
||||
|
||||
This approach allows us to reclaim high-value punctuation like `?` for other
|
||||
uses (such as optional types) while providing a more explicit and less
|
||||
punctuated syntax for advanced generic programming.
|
||||
|
||||
Example:
|
||||
|
||||
```carbon
|
||||
fn F[T: Core.ExtType](fwd arg: T) -> fwd T;
|
||||
```
|
||||
|
||||
> **Open question:** Should we require the `fwd` modifier on call arguments as
|
||||
> well, analogously to how `ref` is required on arguments for reference
|
||||
> parameters?
|
||||
|
||||
#### Future Work on Extended Types
|
||||
|
||||
Once the design for extended types in proposal #5389 is more complete, we may
|
||||
also want to replace `Core.ExtType` with a new built-in keyword `exttype` for
|
||||
the type of extended types, and potentially replace `exttype(expr)` literals
|
||||
with library entities. This would make `exttype` analogous to `type` in the
|
||||
grammar.
|
||||
|
||||
## Rationale
|
||||
|
||||
This proposal advances the following Carbon goals and principles:
|
||||
|
||||
- [**Code that is easy to read, understand, and write**](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write):
|
||||
Removing dense punctuation in favor of keywords with meaningful names makes
|
||||
code less like ASCII-art and more immediately readable. The contextual
|
||||
defaults are carefully chosen to match what nearly all code uses in
|
||||
practice, keeping keywords sparse while remaining explicit when they are
|
||||
needed.
|
||||
|
||||
- [**Software and language evolution**](/docs/project/goals.md#software-and-language-evolution):
|
||||
Reclaiming `!` and `?` as punctuation opens up syntax space for other
|
||||
high-value features. In particular, `!` is a strong candidate for operations
|
||||
that are required to succeed or terminate (for example, unwrapping
|
||||
optionals), which would have been visually ambiguous if `!` were also used
|
||||
for generics.
|
||||
|
||||
- [**Progressive disclosure**](/docs/project/principles/progressive_disclosure.md):
|
||||
The contextual defaults allow learners to work with generic interfaces and
|
||||
classes without needing to understand or type phase keywords at first.
|
||||
Keywords only become relevant when departing from the defaults, which is a
|
||||
rarer, more advanced case. This mirrors how Carbon teaches other concepts
|
||||
progressively.
|
||||
|
||||
- [**Prefer only one way to do a given thing**](/docs/project/principles/one_way.md):
|
||||
Disallowing redundant phase keywords (those that match the contextual
|
||||
default) ensures there is exactly one canonical way to write each parameter
|
||||
declaration, consistent with how Carbon handles other defaults such as
|
||||
`public` access.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
### Keep the `:!` syntax
|
||||
|
||||
One alternative was to retain the existing punctuation-based syntax where `:!`
|
||||
is used to denote checked generic parameters and template generic parameters.
|
||||
|
||||
- **Advantages**:
|
||||
- Maintains continuity with the previously established design.
|
||||
- Is very concise, requiring no keywords.
|
||||
- A parameter's phase is encoded explicitly in its syntax and is
|
||||
independent of its position, so moving a parameter between the `[]` and
|
||||
`()` lists does not change its meaning. Under the proposed contextual
|
||||
defaults, such a move changes the default phase and requires adding a
|
||||
keyword to preserve it, making this kind of refactoring of a function
|
||||
signature slightly less straightforward.
|
||||
- **Disadvantages**:
|
||||
- The syntax makes code look like "ASCII-art" due to the high density of
|
||||
punctuation.
|
||||
- The connection between `!` and generics is not an effective mnemonic.
|
||||
- It blocks other potential uses for `!`, such as for operations that are
|
||||
required to succeed or terminate (for example, unwrapping optionals).
|
||||
- It does not scale well to controlling the phase of function parameters.
|
||||
- **Decision**: This alternative was rejected because the disadvantages in
|
||||
readability and extensibility outweigh the benefit of conciseness, including
|
||||
the modest refactoring cost noted above. The leads decided to move towards
|
||||
keywords and contextual defaults.
|
||||
|
||||
### Alternative keyword names
|
||||
|
||||
Several alternative keywords were considered for the three phase keywords.
|
||||
|
||||
For `generic`, the key candidates considered were:
|
||||
|
||||
- **`symbolic`**: Reflects the technical description of symbolic compile-time
|
||||
evaluation.
|
||||
- **`comptime`**: Reflects when the value is known (compile time).
|
||||
- **`checked`**: Reflects the semantic behavior that these parameters are
|
||||
type-checked at the definition site.
|
||||
|
||||
For `runtime`, the main candidate discussed was:
|
||||
|
||||
- **`dynamic`**: Reflects that values are dynamically determined at runtime.
|
||||
|
||||
Looking across these options:
|
||||
|
||||
- **Advantages**:
|
||||
- `symbolic` is more technically precise for compiler experts as it
|
||||
reflects the symbolic evaluation phase.
|
||||
- `comptime` is a familiar pattern from other modern systems languages.
|
||||
- `checked` is highly precise about the checking model used, matches the
|
||||
terminology we use in the design, and matches the structure of
|
||||
`template`.
|
||||
- `dynamic` uses a term that is recognizable for runtime behavior.
|
||||
- **Disadvantages**:
|
||||
- None of the alternatives offer as strong a mnemonic connection to the
|
||||
_programming concepts_ they represent as the chosen keywords.
|
||||
- `symbolic` is inaccessible jargon and less teachable to developers not
|
||||
familiar with compiler or type theory terminology.
|
||||
- `comptime` describes _when_ the value is known, not _why_ or _how_ it is
|
||||
used, lacking a connection to generic programming.
|
||||
- `checked` focuses on the implementation mechanism (checking) rather than
|
||||
the programmer's intent (generic programming) and loses the immediate
|
||||
familiarity of the term `generic`.
|
||||
- `dynamic` conflicts with the well-established use in dynamic dispatch
|
||||
(for example, Rust's `dyn`), making it a poor fit for Carbon.
|
||||
- **Decision**: The chosen keywords (`generic`, `template`, `runtime`) were
|
||||
found to best balance accessibility with precision. `generic` in particular
|
||||
connects directly to the well-known concept of generic programming, making
|
||||
it both familiar and teachable.
|
||||
|
||||
### Use `template generic` instead of just `template`
|
||||
|
||||
An alternative considered was to require `template generic` (two keywords) for
|
||||
template generic parameters, and `generic` for checked generic parameters, to
|
||||
make it clear that templates _are_ generics.
|
||||
|
||||
Under this model, the terminology is that we have "generic parameters" that come
|
||||
in two semantic forms: "checked generic parameters" and "template generic
|
||||
parameters". Both of these are considered "generic parameters". The _default_
|
||||
semantic is checked generic parameters, so when a parameter is marked `generic`
|
||||
(or defaults to it), it gets that semantic. The rejected alternative would be to
|
||||
use both keywords as `template generic` for the template case, rather than
|
||||
omitting the `generic` keyword and just using `template`.
|
||||
|
||||
- **Advantages**:
|
||||
- The syntax would more strictly reflect the terminology that templates
|
||||
are a kind of generic.
|
||||
- **Disadvantages**:
|
||||
- It makes the syntax significantly more verbose in a case where there is
|
||||
nothing else that could be meant. The _only_ way to have the `template`
|
||||
keyword on a parameter is for it to be a generic parameter, so adding
|
||||
`generic` provides no additional information.
|
||||
- **Decision**: Rejected in favor of using just `template` to avoid
|
||||
unnecessary verbosity.
|
||||
|
||||
### Context-independent syntax
|
||||
|
||||
An alternative approach proposed making the phase of every parameter fully
|
||||
explicit in its declaration, without any contextual defaults. The specific
|
||||
proposal from the discussion used a `static` modifier for compile-time value
|
||||
parameters, so that the phase could always be read directly from the declaration
|
||||
without needing to know whether the parameter appears in `()` or `[]`:
|
||||
|
||||
```carbon
|
||||
fn MakeArray(T: type, static Length: i32) -> Array(T, Length);
|
||||
fn ReverseArray[T: type, static Length: i32](ref a: Array(T, Length));
|
||||
```
|
||||
|
||||
This is analogous to how `ref` and `val` modifiers make value categories
|
||||
explicit today, with the goal of making each parameter declaration
|
||||
self-contained.
|
||||
|
||||
- **Advantages**:
|
||||
- Each parameter declaration contains all the information needed to
|
||||
determine its phase, without requiring knowledge of the surrounding
|
||||
syntactic context.
|
||||
- Avoids any cognitive overhead from remembering contextual defaults.
|
||||
- **Disadvantages**:
|
||||
- `static` is heavily overloaded in C++, covering storage duration, class
|
||||
membership, and file-scope linkage, which creates significant confusion
|
||||
for C++ developers migrating to Carbon.
|
||||
- Types like integers can be used in both runtime and compile-time
|
||||
contexts (for example, as array sizes). Requiring an explicit `static`
|
||||
keyword for compile-time integers creates pressure towards having
|
||||
separate compile-time and runtime vocabulary types, which Carbon has
|
||||
aimed to avoid to keep the type vocabulary compact.
|
||||
- **Decision**: Rejected in favor of contextual defaults. The chosen defaults
|
||||
align with what nearly all code does in practice (most explicit function
|
||||
parameters are runtime, and most parameters to interfaces and classes are
|
||||
checked generics), so keywords remain sparse while still being explicit when
|
||||
non-default behavior is needed. The `static` keyword in particular was found
|
||||
to have significant overloading concerns coming from C++.
|
||||
|
||||
### Erased model for generics
|
||||
|
||||
An alternative approach proposed using _type erasure_ as the foundational mental
|
||||
model for generic parameters, paralleling the way languages like Java implement
|
||||
generics. Under this model, a generic type parameter is said to be "erased" at
|
||||
runtime: the type information is available at compile time but not preserved in
|
||||
the runtime representation. This would use `erased` as the keyword instead of
|
||||
`generic`:
|
||||
|
||||
```carbon
|
||||
// T is erased (available at compile time, erased at runtime)
|
||||
interface I(T: type) {
|
||||
fn Op(self, arg: T) -> T;
|
||||
}
|
||||
|
||||
// Explicit erased parameter in a function
|
||||
fn ScopedParams[runtime heap: Heap](erased T: type) -> T*;
|
||||
```
|
||||
|
||||
This model has particular implications for _associated constants_ in interfaces.
|
||||
Under the erased model, associated constants would be thought of as values that
|
||||
are erased from the runtime representation (present at compile time but not
|
||||
available at runtime), rather than as fields of a compile-time class that are
|
||||
inherently generic by context.
|
||||
|
||||
- **Advantages**:
|
||||
- "Erased" is technically accurate in certain respects: when using Carbon
|
||||
checked generics (as opposed to template generics), the specific type
|
||||
bound to a checked generic parameter is not available at runtime.
|
||||
- Connects to a concept familiar from type erasure literature and
|
||||
languages like Java, where this is the standard implementation model for
|
||||
generics.
|
||||
- **Disadvantages**:
|
||||
- The term "erased" focuses on what is _lost_ at runtime rather than what
|
||||
the concept _enables_; it describes an implementation detail rather than
|
||||
the programming paradigm. The keyword `generic` more directly connects
|
||||
to the reason a developer reaches for this feature.
|
||||
- Carbon's generics are not purely erasure-based: checked generics may use
|
||||
erasure techniques, but templates generate fully specialized code. Using
|
||||
"erased" would imply a single implementation strategy that doesn't
|
||||
capture the full picture of Carbon's compile-time programming model.
|
||||
- The interface-as-compile-time-class model chosen for Carbon makes
|
||||
associated constants more naturally generic: an interface is treated as
|
||||
a class whose "evaluation time" is inherently the symbolic compile-time
|
||||
phase, so its fields act as generic constants by context, with no extra
|
||||
keyword required. The erased model framing fits less cleanly with this
|
||||
interface design.
|
||||
- The `generic` versus `template` terminology split, which is the clearest
|
||||
way to distinguish the two distinct kinds of compile-time parameters in
|
||||
Carbon, is obscured by "erased" framing, since templates are not erased.
|
||||
- **Decision**: Rejected in favor of the `generic`/`template` split and the
|
||||
interface-as-compile-time-class model. The team preferred a terminology that
|
||||
describes the _programming concept_ rather than an implementation detail,
|
||||
and found the model where interface fields are inherently generic by context
|
||||
to be more intuitive and consistent with the rest of the design.
|
||||
|
||||
### Context-sensitive defaults based on parameter type
|
||||
|
||||
One alternative suggested was to make explicit function parameters default to
|
||||
checked generic if they cannot be represented at runtime (such as types). This
|
||||
would allow omitting `generic` even in the explicit `()` parameter list when the
|
||||
parameter type makes the phase unambiguous:
|
||||
|
||||
```carbon
|
||||
fn F1[Q: type](arg1: Q, QQ: type, arg2: QQ) -> (Q, QQ);
|
||||
```
|
||||
|
||||
- **Advantages**:
|
||||
- Allows omitting keywords in more cases, reducing verbosity further.
|
||||
- Creates a natural feel where `T: type` always implies compile-time,
|
||||
regardless of position.
|
||||
- **Disadvantages**:
|
||||
- Types like integers can be used in both runtime and compile-time
|
||||
contexts, for example as array size parameters. Requiring `generic` for
|
||||
compile-time integers but not for compile-time types creates an
|
||||
inconsistent rule that would be difficult to learn.
|
||||
- This creates pressure towards having separate compile-time and runtime
|
||||
vocabulary types (for example, a compile-time integer versus a runtime
|
||||
integer), which Carbon has aimed to avoid to keep the type vocabulary
|
||||
compact.
|
||||
- Determining whether a keyword is required depends on the type of the
|
||||
parameter, which requires resolving imports before the parser can
|
||||
determine the meaning. This loses the benefit of a simple, purely local
|
||||
syntactic rule, the same benefit that `:!` provided today.
|
||||
- **Decision**: Rejected due to the added complexity, the inconsistency
|
||||
introduced by types usable in both phases, and the loss of a simple local
|
||||
syntactic rule. The chosen defaults (based on syntactic position, not
|
||||
parameter type) are easier to explain and implement.
|
||||
|
||||
### Allow redundant phase keywords
|
||||
|
||||
Another alternative was to allow keywords matching the contextual default to be
|
||||
used optionally, for example allowing `generic T: type` in a deduced parameter
|
||||
list where checked generic is already the default semantic.
|
||||
|
||||
- **Advantages**:
|
||||
- Provides a simpler mental model for beginners: the rule would be "just
|
||||
always write the keyword if you want to be explicit" rather than "write
|
||||
it only when non-default."
|
||||
- Would allow users to treat the shorthand as a style rule enforced by a
|
||||
linter, rather than a language rule enforced by the compiler.
|
||||
- Supports a progressive learning path where users learn the explicit form
|
||||
first and adopt the shorthand later.
|
||||
- **Disadvantages**:
|
||||
- Creates two syntactically valid ways to say the same thing, which
|
||||
confuses readers who may wonder why the author was explicit about the
|
||||
default, suggesting intentionality where there is none.
|
||||
- Inconsistent with how Carbon handles other defaults. For example, Carbon
|
||||
does not allow writing `public` in a context where `public` is already
|
||||
the default access, for the same reason: explicit statement of a default
|
||||
implies it was chosen deliberately, which is misleading.
|
||||
- **Decision**: Rejected to ensure consistency and avoid confusion, following
|
||||
the established Carbon pattern of not allowing redundant keywords that match
|
||||
a contextual default. The compiler enforcing this as an error (rather than a
|
||||
linter warning) means the rule is consistently applied and the absence of a
|
||||
keyword reliably communicates "this is the default" across all code.
|
||||
|
||||
### Use `exprtype` and `expr` keywords
|
||||
|
||||
One alternative considered for replacing "forms" was to use the terminology
|
||||
"expression types" with `exprtype` as the bottom type and `expr` as the binding
|
||||
modifier.
|
||||
|
||||
- **Advantages**:
|
||||
- Maintains progressive disclosure by keeping `type` as the primary term
|
||||
for object types and qualifying it as `exprtype` for expression types.
|
||||
- Connects directly to the concept of expression metadata.
|
||||
- **Disadvantages**:
|
||||
- It has a slightly awkward construction where the narrower term ("type")
|
||||
is the base term, and the broader term ("expression type") is qualified.
|
||||
- It confusingly implies that it refers to the _type of the expression_,
|
||||
while we want that use of the term "type" to not include the extended
|
||||
information.
|
||||
- It also implies with `expr` on a binding that the expression itself is
|
||||
bound and captured, rather than being evaluated first. Hard to explain
|
||||
that this matches the _evaluated_ expression.
|
||||
- **Decision**: This alternative was rejected in favor of the **Extended
|
||||
Types** model. The team preferred "extended types" as the terminology anchor
|
||||
(yielding `Core.ExtType`). For the binding modifier, `fwd` was chosen
|
||||
because it connects to the use case of forwarding extended type information
|
||||
(similar to C++ `std::forward`) and fits well as a three-letter keyword
|
||||
similar to `ref`, `var`, and `val`.
|
||||
@@ -1768,7 +1768,7 @@ auto CategoryConverter::DoStep(const SemIR::InstId expr_id,
|
||||
return Done{SemIR::ErrorInst::InstId};
|
||||
|
||||
case SemIR::ExprCategory::Dependent:
|
||||
context_.TODO(expr_id, "Support symbolic expression forms");
|
||||
context_.TODO(expr_id, "Support symbolic extended types");
|
||||
return Done{SemIR::ErrorInst::InstId};
|
||||
|
||||
case SemIR::ExprCategory::InPlaceInitializing:
|
||||
|
||||
Reference in New Issue
Block a user