Skip to content

Commit 8c53252

Browse files
authored
Add support to allow invoking method with generic type arguments (PowerShell#12412)
1 parent 9f722ef commit 8c53252

File tree

9 files changed

+653
-93
lines changed

9 files changed

+653
-93
lines changed

src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2107,6 +2107,14 @@ private List<CompletionResult> GetResultForIdentifier(CompletionContext completi
21072107
result = CompletionCompleters.CompleteCommandArgument(completionContext);
21082108
replacementIndex = completionContext.ReplacementIndex;
21092109
replacementLength = completionContext.ReplacementLength;
2110+
2111+
if (result.Count == 0
2112+
&& completionContext.TokenAtCursor.TokenFlags.HasFlag(TokenFlags.TypeName)
2113+
&& lastAst?.Find(a => a is MemberExpressionAst, searchNestedScriptBlocks: false) is not null)
2114+
{
2115+
result = CompletionCompleters.CompleteType(completionContext.TokenAtCursor.Text).ToList();
2116+
}
2117+
21102118
return result;
21112119
}
21122120

src/System.Management.Automation/engine/CoreAdapter.cs

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1394,32 +1394,37 @@ private static MethodInformation FindBestMethodImpl(
13941394
// be turned into an array.
13951395
// We also skip the optimization if the number of arguments and parameters is different
13961396
// so we let the loop deal with possible optional parameters.
1397-
if ((methods.Length == 1) &&
1398-
(!methods[0].hasVarArgs) &&
1399-
(!methods[0].isGeneric) &&
1400-
(methods[0].method == null || !(methods[0].method.DeclaringType.IsGenericTypeDefinition)) &&
1397+
if (methods.Length == 1
1398+
&& !methods[0].hasVarArgs
14011399
// generic methods need to be double checked in a loop below - generic methods can be rejected if type inference fails
1402-
(methods[0].parameters.Length == arguments.Length))
1400+
&& !methods[0].isGeneric
1401+
&& (methods[0].method is null || !methods[0].method.DeclaringType.IsGenericTypeDefinition)
1402+
&& methods[0].parameters.Length == arguments.Length)
14031403
{
14041404
return methods[0];
14051405
}
14061406

14071407
Type[] argumentTypes = arguments.Select(EffectiveArgumentType).ToArray();
1408+
Type[] genericParameters = invocationConstraints?.GenericTypeParameters ?? Array.Empty<Type>();
14081409
List<OverloadCandidate> candidates = new List<OverloadCandidate>();
14091410
for (int i = 0; i < methods.Length; i++)
14101411
{
1411-
MethodInformation method = methods[i];
1412+
MethodInformation methodInfo = methods[i];
14121413

1413-
if (method.method != null && method.method.DeclaringType.IsGenericTypeDefinition)
1414+
if (methodInfo.method?.DeclaringType.IsGenericTypeDefinition == true
1415+
|| (!methodInfo.isGeneric && genericParameters.Length > 0))
14141416
{
1415-
continue; // skip methods defined by an *open* generic type
1417+
// If method is defined by an *open* generic type, or
1418+
// if generic parameters were provided and this method isn't generic, skip it.
1419+
continue;
14161420
}
14171421

1418-
if (method.isGeneric)
1422+
if (methodInfo.isGeneric)
14191423
{
14201424
Type[] argumentTypesForTypeInference = new Type[argumentTypes.Length];
14211425
Array.Copy(argumentTypes, argumentTypesForTypeInference, argumentTypes.Length);
1422-
if (invocationConstraints != null && invocationConstraints.ParameterTypes != null)
1426+
1427+
if (invocationConstraints?.ParameterTypes is not null)
14231428
{
14241429
int parameterIndex = 0;
14251430
foreach (Type typeConstraintFromCallSite in invocationConstraints.ParameterTypes)
@@ -1433,41 +1438,57 @@ private static MethodInformation FindBestMethodImpl(
14331438
}
14341439
}
14351440

1436-
method = TypeInference.Infer(method, argumentTypesForTypeInference);
1437-
if (method == null)
1441+
if (genericParameters.Length > 0 && methodInfo.method is MethodInfo originalMethod)
1442+
{
1443+
try
1444+
{
1445+
methodInfo = new MethodInformation(
1446+
originalMethod.MakeGenericMethod(genericParameters),
1447+
parametersToIgnore: 0);
1448+
}
1449+
catch (ArgumentException)
1450+
{
1451+
// Just skip this possibility if the generic type parameters can't be used to make
1452+
// a valid generic method here.
1453+
continue;
1454+
}
1455+
}
1456+
1457+
methodInfo = TypeInference.Infer(methodInfo, argumentTypesForTypeInference);
1458+
if (methodInfo is null)
14381459
{
14391460
// Skip generic methods for which we cannot infer type arguments
14401461
continue;
14411462
}
14421463
}
14431464

1444-
if (!IsInvocationTargetConstraintSatisfied(method, invocationConstraints))
1465+
if (!IsInvocationTargetConstraintSatisfied(methodInfo, invocationConstraints))
14451466
{
14461467
continue;
14471468
}
14481469

1449-
ParameterInformation[] parameters = method.parameters;
1470+
ParameterInformation[] parameters = methodInfo.parameters;
14501471
if (arguments.Length != parameters.Length)
14511472
{
14521473
// Skip methods w/ an incorrect # of arguments.
14531474

14541475
if (arguments.Length > parameters.Length)
14551476
{
14561477
// If too many args,it's only OK if the method is varargs.
1457-
if (!method.hasVarArgs)
1478+
if (!methodInfo.hasVarArgs)
14581479
{
14591480
continue;
14601481
}
14611482
}
14621483
else
14631484
{
14641485
// Too few args, OK if there are optionals, or varargs with the param array omitted
1465-
if (!method.hasOptional && (!method.hasVarArgs || (arguments.Length + 1) != parameters.Length))
1486+
if (!methodInfo.hasOptional && (!methodInfo.hasVarArgs || (arguments.Length + 1) != parameters.Length))
14661487
{
14671488
continue;
14681489
}
14691490

1470-
if (method.hasOptional)
1491+
if (methodInfo.hasOptional)
14711492
{
14721493
// Count optionals. This code is rarely hit, mainly when calling code in the
14731494
// assembly Microsoft.VisualBasic. If it were more frequent, the optional count
@@ -1490,7 +1511,7 @@ private static MethodInformation FindBestMethodImpl(
14901511
}
14911512
}
14921513

1493-
OverloadCandidate candidate = new OverloadCandidate(method, arguments.Length);
1514+
OverloadCandidate candidate = new OverloadCandidate(methodInfo, arguments.Length);
14941515
for (int j = 0; candidate != null && j < parameters.Length; j++)
14951516
{
14961517
ParameterInformation parameter = parameters[j];
@@ -1590,6 +1611,22 @@ private static MethodInformation FindBestMethodImpl(
15901611
methods[0].method.DeclaringType.FullName);
15911612
return null;
15921613
}
1614+
else if (genericParameters.Length != 0 && genericParameters.Contains(null))
1615+
{
1616+
errorId = "TypeNotFoundForGenericMethod";
1617+
errorMsg = ExtendedTypeSystem.MethodGenericArgumentTypeNotFoundException;
1618+
return null;
1619+
}
1620+
else if (genericParameters.Length != 0)
1621+
{
1622+
errorId = "MethodCountCouldNotFindBestGeneric";
1623+
errorMsg = string.Format(
1624+
ExtendedTypeSystem.MethodGenericArgumentCountException,
1625+
methods[0].method.Name,
1626+
genericParameters.Length,
1627+
arguments.Length);
1628+
return null;
1629+
}
15931630
else
15941631
{
15951632
errorId = "MethodCountCouldNotFindBest";

src/System.Management.Automation/engine/MshMemberInfo.cs

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1909,9 +1909,18 @@ internal class PSMethodInvocationConstraints
19091909
internal PSMethodInvocationConstraints(
19101910
Type methodTargetType,
19111911
Type[] parameterTypes)
1912+
: this(methodTargetType, genericTypeParameters: null, parameterTypes)
19121913
{
1913-
this.MethodTargetType = methodTargetType;
1914+
}
1915+
1916+
internal PSMethodInvocationConstraints(
1917+
Type methodTargetType,
1918+
Type[] genericTypeParameters,
1919+
Type[] parameterTypes)
1920+
{
1921+
MethodTargetType = methodTargetType;
19141922
_parameterTypes = parameterTypes;
1923+
GenericTypeParameters = genericTypeParameters;
19151924
}
19161925

19171926
/// <remarks>
@@ -1926,6 +1935,11 @@ internal PSMethodInvocationConstraints(
19261935

19271936
private readonly Type[] _parameterTypes;
19281937

1938+
/// <summary>
1939+
/// Gets the generic type parameters for the method invocation.
1940+
/// </summary>
1941+
public Type[] GenericTypeParameters { get; }
1942+
19291943
internal static bool EqualsForCollection<T>(ICollection<T> xs, ICollection<T> ys)
19301944
{
19311945
if (xs == null)
@@ -1946,8 +1960,6 @@ internal static bool EqualsForCollection<T>(ICollection<T> xs, ICollection<T> ys
19461960
return xs.SequenceEqual(ys);
19471961
}
19481962

1949-
// TODO: IEnumerable<Type> genericTypeParameters { get; private set; }
1950-
19511963
public bool Equals(PSMethodInvocationConstraints other)
19521964
{
19531965
if (other is null)
@@ -1970,6 +1982,11 @@ public bool Equals(PSMethodInvocationConstraints other)
19701982
return false;
19711983
}
19721984

1985+
if (!EqualsForCollection(GenericTypeParameters, other.GenericTypeParameters))
1986+
{
1987+
return false;
1988+
}
1989+
19731990
return true;
19741991
}
19751992

@@ -1994,18 +2011,7 @@ public override bool Equals(object obj)
19942011
}
19952012

19962013
public override int GetHashCode()
1997-
{
1998-
// algorithm based on https://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode
1999-
unchecked
2000-
{
2001-
int result = 61;
2002-
2003-
result = result * 397 + (MethodTargetType != null ? MethodTargetType.GetHashCode() : 0);
2004-
result = result * 397 + ParameterTypes.SequenceGetHashCode();
2005-
2006-
return result;
2007-
}
2008-
}
2014+
=> HashCode.Combine(MethodTargetType, ParameterTypes, GenericTypeParameters);
20092015

20102016
public override string ToString()
20112017
{
@@ -2018,6 +2024,22 @@ public override string ToString()
20182024
separator = " ";
20192025
}
20202026

2027+
if (GenericTypeParameters != null)
2028+
{
2029+
sb.Append(separator);
2030+
sb.Append("genericTypeParams: ");
2031+
2032+
separator = string.Empty;
2033+
foreach (Type parameter in GenericTypeParameters)
2034+
{
2035+
sb.Append(separator);
2036+
sb.Append(ToStringCodeMethods.Type(parameter, dropNamespaces: true));
2037+
separator = ", ";
2038+
}
2039+
2040+
separator = " ";
2041+
}
2042+
20212043
if (_parameterTypes != null)
20222044
{
20232045
sb.Append(separator);
@@ -5035,7 +5057,7 @@ internal struct Enumerator : IEnumerator<T>
50355057
private readonly PSMemberInfoInternalCollection<T> _allMembers;
50365058

50375059
/// <summary>
5038-
/// Constructs this instance to enumerate over members.
5060+
/// Initializes a new instance of the <see cref="Enumerator"/> class to enumerate over members.
50395061
/// </summary>
50405062
/// <param name="integratingCollection">Members we are enumerating.</param>
50415063
internal Enumerator(PSMemberInfoIntegratingCollection<T> integratingCollection)
@@ -5063,8 +5085,8 @@ internal Enumerator(PSMemberInfoIntegratingCollection<T> integratingCollection)
50635085
/// Moves to the next element in the enumeration.
50645086
/// </summary>
50655087
/// <returns>
5066-
/// false if there are no more elements to enumerate
5067-
/// true otherwise
5088+
/// If there are no more elements to enumerate, returns false.
5089+
/// Returns true otherwise.
50685090
/// </returns>
50695091
public bool MoveNext()
50705092
{
@@ -5093,7 +5115,7 @@ public bool MoveNext()
50935115
}
50945116

50955117
/// <summary>
5096-
/// Current PSMemberInfo in the enumeration.
5118+
/// Gets the current PSMemberInfo in the enumeration.
50975119
/// </summary>
50985120
/// <exception cref="ArgumentException">For invalid arguments.</exception>
50995121
T IEnumerator<T>.Current

src/System.Management.Automation/engine/parser/Compiler.cs

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,24 +1174,34 @@ internal static Type GetTypeConstraintForMethodResolution(ExpressionAst expr)
11741174
return firstConvert?.Type.TypeName.GetReflectionType();
11751175
}
11761176

1177-
internal static PSMethodInvocationConstraints CombineTypeConstraintForMethodResolution(Type targetType, Type argType)
1177+
internal static PSMethodInvocationConstraints CombineTypeConstraintForMethodResolution(
1178+
Type targetType,
1179+
Type argType,
1180+
Type[] genericArguments = null)
11781181
{
1179-
if (targetType == null && argType == null)
1182+
if (targetType is null
1183+
&& argType is null
1184+
&& (genericArguments is null || genericArguments.Length == 0))
11801185
{
11811186
return null;
11821187
}
11831188

1184-
return new PSMethodInvocationConstraints(targetType, new[] { argType });
1189+
return new PSMethodInvocationConstraints(targetType, genericArguments, new[] { argType });
11851190
}
11861191

1187-
internal static PSMethodInvocationConstraints CombineTypeConstraintForMethodResolution(Type targetType, Type[] argTypes)
1192+
internal static PSMethodInvocationConstraints CombineTypeConstraintForMethodResolution(
1193+
Type targetType,
1194+
Type[] argTypes,
1195+
Type[] genericArguments = null)
11881196
{
1189-
if (targetType == null && (argTypes == null || argTypes.Length == 0))
1197+
if (targetType is null
1198+
&& (argTypes is null || argTypes.Length == 0)
1199+
&& (genericArguments is null || genericArguments.Length == 0))
11901200
{
11911201
return null;
11921202
}
11931203

1194-
return new PSMethodInvocationConstraints(targetType, argTypes);
1204+
return new PSMethodInvocationConstraints(targetType, genericArguments, argTypes);
11951205
}
11961206

11971207
internal static Expression ConvertValue(TypeConstraintAst typeConstraint, Expression expr)
@@ -6338,17 +6348,47 @@ public object VisitMemberExpression(MemberExpressionAst memberExpressionAst)
63386348

63396349
internal static PSMethodInvocationConstraints GetInvokeMemberConstraints(InvokeMemberExpressionAst invokeMemberExpressionAst)
63406350
{
6341-
var arguments = invokeMemberExpressionAst.Arguments;
6351+
ReadOnlyCollection<ExpressionAst> arguments = invokeMemberExpressionAst.Arguments;
6352+
Type[] argumentTypes = null;
6353+
if (arguments is not null)
6354+
{
6355+
argumentTypes = new Type[arguments.Count];
6356+
for (var i = 0; i < arguments.Count; i++)
6357+
{
6358+
argumentTypes[i] = GetTypeConstraintForMethodResolution(arguments[i]);
6359+
}
6360+
}
6361+
63426362
var targetTypeConstraint = GetTypeConstraintForMethodResolution(invokeMemberExpressionAst.Expression);
6343-
return CombineTypeConstraintForMethodResolution(
6344-
targetTypeConstraint,
6345-
arguments?.Select(Compiler.GetTypeConstraintForMethodResolution).ToArray());
6363+
6364+
ReadOnlyCollection<ITypeName> genericArguments = invokeMemberExpressionAst.GenericTypeArguments;
6365+
Type[] genericTypeArguments = null;
6366+
if (genericArguments is not null)
6367+
{
6368+
genericTypeArguments = new Type[genericArguments.Count];
6369+
for (var i = 0; i < genericArguments.Count; i++)
6370+
{
6371+
genericTypeArguments[i] = genericArguments[i].GetReflectionType();
6372+
}
6373+
}
6374+
6375+
return CombineTypeConstraintForMethodResolution(targetTypeConstraint, argumentTypes, genericTypeArguments);
63466376
}
63476377

63486378
internal static PSMethodInvocationConstraints GetInvokeMemberConstraints(BaseCtorInvokeMemberExpressionAst invokeMemberExpressionAst)
63496379
{
63506380
Type targetTypeConstraint = null;
6351-
var arguments = invokeMemberExpressionAst.Arguments;
6381+
ReadOnlyCollection<ExpressionAst> arguments = invokeMemberExpressionAst.Arguments;
6382+
Type[] argumentTypes = null;
6383+
if (arguments is not null)
6384+
{
6385+
argumentTypes = new Type[arguments.Count];
6386+
for (var i = 0; i < arguments.Count; i++)
6387+
{
6388+
argumentTypes[i] = GetTypeConstraintForMethodResolution(arguments[i]);
6389+
}
6390+
}
6391+
63526392
TypeDefinitionAst typeDefinitionAst = Ast.GetAncestorTypeDefinitionAst(invokeMemberExpressionAst);
63536393
if (typeDefinitionAst != null)
63546394
{
@@ -6359,9 +6399,7 @@ internal static PSMethodInvocationConstraints GetInvokeMemberConstraints(BaseCto
63596399
Diagnostics.Assert(false, "BaseCtorInvokeMemberExpressionAst must be used only inside TypeDefinitionAst");
63606400
}
63616401

6362-
return CombineTypeConstraintForMethodResolution(
6363-
targetTypeConstraint,
6364-
arguments?.Select(Compiler.GetTypeConstraintForMethodResolution).ToArray());
6402+
return CombineTypeConstraintForMethodResolution(targetTypeConstraint, argumentTypes, genericArguments: null);
63656403
}
63666404

63676405
internal Expression InvokeMember(

0 commit comments

Comments
 (0)