mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 13:50:10 +01:00
Document the phase of associated constants, which the discussions of
contextual phase defaults never covered: an associated constant is
always a checked generic binding. This is deliberately not presented as
a contextual default, because no other phase is possible for the
construct; correspondingly, no phase keyword (including `template`) is
allowed on one. Also rework the associated constants section of the
design README where the migration left a non-sequitur ("set to
compile-time values ... and so are defined using a `let` declaration"):
describe the `let` syntax and the binding's contextual phase separately.
Use consistent terminology for bindings versus constants. Bindings are
"checked" or "template" (generic) bindings, replacing the "symbolic
binding" and bare "generic binding" terms, so "symbolic" now only
describes constants and values. The "Symbolic facet bindings" section of
the generics details becomes "Checked facet bindings". The
expression-phase terms "symbolic constant" and "template constant" are
unchanged, and the binding-pattern definitions now name the constant
each kind binds. Template bindings are additionally described as
dependent and late checked, with each instantiation providing the
binding's value.
Also normalize the remaining "regular parameter" mentions to "runtime
parameter" to match the binding terminology, and describe the
compile-time "let template" as introducing a template generic binding
"T: C" whose uses are template constants.
Assisted-by: Claude Code
234 lines
8.1 KiB
Markdown
234 lines
8.1 KiB
Markdown
# `if` expressions
|
|
|
|
<!--
|
|
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
|
Exceptions. See /LICENSE for license information.
|
|
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
-->
|
|
|
|
<!-- toc -->
|
|
|
|
## Table of contents
|
|
|
|
- [Overview](#overview)
|
|
- [Syntax](#syntax)
|
|
- [Semantics](#semantics)
|
|
- [Finding a common type](#finding-a-common-type)
|
|
- [Symmetry](#symmetry)
|
|
- [Same type](#same-type)
|
|
- [Implicit conversions](#implicit-conversions)
|
|
- [Alternatives considered](#alternatives-considered)
|
|
- [References](#references)
|
|
|
|
<!-- tocstop -->
|
|
|
|
## Overview
|
|
|
|
An `if` expression is an expression of the form:
|
|
|
|
> `if` _condition_ `then` _value1_ `else` _value2_
|
|
|
|
The _condition_ is converted to a `bool` value in the same way as the condition
|
|
of an `if` statement.
|
|
|
|
> **Note:** These conversions have not yet been decided.
|
|
|
|
The _value1_ and _value2_ are implicitly converted to their
|
|
[common type](#finding-a-common-type), which is the type of the `if` expression.
|
|
|
|
## Syntax
|
|
|
|
`if` expressions have very low precedence, and cannot appear as the operand of
|
|
any operator, except as the right-hand operand in an assignment. They can appear
|
|
in other context where an expression is permitted, such as within parentheses,
|
|
as the operand of a `return` statement, as an initializer, or in a
|
|
comma-separated list such as a function call.
|
|
|
|
The _value1_ and _value2_ expressions are arbitrary expressions, and can
|
|
themselves be `if` expressions. _value2_ extends as far to the right as
|
|
possible. An `if` expression can be parenthesized if the intent is for _value2_
|
|
to end earlier.
|
|
|
|
```
|
|
// OK, same as `if cond then (1 + 1) else (2 + (4 * 6))`
|
|
var a: i32 = if cond then 1 + 1 else 2 + 4 * 6;
|
|
|
|
// OK
|
|
var b: i32 = (if cond then 1 + 1 else 2) + 4 * 6;
|
|
```
|
|
|
|
An `if` keyword at the start of a statement is always interpreted as an
|
|
[`if` statement](/docs/design/control_flow/conditionals.md), never as an `if`
|
|
expression, even if it is followed eventually by a `then` keyword.
|
|
|
|
## Semantics
|
|
|
|
The converted _condition_ is evaluated. If it evaluates to `true`, then the
|
|
converted _value1_ is evaluated and its value is the result of the expression.
|
|
Otherwise, the converted _value2_ is evaluated and its value is the result of
|
|
the expression.
|
|
|
|
## Finding a common type
|
|
|
|
The common type of two types `T` and `U` is `(T as CommonType(U)).Result`, where
|
|
`CommonType` is the `Carbon.CommonType` constraint. `CommonType` is notionally
|
|
defined as follows:
|
|
|
|
```
|
|
constraint CommonType(U: CommonTypeWith(Self)) {
|
|
extend CommonTypeWith(U) where .Result == U.Result;
|
|
}
|
|
```
|
|
|
|
The actual definition is a bit more complex than this, as described in
|
|
[symmetry](#symmetry).
|
|
|
|
The interface `CommonTypeWith` is used to customize the behavior of
|
|
`CommonType`:
|
|
|
|
```
|
|
interface CommonTypeWith(U: type) {
|
|
let Result: type
|
|
where Self impls ImplicitAs(.Self) and
|
|
U impls ImplicitAs(.Self);
|
|
}
|
|
```
|
|
|
|
The implementation `A as CommonTypeWith(B)` specifies the type that `A` would
|
|
like to result from unifying `A` and `B` as its `Result`.
|
|
|
|
_Note:_ It is required that both types implicitly convert to the common type.
|
|
Some blanket `impl` declaractions for `CommonTypeWith` are provided as part of
|
|
the prelude. These are described in the following sections.
|
|
|
|
_Note:_ The same mechanism is expected to eventually be used to compute common
|
|
types in other circumstances.
|
|
|
|
### Symmetry
|
|
|
|
The common type of `T` and `U` should always be the same as the common type of
|
|
`U` and `T`. This is enforced in two steps:
|
|
|
|
- A `SymmetricCommonTypeWith` interface implicitly provides a
|
|
`B as CommonTypeWith(A)` implementation whenever one doesn't exist but an
|
|
`A as CommonTypeWith(B)` implementation exists.
|
|
- `CommonType` is defined in terms of `SymmetricCommonTypeWith`, and requires
|
|
that both `A as SymmetricCommonTypeWith(B)` and
|
|
`B as SymmetricCommonTypeWith(A)` produce the same type.
|
|
|
|
The interface `SymmetricCommonTypeWith` is an implementation detail of the
|
|
`CommonType` constraint. It is defined and implemented as follows:
|
|
|
|
```
|
|
interface SymmetricCommonTypeWith(U: type) {
|
|
let Result: type
|
|
where Self impls ImplicitAs(.Self) and
|
|
U impls ImplicitAs(.Self);
|
|
}
|
|
match_first {
|
|
impl forall [T: type, U: CommonTypeWith(T)]
|
|
T as SymmetricCommonTypeWith(U) where .Result = U.Result {}
|
|
impl forall [U: type, T: CommonTypeWith(U)]
|
|
T as SymmetricCommonTypeWith(U) where .Result = T.Result {}
|
|
}
|
|
```
|
|
|
|
The `SymmetricCommonTypeWith` interface is not exported, so users may not
|
|
declare their own implementations of it, and only the two blanket `impl`
|
|
declarations above are used. The `CommonType` constraint is then defined as
|
|
follows:
|
|
|
|
```
|
|
constraint CommonType(U: SymmetricCommonTypeWith(Self)) {
|
|
extend SymmetricCommonTypeWith(U) where .Result == U.Result;
|
|
}
|
|
```
|
|
|
|
When computing the common type of `T` and `U`, if only one of the types provides
|
|
a `CommonTypeWith` implementation, that determines the common type. If both
|
|
types provide a `CommonTypeWith` implementation and their `Result` types are the
|
|
same, that determines the common type. Otherwise, if both types provide
|
|
implementations but their `Result` types differ, there is no common type, and
|
|
the `CommonType` constraint is not met. For example, given:
|
|
|
|
```
|
|
// Implementation #1
|
|
impl forall [T: type] MyX as CommonTypeWith(T) where .Result = MyX {}
|
|
|
|
// Implementation #2
|
|
impl forall [T: type] MyY as CommonTypeWith(T) where .Result = MyY {}
|
|
```
|
|
|
|
`MyX as CommonTypeWith(MyY)` will select #1, and `MyY as CommonTypeWith(MyX)`
|
|
will select #2, but the constraints on `MyX as CommonType(MyY)` will not be met
|
|
because result types differ.
|
|
|
|
### Same type
|
|
|
|
If `T` is the same type as `U`, the result is that type:
|
|
|
|
```
|
|
final impl forall [T: type] T as CommonTypeWith(T) where .Result = T {}
|
|
```
|
|
|
|
_Note:_ This rule is intended to be considered more specialized than the other
|
|
rules in this document.
|
|
|
|
Because this `impl` is declared `final`, `T.(CommonType(T)).Result` is always
|
|
assumed to be `T`, even in contexts where `T` involves a checked binding and so
|
|
the result would normally be an unknown type whose facet type is `type`.
|
|
|
|
```
|
|
fn F[T: Hashable](c: bool, x: T, y: T) -> HashCode {
|
|
// OK, type of `if` expression is `T`.
|
|
return (if c then x else y).Hash();
|
|
}
|
|
```
|
|
|
|
### Implicit conversions
|
|
|
|
If `T` implicitly converts to `U`, the common type is `U`:
|
|
|
|
```
|
|
impl forall [T: type, U: ImplicitAs(T)]
|
|
T as CommonTypeWith(U) where .Result = T {}
|
|
```
|
|
|
|
_Note:_ If an implicit conversion is possible in both directions, and no more
|
|
specific implementation exists, the constraints on `T as CommonType(U)` will not
|
|
be met because `(T as CommonTypeWith(U)).Result` and
|
|
`(U as CommonTypeWith(T)).Result` will differ. In order to define a common type
|
|
for such a case, `CommonTypeWith` implementations in both directions must be
|
|
provided to override the blanket `impl` declarations in both directions:
|
|
|
|
```
|
|
impl MyString as CommonTypeWith(YourString) where .Result = MyString {}
|
|
impl YourString as CommonTypeWith(MyString) where .Result = MyString {}
|
|
var my_string: MyString;
|
|
var your_string: YourString;
|
|
// The type of `also_my_string` is `MyString`.
|
|
var also_my_string: auto = if cond then my_string else your_string;
|
|
```
|
|
|
|
## Alternatives considered
|
|
|
|
- [Provide no conditional expression](/proposals/p000911-conditional-expressions.md#no-conditional-expression)
|
|
- Use
|
|
[`cond ? expr1 : expr2`, like in C and C++](/proposals/p000911-conditional-expressions.md#use-c-syntax)
|
|
syntax
|
|
- Use
|
|
[`if (cond) expr1 else expr2`](/proposals/p000911-conditional-expressions.md#no-then)
|
|
syntax
|
|
- Use
|
|
[`if (cond) then expr1 else expr2`](/proposals/p000911-conditional-expressions.md#require-parentheses-around-the-condition)
|
|
syntax
|
|
- Allow
|
|
[`1 + if cond then expr1 else expr2`](/proposals/p000911-conditional-expressions.md#never-require-enclosing-parentheses)
|
|
- [Only require one `impl` to specify the common type if implicit conversions in both directions are possible](/proposals/p000911-conditional-expressions.md#implicit-conversions-in-both-directions)
|
|
- [Introduce special rules for lvalue conditionals](/proposals/p000911-conditional-expressions.md#support-lvalue-conditionals)
|
|
|
|
## References
|
|
|
|
- Proposal
|
|
[#911: Conditional expressions](https://github.com/carbon-language/carbon-lang/pull/911).
|