PowerShell icon indicating copy to clipboard operation
PowerShell copied to clipboard

Member-access enumeration doesn't work with numeric hashtable keys

Open mklement0 opened this issue 3 years ago • 17 comments

Prerequisites

Steps to reproduce

Undoubtedly an edge case, but it makes me wonder whether other discrepancies between single-object dot notation (property access) and member-enumeration-based dot notation exist:

@{ 42 = 'foo' }.42           # OK -> 'foo'

(@{ 42 = 'foo1' }, @{ 42 = 'foo2' }).42  -join ', ' # !! No output

Note that the problem does not occur with string-valued hashtable keys (which is what's typical), e.g. (@{ a42 = 'foo1' }, @{ a42 = 'foo2' }).a42 -join ', ' Similarly, the following works as expected: ([pscustomobject] @{ 42 = 'foo1' }, [pscustomobject] @{ 42 = 'foo2' }).42 -join ', '

Expected behavior

foo
foo1, foo2

Actual behavior

foo

Error details

No response

Environment data

PowerShell Core 7.3.0-preview.4

Visuals

No response

mklement0 avatar Jun 11 '22 16:06 mklement0

Supplementary

  @{  42  = 'foo' }.42      #Returns "foo"
 (@{  42  = 'foo' }).42     #Returns "foo"
@(@{  42  = 'foo' }).42     #Returns nothing
@(@{ "42" = 'foo' }).42     #Returns "foo"

It appears the member operator converts its operand to a string when processing members of an array.

jhoneill avatar Jun 13 '22 09:06 jhoneill

It never worked because "selector" .42 is expected to be a string, so expression (@{ 42 = 'foo1' }, @{ 42 = 'foo2' }).42 is incorrect itself

scriptingstudio avatar Jun 13 '22 16:06 scriptingstudio

@scriptingstudio, please note the first example in the OP, which does work: @{ 42 = 'foo' }.42 - the issue at hand is that with member-access enumeration it doesn't.

mklement0 avatar Jun 13 '22 16:06 mklement0

To put it differently: the promise of member-access enumeration is that:

(@{ 42 = 'foo1' }, @{ 42 = 'foo2' }).42 # !! BROKEN

is shorthand for:

@{ 42 = 'foo1' }, @{ 42 = 'foo2' } | ForEach-Object { $_.42 }   # OK

Clearly, the promise isn't being fulfilled here.

mklement0 avatar Jun 13 '22 16:06 mklement0

That is NOT shorthand, because your array is not array of psobjects

scriptingstudio avatar Jun 13 '22 16:06 scriptingstudio

psobject property type is always a string, but a hashtabe is a diff story

scriptingstudio avatar Jun 13 '22 16:06 scriptingstudio

This issue is about the behavior of the member-access operator, . - it doesn't matter what .NET type it operates on, and that with hashtables, as a syntactic convenience, it allows accessing entries as if they were properties.

The point is that if a given . access works with a given scalar, it should work via member-access enumeration on a collection of such scalars.

Otherwise, we're faced with a confusing inconsistency for which I see no justification; see also: https://github.com/PowerShell/PowerShell/issues/17514.

mklement0 avatar Jun 13 '22 17:06 mklement0

It never worked because "selector" .42 is expected to be a string, so expression (@{ 42 = 'foo1' }, @{ 42 = 'foo2' }).42 is incorrect itself

@scriptingstudio yes., that would be fine if @{ 42 = 'foo1' }.42 did not work either.

@mklement0 $h = @{ 42 = 'foo1' } ; $h.42
returns nothing but $h = @{ "42" = 'foo1' } ; $h.42 returns foo1 so it looks like its the @{}. combination is processed differently from everything else - it's no applicable to other hashtables

 (@{ 42 = 'foo1' }, @{ 42 = 'foo2' } | ForEach-Object { $_.42 } 

Outputs nothing.

jhoneill avatar Jun 13 '22 19:06 jhoneill

@jhoneill, I think you swapped the two scenarios:

$h = @{ 42 = 'foo1' } ; $h.42   # -> 'foo1': property "name" type ([int]) matches the type of the hash-entry key.

$h = @{ '42' = 'foo1' } ; $h.42   # -> $null; property "name" type does *not* match

# Quoting fixes the mismatch
$h = @{ '42' = 'foo1' } ; $h.'42'   # -> 'foo1'

This distinction is tricky, for sure, and specific to hash tables, but it has always worked this way - and it is a separate issue from the one at hand.

mklement0 avatar Jun 13 '22 20:06 mklement0

@mklement0 image

That's what I got which showed $h.42 converted 42 to a string

jhoneill avatar Jun 13 '22 20:06 jhoneill

As an aside: while switching to [pscustomobject] avoids this particular problem - $o = [pscustomobject] @{ '42' = 'foo1' } ; $o.42 works without needing to quote the property name - the fact that a property name is generally parsed as a number literal if it looks like one can also break [pscustomobject] access; e.g.:

$o = [pscustomobject] @{ '1L' = 'foo1'; '1' = 'why me?' } ; $o.1L  # -> !! 'why me?'

That is, the 1L in .1L was parsed as a [long] with value 1, and the stringified version of it is just '1', so that was the property that was accessed.

Or, for even more fun:

 $o = [pscustomobject] @{ '15' = 'why me?' } ; $o.0xf # -> !! 'why me?'

mklement0 avatar Jun 13 '22 20:06 mklement0

Can you explain this, because it beats me :-) image

jhoneill avatar Jun 13 '22 20:06 jhoneill

@jhoneill, that is very curious - the behavior on the right side is what I expect and see consistently; I have no explanation for the left side. Can you consistently reproduce the left-side behavior and, if so, how?

mklement0 avatar Jun 13 '22 20:06 mklement0

@mklement0 I still had the window I tested in the first time open, so I've just dumped the history out and reproduced it. But I can't see anything which would change the the behaviour of the member operator. It's in this gist if you want to have a play https://gist.github.com/jhoneill/7239a13c0faaa2b875c267ea093464dd

Gist
GitHub Gist: instantly share code, notes, and snippets.

jhoneill avatar Jun 13 '22 21:06 jhoneill

Thanks - just dot-sourcing a script with these commands does not surface the issue for me. Either way, it seems clear that we're dealing with a separate bug.

mklement0 avatar Jun 13 '22 21:06 mklement0

I got it down to this image

It's almost like the member operator remembers that it has seen a string property "42" causing it to change behaviour - try with and without line 2 :-)

$psco = [pscustomobject]@{ 42 = 'foo1' }
$Psco.42
$h = @{ 42 = 'foo1' }
$h.42
$h = (@{ "42" = 'foo1' })
$h.42

jhoneill avatar Jun 13 '22 22:06 jhoneill

Great sleuthing, @jhoneill; what an insidious bug (though probably rare in the wild); I've taken the liberty of reporting it in #17525

mklement0 avatar Jun 14 '22 15:06 mklement0

This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you.

This issue has been marked as "No Activity" as there has been no activity for 6 months. It has been closed for housekeeping purposes.