From 6e6405c0bb2f0afc0342439767b77e23ec6472f9 Mon Sep 17 00:00:00 2001 From: Chandler Carruth Date: Mon, 29 Jun 2026 15:51:57 -0700 Subject: [PATCH] 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 --- docs/design/README.md | 66 ++- docs/design/expressions/member_access.md | 49 +- docs/design/functions.md | 12 + docs/design/generics/terminology.md | 68 ++- .../lexical_conventions/symbolic_tokens.md | 6 + docs/design/pattern_matching.md | 115 ++-- docs/design/values.md | 381 +++++++------ docs/design/variadics.md | 13 + docs/project/faq.md | 44 +- ...d-with-keywords-and-contextual-defaults.md | 535 ++++++++++++++++++ toolchain/check/convert.cpp | 2 +- 11 files changed, 981 insertions(+), 310 deletions(-) create mode 100644 proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md diff --git a/docs/design/README.md b/docs/design/README.md index 96974e72b559..a81a414c543a 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -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]() 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]() +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) { diff --git a/docs/design/expressions/member_access.md b/docs/design/expressions/member_access.md index a686486c5ba2..8209e09b5ac2 100644 --- a/docs/design/expressions/member_access.md +++ b/docs/design/expressions/member_access.md @@ -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 diff --git a/docs/design/functions.md b/docs/design/functions.md index f3a4a16365ca..aea4920d34e9 100644 --- a/docs/design/functions.md +++ b/docs/design/functions.md @@ -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) diff --git a/docs/design/generics/terminology.md b/docs/design/generics/terminology.md index b438199ef643..6a158a9c0f05 100644 --- a/docs/design/generics/terminology.md +++ b/docs/design/generics/terminology.md @@ -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) @@ -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 - "\`:` \" syntax (or "\"). -- **Checked parameters** are designated using `:!` between the name and the - type (so it is "\`:!` \"). -- **Template parameters** are designated using "`template` \`:!` - \". +- **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) diff --git a/docs/design/lexical_conventions/symbolic_tokens.md b/docs/design/lexical_conventions/symbolic_tokens.md index f1ecf731b2fa..eea088b08a0f 100644 --- a/docs/design/lexical_conventions/symbolic_tokens.md +++ b/docs/design/lexical_conventions/symbolic_tokens.md @@ -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) diff --git a/docs/design/pattern_matching.md b/docs/design/pattern_matching.md index 7947d8e5cd87..11c26210a84b 100644 --- a/docs/design/pattern_matching.md +++ b/docs/design/pattern_matching.md @@ -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) diff --git a/docs/design/values.md b/docs/design/values.md index 4aaf57a2b70f..20eb62855bbd 100644 --- a/docs/design/values.md +++ b/docs/design/values.md @@ -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 `` 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 `` 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 `` 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 `` 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 `(, , ... )` 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 = , .b = , ... .z = }` 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 `(, , ... )`. -- 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 = , .b = , ... .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 diff --git a/docs/design/variadics.md b/docs/design/variadics.md index 076fbbb8ad59..ee9b1e4662d5 100644 --- a/docs/design/variadics.md +++ b/docs/design/variadics.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. diff --git a/docs/project/faq.md b/docs/project/faq.md index 5385b85f6439..054bac36dc52 100644 --- a/docs/project/faq.md +++ b/docs/project/faq.md @@ -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()`). 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`? diff --git a/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md b/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md new file mode 100644 index 000000000000..ca08ed392c92 --- /dev/null +++ b/proposals/p007254-replace-and-with-keywords-and-contextual-defaults.md @@ -0,0 +1,535 @@ +# Replace `:!` and `:?` with keywords and contextual defaults + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/7254) + + + +## 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) + + + +## 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`. diff --git a/toolchain/check/convert.cpp b/toolchain/check/convert.cpp index 618c3f8712b7..4a65817c44af 100644 --- a/toolchain/check/convert.cpp +++ b/toolchain/check/convert.cpp @@ -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: