Skip to content

Commit c3dafbb

Browse files
daxian-dbwjoeyaiello
authored andcommitted
Support the ternary operator (RFC0045) (#218)
* Ternary operator RFC * Address comments * Fix typo * Prepare ternary operator for accepting
1 parent 604dbba commit c3dafbb

File tree

1 file changed

+165
-0
lines changed

1 file changed

+165
-0
lines changed
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
---
2+
RFC: RFC0045
3+
Author: Dongbo Wang
4+
Status: Experimental-Accepted
5+
SupercededBy: N/A
6+
Version: 0.1
7+
Area: Language
8+
Comments Due: August 31st, 2019
9+
Plan to implement: Yes, PS7
10+
---
11+
12+
# Add Ternary Operator to PowerShell Language
13+
14+
The ternary operator is one of the highly demanded enhancement to PowerShell language.
15+
It received 46 up-votes in the issue [Suggestion: implement ternary conditionals](https://github.com/PowerShell/PowerShell/issues/3239) as of the writing of this RFC.
16+
The [prototype draft pull request](https://github.com/PowerShell/PowerShell/pull/10161) for this feature has also received a lot of attention and discussions.
17+
18+
The PowerShell Committee has approved to add the ternary operator to the language,
19+
and this RFC is mainly to capture the design points and implementation details.
20+
21+
## Motivation
22+
23+
> As a PowerShell user, I can use ternary operator as a replacement of if-else statement for simple conditional cases.
24+
25+
## Specification
26+
27+
We will follow the C# ternary operator syntax:
28+
29+
```none
30+
<condition> ? <if-true> : <if-false>
31+
```
32+
33+
Two basic design points:
34+
35+
- Ternary operator produces an expression, just like the binary and unary operators
36+
- Ternary operator has lower precedence order than the binary operator
37+
38+
The ternary operator behaves like the simplified if-else statement.
39+
The `condition-expression` will always be evaluated,
40+
and its result will be converted to boolean to determine which branch will be evaluated next:
41+
42+
- `if-true-expression` will execute if the condition's result is evaluated as `true`
43+
- `if-false-expression` will execute if the condition's result is evaluated as `false`
44+
45+
Some usage examples (see more examples [in the tests](https://github.com/PowerShell/PowerShell/pull/10367/files#diff-b63d92046948a571e210b3abb5a7e685)):
46+
47+
```powershell
48+
...
49+
$psExec = $IsCoreCLR ? 'pwsh' : 'powershell'
50+
...
51+
52+
...
53+
$argument = $env:CI -eq 'true' ? '-Tag CI' : '-Tag Daily'
54+
...
55+
56+
...
57+
$message = (Test-Path $path) ? "Path exists" : "Path not found"
58+
...
59+
60+
...
61+
return $blackList -contains $target ? (Block-Target $target) : (Register-Target $target)
62+
...
63+
```
64+
65+
#### Parser Updates
66+
67+
A new AST type `TernaryExpressionAst` will be introduced to represent a ternary operator expression.
68+
The public API surface of this type is as follows:
69+
70+
```c#
71+
public class TernaryExpressionAst : ExpressionAst
72+
{
73+
public TernaryExpressionAst(IScriptExtent extent, ExpressionAst condition, ExpressionAst ifTrue, ExpressionAst ifFalse);
74+
public ExpressionAst Condition { get; }
75+
public ExpressionAst IfTrue { get; }
76+
public ExpressionAst IfFalse { get; }
77+
78+
public Ast Copy();
79+
}
80+
```
81+
82+
The parsing grammar is as follows:
83+
84+
```none
85+
<expression> '?' new-lines:opt <expression> new-lines:opt ':' new-lines:opt <expression>
86+
```
87+
88+
The challenge in the parser update is around how to deal with number constant expressions more naturally with the ternary operators.
89+
When digits are mixed with `?` or `:`, such as `?123`, `:456`, `123:` and `?123:456`, they are recognized as generic tokens today,
90+
because it's totally legit to have a function or script with those names. However, when it comes to the ternary expression,
91+
this means confusing errors would occur for scripts like `return ${succeed}?0:1` since `?0:1` would be treated as a generic token.
92+
Therefore, spaces around `?` and `:` would be required to use number constant expressions with the ternary operator.
93+
That would certainly be counter-intuitive and should be addressed in the parser update.
94+
95+
The approach I took in the prototype is to make `?` and `:` conditionally force to start a new token when scanning for numbers.
96+
When it's known for sure that we are expecting an expression,
97+
allowing a generic token like `123?` is not useful and bound to result in parsing errors.
98+
In those cases, we will force to start a new token upon seeing characters `?` and `:` when scanning for a number,
99+
so that that number constant expressions can work with ternary operators more intuitively.
100+
101+
Note that, the handling of `?` and `:` doesn't change how those two characters can be used in variable expressions,
102+
and also doesn't affect function and commands with those two characters in names.
103+
104+
#### AST Visitors
105+
106+
Given the fact that we are adding a new AST type, necessary changes are required to `ICustomAstVisitor2`, `AstVisitor2`,
107+
and all other visitor types that implemnent those two.
108+
Obvious examples are the `VariableAnalysis` and `Compiler` as you have to update them properly to correctly enable the new language element.
109+
The not-so-obvious ones include `ConstantValueVisitor`, `SafeValueVisitor`, `TypeInferenceVisitor` and more.
110+
Take the `TypeInferenceVisitor` as an instance, failing to update it may cause tab completion issues that are hard to catch.
111+
112+
It's worth to discuss the `InternalVisit` implemention of the new AST type a bit.
113+
Many `Find*Visitor` types depend on this method to search through an AST tree,
114+
and almost all of them derive from `AstVisitor` instead of `AstVisitor2` because
115+
currently the AST types covered in `AstVisitor2` all have their own special scopes,
116+
such as the `TypeDefinitionAst` and `DynamicKeywordStatement`.
117+
In order to avoid making changes to all those existing `Find*Visitor` types,
118+
the `InternalVisit` method of `TernaryExpressionAst` is implemented as follows to accept an `AstVisitor` visitor for its AST members.
119+
In this way, those existing `Find*Visitor` types can work with this new AST type to search through its members.
120+
121+
```c#
122+
// This pattern appears in several other ASTs.
123+
internal override AstVisitAction InternalVisit(AstVisitor visitor)
124+
{
125+
var action = AstVisitAction.Continue;
126+
if (visitor is AstVisitor2 visitor2)
127+
{
128+
action = visitor2.VisitTernaryExpression(this);
129+
if (action == AstVisitAction.SkipChildren)
130+
return visitor.CheckForPostAction(this, AstVisitAction.Continue);
131+
}
132+
133+
if (action == AstVisitAction.Continue)
134+
action = Condition.InternalVisit(visitor);
135+
136+
if (action == AstVisitAction.Continue)
137+
action = IfTrue.InternalVisit(visitor);
138+
139+
if (action == AstVisitAction.Continue)
140+
action = IfFalse.InternalVisit(visitor);
141+
142+
return visitor.CheckForPostAction(this, action);
143+
}
144+
```
145+
146+
### Alternate Proposals and Considerations
147+
148+
Different syntax proposals were brought up in the discussions:
149+
150+
```none
151+
## Use '-then -else' instead of '? :'
152+
<condition-expression> -then <then-expression> -else <else-expression>
153+
```
154+
```none
155+
## Use syntax similar to '-replace' and '-f'
156+
<condition-expression> -ternary <then-expression>, <else-expression>
157+
```
158+
159+
One of the concerns with using `? :` is the visual acuity impact of scanning code where `?` typically has only been used as the alias for `Where-Object`.
160+
However, we expect that as we implement other language features like null-coalescing, null-conditional assignment, and null-conditional access,
161+
we are likely to use `?` in some other forms.
162+
Also, users can choose to opt out of using this new language feature,
163+
and the expectation is that most usage of this new language element will be from C# developers.
164+
165+
After a discussion at length, the PowerShell committee agrees to align with the C# syntax given the above considerations.

0 commit comments

Comments
 (0)