Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
be8587e
Expression form basics
geoffromer May 27, 2025
32c4e1c
Typo fix
geoffromer May 27, 2025
dcd79b6
Apply suggestions from code review
geoffromer May 29, 2025
5dcbfa2
Add proposal doc
geoffromer May 28, 2025
c02677b
Respond to reviewer comments
geoffromer May 29, 2025
e5128ce
Respond to reviewer comments
geoffromer May 30, 2025
fe90de1
Respond to reviewer comments
geoffromer Jun 4, 2025
7c5efcf
Apply suggestions from code review
geoffromer Jun 5, 2025
8e9803b
Apply suggestions from code review
geoffromer Jun 5, 2025
3ef17ee
Respond to reviewer comments
geoffromer Jun 5, 2025
a418308
Apply suggestions from code review
geoffromer Jun 26, 2025
10bc415
Apply suggestions from code review
geoffromer Jun 26, 2025
3a092a6
Respond to reviewer comments
geoffromer Jun 30, 2025
ea46717
Apply suggestions from code review
geoffromer Jul 22, 2025
41e6eec
Respond to reviewer comments
geoffromer Jul 22, 2025
9f4ec05
Merge branch 'trunk' into forms-proposal
geoffromer Aug 6, 2025
6bcbb95
Major restructuring
geoffromer Aug 7, 2025
97f31e6
Respond to reviewer comments
geoffromer Aug 7, 2025
76bc89e
Miscellaneous fixes
geoffromer Aug 20, 2025
73efff9
Add missing alternatives-considered
geoffromer Aug 22, 2025
dbe872b
Respond to reviewer comments
geoffromer Sep 9, 2025
f3b4735
Respond to reviewer comments
geoffromer Sep 15, 2025
5d59604
Apply suggestions from code review
geoffromer Sep 29, 2025
23cb28f
Respond to reviewer comments
geoffromer Sep 29, 2025
dff427f
Apply suggestions from code review
geoffromer Sep 29, 2025
5d6ecf0
Respond to reviewer comments
geoffromer Oct 2, 2025
6d8dc75
Apply suggestions from code review
geoffromer Nov 17, 2025
7e7745c
Respond to reviewer comments
geoffromer Nov 18, 2025
c68460c
Update to reflect likely resolution of #6160
geoffromer Nov 20, 2025
c2b26e6
Change "owning" to "entire"
geoffromer Nov 20, 2025
95db3c1
Respond to reviewer comments, and fix some errors
geoffromer Nov 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions docs/design/classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -600,19 +600,21 @@ Assert(different_order.x == 3);
Assert(different_order.y == 2);
```

Initialization and assignment occur field-by-field. The order of fields is
determined from the target on the left side of the `=`. This rule matches what
we expect for classes with encapsulation more generally.
Initialization and assignment occur field-by-field. The overall order is that
the initializer is fully evaluated in the order it's written, and then each
field of the new object is initialized from the corresponding element of the
result, in the new object's field order. However, in some cases evaluation of
the initializer can directly initialize fields on the left-hand side, without
any intervening conversions. When that happens, the order of initialization of
those fields is determined by the evaluation order of the initializer. See
[here](values.md#type-conversions) for details.

**Open question:** What operations and in what order happen for assignment and
initialization?

- Is assignment just destruction followed by initialization? Is that
destruction completed for the whole object before initializing, or is it
interleaved field-by-field?
- When initializing to a literal value, is a temporary containing the literal
value constructed first or are the fields initialized directly? The latter
approach supports types that can't be moved or copied, such as mutex.
- Perhaps some operations are _not_ ordered with respect to each other?

### Operations performed field-wise
Expand Down
5 changes: 5 additions & 0 deletions docs/design/expressions/implicit_conversions.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- [Same type](#same-type)
- [Pointer conversions](#pointer-conversions)
- [Facet types](#facet-types)
- [Struct, tuple, and array types](#struct-tuple-and-array-types)
- [Consistency with `as`](#consistency-with-as)
- [Extensibility](#extensibility)
- [Alternatives considered](#alternatives-considered)
Expand Down Expand Up @@ -189,6 +190,10 @@ implicitly converted to the facet type `TT2` if `T`
[satisfies the requirements](../generics/details.md#subtyping-between-facet-types)
of `TT2`.

### Struct, tuple, and array types

See [here](/docs/design/values.md#type-conversions).

## Consistency with `as`

An implicit conversion of an expression `E` of type `T` to type `U`, when
Expand Down
47 changes: 31 additions & 16 deletions docs/design/expressions/member_access.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 and facets](#types-and-facets)
- [Types, forms, and facets](#types-forms-and-facets)
- [Tuple indexing](#tuple-indexing)
- [Values](#values)
- [Facet binding](#facet-binding)
Expand Down Expand Up @@ -121,13 +121,18 @@ 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, 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
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.

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.

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
succeed and to result in a member of a type, interface, or non-type facet, or a
Expand Down Expand Up @@ -183,11 +188,12 @@ class Bar {
}
```

