Skip to content

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

@mklement0

Description

@mklement0

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue-Discussionthe issue may not have a clear classification yet. The issue may generate an RFC or may be reclassifResolution-AnsweredThe question is answered.WG-Languageparser, language semantics

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions