diff --git a/docs/design/expressions/README.md b/docs/design/expressions/README.md index 820410e3070d7..3fbc77cd8154e 100644 --- a/docs/design/expressions/README.md +++ b/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) @@ -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++. diff --git a/docs/design/expressions/if.md b/docs/design/expressions/if.md new file mode 100644 index 0000000000000..e8d9c7047af28 --- /dev/null +++ b/docs/design/expressions/if.md @@ -0,0 +1,243 @@ +# `if` expressions + + + + + +## 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) + + + +## 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). diff --git a/docs/design/lexical_conventions/words.md b/docs/design/lexical_conventions/words.md index 9fe6115ba37a5..b32c765c3ecba 100644 --- a/docs/design/lexical_conventions/words.md +++ b/docs/design/lexical_conventions/words.md @@ -73,6 +73,7 @@ The following words are interpreted as keywords: - `protected` - `return` - `returned` +- `then` - `var` - `virtual` - `where` diff --git a/proposals/p0911.md b/proposals/p0911.md new file mode 100644 index 0000000000000..2159c8b380cbe --- /dev/null +++ b/proposals/p0911.md @@ -0,0 +1,593 @@ +# Conditional expressions + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/911) + + + +## 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) + + + +## 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.