Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested member access (dot notation) on a number-like property name fails #14036

Closed
mklement0 opened this issue Nov 11, 2020 · 15 comments
Closed
Labels
Issue-Question ideally support can be provided via other mechanisms, but sometimes folks do open an issue to get a Resolution-No Activity Issue has had no activity for 6 months or more WG-Engine core PowerShell engine, interpreter, and runtime

Comments

@mklement0
Copy link
Contributor

mklement0 commented Nov 11, 2020

Note: This is a relatively minor issue with an easy workaround, but it would be good to understand if there are other ramifications and a fix would certainly be nice.

Steps to reproduce

([pscustomobject] @{ 1 = 'foo ' }).1 | Should -BeExactly 'foo '
([pscustomobject] @{ 1 = 'foo ' }).1.Trim() | Should -BeExactly 'foo'

Expected behavior

Both tests should succeed.

Actual behavior

The 2nd test fails, because the .1 is apparently no longer recognized as a property access in combination with the .Trim() call:

ParserError:
Line |
   1 |  ([pscustomobject] @{ 1 = 'foo ' }).1.Trim() | Should -BeExactly 'foo'
     |                                     ~
     | Missing property name after reference operator.

Note:

  • If the property name cannot be parsed as a number literal, the problem doesn't surface (e.g., .a1).

  • A related problem is trying to use a property name that starts with a digit; e.g.,
    ([pscustomobject] @{ '1a' = 'foo ' }).1a breaks, unless .'1a' is used instead.

  • Workarounds:

    • Quote the property name (thanks, @jantari): ([pscustomobject] @{ 1 = 'foo ' }).'1'.Trim()
    • Enclose the original property access in (...): (([pscustomobject] @{ 1 = 'foo ' }).1).Trim()
      • This workaround also works when accessing hashtables that have actual numeric keys via property syntax, such as with the automatic $Matches variable:
        • $null = 'foo bar' -match '^(foo\s)'; ($Matches.1).Trim()
    • As @BrucePay points out, enclosing just the property name / key in (...) also works in both scenarios:
      • ([pscustomobject] @{ 1 = 'foo ' }).(1).Trim()
      • $null = 'foo bar' -match '^(foo\s)'; $Matches.(1).Trim()

Environment data

PowerShell Core 7.1.0-rc.2
@mklement0 mklement0 added the Issue-Question ideally support can be provided via other mechanisms, but sometimes folks do open an issue to get a label Nov 11, 2020
@jantari
Copy link

jantari commented Nov 11, 2020

Although you're probably aware of it I just want to mention for others who have this issue that this can be worked around for now by quoting the property:

([pscustomobject] @{ 1 = 'foo ' }).1.Trim() # FAILS
([pscustomobject] @{ 1 = 'foo ' }).'1'.Trim() # WORKS

This also works for other property-names that make PowerShell misbehave, such as properties starting with a '#' etc.

@mklement0
Copy link
Contributor Author

Thanks, @jantari - I've added the workaround to the OP. Note that (...) works in all cases, because quoting isn't an option when you access hashtables with truly numeric keys with property syntax, which can happen with the automatic $Matches variable - I've added that to the OP too.

@iSazonov iSazonov added the WG-Engine core PowerShell engine, interpreter, and runtime label Nov 13, 2020
@bpayette
Copy link
Contributor

There is actually a lexical ambiguity here. Consider data.1.0. Is this a nested index or indexing with the double 1.0? In practice, we do a shift instead of a reduce so it becomes indexing with a float. I think that with data.1.b, shifting results in an invalid number ultimately resulting in the error you see (I haven't actually checked the code so I don't have the exact details.) Anyway, you can eliminate the ambiguity by putting the number in parens:

([pscustomobject] @{ 1 = 'foo ' }).(1).Trim()

@vexx32
Copy link
Collaborator

vexx32 commented Nov 13, 2020

Sort of? Property access tends to convert basically everything to a string in most cases, because most properties can only have strings for the property name. PSObject / PSCustomObject creates objects with string property names, not integer ones. So, yes, that works... incidentally only. $obj.PSObject.Properties.Name.GetType() on that object you create there will tell you the property name is still a string.

