Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
fa7e3a2
Working ?. for member and member invoke
adityapatwardhan Oct 3, 2019
5b55dba
Remove extra line
adityapatwardhan Nov 1, 2019
f226a4b
InvokeMemberExpression in compiler
adityapatwardhan Nov 5, 2019
345f687
Reverting Binder changes
adityapatwardhan Nov 5, 2019
ea0364f
?[] working
adityapatwardhan Nov 7, 2019
a73d726
Fix issue with return value of indexed member
adityapatwardhan Nov 8, 2019
a453d25
Fix issue with assigning null members
adityapatwardhan Nov 8, 2019
67b1317
Fix experimental feature condition
adityapatwardhan Nov 8, 2019
a2d6723
Fix bug in ?.()
adityapatwardhan Nov 12, 2019
f946cbb
Add tests
adityapatwardhan Nov 12, 2019
cbbacd3
CodeFactor fixes
adityapatwardhan Nov 12, 2019
9d02ad2
More CodeFactor fixes
adityapatwardhan Nov 12, 2019
36177cf
Add one more test
adityapatwardhan Nov 12, 2019
87b380e
Restructure tests as per experimental feature name
adityapatwardhan Nov 12, 2019
d993668
Add Parsing tests
adityapatwardhan Nov 13, 2019
4d65d27
Address Dongbo's feedback
adityapatwardhan Nov 13, 2019
c63ba46
Remove dead code from tokenizer
adityapatwardhan Nov 13, 2019
6c1b5ab
Fix tests and remove dead code
adityapatwardhan Nov 13, 2019
fef4f9f
Fix tab completion
adityapatwardhan Nov 13, 2019
51e0453
Fix bug introduced in refactoring
adityapatwardhan Nov 13, 2019
f75f71b
Address code review comments
adityapatwardhan Nov 14, 2019
b27a8f2
Fix null conditional access to dyamic method and add test
adityapatwardhan Nov 14, 2019
a39ae8d
Replace Expression.IfThenElse condition with Expression.Condition
adityapatwardhan Nov 14, 2019
b3a94dd
Change Semantics checks to be done in CheckAssignmentTarget
adityapatwardhan Nov 14, 2019
50764c7
Add more tests for dynamic method name or property
adityapatwardhan Nov 15, 2019
6d3af7f
Fix expected error ID
adityapatwardhan Nov 15, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ internal List<CompletionResult> GetResultHelper(CompletionContext completionCont

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adityapatwardhan, your last commit had 1 failures in PowerShell-CI-windows
Enter-PSHostProcess tests.By Process Id.Can enter using NamedPipeConnectionInfo

Exception calling "Invoke" with "0" argument(s): "The runspace state is not valid for this operation."
at <ScriptBlock>, D:\a\1\s\test\powershell\Modules\Microsoft.PowerShell.Core\Enter-PSHostProcess.Tests.ps1: line 136
136:                 $ps.AddScript('$pid').Invoke() | Should -Be $pwshId

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adityapatwardhan, your last commit had 1 failures in PowerShell-CI-windows
NullConditionalMemberAccess.?. operator tests.Use ?. on a dynamic method name that does not exist

Expected an exception, with FullyQualifiedErrorId 'Argument' to be thrown, but the FullyQualifiedErrorId was 'MethodNotFound'. from D:\a\1\s\test\powershell\Language\Operators\NullConditional.Tests.ps1:343 char:15
    +             { (Get-Date '11/11/2019')?.$methodName() } | Should -Thro ?
    +               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
at <ScriptBlock>, D:\a\1\s\test\powershell\Language\Operators\NullConditional.Tests.ps1: line 343
343:             { (Get-Date '11/11/2019')?.$methodName() } | Should -Throw -ErrorId 'Argument'

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adityapatwardhan, your last commit had 1 failures in PowerShell-CI-linux
NullConditionalMemberAccess.?. operator tests.Use ?. on a dynamic method name that does not exist

Expected an exception, with FullyQualifiedErrorId 'Argument' to be thrown, but the FullyQualifiedErrorId was 'MethodNotFound'. from /home/vsts/work/1/s/test/powershell/Language/Operators/NullConditional.Tests.ps1:343 char:15
    +             { (Get-Date '11/11/2019')?.$methodName() } | Should -Thro ?
    +               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
at <ScriptBlock>, /home/vsts/work/1/s/test/powershell/Language/Operators/NullConditional.Tests.ps1: line 343
343:             { (Get-Date '11/11/2019')?.$methodName() } | Should -Throw -ErrorId 'Argument'

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adityapatwardhan, your last commit had 1 failures in PowerShell-CI-macos
NullConditionalMemberAccess.?. operator tests.Use ?. on a dynamic method name that does not exist

Expected an exception, with FullyQualifiedErrorId 'Argument' to be thrown, but the FullyQualifiedErrorId was 'MethodNotFound'. from /Users/runner/runners/2.160.0/work/1/s/test/powershell/Language/Operators/NullConditional.Tests.ps1:343 char:15
    +             { (Get-Date '11/11/2019')?.$methodName() } | Should -Thro ?
    +               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
at <ScriptBlock>, /Users/runner/runners/2.160.0/work/1/s/test/powershell/Language/Operators/NullConditional.Tests.ps1: line 343
343:             { (Get-Date '11/11/2019')?.$methodName() } | Should -Throw -ErrorId 'Argument'

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adityapatwardhan, your last commit had 20 failures in PowerShell-CI-static-analysis
(These are 5 of the failures)

Verify Markdown Links.Verify links in /home/vsts/work/1/s/README.md.https://docs.microsoft.com/powershell/scripting/setup/installing-powershell-core-on-windows?view=powershell-6 should work

retry of URL failed with error: Response status code does not indicate success: 404 (Not Found).
at <ScriptBlock>, /home/vsts/work/1/s/test/common/markdown/markdown-link.tests.ps1: line 117
117:                                     throw "retry of URL failed with error: $($_.Exception.Message)"

Verify Markdown Links.Verify links in /home/vsts/work/1/s/README.md.https://docs.microsoft.com/powershell/scripting/setup/installing-powershell-core-on-linux?view=powershell-6#ubuntu-1804 should work

retry of URL failed with error: Response status code does not indicate success: 404 (Not Found).
at <ScriptBlock>, /home/vsts/work/1/s/test/common/markdown/markdown-link.tests.ps1: line 117
117:                                     throw "retry of URL failed with error: $($_.Exception.Message)"

Verify Markdown Links.Verify links in /home/vsts/work/1/s/README.md.https://docs.microsoft.com/powershell/scripting/setup/installing-powershell-core-on-linux?view=powershell-6#ubuntu-1604 should work

retry of URL failed with error: Response status code does not indicate success: 404 (Not Found).
at <ScriptBlock>, /home/vsts/work/1/s/test/common/markdown/markdown-link.tests.ps1: line 117
117:                                     throw "retry of URL failed with error: $($_.Exception.Message)"

Verify Markdown Links.Verify links in /home/vsts/work/1/s/README.md.https://docs.microsoft.com/powershell/scripting/setup/installing-powershell-core-on-linux?view=powershell-6#debian-9 should work

retry of URL failed with error: Response status code does not indicate success: 404 (Not Found).
at <ScriptBlock>, /home/vsts/work/1/s/test/common/markdown/markdown-link.tests.ps1: line 117
117:                                     throw "retry of URL failed with error: $($_.Exception.Message)"

Verify Markdown Links.Verify links in /home/vsts/work/1/s/README.md.https://docs.microsoft.com/powershell/scripting/setup/installing-powershell-core-on-linux?view=powershell-6#centos-7 should work

retry of URL failed with error: Response status code does not indicate success: 404 (Not Found).
at <ScriptBlock>, /home/vsts/work/1/s/test/common/markdown/markdown-link.tests.ps1: line 117
117:                                     throw "retry of URL failed with error: $($_.Exception.Message)"

case TokenKind.Dot:
case TokenKind.ColonColon:
case TokenKind.QuestionDot:
replacementIndex += tokenAtCursor.Text.Length;
replacementLength = 0;
result = CompletionCompleters.CompleteMember(completionContext, @static: tokenAtCursor.Kind == TokenKind.ColonColon);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ static ExperimentalFeature()
new ExperimentalFeature(
name: "PSPipelineChainOperators",
description: "Allow use of && and || as operators between pipeline invocations"),
new ExperimentalFeature(
name: "PSNullConditionalOperators",
description: "Support the null conditional member access operators in PowerShell language")
};
EngineExperimentalFeatures = new ReadOnlyCollection<ExperimentalFeature>(engineFeatures);

