Ver Fonte

Conditional expressions (#911)

This proposal introduces a conditional operator of the form:

```
if cond then value1 else value2
```

Co-authored-by: josh11b <josh11b@users.noreply.github.com>
Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
Richard Smith há 4 anos atrás
pai
commit
f6cbd2231e

+ 13 - 0
docs/design/expressions/README.md

@@ -13,6 +13,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 -   [Overview](#overview)
 -   [Operators](#operators)
 -   [Conversions and casts](#conversions-and-casts)
+-   [`if` expressions](#if-expressions)
 
 <!-- tocstop -->
 
@@ -57,3 +58,15 @@ fn Baz(n: i64) {
   Bar(n);
 }
 ```
+
+## `if` expressions
+
+An [`if` expression](if.md) chooses between two expressions.
+
+```
+fn Run(args: Span(StringView)) {
+  var file: StringView = if args.size() > 1 then args[1] else "/dev/stdin";
+}
+```
+
+`if` expressions are analogous to `?:` ternary expressions in C and C++.

+ 243 - 0
docs/design/expressions/if.md

@@ -0,0 +1,243 @@
+# `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 is ImplicitAs(.Self) and
+          U is 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`s 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 is ImplicitAs(.Self) and
+          U is ImplicitAs(.Self);
+}
+match_first {
+  impl [T:! Type, U:! CommonTypeWith(T)] T as SymmetricCommonTypeWith(U) {
+    let Result:! Type = U.Result;
+  }
+  impl [U:! Type, T:! CommonTypeWith(U)] T as SymmetricCommonTypeWith(U) {
+    let Result:! Type = T.Result;
+  }
+}
+```
+
+The `SymmetricCommonTypeWith` interface is not exported, so user-defined `impl`s
+can't be defined, and only the two blanket `impl`s 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 [T:! Type] MyX as CommonTypeWith(T) {
+  let Result:! Type = MyX;
+}
+
+// Implementation #2
+impl [T:! Type] MyY as CommonTypeWith(T) {
+  let Result:! Type = 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 [T:! Type] T as CommonTypeWith(T) {
+  let Result:! Type = 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 generic parameter and
+so the result would normally be an unknown type whose type-of-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 [T:! Type, U:! ImplicitAs(T)] T as CommonTypeWith(U) {
+  let Result:! Type = 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`s in both directions:
+
+```
+impl MyString as CommonTypeWith(YourString) {
+  let Result:! Type = MyString;
+}
+impl YourString as CommonTypeWith(MyString) {
+  let Result:! Type = 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/p0911.md#no-conditional-expression)
+-   Use
+    [`cond ? expr1 : expr2`, like in C and C++](/proposals/p0911.md#use-c-syntax)
+    syntax
+-   Use [`if (cond) expr1 else expr2`](/proposals/p0911.md#no-then) syntax
+-   Use
+    [`if (cond) then expr1 else expr2`](/proposals/p0911.md#require-parentheses-around-the-condition)
+    syntax
+-   Allow
+    [`1 + if cond then expr1 else expr2`](/proposals/p0911.md#never-require-enclosing-parentheses)
+-   [Only require one `impl` to specify the common type if implicit conversions in both directions are possible](/proposals/p0911.md#implicit-conversions-in-both-directions)
+-   [Introduce special rules for lvalue conditionals](/proposals/p0911.md#support-lvalue-conditionals)
+
+## References
+
+-   Proposal
+    [#911: Conditional expressions](https://github.com/carbon-language/carbon-lang/pull/911).

+ 1 - 0
docs/design/lexical_conventions/words.md

@@ -73,6 +73,7 @@ The following words are interpreted as keywords:
 -   `protected`
 -   `return`
 -   `returned`
+-   `then`
 -   `var`
 -   `virtual`
 -   `where`

+ 593 - 0
proposals/p0911.md

@@ -0,0 +1,593 @@
+# Conditional 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
+-->
+
+[Pull request](https://github.com/carbon-language/carbon-lang/pull/911)
+
+<!-- toc -->
+
+## Table of contents
+
+-   [Problem](#problem)
+-   [Background](#background)
+-   [Proposal](#proposal)
+-   [Details](#details)
+    -   [Chaining](#chaining)
+    -   [Precedence and ambiguity](#precedence-and-ambiguity)
+-   [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals)
+-   [Alternatives considered](#alternatives-considered)
+    -   [No conditional expression](#no-conditional-expression)
+    -   [Use C syntax](#use-c-syntax)
+    -   [No `then`](#no-then)
+    -   [Require parentheses around the condition](#require-parentheses-around-the-condition)
+    -   [Never require enclosing parentheses](#never-require-enclosing-parentheses)
+    -   [Variable-precedence `if`](#variable-precedence-if)
+    -   [Implicit conversions in both directions](#implicit-conversions-in-both-directions)
+    -   [Support lvalue conditionals](#support-lvalue-conditionals)
+-   [Future work](#future-work)
+    -   [Too many user-facing interfaces](#too-many-user-facing-interfaces)
+    -   [Incompatible `CommonType` implementations diagnosed late](#incompatible-commontype-implementations-diagnosed-late)
+    -   [`impl` ordering depends on operand order](#impl-ordering-depends-on-operand-order)
+
+<!-- tocstop -->
+
+## Problem
+
+Programs need to be able to select between multiple different paths of execution
+and multiple different values. In a rich expression language, developers expect
+to be able to do this within a subexpression of some overall expression.
+
+## Background
+
+C-family languages provide a `cond ? value1 : value2` operator.
+
+-   This operator has confusing syntax, because both `cond` and `value2` are
+    undelimited, and it's often unclear to developers how much of the adjacent
+    expressions are part of the conditional expression. For example:
+    ```
+    int n = has_thing1 && cond ? has_thing2 : has_thing3 && has_thing4;
+    ```
+    is parsed as
+    ```
+    int n = (has_thing1 && cond) ? has_thing2 : (has_thing3 && has_thing4);
+    ```
+    Also, `value1` and `value2` are parsed with different rules:
+    ```
+    cond ? f(), g() : h(), i();
+    ```
+    is parsed as
+    ```
+    (cond ? f(), g() : h()), i();
+    ```
+-   In C++, this operator has confusing semantics, due to having a complicated
+    set of rules governing how the target type is determined.
+-   Despite the complications of the rules, the result type of `?:` is not
+    customizable. Instead, C++ invented a `std::common_type` trait that models
+    what the result of `?:` should have been.
+
+Rust allows most statements to be used as expressions, with `if` statements
+being an important case of this: `Use(if cond { v1 } else { v2 })`.
+
+-   This has a number of behaviors that would be surprising to developers coming
+    from C++ and C, such as a final `;` in a `{...}` making a semantic
+    difference.
+-   The expression semantics leak into the statement semantics. For example,
+    Rust rejects:
+
+    ```
+    fn f() {}
+    fn g() -> i32 {}
+
+    fn main() {
+      if true { f() } else { g() };
+      return;
+    }
+    ```
+
+    ... because the two arms of the `if` don't have the same type.
+
+-   We have already
+    [decided](https://github.com/carbon-language/carbon-lang/issues/430) that we
+    do not want Carbon to treat statements such as `if` as being expressions
+    without some kind of syntactic distinction.
+
+## Proposal
+
+Provide a conditional expression with the syntax:
+
+> ```
+> if cond then value1 else value2
+> ```
+
+`then` is a new keyword introduced for this purpose.
+
+## Details
+
+### Chaining
+
+This syntax can be chained like `if` statements:
+
+```
+Print(if guess < value
+      then "Too low!"
+      else if guess > value
+      then "Too high!"
+      else "Correct!")
+```
+
+Unlike with `if` statements, this doesn't require a special rule.
+
+### Precedence and ambiguity
+
+An `if` expression can be used as a top-level expression, or within parentheses
+or a comma-separated list such as a function call. They have low precedence, so
+cannot be used as the operand of any operator, with the exception of assignment
+(if assignment is treated as an operator), but they can appear in other contexts
+where an arbitrary expression is permitted, for example as the operand of
+`return`, the initializer of a variable, or even as the condition of another
+`if` expression or `if` statement.
+
+```
+// Error, can't use `if` here.
+var v: i32 = 1 * if cond then 2 else 3 + 4;
+```
+
+`value2` extends as far to the right as possible:
+
+```
+var v: i32 = if cond then 2 else 3 + 4;
+```
+
+is the same as
+
+```
+var v: i32 = if cond then 2 else (3 + 4);
+```
+
+not
+
+```
+var v: i32 = (if cond then 2 else 3) + 4;
+```
+
+The intent is that an `if` expression is used to produce a value, not only for
+its side-effects. If only the side-effects are desired, an `if` statement should
+be used instead. Because `value2` extends as far to the right as possible, if an
+`if` expression appeared at the start of a statement, its value could never be
+used:
+
+```
+if cond then value1 else value2;
+```
+
+For this reason and to avoid the need for lookahead or disambiguation, an `if`
+keyword appearing at the start of a statement is always interpreted as beginning
+an `if` statement and never as beginning an `if` expression.
+
+## Rationale based on Carbon's goals
+
+-   [Language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem)
+    -   The `if ... then ... else` syntax should be easier to format
+        automatically in an unsurprising way than a `?:` syntax because it is
+        clear that the `then` and `else` keywords should be wrapped to the start
+        of a new line when wrapping the overall conditional expression.
+-   [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write)
+    -   Including such an expression is expected to improve ergonomics.
+    -   An explicit delimiter for the start of the condition expression makes it
+        easier to read, correctly write, and understand the precedence of
+        conditional expressions.
+    -   Making the `value2` portion as long as possible gives a simple rule that
+        it seems feasible for every Carbon developer to remember. This rule is
+        expected to be unsurprising both due to using the same rule for `value1`
+        and `value2`, and because it means that `if` consistently behaves like a
+        very low precedence prefix operator.
+    -   The use of an explicit `if` keyword for flow control makes the
+        distinction between flow control and linear computation clearer.
+    -   The readability of a multi-line `if` expression is improved by having a
+        `then` and `else` keyword of the same length
+-   [Interoperability with and migration from existing C++ code](/docs/project/goals.md#interoperability-with-and-migration-from-existing-c-code)
+    -   Migration is improved by providing an operator set that largely matches
+        the C++ operator set.
+
+## Alternatives considered
+
+### No conditional expression
+
+We could provide no conditional expression, and instead ask people to use a
+different mechanism to achieve this functionality. Some options include:
+
+-   Use of an `if` statement:
+    ```
+    var v: Result;
+    if (cond) {
+      v = value1;
+    } else {
+      v = value2;
+    }
+    Use(v);
+    ```
+-   A function call syntax:
+    ```
+    Use(cond.Select(value1, value2));
+    ```
+    or, with short-circuiting and lambdas:
+    ```
+    Use(cond.LazySelect($(value1), $(value2)));
+    ```
+-   An `if` statement in a lambda:
+    ```
+    Use(${ if (cond) { return value1; } else { return value2; } });
+    ```
+
+The above assumes a placeholder `$(...)` syntax for a single-expression lambda,
+and a `${...}` syntax for a lambda with statements as its body.
+
+Advantages:
+
+-   No new dedicated syntax.
+
+Disadvantages:
+
+-   Conditional expressions are commonly used, commonly desired, and Carbon
+    developers -- especially those coming from C++ and C -- will be disappointed
+    by their absence.
+-   Readability and ergonomics will be harmed by making this common operation
+    more verbose, even if an idiom is established.
+
+### Use C syntax
+
+We could use the C `cond ? value1 : value2` syntax.
+
+Advantages:
+
+-   Familiar to developers coming from C++ and C.
+
+Disadvantages:
+
+-   These operators have serious precedence problems in C++ and C. We could
+    address those by making more cases ambiguous, at the cost of harming
+    familiarity and requiring parentheses in more cases.
+-   The `:` token is already in use in name binding; using it as part of a
+    conditional expression would be confusing.
+-   The `?` token is likely to be desirable for use in optional unwrapping and
+    error handling.
+
+### No `then`
+
+We could use
+
+> ```
+> if (cond) value1 else value2
+> ```
+
+instead of
+
+> ```
+> if cond then value1 else value2
+> ```
+
+Note that we cannot avoid parentheses in this formulation without risking
+syntactic ambiguities.
+
+Advantages:
+
+-   Looks more like an `if` statement, albeit one with unbraced operands.
+-   Slightly shorter.
+-   Better line-wrapping for chained `if` expressions:
+    ```
+    Print(if (guess < value)
+            "Too low!"
+          else if (guess > value)
+            "Too high!"
+          else
+            "Correct!")
+    ```
+    may be more readable than
+    ```
+    Print(if guess < value
+          then "Too low!"
+          else if guess > value
+          then "Too high!"
+          else "Correct!")
+    ```
+    or
+    ```
+    Print(if guess < value
+            then "Too low!"
+            else if guess > value
+              then "Too high!"
+              else "Correct!")
+    ```
+
+Disadvantages:
+
+-   Potentially worse line wrapping. The `else` would presumably be wrapped onto
+    a line by itself, wasting vertical space, whereas `then` and `else` when
+    paired can both comfortably precede their values on the same line; consider
+    ```
+    F(if (cond)
+        value1
+      else
+        value2)
+    ```
+    occupies more space than
+    ```
+    F(if cond
+      then value1
+      else value2)
+    ```
+-   May create confusion between `if` statements and `if` expressions by
+    resembling an `if` statement but not matching the semantics.
+-   May cause evolutionary problems due to syntactic conflict if we ever make
+    the braces or parentheses in `if` statements optional.
+-   Requires parentheses, and hence additional presses of "Shift" on US
+    keyboards, making it slightly harder to type.
+
+### Require parentheses around the condition
+
+We could use:
+
+> ```
+> if (cond) then value1 else value2
+> ```
+
+However, it's not clear that there is value in requiring both parentheses and a
+new keyword. It also seems jarring that this so closely resembles an `if`
+statement but adds a `then` keyword that the `if` statement lacks.
+
+### Never require enclosing parentheses
+
+We could allow an `if` expression to appear anywhere a parenthesized expression
+can appear, and retain the rule that `value2` extends as far to the right as
+possible.
+
+Advantages:
+
+-   Removes extra ceremony from a construct that is already more verbose than
+    the corresponding `?:` construct in C++.
+-   The requirement to include parentheses may be irritating in cases where
+    there is no other possible interpretation, such as
+    `1 + (if cond then 2 else 3)`.
+
+Disadvantages:
+
+-   Visually ambiguous where `value2` ends in some cases, and violates
+    precedence rules.
+-   Hard for a simple yacc/bison parser to handle, due to ambiguity of `if` at
+    the start of a statement and ambiguity when parsing `value2`.
+
+### Variable-precedence `if`
+
+We could allow an `if` expression to appear anywhere a parenthesized expression
+can appear, and parse the `value1` and `value2` as if they appeared in place of
+the `if` expression:
+
+```carbon
+var n: i32 = 1 + if cond then 2 * 3 else 4 * 5 + 6;
+// ... is interpreted as ...
+var n: i32 = (1 + (if cond then (2 * 3) else (4 * 5))) + 6;
+
+// Error: expected `else` but found `+ 4`.
+var m: i32 = 1 + if cond then 2 * 3 + 4 else 5 + 6;
+```
+
+Advantages:
+
+-   Same as previous option.
+
+Disadvantages:
+
+-   Confusing to readers, because it's not clear locally where the expression
+    after `else` ends, and discovering this requires looking backwards to before
+    the `if`.
+-   Hard for a simple yacc/bison parser to handle, due to needing at least one
+    production for an `if` statement for each precedence level. Also, those
+    productions will result in grammar ambiguities that will need to be
+    resolved.
+
+### Implicit conversions in both directions
+
+Suppose we have two types where implicit conversions in both directions are
+possible:
+
+```carbon
+class A {}
+class B {}
+impl A as ImplicitAs(B) { ... }
+impl B as ImplicitAs(A) { ... }
+```
+
+By default, an expression `if cond then {} as A else {} as B` would be
+ambiguous. If the author of `A` or `B` wishes to change this behavior:
+
+-   If the common type should be `A`, then `impl A as CommonTypeWith(B)` must be
+    provided specifying the common type is `A`.
+-   If the common type should be `B`, then `impl B as CommonTypeWith(A)` must be
+    provided specifying the common type is `B`.
+-   If the common type should be something else, then both `impl`s need to be
+    provided:
+    ```
+    impl A as CommonTypeWith(B) { let Result:! Type = C; }
+    impl B as CommonTypeWith(A) { let Result:! Type = C; }
+    ```
+
+We could change the rules so instead, in any of the above cases, implementing
+either `A as CommonTypeWith(B)` or `B as CommonTypeWith(A)` would suffice.
+
+Advantages:
+
+-   Simplifies the user experience in this case.
+
+Disadvantages:
+
+-   Introduces non-uniformity: the blanket `impl` of `CommonTypeWith` in terms
+    of `ImplicitAs` would get this special treatment, but other blanket `impl`s
+    would not.
+-   Introduces complexity, which might not be fully hidden from users. At
+    minimum, we would need to explain that `ImplicitAs` is treated specially
+    here.
+-   The case in which two `impl`s are required is a corner case. It's somewhat
+    uncommon for implicit conversions to be possible in both directions between
+    two types. In those cases, it's more uncommon for there to be a clear best
+    "common type". And even then, most of the time the common type will be one
+    of the two types being unified.
+
+From a more abstract perspective: the process of finding a common type involves
+asking each type to implicitly convert to the destination type that it thinks is
+best, and then failing if both sides didn't convert to the same type. If `A`
+implicitly converts to `B` and the other way around, then both sides of this
+process should be overridden in order to get both types to implicitly convert to
+`C` instead.
+
+### Support lvalue conditionals
+
+Carbon doesn't formally have a notion of lvalue or rvalue yet; this notion is
+expected to be added by
+[#821: Values, variables, pointers, and references](https://github.com/carbon-language/carbon-lang/pull/821).
+In any case, we certainly intend to distinguish between expressions that
+represent values and expressions that represent locations where values could
+appear. We therefore need to decide whether a conditional expression can ever be
+in the latter category. For example:
+
+```carbon
+var a: String;
+var b: String;
+var c: bool;
+// Valid?
+(if c then a else b) = "Hello";
+```
+
+We could permit this, as C++ does. For example, we could say:
+
+> If both _value1_ and _value2_ are lvalues then
+> `if cond then value1 else value2` is rewritten to
+> `*(if cond then &value1 else &value2)` if those pointer types have a common
+> type.
+
+The other reason we might want to consider this alternative is performance. In
+C++, this code avoids making a `std::string` copy:
+
+```c++
+std::string a;
+std::string b;
+std::string c;
+bool cond;
+// ...
+bool equal = c == (cond ? a : b);
+```
+
+... by treating the conditional expression as an lvalue of type `std::string`
+rather than as a prvalue. However, in Carbon, following #821, we would expect
+that the equivalent of a prvalue of type `std::string` would not necessarily
+imply that a copy is made. Rather, Carbon's equivalent of prvalues would
+represent either a set of instructions to initialize a value (as in C++), or the
+location of some existing value that we are temporarily "borrowing".
+
+With that in mind:
+
+Advantages:
+
+-   More similar to C++.
+-   Permits certain operations that have an obvious intended meaning, such as
+    assignment to a conditional.
+
+Disadvantages:
+
+-   Modification through an lvalue conditional is seldom used in C++, indicating
+    that this is not an important feature. The other benefits of a conditional
+    producing an lvalue are expected to be obtained by #821.
+-   Mutable inputs to operations ("out parameters") in Carbon are expected to be
+    expressed as pointers under #821, so there will be a `&` somewhere anyway;
+    given the choice between an lvalue conditional:
+    ```
+    F(&(if cond then a else b));
+    ```
+    and an rvalue-only conditional:
+    ```
+    F(if cond then &a else &b);
+    ```
+    the latter option would likely be preferred even if the former were
+    available.
+-   This would create an inconsistency in behavior, which would be particularly
+    visible in a generic when determining what constraints are necessary to
+    type-check an `if` expression -- the constraints would depend not only on
+    operand types, but also on value category, and may result in a hard to
+    express constraint such as "either `T*` and `U*` have a common type or `T`
+    and `U` have a common type".
+-   Certain kinds of lvalue conditional expression have turned out to be hard to
+    implement in C++, such as a conditional involving bit-field lvalues. We can
+    entirely avoid that class of implementation problems by treating conditional
+    expressions as rvalues.
+
+This should be revisited if the direction in #821 changes substantially from the
+assumptions described above.
+
+## Future work
+
+There are some known issues with the way that the extensibility mechanism works
+in this proposal. It is hoped that extensions to Carbon's generics mechanism
+will provide simple ways to resolve these issues. This design should be
+revisited once those mechanisms are available.
+
+### Too many user-facing interfaces
+
+We provide both `CommonTypeWith`, as an extension point, and `CommonType`, as a
+constraint. It would be preferable to provide only a single name that functions
+both as the extension point and as a constraint, but we don't have a good way to
+automatically make `impl`s symmetric and avoid `impl` cycles if we use only one
+interface.
+
+### Incompatible `CommonType` implementations diagnosed late
+
+Example:
+
+```
+class A {}
+class B {}
+
+impl A as CommonTypeWith(B) where .Result = A {}
+impl B as CommonTypeWith(A) where .Result = B {}
+
+fn F(a: A, b: B) -> auto { return if true then a else b; }
+```
+
+The definition of function `F` is rejected, because `A` and `B` have no
+(consistent) common type. It would be preferable to reject the `impl`
+definitions.
+
+### `impl` ordering depends on operand order
+
+Example:
+
+```
+class A(T:! Type) {}
+class B(T:! Type) {}
+
+interface Fungible {}
+impl A(T:! Type) as Fungible {}
+impl B(T:! Type) as Fungible {}
+
+// #1
+impl A(T:! Type) as CommonTypeWith(U:! Fungible) where .Result = A(T) {}
+// #2
+impl B(T:! Type) as CommonTypeWith(A(T)) where .Result = T {}
+
+fn F(a: A(i32), b: B(i32)) -> auto { return if true then a else b; }
+```
+
+Here, reversed #2 is a better match than #1, because it matches both `A(?)` and
+`B(?)`, so #2 should be consider the best-matching `impl`. However, we never
+compare reversed #2 against non-reversed #1. Instead, we look for:
+
+1.  `impl A(i32) as SymmetricCommonTypeWith(B(i32))`, which selects #1 as being
+    better than the blanket `impl` that reverses operand order.
+2.  `impl B(i32) as SymmetricCommonTypeWith(A(i32))`, which selects #2 as being
+    better than the blanket `impl` that reverses operand order.
+
+So we decide that the `if` in `F` is ambiguous, even though there is a unique
+best `CommonTypeWith` match. If either #1 or #2 is written with the operand
+order reversed, then `F` would be accepted.