### Types and facets
### Types, forms, and facets

If the first operand is a 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).
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
[#1293](https://github.com/carbon-language/carbon-lang/issues/1293).

Like the previous case, types (including
[facet types](/docs/design/generics/terminology.md#facet-type)) have member
Expand Down Expand Up @@ -222,6 +228,9 @@ 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.

### Tuple indexing

Tuple types have member names that are *integer-literal*s, not *word*s.
Expand Down Expand Up @@ -267,9 +276,9 @@ let n: i32 = p->(e);

### Values

If the first operand is not a type, 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, form, package, namespace, or facet, it does
not have member names, and a search is performed into the type of the first
operand instead.

```carbon
interface Printable {
Expand Down Expand Up @@ -717,16 +726,22 @@ fn SumIntegers(v: Vector(Integer)) -> Integer {
## Instance binding

Next, _instance binding_ may be performed. This associates an expression with a
particular object instance. For example, this is the value bound to `self` when
calling a method.
particular object or value instance. For example, this is the value bound to
`self` when calling a method.

For the simple member access syntax `x.y`, if `x` is an entity that has member
names, such as a namespace or a type, then `y` is looked up within `x`, and
instance binding is not performed. Otherwise, `y` is looked up within the type
of `x` and instance binding is performed if an instance member is found.

If instance binding is performed:
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 required to have
that 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 outcome of that conversion becomes the outcome of `x.f`.
All other elements are [discarded](/docs/design/values.md#form-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
Expand Down
152 changes: 71 additions & 81 deletions docs/design/pattern_matching.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand All @@ -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 {
Expand All @@ -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) {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The 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 var is reached whenever the object is created), because we're going to modify properties of the original object (by extending its lifetime) at the point where we perform the var binding.

Example of a case where things get interesting:

var a: A = if cond then MakeOwningEphemeralReference() else MakeDifferentOwningEphemeralReference();

Assuming that if-then-else preserves category (it doesn't currently), the lifetimes of the two materialized temporaries would be complicated here. C++ does support this kind of thing when binding the result of the corresponding ?: expression to a reference, and compilers implicitly generate and track flags to determine which of the two temporaries should be destroyed at the end of the scope. We can do the same kind of thing if necessary.

I assume the reason we're doing it this way is so that nested vars can do destructuring adopt-a-temporary magic in cases like:

fn F() -> (A, B);
// No copies or moves!
let (var a: A, var b: B) = F();

Right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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?

Copy link
Contributor

Choose a reason for hiding this comment

The 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:

fn F(a: entire ephemeral ref X, b: entire ephemeral ref X) -> entire ephemeral ref X;

... but we wouldn't know what storage we need to lifetime extend for

var v: X = F(MakeX(), MakeX());

(though perhaps we could conservatively extend all temporaries reachable by the call?).

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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*);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
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.
Expand All @@ -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:

Expand Down Expand Up @@ -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:

Expand Down
Loading
Loading