You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Enhancement: Ability to invoke generic methods with explicit type parameters when PowerShell cannot figure out <T> from the context #5146
(Array.Empty was chosen as a simplest possible example. In scripts Iʼd use @())
Current behavior
At line:1 char:16
+ [Array]::Empty[string]()
+ ~
Array index expression is missing or not valid.
At line:1 char:16
+ [Array]::Empty[string]()
+ ~~~~~~~
Unexpected token 'string]' in expression or statement.
At line:1 char:24
+ [Array]::Empty[string]()
+ ~
An expression was expected after '('.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingArrayIndexExpression
Ruminations
I understand that itʼs not important to have full support of generics in a scripting language or to have 100% feature parity with C# (I might be wrong, — now we have classes in PowerShell after all 🤔)
Here are some not-very-elegant workarounds from the Interwebs:
At first glance, this is not worthwhile because the main asset of PowerShell is to hide complex details - Powershell automatically calls the correct methods.
As for implementation it seems is a lot of work but not so difficult.
Reacted by Joel Bennett, Vitalii Maklai, Amar Sood, Doug Finke, Thomas Nieto, Kasper Fabæch Brandt, Jason Rowe and Daniele Grandini
Ilya (@iSazonov) Problem is that PowerShell can not automatically call correct methods when there is no way to deduce generic type parameter automatically (as seen in "Steps to demonstrate"). It must be specified explicitly. This is true for other CLR languages too.
We are forced to dig through all the complex details using reflection (see list of workarounds).
Reacted by Joel Bennett, Vitalii Maklai, Michael Klement, Thomas Nieto and Ryouko Konpaku
I mean that the implementation of the general case is likely to be redundant. On the other hand, if there are specific popular scenarios, maybe we could implement them natively in PowerShell.
We also have to take into account that PowerShell performs many implicit type conversions - we may encounter insurmountable hurdles.
This is totally a reasonable thing to add, and a perfect example of why I'm excited PowerShell is open source.
People do want this feature, but as you point out, it's not critical for a shell. With limited bandwidth, this feature was never quite important enough for a small team to implement, but that's not an issue anymore - there are plenty of motivated and capable people who might add this feature now.
For anyone willing to tackle this:
I think the proposed (and obvious) syntax should work, but you must be careful to not break array references, e.g.
When you first see the [, you can't know if you are expecting an expression or a type argument list.
We will need a new property in MemberAst. It might not be obvious why here and not InvokeMemberAst, but sometimes you refer to a method without arguments so you can call it later, e.g.
$op="hello".PadLeft
$op.Invoke(15)
It's probably not important to support the equivalent for generic methods in the initial implementation, but it is important that the representation support it should we want that in the future.
This type probably needs a new property to hold the specified type arguments, which would get added from the Ast here.
Hopefully this is enough to get someone started, if more help is needed, just ask.
Reacted by Flavien MICHALECZEK and Giorgi Chakhidze
I'll take a look when I have some time. It'll certainly be easier than writing PSGenericMethods was, since I can just use the type conversion / binding code / overload identification that's already there instead of having to reinvent it.
This makes it easier to see what's happening at parse-time, and we only need to modify InvokeMemberExpressionAst to support it. If we modify MemberExpressionAst instead (allowing for $op = "Hello".SomeGenericMethod[someType]), it's tricker to distinguish between a property followed by an array index versus a method with a generic type. By looking for MethodName[type](args), it's not ambiguous (parser currently will complain about the open parenthesis).
Because a pipeline isn't allowed as the index expression, I don't think there is any ambiguity - one token of look ahead is sufficient - if could be a type argument list, it is, otherwise it must be an expression.
If a pipeline was allowed, a bare word is ambiguous - is it a type or an command invocation.
Note that one token look ahead doesn't mean there is only one token to look for, there is at least a bare word and an '[', e.g.
I might be biting off more than I can chew on this one. I've tweaked the parser and AST to support the syntax, but I'm having trouble wrapping my head around the compiler and binders to see where to make use of the new information from the AST. Will sleep on it.
Would it make sense to create a new PSGenericMethodInvocationConstraints to store the additional type arguments needed for a generic method as a separate value to the types of the method parameters?
If it's either / or, the first seems much preferable; if we only support type literals, then confusion with numeric indexing shouldn't be a concern ([Array]::Empty[0]); also, even multiple overloads are reported as a singlePSMethod`1 object, so indexing isn't useful anyway.
I think that the latter is simpler to implement as it doesn't require parser changes. It could be implemented as a halfway measure, handling the actual logic of invoking the generic method first, and then we can tackle the parser challenge afterwards.
Rain Sallow (/u/ta11ow) (@vexx32) - the sharing of logic between invoking methods on a PSMethod and invoking methods the normal way is less than ideal. That's not to say you shouldn't bother implementing the former, just don't get your hopes up that the problem is half solved if you go that route.
Dave Wyatt (@dlwyatt) thanks for that! After resolving a few merge conflicts I was able to get that code rebased on top of the current master branch, and with a few nudges from Patrick Meinecke (@SeeminglyScience) here and there on some of the more confusing bits, I've got a working implementation.
Tidying it up and figuring out some tests to write, and then I'll throw up a PR.
This is already sorted in #12412, it's pending a followup on the review. As far as I know, I've sorted all the outstanding comments out, unless I missed something.
Reacted by Oisin Grehan, Giorgi Chakhidze, Patrick Meinecke and Staffan Gustafsson
Activity
iSazonov commentedon Oct 20, 2017
At first glance, this is not worthwhile because the main asset of PowerShell is to hide complex details - Powershell automatically calls the correct methods.
As for implementation it seems is a lot of work but not so difficult.
0xfeeddeadbeef commentedon Oct 20, 2017
Ilya (@iSazonov) Problem is that PowerShell can not automatically call correct methods when there is no way to deduce generic type parameter automatically (as seen in "Steps to demonstrate"). It must be specified explicitly. This is true for other CLR languages too.
We are forced to dig through all the complex details using reflection (see list of workarounds).
iSazonov commentedon Oct 20, 2017
I mean that the implementation of the general case is likely to be redundant. On the other hand, if there are specific popular scenarios, maybe we could implement them natively in PowerShell.
We also have to take into account that PowerShell performs many implicit type conversions - we may encounter insurmountable hurdles.
lzybkr commentedon Oct 20, 2017
This is totally a reasonable thing to add, and a perfect example of why I'm excited PowerShell is open source.
People do want this feature, but as you point out, it's not critical for a shell. With limited bandwidth, this feature was never quite important enough for a small team to implement, but that's not an issue anymore - there are plenty of motivated and capable people who might add this feature now.
For anyone willing to tackle this:
When you first see the
[, you can't know if you are expecting an expression or a type argument list.InvokeMemberAst, but sometimes you refer to a method without arguments so you can call it later, e.g.It's probably not important to support the equivalent for generic methods in the initial implementation, but it is important that the representation support it should we want that in the future.
Hopefully this is enough to get someone started, if more help is needed, just ask.
dlwyatt commentedon Oct 20, 2017
I'll take a look when I have some time. It'll certainly be easier than writing PSGenericMethods was, since I can just use the type conversion / binding code / overload identification that's already there instead of having to reinvent it.
dlwyatt commentedon Oct 20, 2017
Jason Shirk (@lzybkr) , how would you feel about this syntax for the PSMethod reference option:
This makes it easier to see what's happening at parse-time, and we only need to modify InvokeMemberExpressionAst to support it. If we modify MemberExpressionAst instead (allowing for
$op = "Hello".SomeGenericMethod[someType]), it's tricker to distinguish between a property followed by an array index versus a method with a generic type. By looking forMethodName[type](args), it's not ambiguous (parser currently will complain about the open parenthesis).lzybkr commentedon Oct 20, 2017
Because a pipeline isn't allowed as the index expression, I don't think there is any ambiguity - one token of look ahead is sufficient - if could be a type argument list, it is, otherwise it must be an expression.
If a pipeline was allowed, a bare word is ambiguous - is it a type or an command invocation.
Note that one token look ahead doesn't mean there is only one token to look for, there is at least a bare word and an '[', e.g.
The extra square brace is there for a corner case where you want to specify the assembly name along with multiple type arguments, e.g.
dlwyatt commentedon Oct 20, 2017
Hmm... playing devil's advocate for a second, do we want to support variables in the type expression?
If so, then this could be ambiguous:
lzybkr commentedon Oct 21, 2017
That seemed useful in V2, but it's been that long since I've seriously considered it.
And honestly, if we wanted to support that, I'd look for a different syntax for explicit type parameters.
dlwyatt commentedon Oct 21, 2017
ok, cool. I'll be on a plane for 4-5 hours tomorrow morning; if I'm not feeling too groggy (waking up at 4am, bleh), I'll plug away at it a bit.
dlwyatt commentedon Oct 23, 2017
I might be biting off more than I can chew on this one. I've tweaked the parser and AST to support the syntax, but I'm having trouble wrapping my head around the compiler and binders to see where to make use of the new information from the AST. Will sleep on it.
10 remaining items
vexx32 commentedon Apr 17, 2020
Would it make sense to create a new
PSGenericMethodInvocationConstraintsto store the additional type arguments needed for a generic method as a separate value to the types of the method parameters?lzybkr commentedon Apr 17, 2020
Rain Sallow (/u/ta11ow) (@vexx32) - probably not - just add a new field to the existing type.
mklement0 commentedon Apr 17, 2020
Jason Shirk (@lzybkr), Dave Wyatt (@dlwyatt), I'm unclear on what the consensus ended up being regarding the syntax:
vs.
or both?
If it's either / or, the first seems much preferable; if we only support type literals, then confusion with numeric indexing shouldn't be a concern (
[Array]::Empty[0]); also, even multiple overloads are reported as a singlePSMethod`1object, so indexing isn't useful anyway.vexx32 commentedon Apr 17, 2020
I think that the latter is simpler to implement as it doesn't require parser changes. It could be implemented as a halfway measure, handling the actual logic of invoking the generic method first, and then we can tackle the parser challenge afterwards.
Jason Shirk (@lzybkr) summarised the possible challenges better than I can in his comment: #5146 (comment)
lzybkr commentedon Apr 17, 2020
Rain Sallow (/u/ta11ow) (@vexx32) - the sharing of logic between invoking methods on a
PSMethodand invoking methods the normal way is less than ideal. That's not to say you shouldn't bother implementing the former, just don't get your hopes up that the problem is half solved if you go that route.dlwyatt commentedon Apr 18, 2020
vexx32 commentedon Apr 19, 2020
Dave Wyatt (@dlwyatt) thanks for that! After resolving a few merge conflicts I was able to get that code rebased on top of the current master branch, and with a few nudges from Patrick Meinecke (@SeeminglyScience) here and there on some of the more confusing bits, I've got a working implementation.
Tidying it up and figuring out some tests to write, and then I'll throw up a PR.
oising commentedon May 3, 2021
Rain Sallow (/u/ta11ow) (@vexx32) Bump!
vexx32 commentedon May 3, 2021
This is already sorted in #12412, it's pending a followup on the review. As far as I know, I've sorted all the outstanding comments out, unless I missed something.
oising commentedon May 3, 2021
Nice work man!
mikeTWC1984 commentedon Jul 21, 2021
Wondering, what is the status of #12412 ? Is it dead?
vexx32 commentedon Jul 29, 2021
I've rebased it & asked the other members of the Engine working group to give it a look over. 🙂