There's no reason not to assume a string value is being used for the property name IMO. The only exception is dictionaries/hashtables having non-string key types. Regular objects have strings for property names, I don't think there are really any exceptions to that. It's already true in some cases that you must use $table[$value] to index some kinds of keys, this wouldn't be a new thing.

@mklement0
Copy link
Contributor Author

mklement0 commented Nov 13, 2020

Agreed, @vexx32:

  • For a non-IDictionary object, it is unequivocally unhelpful to try to parse the member name as a number, given that property names are always strings.

  • For an IDictionary, recognizing [double]s is a feature - even though it's hard to imagine that it would see much use:

PS>  @{ 1.5 = 'foo ' }.1.5.Trim()
foo

If we were to maintain strict backward compatibility, we can't take this feature away.

However, I've never seen a [double]-keyed dictionary in the wild, and the precision issues around binary floating-point representations alone make this an ill-advised thing to do (and the syntax does not work with [decimal]-typed keys).

Given the presumed rarity of [double]-keyed dictionaries and the fact that users will most likely expect all . chars. to be member-access operators, my vote is to consider this a bucket 3 change and always stop parsing a property name / key when a(n unquoted) . is encountered.

  • If there really is a need to use a [double] key, one must then use $dict.(1.5).Trim(), as @BrucePay suggests, or use $dict[1.5], as you suggest.

If preserving backward compatibility is paramount, an alternative that at least ameliorates the problem - if technically feasible - would be to fall back to considering something like 1.b property / key 1, whose b property is to be accessed , after failing to parse 1.b as a [double].

@mklement0

This comment has been minimized.

@mklement0
Copy link
Contributor Author

mklement0 commented Nov 26, 2020

Longer-term, if we ever get to break backward compatibility more substantially, my recommendation would be, for conceptual simplicity and for avoiding edge cases:

  • Only ever treat bareword property names such as .1 and .1l as strings and always treat an unquoted . as the member-access operator.

  • For everything else, either require an explicit expression context (.(...), .$propName) or index-based access instead, in which case the intent to use an expression is unequivocally signaled.

(To soften the blow, the automatic $Matches variable could be converted to an [ordered] hashtable whose default keys are the stringified indices, so that something like $Matches.1 continues to work); the added benefit is that you both indexed ($Matches[1]) and named-capture-group property then work $Matches.foo).

@catthehacker
Copy link

Not sure if it's directly related but in below example nested properties also doesn't work unless put in quotes

$l=@{I=1;V=5;X=10;L=50;C=100;D=500;M=1000}
$a='IVXLLD'

$l[$a[3]]
# Result:

$l["$($a[3])"]
# Result: 50

@mklement0
Copy link
Contributor Author

@catthehacker, no, this is a different problem: [hashtable] keys must be matched type-exactly during lookup.

Your keys are [string]-typed, despite comprising only a single character (e.g., L - quoting optional in hashtable literals; the quoting style makes no difference, unlike in C#): PowerShell has no literal [char] type.

By contrast, 'IVXLLD'[3] does return a [char] instance, and that's why the lookup fails, and only succeeds with stringification; $l[[string] 'IVXLLD'[3]] would work too.

@catthehacker
Copy link

@mklement0 Thanks a lot, that's awfully helpful.

Copy link
Contributor

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.

2 similar comments
Copy link
Contributor

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.

Copy link
Contributor

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.

@microsoft-github-policy-service microsoft-github-policy-service bot added Resolution-No Activity Issue has had no activity for 6 months or more labels Nov 16, 2023
@jantari
Copy link

jantari commented Nov 16, 2023

This still fails in the latest preview so it's still relevant.

Copy link
Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Issue-Question ideally support can be provided via other mechanisms, but sometimes folks do open an issue to get a Resolution-No Activity Issue has had no activity for 6 months or more WG-Engine core PowerShell engine, interpreter, and runtime
Projects
None yet
Development

No branches or pull requests

6 participants