Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for named argument with .NET Methods #21487

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
871e7ae
Add parsing work for named arguments
jborean93 Mar 28, 2024
da97427
Expand tests and fix up remaining token parser
jborean93 Mar 28, 2024
9544976
Pass through argument names through CallInfo
jborean93 Mar 29, 2024
428bb14
Pass down argument names to best method specifier
jborean93 Apr 1, 2024
6506c85
Add check for duplicate labels in parsing
jborean93 Apr 2, 2024
20d5b31
Add check to ensure named arguments always appear after other named a…
jborean93 Apr 2, 2024
9a0e8b7
Add name logic for overload candidate selection
jborean93 Apr 12, 2024
d06ac7d
Remap args after finding best overload
jborean93 Apr 15, 2024
ad78259
Add test cases
jborean93 Apr 15, 2024
e60730b
Slowly fix params examples - support params and default MI
jborean93 Apr 16, 2024
8cd9681
Use new OverloadArgument setup
jborean93 Apr 16, 2024
29afdd5
Use new OverloadCandidate rules
jborean93 Apr 16, 2024
aac6682
Cover more edge cases and expand docs
jborean93 Apr 17, 2024
388d1f7
Cleanup before PR
jborean93 Apr 17, 2024
d415082
Fix build for Windows
jborean93 Apr 17, 2024
3cdaaf3
Fix index check for CallInfo that is not set
jborean93 Apr 17, 2024
47eaea6
Fix support for null argument names
jborean93 Apr 17, 2024
a414d35
Avoid test class name collision
jborean93 Apr 17, 2024
ac4a71d
Fix up CallInfo ArgumentName logic
jborean93 Apr 17, 2024
b12920f
Add tests for COM
jborean93 Apr 17, 2024
6bfc535
Make .NET args case insensitive and fix collision fallback
jborean93 Apr 17, 2024
8ed0014
Re-add DebuggerDisplay
jborean93 May 19, 2024
4a6fc97
Remove stray WriteLine and rename Ast to NamedMethodArgumentAst
jborean93 Jun 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/System.Management.Automation/engine/COM/ComUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ internal static ParameterInformation[] GetParameterInformation(COM.FUNCDESC func
}

bool fByRef = (ElementDescription.desc.paramdesc.wParamFlags & COM.PARAMFLAG.PARAMFLAG_FOUT) != 0;
parameters[i] = new ParameterInformation(type, fOptional, defaultvalue, fByRef);
parameters[i] = new ParameterInformation(string.Empty, type, fOptional, defaultvalue, fByRef);
}

return parameters;
Expand Down
579 changes: 387 additions & 192 deletions src/System.Management.Automation/engine/CoreAdapter.cs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/System.Management.Automation/engine/ExtraAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ protected override object MethodInvoke(PSMethod method, object[] arguments)

for (int i = 0; i < arguments.Length; i++)
{
parameters[i] = new ParameterInformation(typeof(object), false, null, false);
parameters[i] = new ParameterInformation(string.Empty, typeof(object), false, null, false);
}

MethodInformation[] methodInformation = new MethodInformation[1];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ internal class WMIParameterInformation : ParameterInformation
{
public string Name { get; }

public WMIParameterInformation(string name, Type ty) : base(ty, true, null, false)
public WMIParameterInformation(string name, Type ty) : base(name, ty, true, null, false)
{
Name = name;
}
Expand Down
10 changes: 10 additions & 0 deletions src/System.Management.Automation/engine/parser/AstVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,9 @@ public interface ICustomAstVisitor2 : ICustomAstVisitor

/// <summary/>
object? VisitPipelineChain(PipelineChainAst statementChainAst) => DefaultVisit(statementChainAst);

/// <summary/>
object? VisitNamedMethodArgumentExpression(NamedMethodArgumentAst namedMethodArgExpression) => DefaultVisit(namedMethodArgExpression);
}
#nullable restore

Expand Down Expand Up @@ -378,6 +381,8 @@ internal AstVisitAction CheckParent(Ast ast)
public override AstVisitAction VisitTernaryExpression(TernaryExpressionAst ast) => CheckParent(ast);

