Class MyClass {
DoThing() {
$variable1 = ""
try {
$variable2 = Get-OtherThing
} finally {
Write-Host "Tidying"
}
Write-Host $variable1
Write-Host $variable2
}
}
function Get-MyClass{
return [MyClass]::new()
}
export-modulemember -function Get-MyClass
Module can be imported just fine.
+ Write-Host $variable2
+ ~~~~~~~~~~
Variable is not assigned in the method.
> $PSVersionTable
Name Value
---- -----
PSVersion 6.1.0
PSEdition Core
GitCommitId 6.1.0
OS Microsoft Windows 6.1.7601 S
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
Note that this seems to be a problem of PowerShell's parser on Import-Module of a file with the content above (not PSSA) as commented on the linked issue above
There are similar behaviours in C#. Should it change?
There is no way that $variable2 _isn't_ set by the time it's used, so the error is incorrect. (The same code within a script works perfectly well.)
And it makes using a standard pattern (freeing a resource after you're finished using it) more complicated.
Edit: Also - in C# a set of braces creates a new variable scope. In PowerShell this isn't the case.
In that specific case, yes, but there's not necessarily a (good) way to ensure that it is in the general case. If you have a command that throws an error before setting the variable, the variable can remain unset.
And PS classes and class methods have all sorts of funky restrictions at the moment, so I'm sure there's some stricter parser mandates there too.
/cc @rjmholt as I believe he is currently looking at improvements to classes, if I recall correctly. 馃槃
If the command throws an error before the variable is set then it will never get to the place where it's used, as there's no "catch" there. I absolutely agree that if there was a "catch" then you could end up in that situation.
Am I missing a situation where the variable might be otherwise unset when the usage point is reached?
The reason why the C# compiler has to be super strict and follow the rules here to prove by a particular static flow analysis that the variable is definitely assigned is because C# allows the goto statement where one could jump ahead in scope. PowerShell does not have a goto statement directly but allows to break out of loops. But given that the following works correctly (i.e. the result is 2), we could allow the try-finally scenario. WDYT @lzybkr @BrucePay
$a = 0
:loop while ($true)
{
try {
break loop
$a = 1
}
finally {
$a = 2
}
}
$a
It's far more complicated than that. You need to prove a command or expression can't throw. Consider:
try {
$x = $(throw 1; "hello")
} catch {}
$x
It is, for practical purposes, impossible to prove an expression or command doesn't throw, so PowerShell takes the conservative approach and doesn't even try, it assumes anything can throw.
Hmm, OK, you are right on that.
But looking at PowerShell from a high level, a lot of the language and cmdlets were designed to cope with variables being null, on every object one call e.g. Count:
$foo.Count
$null.Count
Should this compilation error only occur in strict mode then?
PowerShell is forgiving until it isn't. One of the design principles behind classes was to help (via parse time errors) when the forgiving nature of PowerShell can't help.
Ok, makes sense, I agree it is by design then
Once again, there is no Catch statement there. Which means that it is impossible to hit the statement using $variable2 unless it is set.
There is no need to worry about it being $null unless the function it calls returns $null, in which case the try/finally makes no difference.
If there was a catch there then, obviously, yes, it would be possible to reach the "write-host $variable2" if Get-OtherThing threw an exception. But as there isn't, $variable2 will be set.
Or am I missing something? @lzybkr
@andrewducker - you are correct, but PowerShell is making a simplifying assumption for better performance.
Within the finally, we can't assume the variable is set. Properly modeling this control flow is more expensive and was deemed not worth the effort.
That makes sense. Thank you!
Most helpful comment
PowerShell is forgiving until it isn't. One of the design principles behind classes was to help (via parse time errors) when the forgiving nature of PowerShell can't help.