Expand Down
66 changes: 45 additions & 21 deletions src/System.Management.Automation/engine/parser/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6174,15 +6174,14 @@ public object VisitMemberExpression(MemberExpressionAst memberExpressionAst)
}

var target = CompileExpressionOperand(memberExpressionAst.Expression);

// If the ?. operator is used for null conditional check, add the null conditional expression.
var memberNameAst = memberExpressionAst.Member as StringConstantExpressionAst;
if (memberNameAst != null)
{
string name = memberNameAst.Value;
return DynamicExpression.Dynamic(PSGetMemberBinder.Get(name, _memberFunctionType, memberExpressionAst.Static), typeof(object), target);
}
Expression memberAccessExpr = memberNameAst != null
? DynamicExpression.Dynamic(PSGetMemberBinder.Get(memberNameAst.Value, _memberFunctionType, memberExpressionAst.Static), typeof(object), target)
: DynamicExpression.Dynamic(PSGetDynamicMemberBinder.Get(_memberFunctionType, memberExpressionAst.Static), typeof(object), target, Compile(memberExpressionAst.Member));

var memberNameExpr = Compile(memberExpressionAst.Member);
return DynamicExpression.Dynamic(PSGetDynamicMemberBinder.Get(_memberFunctionType, memberExpressionAst.Static), typeof(object), target, memberNameExpr);
return memberExpressionAst.NullConditional ? GetNullConditionalWrappedExpression(target, memberAccessExpr) : memberAccessExpr;
}

