PowerShell / PowerShell

PowerShell for every system!
https://microsoft.com/PowerShell
MIT License
43.55k stars 7.06k forks source link

Default case in Switch not called when variable is in an undefined state #21558

Open TheToor opened 2 weeks ago

TheToor commented 2 weeks ago

Prerequisites

Steps to reproduce

The following code gives no output:

$Test = $Null | Out-Null
switch($Test) {
  $null { Write-Host "It's null" }
  Default { Write-Host "It's Default" }
}

The following codes gives an output on the $null case:

$Test = $Null
switch($Test) {
  $null { Write-Host "It's null" }
  Default { Write-Host "It's Default" }
}

Expected behavior

Either the $null or Default case should be called

Actual behavior

Neither of the cases is called when the output of the pipeline is empty.
This is also reproducable with a custom function returning nothing:
`Function Out-CustomNull { return }`

Emits the same behaviour in the switch case above

Error details

No error

Environment data

Name                           Value
----                           -----
PSVersion                      7.4.2
PSEdition                      Core
GitCommitId                    7.4.2
OS                             Microsoft Windows 10.0.22631
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Visuals

No response

mklement0 commented 2 weeks ago

$Null | Out-Null doesn't return $null; any cmdlet (or function / script file / script block) - such as Out-Null - that produces no output technically returns the [System.Management.Automation.Internal.AutomationNull]::Value singleton, the "enumerable null", if you will.

In an enumeration context - including input provided to a switch statement - [System.Management.Automation.Internal.AutomationNull]::Value behaves like an empty enumerable; that is, no iterations take place; the switch statement is effectively skipped.

That is, it behaves like the following statement: switch (@()) { Default { 'never get here' } }: enumerating the empty array results in no elements, so the statement body is never entered.

What is tricky is that it isn't easy to discover whether a given variable value is a bona fide $null or [System.Management.Automation.Internal.AutomationNull]::Value, especially given that in expression contexts as well as in parameter-binding, the latter is effectively the same as $null.

13465 is a green-lit proposal to make discovery of [System.Management.Automation.Internal.AutomationNull]::Value easier, via $Test -is [System.Management.Automation.AutomationNull], but it hasn't been implemented yet; also, it was inappropriately closed by the bot.

237dmitry commented 2 weeks ago

default is executed when no switches are found that match the condition.

$null -eq $null     # True

undefined state

$a = $null
Get-Variable a
TheToor commented 2 weeks ago

@237dmitry Default is not executed in that case. That is the problem.

$Test = $Null | Out-Null
switch($Test) {
  $null { Write-Host "It's null" }
  Default { Write-Host "It's Default" }
}

Produces no output when the default case should have been run.

TheToor commented 2 weeks ago

Thanks @mklement0 for the detailed explaination. Digging through the other proposal and issues this is probably a case not often encountered but still results in confusing behaviour when it happens.

Any plans to reopen the conversation of your proposed implementation?

237dmitry commented 2 weeks ago

Default is not executed in that case. That is the problem.

This is because there is a $null switch, which is equivalent to $Test and returns $true. If you have a match, then default will not be executed.

Look at full syntax:


 $ & {
         $test = $null
         switch ($test)
         {
            {$_ -eq 1}     { 1 }
            {$_ -eq 'one'} { 'one' }
            {$_ -eq $null} { 'null' }
            default { 'default' }
         }
    }
null

$
mklement0 commented 2 weeks ago

Glad to hear it helped, @TheToor; I have no say in what gets reopened, but I generally think that all auto-closed issues that have the Up-for-Grabs label should be reopened, as previously suggested in https://github.com/PowerShell/PowerShell/issues/21406#issuecomment-2039752097.