Add a couple fixes to generic method invocation (#16822)

This commit is contained in:
Dongbo Wang
2022-02-03 11:58:02 -08:00
committed by GitHub
parent bbb11dd07a
commit 35ad64e11b
5 changed files with 298 additions and 263 deletions
@@ -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)
}
}