public override AstVisitAction VisitPipelineChain(PipelineChainAst ast) => CheckParent(ast);

public override AstVisitAction VisitNamedMethodArgumentExpression(NamedMethodArgumentAst ast) => CheckParent(ast);
}

/// <summary>
Expand Down Expand Up @@ -614,6 +619,8 @@ protected AstVisitAction CheckScriptBlock(Ast ast)
public override AstVisitAction VisitTernaryExpression(TernaryExpressionAst ast) { return Check(ast); }

public override AstVisitAction VisitPipelineChain(PipelineChainAst ast) { return Check(ast); }

public override AstVisitAction VisitNamedMethodArgumentExpression(NamedMethodArgumentAst ast) { return Check(ast); }
}

/// <summary>
Expand Down Expand Up @@ -818,5 +825,8 @@ public abstract class DefaultCustomAstVisitor2 : DefaultCustomAstVisitor, ICusto

/// <summary/>
public virtual object VisitPipelineChain(PipelineChainAst statementChainAst) => DefaultVisit(statementChainAst);

/// <summary/>
public virtual object VisitNamedMethodArgumentExpression(NamedMethodArgumentAst argumentWithLabelAst) => DefaultVisit(argumentWithLabelAst);
}
}
61 changes: 46 additions & 15 deletions src/System.Management.Automation/engine/parser/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -957,20 +957,31 @@ internal Expression CompileExpressionOperand(ExpressionAst exprAst)
return result;
}

private IEnumerable<Expression> CompileInvocationArguments(IReadOnlyList<ExpressionAst> arguments)
private (string[], IEnumerable<Expression>) CompileInvocationArguments(IReadOnlyList<ExpressionAst> arguments)
{
if (arguments is null || arguments.Count == 0)
{
return Array.Empty<Expression>();
return (Array.Empty<string>(), Array.Empty<Expression>());
}

var result = new Expression[arguments.Count];
for (int i = 0; i < result.Length; i++)
// argNames when provided to the CallInfo in left to right order
// with the last name corresponding to the last argument.
var argNames = new List<string>(arguments.Count);
var argExpressions = new Expression[arguments.Count];
for (int i = 0; i < argExpressions.Length; i++)
{
result[i] = CompileExpressionOperand(arguments[i]);
ExpressionAst value = arguments[i];

if (value is NamedMethodArgumentAst labelAst)
{
argNames.Add(labelAst.Label.Value);
value = labelAst.Expression;
}

argExpressions[i] = CompileExpressionOperand(value);
}

return result;
return (argNames.ToArray(), argExpressions);
}

internal Expression ReduceAssignment(ISupportsAssignment left, TokenKind tokenKind, Expression right)
Expand Down Expand Up @@ -3423,9 +3434,9 @@ public object VisitFunctionMember(FunctionMemberAst functionMemberAst)
public object VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst)
{
var target = CompileExpressionOperand(baseCtorInvokeMemberExpressionAst.Expression);
var args = CompileInvocationArguments(baseCtorInvokeMemberExpressionAst.Arguments);
(var argNames, var args) = CompileInvocationArguments(baseCtorInvokeMemberExpressionAst.Arguments);
var baseCtorCallConstraints = GetInvokeMemberConstraints(baseCtorInvokeMemberExpressionAst);
return InvokeBaseCtorMethod(baseCtorCallConstraints, target, args);
return InvokeBaseCtorMethod(baseCtorCallConstraints, target, argNames, args);
}

