Repository navigation
Description
See the comments in #9794 for the full extent of the existing discussion, starting with #9794 (comment)
AutomationNull.Value is sometimes detectable and distinguishable from $null in certain cases, for example:
using namespace System.Management.Automation.Internal
$null -is [psobject] # false
[AutomationNull]::Value -is [psobject] # true
@($null).Count # 1
@([AutomationNull]::Value).Count # 0Patrick Meinecke (@SeeminglyScience) mentioned a possible way we could have it be handled more closely like [dbnull] and [nullstring] are being handled as of #9794, without losing its current function in the pipeline internals, in #9794 (comment):
Ideally if this were to be fixed it would just be removed from assignment, e.g.
$obj = [AutomationNull]::Valuewould populate$objwith truenull. The-isoperator is one of the few things that never "lie" on occasion and I think it would be a detriment to change that.You can also make
[AutomationNull]::Value -is [AutomationNull]work by:
- Removing the
statickeyword from the class decl- Make it inherit
PSObject- Change the singleton instance to
new AutomationNull()That would allow
$autoNull -is [psobject]to still work while enabling$autoNull -is [AutomationNull]. Though I think there are a few places in the compiler where it uses a pattern likeif (expr.ExpressionType == typeof(PSObject) && expr.Value == AutomationNull.Value)so that might need to change.
/cc Dongbo Wang (@daxian-dbw) Michael Klement (@mklement0)
I'm personally in favor of Patrick's solution, as it enables the very clear and concise $item -is [AutomationNull] with few downsides (the biggest downside being potential implementation complications, but in my opinion these are probably worth tackling). Interested to hear any further discussion on this! 💖
Activity
[-]AutomationNull.Value Behaviour[/-][+]AutomationNull Behaviour[/+]mklement0 commentedon Jun 25, 2019
returning
$truemakes perfect sense both intrinsically and for symmetry with[DbNull]::Value -is [DbNull]and[NullString]::Value -is [NullString]returning$true.The current workaround for detecting
[System.Management.Automation.Internal.AutomationNull]::Valueactually requires two tests (to also distinguish[AutomationNull]::Valuefrom an empty collection object).As for why there is an occasional need to distinguish between a true
$nulland[System.Management.Automation.Internal.AutomationNull]::Value:A command that produces no output technically outputs
[System.Management.Automation.Internal.AutomationNull]::Value, which is distinct from a command explicitly returning$null.While
$nulland[System.Management.Automation.Internal.AutomationNull]::Valueare treated the same in an expression context, their behavior differs fundamentally in the pipeline:Also, because the
switchstatement treats its operand as an enumeration, a[System.Management.Automation.Internal.AutomationNull]::Valuevalue causes the statement to be skipped altogether; that is, it is effectively ignored:Related issues:
You cannot pass
[System.Management.Automation.Internal.AutomationNull]::Valueas an argument - it is invariably converted to$null, which Jason Shirk (@lzybkr) surmised may have been an oversight - see No any way to pass empty pipeline (AutomationNull) to function parameter #9150 (comment)The
foreachstatement does not distinguish between$nulland[System.Management.Automation.Internal.AutomationNull]::Value- neither is enumerated.The mystifying
[AutomationNull]::Value -is [AutomationNull]being$falsehas echoes of[pscustomobject] @{ foo = 1 } -as [System.Management.Automation.PSCustomObject]being$nulland-as [pscustomobject]always returning the LHS, with a true$nullLHS being the only exception - see -as operator does not recognize System.Management.Automation.PSCustomObject instances as such #4343 and below.Just like
[AutomationNull]::Value -is [AutomationNull]currently being$falseis a head-scratcher, so is[AutomationNull]::Value -is [psobject]being$trueand - given the unfortunate identity of[psobject]and[pscustomobject]-[AutomationNull]::Value -is [pscustomobject]being equally$true.[AutomationNull]::Value -is [psobject]being$truemakes no sense from an end-user perspective, and looks like another case of[psobject]peeking from behind the curtain - see #5579.iSazonov commentedon Jun 25, 2019
The AutomationNull is defined in System.Management.Automation.Internal namespace that implies only internal non-public use. We free to change the internal AutomationNull but it make sense only if it is really needed for addressing important scenarios.
vexx32 commentedon Jun 25, 2019
If it is purely for internal use it should never be leaking in the ways described above.
However, due to the way it's intended to behave, I'm not sure it can be purely internal and still provide the same utility.
bpayette commentedon Jun 25, 2019
Ilya (@iSazonov) is correct. AutomationNull is for internal use by the runtime. It exists to distinguish an empty stream from a stream containing null.
Rain Sallow (/u/ta11ow) (@vexx32)
All of your examples explicitly reference the type. I would hardly call that a "leak".
What do you think it should do that it currently doesn't do and why do you think that behaviour is useful? For example, this
does not qualify as useful because it's an internal class.
vexx32 commentedon Jun 25, 2019
Sure, for simplicity of definition I opted to use the explicit class name. Michael Klement (@mklement0) offered several other ways to obtain the value. Also, it's not internal. I can reference the type name from PS directly. It might be in the internal namespace, but it's public.
Here's another way to get an auto-null:
Contrived? Yes. Easily found in a real application? Absolutely. Even more so in a pipeline which may not return any value.
bpayette commentedon Jun 25, 2019
Rain Sallow (/u/ta11ow) (@vexx32)
It's in the internal namespace:
System.Management.Automation.Internal
This namespace contains classes and interfaces that must be public for some reason but are not considered part of the public PowerShell API.
{master}PSCore (1:1) > $a = if ($false) { Do-Thing } {master}PSCore (1:2) > $a -eq $null True {master}PSCore (1:3) > $a.GetType().FullName You cannot call a method on a null-valued expression. At line:1 char:1 + $a.GetType().FullName + ~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (:) [], RuntimeException + FullyQualifiedErrorId : InvokeMethodOnNull {master}PSCore (1:4) >So where does AutomationNull show up in this?
vexx32 commentedon Jun 25, 2019
As Dongbo mentioned in the linked PR, AutomationNull doesn't respect GetType(); it's special-cased in that instance to appear as
$null. However, compare$ain that example with true$nullas follows:If it's not meant to be part of the public API, then y'all need to figure out how to hide it better. 😉
SeeminglyScience commentedon Jun 25, 2019
Bruce Payette (@BrucePay) I've had to account for it before with methods that have a parameter typed as
object[]. For example:And to be fair, the documentation does not do a great job at explaining that it shouldn't be used (and by that I mean it says that it's imperative that you do use it). You also have to remember the audience, most PowerShell folks won't think twice about the namespace because they aren't familiar with that pattern. Granted, most folks who end up needing
AutomationNullwill probably be pretty technical, but still.Whether or not it was intended to be used, I'd still advise that care be taken when it comes to breaking changes.
mklement0 commentedon Jun 28, 2019
Good points, Rain Sallow (/u/ta11ow) (@vexx32) and Patrick Meinecke (@SeeminglyScience).
I don't think
[AutomationNull]can be hidden, because users need to understand how it differs from$nulland need to be able to inspect a given value to infer its pipeline behavior - and also more obscure differences such as Patrick Meinecke (@SeeminglyScience)'s method-argument example.In other words: it is insufficient for PowerShell to pretend that a type('s singleton) is
$nullwhen it actually behaves differently, situationally.That difference must be discoverable, and having to resort to obscure and cumbersome workarounds to detect
[AutomationNull]::Value- which you'll never discover unless you know about this "internal" type - is obviously unsatisfactory.So, yes,
is useful - and that it currently returns
$falseis self-contradictory.Furthermore, the type must be documented in the end-user docs.
mklement0 commentedon Nov 19, 2019
Patrick Meinecke (@SeeminglyScience), stumbling upon this again I've noticed bizarre behavior around your example:
SeeminglyScience commentedon Nov 19, 2019
Yeah, looks like a binder bug. There must be a difference in how it approaches converting single item arrays vs more populated arrays. That would explain why the third works, the binder would still be cached.
mklement0 commentedon Nov 19, 2019
Thanks, Patrick Meinecke (@SeeminglyScience); I've created #11118.
mklement0 commentedon Dec 19, 2019
21 remaining items
BrucePay commentedon Feb 26, 2020
Rain Sallow (/u/ta11ow) (@vexx32)
The significant risk of breaking all kinds of things in obscure ways because we've done something to make AutomationNull more visible rather than less visible. As I mentioned before, getting a usable system with AutomationNull took months of bug hunting.
And I have yet to hear a credible rational for changing the semanitcs of AutomationNull. In a value/expression content AutomationNull should work exactly like null. In a pipeline, it's purpose is to indicate that the pipeline returned no results which is not the same as null.
vexx32 commentedon Feb 26, 2020
Bruce Payette (@BrucePay) Sure, but it doesn't work "exactly" like null in all cases. Therefore having a reliable and accessible way to distinguish the two is desirable so that you can properly handle it when you need to.
Your arguments are hypotheticals that we "might" break something, with very little information . The responses from Michael Klement (@mklement0) and Patrick Meinecke (@SeeminglyScience) as well as the occasional question that crops up on a fairly frequent basis in the community chat channels indicates that having a reliable way to distinguish $null from AutomationNull is worth implementing.
Unless you can demonstrate something that the proposed changes will actually break, I'm not sure your concerns can be tested in any way. An untestable hypothesis isn't especially useful as a talking point.
mklement0 commentedon Feb 26, 2020
To add to Rain Sallow (/u/ta11ow) (@vexx32)' s helpful comment: No one in this thread has advocated changing the semantics of AutomationNull - the proposed change in no way modifies the existing behavior, it merely enables reflection on a preexisting behavioral difference that has hitherto been undiscoverable without insider knowledge.
Anecdotal references to past struggles and insisting on not being personally convinced are no substitutes for rational debate, especially if the points that have been made aren't being addressed.
iSazonov commentedon Feb 27, 2020
We were sure that # 9794 is correct but it was reverted. This experience says that each application area can have its own 'null' and it is probably not worth crossing them as far as possible (even if it raises a lot of questions).
mklement0 commentedon Feb 27, 2020
Ilya (@iSazonov), #9794 was a different story altogether, and in a sense the opposite (complement) of what is being proposed here: it tried to extend the set of types that are conflated with
$nullwhen compared it via-eq(in keeping with-eq's loose concept of equality); by contrast, here we're asking to unambiguously identify a "specific flavor of$null". Again, we're only talking about a reflection feature here that makes real-world behavioral differences discoverable - no existing behavior relating to using these values will change.iSazonov commentedon Feb 28, 2020
Rain Sallow (/u/ta11ow) (@vexx32) Michael Klement (@mklement0) The issue description is based on #9794 and mentioned
dbnullandstringnull. This is misleading. I think you need to close this discussion and create a new one where you clearly indicate the desired changes as 1, 2, 3.Please strongly take into account a history which Bruce revealed.
Also please take into account that AutomationNull is "public" mainly for SDK (see C# vs PowerShell - ~3000 vs ~0 results)
SeeminglyScience commentedon Feb 28, 2020
The history he revealed is that there were a lot of bugs when they made the change that allowed autonull to be saved to a variable. Nothing is revealed there, that's a huge change that pretty obviously increases risk.
It's sort of like saying that there was a lot of obscure bugs when PowerShell's parser switched from purely token based to AST based so we shouldn't consider adding an overload to
Parser.ParseInput.If the argument was "this comes up so infrequently that it's not even worth discussing" I'd be with ya 100%. If instead the argument is "significantly more drastic and involved changes we made 10 years ago caused problems" then I have a hard time seeing that as anything other than off topic.
iSazonov commentedon Feb 28, 2020
I mean mainly the old history and experience:
Our new history and experience is that in #9794 we got married [dbnull], [stringnull] and [null] and then stumbled upon AutiomationNull that was highlighted in the separate discussion. In the history no real scenario presents. So question is - what scenarios do we need to fix to help (1) script writers, (2) binary module developers? what can they not do? what can they not overcome? What annoys them and forces them to make bad workarounds?
Although Bruce mentioned only one scenario where AutiomationNull might be used (binary cmdlet), perhaps there are others. I would rather expect that these scenarios were discussed with the aim of hiding or getting rid of AutiomationNull because it is the visibility of AutiomationNull (knowledge that it exists) that causes questions for users, although most likely they do not need it and they can bypass the emerging problem easily.
SteveL-MSFT commentedon Mar 4, 2020
@PowerShell/powershell-committee discussed this. We feel that using
AutomationNulldirectly comes up infrequently enough that it doesn't warrant the time spent on discussion already. We would also be concerned about potential breaking change as noted by Patrick Meinecke (@SeeminglyScience) and Bruce Payette (@bpayette).mklement0 commentedon Mar 4, 2020
How often this comes up isn't the point, as argued before.
That wasn't the point either; it's not about using, but about discovering something that you can't help but encounter in real life, without the language giving you the ability to understand why not all apparent
$nullvalues are created equal.Recent case in point: https://stackoverflow.com/q/60515757/45375
What the proposal turned into would not result in any breaking change (as Patrick Meinecke (@SeeminglyScience) has conclusively argued, aside from his not being personally convinced of the need for a change).
Ilya (@iSazonov):
Re-reading the OP, I agree; unfortunately I didn't get around to it in time.
The exact opposite is the case: most users are unaware of
AutomationNull, but they see its effects, which is why they need to be given a way to discover this value.In light of the above:
AutomationNullmust neither be hidden nor can it be gotten rid of.mklement0 commentedon Aug 17, 2020
Ilya (@iSazonov), I finally got around to this suggestions of yours:
Please see #13465, which focuses just on
-is [AutomationNull]and hopefully avoids the confusion that arose in this discussion around non-existent breaking changes.