diff --git a/src/System.Management.Automation/engine/CoreAdapter.cs b/src/System.Management.Automation/engine/CoreAdapter.cs index b9e8979852..c685fa4613 100644 --- a/src/System.Management.Automation/engine/CoreAdapter.cs +++ b/src/System.Management.Automation/engine/CoreAdapter.cs @@ -1377,6 +1377,27 @@ namespace System.Management.Automation return methodInfo; } + private static Type[] ResolveGenericTypeParameters(object[] genericTypeParameters) + { + if (genericTypeParameters is null || genericTypeParameters.Length == 0) + { + return null; + } + + Type[] genericParamTypes = new Type[genericTypeParameters.Length]; + for (int i = 0; i < genericTypeParameters.Length; i++) + { + genericParamTypes[i] = genericTypeParameters[i] switch + { + Type paramType => paramType, + ITypeName paramTypeName => TypeOps.ResolveTypeName(paramTypeName, paramTypeName.Extent), + _ => throw new ArgumentException("Unexpected value"), + }; + } + + return genericParamTypes; + } + private static MethodInformation FindBestMethodImpl( MethodInformation[] methods, PSMethodInvocationConstraints invocationConstraints, @@ -1404,15 +1425,15 @@ namespace System.Management.Automation return methods[0]; } - Type[] argumentTypes = arguments.Select(EffectiveArgumentType).ToArray(); - Type[] genericParameters = invocationConstraints?.GenericTypeParameters ?? Array.Empty(); - List candidates = new List(); + Type[] genericParamTypes = ResolveGenericTypeParameters(invocationConstraints?.GenericTypeParameters); + var candidates = new List(); + for (int i = 0; i < methods.Length; i++) { MethodInformation methodInfo = methods[i]; if (methodInfo.method?.DeclaringType.IsGenericTypeDefinition == true - || (!methodInfo.isGeneric && genericParameters.Length > 0)) + || (!methodInfo.isGeneric && genericParamTypes is not null)) { // If method is defined by an *open* generic type, or // if generic parameters were provided and this method isn't generic, skip it. @@ -1421,29 +1442,16 @@ namespace System.Management.Automation if (methodInfo.isGeneric) { - Type[] argumentTypesForTypeInference = new Type[argumentTypes.Length]; - Array.Copy(argumentTypes, argumentTypesForTypeInference, argumentTypes.Length); - - if (invocationConstraints?.ParameterTypes is not null) - { - int parameterIndex = 0; - foreach (Type typeConstraintFromCallSite in invocationConstraints.ParameterTypes) - { - if (typeConstraintFromCallSite != null) - { - argumentTypesForTypeInference[parameterIndex] = typeConstraintFromCallSite; - } - - parameterIndex++; - } - } - - if (genericParameters.Length > 0 && methodInfo.method is MethodInfo originalMethod) + if (genericParamTypes is not null) { try { + // This cast is safe, because + // 1. Only ConstructorInfo and MethodInfo derive from MethodBase + // 2. ConstructorInfo.IsGenericMethod is always false + var originalMethod = (MethodInfo)methodInfo.method; methodInfo = new MethodInformation( - originalMethod.MakeGenericMethod(genericParameters), + originalMethod.MakeGenericMethod(genericParamTypes), parametersToIgnore: 0); } catch (ArgumentException) @@ -1453,12 +1461,29 @@ namespace System.Management.Automation continue; } } - - methodInfo = TypeInference.Infer(methodInfo, argumentTypesForTypeInference); - if (methodInfo is null) + else { - // Skip generic methods for which we cannot infer type arguments - continue; + // Infer the generic method when generic parameter types are not specified. + Type[] argumentTypes = arguments.Select(EffectiveArgumentType).ToArray(); + Type[] paramConstraintTypes = invocationConstraints?.ParameterTypes; + + if (paramConstraintTypes is not null) + { + for (int k = 0; k < paramConstraintTypes.Length; k++) + { + if (paramConstraintTypes[k] is not null) + { + argumentTypes[k] = paramConstraintTypes[k]; + } + } + } + + methodInfo = TypeInference.Infer(methodInfo, argumentTypes); + if (methodInfo is null) + { + // Skip generic methods for which we cannot infer type arguments + continue; + } } } @@ -1602,7 +1627,7 @@ namespace System.Management.Automation if (candidates.Count == 0) { - if ((methods.Length > 0) && (methods.All(static m => m.method != null && m.method.DeclaringType.IsGenericTypeDefinition && m.method.IsStatic))) + if (methods.Length > 0 && methods.All(static m => m.method != null && m.method.DeclaringType.IsGenericTypeDefinition && m.method.IsStatic)) { errorId = "CannotInvokeStaticMethodOnUninstantiatedGenericType"; errorMsg = string.Format( @@ -1611,19 +1636,13 @@ namespace System.Management.Automation methods[0].method.DeclaringType.FullName); return null; } - else if (genericParameters.Length != 0 && genericParameters.Contains(null)) - { - errorId = "TypeNotFoundForGenericMethod"; - errorMsg = ExtendedTypeSystem.MethodGenericArgumentTypeNotFoundException; - return null; - } - else if (genericParameters.Length != 0) + else if (genericParamTypes is not null) { errorId = "MethodCountCouldNotFindBestGeneric"; errorMsg = string.Format( ExtendedTypeSystem.MethodGenericArgumentCountException, methods[0].method.Name, - genericParameters.Length, + genericParamTypes.Length, arguments.Length); return null; } diff --git a/src/System.Management.Automation/engine/MshMemberInfo.cs b/src/System.Management.Automation/engine/MshMemberInfo.cs index ef6d185a60..4856b08fb7 100644 --- a/src/System.Management.Automation/engine/MshMemberInfo.cs +++ b/src/System.Management.Automation/engine/MshMemberInfo.cs @@ -1909,17 +1909,17 @@ namespace System.Management.Automation internal PSMethodInvocationConstraints( Type methodTargetType, Type[] parameterTypes) - : this(methodTargetType, genericTypeParameters: null, parameterTypes) + : this(methodTargetType, parameterTypes, genericTypeParameters: null) { } internal PSMethodInvocationConstraints( Type methodTargetType, - Type[] genericTypeParameters, - Type[] parameterTypes) + Type[] parameterTypes, + object[] genericTypeParameters) { MethodTargetType = methodTargetType; - _parameterTypes = parameterTypes; + ParameterTypes = parameterTypes; GenericTypeParameters = genericTypeParameters; } @@ -1931,14 +1931,12 @@ namespace System.Management.Automation /// /// If then there are no constraints /// - public IEnumerable ParameterTypes => _parameterTypes; - - private readonly Type[] _parameterTypes; + public Type[] ParameterTypes { get; } /// /// Gets the generic type parameters for the method invocation. /// - public Type[] GenericTypeParameters { get; } + public object[] GenericTypeParameters { get; } internal static bool EqualsForCollection(ICollection xs, ICollection ys) { @@ -1977,7 +1975,7 @@ namespace System.Management.Automation return false; } - if (!EqualsForCollection(_parameterTypes, other._parameterTypes)) + if (!EqualsForCollection(ParameterTypes, other.ParameterTypes)) { return false; } @@ -2017,35 +2015,47 @@ namespace System.Management.Automation { StringBuilder sb = new StringBuilder(); string separator = string.Empty; - if (MethodTargetType != null) + if (MethodTargetType is not null) { sb.Append("this: "); sb.Append(ToStringCodeMethods.Type(MethodTargetType, dropNamespaces: true)); separator = " "; } - if (GenericTypeParameters != null) + if (GenericTypeParameters is not null) { sb.Append(separator); sb.Append("genericTypeParams: "); separator = string.Empty; - foreach (Type parameter in GenericTypeParameters) + foreach (object parameter in GenericTypeParameters) { sb.Append(separator); - sb.Append(ToStringCodeMethods.Type(parameter, dropNamespaces: true)); + + switch (parameter) + { + case Type paramType: + sb.Append(ToStringCodeMethods.Type(paramType, dropNamespaces: true)); + break; + case ITypeName paramTypeName: + sb.Append(paramTypeName.ToString()); + break; + default: + throw new ArgumentException("Unexpected value"); + } + separator = ", "; } separator = " "; } - if (_parameterTypes != null) + if (ParameterTypes is not null) { sb.Append(separator); sb.Append("args: "); separator = string.Empty; - foreach (var p in _parameterTypes) + foreach (var p in ParameterTypes) { sb.Append(separator); sb.Append(ToStringCodeMethods.Type(p, dropNamespaces: true)); diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index 5f7c54d00c..0a68cb7951 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -1174,25 +1174,20 @@ namespace System.Management.Automation.Language return firstConvert?.Type.TypeName.GetReflectionType(); } - internal static PSMethodInvocationConstraints CombineTypeConstraintForMethodResolution( - Type targetType, - Type argType, - Type[] genericArguments = null) + internal static PSMethodInvocationConstraints CombineTypeConstraintForMethodResolution(Type targetType, Type argType) { - if (targetType is null - && argType is null - && (genericArguments is null || genericArguments.Length == 0)) + if (targetType is null && argType is null) { return null; } - return new PSMethodInvocationConstraints(targetType, genericArguments, new[] { argType }); + return new PSMethodInvocationConstraints(targetType, new[] { argType }); } internal static PSMethodInvocationConstraints CombineTypeConstraintForMethodResolution( Type targetType, Type[] argTypes, - Type[] genericArguments = null) + object[] genericArguments = null) { if (targetType is null && (argTypes is null || argTypes.Length == 0) @@ -1201,7 +1196,7 @@ namespace System.Management.Automation.Language return null; } - return new PSMethodInvocationConstraints(targetType, genericArguments, argTypes); + return new PSMethodInvocationConstraints(targetType, argTypes, genericArguments); } internal static Expression ConvertValue(TypeConstraintAst typeConstraint, Expression expr) @@ -6362,13 +6357,14 @@ namespace System.Management.Automation.Language var targetTypeConstraint = GetTypeConstraintForMethodResolution(invokeMemberExpressionAst.Expression); ReadOnlyCollection genericArguments = invokeMemberExpressionAst.GenericTypeArguments; - Type[] genericTypeArguments = null; + object[] genericTypeArguments = null; if (genericArguments is not null) { - genericTypeArguments = new Type[genericArguments.Count]; + genericTypeArguments = new object[genericArguments.Count]; for (var i = 0; i < genericArguments.Count; i++) { - genericTypeArguments[i] = genericArguments[i].GetReflectionType(); + Type type = genericArguments[i].GetReflectionType(); + genericTypeArguments[i] = (object)type ?? genericArguments[i]; } } diff --git a/src/System.Management.Automation/resources/ExtendedTypeSystem.resx b/src/System.Management.Automation/resources/ExtendedTypeSystem.resx index d617cbf7a3..86b3f82632 100644 --- a/src/System.Management.Automation/resources/ExtendedTypeSystem.resx +++ b/src/System.Management.Automation/resources/ExtendedTypeSystem.resx @@ -159,9 +159,6 @@ Could not find a suitable generic method overload for "{0}" with "{1}" type parameters, and the argument count: "{2}". - - One or more of the generic type parameters provided for the method "{0}" refers to a type which cannot be found. - Multiple ambiguous overloads found for "{0}" and the argument count: "{1}". diff --git a/test/powershell/Language/Parser/MethodInvocation.Tests.ps1 b/test/powershell/Language/Parser/MethodInvocation.Tests.ps1 index e6a9189117..e60f8a2b14 100644 --- a/test/powershell/Language/Parser/MethodInvocation.Tests.ps1 +++ b/test/powershell/Language/Parser/MethodInvocation.Tests.ps1 @@ -1,10 +1,216 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -if ( $IsCoreCLR ) { - return + +Describe 'Generic Method invocation' -Tags 'CI' { + + 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`2[[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] + } + ) + + $ExpectedParseErrors = @( + @{ + Script = '$object.Method[incompl' + ExpectedErrors = @('EndSquareBracketExpectedAtEndOfType') + ErrorCount = 1 + } + @{ + Script = '[type]::Member[incompl' + ExpectedErrors = @('EndSquareBracketExpectedAtEndOfType') + ErrorCount = 1 + } + @{ + Script = '$object.Method[Type1[Type2' + ExpectedErrors = @('EndSquareBracketExpectedAtEndOfAttribute','EndSquareBracketExpectedAtEndOfType') + ErrorCount = 2 + } + @{ + Script = '[array]::empty[type]]()' + ExpectedErrors = @('UnexpectedToken', 'ExpectedExpression') + ErrorCount = 2 + } + @{ + Script = '$object.Method[type,]()' + ExpectedErrors = @('MissingTypename') + ErrorCount = 1 + } + @{ + Script = '$object.Method[]()' + ExpectedErrors = @('MissingArrayIndexExpression', 'UnexpectedToken', 'ExpectedExpression') + ErrorCount = 3 + } + @{ + Script = '$object.Method[,]()' + ExpectedErrors = @('MissingExpressionAfterOperator', 'UnexpectedToken', 'ExpectedExpression') + ErrorCount = 3 + } + @{ + Script = '$object.Method[,type]()' + ExpectedErrors = @('MissingExpressionAfterOperator', 'UnexpectedToken', 'ExpectedExpression') + ErrorCount = 3 + } + @{ + Script = '$object.Method[type()' + ExpectedErrors = @('EndSquareBracketExpectedAtEndOfType', 'UnexpectedToken', 'ExpectedExpression') + ErrorCount = 3 + } + @{ + Script = '$object.Method[type)' + ExpectedErrors = @('EndSquareBracketExpectedAtEndOfType', 'UnexpectedToken') + ErrorCount = 2 + } + ) + } + + It 'does not throw a parse error for "