public object VisitUsingStatement(UsingStatementAst usingStatementAst)
Expand Down Expand Up @@ -6390,11 +6401,12 @@ internal static PSMethodInvocationConstraints GetInvokeMemberConstraints(BaseCto
PSMethodInvocationConstraints constraints,
Expression target,
IEnumerable<Expression> args,
IEnumerable<string> argNames,
bool @static,
bool propertySet,
bool nullConditional = false)
{
var callInfo = new CallInfo(args.Count());
var callInfo = new CallInfo(args.Count(), argNames);
var classScope = _memberFunctionType?.Type;
var binder = name.Equals("new", StringComparison.OrdinalIgnoreCase) && @static
? (CallSiteBinder)PSCreateInstanceBinder.Get(callInfo, constraints, publicTypeOnly: true)
Expand All @@ -6405,9 +6417,13 @@ internal static PSMethodInvocationConstraints GetInvokeMemberConstraints(BaseCto
return nullConditional ? GetNullConditionalWrappedExpression(target, dynamicExprFromBinder) : dynamicExprFromBinder;
}

private static Expression InvokeBaseCtorMethod(PSMethodInvocationConstraints constraints, Expression target, IEnumerable<Expression> args)
private static Expression InvokeBaseCtorMethod(
PSMethodInvocationConstraints constraints,
Expression target,
IEnumerable<string> argNames,
IEnumerable<Expression> args)
{
var callInfo = new CallInfo(args.Count());
var callInfo = new CallInfo(args.Count(), argNames);
var binder = PSInvokeBaseCtorBinder.Get(callInfo, constraints);
return DynamicExpression.Dynamic(binder, typeof(object), args.Prepend(target));
}
Expand All @@ -6432,7 +6448,7 @@ public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMember
var constraints = GetInvokeMemberConstraints(invokeMemberExpressionAst);

var target = CompileExpressionOperand(invokeMemberExpressionAst.Expression);
var args = CompileInvocationArguments(invokeMemberExpressionAst.Arguments);
(var argNames, var args) = CompileInvocationArguments(invokeMemberExpressionAst.Arguments);

var memberNameAst = invokeMemberExpressionAst.Member as StringConstantExpressionAst;
if (memberNameAst != null)
Expand All @@ -6442,9 +6458,10 @@ public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMember
constraints,
target,
args,
argNames,
invokeMemberExpressionAst.Static,
propertySet: false,
invokeMemberExpressionAst.NullConditional);
nullConditional: invokeMemberExpressionAst.NullConditional);
}

