From ecb94dd63eca231cbaee56657199394cce29b12c Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 15 Aug 2019 10:49:24 -0700 Subject: [PATCH 01/23] Implement null condtional assigment operator --- .../engine/parser/Compiler.cs | 16 ++++++ .../engine/parser/token.cs | 9 ++-- .../engine/parser/tokenizer.cs | 11 ++++ .../Operators/NullCondtional.Tests.ps1 | 50 +++++++++++++++++++ 4 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 test/powershell/Language/Operators/NullCondtional.Tests.ps1 diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index c06e23c5c5d..88077c4da00 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -794,6 +794,7 @@ internal Expression ReduceAssignment(ISupportsAssignment left, TokenKind tokenKi case TokenKind.MultiplyEquals: et = ExpressionType.Multiply; break; case TokenKind.DivideEquals: et = ExpressionType.Divide; break; case TokenKind.RemainderEquals: et = ExpressionType.Modulo; break; + case TokenKind.QuestionEquals: return GetCoalesceExpression(right, av, tokenKind); } var exprs = new List(); @@ -803,6 +804,21 @@ internal Expression ReduceAssignment(ISupportsAssignment left, TokenKind tokenKi return Expression.Block(temps, exprs); } + private Expression GetCoalesceExpression(Expression rhs, IAssignableValue leftAssignableValue, TokenKind tokenKind) + { + var exprs = new List(); + var temps = new List(); + var leftExpr = leftAssignableValue.GetValue(this, exprs, temps); + + if (tokenKind == TokenKind.QuestionEquals) + { + exprs.Add(leftAssignableValue.SetValue(this, Expression.MakeBinary(ExpressionType.Coalesce,leftExpr,rhs))); + return Expression.Block(temps, exprs); + } + + return null; + } + internal Expression GetLocal(int tupleIndex) { Expression result = LocalVariablesParameter; diff --git a/src/System.Management.Automation/engine/parser/token.cs b/src/System.Management.Automation/engine/parser/token.cs index d11a5963dd1..9d7052e8e30 100644 --- a/src/System.Management.Automation/engine/parser/token.cs +++ b/src/System.Management.Automation/engine/parser/token.cs @@ -416,6 +416,9 @@ public enum TokenKind /// The ternary operator '?'. QuestionMark = 100, + /// The null conditional assignment operator '?='. + QuestionEquals = 101, + #endregion Operators #region Keywords @@ -669,7 +672,7 @@ public enum TokenFlags SpecialOperator = 0x00001000, /// - /// The token is one of the assignment operators: '=', '+=', '-=', '*=', '/=', or '%=' + /// The token is one of the assignment operators: '=', '+=', '-=', '*=', '/=', '%=' or '?=' /// AssignmentOperator = 0x00002000, @@ -854,7 +857,7 @@ public static class TokenTraits /* Shr */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CanConstantFold, /* Colon */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, /* QuestionMark */ TokenFlags.TernaryOperator | TokenFlags.DisallowedInRestrictedMode, - /* Reserved slot 3 */ TokenFlags.None, + /* QuestionEquals */ TokenFlags.AssignmentOperator, /* Reserved slot 4 */ TokenFlags.None, /* Reserved slot 5 */ TokenFlags.None, /* Reserved slot 6 */ TokenFlags.None, @@ -1051,7 +1054,7 @@ public static class TokenTraits /* Shl */ "-shl", /* Shr */ "-shr", /* Colon */ ":", - /* Reserved slot 2 */ string.Empty, + /* QuestionEquals */ "?=", /* Reserved slot 3 */ string.Empty, /* Reserved slot 4 */ string.Empty, /* Reserved slot 5 */ string.Empty, diff --git a/src/System.Management.Automation/engine/parser/tokenizer.cs b/src/System.Management.Automation/engine/parser/tokenizer.cs index 6f56afdb9c0..e3a301534a1 100644 --- a/src/System.Management.Automation/engine/parser/tokenizer.cs +++ b/src/System.Management.Automation/engine/parser/tokenizer.cs @@ -4996,6 +4996,17 @@ internal Token NextToken() case '?' when InExpressionMode(): return this.NewToken(TokenKind.QuestionMark); + case '?': + c1 = PeekChar(); + + if (c1 == '=') + { + SkipChar(); + return CheckOperatorInCommandMode(c, c1, TokenKind.QuestionEquals); + } + + return ScanGenericToken(c); + case '\0': if (AtEof()) { diff --git a/test/powershell/Language/Operators/NullCondtional.Tests.ps1 b/test/powershell/Language/Operators/NullCondtional.Tests.ps1 new file mode 100644 index 00000000000..270d2a44b1b --- /dev/null +++ b/test/powershell/Language/Operators/NullCondtional.Tests.ps1 @@ -0,0 +1,50 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +Describe 'NullConditionalAssginmentOperator' -tag 'CI' { + + BeforeAll { + $someGuid = New-Guid + + $typesTests = @( + @{ name = 'string'; valueToSet = 'hello'} + @{ name = 'dotnetType'; valueToSet = $someGuid} + @{ name = 'byte'; valueToSet = [byte]0x94} + @{ name = 'intArray'; valueToSet = 1..2} + @{ name = 'stringArray'; valueToSet = 'a'..'c'} + @{ name = 'emptyArray'; valueToSet = @(1,2,3)} + ) + + } + + It 'Variable doesnot exist' { + Remove-Variable variableDoesNotExist -ErrorAction SilentlyContinue -Force + + $variableDoesNotExist ?= 1 + $variableDoesNotExist | Should -Be 1 + + $variableDoesNotExist ?= 2 + $variableDoesNotExist | Should -Be 1 + } + + It 'Variable exists and is null' { + $variableDoesNotExist = $null + + $variableDoesNotExist ?= 2 + $variableDoesNotExist | Should -Be 2 + } + + It 'Validate types - can be set' -TestCases $typesTests { + param ($name, $valueToSet) + + $x = $null + $x ?= $valueToSet + $x | Should -Be $valueToSet + } + + It 'Validate hashtable can be set' { + $x = $null + $x ?= @{ 1 = '1'} + $x.Keys | Should -Be @(1) + } +} From 15119cfb9665aab5042ed62e66078b4567b9ec48 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 22 Aug 2019 10:53:19 -0700 Subject: [PATCH 02/23] Implement ?? operator --- .../engine/parser/Compiler.cs | 9 ++ .../engine/parser/token.cs | 5 +- .../engine/parser/tokenizer.cs | 6 + .../Operators/NullConditional.Tests.ps1 | 110 ++++++++++++++++++ .../Operators/NullCondtional.Tests.ps1 | 50 -------- .../Language/Parser/Parsing.Tests.ps1 | 6 + 6 files changed, 135 insertions(+), 51 deletions(-) create mode 100644 test/powershell/Language/Operators/NullConditional.Tests.ps1 delete mode 100644 test/powershell/Language/Operators/NullCondtional.Tests.ps1 diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index 88077c4da00..de08492095e 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -5247,6 +5247,15 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) CachedReflectionInfo.ParserOps_SplitOperator, _executionContextParameter, Expression.Constant(binaryExpressionAst.ErrorPosition), lhs.Cast(typeof(object)), rhs.Cast(typeof(object)), ExpressionCache.Constant(false)); + case TokenKind.QuestionQuestion: + if (lhs is ConstantExpression lhsConstExpr && lhsConstExpr.Value != null) + { + return lhs; + } + else + { + return Expression.Coalesce(lhs, rhs); + } } throw new InvalidOperationException("Unknown token in binary operator."); diff --git a/src/System.Management.Automation/engine/parser/token.cs b/src/System.Management.Automation/engine/parser/token.cs index 9d7052e8e30..477d0e25926 100644 --- a/src/System.Management.Automation/engine/parser/token.cs +++ b/src/System.Management.Automation/engine/parser/token.cs @@ -419,6 +419,9 @@ public enum TokenKind /// The null conditional assignment operator '?='. QuestionEquals = 101, + /// The null conditional assignment operator '?='. + QuestionQuestion = 101, + #endregion Operators #region Keywords @@ -858,7 +861,7 @@ public static class TokenTraits /* Colon */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, /* QuestionMark */ TokenFlags.TernaryOperator | TokenFlags.DisallowedInRestrictedMode, /* QuestionEquals */ TokenFlags.AssignmentOperator, - /* Reserved slot 4 */ TokenFlags.None, + /* QuestionQuestion */ TokenFlags.BinaryOperator, /* Reserved slot 5 */ TokenFlags.None, /* Reserved slot 6 */ TokenFlags.None, /* Reserved slot 7 */ TokenFlags.None, diff --git a/src/System.Management.Automation/engine/parser/tokenizer.cs b/src/System.Management.Automation/engine/parser/tokenizer.cs index e3a301534a1..9456da5a628 100644 --- a/src/System.Management.Automation/engine/parser/tokenizer.cs +++ b/src/System.Management.Automation/engine/parser/tokenizer.cs @@ -5005,6 +5005,12 @@ internal Token NextToken() return CheckOperatorInCommandMode(c, c1, TokenKind.QuestionEquals); } + if (c1 == '?') + { + SkipChar(); + return CheckOperatorInCommandMode(c, c1, TokenKind.QuestionQuestion); + } + return ScanGenericToken(c); case '\0': diff --git a/test/powershell/Language/Operators/NullConditional.Tests.ps1 b/test/powershell/Language/Operators/NullConditional.Tests.ps1 new file mode 100644 index 00000000000..9f7ee81ac6d --- /dev/null +++ b/test/powershell/Language/Operators/NullConditional.Tests.ps1 @@ -0,0 +1,110 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +Describe 'NullConditionalOperations' -tag 'CI' { + + Context "Null conditional assignment operator ?=" { + BeforeAll { + $someGuid = New-Guid + + $typesTests = @( + @{ name = 'string'; valueToSet = 'hello' } + @{ name = 'dotnetType'; valueToSet = $someGuid } + @{ name = 'byte'; valueToSet = [byte]0x94 } + @{ name = 'intArray'; valueToSet = 1..2 } + @{ name = 'stringArray'; valueToSet = 'a'..'c' } + @{ name = 'emptyArray'; valueToSet = @(1, 2, 3) } + ) + + } + + It 'Variable doesnot exist' { + Remove-Variable variableDoesNotExist -ErrorAction SilentlyContinue -Force + + $variableDoesNotExist ?= 1 + $variableDoesNotExist | Should -Be 1 + + $variableDoesNotExist ?= 2 + $variableDoesNotExist | Should -Be 1 + } + + It 'Variable exists and is null' { + $variableDoesNotExist = $null + + $variableDoesNotExist ?= 2 + $variableDoesNotExist | Should -Be 2 + } + + It 'Validate types - can be set' -TestCases $typesTests { + param ($name, $valueToSet) + + $x = $null + $x ?= $valueToSet + $x | Should -Be $valueToSet + } + + It 'Validate hashtable can be set' { + $x = $null + $x ?= @{ 1 = '1' } + $x.Keys | Should -Be @(1) + } + + It 'Validate lhs is returned' { + $x = 100 + $x ?= 200 + $x | Should -Be 100 + } + + It 'Error case' { + $e = $null + $null = [System.Management.Automation.Language.Parser]::ParseInput('1 ?= 100', [ref] $null, [ref] $e) + $e[0].ErrorId | Should -BeExactly 'InvalidLeftHandSide' + } + } + + Context 'Null coalesce operator ??' { + + It 'Variable does not exist' { + $variableDoesNotExist ?? 100 | Should -Be 100 + } + + It 'Variable exists but is null' { + $x = $null + $x ?? 100 | Should -Be 100 + } + + It 'Lhs is not null' { + $x = 100 + $x ?? 200 | Should -Be 100 + } + + It 'Lhs is a non-null constant' { + 1 ?? 2 | Should -Be 1 + } + + It 'Lhs is `$null' { + $null ?? 'string value' | Should -BeExactly 'string value' + } + + It 'Check precedence of ?? expression resolution' { + $x ?? $null ?? 100 | Should -Be 100 + $null ?? $null ?? 100 | Should -Be 100 + $null ?? $null ?? $null | Should -Be $null + $x ?? 200 ?? $null | Should -Be 200 + $x ?? 200 ?? 300 | Should -Be 200 + 100 ?? $x ?? 200 | Should -Be 100 + $null ?? 100 ?? $null ?? 200 | Should -Be 100 + } + } + + Context 'Combined usage of null conditional operators' { + + It '?? and ?= used together' { + $x ?= 100 ?? 200 + $x | Should -Be 100 + + $y ?= 100 ?? 200 + $y | Should -Be 100 + } + } +} diff --git a/test/powershell/Language/Operators/NullCondtional.Tests.ps1 b/test/powershell/Language/Operators/NullCondtional.Tests.ps1 deleted file mode 100644 index 270d2a44b1b..00000000000 --- a/test/powershell/Language/Operators/NullCondtional.Tests.ps1 +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -Describe 'NullConditionalAssginmentOperator' -tag 'CI' { - - BeforeAll { - $someGuid = New-Guid - - $typesTests = @( - @{ name = 'string'; valueToSet = 'hello'} - @{ name = 'dotnetType'; valueToSet = $someGuid} - @{ name = 'byte'; valueToSet = [byte]0x94} - @{ name = 'intArray'; valueToSet = 1..2} - @{ name = 'stringArray'; valueToSet = 'a'..'c'} - @{ name = 'emptyArray'; valueToSet = @(1,2,3)} - ) - - } - - It 'Variable doesnot exist' { - Remove-Variable variableDoesNotExist -ErrorAction SilentlyContinue -Force - - $variableDoesNotExist ?= 1 - $variableDoesNotExist | Should -Be 1 - - $variableDoesNotExist ?= 2 - $variableDoesNotExist | Should -Be 1 - } - - It 'Variable exists and is null' { - $variableDoesNotExist = $null - - $variableDoesNotExist ?= 2 - $variableDoesNotExist | Should -Be 2 - } - - It 'Validate types - can be set' -TestCases $typesTests { - param ($name, $valueToSet) - - $x = $null - $x ?= $valueToSet - $x | Should -Be $valueToSet - } - - It 'Validate hashtable can be set' { - $x = $null - $x ?= @{ 1 = '1'} - $x.Keys | Should -Be @(1) - } -} diff --git a/test/powershell/Language/Parser/Parsing.Tests.ps1 b/test/powershell/Language/Parser/Parsing.Tests.ps1 index bf07d5914b8..ab879f46869 100644 --- a/test/powershell/Language/Parser/Parsing.Tests.ps1 +++ b/test/powershell/Language/Parser/Parsing.Tests.ps1 @@ -260,6 +260,12 @@ Describe 'function statement parsing' -Tags "CI" { Describe 'assignment statement parsing' -Tags "CI" { ShouldBeParseError '$a,$b += 1,2' InvalidLeftHandSide 0 + ShouldBeParseError '1 ?= 1' InvalidLeftHandSide 0 + ShouldBeParseError '@() ?= 1' InvalidLeftHandSide 0 + ShouldBeParseError '@{} ?= 1' InvalidLeftHandSide 0 + ShouldBeParseError '1..2 ?= 1' InvalidLeftHandSide 0 + ShouldBeParseError '[int] ?= 1' InvalidLeftHandSide 0 + ShouldBeParseError '(Get-Variable x) ?= 1' InvalidLeftHandSide 0 } Describe 'splatting parsing' -Tags "CI" { From 0cb1592642c28f7a15851f1b6e73a5b69449e67b Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Wed, 28 Aug 2019 16:20:53 -0700 Subject: [PATCH 03/23] Add QuestionDot token --- .../engine/parser/token.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/System.Management.Automation/engine/parser/token.cs b/src/System.Management.Automation/engine/parser/token.cs index 477d0e25926..13d60bf5eac 100644 --- a/src/System.Management.Automation/engine/parser/token.cs +++ b/src/System.Management.Automation/engine/parser/token.cs @@ -419,9 +419,12 @@ public enum TokenKind /// The null conditional assignment operator '?='. QuestionEquals = 101, - /// The null conditional assignment operator '?='. + /// The null coalesce operator '??'. QuestionQuestion = 101, + /// The null conditional member access operator '?.'. + QuestionDot = 102, + #endregion Operators #region Keywords @@ -862,7 +865,7 @@ public static class TokenTraits /* QuestionMark */ TokenFlags.TernaryOperator | TokenFlags.DisallowedInRestrictedMode, /* QuestionEquals */ TokenFlags.AssignmentOperator, /* QuestionQuestion */ TokenFlags.BinaryOperator, - /* Reserved slot 5 */ TokenFlags.None, + /* QuestionDot */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, /* Reserved slot 6 */ TokenFlags.None, /* Reserved slot 7 */ TokenFlags.None, /* Reserved slot 8 */ TokenFlags.None, @@ -879,7 +882,7 @@ public static class TokenTraits /* Reserved slot 19 */ TokenFlags.None, /* Reserved slot 20 */ TokenFlags.None, - #endregion Flags for operators + #endregion Flags for operators5 #region Flags for keywords @@ -1058,8 +1061,8 @@ public static class TokenTraits /* Shr */ "-shr", /* Colon */ ":", /* QuestionEquals */ "?=", - /* Reserved slot 3 */ string.Empty, - /* Reserved slot 4 */ string.Empty, + /* QuestionQuestion */ "??", + /* QuestionDot */ "?.", /* Reserved slot 5 */ string.Empty, /* Reserved slot 6 */ string.Empty, /* Reserved slot 7 */ string.Empty, From cfd73f9792e59b8d5622d718556dfa5d44371a9d Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Wed, 25 Sep 2019 15:58:19 -0700 Subject: [PATCH 04/23] Fix issues after merge and test fix --- src/System.Management.Automation/engine/parser/token.cs | 9 +++------ .../engine/parser/tokenizer.cs | 5 +---- .../Language/Operators/NullConditional.Tests.ps1 | 3 +++ 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/System.Management.Automation/engine/parser/token.cs b/src/System.Management.Automation/engine/parser/token.cs index 13d60bf5eac..577f0b213d6 100644 --- a/src/System.Management.Automation/engine/parser/token.cs +++ b/src/System.Management.Automation/engine/parser/token.cs @@ -420,10 +420,7 @@ public enum TokenKind QuestionEquals = 101, /// The null coalesce operator '??'. - QuestionQuestion = 101, - - /// The null conditional member access operator '?.'. - QuestionDot = 102, + QuestionQuestion = 102, #endregion Operators @@ -865,7 +862,7 @@ public static class TokenTraits /* QuestionMark */ TokenFlags.TernaryOperator | TokenFlags.DisallowedInRestrictedMode, /* QuestionEquals */ TokenFlags.AssignmentOperator, /* QuestionQuestion */ TokenFlags.BinaryOperator, - /* QuestionDot */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, + /* Reserved slot 5 */ TokenFlags.None, /* Reserved slot 6 */ TokenFlags.None, /* Reserved slot 7 */ TokenFlags.None, /* Reserved slot 8 */ TokenFlags.None, @@ -1062,7 +1059,7 @@ public static class TokenTraits /* Colon */ ":", /* QuestionEquals */ "?=", /* QuestionQuestion */ "??", - /* QuestionDot */ "?.", + /* Reserved slot 4 */ string.Empty, /* Reserved slot 5 */ string.Empty, /* Reserved slot 6 */ string.Empty, /* Reserved slot 7 */ string.Empty, diff --git a/src/System.Management.Automation/engine/parser/tokenizer.cs b/src/System.Management.Automation/engine/parser/tokenizer.cs index 9456da5a628..27b6997ce50 100644 --- a/src/System.Management.Automation/engine/parser/tokenizer.cs +++ b/src/System.Management.Automation/engine/parser/tokenizer.cs @@ -4994,9 +4994,6 @@ internal Token NextToken() return this.NewToken(TokenKind.Colon); case '?' when InExpressionMode(): - return this.NewToken(TokenKind.QuestionMark); - - case '?': c1 = PeekChar(); if (c1 == '=') @@ -5011,7 +5008,7 @@ internal Token NextToken() return CheckOperatorInCommandMode(c, c1, TokenKind.QuestionQuestion); } - return ScanGenericToken(c); + return this.NewToken(TokenKind.QuestionMark); case '\0': if (AtEof()) diff --git a/test/powershell/Language/Operators/NullConditional.Tests.ps1 b/test/powershell/Language/Operators/NullConditional.Tests.ps1 index 9f7ee81ac6d..049f05b2e66 100644 --- a/test/powershell/Language/Operators/NullConditional.Tests.ps1 +++ b/test/powershell/Language/Operators/NullConditional.Tests.ps1 @@ -63,6 +63,9 @@ Describe 'NullConditionalOperations' -tag 'CI' { } Context 'Null coalesce operator ??' { + BeforeEach { + $x = $null + } It 'Variable does not exist' { $variableDoesNotExist ?? 100 | Should -Be 100 From 137a37bf9f8b49bf3aea2cad46f8eddeb06580ec Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Wed, 25 Sep 2019 16:11:29 -0700 Subject: [PATCH 05/23] Change Null coalescing assigned operator to ??= --- .../engine/parser/Compiler.cs | 4 ++-- .../engine/parser/token.cs | 6 ++--- .../engine/parser/tokenizer.cs | 19 +++++++++++----- .../Operators/NullConditional.Tests.ps1 | 22 +++++++++---------- 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index de08492095e..82d142c9402 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -794,7 +794,7 @@ internal Expression ReduceAssignment(ISupportsAssignment left, TokenKind tokenKi case TokenKind.MultiplyEquals: et = ExpressionType.Multiply; break; case TokenKind.DivideEquals: et = ExpressionType.Divide; break; case TokenKind.RemainderEquals: et = ExpressionType.Modulo; break; - case TokenKind.QuestionEquals: return GetCoalesceExpression(right, av, tokenKind); + case TokenKind.QuestionQuestionEquals: return GetCoalesceExpression(right, av, tokenKind); } var exprs = new List(); @@ -810,7 +810,7 @@ private Expression GetCoalesceExpression(Expression rhs, IAssignableValue leftAs var temps = new List(); var leftExpr = leftAssignableValue.GetValue(this, exprs, temps); - if (tokenKind == TokenKind.QuestionEquals) + if (tokenKind == TokenKind.QuestionQuestionEquals) { exprs.Add(leftAssignableValue.SetValue(this, Expression.MakeBinary(ExpressionType.Coalesce,leftExpr,rhs))); return Expression.Block(temps, exprs); diff --git a/src/System.Management.Automation/engine/parser/token.cs b/src/System.Management.Automation/engine/parser/token.cs index 577f0b213d6..88475a8a5cc 100644 --- a/src/System.Management.Automation/engine/parser/token.cs +++ b/src/System.Management.Automation/engine/parser/token.cs @@ -417,7 +417,7 @@ public enum TokenKind QuestionMark = 100, /// The null conditional assignment operator '?='. - QuestionEquals = 101, + QuestionQuestionEquals = 101, /// The null coalesce operator '??'. QuestionQuestion = 102, @@ -860,7 +860,7 @@ public static class TokenTraits /* Shr */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CanConstantFold, /* Colon */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, /* QuestionMark */ TokenFlags.TernaryOperator | TokenFlags.DisallowedInRestrictedMode, - /* QuestionEquals */ TokenFlags.AssignmentOperator, + /* QuestionQuestionEquals */ TokenFlags.AssignmentOperator, /* QuestionQuestion */ TokenFlags.BinaryOperator, /* Reserved slot 5 */ TokenFlags.None, /* Reserved slot 6 */ TokenFlags.None, @@ -1057,7 +1057,7 @@ public static class TokenTraits /* Shl */ "-shl", /* Shr */ "-shr", /* Colon */ ":", - /* QuestionEquals */ "?=", + /* QuestionQuestionEquals */ "??=", /* QuestionQuestion */ "??", /* Reserved slot 4 */ string.Empty, /* Reserved slot 5 */ string.Empty, diff --git a/src/System.Management.Automation/engine/parser/tokenizer.cs b/src/System.Management.Automation/engine/parser/tokenizer.cs index 27b6997ce50..437eb726bf9 100644 --- a/src/System.Management.Automation/engine/parser/tokenizer.cs +++ b/src/System.Management.Automation/engine/parser/tokenizer.cs @@ -4996,18 +4996,25 @@ internal Token NextToken() case '?' when InExpressionMode(): c1 = PeekChar(); - if (c1 == '=') - { - SkipChar(); - return CheckOperatorInCommandMode(c, c1, TokenKind.QuestionEquals); - } - if (c1 == '?') { SkipChar(); + + c1 = PeekChar(); + + if (c1 == '=') + { + SkipChar(); + return CheckOperatorInCommandMode(c, c1, TokenKind.QuestionQuestionEquals); + } + return CheckOperatorInCommandMode(c, c1, TokenKind.QuestionQuestion); } + + + + return this.NewToken(TokenKind.QuestionMark); case '\0': diff --git a/test/powershell/Language/Operators/NullConditional.Tests.ps1 b/test/powershell/Language/Operators/NullConditional.Tests.ps1 index 049f05b2e66..75f39d923a6 100644 --- a/test/powershell/Language/Operators/NullConditional.Tests.ps1 +++ b/test/powershell/Language/Operators/NullConditional.Tests.ps1 @@ -3,7 +3,7 @@ Describe 'NullConditionalOperations' -tag 'CI' { - Context "Null conditional assignment operator ?=" { + Context "Null conditional assignment operator ??=" { BeforeAll { $someGuid = New-Guid @@ -21,17 +21,17 @@ Describe 'NullConditionalOperations' -tag 'CI' { It 'Variable doesnot exist' { Remove-Variable variableDoesNotExist -ErrorAction SilentlyContinue -Force - $variableDoesNotExist ?= 1 + $variableDoesNotExist ??= 1 $variableDoesNotExist | Should -Be 1 - $variableDoesNotExist ?= 2 + $variableDoesNotExist ??= 2 $variableDoesNotExist | Should -Be 1 } It 'Variable exists and is null' { $variableDoesNotExist = $null - $variableDoesNotExist ?= 2 + $variableDoesNotExist ??= 2 $variableDoesNotExist | Should -Be 2 } @@ -39,25 +39,25 @@ Describe 'NullConditionalOperations' -tag 'CI' { param ($name, $valueToSet) $x = $null - $x ?= $valueToSet + $x ??= $valueToSet $x | Should -Be $valueToSet } It 'Validate hashtable can be set' { $x = $null - $x ?= @{ 1 = '1' } + $x ??= @{ 1 = '1' } $x.Keys | Should -Be @(1) } It 'Validate lhs is returned' { $x = 100 - $x ?= 200 + $x ??= 200 $x | Should -Be 100 } It 'Error case' { $e = $null - $null = [System.Management.Automation.Language.Parser]::ParseInput('1 ?= 100', [ref] $null, [ref] $e) + $null = [System.Management.Automation.Language.Parser]::ParseInput('1 ??= 100', [ref] $null, [ref] $e) $e[0].ErrorId | Should -BeExactly 'InvalidLeftHandSide' } } @@ -102,11 +102,11 @@ Describe 'NullConditionalOperations' -tag 'CI' { Context 'Combined usage of null conditional operators' { - It '?? and ?= used together' { - $x ?= 100 ?? 200 + It '?? and ??= used together' { + $x ??= 100 ?? 200 $x | Should -Be 100 - $y ?= 100 ?? 200 + $y ??= 100 ?? 200 $y | Should -Be 100 } } From 94ad01f3e57b24deb59fb21c349f77145e354a30 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 26 Sep 2019 15:08:47 -0700 Subject: [PATCH 06/23] Make feature experimental --- .../ExperimentalFeature.cs | 5 +++- .../engine/parser/Compiler.cs | 23 +++++++++++-------- .../engine/parser/token.cs | 4 ++-- .../engine/parser/tokenizer.cs | 23 +++++++++---------- 4 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs index 71b0d0f86b9..bcbb241249c 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -114,7 +114,10 @@ static ExperimentalFeature() description: "New parameter set for ForEach-Object to run script blocks in parallel"), new ExperimentalFeature( name: "PSTernaryOperator", - description: "Support the ternary operator in PowerShell language") + description: "Support the ternary operator in PowerShell language"), + new ExperimentalFeature( + name: "PSNullCoalescingOperators", + description: "Support the null coalescing operator and null coalescing assignment operator in PowerShell language") }; EngineExperimentalFeatures = new ReadOnlyCollection(engineFeatures); diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index 82d142c9402..e5bb86d2143 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -806,17 +806,20 @@ internal Expression ReduceAssignment(ISupportsAssignment left, TokenKind tokenKi private Expression GetCoalesceExpression(Expression rhs, IAssignableValue leftAssignableValue, TokenKind tokenKind) { - var exprs = new List(); - var temps = new List(); - var leftExpr = leftAssignableValue.GetValue(this, exprs, temps); - - if (tokenKind == TokenKind.QuestionQuestionEquals) + if (ExperimentalFeature.IsEnabled("PSNullCoalescingOperators")) { - exprs.Add(leftAssignableValue.SetValue(this, Expression.MakeBinary(ExpressionType.Coalesce,leftExpr,rhs))); - return Expression.Block(temps, exprs); - } + var exprs = new List(); + var temps = new List(); + var leftExpr = leftAssignableValue.GetValue(this, exprs, temps); - return null; + if (tokenKind == TokenKind.QuestionQuestionEquals) + { + exprs.Add(leftAssignableValue.SetValue(this, Expression.MakeBinary(ExpressionType.Coalesce, leftExpr, rhs))); + return Expression.Block(temps, exprs); + } + } + + return null; } internal Expression GetLocal(int tupleIndex) @@ -5247,7 +5250,7 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) CachedReflectionInfo.ParserOps_SplitOperator, _executionContextParameter, Expression.Constant(binaryExpressionAst.ErrorPosition), lhs.Cast(typeof(object)), rhs.Cast(typeof(object)), ExpressionCache.Constant(false)); - case TokenKind.QuestionQuestion: + case TokenKind.QuestionQuestion when ExperimentalFeature.IsEnabled("PSNullCoalescingOperators") : if (lhs is ConstantExpression lhsConstExpr && lhsConstExpr.Value != null) { return lhs; diff --git a/src/System.Management.Automation/engine/parser/token.cs b/src/System.Management.Automation/engine/parser/token.cs index 88475a8a5cc..55fe80856aa 100644 --- a/src/System.Management.Automation/engine/parser/token.cs +++ b/src/System.Management.Automation/engine/parser/token.cs @@ -416,7 +416,7 @@ public enum TokenKind /// The ternary operator '?'. QuestionMark = 100, - /// The null conditional assignment operator '?='. + /// The null conditional assignment operator '??='. QuestionQuestionEquals = 101, /// The null coalesce operator '??'. @@ -675,7 +675,7 @@ public enum TokenFlags SpecialOperator = 0x00001000, /// - /// The token is one of the assignment operators: '=', '+=', '-=', '*=', '/=', '%=' or '?=' + /// The token is one of the assignment operators: '=', '+=', '-=', '*=', '/=', '%=' or '??=' /// AssignmentOperator = 0x00002000, diff --git a/src/System.Management.Automation/engine/parser/tokenizer.cs b/src/System.Management.Automation/engine/parser/tokenizer.cs index 437eb726bf9..58564bc5357 100644 --- a/src/System.Management.Automation/engine/parser/tokenizer.cs +++ b/src/System.Management.Automation/engine/parser/tokenizer.cs @@ -4996,24 +4996,23 @@ internal Token NextToken() case '?' when InExpressionMode(): c1 = PeekChar(); - if (c1 == '?') + if (ExperimentalFeature.IsEnabled("PSNullCoalescingOperators")) { - SkipChar(); - - c1 = PeekChar(); - - if (c1 == '=') + if (c1 == '?') { SkipChar(); - return CheckOperatorInCommandMode(c, c1, TokenKind.QuestionQuestionEquals); - } - - return CheckOperatorInCommandMode(c, c1, TokenKind.QuestionQuestion); - } - + c1 = PeekChar(); + if (c1 == '=') + { + SkipChar(); + return CheckOperatorInCommandMode(c, c1, TokenKind.QuestionQuestionEquals); + } + return CheckOperatorInCommandMode(c, c1, TokenKind.QuestionQuestion); + } + } return this.NewToken(TokenKind.QuestionMark); From dee80925c89ada8cb1ab2684f0abae704aba180e Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 26 Sep 2019 17:26:50 -0700 Subject: [PATCH 07/23] Add logic for skipping tests if experimental feature is disabled --- .../Operators/NullConditional.Tests.ps1 | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/test/powershell/Language/Operators/NullConditional.Tests.ps1 b/test/powershell/Language/Operators/NullConditional.Tests.ps1 index 75f39d923a6..7186f115f53 100644 --- a/test/powershell/Language/Operators/NullConditional.Tests.ps1 +++ b/test/powershell/Language/Operators/NullConditional.Tests.ps1 @@ -5,20 +5,34 @@ Describe 'NullConditionalOperations' -tag 'CI' { Context "Null conditional assignment operator ??=" { BeforeAll { - $someGuid = New-Guid - $typesTests = @( - @{ name = 'string'; valueToSet = 'hello' } - @{ name = 'dotnetType'; valueToSet = $someGuid } - @{ name = 'byte'; valueToSet = [byte]0x94 } - @{ name = 'intArray'; valueToSet = 1..2 } - @{ name = 'stringArray'; valueToSet = 'a'..'c' } - @{ name = 'emptyArray'; valueToSet = @(1, 2, 3) } - ) + $skipTest = -not $EnabledExperimentalFeatures.Contains('PSNullCoalescingOperators') + + if ($skipTest) { + Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'PSNullCoalescingOperators' to be enabled." -Verbose + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $PSDefaultParameterValues["it:skip"] = $true + } else { + $someGuid = New-Guid + $typesTests = @( + @{ name = 'string'; valueToSet = 'hello' } + @{ name = 'dotnetType'; valueToSet = $someGuid } + @{ name = 'byte'; valueToSet = [byte]0x94 } + @{ name = 'intArray'; valueToSet = 1..2 } + @{ name = 'stringArray'; valueToSet = 'a'..'c' } + @{ name = 'emptyArray'; valueToSet = @(1, 2, 3) } + ) + } + } + AfterAll { + if ($skipTest) { + $global:PSDefaultParameterValues = $originalDefaultParameterValues + } } It 'Variable doesnot exist' { + Remove-Variable variableDoesNotExist -ErrorAction SilentlyContinue -Force $variableDoesNotExist ??= 1 From 9826e6969461b3068cfd357c842c4bcf593d170c Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 26 Sep 2019 17:29:36 -0700 Subject: [PATCH 08/23] Fix parsing tests --- test/powershell/Language/Parser/Parsing.Tests.ps1 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/powershell/Language/Parser/Parsing.Tests.ps1 b/test/powershell/Language/Parser/Parsing.Tests.ps1 index ab879f46869..b87192d49ab 100644 --- a/test/powershell/Language/Parser/Parsing.Tests.ps1 +++ b/test/powershell/Language/Parser/Parsing.Tests.ps1 @@ -260,12 +260,12 @@ Describe 'function statement parsing' -Tags "CI" { Describe 'assignment statement parsing' -Tags "CI" { ShouldBeParseError '$a,$b += 1,2' InvalidLeftHandSide 0 - ShouldBeParseError '1 ?= 1' InvalidLeftHandSide 0 - ShouldBeParseError '@() ?= 1' InvalidLeftHandSide 0 - ShouldBeParseError '@{} ?= 1' InvalidLeftHandSide 0 - ShouldBeParseError '1..2 ?= 1' InvalidLeftHandSide 0 - ShouldBeParseError '[int] ?= 1' InvalidLeftHandSide 0 - ShouldBeParseError '(Get-Variable x) ?= 1' InvalidLeftHandSide 0 + ShouldBeParseError '1 ??= 1' InvalidLeftHandSide 0 + ShouldBeParseError '@() ??= 1' InvalidLeftHandSide 0 + ShouldBeParseError '@{} ??= 1' InvalidLeftHandSide 0 + ShouldBeParseError '1..2 ??= 1' InvalidLeftHandSide 0 + ShouldBeParseError '[int] ??= 1' InvalidLeftHandSide 0 + ShouldBeParseError '(Get-Variable x) ??= 1' InvalidLeftHandSide 0 } Describe 'splatting parsing' -Tags "CI" { From 9e599c493225af78e724283bbb15ba698f1abd41 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Tue, 1 Oct 2019 16:10:09 -0700 Subject: [PATCH 09/23] Address code review feedback --- .../engine/parser/Compiler.cs | 13 +- .../engine/parser/token.cs | 2 +- .../engine/parser/tokenizer.cs | 1 - .../Operators/NullConditional.Tests.ps1 | 129 ++++++++++++++---- 4 files changed, 112 insertions(+), 33 deletions(-) diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index e5bb86d2143..4970243b14d 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -221,6 +221,8 @@ internal static class CachedReflectionInfo internal static readonly MethodInfo LanguagePrimitives_GetInvalidCastMessages = typeof(LanguagePrimitives).GetMethod(nameof(LanguagePrimitives.GetInvalidCastMessages), staticFlags); + internal static readonly MethodInfo LanguagePrimitives_IsNullLike = + typeof(LanguagePrimitives).GetMethod(nameof(LanguagePrimitives.IsNullLike), staticPublicFlags); internal static readonly MethodInfo LanguagePrimitives_ThrowInvalidCastException = typeof(LanguagePrimitives).GetMethod(nameof(LanguagePrimitives.ThrowInvalidCastException), staticFlags); @@ -814,7 +816,11 @@ private Expression GetCoalesceExpression(Expression rhs, IAssignableValue leftAs if (tokenKind == TokenKind.QuestionQuestionEquals) { - exprs.Add(leftAssignableValue.SetValue(this, Expression.MakeBinary(ExpressionType.Coalesce, leftExpr, rhs))); + exprs.Add( + Expression.Condition( + Expression.Call(CachedReflectionInfo.LanguagePrimitives_IsNullLike, leftExpr), + leftAssignableValue.SetValue(this, rhs), + leftExpr.Convert(typeof(object)))); return Expression.Block(temps, exprs); } } @@ -5257,7 +5263,10 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) } else { - return Expression.Coalesce(lhs, rhs); + return Expression.Condition( + Expression.Call(CachedReflectionInfo.LanguagePrimitives_IsNullLike, lhs), + rhs.Convert(typeof(object)), + lhs.Convert(typeof(object))); } } diff --git a/src/System.Management.Automation/engine/parser/token.cs b/src/System.Management.Automation/engine/parser/token.cs index 55fe80856aa..8b4db096a60 100644 --- a/src/System.Management.Automation/engine/parser/token.cs +++ b/src/System.Management.Automation/engine/parser/token.cs @@ -879,7 +879,7 @@ public static class TokenTraits /* Reserved slot 19 */ TokenFlags.None, /* Reserved slot 20 */ TokenFlags.None, - #endregion Flags for operators5 + #endregion Flags for operators #region Flags for keywords diff --git a/src/System.Management.Automation/engine/parser/tokenizer.cs b/src/System.Management.Automation/engine/parser/tokenizer.cs index 58564bc5357..fbf0900d793 100644 --- a/src/System.Management.Automation/engine/parser/tokenizer.cs +++ b/src/System.Management.Automation/engine/parser/tokenizer.cs @@ -5001,7 +5001,6 @@ internal Token NextToken() if (c1 == '?') { SkipChar(); - c1 = PeekChar(); if (c1 == '=') diff --git a/test/powershell/Language/Operators/NullConditional.Tests.ps1 b/test/powershell/Language/Operators/NullConditional.Tests.ps1 index 7186f115f53..085f0cdcece 100644 --- a/test/powershell/Language/Operators/NullConditional.Tests.ps1 +++ b/test/powershell/Language/Operators/NullConditional.Tests.ps1 @@ -1,36 +1,35 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -Describe 'NullConditionalOperations' -tag 'CI' { - - Context "Null conditional assignment operator ??=" { - BeforeAll { - - $skipTest = -not $EnabledExperimentalFeatures.Contains('PSNullCoalescingOperators') - - if ($skipTest) { - Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'PSNullCoalescingOperators' to be enabled." -Verbose - $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() - $PSDefaultParameterValues["it:skip"] = $true - } else { - $someGuid = New-Guid - $typesTests = @( - @{ name = 'string'; valueToSet = 'hello' } - @{ name = 'dotnetType'; valueToSet = $someGuid } - @{ name = 'byte'; valueToSet = [byte]0x94 } - @{ name = 'intArray'; valueToSet = 1..2 } - @{ name = 'stringArray'; valueToSet = 'a'..'c' } - @{ name = 'emptyArray'; valueToSet = @(1, 2, 3) } - ) - } +Describe 'NullConditionalOperations' -Tags 'CI' { + BeforeAll { + + $skipTest = -not $EnabledExperimentalFeatures.Contains('PSNullCoalescingOperators') + + if ($skipTest) { + Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'PSNullCoalescingOperators' to be enabled." -Verbose + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $PSDefaultParameterValues["it:skip"] = $true + } else { + $someGuid = New-Guid + $typesTests = @( + @{ name = 'string'; valueToSet = 'hello' } + @{ name = 'dotnetType'; valueToSet = $someGuid } + @{ name = 'byte'; valueToSet = [byte]0x94 } + @{ name = 'intArray'; valueToSet = 1..2 } + @{ name = 'stringArray'; valueToSet = 'a'..'c' } + @{ name = 'emptyArray'; valueToSet = @(1, 2, 3) } + ) } + } - AfterAll { - if ($skipTest) { - $global:PSDefaultParameterValues = $originalDefaultParameterValues - } + AfterAll { + if ($skipTest) { + $global:PSDefaultParameterValues = $originalDefaultParameterValues } + } + Context "Null conditional assignment operator ??=" { It 'Variable doesnot exist' { Remove-Variable variableDoesNotExist -ErrorAction SilentlyContinue -Force @@ -69,6 +68,29 @@ Describe 'NullConditionalOperations' -tag 'CI' { $x | Should -Be 100 } + It 'Rhs is a cmdlet' { + $x ??= (Get-Alias -Name 'where') + $x.Definition | Should -BeExactly 'Where-Object' + } + + It 'Lhs is DBNull' { + $x = [System.DBNull]::Value + $x ??= 200 + $x | Should -Be 200 + } + + It 'Lhs is AutomationNull' { + $x = [System.Management.Automation.Internal.AutomationNull]::Value + $x ??= 200 + $x | Should -Be 200 + } + + It 'Lhs is NullString' { + $x = [NullString]::Value + $x ??= 200 + $x | Should -Be 200 + } + It 'Error case' { $e = $null $null = [System.Management.Automation.Language.Parser]::ParseInput('1 ??= 100', [ref] $null, [ref] $e) @@ -86,7 +108,6 @@ Describe 'NullConditionalOperations' -tag 'CI' { } It 'Variable exists but is null' { - $x = $null $x ?? 100 | Should -Be 100 } @@ -112,16 +133,66 @@ Describe 'NullConditionalOperations' -tag 'CI' { 100 ?? $x ?? 200 | Should -Be 100 $null ?? 100 ?? $null ?? 200 | Should -Be 100 } + + It 'Rhs is a cmdlet' { + $result = $x ?? (Get-Alias -Name 'where') + $result.Definition | Should -BeExactly 'Where-Object' + } + + It 'Lhs is DBNull' { + $x = [System.DBNull]::Value + $x ?? 200 | Should -Be 200 + } + + It 'Lhs is AutomationNull' { + $x = [System.Management.Automation.Internal.AutomationNull]::Value + $x ?? 200 | Should -Be 200 + } + + It 'Lhs is NullString' { + $x = [NullString]::Value + $x ?? 200 | Should -Be 200 + } + + It 'Rhs is a get variable expression' { + $x = [System.DBNull]::Value + $y = 2 + $x ?? $y | Should -Be 2 + } + + It 'Lhs is a constant' { + [System.DBNull]::Value ?? 2 | Should -Be 2 + } + + It 'Both are null constants' { + [System.DBNull]::Value ?? [NullString]::Value | Should -Be ([NullString]::Value) + } } Context 'Combined usage of null conditional operators' { + BeforeAll { + function GetNull { + return $null + } + + function GetHello { + return "Hello" + } + } + + BeforeEach { + $x = $null + } + It '?? and ??= used together' { $x ??= 100 ?? 200 $x | Should -Be 100 + } - $y ??= 100 ?? 200 - $y | Should -Be 100 + It '?? and ??= chaining' { + $x ??= $x ?? (GetNull) ?? (GetHello) + $x | Should -BeExactly 'Hello' } } } From 8fd95722f7e04bc199e2e7008f9c9d0f7724b306 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Tue, 1 Oct 2019 17:09:55 -0700 Subject: [PATCH 10/23] Remove parsing test as it interfers with ternary operator --- test/powershell/Language/Parser/Parsing.Tests.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/test/powershell/Language/Parser/Parsing.Tests.ps1 b/test/powershell/Language/Parser/Parsing.Tests.ps1 index b87192d49ab..bb7f78ed8b7 100644 --- a/test/powershell/Language/Parser/Parsing.Tests.ps1 +++ b/test/powershell/Language/Parser/Parsing.Tests.ps1 @@ -265,7 +265,6 @@ Describe 'assignment statement parsing' -Tags "CI" { ShouldBeParseError '@{} ??= 1' InvalidLeftHandSide 0 ShouldBeParseError '1..2 ??= 1' InvalidLeftHandSide 0 ShouldBeParseError '[int] ??= 1' InvalidLeftHandSide 0 - ShouldBeParseError '(Get-Variable x) ??= 1' InvalidLeftHandSide 0 } Describe 'splatting parsing' -Tags "CI" { From 02dc105cce1aa6e815c66dce045696c6df815598 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Wed, 2 Oct 2019 14:29:11 -0700 Subject: [PATCH 11/23] Add few more tests --- .../Language/Operators/NullConditional.Tests.ps1 | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/powershell/Language/Operators/NullConditional.Tests.ps1 b/test/powershell/Language/Operators/NullConditional.Tests.ps1 index 085f0cdcece..72da4baf6f8 100644 --- a/test/powershell/Language/Operators/NullConditional.Tests.ps1 +++ b/test/powershell/Language/Operators/NullConditional.Tests.ps1 @@ -96,6 +96,13 @@ Describe 'NullConditionalOperations' -Tags 'CI' { $null = [System.Management.Automation.Language.Parser]::ParseInput('1 ??= 100', [ref] $null, [ref] $e) $e[0].ErrorId | Should -BeExactly 'InvalidLeftHandSide' } + + It 'Variable is non-null' { + $num = 10 + $num ??= 20 + + $num | Should -Be 10 + } } Context 'Null coalesce operator ??' { @@ -194,5 +201,10 @@ Describe 'NullConditionalOperations' -Tags 'CI' { $x ??= $x ?? (GetNull) ?? (GetHello) $x | Should -BeExactly 'Hello' } + + It 'First two are null' { + $z ??= $null ?? 100 + $z | Should -Be 100 + } } } From 4e8019ec10bd46102607b59c3912c4fa2330097d Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 3 Oct 2019 17:08:21 -0700 Subject: [PATCH 12/23] Address Rob's feedback --- .../engine/parser/Compiler.cs | 37 ++-- .../engine/parser/token.cs | 186 +++++++++--------- .../engine/parser/tokenizer.cs | 4 +- .../Operators/NullConditional.Tests.ps1 | 16 ++ .../Language/Parser/Parsing.Tests.ps1 | 9 + 5 files changed, 142 insertions(+), 110 deletions(-) diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index 4970243b14d..0ce7228d999 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -14,6 +14,7 @@ using System.Management.Automation.Internal; using System.Management.Automation.Interpreter; using System.Management.Automation.Runspaces; +using System.Net; using System.Reflection; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; @@ -662,6 +663,8 @@ internal class Compiler : ICustomAstVisitor2 new Dictionary(SpecialVariables.AutomaticVariableTypes.Length + SpecialVariables.PreferenceVariableTypes.Length, StringComparer.OrdinalIgnoreCase); + private static bool? IsNullCoalescingFeatureEnabled; + static Compiler() { _functionContext = Expression.Parameter(typeof(FunctionContext), "funcContext"); @@ -808,24 +811,28 @@ internal Expression ReduceAssignment(ISupportsAssignment left, TokenKind tokenKi private Expression GetCoalesceExpression(Expression rhs, IAssignableValue leftAssignableValue, TokenKind tokenKind) { - if (ExperimentalFeature.IsEnabled("PSNullCoalescingOperators")) + IsNullCoalescingFeatureEnabled ??= ExperimentalFeature.IsEnabled("PSNullCoalescingOperators"); + + if (IsNullCoalescingFeatureEnabled.Value == false) { - var exprs = new List(); - var temps = new List(); - var leftExpr = leftAssignableValue.GetValue(this, exprs, temps); + return null; + } - if (tokenKind == TokenKind.QuestionQuestionEquals) - { - exprs.Add( - Expression.Condition( - Expression.Call(CachedReflectionInfo.LanguagePrimitives_IsNullLike, leftExpr), - leftAssignableValue.SetValue(this, rhs), - leftExpr.Convert(typeof(object)))); - return Expression.Block(temps, exprs); - } + var exprs = new List(); + var temps = new List(); + Expression leftExpr = leftAssignableValue.GetValue(this, exprs, temps); + + if (tokenKind == TokenKind.QuestionQuestionEquals) + { + exprs.Add( + Expression.Condition( + Expression.Call(CachedReflectionInfo.LanguagePrimitives_IsNullLike, leftExpr), + leftAssignableValue.SetValue(this, rhs), + leftExpr.Convert(typeof(object)))); + return Expression.Block(temps, exprs); } - - return null; + + return null; } internal Expression GetLocal(int tupleIndex) diff --git a/src/System.Management.Automation/engine/parser/token.cs b/src/System.Management.Automation/engine/parser/token.cs index 8b4db096a60..08c3edd7492 100644 --- a/src/System.Management.Automation/engine/parser/token.cs +++ b/src/System.Management.Automation/engine/parser/token.cs @@ -785,99 +785,99 @@ public static class TokenTraits #region Flags for operators - /* AndAnd */ TokenFlags.BinaryOperator | TokenFlags.ParseModeInvariant, - /* OrOr */ TokenFlags.BinaryOperator | TokenFlags.ParseModeInvariant, - /* Ampersand */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, - /* Pipe */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, - /* Comma */ TokenFlags.UnaryOperator | TokenFlags.ParseModeInvariant, - /* MinusMinus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, - /* PlusPlus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, - /* DotDot */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceRange | TokenFlags.DisallowedInRestrictedMode, - /* ColonColon */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, - /* Dot */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, - /* Exclaim */ TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, - /* Multiply */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceMultiply | TokenFlags.CanConstantFold, - /* Divide */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceMultiply | TokenFlags.CanConstantFold, - /* Rem */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceMultiply | TokenFlags.CanConstantFold, - /* Plus */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceAdd | TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, - /* Minus */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceAdd | TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, - /* Equals */ TokenFlags.AssignmentOperator, - /* PlusEquals */ TokenFlags.AssignmentOperator, - /* MinusEquals */ TokenFlags.AssignmentOperator, - /* MultiplyEquals */ TokenFlags.AssignmentOperator, - /* DivideEquals */ TokenFlags.AssignmentOperator, - /* RemainderEquals */ TokenFlags.AssignmentOperator, - /* Redirection */ TokenFlags.DisallowedInRestrictedMode, - /* RedirectInStd */ TokenFlags.ParseModeInvariant | TokenFlags.DisallowedInRestrictedMode, - /* Format */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceFormat | TokenFlags.DisallowedInRestrictedMode, - /* Not */ TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, - /* Bnot */ TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, - /* And */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceLogical | TokenFlags.CanConstantFold, - /* Or */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceLogical | TokenFlags.CanConstantFold, - /* Xor */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceLogical | TokenFlags.CanConstantFold, - /* Band */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceBitwise | TokenFlags.CanConstantFold, - /* Bor */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceBitwise | TokenFlags.CanConstantFold, - /* Bxor */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceBitwise | TokenFlags.CanConstantFold, - /* Join */ TokenFlags.BinaryOperator | TokenFlags.UnaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, - /* Ieq */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Ine */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Ige */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Igt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Ilt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Ile */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Ilike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Inotlike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Imatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, - /* Inotmatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, - /* Ireplace */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, - /* Icontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Inotcontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Iin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Inotin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Isplit */ TokenFlags.BinaryOperator | TokenFlags.UnaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, - /* Ceq */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cne */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cge */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cgt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Clt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cle */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Clike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cnotlike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cmatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, - /* Cnotmatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, - /* Creplace */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, - /* Ccontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cnotcontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cnotin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Csplit */ TokenFlags.BinaryOperator | TokenFlags.UnaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, - /* Is */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* IsNot */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* As */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, - /* PostFixPlusPlus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, - /* PostFixMinusMinus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, - /* Shl */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CanConstantFold, - /* Shr */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CanConstantFold, - /* Colon */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, - /* QuestionMark */ TokenFlags.TernaryOperator | TokenFlags.DisallowedInRestrictedMode, - /* QuestionQuestionEquals */ TokenFlags.AssignmentOperator, - /* QuestionQuestion */ TokenFlags.BinaryOperator, - /* Reserved slot 5 */ TokenFlags.None, - /* Reserved slot 6 */ TokenFlags.None, - /* Reserved slot 7 */ TokenFlags.None, - /* Reserved slot 8 */ TokenFlags.None, - /* Reserved slot 9 */ TokenFlags.None, - /* Reserved slot 10 */ TokenFlags.None, - /* Reserved slot 11 */ TokenFlags.None, - /* Reserved slot 12 */ TokenFlags.None, - /* Reserved slot 13 */ TokenFlags.None, - /* Reserved slot 14 */ TokenFlags.None, - /* Reserved slot 15 */ TokenFlags.None, - /* Reserved slot 16 */ TokenFlags.None, - /* Reserved slot 17 */ TokenFlags.None, - /* Reserved slot 18 */ TokenFlags.None, - /* Reserved slot 19 */ TokenFlags.None, - /* Reserved slot 20 */ TokenFlags.None, + /* AndAnd */ TokenFlags.BinaryOperator | TokenFlags.ParseModeInvariant, + /* OrOr */ TokenFlags.BinaryOperator | TokenFlags.ParseModeInvariant, + /* Ampersand */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, + /* Pipe */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, + /* Comma */ TokenFlags.UnaryOperator | TokenFlags.ParseModeInvariant, + /* MinusMinus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, + /* PlusPlus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, + /* DotDot */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceRange | TokenFlags.DisallowedInRestrictedMode, + /* ColonColon */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, + /* Dot */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, + /* Exclaim */ TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, + /* Multiply */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceMultiply | TokenFlags.CanConstantFold, + /* Divide */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceMultiply | TokenFlags.CanConstantFold, + /* Rem */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceMultiply | TokenFlags.CanConstantFold, + /* Plus */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceAdd | TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, + /* Minus */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceAdd | TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, + /* Equals */ TokenFlags.AssignmentOperator, + /* PlusEquals */ TokenFlags.AssignmentOperator, + /* MinusEquals */ TokenFlags.AssignmentOperator, + /* MultiplyEquals */ TokenFlags.AssignmentOperator, + /* DivideEquals */ TokenFlags.AssignmentOperator, + /* RemainderEquals */ TokenFlags.AssignmentOperator, + /* Redirection */ TokenFlags.DisallowedInRestrictedMode, + /* RedirectInStd */ TokenFlags.ParseModeInvariant | TokenFlags.DisallowedInRestrictedMode, + /* Format */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceFormat | TokenFlags.DisallowedInRestrictedMode, + /* Not */ TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, + /* Bnot */ TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, + /* And */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceLogical | TokenFlags.CanConstantFold, + /* Or */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceLogical | TokenFlags.CanConstantFold, + /* Xor */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceLogical | TokenFlags.CanConstantFold, + /* Band */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceBitwise | TokenFlags.CanConstantFold, + /* Bor */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceBitwise | TokenFlags.CanConstantFold, + /* Bxor */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceBitwise | TokenFlags.CanConstantFold, + /* Join */ TokenFlags.BinaryOperator | TokenFlags.UnaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, + /* Ieq */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Ine */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Ige */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Igt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Ilt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Ile */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Ilike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Inotlike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Imatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, + /* Inotmatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, + /* Ireplace */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, + /* Icontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Inotcontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Iin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Inotin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Isplit */ TokenFlags.BinaryOperator | TokenFlags.UnaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, + /* Ceq */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cne */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cge */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cgt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Clt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cle */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Clike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cnotlike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cmatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, + /* Cnotmatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, + /* Creplace */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, + /* Ccontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cnotcontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cnotin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Csplit */ TokenFlags.BinaryOperator | TokenFlags.UnaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, + /* Is */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* IsNot */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* As */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, + /* PostFixPlusPlus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, + /* PostFixMinusMinus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, + /* Shl */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CanConstantFold, + /* Shr */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CanConstantFold, + /* Colon */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, + /* QuestionMark */ TokenFlags.TernaryOperator | TokenFlags.DisallowedInRestrictedMode, + /* QuestionQuestionEquals */ TokenFlags.AssignmentOperator, + /* QuestionQuestion */ TokenFlags.BinaryOperator, + /* Reserved slot 5 */ TokenFlags.None, + /* Reserved slot 6 */ TokenFlags.None, + /* Reserved slot 7 */ TokenFlags.None, + /* Reserved slot 8 */ TokenFlags.None, + /* Reserved slot 9 */ TokenFlags.None, + /* Reserved slot 10 */ TokenFlags.None, + /* Reserved slot 11 */ TokenFlags.None, + /* Reserved slot 12 */ TokenFlags.None, + /* Reserved slot 13 */ TokenFlags.None, + /* Reserved slot 14 */ TokenFlags.None, + /* Reserved slot 15 */ TokenFlags.None, + /* Reserved slot 16 */ TokenFlags.None, + /* Reserved slot 17 */ TokenFlags.None, + /* Reserved slot 18 */ TokenFlags.None, + /* Reserved slot 19 */ TokenFlags.None, + /* Reserved slot 20 */ TokenFlags.None, #endregion Flags for operators diff --git a/src/System.Management.Automation/engine/parser/tokenizer.cs b/src/System.Management.Automation/engine/parser/tokenizer.cs index fbf0900d793..b676f78a6d6 100644 --- a/src/System.Management.Automation/engine/parser/tokenizer.cs +++ b/src/System.Management.Automation/engine/parser/tokenizer.cs @@ -4994,10 +4994,10 @@ internal Token NextToken() return this.NewToken(TokenKind.Colon); case '?' when InExpressionMode(): - c1 = PeekChar(); - if (ExperimentalFeature.IsEnabled("PSNullCoalescingOperators")) { + c1 = PeekChar(); + if (c1 == '?') { SkipChar(); diff --git a/test/powershell/Language/Operators/NullConditional.Tests.ps1 b/test/powershell/Language/Operators/NullConditional.Tests.ps1 index 72da4baf6f8..c808b259ebf 100644 --- a/test/powershell/Language/Operators/NullConditional.Tests.ps1 +++ b/test/powershell/Language/Operators/NullConditional.Tests.ps1 @@ -91,6 +91,12 @@ Describe 'NullConditionalOperations' -Tags 'CI' { $x | Should -Be 200 } + It 'Lhs is empty string' { + $x = '' + $x ??= 20 + $x | Should -BeExactly '' + } + It 'Error case' { $e = $null $null = [System.Management.Automation.Language.Parser]::ParseInput('1 ??= 100', [ref] $null, [ref] $e) @@ -103,6 +109,11 @@ Describe 'NullConditionalOperations' -Tags 'CI' { $num | Should -Be 10 } + + It 'Lhs is $?' { + { $???=$false} + $? | Should -BeTrue + } } Context 'Null coalesce operator ??' { @@ -111,6 +122,7 @@ Describe 'NullConditionalOperations' -Tags 'CI' { } It 'Variable does not exist' { + Remove-Variable variableDoesNotExist -ErrorAction SilentlyContinue -Force $variableDoesNotExist ?? 100 | Should -Be 100 } @@ -174,6 +186,10 @@ Describe 'NullConditionalOperations' -Tags 'CI' { It 'Both are null constants' { [System.DBNull]::Value ?? [NullString]::Value | Should -Be ([NullString]::Value) } + + It 'Lhs is $?' { + {$???$false} | Should -BeTrue + } } Context 'Combined usage of null conditional operators' { diff --git a/test/powershell/Language/Parser/Parsing.Tests.ps1 b/test/powershell/Language/Parser/Parsing.Tests.ps1 index bb7f78ed8b7..8856c727c9b 100644 --- a/test/powershell/Language/Parser/Parsing.Tests.ps1 +++ b/test/powershell/Language/Parser/Parsing.Tests.ps1 @@ -265,6 +265,15 @@ Describe 'assignment statement parsing' -Tags "CI" { ShouldBeParseError '@{} ??= 1' InvalidLeftHandSide 0 ShouldBeParseError '1..2 ??= 1' InvalidLeftHandSide 0 ShouldBeParseError '[int] ??= 1' InvalidLeftHandSide 0 + ShouldBeParseError '$cricket ?= $soccer' ExpectedValueExpression 0 +} + +Describe 'null coalescing statement parsing' -Tag "CI" { + ShouldBeParseError '$x??y' UnexpectedToken 0 + ShouldBeParseError '$x??=' ExpectedValueExpression 0 + ShouldBeParseError '$x ??Get-Thing' ExpectedValueExpression 0 + ShouldBeParseError '$??=$false' ExpectedValueExpression 0 + ShouldBeParseError '$hello ??? $what' ExpectedValueExpression,MissingColonInTernaryExpression 0 } Describe 'splatting parsing' -Tags "CI" { From a9af2ab312b8970df77418f54ebdc0a82f4d08fe Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Wed, 9 Oct 2019 16:02:10 -0700 Subject: [PATCH 13/23] Refactor according to feedback --- .../engine/parser/Compiler.cs | 50 +++------------- .../engine/parser/token.cs | 4 +- .../engine/parser/tokenizer.cs | 4 +- .../engine/runtime/Binding/Binders.cs | 58 ++++++++++++++++++- 4 files changed, 66 insertions(+), 50 deletions(-) diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index 0ce7228d999..9463ac04e4d 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -14,7 +14,6 @@ using System.Management.Automation.Internal; using System.Management.Automation.Interpreter; using System.Management.Automation.Runspaces; -using System.Net; using System.Reflection; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; @@ -663,8 +662,6 @@ internal class Compiler : ICustomAstVisitor2 new Dictionary(SpecialVariables.AutomaticVariableTypes.Length + SpecialVariables.PreferenceVariableTypes.Length, StringComparer.OrdinalIgnoreCase); - private static bool? IsNullCoalescingFeatureEnabled; - static Compiler() { _functionContext = Expression.Parameter(typeof(FunctionContext), "funcContext"); @@ -791,6 +788,8 @@ internal Expression ReduceAssignment(ISupportsAssignment left, TokenKind tokenKi { IAssignableValue av = left.GetAssignableValue(); ExpressionType et = ExpressionType.Extension; + PSBinaryOperationBinder.ExtendedBinaryOperation extendedOperation = PSBinaryOperationBinder.ExtendedBinaryOperation.None; + switch (tokenKind) { case TokenKind.Equals: return av.SetValue(this, right); @@ -799,42 +798,16 @@ internal Expression ReduceAssignment(ISupportsAssignment left, TokenKind tokenKi case TokenKind.MultiplyEquals: et = ExpressionType.Multiply; break; case TokenKind.DivideEquals: et = ExpressionType.Divide; break; case TokenKind.RemainderEquals: et = ExpressionType.Modulo; break; - case TokenKind.QuestionQuestionEquals: return GetCoalesceExpression(right, av, tokenKind); + case TokenKind.QuestionQuestionEquals: extendedOperation = PSBinaryOperationBinder.ExtendedBinaryOperation.Coalesce; break; } var exprs = new List(); var temps = new List(); var getExpr = av.GetValue(this, exprs, temps); - exprs.Add(av.SetValue(this, DynamicExpression.Dynamic(PSBinaryOperationBinder.Get(et), typeof(object), getExpr, right))); + exprs.Add(av.SetValue(this, DynamicExpression.Dynamic(PSBinaryOperationBinder.Get(et, ignoreCase: true, scalarCompare: false, extendedOperation), typeof(object), getExpr, right))); return Expression.Block(temps, exprs); } - private Expression GetCoalesceExpression(Expression rhs, IAssignableValue leftAssignableValue, TokenKind tokenKind) - { - IsNullCoalescingFeatureEnabled ??= ExperimentalFeature.IsEnabled("PSNullCoalescingOperators"); - - if (IsNullCoalescingFeatureEnabled.Value == false) - { - return null; - } - - var exprs = new List(); - var temps = new List(); - Expression leftExpr = leftAssignableValue.GetValue(this, exprs, temps); - - if (tokenKind == TokenKind.QuestionQuestionEquals) - { - exprs.Add( - Expression.Condition( - Expression.Call(CachedReflectionInfo.LanguagePrimitives_IsNullLike, leftExpr), - leftAssignableValue.SetValue(this, rhs), - leftExpr.Convert(typeof(object)))); - return Expression.Block(temps, exprs); - } - - return null; - } - internal Expression GetLocal(int tupleIndex) { Expression result = LocalVariablesParameter; @@ -5263,18 +5236,9 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) CachedReflectionInfo.ParserOps_SplitOperator, _executionContextParameter, Expression.Constant(binaryExpressionAst.ErrorPosition), lhs.Cast(typeof(object)), rhs.Cast(typeof(object)), ExpressionCache.Constant(false)); - case TokenKind.QuestionQuestion when ExperimentalFeature.IsEnabled("PSNullCoalescingOperators") : - if (lhs is ConstantExpression lhsConstExpr && lhsConstExpr.Value != null) - { - return lhs; - } - else - { - return Expression.Condition( - Expression.Call(CachedReflectionInfo.LanguagePrimitives_IsNullLike, lhs), - rhs.Convert(typeof(object)), - lhs.Convert(typeof(object))); - } + case TokenKind.QuestionQuestion: + binder = PSBinaryOperationBinder.Get(ExpressionType.Extension, ignoreCase: true, scalarCompare: false, PSBinaryOperationBinder.ExtendedBinaryOperation.Coalesce); + return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs); } throw new InvalidOperationException("Unknown token in binary operator."); diff --git a/src/System.Management.Automation/engine/parser/token.cs b/src/System.Management.Automation/engine/parser/token.cs index 08c3edd7492..bf028fb8107 100644 --- a/src/System.Management.Automation/engine/parser/token.cs +++ b/src/System.Management.Automation/engine/parser/token.cs @@ -608,7 +608,7 @@ public enum TokenFlags /// /// The precedence of comparison operators including: '-eq', '-ne', '-ge', '-gt', '-lt', '-le', '-like', '-notlike', /// '-match', '-notmatch', '-replace', '-contains', '-notcontains', '-in', '-notin', '-split', '-join', '-is', '-isnot', '-as', - /// and all of the case sensitive variants of these operators, if they exists. + /// '??', and all of the case sensitive variants of these operators, if they exists. /// BinaryPrecedenceComparison = 3, @@ -861,7 +861,7 @@ public static class TokenTraits /* Colon */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, /* QuestionMark */ TokenFlags.TernaryOperator | TokenFlags.DisallowedInRestrictedMode, /* QuestionQuestionEquals */ TokenFlags.AssignmentOperator, - /* QuestionQuestion */ TokenFlags.BinaryOperator, + /* QuestionQuestion */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, /* Reserved slot 5 */ TokenFlags.None, /* Reserved slot 6 */ TokenFlags.None, /* Reserved slot 7 */ TokenFlags.None, diff --git a/src/System.Management.Automation/engine/parser/tokenizer.cs b/src/System.Management.Automation/engine/parser/tokenizer.cs index b676f78a6d6..fe3fc0951bb 100644 --- a/src/System.Management.Automation/engine/parser/tokenizer.cs +++ b/src/System.Management.Automation/engine/parser/tokenizer.cs @@ -5006,10 +5006,10 @@ internal Token NextToken() if (c1 == '=') { SkipChar(); - return CheckOperatorInCommandMode(c, c1, TokenKind.QuestionQuestionEquals); + return this.NewToken(TokenKind.QuestionQuestionEquals); } - return CheckOperatorInCommandMode(c, c1, TokenKind.QuestionQuestion); + return this.NewToken(TokenKind.QuestionQuestion); } } diff --git a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs index 0671c781d7e..5dfac3fb428 100644 --- a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs +++ b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs @@ -2114,7 +2114,16 @@ internal class PSBinaryOperationBinder : BinaryOperationBinder private static readonly Dictionary, PSBinaryOperationBinder> s_binderCache = new Dictionary, PSBinaryOperationBinder>(); - internal static PSBinaryOperationBinder Get(ExpressionType operation, bool ignoreCase = true, bool scalarCompare = false) + private static bool? s_IsNullCoalescingFeatureEnabled; + + internal enum ExtendedBinaryOperation + { + None, + Coalesce, + CoalesceAssignment + } + + internal static PSBinaryOperationBinder Get(ExpressionType operation, bool ignoreCase = true, bool scalarCompare = false, ExtendedBinaryOperation extendedBinaryOperation = ExtendedBinaryOperation.None) { PSBinaryOperationBinder result; lock (s_binderCache) @@ -2122,7 +2131,7 @@ internal static PSBinaryOperationBinder Get(ExpressionType operation, bool ignor var key = Tuple.Create(operation, ignoreCase, scalarCompare); if (!s_binderCache.TryGetValue(key, out result)) { - result = new PSBinaryOperationBinder(operation, ignoreCase, scalarCompare); + result = new PSBinaryOperationBinder(operation, ignoreCase, scalarCompare, extendedBinaryOperation); s_binderCache.Add(key, result); } } @@ -2132,13 +2141,15 @@ internal static PSBinaryOperationBinder Get(ExpressionType operation, bool ignor private readonly bool _ignoreCase; private readonly bool _scalarCompare; + private readonly ExtendedBinaryOperation _extendedBinaryOperation; internal int _version; - private PSBinaryOperationBinder(ExpressionType operation, bool ignoreCase, bool scalarCompare) + private PSBinaryOperationBinder(ExpressionType operation, bool ignoreCase, bool scalarCompare, ExtendedBinaryOperation extendedBinaryOperation) : base(operation) { _ignoreCase = ignoreCase; _scalarCompare = scalarCompare; + _extendedBinaryOperation = extendedBinaryOperation; this._version = 0; } @@ -2220,6 +2231,8 @@ public override DynamicMetaObject FallbackBinaryOperation(DynamicMetaObject targ return LeftShift(target, arg, errorSuggestion).WriteToDebugLog(this); case ExpressionType.RightShift: return RightShift(target, arg, errorSuggestion).WriteToDebugLog(this); + case ExpressionType.Extension when _extendedBinaryOperation == ExtendedBinaryOperation.Coalesce || _extendedBinaryOperation == ExtendedBinaryOperation.CoalesceAssignment: + return Coalesce(target, arg).WriteToDebugLog(this); } return (errorSuggestion ?? @@ -2267,6 +2280,8 @@ private string GetOperatorText() case ExpressionType.LessThanOrEqual: return _ignoreCase ? TokenKind.Ile.Text() : TokenKind.Cle.Text(); case ExpressionType.LeftShift: return TokenKind.Shl.Text(); case ExpressionType.RightShift: return TokenKind.Shr.Text(); + case ExpressionType.Extension when _extendedBinaryOperation == ExtendedBinaryOperation.Coalesce: return TokenKind.QuestionQuestion.Text(); + case ExpressionType.Extension when _extendedBinaryOperation == ExtendedBinaryOperation.CoalesceAssignment: return TokenKind.QuestionQuestionEquals.Text(); } Diagnostics.Assert(false, "Unexpected operator"); @@ -3405,6 +3420,43 @@ private DynamicMetaObject BinaryComparisonCommon(DynamicMetaObject targetAsEnume return null; } + private DynamicMetaObject Coalesce(DynamicMetaObject target, DynamicMetaObject arg) + { + s_IsNullCoalescingFeatureEnabled ??= ExperimentalFeature.IsEnabled("PSNullCoalescingOperators"); + + if (s_IsNullCoalescingFeatureEnabled.Value == false) + { + return null; + } + + Type targetExpType = target.Expression.Type; + Expression lhs = target.Expression.Cast(typeof(object)); + Expression rhs = arg.Expression.Cast(typeof(object)); + + Expression retExp; + if (targetExpType.IsValueType) + { + retExp = lhs; + } + else if (targetExpType == typeof(DBNull) || targetExpType == typeof(NullString) || targetExpType == typeof(AutomationNull)) + { + retExp = rhs; + } + else if (target.Expression is ConstantExpression targetConstExp) + { + retExp = targetConstExp.Value != null ? lhs : rhs; + } + else + { + retExp = Expression.Condition( + Expression.Call(CachedReflectionInfo.LanguagePrimitives_IsNullLike, lhs), + rhs, + lhs); + } + + return new DynamicMetaObject(retExp, target.CombineRestrictions(arg)); + } + #endregion Comparison operations } From 0e93e14627a862d2bf6fb82fce025e19d9d83233 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Wed, 9 Oct 2019 16:02:59 -0700 Subject: [PATCH 14/23] Add coalesce assignment --- src/System.Management.Automation/engine/parser/Compiler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index 9463ac04e4d..a2d3333e80b 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -798,7 +798,7 @@ internal Expression ReduceAssignment(ISupportsAssignment left, TokenKind tokenKi case TokenKind.MultiplyEquals: et = ExpressionType.Multiply; break; case TokenKind.DivideEquals: et = ExpressionType.Divide; break; case TokenKind.RemainderEquals: et = ExpressionType.Modulo; break; - case TokenKind.QuestionQuestionEquals: extendedOperation = PSBinaryOperationBinder.ExtendedBinaryOperation.Coalesce; break; + case TokenKind.QuestionQuestionEquals: extendedOperation = PSBinaryOperationBinder.ExtendedBinaryOperation.CoalesceAssignment; break; } var exprs = new List(); From 771a86d29afa9523f6d3fd0191b9f6917c504d81 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 10 Oct 2019 14:57:25 -0700 Subject: [PATCH 15/23] Add precedence flag for null coalesce operator and add tests --- .../engine/parser/Parser.cs | 5 +++ .../engine/parser/token.cs | 23 ++++++----- .../Operators/NullConditional.Tests.ps1 | 39 +++++++++++++++++++ 3 files changed, 58 insertions(+), 9 deletions(-) diff --git a/src/System.Management.Automation/engine/parser/Parser.cs b/src/System.Management.Automation/engine/parser/Parser.cs index 28bed16b15e..5e9bcddce4d 100644 --- a/src/System.Management.Automation/engine/parser/Parser.cs +++ b/src/System.Management.Automation/engine/parser/Parser.cs @@ -5701,6 +5701,7 @@ private PipelineBaseAst PipelineRule() // G // G assignment-expression: // G expression assignment-operator statement + // G expression nullcoalesce-assignment-operator statement // G // G pipeline-tail: // G new-lines:opt '|' new-lines:opt command pipeline-tail:opt @@ -6558,6 +6559,10 @@ private ExpressionAst BinaryExpressionRule(bool endNumberOnTernaryOpChars = fals // G additive-expression // G comparison-expression comparison-operator new-lines:opt additive-expression // G + // G nullcoalesce-expression: + // G comparison-expression + // G nullcoalesce-expression '??' new-lines:opt additive-expression + // G // G additive-expression: // G multiplicative-expression // G additive-expression '+' new-lines:opt multiplicative-expression diff --git a/src/System.Management.Automation/engine/parser/token.cs b/src/System.Management.Automation/engine/parser/token.cs index bf028fb8107..cae4255256d 100644 --- a/src/System.Management.Automation/engine/parser/token.cs +++ b/src/System.Management.Automation/engine/parser/token.cs @@ -603,41 +603,46 @@ public enum TokenFlags /// /// The precedence of the bitwise operators '-band', '-bor', and '-bxor' /// - BinaryPrecedenceBitwise = 2, + BinaryPrecedenceBitwise = 3, /// /// The precedence of comparison operators including: '-eq', '-ne', '-ge', '-gt', '-lt', '-le', '-like', '-notlike', /// '-match', '-notmatch', '-replace', '-contains', '-notcontains', '-in', '-notin', '-split', '-join', '-is', '-isnot', '-as', - /// '??', and all of the case sensitive variants of these operators, if they exists. + /// and all of the case sensitive variants of these operators, if they exists. /// - BinaryPrecedenceComparison = 3, + BinaryPrecedenceComparison = 5, + + /// + /// The precedence of null coalesce operator '??'. + /// + BinaryPrecedenceNullCoalesce = 7, /// /// The precedence of the binary operators '+' and '-'. /// - BinaryPrecedenceAdd = 4, + BinaryPrecedenceAdd = 9, /// /// The precedence of the operators '*', '/', and '%'. /// - BinaryPrecedenceMultiply = 5, + BinaryPrecedenceMultiply = 10, /// /// The precedence of the '-f' operator. /// - BinaryPrecedenceFormat = 6, + BinaryPrecedenceFormat = 12, /// /// The precedence of the '..' operator. /// - BinaryPrecedenceRange = 7, + BinaryPrecedenceRange = 13, #endregion Precedence Values /// /// A bitmask to get the precedence of binary operators. /// - BinaryPrecedenceMask = 0x00000007, + BinaryPrecedenceMask = 0x0000000f, /// /// The token is a keyword. @@ -861,7 +866,7 @@ public static class TokenTraits /* Colon */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, /* QuestionMark */ TokenFlags.TernaryOperator | TokenFlags.DisallowedInRestrictedMode, /* QuestionQuestionEquals */ TokenFlags.AssignmentOperator, - /* QuestionQuestion */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* QuestionQuestion */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceNullCoalesce, /* Reserved slot 5 */ TokenFlags.None, /* Reserved slot 6 */ TokenFlags.None, /* Reserved slot 7 */ TokenFlags.None, diff --git a/test/powershell/Language/Operators/NullConditional.Tests.ps1 b/test/powershell/Language/Operators/NullConditional.Tests.ps1 index c808b259ebf..b19f132bf4b 100644 --- a/test/powershell/Language/Operators/NullConditional.Tests.ps1 +++ b/test/powershell/Language/Operators/NullConditional.Tests.ps1 @@ -192,6 +192,45 @@ Describe 'NullConditionalOperations' -Tags 'CI' { } } + Context 'Null Coalesce ?? operator precedence' { + It '?? precedence over -and' { + $true -and $null ?? $true | Should -BeTrue + } + + It '?? precedence over -band' { + 1 -band $null ?? 1 | Should -Be 1 + } + + It '?? precedence over -eq' { + 'x' -eq $null ?? 'x' | Should -BeTrue + $null -eq $null ?? 'x' | Should -BeFalse + } + + It '?? precedence over -as' { + 'abc' -as [datetime] ?? 1 | Should -BeNullOrEmpty + } + + It '?? precedence over -replace' { + 'x' -replace 'x',$null ?? 1 | Should -Be ([string]::empty) + } + + It '+ precedence over ??' { + 2 + $null ?? 3 | Should -Be 2 + } + + It '* precedence over ??' { + 2 * $null ?? 3 | Should -Be 0 + } + + It '-f precedence over ??' { + "{0}" -f $null ?? 'b' | Should -Be ([string]::empty) + } + + It '.. precedence ove ??' { + 1..$null ?? 2 | Should -BeIn 1,0 + } + } + Context 'Combined usage of null conditional operators' { BeforeAll { From 73b898273f5e93f99a072b920d0f918017b396f1 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 10 Oct 2019 15:18:58 -0700 Subject: [PATCH 16/23] Revert formatting changes in token.cs --- .../engine/parser/token.cs | 184 +++++++++--------- 1 file changed, 92 insertions(+), 92 deletions(-) diff --git a/src/System.Management.Automation/engine/parser/token.cs b/src/System.Management.Automation/engine/parser/token.cs index c8209aa7e31..94f7d068154 100644 --- a/src/System.Management.Automation/engine/parser/token.cs +++ b/src/System.Management.Automation/engine/parser/token.cs @@ -790,99 +790,99 @@ public static class TokenTraits #region Flags for operators - /* AndAnd */ TokenFlags.BinaryOperator | TokenFlags.ParseModeInvariant, - /* OrOr */ TokenFlags.BinaryOperator | TokenFlags.ParseModeInvariant, - /* Ampersand */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, - /* Pipe */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, - /* Comma */ TokenFlags.UnaryOperator | TokenFlags.ParseModeInvariant, - /* MinusMinus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, - /* PlusPlus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, - /* DotDot */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceRange | TokenFlags.DisallowedInRestrictedMode, - /* ColonColon */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, - /* Dot */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, - /* Exclaim */ TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, - /* Multiply */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceMultiply | TokenFlags.CanConstantFold, - /* Divide */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceMultiply | TokenFlags.CanConstantFold, - /* Rem */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceMultiply | TokenFlags.CanConstantFold, - /* Plus */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceAdd | TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, - /* Minus */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceAdd | TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, - /* Equals */ TokenFlags.AssignmentOperator, - /* PlusEquals */ TokenFlags.AssignmentOperator, - /* MinusEquals */ TokenFlags.AssignmentOperator, - /* MultiplyEquals */ TokenFlags.AssignmentOperator, - /* DivideEquals */ TokenFlags.AssignmentOperator, - /* RemainderEquals */ TokenFlags.AssignmentOperator, - /* Redirection */ TokenFlags.DisallowedInRestrictedMode, - /* RedirectInStd */ TokenFlags.ParseModeInvariant | TokenFlags.DisallowedInRestrictedMode, - /* Format */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceFormat | TokenFlags.DisallowedInRestrictedMode, - /* Not */ TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, - /* Bnot */ TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, - /* And */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceLogical | TokenFlags.CanConstantFold, - /* Or */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceLogical | TokenFlags.CanConstantFold, - /* Xor */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceLogical | TokenFlags.CanConstantFold, - /* Band */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceBitwise | TokenFlags.CanConstantFold, - /* Bor */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceBitwise | TokenFlags.CanConstantFold, - /* Bxor */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceBitwise | TokenFlags.CanConstantFold, - /* Join */ TokenFlags.BinaryOperator | TokenFlags.UnaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, - /* Ieq */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Ine */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Ige */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Igt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Ilt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Ile */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Ilike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Inotlike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Imatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, - /* Inotmatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, - /* Ireplace */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, - /* Icontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Inotcontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Iin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Inotin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* Isplit */ TokenFlags.BinaryOperator | TokenFlags.UnaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, - /* Ceq */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cne */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cge */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cgt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Clt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cle */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Clike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cnotlike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cmatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, - /* Cnotmatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, - /* Creplace */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, - /* Ccontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cnotcontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Cnotin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, - /* Csplit */ TokenFlags.BinaryOperator | TokenFlags.UnaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, - /* Is */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* IsNot */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, - /* As */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, - /* PostFixPlusPlus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, - /* PostFixMinusMinus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, - /* Shl */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CanConstantFold, - /* Shr */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CanConstantFold, - /* Colon */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, - /* QuestionMark */ TokenFlags.TernaryOperator | TokenFlags.DisallowedInRestrictedMode, + /* AndAnd */ TokenFlags.BinaryOperator | TokenFlags.ParseModeInvariant, + /* OrOr */ TokenFlags.BinaryOperator | TokenFlags.ParseModeInvariant, + /* Ampersand */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, + /* Pipe */ TokenFlags.SpecialOperator | TokenFlags.ParseModeInvariant, + /* Comma */ TokenFlags.UnaryOperator | TokenFlags.ParseModeInvariant, + /* MinusMinus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, + /* PlusPlus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, + /* DotDot */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceRange | TokenFlags.DisallowedInRestrictedMode, + /* ColonColon */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, + /* Dot */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, + /* Exclaim */ TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, + /* Multiply */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceMultiply | TokenFlags.CanConstantFold, + /* Divide */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceMultiply | TokenFlags.CanConstantFold, + /* Rem */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceMultiply | TokenFlags.CanConstantFold, + /* Plus */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceAdd | TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, + /* Minus */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceAdd | TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, + /* Equals */ TokenFlags.AssignmentOperator, + /* PlusEquals */ TokenFlags.AssignmentOperator, + /* MinusEquals */ TokenFlags.AssignmentOperator, + /* MultiplyEquals */ TokenFlags.AssignmentOperator, + /* DivideEquals */ TokenFlags.AssignmentOperator, + /* RemainderEquals */ TokenFlags.AssignmentOperator, + /* Redirection */ TokenFlags.DisallowedInRestrictedMode, + /* RedirectInStd */ TokenFlags.ParseModeInvariant | TokenFlags.DisallowedInRestrictedMode, + /* Format */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceFormat | TokenFlags.DisallowedInRestrictedMode, + /* Not */ TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, + /* Bnot */ TokenFlags.UnaryOperator | TokenFlags.CanConstantFold, + /* And */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceLogical | TokenFlags.CanConstantFold, + /* Or */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceLogical | TokenFlags.CanConstantFold, + /* Xor */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceLogical | TokenFlags.CanConstantFold, + /* Band */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceBitwise | TokenFlags.CanConstantFold, + /* Bor */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceBitwise | TokenFlags.CanConstantFold, + /* Bxor */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceBitwise | TokenFlags.CanConstantFold, + /* Join */ TokenFlags.BinaryOperator | TokenFlags.UnaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, + /* Ieq */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Ine */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Ige */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Igt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Ilt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Ile */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Ilike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Inotlike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Imatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, + /* Inotmatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, + /* Ireplace */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, + /* Icontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Inotcontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Iin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Inotin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* Isplit */ TokenFlags.BinaryOperator | TokenFlags.UnaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, + /* Ceq */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cne */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cge */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cgt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Clt */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cle */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Clike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cnotlike */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cmatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, + /* Cnotmatch */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, + /* Creplace */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, + /* Ccontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cnotcontains */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Cnotin */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator, + /* Csplit */ TokenFlags.BinaryOperator | TokenFlags.UnaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CaseSensitiveOperator | TokenFlags.DisallowedInRestrictedMode, + /* Is */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* IsNot */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison, + /* As */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.DisallowedInRestrictedMode, + /* PostFixPlusPlus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, + /* PostFixMinusMinus */ TokenFlags.UnaryOperator | TokenFlags.PrefixOrPostfixOperator | TokenFlags.DisallowedInRestrictedMode, + /* Shl */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CanConstantFold, + /* Shr */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceComparison | TokenFlags.CanConstantFold, + /* Colon */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, + /* QuestionMark */ TokenFlags.TernaryOperator | TokenFlags.DisallowedInRestrictedMode, /* QuestionQuestionEquals */ TokenFlags.AssignmentOperator, - /* QuestionQuestion */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceNullCoalesce, - /* Reserved slot 5 */ TokenFlags.None, - /* Reserved slot 6 */ TokenFlags.None, - /* Reserved slot 7 */ TokenFlags.None, - /* Reserved slot 8 */ TokenFlags.None, - /* Reserved slot 9 */ TokenFlags.None, - /* Reserved slot 10 */ TokenFlags.None, - /* Reserved slot 11 */ TokenFlags.None, - /* Reserved slot 12 */ TokenFlags.None, - /* Reserved slot 13 */ TokenFlags.None, - /* Reserved slot 14 */ TokenFlags.None, - /* Reserved slot 15 */ TokenFlags.None, - /* Reserved slot 16 */ TokenFlags.None, - /* Reserved slot 17 */ TokenFlags.None, - /* Reserved slot 18 */ TokenFlags.None, - /* Reserved slot 19 */ TokenFlags.None, - /* Reserved slot 20 */ TokenFlags.None, + /* QuestionQuestion */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceNullCoalesce, + /* Reserved slot 5 */ TokenFlags.None, + /* Reserved slot 6 */ TokenFlags.None, + /* Reserved slot 7 */ TokenFlags.None, + /* Reserved slot 8 */ TokenFlags.None, + /* Reserved slot 9 */ TokenFlags.None, + /* Reserved slot 10 */ TokenFlags.None, + /* Reserved slot 11 */ TokenFlags.None, + /* Reserved slot 12 */ TokenFlags.None, + /* Reserved slot 13 */ TokenFlags.None, + /* Reserved slot 14 */ TokenFlags.None, + /* Reserved slot 15 */ TokenFlags.None, + /* Reserved slot 16 */ TokenFlags.None, + /* Reserved slot 17 */ TokenFlags.None, + /* Reserved slot 18 */ TokenFlags.None, + /* Reserved slot 19 */ TokenFlags.None, + /* Reserved slot 20 */ TokenFlags.None, #endregion Flags for operators From 36f4b8578dd85684ed0232ddf518aeb1157cef54 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 10 Oct 2019 16:10:33 -0700 Subject: [PATCH 17/23] Fix parsing test --- test/powershell/Language/Parser/Parsing.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/powershell/Language/Parser/Parsing.Tests.ps1 b/test/powershell/Language/Parser/Parsing.Tests.ps1 index 8856c727c9b..544744b48de 100644 --- a/test/powershell/Language/Parser/Parsing.Tests.ps1 +++ b/test/powershell/Language/Parser/Parsing.Tests.ps1 @@ -265,7 +265,7 @@ Describe 'assignment statement parsing' -Tags "CI" { ShouldBeParseError '@{} ??= 1' InvalidLeftHandSide 0 ShouldBeParseError '1..2 ??= 1' InvalidLeftHandSide 0 ShouldBeParseError '[int] ??= 1' InvalidLeftHandSide 0 - ShouldBeParseError '$cricket ?= $soccer' ExpectedValueExpression 0 + ShouldBeParseError '$cricket ?= $soccer' ExpectedValueExpression,InvalidLeftHandSide 10,0 } Describe 'null coalescing statement parsing' -Tag "CI" { From d530fcdaedf1b97dc9c4d14fcfe23e2b2a49b1ec Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 10 Oct 2019 16:44:16 -0700 Subject: [PATCH 18/23] More test fixes --- test/powershell/Language/Parser/Parsing.Tests.ps1 | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/powershell/Language/Parser/Parsing.Tests.ps1 b/test/powershell/Language/Parser/Parsing.Tests.ps1 index 544744b48de..83dda87b0db 100644 --- a/test/powershell/Language/Parser/Parsing.Tests.ps1 +++ b/test/powershell/Language/Parser/Parsing.Tests.ps1 @@ -269,11 +269,10 @@ Describe 'assignment statement parsing' -Tags "CI" { } Describe 'null coalescing statement parsing' -Tag "CI" { - ShouldBeParseError '$x??y' UnexpectedToken 0 - ShouldBeParseError '$x??=' ExpectedValueExpression 0 - ShouldBeParseError '$x ??Get-Thing' ExpectedValueExpression 0 - ShouldBeParseError '$??=$false' ExpectedValueExpression 0 - ShouldBeParseError '$hello ??? $what' ExpectedValueExpression,MissingColonInTernaryExpression 0 + ShouldBeParseError '$x??=' ExpectedValueExpression 5 + ShouldBeParseError '$x ??Get-Thing' ExpectedValueExpression,UnexpectedToken 5,5 + ShouldBeParseError '$??=$false' ExpectedValueExpression,InvalidLeftHandSide 3,0 + ShouldBeParseError '$hello ??? $what' ExpectedValueExpression,MissingColonInTernaryExpression 9,17 } Describe 'splatting parsing' -Tags "CI" { From 3f0f3d194274aafaa35f5c561b0984f03f2b75d9 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Fri, 11 Oct 2019 10:26:35 -0700 Subject: [PATCH 19/23] Check for experimental feature in parsing.tests.ps1 --- .../Language/Parser/Parsing.Tests.ps1 | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/powershell/Language/Parser/Parsing.Tests.ps1 b/test/powershell/Language/Parser/Parsing.Tests.ps1 index 83dda87b0db..0854e232c29 100644 --- a/test/powershell/Language/Parser/Parsing.Tests.ps1 +++ b/test/powershell/Language/Parser/Parsing.Tests.ps1 @@ -260,6 +260,24 @@ Describe 'function statement parsing' -Tags "CI" { Describe 'assignment statement parsing' -Tags "CI" { ShouldBeParseError '$a,$b += 1,2' InvalidLeftHandSide 0 +} + +Describe 'null coalescing assignment statement parsing' -Tag 'CI' { + BeforeAll { + $skipTest = -not $EnabledExperimentalFeatures.Contains('PSNullCoalescingOperators') + if ($skipTest) { + Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'PSNullCoalescingOperators' to be enabled." -Verbose + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $PSDefaultParameterValues["it:skip"] = $true + } + } + + AfterAll { + if ($skipTest) { + $global:PSDefaultParameterValues = $originalDefaultParameterValues + } + } + ShouldBeParseError '1 ??= 1' InvalidLeftHandSide 0 ShouldBeParseError '@() ??= 1' InvalidLeftHandSide 0 ShouldBeParseError '@{} ??= 1' InvalidLeftHandSide 0 @@ -269,6 +287,21 @@ Describe 'assignment statement parsing' -Tags "CI" { } Describe 'null coalescing statement parsing' -Tag "CI" { + BeforeAll { + $skipTest = -not $EnabledExperimentalFeatures.Contains('PSNullCoalescingOperators') + if ($skipTest) { + Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'PSNullCoalescingOperators' to be enabled." -Verbose + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $PSDefaultParameterValues["it:skip"] = $true + } + } + + AfterAll { + if ($skipTest) { + $global:PSDefaultParameterValues = $originalDefaultParameterValues + } + } + ShouldBeParseError '$x??=' ExpectedValueExpression 5 ShouldBeParseError '$x ??Get-Thing' ExpectedValueExpression,UnexpectedToken 5,5 ShouldBeParseError '$??=$false' ExpectedValueExpression,InvalidLeftHandSide 3,0 From 8f39bbb161794edb900f92306dee3afaeffa2a06 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Fri, 11 Oct 2019 15:34:01 -0700 Subject: [PATCH 20/23] Updated to move Coalesce code out of Binder --- .../engine/parser/Compiler.cs | 43 ++++++++++++-- .../engine/parser/Parser.cs | 7 +-- .../engine/parser/token.cs | 16 ++--- .../engine/runtime/Binding/Binders.cs | 58 +------------------ 4 files changed, 51 insertions(+), 73 deletions(-) diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index a2d3333e80b..6e6ac7c92ed 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -788,7 +788,6 @@ internal Expression ReduceAssignment(ISupportsAssignment left, TokenKind tokenKi { IAssignableValue av = left.GetAssignableValue(); ExpressionType et = ExpressionType.Extension; - PSBinaryOperationBinder.ExtendedBinaryOperation extendedOperation = PSBinaryOperationBinder.ExtendedBinaryOperation.None; switch (tokenKind) { @@ -798,16 +797,49 @@ internal Expression ReduceAssignment(ISupportsAssignment left, TokenKind tokenKi case TokenKind.MultiplyEquals: et = ExpressionType.Multiply; break; case TokenKind.DivideEquals: et = ExpressionType.Divide; break; case TokenKind.RemainderEquals: et = ExpressionType.Modulo; break; - case TokenKind.QuestionQuestionEquals: extendedOperation = PSBinaryOperationBinder.ExtendedBinaryOperation.CoalesceAssignment; break; + case TokenKind.QuestionQuestionEquals when ExperimentalFeature.IsEnabled("PSNullCoalescingOperators"): et = ExpressionType.Coalesce; break; } var exprs = new List(); var temps = new List(); var getExpr = av.GetValue(this, exprs, temps); - exprs.Add(av.SetValue(this, DynamicExpression.Dynamic(PSBinaryOperationBinder.Get(et, ignoreCase: true, scalarCompare: false, extendedOperation), typeof(object), getExpr, right))); + + if(et == ExpressionType.Coalesce) + { + exprs.Add(av.SetValue(this, Coalesce(getExpr, right))); + } + else + { + exprs.Add(av.SetValue(this, DynamicExpression.Dynamic(PSBinaryOperationBinder.Get(et), typeof(object), getExpr, right))); + } + return Expression.Block(temps, exprs); } + private Expression Coalesce(Expression left, Expression right) + { + Type leftType = left.Type; + Expression lhs = left.Cast(typeof(object)); + Expression rhs = right.Cast(typeof(object)); + + if (leftType.IsValueType) + { + return lhs; + } + else if(leftType == typeof(DBNull) || leftType == typeof(NullString) || leftType == typeof(AutomationNull)) + { + return rhs; + } + else + { + return Expression.Condition( + Expression.Call(CachedReflectionInfo.LanguagePrimitives_IsNullLike, lhs), + rhs, + lhs + ); + } + } + internal Expression GetLocal(int tupleIndex) { Expression result = LocalVariablesParameter; @@ -5236,9 +5268,8 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) CachedReflectionInfo.ParserOps_SplitOperator, _executionContextParameter, Expression.Constant(binaryExpressionAst.ErrorPosition), lhs.Cast(typeof(object)), rhs.Cast(typeof(object)), ExpressionCache.Constant(false)); - case TokenKind.QuestionQuestion: - binder = PSBinaryOperationBinder.Get(ExpressionType.Extension, ignoreCase: true, scalarCompare: false, PSBinaryOperationBinder.ExtendedBinaryOperation.Coalesce); - return DynamicExpression.Dynamic(binder, typeof(object), lhs, rhs); + case TokenKind.QuestionQuestion when ExperimentalFeature.IsEnabled("PSNullCoalescingOperators"): + return Coalesce(lhs, rhs); } throw new InvalidOperationException("Unknown token in binary operator."); diff --git a/src/System.Management.Automation/engine/parser/Parser.cs b/src/System.Management.Automation/engine/parser/Parser.cs index 65c5672579d..9c07cbca488 100644 --- a/src/System.Management.Automation/engine/parser/Parser.cs +++ b/src/System.Management.Automation/engine/parser/Parser.cs @@ -5701,7 +5701,6 @@ private PipelineBaseAst PipelineRule() // G // G assignment-expression: // G expression assignment-operator statement - // G expression nullcoalesce-assignment-operator statement // G // G pipeline-tail: // G new-lines:opt '|' new-lines:opt command pipeline-tail:opt @@ -6556,11 +6555,11 @@ private ExpressionAst BinaryExpressionRule(bool endNumberOnTernaryOpChars = fals // G bitwise-expression '-bxor' new-lines:opt comparison-expression // G // G comparison-expression: - // G additive-expression - // G comparison-expression comparison-operator new-lines:opt additive-expression + // G nullcoalesce-expression + // G comparison-expression comparison-operator new-lines:opt nullcoalesce-expression // G // G nullcoalesce-expression: - // G comparison-expression + // G additive-expression // G nullcoalesce-expression '??' new-lines:opt additive-expression // G // G additive-expression: diff --git a/src/System.Management.Automation/engine/parser/token.cs b/src/System.Management.Automation/engine/parser/token.cs index 94f7d068154..6d6ae8b6522 100644 --- a/src/System.Management.Automation/engine/parser/token.cs +++ b/src/System.Management.Automation/engine/parser/token.cs @@ -598,44 +598,44 @@ public enum TokenFlags /// /// The precedence of the logical operators '-and', '-or', and '-xor'. /// - BinaryPrecedenceLogical = 1, + BinaryPrecedenceLogical = 0x1, /// /// The precedence of the bitwise operators '-band', '-bor', and '-bxor' /// - BinaryPrecedenceBitwise = 3, + BinaryPrecedenceBitwise = 0x2, /// /// The precedence of comparison operators including: '-eq', '-ne', '-ge', '-gt', '-lt', '-le', '-like', '-notlike', /// '-match', '-notmatch', '-replace', '-contains', '-notcontains', '-in', '-notin', '-split', '-join', '-is', '-isnot', '-as', /// and all of the case sensitive variants of these operators, if they exists. /// - BinaryPrecedenceComparison = 5, + BinaryPrecedenceComparison = 0x5, /// /// The precedence of null coalesce operator '??'. /// - BinaryPrecedenceNullCoalesce = 7, + BinaryPrecedenceNullCoalesce = 0x7, /// /// The precedence of the binary operators '+' and '-'. /// - BinaryPrecedenceAdd = 9, + BinaryPrecedenceAdd = 0x9, /// /// The precedence of the operators '*', '/', and '%'. /// - BinaryPrecedenceMultiply = 10, + BinaryPrecedenceMultiply = 0xa, /// /// The precedence of the '-f' operator. /// - BinaryPrecedenceFormat = 12, + BinaryPrecedenceFormat = 0xc, /// /// The precedence of the '..' operator. /// - BinaryPrecedenceRange = 13, + BinaryPrecedenceRange = 0xd, #endregion Precedence Values diff --git a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs index 5dfac3fb428..0671c781d7e 100644 --- a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs +++ b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs @@ -2114,16 +2114,7 @@ internal class PSBinaryOperationBinder : BinaryOperationBinder private static readonly Dictionary, PSBinaryOperationBinder> s_binderCache = new Dictionary, PSBinaryOperationBinder>(); - private static bool? s_IsNullCoalescingFeatureEnabled; - - internal enum ExtendedBinaryOperation - { - None, - Coalesce, - CoalesceAssignment - } - - internal static PSBinaryOperationBinder Get(ExpressionType operation, bool ignoreCase = true, bool scalarCompare = false, ExtendedBinaryOperation extendedBinaryOperation = ExtendedBinaryOperation.None) + internal static PSBinaryOperationBinder Get(ExpressionType operation, bool ignoreCase = true, bool scalarCompare = false) { PSBinaryOperationBinder result; lock (s_binderCache) @@ -2131,7 +2122,7 @@ internal static PSBinaryOperationBinder Get(ExpressionType operation, bool ignor var key = Tuple.Create(operation, ignoreCase, scalarCompare); if (!s_binderCache.TryGetValue(key, out result)) { - result = new PSBinaryOperationBinder(operation, ignoreCase, scalarCompare, extendedBinaryOperation); + result = new PSBinaryOperationBinder(operation, ignoreCase, scalarCompare); s_binderCache.Add(key, result); } } @@ -2141,15 +2132,13 @@ internal static PSBinaryOperationBinder Get(ExpressionType operation, bool ignor private readonly bool _ignoreCase; private readonly bool _scalarCompare; - private readonly ExtendedBinaryOperation _extendedBinaryOperation; internal int _version; - private PSBinaryOperationBinder(ExpressionType operation, bool ignoreCase, bool scalarCompare, ExtendedBinaryOperation extendedBinaryOperation) + private PSBinaryOperationBinder(ExpressionType operation, bool ignoreCase, bool scalarCompare) : base(operation) { _ignoreCase = ignoreCase; _scalarCompare = scalarCompare; - _extendedBinaryOperation = extendedBinaryOperation; this._version = 0; } @@ -2231,8 +2220,6 @@ public override DynamicMetaObject FallbackBinaryOperation(DynamicMetaObject targ return LeftShift(target, arg, errorSuggestion).WriteToDebugLog(this); case ExpressionType.RightShift: return RightShift(target, arg, errorSuggestion).WriteToDebugLog(this); - case ExpressionType.Extension when _extendedBinaryOperation == ExtendedBinaryOperation.Coalesce || _extendedBinaryOperation == ExtendedBinaryOperation.CoalesceAssignment: - return Coalesce(target, arg).WriteToDebugLog(this); } return (errorSuggestion ?? @@ -2280,8 +2267,6 @@ private string GetOperatorText() case ExpressionType.LessThanOrEqual: return _ignoreCase ? TokenKind.Ile.Text() : TokenKind.Cle.Text(); case ExpressionType.LeftShift: return TokenKind.Shl.Text(); case ExpressionType.RightShift: return TokenKind.Shr.Text(); - case ExpressionType.Extension when _extendedBinaryOperation == ExtendedBinaryOperation.Coalesce: return TokenKind.QuestionQuestion.Text(); - case ExpressionType.Extension when _extendedBinaryOperation == ExtendedBinaryOperation.CoalesceAssignment: return TokenKind.QuestionQuestionEquals.Text(); } Diagnostics.Assert(false, "Unexpected operator"); @@ -3420,43 +3405,6 @@ private DynamicMetaObject BinaryComparisonCommon(DynamicMetaObject targetAsEnume return null; } - private DynamicMetaObject Coalesce(DynamicMetaObject target, DynamicMetaObject arg) - { - s_IsNullCoalescingFeatureEnabled ??= ExperimentalFeature.IsEnabled("PSNullCoalescingOperators"); - - if (s_IsNullCoalescingFeatureEnabled.Value == false) - { - return null; - } - - Type targetExpType = target.Expression.Type; - Expression lhs = target.Expression.Cast(typeof(object)); - Expression rhs = arg.Expression.Cast(typeof(object)); - - Expression retExp; - if (targetExpType.IsValueType) - { - retExp = lhs; - } - else if (targetExpType == typeof(DBNull) || targetExpType == typeof(NullString) || targetExpType == typeof(AutomationNull)) - { - retExp = rhs; - } - else if (target.Expression is ConstantExpression targetConstExp) - { - retExp = targetConstExp.Value != null ? lhs : rhs; - } - else - { - retExp = Expression.Condition( - Expression.Call(CachedReflectionInfo.LanguagePrimitives_IsNullLike, lhs), - rhs, - lhs); - } - - return new DynamicMetaObject(retExp, target.CombineRestrictions(arg)); - } - #endregion Comparison operations } From dea57932a9b793e6727e93ac811541a990860393 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Mon, 14 Oct 2019 10:52:49 -0700 Subject: [PATCH 21/23] Update TokenFlag name --- src/System.Management.Automation/engine/parser/token.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/System.Management.Automation/engine/parser/token.cs b/src/System.Management.Automation/engine/parser/token.cs index 6d6ae8b6522..b9dae289eff 100644 --- a/src/System.Management.Automation/engine/parser/token.cs +++ b/src/System.Management.Automation/engine/parser/token.cs @@ -615,7 +615,7 @@ public enum TokenFlags /// /// The precedence of null coalesce operator '??'. /// - BinaryPrecedenceNullCoalesce = 0x7, + BinaryPrecedenceCoalesce = 0x7, /// /// The precedence of the binary operators '+' and '-'. @@ -866,7 +866,7 @@ public static class TokenTraits /* Colon */ TokenFlags.SpecialOperator | TokenFlags.DisallowedInRestrictedMode, /* QuestionMark */ TokenFlags.TernaryOperator | TokenFlags.DisallowedInRestrictedMode, /* QuestionQuestionEquals */ TokenFlags.AssignmentOperator, - /* QuestionQuestion */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceNullCoalesce, + /* QuestionQuestion */ TokenFlags.BinaryOperator | TokenFlags.BinaryPrecedenceCoalesce, /* Reserved slot 5 */ TokenFlags.None, /* Reserved slot 6 */ TokenFlags.None, /* Reserved slot 7 */ TokenFlags.None, From 4cf6ba4fcab43568b9c123a96c21b47beead9fb6 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Mon, 14 Oct 2019 17:12:06 -0700 Subject: [PATCH 22/23] Address feedback --- .../engine/parser/Compiler.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index 6e6ac7c92ed..3deccbbdc3e 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -819,24 +819,24 @@ internal Expression ReduceAssignment(ISupportsAssignment left, TokenKind tokenKi private Expression Coalesce(Expression left, Expression right) { Type leftType = left.Type; - Expression lhs = left.Cast(typeof(object)); - Expression rhs = right.Cast(typeof(object)); if (leftType.IsValueType) { - return lhs; + return left; } else if(leftType == typeof(DBNull) || leftType == typeof(NullString) || leftType == typeof(AutomationNull)) { - return rhs; + return right; } else { + Expression lhs = left.Cast(typeof(object)); + Expression rhs = right.Cast(typeof(object)); + return Expression.Condition( Expression.Call(CachedReflectionInfo.LanguagePrimitives_IsNullLike, lhs), rhs, - lhs - ); + lhs); } } From 268a6099fbc9519307bd7cc35b93d5e3f96084d9 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Tue, 15 Oct 2019 11:52:39 -0700 Subject: [PATCH 23/23] Address Ilya's feedback --- .../engine/ExperimentalFeature/ExperimentalFeature.cs | 2 +- .../engine/parser/Compiler.cs | 6 +++--- .../engine/parser/tokenizer.cs | 2 +- .../Language/Operators/NullConditional.Tests.ps1 | 5 +++-- test/powershell/Language/Parser/Parsing.Tests.ps1 | 8 ++++---- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs index 127379b985c..a2ca36f3c7d 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -122,7 +122,7 @@ static ExperimentalFeature() name: "PSUpdatesNotification", description: "Print notification message when new releases are available"), new ExperimentalFeature( - name: "PSNullCoalescingOperators", + name: "PSCoalescingOperators", description: "Support the null coalescing operator and null coalescing assignment operator in PowerShell language") }; EngineExperimentalFeatures = new ReadOnlyCollection(engineFeatures); diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index 3deccbbdc3e..2998cdb4067 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -797,7 +797,7 @@ internal Expression ReduceAssignment(ISupportsAssignment left, TokenKind tokenKi case TokenKind.MultiplyEquals: et = ExpressionType.Multiply; break; case TokenKind.DivideEquals: et = ExpressionType.Divide; break; case TokenKind.RemainderEquals: et = ExpressionType.Modulo; break; - case TokenKind.QuestionQuestionEquals when ExperimentalFeature.IsEnabled("PSNullCoalescingOperators"): et = ExpressionType.Coalesce; break; + case TokenKind.QuestionQuestionEquals when ExperimentalFeature.IsEnabled("PSCoalescingOperators"): et = ExpressionType.Coalesce; break; } var exprs = new List(); @@ -816,7 +816,7 @@ internal Expression ReduceAssignment(ISupportsAssignment left, TokenKind tokenKi return Expression.Block(temps, exprs); } - private Expression Coalesce(Expression left, Expression right) + private static Expression Coalesce(Expression left, Expression right) { Type leftType = left.Type; @@ -5268,7 +5268,7 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) CachedReflectionInfo.ParserOps_SplitOperator, _executionContextParameter, Expression.Constant(binaryExpressionAst.ErrorPosition), lhs.Cast(typeof(object)), rhs.Cast(typeof(object)), ExpressionCache.Constant(false)); - case TokenKind.QuestionQuestion when ExperimentalFeature.IsEnabled("PSNullCoalescingOperators"): + case TokenKind.QuestionQuestion when ExperimentalFeature.IsEnabled("PSCoalescingOperators"): return Coalesce(lhs, rhs); } diff --git a/src/System.Management.Automation/engine/parser/tokenizer.cs b/src/System.Management.Automation/engine/parser/tokenizer.cs index fe3fc0951bb..cd613187a5d 100644 --- a/src/System.Management.Automation/engine/parser/tokenizer.cs +++ b/src/System.Management.Automation/engine/parser/tokenizer.cs @@ -4994,7 +4994,7 @@ internal Token NextToken() return this.NewToken(TokenKind.Colon); case '?' when InExpressionMode(): - if (ExperimentalFeature.IsEnabled("PSNullCoalescingOperators")) + if (ExperimentalFeature.IsEnabled("PSCoalescingOperators")) { c1 = PeekChar(); diff --git a/test/powershell/Language/Operators/NullConditional.Tests.ps1 b/test/powershell/Language/Operators/NullConditional.Tests.ps1 index b19f132bf4b..8f9b86816ec 100644 --- a/test/powershell/Language/Operators/NullConditional.Tests.ps1 +++ b/test/powershell/Language/Operators/NullConditional.Tests.ps1 @@ -4,10 +4,10 @@ Describe 'NullConditionalOperations' -Tags 'CI' { BeforeAll { - $skipTest = -not $EnabledExperimentalFeatures.Contains('PSNullCoalescingOperators') + $skipTest = -not $EnabledExperimentalFeatures.Contains('PSCoalescingOperators') if ($skipTest) { - Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'PSNullCoalescingOperators' to be enabled." -Verbose + Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'PSCoalescingOperators' to be enabled." -Verbose $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() $PSDefaultParameterValues["it:skip"] = $true } else { @@ -69,6 +69,7 @@ Describe 'NullConditionalOperations' -Tags 'CI' { } It 'Rhs is a cmdlet' { + $x = $null $x ??= (Get-Alias -Name 'where') $x.Definition | Should -BeExactly 'Where-Object' } diff --git a/test/powershell/Language/Parser/Parsing.Tests.ps1 b/test/powershell/Language/Parser/Parsing.Tests.ps1 index 0854e232c29..bff912090d2 100644 --- a/test/powershell/Language/Parser/Parsing.Tests.ps1 +++ b/test/powershell/Language/Parser/Parsing.Tests.ps1 @@ -264,9 +264,9 @@ Describe 'assignment statement parsing' -Tags "CI" { Describe 'null coalescing assignment statement parsing' -Tag 'CI' { BeforeAll { - $skipTest = -not $EnabledExperimentalFeatures.Contains('PSNullCoalescingOperators') + $skipTest = -not $EnabledExperimentalFeatures.Contains('PSCoalescingOperators') if ($skipTest) { - Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'PSNullCoalescingOperators' to be enabled." -Verbose + Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'PSCoalescingOperators' to be enabled." -Verbose $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() $PSDefaultParameterValues["it:skip"] = $true } @@ -288,9 +288,9 @@ Describe 'null coalescing assignment statement parsing' -Tag 'CI' { Describe 'null coalescing statement parsing' -Tag "CI" { BeforeAll { - $skipTest = -not $EnabledExperimentalFeatures.Contains('PSNullCoalescingOperators') + $skipTest = -not $EnabledExperimentalFeatures.Contains('PSCoalescingOperators') if ($skipTest) { - Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'PSNullCoalescingOperators' to be enabled." -Verbose + Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'PSCoalescingOperators' to be enabled." -Verbose $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() $PSDefaultParameterValues["it:skip"] = $true }