You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Are the rules for conditional enumeration the same for comparison operators and the pipeline? #5832
$x-eq$RHS$x-contains$RHSfunctionf { param([Parameter(ValueFromPipeline)]$y) process{$y} }
$x| f
functiong { param([Parameter(Position=1)]$y) $y }
g $x
There are conditions of $x which results in PowerShell enumerating $x for each of these treatments. Setting $x=1,2, for example, causes $x to be enumerated by -eq and -contains, at the input to f, and at the output of g. On the other hand, setting $x='12', results in no enumeration.
Are the rules that decide whether or not $x is enumerated the same for the pipeline and those comparison operators that perform conditional enumeration?
The value of [LanguagePrimitives]::GetEnumerator($x)seems to be non-null iff $x gets enumerated by those treatments. Is this a mere coincidence, a rule PowerShell aspires to, or true by construction?
Are there other (user-observable) parts of the language (aside from operators and the pipeline) that perform conditional enumeration? If so, are the rules used by those parts the same?
changed the title [-]Are the rules for conditional enumeration the same for equality/relational operators and the pipeline?[/-][+]Are the rules for conditional enumeration the same for operators and the pipeline?[/+]on Jan 9, 2018
changed the title [-]Are the rules for conditional enumeration the same for operators and the pipeline?[/-][+]Are the rules for conditional enumeration the same for comparison operators and the pipeline?[/+]on Jan 9, 2018
For operators, the rules are implemented here, and I apologize that the code is not necessarily easy to understand.
For the pipeline, the decision is made by LanguagePrimitives.GetEnumerator, you can see the call here.
True by construction for pipelines, rule to aspire to for operators.
Parameter binding comes to mind - e.g. when to bind to a parameter of type object versus object[]. To the best of my knowledge and memory, operators all rely on PSEnumerableBinder.IsEnumerable (there are many uses) and everything else relies on LanguagePrimitives.GetEnumerator.
functionf {
[CmdletBinding(DefaultParameterSetName='scalar')]
param
(
[Parameter(ParameterSetName='scalar',Position=1)]
[int]
$Scalar,
[Parameter(ParameterSetName='array',Position=1)]
[int[]]
$Array
)
return$PSCmdlet.ParameterSetName
}
f 1
f 1,2
f ([System.Collections.ArrayList]@(1,2))
f ([System.Collections.Queue]@(1,2))
decides on the parameter set based on the result of LanguagePrimitives.GetEnumerator for the argument?
The first three calls to f seem consistent with that. However, f ([System.Collections.Queue]@(1,2)) tries (and fails) to bind to $Array despite non-null LP.GetEnumerator for the argument. Trace-Command mentions "Argument type Queue is not IList, treating this as scalar" in that case. That business seems to happen in EncodeCollection which seems to try to convert the argument to an array. I didn't spot a call to GetEnumerator leading up to or in EncodeCollection, however. It seems like this decision hinges on whether the argument is IList rather than LP.GetEnumerator. Is that interpretation correct?
For operators, the rules are implemented here, and I apologize that the code is not necessarily easy to understand.
...
True by construction for pipelines, rule to aspire to for operators.
Do these statements also apply to the . (property) operator? That operator has some behavior whose rules I haven't quite worked out. It seems to enumerate according to LP.GetEnumerator, but only in some cases. For example,
$x='1','2'$x.Chars$x.Length
enumerates $x in $x.Chars but not $x.Length. Can you shed some light on this?
For property access or method invocation, if the object has the property/method, no enumeration takes place.
Count and Length are special - they are assumed to exist on all objects - if the object has the property, it is used, otherwise the sane value is returned (0 for null, 1 otherwise).
Ok. That seems to explain the behavior for all properties and methods I've tested except Length. The behavior for Length seems less like Count and more like other properties. Consider the following:
$y.Length is 3,4 which seems to correspond to the synthesized .Length for each of the strings. $y.Count, on the other hand, is 2. I think that ('1','2').Length is 2 (from my prior example) because [array] has a .Length property for some reason.
Ah, right. If the object is enumerable, then we enumerate. Array adds an alias so it has both Count and Length, but other enumerable types do not, so it's not entirely consistent.
In hindsight, maybe we should have only added Count and not Length to every object.
Activity
[-]Are the rules for conditional enumeration the same for equality/relational operators and the pipeline?[/-][+]Are the rules for conditional enumeration the same for operators and the pipeline?[/+][-]Are the rules for conditional enumeration the same for operators and the pipeline?[/-][+]Are the rules for conditional enumeration the same for comparison operators and the pipeline?[/+]lzybkr commentedon Jan 9, 2018
The rules are intended to be the same.
For operators, the rules are implemented here, and I apologize that the code is not necessarily easy to understand.
For the pipeline, the decision is made by
LanguagePrimitives.GetEnumerator, you can see the call here.True by construction for pipelines, rule to aspire to for operators.
Parameter binding comes to mind - e.g. when to bind to a parameter of type
objectversusobject[]. To the best of my knowledge and memory, operators all rely onPSEnumerableBinder.IsEnumerable(there are many uses) and everything else relies onLanguagePrimitives.GetEnumerator.alx9r commentedon Jan 10, 2018
Thanks Jason Shirk (@lzybkr). With respect to parameter binding, do you mean that
decides on the parameter set based on the result of
LanguagePrimitives.GetEnumeratorfor the argument?The first three calls to
fseem consistent with that. However,f ([System.Collections.Queue]@(1,2))tries (and fails) to bind to$Arraydespite non-nullLP.GetEnumeratorfor the argument.Trace-Commandmentions "Argument type Queue is not IList, treating this as scalar" in that case. That business seems to happen inEncodeCollectionwhich seems to try to convert the argument to an array. I didn't spot a call toGetEnumeratorleading up to or inEncodeCollection, however. It seems like this decision hinges on whether the argument isIListrather thanLP.GetEnumerator. Is that interpretation correct?lzybkr commentedon Jan 10, 2018
Right - I forgot that it's not quite the same - I don't know the reason why though.
alx9r commentedon Jan 10, 2018
Jason Shirk (@lzybkr)
Do these statements also apply to the
.(property) operator? That operator has some behavior whose rules I haven't quite worked out. It seems to enumerate according toLP.GetEnumerator, but only in some cases. For example,enumerates
$xin$x.Charsbut not$x.Length. Can you shed some light on this?lzybkr commentedon Jan 10, 2018
For property access or method invocation, if the object has the property/method, no enumeration takes place.
CountandLengthare special - they are assumed to exist on all objects - if the object has the property, it is used, otherwise the sane value is returned (0 for null, 1 otherwise).alx9r commentedon Jan 11, 2018
Ok. That seems to explain the behavior for all properties and methods I've tested except
Length. The behavior forLengthseems less likeCountand more like other properties. Consider the following:$y.Lengthis3,4which seems to correspond to the synthesized.Lengthfor each of the strings.$y.Count, on the other hand, is2. I think that('1','2').Lengthis2(from my prior example) because[array]has a.Lengthproperty for some reason.lzybkr commentedon Jan 11, 2018
Ah, right. If the object is enumerable, then we enumerate. Array adds an alias so it has both Count and Length, but other enumerable types do not, so it's not entirely consistent.
In hindsight, maybe we should have only added Count and not Length to every object.