mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Add a couple fixes to generic method invocation (#16822)
This commit is contained in:
@@ -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<Type>();
|
||||
List<OverloadCandidate> candidates = new List<OverloadCandidate>();
|
||||
Type[] genericParamTypes = ResolveGenericTypeParameters(invocationConstraints?.GenericTypeParameters);
|
||||
var candidates = new List<OverloadCandidate>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
/// <remarks>
|
||||
/// If <see langword="null"/> then there are no constraints
|
||||
/// </remarks>
|
||||
public IEnumerable<Type> ParameterTypes => _parameterTypes;
|
||||
|
||||
private readonly Type[] _parameterTypes;
|
||||
public Type[] ParameterTypes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the generic type parameters for the method invocation.
|
||||
/// </summary>
|
||||
public Type[] GenericTypeParameters { get; }
|
||||
public object[] GenericTypeParameters { get; }
|
||||
|
||||
internal static bool EqualsForCollection<T>(ICollection<T> xs, ICollection<T> 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));
|
||||
|
||||
@@ -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<ITypeName> 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];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -159,9 +159,6 @@
|
||||
<data name="MethodGenericArgumentCountException" xml:space="preserve">
|
||||
<value>Could not find a suitable generic method overload for "{0}" with "{1}" type parameters, and the argument count: "{2}".</value>
|
||||
</data>
|
||||
<data name="MethodGenericArgumentTypeNotFoundException" xml:space="preserve">
|
||||
<value>One or more of the generic type parameters provided for the method "{0}" refers to a type which cannot be found.</value>
|
||||
</data>
|
||||
<data name="MethodAmbiguousException" xml:space="preserve">
|
||||
<value>Multiple ambiguous overloads found for "{0}" and the argument count: "{1}".</value>
|
||||
</data>
|
||||
|
||||
@@ -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 "<Script>"' -TestCases $EmptyArrayCases {
|
||||
param($Script)
|
||||
|
||||
{ [scriptblock]::Create($script) } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'reports a parse error for "<Script>"' -TestCases $ExpectedParseErrors {
|
||||
param($Script, $ExpectedErrors, $ErrorCount)
|
||||
|
||||
$parseErrors = $null
|
||||
[System.Management.Automation.Language.Parser]::ParseInput($Script, [ref]$null, [ref]$parseErrors)
|
||||
|
||||
$parseErrors.Count | Should -Be $ErrorCount
|
||||
|
||||
for ($i = 0; $i -lt $ErrorCount; $i++) {
|
||||
$parseErrors[$i].ErrorId | Should -BeExactly $ExpectedErrors[$i]
|
||||
}
|
||||
}
|
||||
|
||||
It 'can call a generic method "<Script>" with no arguments' -TestCases $EmptyArrayCases {
|
||||
param($Script, $ExpectedType)
|
||||
|
||||
$command = "`$r = $Script; `$r.GetType(); `$r.Length"
|
||||
$result = Invoke-Expression -Command $command
|
||||
$result[0] | Should -Be $ExpectedType
|
||||
$result[1] | Should -Be 0
|
||||
}
|
||||
|
||||
It 'can call generic instance methods' {
|
||||
$dictionary = [System.Collections.Concurrent.ConcurrentDictionary[string, int]]::new()
|
||||
|
||||
$addEntryScript = {
|
||||
param($key, $float)
|
||||
|
||||
if ($float -gt 0.5) {
|
||||
return 10
|
||||
}
|
||||
else {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
$updateEntryScript = {
|
||||
param($key, $currentValue, $float)
|
||||
|
||||
if ($currentValue / $float -gt 2) {
|
||||
return 5
|
||||
}
|
||||
else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
$FloatValue = 0.4
|
||||
$Key = 'Test'
|
||||
|
||||
# Add entry
|
||||
$dictionary.AddOrUpdate[float]($Key, $addEntryScript, $updateEntryScript, $FloatValue)
|
||||
$dictionary.$Key | Should -Be 1
|
||||
|
||||
# Update entry
|
||||
$dictionary.AddOrUpdate[float]($Key, $addEntryScript, $updateEntryScript, $FloatValue)
|
||||
$dictionary.$Key | Should -Be 5
|
||||
}
|
||||
|
||||
It 'can call generic static methods with arguments' {
|
||||
[System.Linq.Enumerable]::Select[int, int](
|
||||
[int[]](0..10),
|
||||
[func[int, int]] { $args[0] + 2 }
|
||||
) | Should -Be @(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
|
||||
}
|
||||
|
||||
It 'gives a runtime error if too many type parameters are given' {
|
||||
try {
|
||||
[array]::Empty[string, int]()
|
||||
}
|
||||
catch {
|
||||
$_.FullyQualifiedErrorId | Should -BeExactly 'MethodCountCouldNotFindBestGeneric'
|
||||
}
|
||||
}
|
||||
|
||||
It 'gives a runtime error if a nonexistent type is specified' {
|
||||
try {
|
||||
[array]::Empty[thisdoesnotexist]()
|
||||
}
|
||||
catch {
|
||||
$_.Exception.Message | Should -BeLike "*[thisdoesnotexist]*"
|
||||
$_.FullyQualifiedErrorId | Should -BeExactly 'TypeNotFound'
|
||||
}
|
||||
}
|
||||
|
||||
It 'lists the same method group information with and without type parameters' {
|
||||
$dict = [System.Collections.Concurrent.ConcurrentDictionary[string, int]]::new()
|
||||
$noTypeParams = $dict.AddOrUpdate
|
||||
$typeParams = $dict.AddOrUpdate[string]
|
||||
$extraTypeParams = $dict.AddOrUpdate[string, int]
|
||||
|
||||
$typeParams.OverloadDefinitions | Should -BeExactly $noTypeParams.OverloadDefinitions
|
||||
$extraTypeParams.OverloadDefinitions | Should -BeExactly $typeParams.OverloadDefinitions
|
||||
}
|
||||
|
||||
It 'successfully invokes common Linq generic methods' {
|
||||
[System.Collections.Generic.List[int]]$list = @( 1, 2, 3, 4, 5 )
|
||||
$result = [System.Linq.Enumerable]::Select[int, float](
|
||||
$list,
|
||||
[Func[int, float]]{
|
||||
param($item)
|
||||
[math]::Pow($item, 3)
|
||||
}
|
||||
)
|
||||
|
||||
$result.GetType().Name | Should -BeExactly 'SelectListIterator`2'
|
||||
$typeArgs = $result.GetType().GenericTypeArguments
|
||||
$typeArgs[0] | Should -Be ([int])
|
||||
$typeArgs[1] | Should -Be ([float])
|
||||
|
||||
$resultList = $result.ToList()
|
||||
$resultList.GetType().Name | Should -Be 'List`1'
|
||||
$resultList.GetType().GenericTypeArguments | Should -Be ([float])
|
||||
$resultList | Should -Be @( 1, 8, 27, 64, 125)
|
||||
}
|
||||
|
||||
It 'can use type that is loaded at runtime' {
|
||||
Add-Type -TypeDefinition "public class GenericTypeTest {}"
|
||||
$r = [Array]::Empty[GenericTypeTest]()
|
||||
|
||||
$r.GetType() | Should -Be ([GenericTypeTest[]])
|
||||
$r.Length | Should -Be 0
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Interface inheritance with remoting proxies" -Tags "CI" {
|
||||
|
||||
if ( $IsCoreCLR ) {
|
||||
Write-Verbose -Verbose "Skip this test because it's .NET Framework dependency."
|
||||
return
|
||||
}
|
||||
|
||||
$src = @"
|
||||
using System;
|
||||
using System.ServiceModel;
|
||||
@@ -81,196 +287,3 @@ namespace MSFT_716893
|
||||
([MSFT_716893.IInterface1]$proxy).BaseOperation(22) | Should -Be "3 - 22"
|
||||
}
|
||||
}
|
||||
|
||||
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[[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 = 'UnexpectedToken','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', 'ExprectedExpression'
|
||||
ErrorCount = 3
|
||||
}
|
||||
@{
|
||||
Script = '$object.Method[,type]()'
|
||||
ExpectedErrors = 'MissingExpressionAfterOperator', 'UnexpectedToken', 'ExpectedExpression'
|
||||
ErrorCount = 3
|
||||
}
|
||||
@{
|
||||
Script = '$object.Method[type()'
|
||||
ExpectedErrors = 'UnexpectedToken', 'ExpectedExpression'
|
||||
ErrorCount = 2
|
||||
}
|
||||
@{
|
||||
Script = '$object.Method[type)'
|
||||
ExpectedErrors = 'EndSquareBracketExpectedAtEndOfType', 'UnexpectedToken'
|
||||
ErrorCount = 2
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
It 'does not throw a parse error for "<Script>"' -TestCases $EmptyArrayCases {
|
||||
param($Script)
|
||||
|
||||
{ [scriptblock]::Create($script) } | Should -Not -Throw
|
||||
}
|
||||
|
||||
It 'reports a parse error for "<Script>"' -TestCases $ExpectedParseErrors {
|
||||
param($Script, $ExpectedError, $ErrorCount)
|
||||
|
||||
$parseErrors = $null
|
||||
[System.Management.Automation.Language.Parser]::ParseInput($Script, [ref]$null, [ref]$parseErrors)
|
||||
|
||||
$parseErrors.Count | Should -Be $ErrorCount
|
||||
$parseErrors.ErrorId | Should -BeIn $ExpectedError
|
||||
}
|
||||
|
||||
It 'can call a generic method "<Script>" with no arguments' -TestCases $EmptyArrayCases {
|
||||
param($Script, $ExpectedType)
|
||||
|
||||
$Result = & [scriptblock]::Create($Script)
|
||||
$Result.GetType() | Should -Be $ExpectedType
|
||||
|
||||
$Result.Length | Should -Be 0
|
||||
}
|
||||
|
||||
It 'can call generic instance methods' {
|
||||
$dictionary = [System.Collections.Concurrent.ConcurrentDictionary[string, int]]::new()
|
||||
|
||||
$addEntryScript = {
|
||||
param($key, $float)
|
||||
|
||||
if ($float -gt 0.5) {
|
||||
return 10
|
||||
}
|
||||
else {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
$updateEntryScript = {
|
||||
param($key, $currentValue, $float)
|
||||
|
||||
if ($currentValue / $float -gt 2) {
|
||||
return 5
|
||||
}
|
||||
else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
$FloatValue = 0.4
|
||||
$Key = 'Test'
|
||||
|
||||
# Add entry
|
||||
$dictionary.AddOrUpdate[float]($Key, $addEntryScript, $updateEntryScript, $FloatValue)
|
||||
$dictionary.$Key | Should -Be 1
|
||||
|
||||
# Update entry
|
||||
$dictionary.AddOrUpdate[float]($Key, $addEntryScript, $updateEntryScript, $FloatValue)
|
||||
$dictionary.$Key | Should -Be 5
|
||||
}
|
||||
|
||||
It 'can call generic static methods with arguments' {
|
||||
[System.Linq.Enumerable]::Select[int, int](
|
||||
[int[]](0..10),
|
||||
[func[int, int]] { $args[0] + 2 }
|
||||
) | Should -Be @(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
|
||||
}
|
||||
|
||||
It 'gives a runtime error if too many type parameters are given' {
|
||||
try {
|
||||
[array]::Empty[string, int]()
|
||||
}
|
||||
catch {
|
||||
$_.Exception.Message | Should -BeExactly 'Could not find a suitable generic method overload for "Empty" with "2" type parameters, and the argument count: "0".'
|
||||
$_.FullyQualifiedErrorId | Should -BeExactly 'MethodCountCouldNotFindBestGeneric'
|
||||
}
|
||||
}
|
||||
|
||||
It 'gives a runtime error if a nonexistent type is specified' {
|
||||
try {
|
||||
[array]::Empty[thisdoesnotexist]()
|
||||
}
|
||||
catch {
|
||||
$_.Exception.Message | Should -BeExactly 'One or more of the generic type parameters provided for the method "Empty" refers to a type which cannot be found.'
|
||||
$_.FullyQualifiedErrorId | Should -BeExactly 'TypeNotFoundForGenericMethod'
|
||||
}
|
||||
}
|
||||
|
||||
It 'lists the same method group information with and without type parameters' {
|
||||
$dict = [System.Collections.Concurrent.ConcurrentDictionary[string, int]]::new()
|
||||
$noTypeParams = $dict.AddOrUpdate
|
||||
$typeParams = $dict.AddOrUpdate[string]
|
||||
$extraTypeParams = $dict.AddOrUpdate[string, int]
|
||||
|
||||
$typeParams.OverloadDefinitions | Should -BeExactly $noTypeParams.OverloadDefinitions
|
||||
$extraTypeParams.OverloadDefinitions | Should -BeExactly $typeParams.OverloadDefinitions
|
||||
}
|
||||
|
||||
It 'successfully invokes common Linq generic methods' {
|
||||
[System.Collections.Generic.List[int]]$list = @( 1, 2, 3, 4, 5 )
|
||||
$result = [System.Linq.Enumerable]::Select[int, float](
|
||||
$list,
|
||||
[Func[int, float]]{
|
||||
param($item)
|
||||
[math]::Pow($item, 3)
|
||||
}
|
||||
)
|
||||
|
||||
$result.GetType().Name | Should -BeExactly 'SelectListIterator`2'
|
||||
$typeArgs = $result.GetType().GenericTypeArguments
|
||||
$typeArgs[0] | Should -Be ([int])
|
||||
$typeArgs[1] | Should -Be ([float])
|
||||
|
||||
$resultList = $result.ToList()
|
||||
$resultList.GetType().Name | Should -Be 'List`1'
|
||||
$resultList.GetType().GenericTypeArguments | Should -Be ([float])
|
||||
$resultList | Should -Be @( 1, 8, 27, 64, 125)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user