@() (array-subexpression operator) should not be optimized away when a cast to an array type is involved
See original GitHub issueThis 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), where1, 2would not only suffice, but, in PSv5-,@()would actually (effectively) clone the array.
- I presume that the motivation for this optimization was the widespread, but redundant and inefficient practice of enclosing
-
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:
- Created 6 years ago
- Reactions:1
- Comments:31 (23 by maintainers)

Top Related StackOverflow Question
@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.
Thanks for clarify. Continue 😄