internal static PSMethodInvocationConstraints GetInvokeMemberConstraints(InvokeMemberExpressionAst invokeMemberExpressionAst)
Expand Down Expand Up @@ -6219,14 +6218,18 @@ internal Expression InvokeMember(
Expression target,
IEnumerable<Expression> args,
bool @static,
bool propertySet)
bool propertySet,
bool nullConditional = false)
{
var callInfo = new CallInfo(args.Count());
var classScope = _memberFunctionType != null ? _memberFunctionType.Type : null;
var binder = name.Equals("new", StringComparison.OrdinalIgnoreCase) && @static
? (CallSiteBinder)PSCreateInstanceBinder.Get(callInfo, constraints, publicTypeOnly: true)
: PSInvokeMemberBinder.Get(name, callInfo, @static, propertySet, constraints, classScope);
return DynamicExpression.Dynamic(binder, typeof(object), args.Prepend(target));

var dynamicExprFromBinder = DynamicExpression.Dynamic(binder, typeof(object), args.Prepend(target));

return nullConditional ? GetNullConditionalWrappedExpression(target, dynamicExprFromBinder) : dynamicExprFromBinder;
}

private Expression InvokeBaseCtorMethod(PSMethodInvocationConstraints constraints, Expression target, IEnumerable<Expression> args)
Expand All @@ -6242,10 +6245,13 @@ internal Expression InvokeDynamicMember(
Expression target,
IEnumerable<Expression> args,
bool @static,
bool propertySet)
bool propertySet,
bool nullConditional = false)
{
var binder = PSInvokeDynamicMemberBinder.Get(new CallInfo(args.Count()), _memberFunctionType, @static, propertySet, constraints);
return DynamicExpression.Dynamic(binder, typeof(object), args.Prepend(memberNameExpr).Prepend(target));
var dynamicExprFromBinder = DynamicExpression.Dynamic(binder, typeof(object), args.Prepend(memberNameExpr).Prepend(target));

return nullConditional ? GetNullConditionalWrappedExpression(target, dynamicExprFromBinder) : dynamicExprFromBinder;
}

public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst)
Expand All @@ -6258,11 +6264,18 @@ public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMember
var memberNameAst = invokeMemberExpressionAst.Member as StringConstantExpressionAst;
if (memberNameAst != null)
{
return InvokeMember(memberNameAst.Value, constraints, target, args, invokeMemberExpressionAst.Static, false);
return InvokeMember(
memberNameAst.Value,
constraints,
target,
args,
invokeMemberExpressionAst.Static,
propertySet: false,
invokeMemberExpressionAst.NullConditional);
}

