Skip to content

Commit ecb94dd

Browse files
Implement null condtional assigment operator
1 parent 985c9d3 commit ecb94dd

File tree

4 files changed

+83
-3
lines changed

4 files changed

+83
-3
lines changed

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,7 @@ internal Expression ReduceAssignment(ISupportsAssignment left, TokenKind tokenKi
794794
case TokenKind.MultiplyEquals: et = ExpressionType.Multiply; break;
795795
case TokenKind.DivideEquals: et = ExpressionType.Divide; break;
796796
case TokenKind.RemainderEquals: et = ExpressionType.Modulo; break;
797+
case TokenKind.QuestionEquals: return GetCoalesceExpression(right, av, tokenKind);
797798
}
798799

799800
var exprs = new List<Expression>();
@@ -803,6 +804,21 @@ internal Expression ReduceAssignment(ISupportsAssignment left, TokenKind tokenKi
803804
return Expression.Block(temps, exprs);
804805
}
805806

807+
private Expression GetCoalesceExpression(Expression rhs, IAssignableValue leftAssignableValue, TokenKind tokenKind)
808+
{
809+
var exprs = new List<Expression>();
810+
var temps = new List<ParameterExpression>();
811+
var leftExpr = leftAssignableValue.GetValue(this, exprs, temps);
812+
813+
if (tokenKind == TokenKind.QuestionEquals)
814+
{
815+
exprs.Add(leftAssignableValue.SetValue(this, Expression.MakeBinary(ExpressionType.Coalesce,leftExpr,rhs)));
816+
return Expression.Block(temps, exprs);
817+
}
818+
819+
return null;
820+
}
821+
806822
internal Expression GetLocal(int tupleIndex)
807823
{
808824
Expression result = LocalVariablesParameter;

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,9 @@ public enum TokenKind
416416
/// <summary>The ternary operator '?'.</summary>
417417
QuestionMark = 100,
418418

419+
/// <summary>The null conditional assignment operator '?='.</summary>
420+
QuestionEquals = 101,
421+
419422
#endregion Operators
420423

421424
#region Keywords
@@ -669,7 +672,7 @@ public enum TokenFlags
669672
SpecialOperator = 0x00001000,
670673

671674
/// <summary>
672-
/// The token is one of the assignment operators: '=', '+=', '-=', '*=', '/=', or '%='
675+
/// The token is one of the assignment operators: '=', '+=', '-=', '*=', '/=', '%=' or '?='
673676
/// </summary>
674677
AssignmentOperator = 0x00002000,
675678

@@ -854,7 +857,7 @@ public static class TokenTraits
854857
/* Shr */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CanConstantFold,
855858
/* Colon */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode,
856859
/* QuestionMark */ TokenFlags.TernaryOperator | TokenFlags.DisallowedInRestrictedMode,
857-
/* Reserved slot 3 */ TokenFlags.None,
860+
/* QuestionEquals */ TokenFlags.AssignmentOperator,
858861
/* Reserved slot 4 */ TokenFlags.None,
859862
/* Reserved slot 5 */ TokenFlags.None,
860863
/* Reserved slot 6 */ TokenFlags.None,
@@ -1051,7 +1054,7 @@ public static class TokenTraits
10511054
/* Shl */ "-shl",
10521055
/* Shr */ "-shr",
10531056
/* Colon */ ":",
1054-
/* Reserved slot 2 */ string.Empty,
1057+
/* QuestionEquals */ "?=",
10551058
/* Reserved slot 3 */ string.Empty,
10561059
/* Reserved slot 4 */ string.Empty,
10571060
/* Reserved slot 5 */ string.Empty,

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4996,6 +4996,17 @@ internal Token NextToken()
49964996
case '?' when InExpressionMode():
49974997
return this.NewToken(TokenKind.QuestionMark);
49984998

4999+
case '?':
5000+
c1 = PeekChar();
5001+
5002+
if (c1 == '=')
5003+
{
5004+
SkipChar();
5005+
return CheckOperatorInCommandMode(c, c1, TokenKind.QuestionEquals);
5006+
}
5007+
5008+
return ScanGenericToken(c);
5009+
49995010
case '\0':
50005011
if (AtEof())
50015012
{
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
Describe 'NullConditionalAssginmentOperator' -tag 'CI' {
5+
6+
BeforeAll {
7+
$someGuid = New-Guid
8+
9+
$typesTests = @(
10+
@{ name = 'string'; valueToSet = 'hello'}
11+
@{ name = 'dotnetType'; valueToSet = $someGuid}
12+
@{ name = 'byte'; valueToSet = [byte]0x94}
13+
@{ name = 'intArray'; valueToSet = 1..2}
14+
@{ name = 'stringArray'; valueToSet = 'a'..'c'}
15+
@{ name = 'emptyArray'; valueToSet = @(1,2,3)}
16+
)
17+
18+
}
19+
20+
It 'Variable doesnot exist' {
21+
Remove-Variable variableDoesNotExist -ErrorAction SilentlyContinue -Force
22+
23+
$variableDoesNotExist ?= 1
24+
$variableDoesNotExist | Should -Be 1
25+
26+
$variableDoesNotExist ?= 2
27+
$variableDoesNotExist | Should -Be 1
28+
}
29+
30+
It 'Variable exists and is null' {
31+
$variableDoesNotExist = $null
32+
33+
$variableDoesNotExist ?= 2
34+
$variableDoesNotExist | Should -Be 2
35+
}
36+
37+
It 'Validate types - <name> can be set' -TestCases $typesTests {
38+
param ($name, $valueToSet)
39+
40+
$x = $null
41+
$x ?= $valueToSet
42+
$x | Should -Be $valueToSet
43+
}
44+
45+
It 'Validate hashtable can be set' {
46+
$x = $null
47+
$x ?= @{ 1 = '1'}
48+
$x.Keys | Should -Be @(1)
49+
}
50+
}

0 commit comments

Comments
 (0)