From 288d54388047cb97600f6afc88122ed098133e69 Mon Sep 17 00:00:00 2001 From: Joel Sallow <32407840+vexx32@users.noreply.github.com> Date: Wed, 22 Apr 2020 00:55:19 -0400 Subject: [PATCH 01/16] :sparkles: Add parsing rules and AST members - Add parsing rules to recognise generic method args - Add AST members to MemberExpressionAst / InvokeMemberExpressionAst so that the parsed information can be stored in the AST. Co-authored-by: Dave Wyatt --- .../engine/parser/Parser.cs | 136 ++++++++++++++---- .../engine/parser/ast.cs | 71 +++++++-- 2 files changed, 171 insertions(+), 36 deletions(-) diff --git a/src/System.Management.Automation/engine/parser/Parser.cs b/src/System.Management.Automation/engine/parser/Parser.cs index 3afc55e9861..400fdeca599 100644 --- a/src/System.Management.Automation/engine/parser/Parser.cs +++ b/src/System.Management.Automation/engine/parser/Parser.cs @@ -1437,7 +1437,7 @@ private ITypeName GetSingleGenericArgument(Token firstToken) return typeName; } - private ITypeName GenericTypeArgumentsRule(Token genericTypeName, Token firstToken, bool unBracketedGenericArg) + private List GenericArgumentsRule(Token firstToken, bool unBracketedGenericArg, out Token lastToken) { Diagnostics.Assert(firstToken.Kind == TokenKind.Identifier || firstToken.Kind == TokenKind.LBracket, "unexpected first token"); RuntimeHelpers.EnsureSufficientExecutionStack(); @@ -1446,13 +1446,12 @@ private ITypeName GenericTypeArgumentsRule(Token genericTypeName, Token firstTok ITypeName typeName = GetSingleGenericArgument(firstToken); genericArguments.Add(typeName); - Token commaOrRBracketToken; Token token; while (true) { SkipNewlines(); - commaOrRBracketToken = NextToken(); - if (commaOrRBracketToken.Kind != TokenKind.Comma) + lastToken = NextToken(); + if (lastToken.Kind != TokenKind.Comma) { break; } @@ -1467,30 +1466,41 @@ private ITypeName GenericTypeArgumentsRule(Token genericTypeName, Token firstTok } else { - ReportIncompleteInput(After(commaOrRBracketToken), + ReportIncompleteInput( + After(lastToken), nameof(ParserStrings.MissingTypename), ParserStrings.MissingTypename); - typeName = new TypeName(commaOrRBracketToken.Extent, ":ErrorTypeName:"); + typeName = new TypeName(lastToken.Extent, ":ErrorTypeName:"); } genericArguments.Add(typeName); } - if (commaOrRBracketToken.Kind != TokenKind.RBracket) + return genericArguments; + } + + private ITypeName GenericTypeArgumentsRule(Token genericTypeName, Token firstToken, bool unBracketedGenericArg) + { + List genericArguments = GenericArgumentsRule(firstToken, unBracketedGenericArg, out Token rBracketToken); + + if (rBracketToken.Kind != TokenKind.RBracket) { // ErrorRecovery: pretend we had the closing bracket and just continue on. - - UngetToken(commaOrRBracketToken); - ReportIncompleteInput(Before(commaOrRBracketToken), - nameof(ParserStrings.EndSquareBracketExpectedAtEndOfAttribute), - ParserStrings.EndSquareBracketExpectedAtEndOfAttribute); - commaOrRBracketToken = null; + UngetToken(rBracketToken); + ReportIncompleteInput( + Before(rBracketToken), + nameof(ParserStrings.UnexpectedToken), + ParserStrings.UnexpectedToken); + rBracketToken = null; } var openGenericType = new TypeName(genericTypeName.Extent, genericTypeName.Text); - var result = new GenericTypeName(ExtentOf(genericTypeName.Extent, ExtentFromFirstOf(commaOrRBracketToken, genericArguments.LastOrDefault(), firstToken)), - openGenericType, genericArguments); - token = PeekToken(); + var result = new GenericTypeName( + ExtentOf(genericTypeName.Extent, ExtentFromFirstOf(rBracketToken, genericArguments.LastOrDefault(), firstToken)), + openGenericType, + genericArguments); + + Token token = PeekToken(); if (token.Kind == TokenKind.LBracket) { SkipToken(); @@ -7703,6 +7713,8 @@ private ExpressionAst MemberAccessRule(ExpressionAst targetExpr, Token operatorT // On entry, we've verified that operatorToken is not preceded by whitespace. CommandElementAst member = MemberNameRule(); + List genericTypeArguments = null; + Token rBracket = null; if (member == null) { @@ -7715,27 +7727,96 @@ private ExpressionAst MemberAccessRule(ExpressionAst targetExpr, Token operatorT member = GetSingleCommandArgument(CommandArgumentContext.CommandArgument) ?? new ErrorExpressionAst(ExtentOf(targetExpr, operatorToken)); } - else + // Member name may be an incomplete token like `$a.$(Command-Name`; do not look for generic args or + // invocation token(s) if the member name token is recognisably incomplete. + else if (_ungotToken == null) { + genericTypeArguments = GenericMethodTypeArgumentsRule(out rBracket); Token lParen = NextInvokeMemberToken(); + if (lParen != null) { + // Fall through to the last available token expected in a member name for calculating the expected + // position of the lParen. This accounts for things like `[Array]::Empty[int[]()` so that we don't + // get unhandled behaviour when input is expected but incomplete. + int endColumnNumber = rBracket?.Extent?.EndColumnNumber + ?? genericTypeArguments?.LastOrDefault()?.Extent?.EndColumnNumber + ?? member.Extent.EndColumnNumber; + Diagnostics.Assert(lParen.Kind == TokenKind.LParen || lParen.Kind == TokenKind.LCurly, "token kind incorrect"); - Diagnostics.Assert(member.Extent.EndColumnNumber == lParen.Extent.StartColumnNumber, - "member and paren must be adjacent"); - return MemberInvokeRule(targetExpr, lParen, operatorToken, member); + Diagnostics.Assert( + endColumnNumber == lParen.Extent.StartColumnNumber, + "member and paren must be adjacent when the method is not generic"); + return MemberInvokeRule(targetExpr, lParen, operatorToken, member, genericTypeArguments); } } return new MemberExpressionAst( - ExtentOf(targetExpr, member), - targetExpr, - member, - @static: operatorToken.Kind == TokenKind.ColonColon, - nullConditional: operatorToken.Kind == TokenKind.QuestionDot); + ExtentOf(targetExpr, member), + targetExpr, + member, + @static: operatorToken.Kind == TokenKind.ColonColon, + nullConditional: operatorToken.Kind == TokenKind.QuestionDot, + genericTypeArguments); + } + + private List GenericMethodTypeArgumentsRule(out Token rBracketToken) + { + List genericTypes = null; + + int resyncIndex = _tokenizer.GetRestorePoint(); + Token lBracket = NextToken(); + rBracketToken = null; + + if (lBracket.Kind == TokenKind.LBracket) + { + // This is either a member expression with generic type arguments, or some sort of collection index + // on a property. + var oldTokenizerMode = _tokenizer.Mode; + try + { + // Switch to typename mode to avoid aggressive argument tokenization. + SetTokenizerMode(TokenizerMode.TypeName); + + SkipNewlines(); + Token firstToken = NextToken(); + if (firstToken.Kind != TokenKind.Number + && (firstToken.Kind == TokenKind.Identifier || firstToken.Kind == TokenKind.LBracket)) + { + resyncIndex = -1; + genericTypes = GenericArgumentsRule(firstToken, false, out rBracketToken); + + if (rBracketToken.Kind != TokenKind.RBracket) + { + UngetToken(rBracketToken); + ReportIncompleteInput( + Before(rBracketToken), + nameof(ParserStrings.EndSquareBracketExpectedAtEndOfType), + ParserStrings.EndSquareBracketExpectedAtEndOfType); + rBracketToken = null; + } + } + } + finally + { + SetTokenizerMode(oldTokenizerMode); + } + } + + if (resyncIndex > 0) + { + Resync(resyncIndex); + } + + return genericTypes; } - private ExpressionAst MemberInvokeRule(ExpressionAst targetExpr, Token lBracket, Token operatorToken, CommandElementAst member) + private ExpressionAst MemberInvokeRule( + ExpressionAst targetExpr, + Token lBracket, + Token operatorToken, + CommandElementAst member, + IEnumerable genericTypes) { // G invocation-expression: target-expression passed as a parameter. lBracket can be '(' or '{'. // G target-expression member-name invoke-param-list @@ -7766,7 +7847,8 @@ private ExpressionAst MemberInvokeRule(ExpressionAst targetExpr, Token lBracket, member, arguments, operatorToken.Kind == TokenKind.ColonColon, - operatorToken.Kind == TokenKind.QuestionDot); + operatorToken.Kind == TokenKind.QuestionDot, + genericTypes); } private List InvokeParamParenListRule(Token lParen, out IScriptExtent lastExtent) diff --git a/src/System.Management.Automation/engine/parser/ast.cs b/src/System.Management.Automation/engine/parser/ast.cs index 3e1f9ce60a5..8c506888bca 100644 --- a/src/System.Management.Automation/engine/parser/ast.cs +++ b/src/System.Management.Automation/engine/parser/ast.cs @@ -7873,10 +7873,16 @@ public class MemberExpressionAst : ExpressionAst, ISupportsAssignment /// True if the '::' operator was used, false if '.' is used. /// True if the member access is for a static member, using '::', false if accessing a member on an instance using '.'. /// + /// The generic type arguments passed to the member. /// /// If , , or is null. /// - public MemberExpressionAst(IScriptExtent extent, ExpressionAst expression, CommandElementAst member, bool @static) + public MemberExpressionAst( + IScriptExtent extent, + ExpressionAst expression, + CommandElementAst member, + bool @static, + IEnumerable genericTypes = null) : base(extent) { if (expression == null || member == null) @@ -7889,6 +7895,11 @@ public MemberExpressionAst(IScriptExtent extent, ExpressionAst expression, Comma this.Member = member; SetParent(member); this.Static = @static; + + if (genericTypes?.Any() == true) + { + this.GenericTypeArguments = new ReadOnlyCollection(genericTypes.ToArray()); + } } /// @@ -7902,11 +7913,18 @@ public MemberExpressionAst(IScriptExtent extent, ExpressionAst expression, Comma /// The name or expression naming the member to access. /// True if the '::' operator was used, false if '.' or '?.' is used. /// True if '?.' used. + /// The generic type arguments passed to the member. /// /// If , , or is null. /// - public MemberExpressionAst(IScriptExtent extent, ExpressionAst expression, CommandElementAst member, bool @static, bool nullConditional) - : this(extent, expression, member, @static) + public MemberExpressionAst( + IScriptExtent extent, + ExpressionAst expression, + CommandElementAst member, + bool @static, + bool nullConditional, + IEnumerable genericTypes = null) + : this(extent, expression, member, @static, genericTypes) { this.NullConditional = nullConditional; } @@ -7931,6 +7949,11 @@ public MemberExpressionAst(IScriptExtent extent, ExpressionAst expression, Comma /// public bool NullConditional { get; protected set; } + /// + /// List of generic type arguments passed to this member. + /// + public ReadOnlyCollection GenericTypeArguments { get; private set; } + /// /// Copy the MemberExpressionAst instance. /// @@ -7938,7 +7961,14 @@ public override Ast Copy() { var newExpression = CopyElement(this.Expression); var newMember = CopyElement(this.Member); - return new MemberExpressionAst(this.Extent, newExpression, newMember, this.Static, this.NullConditional); + + return new MemberExpressionAst( + this.Extent, + newExpression, + newMember, + this.Static, + this.NullConditional, + this.GenericTypeArguments); } #region Visitors @@ -7986,11 +8016,18 @@ public class InvokeMemberExpressionAst : MemberExpressionAst, ISupportsAssignmen /// /// True if the invocation is for a static method, using '::', false if invoking a method on an instance using '.'. /// + /// The generic type arguments passed to the method. /// /// If is null. /// - public InvokeMemberExpressionAst(IScriptExtent extent, ExpressionAst expression, CommandElementAst method, IEnumerable arguments, bool @static) - : base(extent, expression, method, @static) + public InvokeMemberExpressionAst( + IScriptExtent extent, + ExpressionAst expression, + CommandElementAst method, + IEnumerable arguments, + bool @static, + IEnumerable genericTypes = null) + : base(extent, expression, method, @static, genericTypes) { if (arguments != null && arguments.Any()) { @@ -8013,11 +8050,19 @@ public InvokeMemberExpressionAst(IScriptExtent extent, ExpressionAst expression, /// True if the invocation is for a static method, using '::', false if invoking a method on an instance using '.' or '?.'. /// /// True if the operator used is '?.'. + /// The generic type arguments passed to the method. /// /// If is null. /// - public InvokeMemberExpressionAst(IScriptExtent extent, ExpressionAst expression, CommandElementAst method, IEnumerable arguments, bool @static, bool nullConditional) - : this(extent, expression, method, arguments, @static) + public InvokeMemberExpressionAst( + IScriptExtent extent, + ExpressionAst expression, + CommandElementAst method, + IEnumerable arguments, + bool @static, + bool nullConditional, + IEnumerable genericTypes = null) + : this(extent, expression, method, arguments, @static, genericTypes) { this.NullConditional = nullConditional; } @@ -8035,7 +8080,15 @@ 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, this.NullConditional); + + return new InvokeMemberExpressionAst( + this.Extent, + newExpression, + newMethod, + newArguments, + this.Static, + this.NullConditional, + this.GenericTypeArguments); } #region Visitors From 7ced98cf9fd2391170f00472ce88497590990478 Mon Sep 17 00:00:00 2001 From: Joel Sallow <32407840+vexx32@users.noreply.github.com> Date: Wed, 22 Apr 2020 01:02:56 -0400 Subject: [PATCH 02/16] :sparkles: Add member binding logic - Adds member binding logic which works via invocation constraints - Ensures that only an appropriate generic method can be bound if generic type arguments are specified. --- .../engine/CoreAdapter.cs | 37 ++++++++++++---- .../engine/MshMemberInfo.cs | 40 ++++++++++++++++-- .../engine/parser/Compiler.cs | 42 ++++++++++++------- 3 files changed, 94 insertions(+), 25 deletions(-) diff --git a/src/System.Management.Automation/engine/CoreAdapter.cs b/src/System.Management.Automation/engine/CoreAdapter.cs index bf9775b35ef..fa8d6015d9e 100644 --- a/src/System.Management.Automation/engine/CoreAdapter.cs +++ b/src/System.Management.Automation/engine/CoreAdapter.cs @@ -1394,32 +1394,37 @@ private static MethodInformation FindBestMethodImpl( // be turned into an array. // We also skip the optimization if the number of arguments and parameters is different // so we let the loop deal with possible optional parameters. - if ((methods.Length == 1) && - (!methods[0].hasVarArgs) && - (!methods[0].isGeneric) && - (methods[0].method == null || !(methods[0].method.DeclaringType.IsGenericTypeDefinition)) && + if (methods.Length == 1 + && !methods[0].hasVarArgs // generic methods need to be double checked in a loop below - generic methods can be rejected if type inference fails - (methods[0].parameters.Length == arguments.Length)) + && !methods[0].isGeneric + && (methods[0].method == null || !(methods[0].method.DeclaringType.IsGenericTypeDefinition)) + && methods[0].parameters.Length == arguments.Length) { return methods[0]; } Type[] argumentTypes = arguments.Select(EffectiveArgumentType).ToArray(); + Type[] genericParameters = invocationConstraints?.GenericTypeParameters ?? Array.Empty(); List candidates = new List(); for (int i = 0; i < methods.Length; i++) { MethodInformation method = methods[i]; - if (method.method != null && method.method.DeclaringType.IsGenericTypeDefinition) + if (method.method != null && method.method.DeclaringType.IsGenericTypeDefinition + || !method.isGeneric && genericParameters.Length > 0) { - continue; // skip methods defined by an *open* generic type + // If method is defined by an *open* generic type, or + // if generic parameters were provided and this method isn't generic, skip it. + continue; } if (method.isGeneric) { Type[] argumentTypesForTypeInference = new Type[argumentTypes.Length]; Array.Copy(argumentTypes, argumentTypesForTypeInference, argumentTypes.Length); - if (invocationConstraints != null && invocationConstraints.ParameterTypes != null) + + if (invocationConstraints?.ParameterTypes != null) { int parameterIndex = 0; foreach (Type typeConstraintFromCallSite in invocationConstraints.ParameterTypes) @@ -1433,6 +1438,22 @@ private static MethodInformation FindBestMethodImpl( } } + if (genericParameters?.Length > 0 && method.method is MethodInfo originalMethod) + { + try + { + method = new MethodInformation( + originalMethod.MakeGenericMethod(genericParameters), + parametersToIgnore: 0); + } + catch (ArgumentException) + { + // Just skip this possibility if the generic type parameters can't be used to make + // a valid generic method here. + continue; + } + } + method = TypeInference.Infer(method, argumentTypesForTypeInference); if (method == null) { diff --git a/src/System.Management.Automation/engine/MshMemberInfo.cs b/src/System.Management.Automation/engine/MshMemberInfo.cs index 73ee243f6e8..4d58863a9f7 100644 --- a/src/System.Management.Automation/engine/MshMemberInfo.cs +++ b/src/System.Management.Automation/engine/MshMemberInfo.cs @@ -1909,9 +1909,18 @@ internal class PSMethodInvocationConstraints internal PSMethodInvocationConstraints( Type methodTargetType, Type[] parameterTypes) + : this(methodTargetType, genericTypeParameters: null, parameterTypes) { - this.MethodTargetType = methodTargetType; + } + + internal PSMethodInvocationConstraints( + Type methodTargetType, + Type[] genericTypeParameters, + Type[] parameterTypes) + { + MethodTargetType = methodTargetType; _parameterTypes = parameterTypes; + GenericTypeParameters = genericTypeParameters?.ToArray() ?? Array.Empty(); } /// @@ -1926,6 +1935,11 @@ internal PSMethodInvocationConstraints( private readonly Type[] _parameterTypes; + /// + /// Gets the generic type parameters for the method invocation. + /// + public Type[] GenericTypeParameters { get; private set; } + internal static bool EqualsForCollection(ICollection xs, ICollection ys) { if (xs == null) @@ -1946,8 +1960,6 @@ internal static bool EqualsForCollection(ICollection xs, ICollection ys return xs.SequenceEqual(ys); } - // TODO: IEnumerable genericTypeParameters { get; private set; } - public bool Equals(PSMethodInvocationConstraints other) { if (other is null) @@ -1970,6 +1982,11 @@ public bool Equals(PSMethodInvocationConstraints other) return false; } + if (!EqualsForCollection(GenericTypeParameters, other.GenericTypeParameters)) + { + return false; + } + return true; } @@ -2002,6 +2019,7 @@ public override int GetHashCode() result = result * 397 + (MethodTargetType != null ? MethodTargetType.GetHashCode() : 0); result = result * 397 + ParameterTypes.SequenceGetHashCode(); + result = result * 397 + GenericTypeParameters.SequenceGetHashCode(); return result; } @@ -2018,6 +2036,22 @@ public override string ToString() separator = " "; } + if (GenericTypeParameters.Length != 0) + { + sb.Append(separator); + sb.Append("genericTypeParams: "); + + separator = string.Empty; + foreach (Type parameter in GenericTypeParameters) + { + sb.Append(separator); + sb.Append(ToStringCodeMethods.Type(parameter, dropNamespaces: true)); + separator = ", "; + } + + separator = " "; + } + if (_parameterTypes != null) { sb.Append(separator); diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index 14057f05f68..fc9c1ba1627 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -1172,24 +1172,30 @@ internal static Type GetTypeConstraintForMethodResolution(ExpressionAst expr) return firstConvert?.Type.TypeName.GetReflectionType(); } - internal static PSMethodInvocationConstraints CombineTypeConstraintForMethodResolution(Type targetType, Type argType) + internal static PSMethodInvocationConstraints CombineTypeConstraintForMethodResolution( + Type targetType, + Type argType, + Type[] genericArguments = null) { - if (targetType == null && argType == null) + if (targetType == null && argType == null && !(genericArguments?.Length > 0)) { return null; } - return new PSMethodInvocationConstraints(targetType, new[] { argType }); + return new PSMethodInvocationConstraints(targetType, genericArguments, new[] { argType }); } - internal static PSMethodInvocationConstraints CombineTypeConstraintForMethodResolution(Type targetType, Type[] argTypes) + internal static PSMethodInvocationConstraints CombineTypeConstraintForMethodResolution( + Type targetType, + Type[] argTypes, + Type[] genericArguments = null) { - if (targetType == null && (argTypes == null || argTypes.Length == 0)) + if (targetType == null && !(argTypes?.Length > 0) && !(genericArguments?.Length > 0)) { return null; } - return new PSMethodInvocationConstraints(targetType, argTypes); + return new PSMethodInvocationConstraints(targetType, genericArguments, argTypes); } internal static Expression ConvertValue(TypeConstraintAst typeConstraint, Expression expr) @@ -6310,18 +6316,28 @@ public object VisitMemberExpression(MemberExpressionAst memberExpressionAst) internal static PSMethodInvocationConstraints GetInvokeMemberConstraints(InvokeMemberExpressionAst invokeMemberExpressionAst) { - var arguments = invokeMemberExpressionAst.Arguments; + var arguments = invokeMemberExpressionAst.Arguments + ?.Select(Compiler.GetTypeConstraintForMethodResolution) + .ToArray(); var targetTypeConstraint = GetTypeConstraintForMethodResolution(invokeMemberExpressionAst.Expression); - return CombineTypeConstraintForMethodResolution( - targetTypeConstraint, - arguments?.Select(Compiler.GetTypeConstraintForMethodResolution).ToArray()); + var genericArguments = invokeMemberExpressionAst.GenericTypeArguments + ?.Select(t => t.GetReflectionType()) + .ToArray(); + + return CombineTypeConstraintForMethodResolution(targetTypeConstraint, arguments, genericArguments); } internal static PSMethodInvocationConstraints GetInvokeMemberConstraints(BaseCtorInvokeMemberExpressionAst invokeMemberExpressionAst) { Type targetTypeConstraint = null; - var arguments = invokeMemberExpressionAst.Arguments; + var arguments = invokeMemberExpressionAst.Arguments + ?.Select(Compiler.GetTypeConstraintForMethodResolution) + .ToArray(); TypeDefinitionAst typeDefinitionAst = Ast.GetAncestorTypeDefinitionAst(invokeMemberExpressionAst); + var genericArguments = invokeMemberExpressionAst.GenericTypeArguments + ?.Select(t => t.GetReflectionType()) + .ToArray(); + if (typeDefinitionAst != null) { targetTypeConstraint = (typeDefinitionAst as TypeDefinitionAst).Type.BaseType; @@ -6331,9 +6347,7 @@ internal static PSMethodInvocationConstraints GetInvokeMemberConstraints(BaseCto Diagnostics.Assert(false, "BaseCtorInvokeMemberExpressionAst must be used only inside TypeDefinitionAst"); } - return CombineTypeConstraintForMethodResolution( - targetTypeConstraint, - arguments?.Select(Compiler.GetTypeConstraintForMethodResolution).ToArray()); + return CombineTypeConstraintForMethodResolution(targetTypeConstraint, arguments, genericArguments); } internal Expression InvokeMember( From b0cf91d950d0326f252580c88a0a38ad3d45e051 Mon Sep 17 00:00:00 2001 From: Joel Sallow <32407840+vexx32@users.noreply.github.com> Date: Wed, 22 Apr 2020 01:05:53 -0400 Subject: [PATCH 03/16] :art: Add tab completion support - Enables tab completion of types for generic method args --- .../engine/CommandCompletion/CompletionAnalysis.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs index 76be6418805..528e57b8333 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs @@ -2102,6 +2102,12 @@ private List GetResultForIdentifier(CompletionContext completi result = CompletionCompleters.CompleteCommandArgument(completionContext); replacementIndex = completionContext.ReplacementIndex; replacementLength = completionContext.ReplacementLength; + + if (result.Count == 0 && completionContext.TokenAtCursor.TokenFlags.HasFlag(TokenFlags.TypeName)) + { + result = CompletionCompleters.CompleteType(completionContext.TokenAtCursor.Text).ToList(); + } + return result; } From 4d70f8c6ffd445ce868cdf4a4038fb57a22b2ff6 Mon Sep 17 00:00:00 2001 From: Joel Sallow <32407840+vexx32@users.noreply.github.com> Date: Wed, 22 Apr 2020 09:02:39 -0400 Subject: [PATCH 04/16] :wrench: Narrow completion to MemberExpressionAst --- .../engine/CommandCompletion/CompletionAnalysis.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs index 528e57b8333..4e8a526757f 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs @@ -2103,7 +2103,9 @@ private List GetResultForIdentifier(CompletionContext completi replacementIndex = completionContext.ReplacementIndex; replacementLength = completionContext.ReplacementLength; - if (result.Count == 0 && completionContext.TokenAtCursor.TokenFlags.HasFlag(TokenFlags.TypeName)) + if (result.Count == 0 + && lastAst?.Find(a => a is MemberExpressionAst, searchNestedScriptBlocks: false) != null + && completionContext.TokenAtCursor.TokenFlags.HasFlag(TokenFlags.TypeName)) { result = CompletionCompleters.CompleteType(completionContext.TokenAtCursor.Text).ToList(); } From 2c79dc15b376cb1ae4067a7631a4c822ea6e7cc6 Mon Sep 17 00:00:00 2001 From: Joel Sallow <32407840+vexx32@users.noreply.github.com> Date: Wed, 22 Apr 2020 01:06:38 -0400 Subject: [PATCH 05/16] :white_check_mark: Add tests for generic methods --- .../Parser/MethodInvocation.Tests.ps1 | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/test/powershell/Language/Parser/MethodInvocation.Tests.ps1 b/test/powershell/Language/Parser/MethodInvocation.Tests.ps1 index f28b6801389..02b7b9d290d 100644 --- a/test/powershell/Language/Parser/MethodInvocation.Tests.ps1 +++ b/test/powershell/Language/Parser/MethodInvocation.Tests.ps1 @@ -81,3 +81,82 @@ namespace MSFT_716893 ([MSFT_716893.IInterface1]$proxy).BaseOperation(22) | Should -Be "3 - 22" } } + +Describe 'Generic Method invocation' { + + BeforeAll { + $EmptyArrayCases = @( + @{ + Script = '[Array]::Empty[string]()' + ExpectedType = [string[]] + } + @{ + Script = '[Array]::Empty[System.Collections.Generic.Dictionary[System.Numerics.BigInteger, System.Collections.Generic.List[string[,]]]]()' + ExpectedType = [System.Collections.Generic.Dictionary[System.Numerics.BigInteger, System.Collections.Generic.List[string[, ]]][]] + } + @{ + Script = '[Array]::$("Empty")[[System.Collections.Generic.Dictionary[[System.String, System.Private.CoreLib],[System.Numerics.BigInteger, System.Runtime.Numerics]], System.Private.CoreLib]]()' + ExpectedType = [System.Collections.Generic.Dictionary`2[[System.String, System.Private.CoreLib], [System.Numerics.BigInteger, System.Runtime.Numerics]][], System.Private.CoreLib] + } + ) + } + + It 'does not throw a parse error for "