var memberNameExpr = Compile(invokeMemberExpressionAst.Member);
return InvokeDynamicMember(memberNameExpr, constraints, target, args, invokeMemberExpressionAst.Static, false);
return InvokeDynamicMember(memberNameExpr, constraints, target, args, invokeMemberExpressionAst.Static, propertySet: false, invokeMemberExpressionAst.NullConditional);
}

public object VisitArrayExpression(ArrayExpressionAst arrayExpressionAst)
Expand Down Expand Up @@ -6422,15 +6435,26 @@ public object VisitIndexExpression(IndexExpressionAst indexExpressionAst)
// In the former case, the user is requesting an array slice. In the latter case, they index expression is likely
// an array (dynamically determined) and they don't want an array slice, they want to use the array as the index
// expression.
if (arrayLiteral != null && arrayLiteral.Elements.Count > 1)
{
return DynamicExpression.Dynamic(
PSGetIndexBinder.Get(arrayLiteral.Elements.Count, constraints),
typeof(object),
arrayLiteral.Elements.Select(CompileExpressionOperand).Prepend(targetExpr));
}
Expression indexingExpr = arrayLiteral != null && arrayLiteral.Elements.Count > 1
? DynamicExpression.Dynamic(
PSGetIndexBinder.Get(arrayLiteral.Elements.Count, constraints),
typeof(object),
arrayLiteral.Elements.Select(CompileExpressionOperand).Prepend(targetExpr))
: DynamicExpression.Dynamic(
PSGetIndexBinder.Get(argCount: 1, constraints),
typeof(object),
targetExpr,
CompileExpressionOperand(index));

return DynamicExpression.Dynamic(PSGetIndexBinder.Get(1, constraints), typeof(object), targetExpr, CompileExpressionOperand(index));
return indexExpressionAst.NullConditional ? GetNullConditionalWrappedExpression(targetExpr, indexingExpr) : indexingExpr;
}

private static Expression GetNullConditionalWrappedExpression(Expression targetExpr, Expression memberAccessExpression)
{
return Expression.Condition(
Expression.Call(CachedReflectionInfo.LanguagePrimitives_IsNullLike, targetExpr.Cast(typeof(object))),
ExpressionCache.NullConstant,
memberAccessExpression);
}

public object VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst)
Expand Down
22 changes: 16 additions & 6 deletions src/System.Management.Automation/engine/parser/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7347,11 +7347,11 @@ private ExpressionAst CheckPostPrimaryExpressionOperators(Token token, Expressio
// To support fluent style programming, allow newlines after the member access operator.
SkipNewlines();

if (token.Kind == TokenKind.Dot || token.Kind == TokenKind.ColonColon)
if (token.Kind == TokenKind.Dot || token.Kind == TokenKind.ColonColon || token.Kind == TokenKind.QuestionDot)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In C#, there is no point to support ?. for member access to static members, but in PowerShell, you can do $a = [string]; $a::Equals(...), which makes it debatable whether we want to support it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though it is technically possible to implement something like: $a = [string]; ${a}?::Equals(...), i doubt there is any usefulness to this.

Copy link
Member

@daxian-dbw daxian-dbw Nov 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's technically possible. I vote for postponing it until we have a ask. @SteveL-MSFT any thoughts?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though it is technically possible to implement something like: $a = [string]; ${a}?::Equals(...), i doubt there is any usefulness to this.

Where I think it would be most useful is situations where you want to invoke a static method only if an assembly is already loaded. e.g.

('MyCustomType' -as [type])?::Initialize()

Copy link
Collaborator

@vexx32 vexx32 Nov 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That certainly beats my current method! (and has better utility, probably, as well)

switch ($null) {
    ('typename' -as [type]) { . Type.ps1 }
    ('type2name' -as [type]) { . Type2.ps1 }
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it's additive we can postpone

{
expr = MemberAccessRule(expr, token);
}
else if (token.Kind == TokenKind.LBracket)
else if (token.Kind == TokenKind.LBracket || token.Kind == TokenKind.QuestionLBracket)
{
expr = ElementAccessRule(expr, token);
}
Expand Down Expand Up @@ -7772,8 +7772,12 @@ private ExpressionAst MemberAccessRule(ExpressionAst targetExpr, Token operatorT
}
}

return new MemberExpressionAst(ExtentOf(targetExpr, member),
targetExpr, member, operatorToken.Kind == TokenKind.ColonColon);
return new MemberExpressionAst(
ExtentOf(targetExpr, member),
targetExpr,
member,
@static: operatorToken.Kind == TokenKind.ColonColon,
nullConditional: operatorToken.Kind == TokenKind.QuestionDot);
}

