Fix array expression to not return null or throw error#4296
Fix array expression to not return null or throw error#4296daxian-dbw merged 8 commits intoPowerShell:masterfrom
Conversation
| It "@([object[]](1,2,3)) should return a 3-element array of object[]" { | ||
| $result = @([object[]](1,2,3)) | ||
| $result.GetType().FullName | Should Be "System.Object[]" | ||
| $result.Length | Should Be 3 |
There was a problem hiding this comment.
I believe we should check $result.Length before $result.GetType() in all test here. This is not important in these tests, but it is a bad pattern for other tests in which the $result are calculated. (I mean that somebody can copy-paste the bad pattern).
There was a problem hiding this comment.
What do you mean by the $result are calculated? Can you please elaborate a bit and give an example?
There was a problem hiding this comment.
From Get-Content.Tests.ps1:
It "should Get-Content with a variety of -Tail and -ReadCount values" {#[DRT]
set-content -path $testPath "Hello,World","Hello2,World2","Hello3,World3","Hello4,World4"
$result=get-content -path $testPath -readcount:-1 -tail 5
$result.Length | Should Be 4There was a problem hiding this comment.
Can we use Should BeOfType?
There was a problem hiding this comment.
To use Should BeOfType I need to wrap $result in a new array because it will be unraveled otherwise. I feel it's clearer to just use GetType().FullName in this case.
As for the bad pattern, I'm still confused. Why do you think putting $result.Length | Should Be 3 after the type check would be a bad pattern? More technically speaking, the length check should happen after we know $result is an array, otherwise we might (actually impossible) end up checking the Length property of some random type :)
There was a problem hiding this comment.
function foo {$null}
$res = foo
$res.GetType()
You cannot call a method on a null-valued expression.
At line:1 char:1
+ $res.GetType()
+ ~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNullThere was a problem hiding this comment.
Ah, I see your point. A Should Not BeNullOrEmpty check should be done in that case.
| } | ||
|
|
||
| It "@([int[]](1,2,3)) should return a 3-element array of object[]" { | ||
| $result = @([int[]](1,2,3)) |
There was a problem hiding this comment.
Should we test complex types? PSCustomObject?
There was a problem hiding this comment.
For any array type other than [object[]], it will fall in the code path of PSToObjectArrayBinder. I just use [int[]] as a simple validation.
There was a problem hiding this comment.
Sorry, I mean member types.
There was a problem hiding this comment.
Oh, the member type doesn't matter for the scenario of this fix. It's OK as long as the conversion works.
|
Should we add tests for |
| values = values ?? CaptureAstResults(subExpr, CaptureAstContext.Enumerable); | ||
|
|
||
| if (values.Type.IsArray) | ||
| if (values.Type == typeof(object[]) || values.Type == typeof(List<object>)) |
There was a problem hiding this comment.
The goal of the optimization was to cover @(1,2) - so maybe the original test was too general. This fix ends up adding unnecessary overhead to the optimization by cloning and adding an if test.
I think a better fix is to just constrain the optimization more - basically if (subExpr is ArrayLiteralAst).
I don't recall why I added the List<object> case.
There was a problem hiding this comment.
The clone was added to address a comment raised by @PetSerAl about $a = 1,2,3; @([object[]]$a) doesn't return a new array. But if we only consider if (pureExpr is ArrayLiteralAst) then the clone and the null check are both not necessary anymore.
The List<object> case seems like an optimization for CaptureAstResults, because a List<object> instance will be created to hold the objects written to pipe in CaptureAstResults.
Are you suggesting to not cover the cases of values.Type == typeof(object[]) and values.Type == typeof(List<object>) here?
There was a problem hiding this comment.
Replace the cases of values.Type.IsArray and values.Type == typeof(List<object>) with if (pureExprAst is ArrayLiteralAst). So the former cases will be handled by the dynamic site.
lzybkr
left a comment
There was a problem hiding this comment.
I think the change is fine.
I should add that the intent of @() is not to create a new array, but to ensure the result is an array, and not necessarily an object[].
In other words, I think we could be open to changing what the language specification says to allow for better performance.
The last commit removed the |
Fix #4280
Summary
This PR focuses on 3 issues:
@(...)should only returnobject[]array.@([object[]]$null).GetType()fails with error"You cannot call a method on a null-valued expression."@([System.Collections.Generic.List[object]]$null)fails with error"Object reference not set to an instance of an object."Marked with label
Review-Committeeto review the first issue.