Skip to content

Argument-mode parsing: Why is a subexpression at the start of an unquoted token treated differently than a variable reference? #3217

Closed

Description

This is a primarily a design rationale question:

The issue is that the rules are hard to remember, and perhaps knowing why they are the way they are helps with that.

If these work:

$a = 4
Write-Output $a/H         # -> '4/H'
Write-Output H/$a         # -> 'H/4'
Write-Output H/$(2 + 2)   # -> 'H/4'

why shouldn't Write-Output $(2 + 2)/H expand to '4/H' too, instead of being treated as 2 arguments, 4 and '/H'?

Why is a subexpression at the start treated differently than a variable reference?

Also, why is Write-Output .$HOME treated as 2 arguments (only happens if the . is directly followed by a $)?

For a full discussion, please see this Stack Overflow question.

Activity

  1. changed the title [-]Argument-mode parsing: Why is a subexpression at the start treated differently than a variable reference?[/-] [+]Argument-mode parsing: Why is a subexpression at the start of an unquoted token treated differently than a variable reference?[/+] on Feb 27, 2017
  2. added
    WG-Languageparser, language semantics
    Issue-Questionideally support can be provided via other mechanisms, but sometimes folks do open an issue to get a
    on Feb 27, 2017
  3. lzybkr commented on Feb 27, 2017

    @lzybkr
    Contributor
  4. JamesWTruher commented on May 24, 2017

    @JamesWTruher
    Collaborator

    if we look at what the parameter binder believes it's arguments are we can understand the output better:

    PS> trace-command *param* { write-output $(2+2)/H } -pshost                                                                                                                                                             
    DEBUG: ParameterBinderController Information: 0 :  WriteLine   Argument count: 2
    <snip>
    4
    /H
    

    vs

    PS> trace-command *param* { write-output H/$(2+2) } -pshost                                                                                                                                                             
    DEBUG: ParameterBinderController Information: 0 :  WriteLine   Argument count: 1
    <snip>
    H/4
    

    So, by the time the arguments get to the parameter binder it's already either broken up (or not) the tokens. There's definitely a difference between a variable reference $a and a subexpression $(2+2) so i would expect a difference in the behavior between write-output $(2+2)/H and write-output $a/H.

    I suspect that it's the location of the obvious string element which causes the behavioral change which changes it from two arguments to one. I haven't tracked that down though.
    write-output H/... is an obvious string (since that's how it starts), pretty much anything that follows it which isn't a token separator (space for example) is just going to be tacked on the end.

    As for write-output .$HOME - remember that the . serves two functions, one as a invocation operator and another as a property reference operator, depending on context. In this case, we've definitely got 2 tokens . (the invocation operator) and $HOME, so we emit two lines.
    FWIW, Write-Host sees these two tokens as well:

    Write-Host .$HOME
    . /Users/james
    

    note the space. Why the difference? The difference between write-host and write-object is the parameter type for the input object. in write-host the type is object and in write-output the parameter type is PSObject[].

  5. mklement0 commented on May 24, 2017

    @mklement0
    ContributorAuthor

    Thanks for that, James Truher (@JamesWTruher).

    To provide more motivation for my question, which is primarily about the user experience, not the technical underpinnings:

    • It is obscure to have behavior depend on the order of tokens in this case, especially since variable references and subexpressions are not treated differently when explicitly embedded inside "..."

      • Write-Output "$(2 + 2)/H" yields only 1 argument.

      • In POSIX-like shells, a command/arithmetic substitution (the equivalent of a subexpression in PS) does work as the first token:
        echo $(( 2 + 2 ))/H yields 1 argument, 4/H

      • That said, (the expanded results of) unquoted tokens in POSIX-like shells are subject to shell expansions that are often unwanted, leading to the annoyance that double quotes must be used everywhere (and beginners forgetting them is a constant source of bugs).

      • PowerShell generally frees us from that annoyance, so it's unfortunate that it doesn't do so in this case (because it results in multiple arguments).

      • Generally, remembering when it works and when it doesn't, in the absence of an intuitive reason, is a challenge and, I suspect, will be an ongoing source of confusion.

    • The same obscure behavior applies to juxtaposing differently quoted substrings (which are commonly used in POSIX-like shells):

      • Write-Output bare"I'm quoted" -> 1 argument, because the 1st token is unquoted:
        bareI'm quoted
      • Write-Output "I'm quoted"bare -> 2 arguments, because the 1st token is quoted:
        I'm quoted and bare
    • As for the .$HOME case: Given that the token is a non-initial token parsed in argument mode, it shouldn't be interpreted as the invocation operator.
      If unquoted tokens are to be treated like expandable strings (i.e., as if the token were explicitly enclosed in "...", "unless it begins with one of the following special characters: dollar sign ($), at sign (@), single quotation mark ('), double quotation mark ("), or an opening parenthesis (()", according to Get-Help about_Parsing), then .$HOME should work the same way as, say, x$HOME.

    I suspect the answer is that changing the behaviors described is way too late and risky, but perhaps the pitfalls of the existing behavior can be documented somewhere.

  6. iRon7 commented on Jul 6, 2020

    @iRon7

    I came across this issue as I created a similar Unquoted Strings request at the The PowerShell Best Practices and Style Guide a few weeks ago without any activity. I have also created a similar statckoverflow question: Unquoted key rules / best practice, as I got lost in the (open and close) cascaded issues and whether this is already covered or even answered. And if not, to which issue I should add my findings (or create a new one?).

  7. mklement0 commented on Jul 6, 2020

    @mklement0
    ContributorAuthor

    iRon7, #6467 is more comprehensive and there's a related docs issue now: MicrosoftDocs/PowerShell-Docs#3440

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue-Questionideally support can be provided via other mechanisms, but sometimes folks do open an issue to get aResolution-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