private ExpressionAst MemberInvokeRule(ExpressionAst targetExpr, Token lBracket, Token operatorToken, CommandElementAst member)
Expand Down Expand Up @@ -7801,7 +7805,13 @@ private ExpressionAst MemberInvokeRule(ExpressionAst targetExpr, Token lBracket,
lastExtent = argument.Extent;
}

return new InvokeMemberExpressionAst(ExtentOf(targetExpr, lastExtent), targetExpr, member, arguments, operatorToken.Kind == TokenKind.ColonColon);
return new InvokeMemberExpressionAst(
ExtentOf(targetExpr, lastExtent),
targetExpr,
member,
arguments,
operatorToken.Kind == TokenKind.ColonColon,
operatorToken.Kind == TokenKind.QuestionDot);
}

private List<ExpressionAst> InvokeParamParenListRule(Token lParen, out IScriptExtent lastExtent)
Expand Down Expand Up @@ -7923,7 +7933,7 @@ private ExpressionAst ElementAccessRule(ExpressionAst primaryExpression, Token l
rBracket = null;
}

return new IndexExpressionAst(ExtentOf(primaryExpression, ExtentFromFirstOf(rBracket, indexExpr)), primaryExpression, indexExpr);
return new IndexExpressionAst(ExtentOf(primaryExpression, ExtentFromFirstOf(rBracket, indexExpr)), primaryExpression, indexExpr, lBracket.Kind == TokenKind.QuestionLBracket);
}

