question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

@() (array-subexpression operator) should not be optimized away when a cast to an array type is involved

See original GitHub issue

This is a follow-up to the discussion involving @daxian-dbw and @PetSerAl in #4257

Context:

  • PSv5.1 introduced a generally helpful optimization that optimizes @() away in cases where an array is directly constructed inside of it.

    • I presume that the motivation for this optimization was the widespread, but redundant and inefficient practice of enclosing ,-constructed arrays (“array literals”) in @(), such as in @(1, 2), where 1, 2 would not only suffice, but, in PSv5-, @() would actually (effectively) clone the array.
  • Unfortunately, this optimization is also applied when a cast to an array type (e.g, [int[]]) is involved, which can have unpleasant side effects - see below.
    Not optimizing in this scenario makes for conceptually clearer, predictable behavior.

  • While a change would technically be a breaking one, it is hard to imagine anyone having relied on the optimized behavior with casts - a typical Bucket 3: Unlikely Grey Area change.

    • Also note that when the optimization was introduced in PSv5.1, it was technically a breaking change too - though I suspect it wasn’t announced as such or even at all.

Steps to reproduce

@([int[]] (1, 2)).GetType().Name

$arr = 1, 2; @([int[]] $arr).GetType().Name

Expected behavior

Object[]
Object[]

Actual behavior

Int32[]
Int32[]

Note that versions up to and including v5 - before the optimization was introduced - behave as expected.

Why is the current behavior problematic?

The examples in this section are courtesy of @PetSerAl.

All examples are undoubtedly edge cases, but the larger point is that this behavior should never have been introduced, and, given its “stealth status”, this is an opportunity to get rid of it.

  • It subverts the fundamental assumption that @() always creates regular PowerShell arrays (System.Object[]):
> @([int[]] (1, 2))[-1] = 'foo'   # BREAKS, because the array is [int[]], not [object[]]
Cannot convert value "foo" to type "System.Int32".  ...
...

@PetSerAl points out that this fundamental assumption is even still reflected in other places in the source code: ArrayExpressionAst.StaticType (a @(...) expression is of type [System.Management.Automation.Language.ArrayExpressionAst]).

  • It subverts the expectation that @() always clones an array:
> $a = [int[]] (1, 2); $b = @([int[]] $a); [object]::ReferenceEquals($a, $b)
True    # $a and $b unexpectedly reference the same array.

Note that the accidental reference equality only happens ~if the source type and the cast type are identical~ if the cast type is either the same as the source type or a type that the source type is (directly or indirectly) derived from (covariance).

Thus, if the cast type is [object[]] and the source type is a reference type too, the problem always surfaces.

Environment data

PowerShell Core v6.0.0-beta.4 on macOS 10.12.5
PowerShell Core v6.0.0-beta.4 on Ubuntu 16.04.2 LTS
PowerShell Core v6.0.0-beta.4 on Microsoft Windows 10 Pro (64-bit; v10.0.15063)
Windows PowerShell v5.1.15063.413 on Microsoft Windows 10 Pro (64-bit; v10.0.15063)

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Reactions:1
  • Comments:31 (23 by maintainers)

github_iconTop GitHub Comments

1reaction
mklement0commented, Aug 3, 2017

@iSazonov: I think that a mention in the release notes should be sufficient, given that the original optimization was never documented and many people probably never noticed it.

1reaction
iSazonovcommented, Jul 20, 2017

Thanks for clarify. Continue 😄

$a = 1..3
$b = @($a = 1..4;[object[]]$a)
[object]::ReferenceEquals($a, $b)

$a = 1..3
$b = @([object[]]$a)
[object]::ReferenceEquals($a, $b)

Read more comments on GitHub >

github_iconTop Results From Across the Web

Surprising behavior of @() (array subexpression operator) ...
Right answer: them does not different, both write array into ... should not be optimized away when a cast to an array type...
Read more >
Objects with no '.Count' Property - use of @() (array ...
Objects with no '.Count' Property - use of @() (array subexpression operator) vs. [Array] cast · 2. $Result = @(Your command here) ·...
Read more >
Does the gcc optimize out local arrays the same way it ...
"Optimized out" means the compiler realises the variable isn't necessary, and deletes the variable. For example, this code:
Read more >
3.11 Options That Control Optimization
This option tells the loop optimizer to use language constraints to derive bounds for the number of iterations of a loop. This assumes...
Read more >
Optimizing C and C++ code
In this article we will explore optimization techniques for C and C++ code ... When arrays of structures are involved, the compiler performs...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found