Powershell: { $false } cases in switch statements are skipped even if the value it's matching is also $false

Created on 4 Jan 2019  路  8Comments  路  Source: PowerShell/PowerShell

The documentation on switch statements here has this to say about scriptblock-expression case statements (emphasis added):

If the condition is an expression or a script block, it is evaluated just before it is compared to the value. The value is assigned to the $_ automatic variable and is available in the expression. The match succeeds if the expression is true or matches the value. The expression is evaluated in its own scope.

A case statement like { $false } (or one evaluating to such) is currently skipped, even if the value passed into the switch is $false explicitly.

Steps to reproduce

switch ($false) {
    $false {
        "This is expected."
    }
    { $false } {
        "This should also pass as it evaluates to the specified target value."
    }
    { 1 -eq 2 } {
        "Once again, a false boolean statement should trigger the match."
    }
}

Expected behavior

This is expected.
This should also pass as it evaluates to the specified target value.
Once again, a false boolean statement should trigger the match.

Actual behavior

This is expected.

Actual Encounter

I was attempting to use logic like this to determine if types were already loaded for use in PSKoans, much like this:

switch ($false) {
    { 'MyType' -as [type] } {
        class MyType {}
    }
    { 'MyType2' -as [type] } {
        class MyType2 {}
    }
}

I had to flip the logic, add a series of -not () and make the switch check against $true instead to make this work, which seems silly and unnecessary. 馃槃

Environment data

Name                           Value
----                           -----
PSVersion                      6.1.0
PSEdition                      Core
GitCommitId                    6.1.0
OS                             Microsoft Windows 10.0.17134
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0
Issue-Discussion Resolution-Answered WG-Engine

All 8 comments

Note this only affects expressions encased in script blocks, _not_ expressions in parentheses:

switch ($false) {
    (1 -eq 2) {
        "surprise!"
    }
    { 1-eq 2 } {
        "surprise2!"
    }
}

yields:

surprise!

That's an interesting find, and it applies to all "falsy" values, including 0 and ''.

My vote would be to _only_ support signaling a scriptblock-based match via (implied) Boolean result, not also by value identity, which is where the ambiguity comes from.

Not sure about backward compatibility, but at the very least we should update the docs to recommend using the Boolean approach only.

Note that the only good reason to use a scriptblock to begin with is if you need to determine a match via $_; otherwise, an expression is preferable anyway.

@mklement0 Yeah, I've been playing with it and I'm really not sure it _is_ behaving as the docs say at all.

PS C:\Program Files\PowerShell\6> switch (7) {
>> 7 { "bare number"}
>> 6 { "different bare number" }
>> {7} {"scriptblock number"}
>> {6} {"different scriptblock number"}
>> {1} {"still positive"}
>> {0} {"falsey numeral"}
>> {"hello"}{"truthy string"}
>> {""}{"falsy string"}
>> }
bare number
scriptblock number
different scriptblock number
still positive
truthy string

So this _might_ just be an issue of the docs not clearly indicating the difference between a scriptblock expression and a bare or parenthesis-wrapped expression.

FWIW I did find a better solution for my original intent for type-checking, as well:

switch ($null) {
    ('MyType' -as [type]) {
        class MyType {}
    }
    ('MyType2' -as [type]) {
        class MyType2 {}
    }
}

Good point, @vexx32 - your test command suggests that the script block output is (sensibly) _always_ coerced to a Boolean (without a value comparison), so, yes, this smells like a mere documentation bug.

To provide some fodder for updating the docs:

  • Use an expression ((...)) if you want to dynamically calculate _the value_ to match; that is, use an expression if you want its result to be compared directly to the input value; by default, the equivalent of
    <input> -eq <expr> determines a match, where <input> is the input value and <expr> the branch condition. Note that you cannot reference the current input value as $_ in such an expression; use a script block for that - see below.

  • Use a script block ({ ... }) to dynamically test for a match via a _conditional_ that involves the _current input value_, as in $_. That is, the script block must return a (possibly coerced) _Boolean_ that indicates whether the input is considered a match or not.

Looks good, @mklement0! I would also mention that any invariant (not $_-based) script block expression that evaluates or coerces to $true is essentially similar to a default case, and will always be run unless a previous case exits the switch statement before it can be reached.

I鈥檝e opened docs issue https://github.com/PowerShell/PowerShell-Docs/issues/3512 (it covers other issues too).

@vexx32 Yeah this is a doc bug. The docs are mixing two very different concepts. A switch statement clause may begin with either :

  1. an expression whose _value_ is used to match against.
    or
  2. a literal scriptblock whose value is not matched against but instead, is executed during clause processing. It receives the object to match in $_ and the match succeeds only if the scriptblock returns a true value. So a scriptblock {$false} will always fail regardless of the value being matched but a scriptblock {$_ -eq $false} will succeed if the value to match is $false:
PSCore (1:54) >  switch ($false) {{$_ -eq $false} {"True" }}
True

Sounds good! I'll close this one for now since @mklement0 has gone ahead and created the docs issue. 馃檪

Was this page helpful?
0 / 5 - 0 ratings