#endregion Expressions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,14 @@ private void CheckAssignmentTarget(ExpressionAst ast, bool simpleAssignment, Act
{
errorAst = ast;
}
else if (ast is MemberExpressionAst memberExprAst && memberExprAst.NullConditional)
{
errorAst = ast;
}
else if (ast is IndexExpressionAst indexExprAst && indexExprAst.NullConditional)
{
errorAst = ast;
}
else if (ast is AttributedExpressionAst)
{
// Check for multiple types combined with [ref].
Expand Down
77 changes: 73 additions & 4 deletions src/System.Management.Automation/engine/parser/ast.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7851,6 +7851,26 @@ public MemberExpressionAst(IScriptExtent extent, ExpressionAst expression, Comma
this.Static = @static;
}

/// <summary>
/// Initializes a new instance of the <see cref="MemberExpressionAst"/> class.
/// </summary>
/// <param name="extent">
/// The extent of the expression, starting with the expression before the operator '.', '::' or '?.' and ending after
/// membername or expression naming the member.
/// </param>
/// <param name="expression">The expression before the member access operator '.', '::' or '?.'.</param>
/// <param name="member">The name or expression naming the member to access.</param>
/// <param name="static">True if the '::' operator was used, false if '.' or '?.' is used.</param>
/// <param name="nullConditional">True if '?.' used.</param>
/// <exception cref="PSArgumentNullException">
/// If <paramref name="extent"/>, <paramref name="expression"/>, or <paramref name="member"/> is null.
/// </exception>
public MemberExpressionAst(IScriptExtent extent, ExpressionAst expression, CommandElementAst member, bool @static, bool nullConditional)
: this(extent, expression, member, @static)
{
this.NullConditional = nullConditional;
}

/// <summary>
/// The expression that produces the value to retrieve the member from. This property is never null.
/// </summary>
Expand All @@ -7866,14 +7886,19 @@ public MemberExpressionAst(IScriptExtent extent, ExpressionAst expression, Comma
/// </summary>
public bool Static { get; private set; }

/// <summary>
/// Gets a value indicating true if the operator used is ?. or ?[].
/// </summary>
public bool NullConditional { get; protected set; }

/// <summary>
/// Copy the MemberExpressionAst instance.
/// </summary>
public override Ast Copy()
{
var newExpression = CopyElement(this.Expression);
var newMember = CopyElement(this.Member);
return new MemberExpressionAst(this.Extent, newExpression, newMember, this.Static);
return new MemberExpressionAst(this.Extent, newExpression, newMember, this.Static, this.NullConditional);
}

#region Visitors
Expand Down Expand Up @@ -7915,7 +7940,7 @@ public class InvokeMemberExpressionAst : MemberExpressionAst, ISupportsAssignmen
/// The extent of the expression, starting with the expression before the invocation operator and ending with the
/// closing paren after the arguments.
/// </param>
/// <param name="expression">The expression before the invocation operator ('.' or '::').</param>
/// <param name="expression">The expression before the invocation operator ('.', '::').</param>
/// <param name="method">The method to invoke.</param>
/// <param name="arguments">The arguments to pass to the method.</param>
/// <param name="static">
Expand All @@ -7934,6 +7959,29 @@ public InvokeMemberExpressionAst(IScriptExtent extent, ExpressionAst expression,
}
}

/// <summary>
/// Initializes a new instance of the <see cref="InvokeMemberExpressionAst"/> class.
/// </summary>
/// <param name="extent">
/// The extent of the expression, starting with the expression before the invocation operator and ending with the
/// closing paren after the arguments.
/// </param>
/// <param name="expression">The expression before the invocation operator ('.', '::' or '?.').</param>
/// <param name="method">The method to invoke.</param>
/// <param name="arguments">The arguments to pass to the method.</param>
/// <param name="static">
/// True if the invocation is for a static method, using '::', false if invoking a method on an instance using '.' or '?.'.
/// </param>
/// <param name="nullConditional">True if the operator used is '?.'.</param>
/// <exception cref="PSArgumentNullException">
/// If <paramref name="extent"/> is null.
/// </exception>
public InvokeMemberExpressionAst(IScriptExtent extent, ExpressionAst expression, CommandElementAst method, IEnumerable<ExpressionAst> arguments, bool @static, bool nullConditional)
: this(extent, expression, method, arguments, @static)
{
this.NullConditional = nullConditional;
}

/// <summary>
/// The non-empty collection of arguments to pass when invoking the method, or null if no arguments were specified.
/// </summary>
Expand All @@ -7947,7 +7995,7 @@ public override Ast Copy()
var newExpression = CopyElement(this.Expression);
var newMethod = CopyElement(this.Member);
var newArguments = CopyElements(this.Arguments);
return new InvokeMemberExpressionAst(this.Extent, newExpression, newMethod, newArguments, this.Static);
return new InvokeMemberExpressionAst(this.Extent, newExpression, newMethod, newArguments, this.Static, this.NullConditional);
}

#region Visitors
Expand Down Expand Up @@ -10220,6 +10268,22 @@ public IndexExpressionAst(IScriptExtent extent, ExpressionAst target, Expression
SetParent(index);
}

/// <summary>
/// Initializes a new instance of the <see cref="IndexExpressionAst"/> class.
/// </summary>
/// <param name="extent">The extent of the expression.</param>
/// <param name="target">The expression being indexed.</param>
/// <param name="index">The index expression.</param>
/// <param name="nullConditional">Access the index only if the target is not null.</param>
/// <exception cref="PSArgumentNullException">
/// If <paramref name="extent"/>, <paramref name="target"/>, or <paramref name="index"/> is null.
/// </exception>
public IndexExpressionAst(IScriptExtent extent, ExpressionAst target, ExpressionAst index, bool nullConditional)
: this(extent, target, index)
{
this.NullConditional = nullConditional;
}

/// <summary>
/// Return the ast for the expression being indexed. This value is never null.
/// </summary>
Expand All @@ -10230,14 +10294,19 @@ public IndexExpressionAst(IScriptExtent extent, ExpressionAst target, Expression
/// </summary>
public ExpressionAst Index { get; private set; }

/// <summary>
/// Gets a value indicating whether ?[] operator is being used.
/// </summary>
public bool NullConditional { get; private set; }

/// <summary>
/// Copy the IndexExpressionAst instance.
/// </summary>
public override Ast Copy()
{
var newTarget = CopyElement(this.Target);
var newIndex = CopyElement(this.Index);
return new IndexExpressionAst(this.Extent, newTarget, newIndex);
return new IndexExpressionAst(this.Extent, newTarget, newIndex, this.NullConditional);
}

#region Visitors
Expand Down
Loading