-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Expression form basics #5545
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Expression form basics #5545
Changes from 18 commits
be8587e
32c4e1c
dcd79b6
5dcbfa2
c02677b
e5128ce
fe90de1
7c5efcf
8e9803b
3ef17ee
a418308
10bc415
3a092a6
ea46717
41e6eec
9f4ec05
6bcbb95
97f31e6
76bc89e
73efff9
dbe872b
f3b4735
5d59604
23cb28f
dff427f
5d6ecf0
6d8dc75
7e7745c
c68460c
c2b26e6
95db3c1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,7 +18,6 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| - [Name binding patterns](#name-binding-patterns) | ||
| - [Unused bindings](#unused-bindings) | ||
| - [Alternatives considered](#alternatives-considered-1) | ||
| - [Compile-time bindings](#compile-time-bindings) | ||
| - [`auto` and type deduction](#auto-and-type-deduction) | ||
| - [Alternatives considered](#alternatives-considered-2) | ||
| - [`var`](#var) | ||
|
|
@@ -130,14 +129,16 @@ fn F() { | |
|
|
||
| A name binding pattern is a pattern. | ||
|
|
||
| - _binding-pattern_ ::= _identifier_ `:` _expression_ | ||
| - _binding-pattern_ ::= `ref`? _identifier_ `:` _expression_ | ||
| - _binding-pattern_ ::= `template`? _identifier_ `:!` _expression_ | ||
| - _proper-pattern_ ::= _binding-pattern_ | ||
|
|
||
| A name binding pattern declares a _binding_ with a name specified by the | ||
| _identifier_, which can be used as an expression. If the binding pattern is | ||
| enclosed by a `var` pattern, it is a _reference binding pattern_, and the | ||
| binding is a durable reference expression. Otherwise, it is a _value binding | ||
| pattern_, and the binding is a value expression. | ||
| prefixed with `ref` or enclosed by a `var` pattern, it is a _reference binding | ||
| pattern_, and otherwise it is a _value binding pattern_. A binding pattern | ||
| enclosed by a `var` pattern cannot have a `ref` prefix, because it would be | ||
| redundant. | ||
|
|
||
| A _variable binding pattern_ is a special kind of reference binding pattern, | ||
| which is the immediate subpattern of its enclosing `var` pattern. | ||
|
|
@@ -146,15 +147,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. | ||
|
|
||
| The type of the binding is specified by the _expression_. If the pattern is a | ||
| value binding pattern, the scrutinee is implicitly converted to a value | ||
| expression of that type if necessary, and the binding is _bound_ to the | ||
| converted value. If the pattern is a reference binding pattern, the enclosing | ||
| `var` pattern will ensure that the scrutinee is already a durable reference | ||
| expression with the specified type, and the binding is bound directly to it. | ||
|
|
||
| A use of a value binding is a value expression of the declared type, and a use | ||
| of a reference binding is a durable reference expression of the declared type. | ||
| 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`. | ||
|
|
||
| The binding declared by a binding pattern has a | ||
| [primitive form](values.md#expression-forms) with the following components: | ||
|
|
||
| - The type is _expression_. | ||
| - The category is "value" if the pattern is a value binding pattern, "owning | ||
| durable reference" if it's a variable binding pattern, or "non-owning | ||
| durable reference" if it's a non-variable reference binding pattern. | ||
| - The phase is "runtime", "symbolic", or "template" depending on whether the | ||
| pattern is a runtime, symbolic, or template binding pattern. | ||
|
|
||
| During pattern matching, the scrutinee is converted as needed to have the same | ||
| form, and then the binding is _bound_ to the result of these conversions. This | ||
| makes a runtime or template binding an 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. | ||
|
|
||
| ```carbon | ||
| fn F() -> i32 { | ||
|
|
@@ -169,46 +185,19 @@ fn F() -> i32 { | |
| } | ||
| ``` | ||
|
|
||
| When a new object needs to be created for the binding, the lifetime of the bound | ||
| value matches the scope of the binding. | ||
|
|
||
| ```carbon | ||
| class NoisyDestructor { | ||
| fn Make() -> Self { return {}; } | ||
| impl i32 as ImplicitAs(NoisyDestructor) { | ||
| fn Convert[me: i32]() -> Self { return Make(); } | ||
| } | ||
| destructor { | ||
| Print("Destroyed!"); | ||
| } | ||
| } | ||
|
|
||
| fn G() { | ||
| // Does not print "Destroyed!". | ||
| let n: NoisyDestructor = NoisyDestructor.Make(); | ||
| Print("Body of G"); | ||
| // Prints "Destroyed!" here. | ||
| } | ||
|
|
||
| fn H(n: i32) { | ||
| // Does not print "Destroyed!". | ||
| let (v: NoisyDestructor, w: i32) = (n, n); | ||
| Print("Body of H"); | ||
| // Prints "Destroyed!" here. | ||
| } | ||
| ``` | ||
|
|
||
| #### Unused bindings | ||
|
|
||
| A syntax like a binding but with `_` in place of an identifier, or `unused` | ||
| before the name, can be used to ignore part of a value. Names that are qualified | ||
| with the `unused` keyword are visible for name lookup but uses are invalid, | ||
| including when they cause ambiguous name lookup errors. If attempted to be used, | ||
| a compiler error will be shown to the user, instructing them to either remove | ||
| the `unused` qualifier or remove the use. | ||
| before the name, [discards](values.md#form-conversions) the scrutinee. Names | ||
| that are qualified with the `unused` keyword are visible for name lookup but | ||
| uses are invalid, including when they cause ambiguous name lookup errors. If | ||
| attempted to be used, a compiler error will be shown to the user, instructing | ||
| them to either remove the `unused` qualifier or remove the use. | ||
|
|
||
| - _binding-pattern_ ::= `_` `:` _expression_ | ||
| - _binding-pattern_ ::= `template`? `_` `:!` _expression_ | ||
| - _binding-pattern_ ::= `unused` _identifier_ `:` _expression_ | ||
| - _binding-pattern_ ::= `unused` `template`? _identifier_ `:!` _expression_ | ||
|
|
||
| ```carbon | ||
| fn F(n: i32) { | ||
|
|
@@ -245,27 +234,6 @@ fn J(unused n: i32); | |
| - [Anonymous, named identifiers](/proposals/p2022.md#anonymous-named-identifiers) | ||
| - [Attributes](/proposals/p2022.md#attributes) | ||
|
|
||
| #### Compile-time bindings | ||
|
|
||
| A `:!` can be used in place of `:` for a binding that is usable at compile time. | ||
|
|
||
| - _compile-time-pattern_ ::= `template`? _identifier_ `:!` _expression_ | ||
| - _compile-time-pattern_ ::= `template`? `_` `:!` _expression_ | ||
| - _compile-time-pattern_ ::= `unused` `template`? _identifier_ `:!` | ||
| _expression_ | ||
| - _proper-pattern_ ::= _compile-time-pattern_ | ||
|
|
||
| ```carbon | ||
| // ✅ `F` takes a symbolic facet parameter `T` and a parameter `x` of type `T`. | ||
| fn F(T:! type, x: T) { | ||
| var v: T = x; | ||
| } | ||
| ``` | ||
|
|
||
| The `template` keyword indicates the binding pattern is introducing a template | ||
| binding, so name lookups into the binding will not be fully resolved until its | ||
| value is known. | ||
|
|
||
| #### `auto` and type deduction | ||
|
|
||
| The `auto` keyword is a placeholder for a unique deduced type. | ||
|
|
@@ -318,10 +286,15 @@ scrutinee. | |
|
|
||
| - _proper-pattern_ ::= `var` _proper-pattern_ | ||
|
|
||
| A `var` pattern matches when its nested pattern matches. The type of the storage | ||
| is the resolved type of the nested _pattern_. Any binding patterns within the | ||
| nested pattern are reference binding patterns, and their bindings refer to | ||
| portions of the corresponding storage rather than to the scrutinee. | ||
| The scrutinee is expected to have the same type as the resolved type of the | ||
| nested _proper-pattern_, and it is expected to be a runtime-phase owning | ||
| ephemeral reference expression. The scrutinee expression is converted as needed | ||
| to satisfy those expectations, and the `var` pattern takes ownership of the | ||
| referenced object, promotes it to an owning _durable_ reference expression, and | ||
| matches the nested _proper-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). | ||
|
Comment on lines
+310
to
+311
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Notably: this means that for an owning ephemeral reference expression, we don't only require that we know statically that the object is complete, but we also require that we know which object it is (and ideally that there's only one and that the Example of a case where things get interesting: var a: A = if cond then MakeOwningEphemeralReference() else MakeDifferentOwningEphemeralReference();Assuming that I assume the reason we're doing it this way is so that nested fn F() -> (A, B);
// No copies or moves!
let (var a: A, var b: B) = F();Right?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this is motivated by destructuring into nested vars, and the rest of what you said sounds right. Are you asking for any changes?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be useful to explicitly mention this, as I don't think it's a consequence of the current definition of "entire" and seems like an important nuance in the term. For example, with the current definition as "statically known to refer to a complete object", it might be reasonable to the ability to specify this in a form: ... but we wouldn't know what storage we need to lifetime extend for (though perhaps we could conservatively extend all temporaries reachable by the call?).
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, how's this? Note that I've also cleaned up some unrelated errors that I noticed in the surrounding text. |
||
|
|
||
| ```carbon | ||
| fn F(p: i32*); | ||
|
|
@@ -358,8 +331,12 @@ A _tuple-pattern_ containing no commas is treated as grouping parens: the | |
| contained _proper-pattern_ is matched directly against the scrutinee. Otherwise, | ||
| the behavior is as follows. | ||
|
|
||
| A tuple pattern is matched left-to-right. The scrutinee is required to be of | ||
| tuple type. | ||
| The scrutinee is required to be of tuple type, with the same arity as the number | ||
| of nested _proper-patterns_. It is converted to a tuple form by | ||
| [form decomposition](values.md#form-conversions), and then each nested | ||
| _proper-pattern_ in left-to-right order is matched against the corresponding | ||
| element of the converted scrutinee's [outcome](values.md#expression-forms). The | ||
| tuple pattern matches if all of these sub-matches succeed. | ||
|
|
||
| Note that a tuple pattern must contain at least one _proper-pattern_. Otherwise, | ||
| it is a tuple-valued expression. However, a tuple pattern and a corresponding | ||
|
|
@@ -392,10 +369,18 @@ match ({.a = 1, .b = 2}) { | |
| } | ||
| ``` | ||
|
|
||
| The scrutinee is required to be of struct type, and to have the same set of | ||
| field names as the pattern. The pattern is matched left-to-right, meaning that | ||
| matching is performed in the field order specified in the pattern, not in the | ||
| field order of the scrutinee. This is consistent with the behavior of matching | ||
| 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, for each | ||
| subpattern of the struct pattern in left-to-right order, the subpattern is | ||
| matched with the same-named element of the converted scrutinee's | ||
| [outcome](values.md#expression-forms). If the scrutinee outcome has any field | ||
| names not present in the pattern, those sub-outcomes are | ||
| [discarded](values.md#form-conversions) in lexical order if the pattern has a | ||
| trailing `_`, or diagnosed as an error if it does not. The struct pattern | ||
josh11b marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| matches if all of these sub-matches succeed. | ||
|
|
||
| Note that the left-to-right order is consistent with the behavior of matching | ||
| against a struct-valued expression, where the expression pattern becomes the | ||
| left operand of the `==` and so determines the order in which `==` comparisons | ||
| for fields are performed. | ||
|
|
@@ -409,6 +394,9 @@ match ({.a = 1, .b = 2}) { | |
| } | ||
| ``` | ||
|
|
||
| Likewise, `ref a: T` is synonymous with `.a = ref a: T`, and `var a: T` is | ||
| synonymous with `.a = var a: T`. | ||
|
|
||
| If some fields should be ignored when matching, a trailing `, _` can be added to | ||
| specify this: | ||
|
|
||
|
|
@@ -717,8 +705,10 @@ 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. | ||
|
|
||
| Any initializing expressions in the scrutinee of a `match` statement are | ||
| [materialized](values.md#temporary-materialization) before pattern matching | ||
| If the scrutinee expression's [form](values.md#expression-forms) contains any | ||
| primitive forms with category "initializing", they are converted to non-owning | ||
| ephemeral 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: | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.