Skip to content

Fix array expression to not return null or throw error - #4296

Merged
Dongbo Wang (daxian-dbw) merged 8 commits into
PowerShell:masterfrom
daxian-dbw:array
Jul 25, 2017
Merged

Fix array expression to not return null or throw error#4296
Dongbo Wang (daxian-dbw) merged 8 commits into
PowerShell:masterfrom
daxian-dbw:array

Conversation

@daxian-dbw

@daxian-dbw Dongbo Wang (daxian-dbw) commented Jul 19, 2017

Copy link
Copy Markdown
Member

Fix #4280

Summary

This PR focuses on 3 issues:

  1. According to PowerShell Language Specification Version 3.0, as quoted: "The result is the (possibly empty) unconstrained 1-dimensional array", @(...) should only return object[] array.
  2. @([object[]]$null).GetType() fails with error "You cannot call a method on a null-valued expression."
  3. @([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.

@daxian-dbw Dongbo Wang (daxian-dbw) added the Review - Committee The PR/Issue needs a review from the PowerShell Committee label Jul 19, 2017
@daxian-dbw Dongbo Wang (daxian-dbw) changed the title Array Fix array expression to not return null or throw error Jul 19, 2017
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

Copy link
Copy Markdown
Collaborator

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).

Copy link
Copy Markdown
Member Author

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?

Copy link
Copy Markdown
Collaborator

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

Copy link
Copy Markdown
Collaborator

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?

Copy link
Copy Markdown
Member Author

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 :)

Copy link
Copy Markdown
Collaborator

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

Copy link
Copy Markdown
Member Author

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))

Copy link
Copy Markdown
Collaborator

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?

Copy link
Copy Markdown
Member Author

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.

Copy link
Copy Markdown
Collaborator

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.

Copy link
Copy Markdown
Member Author

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.

@iSazonov

Copy link
Copy Markdown
Collaborator

Should we add tests for @($var) ? It seems absent.

values = values ?? CaptureAstResults(subExpr, CaptureAstContext.Enumerable);

if (values.Type.IsArray)
if (values.Type == typeof(object[]) || values.Type == typeof(List<object>))

@lzybkr Jason Shirk (lzybkr) Jul 24, 2017

Copy link
Copy Markdown
Contributor

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.

@daxian-dbw Dongbo Wang (daxian-dbw) Jul 24, 2017

Copy link
Copy Markdown
Member Author

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?

Copy link
Copy Markdown
Member Author

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.

@lzybkr Jason Shirk (lzybkr) left a comment

Copy link
Copy Markdown
Contributor

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.

@daxian-dbw

Copy link
Copy Markdown
Member Author

the intent of @() is not to create a new array, but to ensure the result is an array

The last commit removed the [object]::ReferenceEquals check from the test per Jason's comment.
But the check for System.Object[] is kept as we haven't changed the language specification yet.

@daxian-dbw
Dongbo Wang (daxian-dbw) merged commit 0d8eff6 into PowerShell:master Jul 25, 2017
@SteveL-MSFT Steve Lee (SteveL-MSFT) removed the Review - Committee The PR/Issue needs a review from the PowerShell Committee label Aug 16, 2018
Thatgfsj (Thatgfsj) pushed a commit to Thatgfsj/PowerShell that referenced this pull request Aug 6, 2026
This change fixes 3 issues:
- According to [PowerShell Language Specification Version 3.0](https://www.microsoft.com/en-us/download/details.aspx?id=36389), as quoted: "_The result is the (possibly empty) unconstrained 1-dimensional array_", `@(...)` should only return `object[]` 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."`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants