mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Further improve PSMethod to Delegate conversion (#6851)
Refactor code to make it easier to maintain and a little faster. Changes are as follows:
1. Support finding a matching signature with variance. But make PowerShell prefer exact match over a match with variance.
2. The metadata signatures in `PSMethod<..>` are generated based on the array of method overloads in `MethodCacheEntry.MethodInformationStructures`, in the exact same order. So in `LanguagePrimitive.ConvertViaParseMethod`, when we try to figure out if there is a match using the metadata signatures in `PSMethod<..>`, we can get the index of the matching signature, and the same index should locate the matching method in `MethodCacheEntry.MethodInformationStructures`. Therefore, we don't need to compare signatures again in the actual conversion method, and instead, we can just leverage the index we found when figuring out the conversion in `ConvertViaParseMethod`.
- This gets rid of the reflection call `GetMethod("Invoke")` and the subsequent signature comparisons in the final conversion method.
- Also, when comparing signatures using `PSMethod<..>` in `ConvertViaParseMethod`, we can just use the generic argument types of each `Func<..>` metadata type, instead of calling `GetMethod("Invoke")` and then `GetParameters()`. This makes the code for comparing signatures simpler (the type `SignatureComparator`).
- Move `MatchesPSMethodProjectedType` from `PSMemberInfo.cs` to the type `SignatureComparator` in `LanguagePrimitives.cs`, as it's closely related to the signature comparison. Also, renamed it to `ProjectedTypeMatchesTargetType`.
- These changes make PSMethod-to-Delegate conversion a little faster, but no big improvement, as the true bottleneck probably is in delegate creation(?). Actually, the performance of this conversion is not critical at all at this moment because this feature should rarely be used in any hot script path. So this exercise is mainly for fun.
3. Remove `PSEnum<T>`. We can directly use enum types when constructing the metadata type `Func<..>`.
4. Remove the code that generates metadata signatures for generic method definitions (call `MakeGenericMethod` with fake types like `GenericType0`, `GenericType1`). This is because:
- We don't support convert generic method to delegate today, so may be better not spending time on preparing the metadata signature types for those methods.
- When the day comes that we need to support it, it's better to use generic argument types directly to construct the `Func<..>` metadata types. I left comments in `GetMethodGroupType` method in `PSMemberInfo.cs` to explain why that approach is better.
This commit is contained in:
@@ -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<T>.
|
||||
// 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<T>.
|
||||
// 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<object>(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<PSNonBindableType>)) { continue; }
|
||||
|
||||
Type[] argumentTypes = signatureType.GenericTypeArguments;
|
||||
if (comparator.ProjectedSignatureMatchesTarget(argumentTypes, out bool signaturesMatchExactly))
|
||||
{
|
||||
return CacheConversion<Delegate>(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<Delegate>(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)
|
||||
/// <summary>
|
||||
/// Check if a projected signature matches the target method.
|
||||
/// </summary>
|
||||
/// <param name="argumentTypes">
|
||||
/// 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.
|
||||
/// </param>
|
||||
/// <param name="signaturesMatchExactly">
|
||||
/// Set by this method to indicate if it's an exact match.
|
||||
/// </param>
|
||||
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<MethodGroup<>> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<PSNonBindableType>);
|
||||
}
|
||||
|
||||
if (methodInfo.IsGenericMethodDefinition)
|
||||
{
|
||||
methodInfo = ReplaceGenericTypeArgumentsWithMarkerTypes(methodInfo);
|
||||
if (methodInfo == null)
|
||||
{
|
||||
// this happens when there are constraints on the generic type parameters
|
||||
return typeof(Func<PSNonBindableType>);
|
||||
}
|
||||
// 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<T>(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<PSNonBindableType>);
|
||||
}
|
||||
|
||||
var parameterInfos = methodInfo.GetParameters();
|
||||
if (parameterInfos.Length > 16)
|
||||
{
|
||||
// Too many parameters, an unlikely scenario.
|
||||
return typeof(Func<PSNonBindableType>);
|
||||
}
|
||||
|
||||
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<string, DotNetAdapter, object, object, bool, bool, PSMethod> CreatePSMethodConstructor(MethodInformation[] methods)
|
||||
@@ -2829,113 +2780,11 @@ namespace System.Management.Automation
|
||||
}
|
||||
}
|
||||
|
||||
class PSOutParameter<T> { 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<T> : 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<T> { internal int value; }
|
||||
|
||||
struct PSEnum<T> { }
|
||||
|
||||
struct PSPointer<T> { }
|
||||
|
||||
struct PSTypedReference { }
|
||||
internal abstract class PSNonBindableType { }
|
||||
internal class VOID { }
|
||||
internal class PSOutParameter<T> { }
|
||||
internal struct PSPointer<T> { }
|
||||
internal struct PSTypedReference { }
|
||||
|
||||
internal abstract class MethodGroup { }
|
||||
internal class MethodGroup<T1> : MethodGroup { }
|
||||
@@ -2945,11 +2794,6 @@ namespace System.Management.Automation
|
||||
internal class MethodGroup<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16> : MethodGroup { }
|
||||
internal class MethodGroup<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29, T30, T31, T32> : MethodGroup { }
|
||||
|
||||
class VOID
|
||||
{
|
||||
private VOID() { }
|
||||
}
|
||||
|
||||
internal struct PSMethodSignatureEnumerator : IEnumerator<Type>
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -370,7 +370,4 @@ PS> [System.Collections.Generic.Comparer``1]::get_Default()
|
||||
<data name="InvalidCastExceptionNonCoreType" xml:space="preserve">
|
||||
<value>Cannot convert value to type "{0}". Only core types are supported in this language mode.</value>
|
||||
</data>
|
||||
<data name="PSMethodToDelegateNoMatchingOverLoad" xml:space="preserve">
|
||||
<value>No matching overload found from {0} to {1}</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -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<System.IO.FileInfo, string, object>.
|
||||
|
||||
## 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"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user