diff --git a/src/System.Management.Automation/engine/LanguagePrimitives.cs b/src/System.Management.Automation/engine/LanguagePrimitives.cs index 4bf0017169..bec3a84d71 100644 --- a/src/System.Management.Automation/engine/LanguagePrimitives.cs +++ b/src/System.Management.Automation/engine/LanguagePrimitives.cs @@ -19,7 +19,7 @@ using System.IO; using System.Text; using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; -using System.Diagnostics.CodeAnalysis; // for fxcop +using System.Diagnostics.CodeAnalysis; using Dbg = System.Management.Automation.Diagnostics; using MethodCacheEntry = System.Management.Automation.DotNetAdapter.MethodCacheEntry; @@ -3202,49 +3202,6 @@ namespace System.Management.Automation valueToConvert.ToString(), resultType.ToString(), exception.Message); } - private static Delegate ConvertPSMethodInfoToDelegate(object valueToConvert, - Type resultType, - bool recurse, - PSObject originalValueToConvert, - IFormatProvider formatProvider, - TypeTable backupTable) - { - // We can only possibly convert PSMethod instance of the type PSMethod. - // Such a PSMethod essentially represents a set of .NET method overloads. - var psMethod = (PSMethod)valueToConvert; - - try - { - var methods = (MethodCacheEntry)psMethod.adapterData; - var isStatic = psMethod.instance is Type; - var targetMethodInfo = resultType.GetMethod("Invoke"); - var comparator = new DelegateArgsComparator(targetMethodInfo); - - foreach (var methodInformation in methods.methodInformationStructures) - { - var candidate = (MethodInfo)methodInformation.method; - if (comparator.SignatureMatches(candidate.ReturnType, candidate.GetParameters())) - { - return isStatic ? candidate.CreateDelegate(resultType) - : candidate.CreateDelegate(resultType, psMethod.instance); - } - } - } - catch (Exception e) - { - typeConversion.WriteLine("PSMethod to Delegate exception: \"{0}\".", e.Message); - throw new PSInvalidCastException("InvalidCastExceptionPSMethodToDelegate", e, - ExtendedTypeSystem.InvalidCastExceptionWithInnerException, - valueToConvert.ToString(), resultType.ToString(), e.Message); - } - - var msg = String.Format(ExtendedTypeSystem.PSMethodToDelegateNoMatchingOverLoad, psMethod, resultType); - typeConversion.WriteLine($"PSMethod to Delegate exception: \"{msg}\"."); - throw new PSInvalidCastException("InvalidCastExceptionPSMethodToDelegate", null, - ExtendedTypeSystem.InvalidCastExceptionWithInnerException, - valueToConvert.ToString(), resultType.ToString(), msg); - } - private static object ConvertToNullable(object valueToConvert, Type resultType, bool recursion, @@ -3486,6 +3443,65 @@ namespace System.Management.Automation return ConvertStringToEnum(sbResult.ToString(), resultType, recursion, originalValueToConvert, formatProvider, backupTable); } + private class PSMethodToDelegateConverter + { + // Index of the matching overload method. + private readonly int _matchIndex; + // Size of the cache. It's rare to have more than 10 overloads for a method. + private const int CacheSize = 10; + private static readonly PSMethodToDelegateConverter[] s_converterCache = new PSMethodToDelegateConverter[CacheSize]; + + private PSMethodToDelegateConverter(int matchIndex) + { + _matchIndex = matchIndex; + } + + internal static PSMethodToDelegateConverter GetConverter(int matchIndex) + { + if (matchIndex >= CacheSize) { return new PSMethodToDelegateConverter(matchIndex); } + + var result = s_converterCache[matchIndex]; + if (result == null) + { + // If the cache entry is null, generate a new instance for the cache slot. + var converter = new PSMethodToDelegateConverter(matchIndex); + Threading.Interlocked.CompareExchange(ref s_converterCache[matchIndex], converter, null); + result = s_converterCache[matchIndex]; + } + + return result; + } + + internal Delegate Convert(object valueToConvert, + Type resultType, + bool recursion, + PSObject originalValueToConvert, + IFormatProvider formatProvider, + TypeTable backupTable) + { + // We can only possibly convert PSMethod instance of the type PSMethod. + // Such a PSMethod essentially represents a set of .NET method overloads. + var psMethod = (PSMethod)valueToConvert; + + try + { + var methods = (MethodCacheEntry)psMethod.adapterData; + var isStatic = psMethod.instance is Type; + + var candidate = (MethodInfo)methods.methodInformationStructures[_matchIndex].method; + return isStatic ? candidate.CreateDelegate(resultType) + : candidate.CreateDelegate(resultType, psMethod.instance); + } + catch (Exception e) + { + typeConversion.WriteLine("PSMethod to Delegate exception: \"{0}\".", e.Message); + throw new PSInvalidCastException("InvalidCastExceptionPSMethodToDelegate", e, + ExtendedTypeSystem.InvalidCastExceptionWithInnerException, + valueToConvert.ToString(), resultType.ToString(), e.Message); + } + } + } + private class ConvertViaParseMethod { // TODO - use an ETS wrapper that generates a dynamic method @@ -4772,66 +4788,148 @@ namespace System.Management.Automation return CacheConversion(fromType, toType, LanguagePrimitives.ConvertIntegerToEnum, ConversionRank.Language); } - if (fromType.IsSubclassOf(typeof(PSMethod)) && toType.IsSubclassOf(typeof(Delegate))) + if (fromType.IsSubclassOf(typeof(PSMethod)) && toType.IsSubclassOf(typeof(Delegate)) && !toType.IsAbstract) { - var mi = toType.GetMethod("Invoke"); - - var comparator = new DelegateArgsComparator(mi); + var targetMethod = toType.GetMethod("Invoke"); + var comparator = new SignatureComparator(targetMethod); var signatureEnumerator = new PSMethodSignatureEnumerator(fromType); + int index = -1, matchedIndex = -1; + while (signatureEnumerator.MoveNext()) { - var candidate = signatureEnumerator.Current.GetMethod("Invoke"); - if (comparator.SignatureMatches(candidate.ReturnType, candidate.GetParameters())) + index++; + var signatureType = signatureEnumerator.Current; + // Skip the non-bindable signatures + if (signatureType == typeof(Func)) { continue; } + + Type[] argumentTypes = signatureType.GenericTypeArguments; + if (comparator.ProjectedSignatureMatchesTarget(argumentTypes, out bool signaturesMatchExactly)) { - return CacheConversion(fromType, toType, LanguagePrimitives.ConvertPSMethodInfoToDelegate, ConversionRank.Language); + if (signaturesMatchExactly) + { + // We prefer the signature that exactly matches the target delegate. + matchedIndex = index; + break; + } + + // If there is no exact match, then we use the first compatible signature we found. + if (matchedIndex == -1) { matchedIndex = index; } } } + + if (matchedIndex > -1) + { + // We got the index of the matching method signature based on the PSMethod<..> type. + // Signatures in PSMethod<..> type were constructed based on the array of method overloads, + // in the exact order. So we can use this index directly to locate the matching overload in + // the converter, without having to compare the signature again. + var converter = PSMethodToDelegateConverter.GetConverter(matchedIndex); + return CacheConversion(fromType, toType, converter.Convert, ConversionRank.Language); + } } return null; } - struct DelegateArgsComparator + private struct SignatureComparator { - private readonly ParameterInfo[] _targetParametersInfos; - private readonly Type _returnType; - - public DelegateArgsComparator(MethodInfo targetMethodInfo) + enum TypeMatchingContext { - _returnType = targetMethodInfo.ReturnType; - _targetParametersInfos = targetMethodInfo.GetParameters(); + ReturnType, + ParameterType, + OutParameterType } - public bool SignatureMatches(Type returnType, ParameterInfo[] arguments) + private readonly ParameterInfo[] targetParameters; + private readonly Type targetReturnType; + + internal SignatureComparator(MethodInfo targetMethodInfo) { - return ReturnTypeMatches(returnType) && ParameterTypesMatches(arguments); + targetReturnType = targetMethodInfo.ReturnType; + targetParameters = targetMethodInfo.GetParameters(); } - private bool ReturnTypeMatches(Type returnType) + /// + /// Check if a projected signature matches the target method. + /// + /// + /// The type arguments from the metadata type 'Func[..]' that represents the projected signature. + /// It contains the return type as the last item in the array. + /// + /// + /// Set by this method to indicate if it's an exact match. + /// + internal bool ProjectedSignatureMatchesTarget(Type[] argumentTypes, out bool signaturesMatchExactly) { - return PSMethod.MatchesPSMethodProjectedType(_returnType, returnType, testAssignment: true); + signaturesMatchExactly = false; + int length = argumentTypes.Length; + if (length != targetParameters.Length + 1) { return false; } + + bool typesMatchExactly, allTypesMatchExactly; + Type sourceReturnType = argumentTypes[length - 1]; + + if (ProjectedTypeMatchesTargetType(sourceReturnType, targetReturnType, TypeMatchingContext.ReturnType, out typesMatchExactly)) + { + allTypesMatchExactly = typesMatchExactly; + for (int i = 0; i < targetParameters.Length; i++) + { + var targetParam = targetParameters[i]; + var sourceType = argumentTypes[i]; + var matchContext = targetParam.IsOut ? TypeMatchingContext.OutParameterType : TypeMatchingContext.ParameterType; + + if (!ProjectedTypeMatchesTargetType(sourceType, targetParam.ParameterType, matchContext, out typesMatchExactly)) + { + return false; + } + allTypesMatchExactly &= typesMatchExactly; + } + + signaturesMatchExactly = allTypesMatchExactly; + return true; + } + + return false; } - private bool ParameterTypesMatches(ParameterInfo[] arguments) + private static bool ProjectedTypeMatchesTargetType(Type sourceType, Type targetType, TypeMatchingContext matchContext, out bool matchExactly) { - var argsCount = _targetParametersInfos.Length; - // void is encoded as typeof(VOID) in the PSMethod> as the last parameter - if (arguments.Length != argsCount) + matchExactly = false; + if (targetType.IsByRef || targetType.IsPointer) + { + if (!sourceType.IsGenericType) { return false; } + + var sourceTypeDef = sourceType.GetGenericTypeDefinition(); + bool isOutParameter = matchContext == TypeMatchingContext.OutParameterType; + + if (targetType.IsByRef && sourceTypeDef == (isOutParameter ? typeof(PSOutParameter<>) : typeof(PSReference<>)) || + targetType.IsPointer && sourceTypeDef == typeof(PSPointer<>)) + { + // For ref/out parameter types and pointer types, the element types need to match exactly. + if (targetType.GetElementType() == sourceType.GenericTypeArguments[0]) + { + matchExactly = true; + return true; + } + } + return false; + } + + if (targetType == sourceType || + targetType == typeof(void) && sourceType == typeof(VOID) || + targetType == typeof(TypedReference) && sourceType == typeof(PSTypedReference)) + { + matchExactly = true; + return true; + } + + if (targetType == typeof(void) || targetType == typeof(TypedReference)) { return false; } - for (int i = 0; i < arguments.Length; i++) - { - var arg = arguments[i]; - var argType = arg.ParameterType; - var targetParamType = _targetParametersInfos[i].ParameterType; - var isOut = (arg.Attributes | ParameterAttributes.Out) == ParameterAttributes.Out; - if (!PSMethod.MatchesPSMethodProjectedType(targetParamType, argType, isOut: isOut)) - { - return false; - } - } - return true; + + return matchContext == TypeMatchingContext.ReturnType + ? targetType.IsAssignableFrom(sourceType) + : sourceType.IsAssignableFrom(targetType); } } diff --git a/src/System.Management.Automation/engine/MshMemberInfo.cs b/src/System.Management.Automation/engine/MshMemberInfo.cs index 0890f0c7e3..b19f099db4 100644 --- a/src/System.Management.Automation/engine/MshMemberInfo.cs +++ b/src/System.Management.Automation/engine/MshMemberInfo.cs @@ -2655,43 +2655,56 @@ namespace System.Management.Automation return method.PSMethodCtor.Invoke(name, dotNetInstanceAdapter, baseObject, method, isSpecial, isHidden); } - static Type GetMethodGroupType(MethodInfo methodInfo) + private static Type GetMethodGroupType(MethodInfo methodInfo) { if (methodInfo.DeclaringType.IsGenericTypeDefinition) { + // If the method is from a generic type definition, consider it not convertible. return typeof(Func); } if (methodInfo.IsGenericMethodDefinition) { - methodInfo = ReplaceGenericTypeArgumentsWithMarkerTypes(methodInfo); - if (methodInfo == null) - { - // this happens when there are constraints on the generic type parameters - return typeof(Func); - } + // For a generic method, it's possible to infer the generic parameters based on the target delegate. + // However, we don't yet handle generic methods in PSMethod-to-Delegate conversion, so for now, we + // don't produce the metadata type that represents the signature of a generic method. + // + // Say one day we want to support generic method in PSMethod-to-Delegate conversion and need to produce + // the metadata type, we should use the generic parameter types from the MethodInfo directly to construct + // the Func<> metadata type. See the concept shown in the following scripts: + // $class = "public class Zoo { public static T GetName(int index, T input) { return default(T); } }" + // Add-Type -TypeDefinition $class + // $method = [Zoo].GetMethod("GetName") + // $allTypes = $method.GetParameters().ParameterType + $method.ReturnType + // $metadataType = [Func`3].MakeGenericType($allTypes) + // In this way, '$metadataType.ContainsGenericParameters' returns 'True', indicating it represents a generic method. + // And also, given a generic argument type from `$metadataType.GetGenericArguments()`, it's easy to tell if it's a + // generic parameter (for example, 'T') based on the property 'IsGenericParameter'. + // Moreover, it's also easy to get constraints of the generic parameter, via 'GetGenericParameterConstraints()' + // and 'GenericParameterAttributes'. + return typeof(Func); } var parameterInfos = methodInfo.GetParameters(); if (parameterInfos.Length > 16) { + // Too many parameters, an unlikely scenario. return typeof(Func); } - var res = new Type[parameterInfos.Length + 1]; - for (int i = 0; i < res.Length - 1; i++) + var methodTypes = new Type[parameterInfos.Length + 1]; + for (int i = 0; i < parameterInfos.Length; i++) { var parameterInfo = parameterInfos[i]; - var parameterType = parameterInfo.ParameterType; - res[i] = GetPSMethodTypeProjection(parameterType, - (parameterInfo.Attributes | ParameterAttributes.Out) == ParameterAttributes.Out); + Type parameterType = parameterInfo.ParameterType; + methodTypes[i] = GetPSMethodProjectedType(parameterType, parameterInfo.IsOut); } - var returnType = GetPSMethodTypeProjection(methodInfo.ReturnType); - res[parameterInfos.Length] = returnType; + + methodTypes[parameterInfos.Length] = GetPSMethodProjectedType(methodInfo.ReturnType); try { - return DelegateHelpers.MakeDelegate(res); + return DelegateHelpers.MakeDelegate(methodTypes); } catch (TypeLoadException) { @@ -2699,92 +2712,30 @@ namespace System.Management.Automation } } - private static Type GetPSMethodTypeProjection(Type type, bool isOut = false) + private static Type GetPSMethodProjectedType(Type type, bool isOut = false) { if (type == typeof(void)) { return typeof(VOID); } + if (type == typeof(TypedReference)) { return typeof(PSTypedReference); } - var resType = type.IsEnum ? typeof(PSEnum<>).MakeGenericType(type) : type; - if (resType.HasElementType) { - var psMethodTypeProjection = GetPSMethodTypeProjection(resType.GetElementType()); - if (type.IsPointer) - { - resType = typeof(PSPointer<>).MakeGenericType(psMethodTypeProjection); - } - if (type.IsByRef) - { - resType = isOut ? typeof(PSOutParameter<>).MakeGenericType(psMethodTypeProjection) : typeof(PSReference<>).MakeGenericType(psMethodTypeProjection); - } + + if (type.IsByRef) + { + var elementType = type.GetElementType(); + type = isOut ? typeof(PSOutParameter<>).MakeGenericType(elementType) + : typeof(PSReference<>).MakeGenericType(elementType); + } + else if (type.IsPointer) + { + type = typeof(PSPointer<>).MakeGenericType(type.GetElementType()); } - return resType; - } - - internal static bool MatchesPSMethodProjectedType(Type targetType, Type projectedSourceType, bool testAssignment = false, bool isOut = false) - { - var sourceType = projectedSourceType; - if (targetType.IsByRef || targetType.IsPointer) - { - if (!projectedSourceType.IsGenericType) return false; - var defType = projectedSourceType.GetGenericTypeDefinition(); - if (targetType.IsByRef && defType == (isOut ? typeof(PSOutParameter<>) : typeof(PSReference<>)) - || targetType.IsPointer && defType == typeof(PSPointer<>)) - { - return MatchesPSMethodProjectedType(targetType.GetElementType(), - projectedSourceType.GenericTypeArguments[0], testAssignment, isOut); - } - } - if (targetType.IsEnum) - { - if (sourceType.IsGenericType && sourceType.GetGenericTypeDefinition() != typeof(PSEnum<>)) - { - return false; - } - sourceType = sourceType.GenericTypeArguments[0]; - } - - if (targetType == typeof(void) && sourceType == typeof(VOID)) - { - return true; - } - if (targetType == typeof(TypedReference) && sourceType == typeof(PSTypedReference)) - { - return true; - } - if (testAssignment) - { - return targetType.IsAssignableFrom(sourceType); - } - return targetType == sourceType; - } - - private static MethodInfo ReplaceGenericTypeArgumentsWithMarkerTypes(MethodInfo methodInfo) - { - if (!methodInfo.ContainsGenericParameters) - { - return methodInfo; - } - - var genArgs = methodInfo.GetGenericArguments(); - var concrete = new Type[genArgs.Length]; - for (int i = 0; i < genArgs.Length; i++) - { - var genArg = genArgs[i]; - if (genArg.GetGenericParameterConstraints().Length != 0) - { - return null; - } - var gpa = genArg.GenericParameterAttributes; - concrete[i] = (gpa & GenericParameterAttributes.NotNullableValueTypeConstraint) == GenericParameterAttributes.NotNullableValueTypeConstraint - ? PSGenericValueType.GetGenericType(i) - : PSGenericType.GetGenericType(i); - } - return methodInfo.MakeGenericMethod(concrete); + return type; } private static Func CreatePSMethodConstructor(MethodInformation[] methods) @@ -2829,113 +2780,11 @@ namespace System.Management.Automation } } - class PSOutParameter { private PSOutParameter() { }} - - abstract class PSNonBindableType { } - - abstract class PSGenericType - { - public static Type GetGenericType(int i) - { - switch (i) - { - case 0: return typeof(PSGenericType0); - case 1: return typeof(PSGenericType1); - case 2: return typeof(PSGenericType2); - case 3: return typeof(PSGenericType3); - case 4: return typeof(PSGenericType4); - case 5: return typeof(PSGenericType5); - case 6: return typeof(PSGenericType6); - case 7: return typeof(PSGenericType7); - case 8: return typeof(PSGenericType8); - case 9: return typeof(PSGenericType9); - case 10: return typeof(PSGenericType10); - case 11: return typeof(PSGenericType11); - case 12: return typeof(PSGenericType12); - case 13: return typeof(PSGenericType13); - case 14: return typeof(PSGenericType14); - case 15: return typeof(PSGenericType15); - case 16: return typeof(PSGenericType16); - default: - return typeof(PSGenericType<>).MakeGenericType(GetGenericType(i - 1)); - } - } - } - - class PSGenericType0 : PSGenericType { internal PSGenericType0() { } } - class PSGenericType1 : PSGenericType { internal PSGenericType1() { } } - class PSGenericType2 : PSGenericType { internal PSGenericType2() { } } - class PSGenericType3 : PSGenericType { internal PSGenericType3() { } } - class PSGenericType4 : PSGenericType { internal PSGenericType4() { } } - class PSGenericType5 : PSGenericType { internal PSGenericType5() { } } - class PSGenericType6 : PSGenericType { internal PSGenericType6() { } } - class PSGenericType7 : PSGenericType { internal PSGenericType7() { } } - class PSGenericType8 : PSGenericType { internal PSGenericType8() { } } - class PSGenericType9 : PSGenericType { internal PSGenericType9() { } } - class PSGenericType10 : PSGenericType { internal PSGenericType10() { } } - class PSGenericType11 : PSGenericType { internal PSGenericType11() { } } - class PSGenericType12 : PSGenericType { internal PSGenericType12() { } } - class PSGenericType13 : PSGenericType { internal PSGenericType13() { } } - class PSGenericType14 : PSGenericType { internal PSGenericType14() { } } - class PSGenericType15 : PSGenericType { internal PSGenericType15() { } } - class PSGenericType16 : PSGenericType { internal PSGenericType16() { } } - - class PSGenericType : PSGenericType { internal PSGenericType() { } } - - struct PSGenericValueType - { - internal static Type GetGenericType(int i) - { - switch (i) - { - case 0: return typeof(PSGenericValueType0); - case 1: return typeof(PSGenericValueType1); - case 2: return typeof(PSGenericValueType2); - case 3: return typeof(PSGenericValueType3); - case 4: return typeof(PSGenericValueType4); - case 5: return typeof(PSGenericValueType5); - case 6: return typeof(PSGenericValueType6); - case 7: return typeof(PSGenericValueType7); - case 8: return typeof(PSGenericValueType8); - case 9: return typeof(PSGenericValueType9); - case 10: return typeof(PSGenericValueType10); - case 11: return typeof(PSGenericValueType11); - case 12: return typeof(PSGenericValueType12); - case 13: return typeof(PSGenericValueType13); - case 14: return typeof(PSGenericValueType14); - case 15: return typeof(PSGenericValueType15); - case 16: return typeof(PSGenericValueType16); - default: - return typeof(PSGenericValueType<>).MakeGenericType(GetGenericType(i - 1)); - } - } - } - - struct PSGenericValueType0 { internal int value; } - struct PSGenericValueType1 { internal int value; } - struct PSGenericValueType2 { internal int value; } - struct PSGenericValueType3 { internal int value; } - struct PSGenericValueType4 { internal int value; } - struct PSGenericValueType5 { internal int value; } - struct PSGenericValueType6 { internal int value; } - struct PSGenericValueType7 { internal int value; } - struct PSGenericValueType8 { internal int value; } - struct PSGenericValueType9 { internal int value; } - struct PSGenericValueType10 { internal int value; } - struct PSGenericValueType11 { internal int value; } - struct PSGenericValueType12 { internal int value; } - struct PSGenericValueType13 { internal int value; } - struct PSGenericValueType14 { internal int value; } - struct PSGenericValueType15 { internal int value; } - struct PSGenericValueType16 { internal int value; } - - struct PSGenericValueType { internal int value; } - - struct PSEnum { } - - struct PSPointer { } - - struct PSTypedReference { } + internal abstract class PSNonBindableType { } + internal class VOID { } + internal class PSOutParameter { } + internal struct PSPointer { } + internal struct PSTypedReference { } internal abstract class MethodGroup { } internal class MethodGroup : MethodGroup { } @@ -2945,11 +2794,6 @@ namespace System.Management.Automation internal class MethodGroup : MethodGroup { } internal class MethodGroup : MethodGroup { } - class VOID - { - private VOID() { } - } - internal struct PSMethodSignatureEnumerator : IEnumerator { private int _currentIndex; @@ -2985,7 +2829,7 @@ namespace System.Management.Automation var remaining = index - (length - 1); return MoveNext(t, remaining); } - if (index >= genericTypeArguments.Length) + if (index >= length) { Current = null; return false; diff --git a/src/System.Management.Automation/resources/ExtendedTypeSystem.resx b/src/System.Management.Automation/resources/ExtendedTypeSystem.resx index 9b836fb66b..9541ee6a0d 100644 --- a/src/System.Management.Automation/resources/ExtendedTypeSystem.resx +++ b/src/System.Management.Automation/resources/ExtendedTypeSystem.resx @@ -370,7 +370,4 @@ PS> [System.Collections.Generic.Comparer``1]::get_Default() Cannot convert value to type "{0}". Only core types are supported in this language mode. - - No matching overload found from {0} to {1} - diff --git a/test/powershell/Language/Parser/Conversions.Tests.ps1 b/test/powershell/Language/Parser/Conversions.Tests.ps1 index fbc0aee7eb..ced48d9cf3 100644 --- a/test/powershell/Language/Parser/Conversions.Tests.ps1 +++ b/test/powershell/Language/Parser/Conversions.Tests.ps1 @@ -396,4 +396,94 @@ Describe 'method conversion' -Tags 'CI' { [Func[timespan, timespan, timespan, timespan, timespan, timespan, timespan, timespan, timespan]] $f72 = [M]::Foo $f72.Invoke($timeSpan, [Timespan]::Zero, [Timespan]::Zero, [Timespan]::Zero, [Timespan]::Zero, [Timespan]::Zero, [Timespan]::Zero, [Timespan]::Zero) | Should -Be $timeSpan } + + enum E { + Day; + Week; + Year; + } + + class N { + ## Attempt to convert methods to Func. + + ## Different methods with same overload signatures. + ## The second and third overloads match the target delegate with variance. + [string] GetA([int] $i, [string] $s) { return "GetA-int-string-string" } + [string] GetA([System.IO.FileSystemInfo] $fsinfo, [object] $o) { return "GetA-filesysteminfo-object-string" } + [string] GetA([System.IO.FileInfo] $finfo, [object] $o) { return "GetA-fileinfo-object-string" } + + [string] GetAPrime([int] $i, [string] $s) { return "GetAPrime-int-string-string" } + [string] GetAPrime([System.IO.FileSystemInfo] $fsinfo, [object] $o) { return "GetAPrime-filesysteminfo-object-string" } + [string] GetAPrime([System.IO.FileInfo] $finfo, [object] $o) { return "GetAPrime-fileinfo-object-string" } + + static [string] GetAStatic([int] $i, [string] $s) { return "GetAStatic-int-string-string" } + static [string] GetAStatic([System.IO.FileSystemInfo] $fsinfo, [object] $o) { return "GetAStatic-filesysteminfo-object-string" } + static [string] GetAStatic([System.IO.FileInfo] $finfo, [object] $o) { return "GetAStatic-fileinfo-object-string" } + + ## Different methods with same overload signatures. + ## The first overload matches the target delegate with variance, + ## while the second overload matches the target delegate exactly. + [string] GetB([System.IO.FileSystemInfo] $fsinfo, [object] $o) { return "GetB-filesysteminfo-object-string" } + [object] GetB([System.IO.FileInfo] $finfo, [string] $s) { return "GetB-fileinfo-string-object" } + [string] GetB([datetime] $d) { return "GetB-datetime-string" } + + [string] GetBPrime([System.IO.FileSystemInfo] $fsinfo, [object] $o) { return "GetBPrime-filesysteminfo-object-string" } + [object] GetBPrime([System.IO.FileInfo] $finfo, [string] $s) { return "GetBPrime-fileinfo-string-object" } + [string] GetBPrime([datetime] $d) { return "GetBPrime-datetime-string" } + + static [string] GetBStatic([System.IO.FileSystemInfo] $fsinfo, [object] $o) { return "GetBStatic-filesysteminfo-object-string" } + static [object] GetBStatic([System.IO.FileInfo] $finfo, [string] $s) { return "GetBStatic-fileinfo-string-object" } + static [string] GetBStatic([datetime] $d) { return "GetBStatic-datetime-string" } + + ## Test enum parameter type + [object] GetC([E] $e) { return $e.ToString() } + } + + It "Different method overloads with same signatures/orders should have same PSMethod type" { + $n = [N]::new() + + $n.GetA.GetType() | Should -Be ($n.GetAPrime.GetType()) + $n.GetA.GetType() | Should -Be ([N]::GetAStatic.GetType()) + + $n.GetB.GetType() | Should -Be ($n.GetBPrime.GetType()) + $n.GetB.GetType() | Should -Be ([N]::GetBStatic.GetType()) + } + + It "Match signature with variance and use the first match when there is no exact match" { + $n = [N]::new() + + [Func[[System.IO.FileInfo], [string], [object]]] $f = $n.GetA + $f.Invoke($null, $null) | Should -BeExactly "GetA-filesysteminfo-object-string" + + $f = $n.GetAPrime ## $n.GetAPrime has the same type as $n.GetA, so it should hit the conversion cache + $f.Invoke([System.IO.FileInfo]::new("aaa"), "bbb") | Should -BeExactly "GetAPrime-filesysteminfo-object-string" + + $f = [N]::GetAStatic ## [N]::GetAStatic has the same type as $n.GetA, so it should hit the conversion cache + $f.Invoke($null, "") | Should -BeExactly "GetAStatic-filesysteminfo-object-string" + } + + It "Exact match is preferred over match with variance" { + $n = [N]::new() + + [Func[[System.IO.FileInfo], [string], [object]]] $f = $n.GetB + $f.Invoke($null, $null) | Should -BeExactly "GetB-fileinfo-string-object" + + $f = $n.GetBPrime ## $n.GetBPrime has the same type as $n.GetB, so it should hit the conversion cache + $f.Invoke([System.IO.FileInfo]::new("ccc"), "ddd") | Should -BeExactly "GetBPrime-fileinfo-string-object" + + $f = [N]::GetBStatic ## [N]::GetBStatic has the same type as $n.GetB, so it should hit the conversion cache + $f.Invoke($null, "") | Should -BeExactly "GetBStatic-fileinfo-string-object" + } + + It "Test enum type parameter" { + $n = [N]::new() + + [Func[[E], [object]]] $f = $n.GetC + $f.Invoke([E]::Week) | Should -BeExactly "Week" + } + + It "Test fail-to-convert code path" { + $n = [N]::new() + { [System.Management.Automation.LanguagePrimitives]::ConvertTo($n.GetC, [Func[[int], [object]]]) } | Should -Throw -ErrorId "PSInvalidCastException" + } }