-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Conversions from PSMethod to Delegate #5287
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Underpinnings to make calling of Extension methods /Linq
easier from PowerShell.
Enables the following that previously had to be done via
reflection.
class M {
[int] Twice([int] $value) { return 2 * $value }
[int] DoubleSum([int[]] $values) {
return [Linq.Enumerable]::Sum($values, [M]::Twice)
}
}
Each PSMethod is created as with a unique type for the combinations of
method signatures in the MethodInfos it represents.
PSMethod<T> where T is a MethodGroup<>, potentially recursive in the
last template argument.
This way, we can determine by just looking at the type of a PSMethod
if there exists a conversion from the PSMethod to a delegate.
| ## To prevent unraveling, Command needs to be `{, [List[int]]@(1,2)}`, but then the test case title | ||
| ## would become `, [List[int]]@(1,2)`, which is more confusing than `$result = [List[int]]@(1,2)`. | ||
| ## This is why the current form of `$result = [List[int]]@(1,2)` is used intentionally here. | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
VSCode formats this.
Is it OK to keep this formatting?
Or is there a setting that aligns one line hashtables?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In comments usualy we use ' not `.
Also we should put the comment before $testCases1 and start it with one #
| @{ Command = {$result = [Collections.Generic.List[int]]@(1,2)}; CollectionType = 'List`1'; ElementType = "Int32"; Elements = @(1,2) } | ||
| @{ Command = {$result = [Collections.Generic.List[int]]"4"}; CollectionType = 'List`1'; ElementType = "Int32"; Elements = @(4) } | ||
| @{ Command = {$result = [Collections.Generic.List[int]]@("4","5")}; CollectionType = 'List`1'; ElementType = "Int32"; Elements = @(4,5) } | ||
| @{ Command = {$result = [Collections.Generic.List[int]]@(1)}; CollectionType = 'List`1'; ElementType = "Int32"; Elements = @(1) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please keep the previous formatting pattern - it is more readable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find git add -p very useful to add just my changes and not the formatting changes.
| @{ Command = {$result = [Collections.Generic.List[System.IO.FileInfo]]@('TestFile')}; | ||
| CollectionType = 'List`1'; ElementType = "FileInfo"; Elements = @('TestFile') } | ||
| CollectionType = 'List`1'; ElementType = "FileInfo"; Elements = @('TestFile') | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should avoid formatting changes.
Please revert such formatting changes in the file not related to the PR.
|
|
||
| } | ||
|
|
||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove extra line. Below too.
| $f = [Func[int, int16, int]] [M]::Add | ||
| $f.Invoke(2, 6) | Should be 8 | ||
| } | ||
| It "can call all overloads of M::Foo" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add new line.
| [Func[TimeSpan, TimeSpan, TimeSpan, TimeSpan, TimeSpan]] $f24 = [M]::Foo | ||
| $f24.Invoke($timeSpan, [Timespan]::Zero, [Timespan]::Zero, [Timespan]::Zero) | Should BE $timeSpan | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove extra line.
| 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 Unit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Private class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private is not allowed as a modifier. It is implicit.
| return MoveNext(_t, _current); | ||
| } | ||
|
|
||
| bool MoveNext(Type type, int index) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Public? Private?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a good reason for this private method? It's only called in one place.
| return true; | ||
| } | ||
|
|
||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove extra line.
| { | ||
| return new PSMethod<T>(name, adapter, baseObject, adapterData, isSpecial, isHidden); | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove extra line.
|
|
||
| } | ||
|
|
||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove extra line.
lzybkr
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good - just a couple of small complaints.
This must have been a lot of fun to implement - nice work.
| : candidate.CreateDelegate(resultType, psMethod.instance); | ||
| } | ||
| } | ||
| var msg = $"No matching overload found from {psMethod} to {resultType}"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strings that appear in error messages need to be in a resource for localization.
| return _returnType == typeof(void) ? returnType == typeof(Unit) : _returnType.IsAssignableFrom(returnType); | ||
| } | ||
|
|
||
| private bool ParameterTypeMatches(ParameterInfo[] arguments) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ParameterTypesMatches?
| return Create(name, dotNetInstanceAdapter, baseObject, method, false, false); | ||
| } | ||
|
|
||
| internal static PSMethod Create(string name, DotNetAdapter dotNetInstanceAdapter, object baseObject, DotNetAdapter.MethodCacheEntry method, bool isSpecial, bool isHidden) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm considering imposing a limit on line length :) - working on a laptop this week and GitHub isn't letting me scroll to see the rest of this line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
4k 27" monitor may have desensitized me to the issue :)
| case 63: return typeof(MethodGroup<,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,>).MakeGenericType(types[0], types[1], types[2], types[3], types[4], types[5], types[6], types[7], types[8], types[9], types[10], types[11], types[12], types[13], types[14], types[15], types[16], types[17], types[18], types[19], types[20], types[21], types[22], types[23], types[24], types[25], types[26], types[27], types[28], types[29], types[30], CreateMethodGroup(types, 31, 32)); | ||
|
|
||
| } | ||
| throw Assert.Unreachable; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should have a real error or at least NotSupported exception.
And FYI - I've seen real code with > 300 parameters - code from a company I'd like to name but won't. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be a problem? I don't really like having these arbitrary constants as limitations.
Is that 300 generic type parameters?
I took some of the limitations from the choises already made for Func/Action, but 63 was just a bit higher than the 58, which was the hightest number of overloads any type in the assemblies loaded by Windows Powershell used.
I am very open to suggestions here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, not type parameters, just normal. But I think what you have is fine, just need a better error.
| case 15: return typeof(PSGenericType15); | ||
| case 16: return typeof(PSGenericType16); | ||
| } | ||
| throw Assert.Unreachable; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Real error here - and must resist urge to think of template metaprogramming to solve this problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah - templates != generics. Or do you have some tricks up your sleave that I don't know about?
Hardcoded up to a limit, and dynamic type generation after that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, but hard coded is fine. MutableTuple has an interesting approach if you want to explore it - basically you have:
PSGenericType
PSGenericType<T1, T2>
PSGenericType<T1, T2, T3, T4>
PSGenericType<T1, T2, T3, T4, T5, T6, T7, T8>
etc.
And if your list is too long, you encode the last parameter as another PSGenericType<...>.
If you don't have a power of 2, you use placeholder types like you've done with Unit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually already do that to an extent with MethodGroup. Maybe I could rewrite it to be more general.
| @{ Command = {$result = [Collections.Generic.List[int]]@(1,2)}; CollectionType = 'List`1'; ElementType = "Int32"; Elements = @(1,2) } | ||
| @{ Command = {$result = [Collections.Generic.List[int]]"4"}; CollectionType = 'List`1'; ElementType = "Int32"; Elements = @(4) } | ||
| @{ Command = {$result = [Collections.Generic.List[int]]@("4","5")}; CollectionType = 'List`1'; ElementType = "Int32"; Elements = @(4,5) } | ||
| @{ Command = {$result = [Collections.Generic.List[int]]@(1)}; CollectionType = 'List`1'; ElementType = "Int32"; Elements = @(1) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find git add -p very useful to add just my changes and not the formatting changes.
…eric method Refactoring to move generation and test of method signature to the same place.
|
I would still like a discussion about what's the right thing to do methods with more than 63 overloads and or more than 16 generic parameters. These are for sure edge cases, but it would be nice to at least handle them gracefully |
|
@powercode - if you want full generality, as long as To be concrete, say we only support 1 or 2 methods in Using this trick, you could use a single generic type to represent all generic type parameters, the length of the list would encode T0, T1, etc. |
|
@lzybkr I kinda already to that: It is recursive in the last parameter. |
|
I just learned that I need to have a representation of Span too. That special class cannot be used as a generic argument. https://github.com/dotnet/corefxlab/blob/master/docs/specs/span.md |
|
@lzybkr I will explore the |
Handling more cases and having a Func<PSNonBindable> fallback for the cases that I don't handle (Mostly Methods for open generic types and Generic Methods with type constraints.
Correct spelling
Correct formatting
Fix formatting
|
|
||
| internal struct PSMethodSignatureEnumerator : IEnumerator<Type> | ||
| { | ||
| private int _current; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_current is not the backing field for Current, you should rename _current to avoid any confusion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point
lzybkr
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just had one naming nit that should be addressed.
renaming _current -> _currentIndex
iSazonov
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with 3 minor comment.
| <data name="PSMethodToDelegateNoMatchingOverLoad" xml:space="preserve"> | ||
| <value>No matching overload found from {0} to {1}</value> | ||
| </data> | ||
| </root> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add new line at EOF.
| [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 | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove extra line.
| valueToConvert.ToString(), resultType.ToString(), exception.Message); | ||
| } | ||
|
|
||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove extra line.
daxian-dbw
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for this late review. I will address the comments myself and also start a feature test run.
| { | ||
| var mi = toType.GetMethod("Invoke"); | ||
|
|
||
| var isAction = mi.ReturnType == typeof(void); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The isAction variable is not used anywhere.
| return typeof(Func<PSNonBindableType>); | ||
| } | ||
| } | ||
| if (methodInfo.DeclaringType.IsGenericTypeDefinition) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check should be moved to the top. If the methodInfo is a generic method from a generic type definition, we will be wasting time calling ReplaceGenericTypeArgumentsWithMarkerTypes.
| var returnType = methodInfo.ReturnType == typeof(void) ? typeof(Unit) : GetPSMethodTypeProjection(methodInfo.ReturnType); | ||
| res[parameterInfos.Length] = returnType; | ||
|
|
||
| if (res.Length > 17) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check should be moved to before the 'for' loop: if (parameterInfos.Length > 16)
| res[i] = GetPSMethodTypeProjection(parameterType, | ||
| (parameterInfo.Attributes | ParameterAttributes.Out) == ParameterAttributes.Out); | ||
| } | ||
| var returnType = methodInfo.ReturnType == typeof(void) ? typeof(Unit) : GetPSMethodTypeProjection(methodInfo.ReturnType); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GetPSMethodTypeProjection(typeof(void)) will return typeof(Unit), so no need to use a conditional operator here.
| { | ||
| if (type == typeof(void)) | ||
| { | ||
| type = typeof(Unit); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you mean return typeof(Unit).
A few further improvement points
Two questions about the design
|
|
@daxian-dbw - The use of If you have a conversion from If it is type based, then there is no conversion from Put another way, the |
|
@lzybkr That makes perfect sense now. I forgot about the conversion cache.
Can you please also shed some light on the |
|
The failed tests in macOS build are known test failures. It's tracked by #5590. |
|
@powercode will have to address |
|
@powercode I will merge this PR, but let's continue the discussion if you have time 😄 |
|
Sorry all for my horrible response time. I have to revisit the code and look at it. Some of the special cases, like |
|
Should we document the feature? |
Underpinnings to make calling of Extension methods /Linq
easier from PowerShell.
Enables the following that previously had to be done via
reflection.
Each PSMethod is created as with a unique type for the combinations of
method signatures in the MethodInfos it represents.
PSMethod where T is a MethodGroup<>, potentially recursive in the
last template argument.
This way, we can determine by just looking at the type of a PSMethod
if there exists a conversion from the PSMethod to a delegate.