Skip to content

Commit 15119cf

Browse files
Implement ?? operator
1 parent ecb94dd commit 15119cf

File tree

6 files changed

+135
-51
lines changed

6 files changed

+135
-51
lines changed

src/System.Management.Automation/engine/parser/Compiler.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5247,6 +5247,15 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst)
52475247
CachedReflectionInfo.ParserOps_SplitOperator,
52485248
_executionContextParameter, Expression.Constant(binaryExpressionAst.ErrorPosition), lhs.Cast(typeof(object)), rhs.Cast(typeof(object)),
52495249
ExpressionCache.Constant(false));
5250+
case TokenKind.QuestionQuestion:
5251+
if (lhs is ConstantExpression lhsConstExpr && lhsConstExpr.Value != null)
5252+
{
5253+
return lhs;
5254+
}
5255+
else
5256+
{
5257+
return Expression.Coalesce(lhs, rhs);
5258+
}
52505259
}
52515260

52525261
throw new InvalidOperationException("Unknown token in binary operator.");

src/System.Management.Automation/engine/parser/token.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,9 @@ public enum TokenKind
419419
/// <summary>The null conditional assignment operator '?='.</summary>
420420
QuestionEquals = 101,
421421

422+
/// <summary>The null conditional assignment operator '?='.</summary>
423+
QuestionQuestion = 101,
424+
422425
#endregion Operators
423426

424427
#region Keywords
@@ -858,7 +861,7 @@ public static class TokenTraits
858861
/* Colon */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode,
859862
/* QuestionMark */ TokenFlags.TernaryOperator | TokenFlags.DisallowedInRestrictedMode,
860863
/* QuestionEquals */ TokenFlags.AssignmentOperator,
861-
/* Reserved slot 4 */ TokenFlags.None,
864+
/* QuestionQuestion */ TokenFlags.BinaryOperator,
862865
/* Reserved slot 5 */ TokenFlags.None,
863866
/* Reserved slot 6 */ TokenFlags.None,
864867
/* Reserved slot 7 */ TokenFlags.None,

src/System.Management.Automation/engine/parser/tokenizer.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5005,6 +5005,12 @@ internal Token NextToken()
50055005
return CheckOperatorInCommandMode(c, c1, TokenKind.QuestionEquals);
50065006
}
50075007

5008+
if (c1 == '?')
5009+
{
5010+
SkipChar();
5011+
return CheckOperatorInCommandMode(c, c1, TokenKind.QuestionQuestion);
5012+
}
5013+
50085014
return ScanGenericToken(c);
50095015

50105016
case '\0':
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
Describe 'NullConditionalOperations' -tag 'CI' {
5+
6+
Context "Null conditional assignment operator ?=" {
7+
BeforeAll {
8+
$someGuid = New-Guid
9+
10+
$typesTests = @(
11+
@{ name = 'string'; valueToSet = 'hello' }
12+
@{ name = 'dotnetType'; valueToSet = $someGuid }
13+
@{ name = 'byte'; valueToSet = [byte]0x94 }
14+
@{ name = 'intArray'; valueToSet = 1..2 }
15+
@{ name = 'stringArray'; valueToSet = 'a'..'c' }
16+
@{ name = 'emptyArray'; valueToSet = @(1, 2, 3) }
17+
)
18+
19+
}
20+
21+
It 'Variable doesnot exist' {
22+
Remove-Variable variableDoesNotExist -ErrorAction SilentlyContinue -Force
23+
24+
$variableDoesNotExist ?= 1
25+
$variableDoesNotExist | Should -Be 1
26+
27+
$variableDoesNotExist ?= 2
28+
$variableDoesNotExist | Should -Be 1
29+
}
30+
31+
It 'Variable exists and is null' {
32+
$variableDoesNotExist = $null
33+
34+
$variableDoesNotExist ?= 2
35+
$variableDoesNotExist | Should -Be 2
36+
}
37+
38+
It 'Validate types - <name> can be set' -TestCases $typesTests {
39+
param ($name, $valueToSet)
40+
41+
$x = $null
42+
$x ?= $valueToSet
43+
$x | Should -Be $valueToSet
44+
}
45+
46+
It 'Validate hashtable can be set' {
47+
$x = $null
48+
$x ?= @{ 1 = '1' }
49+
$x.Keys | Should -Be @(1)
50+
}
51+
52+
It 'Validate lhs is returned' {
53+
$x = 100
54+
$x ?= 200
55+
$x | Should -Be 100
56+
}
57+
58+
It 'Error case' {
59+
$e = $null
60+
$null = [System.Management.Automation.Language.Parser]::ParseInput('1 ?= 100', [ref] $null, [ref] $e)
61+
$e[0].ErrorId | Should -BeExactly 'InvalidLeftHandSide'
62+
}
63+
}
64+
65+
Context 'Null coalesce operator ??' {
66+
67+
It 'Variable does not exist' {
68+
$variableDoesNotExist ?? 100 | Should -Be 100
69+
}
70+
71+
It 'Variable exists but is null' {
72+
$x = $null
73+
$x ?? 100 | Should -Be 100
74+
}
75+
76+
It 'Lhs is not null' {
77+
$x = 100
78+
$x ?? 200 | Should -Be 100
79+
}
80+
81+
It 'Lhs is a non-null constant' {
82+
1 ?? 2 | Should -Be 1
83+
}
84+
85+
It 'Lhs is `$null' {
86+
$null ?? 'string value' | Should -BeExactly 'string value'
87+
}
88+
89+
It 'Check precedence of ?? expression resolution' {
90+
$x ?? $null ?? 100 | Should -Be 100
91+
$null ?? $null ?? 100 | Should -Be 100
92+
$null ?? $null ?? $null | Should -Be $null
93+
$x ?? 200 ?? $null | Should -Be 200
94+
$x ?? 200 ?? 300 | Should -Be 200
95+
100 ?? $x ?? 200 | Should -Be 100
96+
$null ?? 100 ?? $null ?? 200 | Should -Be 100
97+
}
98+
}
99+
100+
Context 'Combined usage of null conditional operators' {
101+
102+
It '?? and ?= used together' {
103+
$x ?= 100 ?? 200
104+
$x | Should -Be 100
105+
106+
$y ?= 100 ?? 200
107+
$y | Should -Be 100
108+
}
109+
}
110+
}

test/powershell/Language/Operators/NullCondtional.Tests.ps1

Lines changed: 0 additions & 50 deletions
This file was deleted.

test/powershell/Language/Parser/Parsing.Tests.ps1

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,12 @@ Describe 'function statement parsing' -Tags "CI" {
260260

261261
Describe 'assignment statement parsing' -Tags "CI" {
262262
ShouldBeParseError '$a,$b += 1,2' InvalidLeftHandSide 0
263+
ShouldBeParseError '1 ?= 1' InvalidLeftHandSide 0
264+
ShouldBeParseError '@() ?= 1' InvalidLeftHandSide 0
265+
ShouldBeParseError '@{} ?= 1' InvalidLeftHandSide 0
266+
ShouldBeParseError '1..2 ?= 1' InvalidLeftHandSide 0
267+
ShouldBeParseError '[int] ?= 1' InvalidLeftHandSide 0
268+
ShouldBeParseError '(Get-Variable x) ?= 1' InvalidLeftHandSide 0
263269
}
264270

265271
Describe 'splatting parsing' -Tags "CI" {

0 commit comments

Comments
 (0)