var memberNameExpr = Compile(invokeMemberExpressionAst.Member);
Expand Down Expand Up @@ -6764,7 +6781,14 @@ public Expression GetValue(Compiler compiler, List<Expression> exprs, List<Param
var memberNameAst = InvokeMemberExpressionAst.Member as StringConstantExpressionAst;
if (memberNameAst != null)
{
return compiler.InvokeMember(memberNameAst.Value, constraints, _targetExprTemp, _argExprTemps, @static: false, propertySet: false);
return compiler.InvokeMember(
memberNameAst.Value,
constraints,
_targetExprTemp,
_argExprTemps,
Array.Empty<string>(),
@static: false,
propertySet: false);
}

var memberNameExpr = GetMemberNameExpr(compiler);
Expand All @@ -6785,7 +6809,14 @@ public Expression SetValue(Compiler compiler, Expression rhs)
var args = GetArgumentExprs(compiler);
if (memberNameAst != null)
{
return compiler.InvokeMember(memberNameAst.Value, constraints, target, args.Append(rhs), @static: false, propertySet: true);
return compiler.InvokeMember(
memberNameAst.Value,
constraints,
target,
args.Append(rhs),
Array.Empty<string>(),
@static: false,
propertySet: true);
}

var memberNameExpr = GetMemberNameExpr(compiler);
Expand Down
90 changes: 85 additions & 5 deletions src/System.Management.Automation/engine/parser/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7891,13 +7891,16 @@ private List<ExpressionAst> InvokeParamParenListRule(Token lParen, out IScriptEx
// G argument-list: '(' is passed in lParen
// G argument-expression-list:opt new-lines:opt ')'
// G argument-expression-list:
// G argument-expression
// G argument-expression new-lines:opt ',' argument-expression-list
// G argument-label-expression:opt argument-expression
// G argument-label-expression:opt argument-expression new-lines:opt ',' argument-expression-list
// G argument-expression:
// G See grammar for expression - the only difference is that an
// G array-literal-expression is not allowed - the comma is used
// G to separate argument-expressions.
// G argument-label-expression:
// G simple-name ':'

HashSet<string> existingArguments = new HashSet<string>();
List<ExpressionAst> arguments = new List<ExpressionAst>();
Token comma = null;
Token rParen = null;
Expand All @@ -7912,13 +7915,53 @@ private List<ExpressionAst> InvokeParamParenListRule(Token lParen, out IScriptEx
while (true)
{
SkipNewlines();

StringConstantExpressionAst argumentName = SimpleNameRule(out Token argNameToken);
if (argumentName is not null)
{
Token colon = NextToken();
if (colon.Kind != TokenKind.Colon)
{
UngetToken(colon);

ReportIncompleteInput(After(argNameToken),
nameof(ParserStrings.MissingColonAfterArgumentLabel),
ParserStrings.MissingColonAfterArgumentLabel,
argumentName.Extent.ToString(),
colon.Text);
reportedError = true;
break;
}
}

ExpressionAst argument = ExpressionRule();
if (argument == null)
{
if (comma != null)
// ErrorRecovery: sync at closing paren or newline.

if (argumentName is not null)
{
// ErrorRecovery: sync at closing paren or newline.
if (comma is null)
{
comma = NextToken();
UngetToken(comma);
}

string nextTokenText = comma.Text;
if (nextTokenText.Length == 1 && char.IsControl(nextTokenText[0]))
{
nextTokenText = string.Format("\\u{0:x4}", (short)nextTokenText[0]);
}

ReportIncompleteInput(After(argumentName.Extent),
nameof(ParserStrings.MissingArgumentAfterLabel),
ParserStrings.MissingArgumentAfterLabel,
argumentName.Value,
nextTokenText);
reportedError = true;
}
else if (comma != null)
{
ReportIncompleteInput(After(comma),
nameof(ParserStrings.MissingExpressionAfterToken),
ParserStrings.MissingExpressionAfterToken,
Expand All @@ -7929,7 +7972,35 @@ private List<ExpressionAst> InvokeParamParenListRule(Token lParen, out IScriptEx
break;
}

arguments.Add(argument);
if (argumentName is null)
{
if (existingArguments.Count > 0)
{
// ErrorRecovery: sync at closing paren or newline.

ReportError(argument.Extent,
nameof(ParserStrings.UnnamedArgumentAfterNamed),
ParserStrings.UnnamedArgumentAfterNamed);
reportedError = true;
break;
}

arguments.Add(argument);
}
else
{
IScriptExtent entryExtent = ExtentOf(argumentName, argument);
if (!existingArguments.Add(argumentName.Value))
{
ReportError(entryExtent,
nameof(ParserStrings.DuplicateArgumentLabel),
ParserStrings.DuplicateArgumentLabel,
argumentName.Value);
reportedError = true;
break;
}
arguments.Add(new NamedMethodArgumentAst(entryExtent, argumentName, argument));
}

SkipNewlines();
comma = NextToken();
Expand Down Expand Up @@ -8127,6 +8198,15 @@ internal bool ReportIncompleteInput(IScriptExtent extent, string errorId, string
return incompleteInput;
}

internal bool ReportIncompleteInput(IScriptExtent extent, string errorId, string errorMsg, params object[] args)
{
// If the error position isn't at the end of the input, then we don't want to mark the error
// as incomplete input.
bool incompleteInput = _tokenizer.IsAtEndOfScript(extent, checkCommentsAndWhitespace: true);
SaveError(extent, errorId, errorMsg, incompleteInput, args);
return incompleteInput;
}

internal bool ReportIncompleteInput(IScriptExtent errorPosition,
IScriptExtent errorDetectedPosition,
string errorId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,9 @@ public abstract class AstVisitor2 : AstVisitor

/// <summary/>
public virtual AstVisitAction VisitPipelineChain(PipelineChainAst statementChain) => DefaultVisit(statementChain);

/// <summary/>
public virtual AstVisitAction VisitNamedMethodArgumentExpression(NamedMethodArgumentAst argumentWithLabelAst) => DefaultVisit(argumentWithLabelAst);
}

/// <summary>
Expand Down