<p>Thus far, I see two things that can go wrong when implementing the <code>?:</code> (ternary conditional) operator:</p>
<ol>
<li>PHP famously <a href="https://wiki.php.net/rfc/ternary_associativity" rel="noreferrer">incorrectly implemented the <code>?:</code> operator as being left-associative</a> rather than right-associative, making it much harder to write else-if statements using <code>?:</code>.</li>
<li>As the <a href="https://stackoverflow.com/a/62104607/8902065">StackOverflow user called <i>rici</i> warned me</a>, my <a href="https://flatassembler.github.io/compiler.html" rel="noreferrer">AEC-to-x86 compiler</a> generates incorrect code for <code>?:</code>. Namely, the code it produces calculates the results of the second operand and the third operand before calculating the result of the first operand (the condition), which can, according to <i>rici</i>, lead to unexpected divide-by-zero errors for expressions such as <code>d = 0 ? 0 : n / d</code> (if <code>d</code> is equal to zero; <code>=</code> is the equality operator in AEC, not an assignment operator). There does not seem to be a simple solution, given the way my AEC-to-x86 compiler is structured.</li>
</ol>
<p>Are you aware of any other caveats you might run into when implementing the <code>?:</code> operator in your language?</p>

<compilers><parsing><operators><conditional>
What are some caveats to be careful about when implementing the `?:` (ternary conditional) operator?
<p>There's a particular issue in statically typed languages. The expression <code>a ? b : c</code> needs to have a type, but in most cases it's too strict to say that the types of <code>b</code> and <code>c</code> must be identical. For example, in Java:</p>
<ul>
<li><code>cond ? 1 : 1.5</code> is allowed, where the branches are of type <code>int</code> and <code>double</code> respectively.</li>
<li><code>cond ? new Cat() : new Dog()</code> is allowed, where <code>Cat</code> and <code>Dog</code> are different classes.</li>
</ul>
<p>These are two genuinely different cases, both of which must be handled:</p>
<ul>
<li>In the first case there is no common supertype of <code>int</code> and <code>double</code>, but there are rules for promoting from one primitive type to another. These rules must be specified, and the conversion operation must be inserted into the compiled output.</li>
<li>In the second case, there is a common supertype of <code>Cat</code> and <code>Dog</code> but it needs to be determined; it might be a superclass like <code>Animal</code> or <code>Object</code>, or perhaps some more complicated type based on what interfaces are shared by both classes, like <code>Serializable &amp; Pettable</code>.</li>
</ul>
<p>More abstractly, this means your type system needs to support a &quot;least upper bound&quot; (or <a href="https://en.wikipedia.org/wiki/Join_and_meet" rel="noreferrer">join</a>) operation, which determines the strictest common supertype of two types, possibly while also allowing implicit type conversions (like from <code>int</code> to <code>double</code>). This operation needs to not only be implemented in the compiler, but also specified since your users may depend on its behaviour.</p>
<p>Depending on the other features of your language (e.g. list literals), your type system might already have a &quot;least upper bound&quot; operation; however, for many simpler languages, the <code>?:</code> ternary operator is the only language construct that requires it.</p>

