Note: this RFC was adapted from an internal proposal that predates RFC process
Status: Implemented
Introduce a form of ternary conditional using if cond then value else alternative
syntax.
Luau does not have a first-class ternary operator; when a ternary operator is needed, it is usually emulated with and/or
expression, such as cond and value or alternative
.
This expression evaluates to value
if cond
and value
are truthy, and alternative
otherwise. In particular it means that when value
is false
or nil
, the result of the entire expression is alternative
even when cond
is truthy - which doesn’t match the expected ternary logic and is a frequent source of subtle errors.
Instead of and/or
, if/else
statement can be used but since that requires a separate mutable variable, this option isn’t ergonomic. An immediately invoked function expression is also unergonomic and results in performance issues at runtime.
To solve these problems, we propose introducing a first-class ternary conditional. Instead of ? :
common in C-like languages, we propose an if-then-else
expression form that is syntactically similar to if-then-else
statement, but lacks terminating end
.
Concretely, the if-then-else
expression must match if <expr> then <expr> else <expr>
; it can also contain an arbitrary number of elseif
clauses, like if <expr> then <expr> elseif <expr> then <expr> else <expr>
. Unlike if statements, else
is mandatory.
The result of the expression is the then-expression when condition is truthy (not nil
or false
) and else-expression otherwise. Only one of the two possible resulting expressions is evaluated.
Example:
local x = if FFlagFoo then A else B
MyComponent.validateProps = t.strictInterface({
layoutOrder = t.optional(t.number),
newThing = if FFlagUseNewThing then t.whatever() else nil,
})
Note that else
is mandatory because it’s always better to be explicit. If it weren’t mandatory, it opens the possiblity that someone might be writing a chain of if-then-else and forgot to add in the final else
that doesn’t return a nil
value! Enforcing this syntactically ensures the program does not run. Also, with it being mandatory, it solves many cases where parsing the expression is ambiguous due to the infamous dangling else.
This example will not do what it looks like it’s supposed to do! The if expression will successfully parse and be interpreted as to return h()
if g()
evaluates to some falsy value, when in actual fact the clear intention is to evaluate h()
only if f()
is falsy.
if f() then
...
local foo = if g() then x
else
h()
...
end
The only way to solve this had we chose optional else
branch would be to wrap the if expression in parentheses or to place a semi-colon.
Studio’s script editor autocomplete currently adds an indented block followed by end
whenever a line ends that includes a then
token. This can make use of the if expression unpleasant as developers have to keep fixing the code by removing auto-inserted end
. We can work around this on the editor side by (short-term) differentiating between whether if
token is the first on its line, and (long-term) by refactoring completion engine to use infallible parser for the block completer.
Parser recovery can also be more fragile due to leading if
keyword - when if
was encountered previously, it always meant an unfinished expression, but now it may start an if-expr
that, when confused with if-end
statement can lead to a substantially incorrect parse that is difficult to recover from. However, similar issues occur frequently due to function call statements and as such it’s not clear that this makes the recovery materially worse.
While this is not a problem today, in the past we’ve contemplated adding support for mid-block return
statements; these would create an odd grammatical quirk where an if..then
statement following an empty return
would parse as an if
expression. This would happen even without if
expressions though for function calls (e.g. return
followed by print(1)
), and is more of a problem with the potential return
statement changes and less of a problem with this proposal.
We’ve evaluated many alternatives for the proposed syntax.
b if a else c
Undesirable because expression evaluation order is not left-to-right which is a departure from all other Lua expressions. Additionally, since b
may be ending a statement (followed by if
statement), resolving this ambiguity requires parsing a
as expression and backtracking if else
is not found, which is expensive and likely to introduce further ambiguities.
a ? b : c
Problematic because :
is used for method calls. In Julia ? :
and :
are both operators which are disambiguated by requiring spaces in the first case and prohibiting them in the second case; this breaks backwards compatibility and doesn’t match the rest of the language where whitespace in the syntax is not significant.
iff(a, b, c)
If implemented as a regular function call, this would break short-circuit behavior. If implemented as a special builtin, it would look like a regular function call but have magical behavior – something likely to confuse developers.
a ?? b !! c
Syntax deemed too unconventional to use in Luau.
(if a then b else c)
Ada uses this syntax (with parentheses required for clarity). Similar solutions were discussed for as
previously and rejected to make it easier for humans and machines to understand the language syntax.
a then b else c
This is ambiguous in some cases (like within if condition) so not feasible from a grammar perspective.
if a then b else c end
The end
here is unnecessary since c
is not a block of statements – it is simply an expression. Thus, use of end
here would be inconsistent with its other uses in the language. It also makes the syntax more cumbersome to use and could lead to developers sticking with the error-prone a and b or c
alternative.
elseif
supportWe discussed a simpler version of this proposal without elseif
support. Unlike if statements, here elseif
is purely syntactic sugar as it’s fully equivalent to else if
. However, supporting elseif
makes if expression more consistent with if statement - it is likely that developers familiar with Luau are going to try using elseif
out of habit. Since supporting elseif
here is trivial we decided to keep it for consistency.