-
Notifications
You must be signed in to change notification settings - Fork 7.6k
Fix array expression to not return null or throw error #4296
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
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 4
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 we use Should BeOfType?
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 : InvokeMethodOnNull
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we test complex types? PSCustomObject?
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I mean member types.
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, 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 |
@@ -5500,16 +5502,28 @@ public object VisitArrayExpression(ArrayExpressionAst arrayExpressionAst) | |||
} | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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 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-Committee
to review the first issue.