Member-access enumeration doesn't work with numeric hashtable keys
Prerequisites
- [X] Write a descriptive title.
- [X] Make sure you are able to repro it on the latest released version
- [X] Search the existing issues.
- [X] Refer to the FAQ.
- [X] Refer to Differences between Windows PowerShell 5.1 and PowerShell.
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
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.
It never worked because "selector" .42 is expected to be a string, so expression (@{ 42 = 'foo1' }, @{ 42 = 'foo2' }).42 is incorrect itself
@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.
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.
That is NOT shorthand, because your array is not array of psobjects
psobject property type is always a string, but a hashtabe is a diff story
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.
It never worked because "selector"
.42is expected to be a string, so expression(@{ 42 = 'foo1' }, @{ 42 = 'foo2' }).42is 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, 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

That's what I got which showed $h.42 converted 42 to a string
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?'
Can you explain this, because it beats me :-)

@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 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.
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.
I got it down to this

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
Great sleuthing, @jhoneill; what an insidious bug (though probably rare in the wild); I've taken the liberty of reporting it in #17525
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.