CC BY-SA 4.0
2023-06-10T15:42:25.783Z
2023-06-10T15:42:25.783Z
kaya3
CC BY-SA 4.0
2023-06-10T20:06:18.080Z
Thank you, you helped me discover (and fix) two bugs in my AEC-to-WebAssembly compiler: https://github.com/FlatAssembler/AECforWebAssembly/issues/19
FlatAssembler
CC BY-SA 4.0
2023-06-11T04:33:12.000Z
Swift allows both `a ? b : c` and `if a { b } else { c }` as expressions, but they type-check differently, for complicated reasons: https://swiftfiddle.com/xoialk2zarbznco2uslp6mt3sa
Bbrk24
CC BY-SA 4.0
2023-06-11T16:00:41.377Z
@Bbrk24 That sounds like it would make for a good answer ─ I'd be interested in reading about what the differences between those two forms are, and why.
kaya3
CC BY-SA 4.0
2023-07-05T18:26:16.870Z
As an expansion of a note mentioned in passing, Haskell does have a ternary operation that requires its operands to have the same concrete type: `if a then b else c` simply will not compile if `b` and `c` are not able to have the same type, for instance, the expression `if a then (10 :: Int) else 0.5` will not compile because there is no way to make 0.5 manifest as an `Int`.
OpenAI was the last straw
<p>Going off of <a href="https://languagedesign.stackexchange.com/a/1470/15">@kaya3's answer about type inference</a>, that comes with its own set of footguns.</p>
<p>For example, consider this Swift code:</p>
<pre class="lang-swift prettyprint-override"><code>_ = [1].lazy.map { _ in print(&quot;Never called, because lazy&quot;) }
_ = true ? [1].lazy.map { _ in print(&quot;...but this is?&quot;) } : []
</code></pre>
<p>So, Swift has a somewhat complicated set of sequence and collection protocols. <code>Sequence</code> has a bunch of methods, like <code>map</code> and <code>filter</code>, that return arrays. <code>LazySequenceProtocol</code> has methods by the same names, but they return more lazy sequences. Putting <code>.lazy</code> after a sequence wraps it in a lazy sequence.</p>
<p><code>LazySequenceProtocol</code> inherits from <code>Sequence</code>, so it still has the versions that return arrays. You can't normally call them because they're shadowed, but they exist. In the second line above, the <code>[]</code> in the false case forces the whole expression to be of type <code>Array&lt;_&gt;</code>, and the eager version is called.</p>
<p>For this reason, <a href="https://github.com/apple/swift-evolution/blob/main/proposals/0380-if-switch-expressions.md#detailed-design" rel="nofollow noreferrer">among others</a>, Swift opted to disable this bidirectional type inference in <code>if</code> expressions. The following code does not compile*:</p>
<pre class="lang-swift prettyprint-override"><code>_ = if true {
    [1].lazy.map { _ in print(&quot;Can't be called if it doesn't compile&quot;) }
} else {
    []
}
</code></pre>
<p>Unfortunately, neither does this:</p>
<pre class="lang-swift prettyprint-override"><code>_ = if condition { 1 } else { 1.5 }
</code></pre>
<p>You have to explicitly write <code>1.0</code> or <code>1 as Double</code>.</p>
<p>*Well, it doesn't compile in 5.8 regardless, because <code>if</code> expressions are a feature in 5.9+. At the time of writing, you have to use a nightly build of Swift in order to get a meaningful error message.</p>

CC BY-SA 4.0
2023-06-11T16:49:29.573Z
2023-06-30T13:19:20.243Z
2023-06-30T13:19:20.243Z
Bbrk24
# What are some caveats to be careful about when implementing the `?:` (ternary conditional) operator?
Posted by: username_0 on 2023-06-10
Thus far, I see two things that can go wrong when implementing the `?:` (ternary conditional) operator:

1. PHP famously [incorrectly implemented the `?:` operator as being left-associative](https://wiki.php.net/rfc/ternary_associativity) rather than right-associative, making it much harder to write else-if statements using `?:`.
2. As the [StackOverflow user called *rici* warned me](https://stackoverflow.com/a/62104607/8902065), my [AEC-to-x86 compiler](https://flatassembler.github.io/compiler.html) generates incorrect code for `?:`. Namely, the code it produces calculates the results of the second operand and the third operand before calculating the result of the first operand (the condition), which can, according to *rici*, lead to unexpected divide-by-zero errors for expressions such as `d = 0 ? 0 : n / d` (if `d` is equal to zero; `=` is the equality operator in AEC, not an assignment operator). There does not seem to be a simple solution, given the way my AEC-to-x86 compiler is structured.

Are you aware of any other caveats you might run into when implementing the `?:` operator in your language?
**Tags:** compilers parsing operators conditional
---
## 2 answers
### Answer by username_1 on 2023-06-10. Score: 9 | Accepted answer
There's a particular issue in statically typed languages. The expression `a ? b : c` needs to have a type, but in most cases it's too strict to say that the types of `b` and `c` must be identical. For example, in Java:

* `cond ? 1 : 1.5` is allowed, where the branches are of type `int` and `double` respectively.
* `cond ? new Cat() : new Dog()` is allowed, where `Cat` and `Dog` are different classes.

These are two genuinely different cases, both of which must be handled:

* In the first case there is no common supertype of `int` and `double`, but there are rules for promoting from one primitive type to another. These rules must be specified, and the conversion operation must be inserted into the compiled output.
* In the second case, there is a common supertype of `Cat` and `Dog` but it needs to be determined; it might be a superclass like `Animal` or `Object`, or perhaps some more complicated type based on what interfaces are shared by both classes, like `Serializable & Pettable`.

More abstractly, this means your type system needs to support a "least upper bound" (or [join](https://en.wikipedia.org/wiki/Join_and_meet)) operation, which determines the strictest common supertype of two types, possibly while also allowing implicit type conversions (like from `int` to `double`). This operation needs to not only be implemented in the compiler, but also specified since your users may depend on its behaviour.

Depending on the other features of your language (e.g. list literals), your type system might already have a "least upper bound" operation; however, for many simpler languages, the `?:` ternary operator is the only language construct that requires it.
> **Comments:**
> **username_0** (2023-06-10): Thank you, you helped me discover (and fix) two bugs in my AEC-to-WebAssembly compiler: https://github.com/FlatAssembler/AECforWebAssembly/issues/19  
> **username_2** (2023-06-11): Swift allows both `a ? b : c` and `if a { b } else { c }` as expressions, but they type-check differently, for complicated reasons: https://swiftfiddle.com/xoialk2zarbznco2uslp6mt3sa  
> **username_1** (2023-06-11): @Bbrk24 That sounds like it would make for a good answer ─ I'd be interested in reading about what the differences between those two forms are, and why.  
> **username_3** (2023-07-05): As an expansion of a note mentioned in passing, Haskell does have a ternary operation that requires its operands to have the same concrete type: `if a then b else c` simply will not compile if `b` and `c` are not able to have the same type, for instance, the expression `if a then (10 :: Int) else 0.5` will not compile because there is no way to make 0.5 manifest as an `Int`.  
---
### Answer by username_2 on 2023-06-11. Score: 6
Going off of @kaya3's answer about type inference, that comes with its own set of footguns.
For example, consider this Swift code:
_ = [1].lazy.map { _ in print("Never called, because lazy") }
_ = true ? [1].lazy.map { _ in print("...but this is?") } : []

So, Swift has a somewhat complicated set of sequence and collection protocols. Sequence has a bunch of methods, like map and filter, that return arrays. LazySequenceProtocol has methods by the same names, but they return more lazy sequences. Putting .lazy after a sequence wraps it in a lazy sequence.
LazySequenceProtocol inherits from Sequence, so it still has the versions that return arrays. You can't normally call them because they're shadowed, but they exist. In the second line above, the [] in the false case forces the whole expression to be of type Array<_>, and the eager version is called.
For this reason, among others, Swift opted to disable this bidirectional type inference in if expressions. The following code does not compile*:
_ = if true {
    [1].lazy.map { _ in print("Can't be called if it doesn't compile") }
} else {
    []
}

Unfortunately, neither does this:
_ = if condition { 1 } else { 1.5 }

You have to explicitly write 1.0 or 1 as Double.
*Well, it doesn't compile in 5.8 regardless, because if expressions are a feature in 5.9+. At the time of writing, you have to use a nightly build of Swift in order to get a meaningful error message.
---
