Skip to content

Do not require { ... } around variable names for null-conditional member access #11379

@mklement0

Description

@mklement0

Follow-up from #3240.

Null-conditional member access (?.) was implemented and will be available as an experimental feature in 7.0

However, in the interest of backward compatibility the implementation currently requires that the variable name be enclosed in {...}, because ? is technically a legal character in a variable name whose use surprisingly doesn't require {...}.

That is, if you want to ignore an attempt to call$obj.Method() if $obj is $null (or undefined), you would expect the following, analogous to C#:

# Does NOT work as intended.
# 'obj?' as a whole is interpreted as the variable name.
$obj?.Method()

Instead, you must currently use:

# !! Must enclose 'obj' in {...} to signal that '?' is a syntactic element as part of '?.'
${obj}?.Method()

Update: Null-coalescing - $var??='default and $a??$b - and the ternary operator - $var?1:0 - are similarly affected (though there the use of whitespace before the ? resolves the issue).

This requirement is (a) unexpected and (b) cumbersome:

  • New users will not expect the need for {...} and won't necessarily know that it is what is required when the following fails (possibly undetected, unless Set-StrictMode -Version 2 or higher is in effect): $o = [pscustomobject] @{ one = 1 }; $o?.one

  • Even once users do know about the need for {...}:

    • They will forget to use it on occasion, because of the counter-intuitive need for it.
    • When they do remember, this seemingly artificial requirement will be an ongoing source of frustration, especially since {...} is hard to type.

Use of {...} shouldn't be necessary, and while not requiring it amounts to a breaking change, it arguably falls into bucket 3: Unlikely Grey Area.


Why this change should be considered acceptable:

Note: @rjmholt discusses why changing PowerShell's syntax is highly problematic in general in this comment, but for the reasons presented below I think it is worth making an exception here.

?. is a new syntactic feature; by definition scripts that use it cannot (meaningfully) run on older versions - unless you specifically add conditionals that provide legacy-version-compatible code paths, but that seems hardly worth it - you would then just stick with the legacy features.

Yes, the interpretation of $var?.foo in old scripts that used $var? as a variable name would break, but:

  • ? should never have been allowed as part of an identifier without enclosing it in {...}.

  • Since being able to do so is unexpected and probably even unknown to many, such variable names are exceedingly rare in the wild, speaking from personal experience, but @mburszley's analysis provides more tangible evidence.

    • Even Ruby only allows ? (and !) at the end of identifiers, and there only of method identifiers, and I suspect that most Ruby users are aware that they should not assume that other languages support the same thing.

So, pragmatically speaking:

  • The vast majority of existing code will not be affected - a token such as $var?.foo will simply not be encountered.

  • If you write $var?.foo with the new semantics, then yes, running that on older versions could result in different behavior (rather than breaking in an obvious manner), depending on what strict mode is in effect - but you should always enforce the minimum version required to run your code as intended anyway (#requires -Version, module-manifest keys).

All in all, to me this a clear case of a bucket 3 change: a technically breaking change that breaks very little existing code while offering real benefits (or, conversely, avoiding perennial headaches due to unexpected behavior).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Committee-ReviewedPS-Committee has reviewed this and made a decisionIssue-Enhancementthe issue is more of a feature request than a bugWG-Languageparser, language semantics

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions