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:
Chandler Carruth
2026-06-29 22:51:57 +00:00
committed by GitHub
co-authored by Geoff Romer
parent 366cb1010b
commit 6e6405c0bb
11 changed files with 981 additions and 310 deletions
+43 -23
View File
@@ -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) {
+25 -24
View File
@@ -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
+12
View File
@@ -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)
+40 -28
View File
@@ -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)
+70 -45
View File
@@ -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
View File
@@ -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
+13
View File
@@ -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
View File
@@ -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`.
+1 -1
View File
@@ -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: