Skip to content

Are the rules for conditional enumeration the same for comparison operators and the pipeline? #5832

Closed

Description

@alx9r

Consider the following treatments of $x:

$x -eq $RHS
$x -contains $RHS

function f { param([Parameter(ValueFromPipeline)]$y) process{$y} }
$x | f

function g { 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.

  1. Are the rules that decide whether or not $x is enumerated the same for the pipeline and those comparison operators that perform conditional enumeration?
  2. 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?
  3. 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?

Activity

  1. 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
  2. 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
  3. lzybkr commented on Jan 9, 2018

    @lzybkr
    Contributor
    1. 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.

    2. True by construction for pipelines, rule to aspire to for operators.

    3. 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.

  4. added
    Issue-Discussionthe issue may not have a clear classification yet. The issue may generate an RFC or may be reclassif
    on Jan 9, 2018
  5. alx9r commented on Jan 10, 2018

    @alx9r
    Author

    Thanks Jason Shirk (@lzybkr). With respect to parameter binding, do you mean that

    function f {
        [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?

  6. lzybkr commented on Jan 10, 2018

    @lzybkr
    Contributor

    Right - I forgot that it's not quite the same - I don't know the reason why though.

  7. alx9r commented on Jan 10, 2018

    @alx9r
    Author

    Jason Shirk (@lzybkr)

    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?

  8. lzybkr commented on Jan 10, 2018

    @lzybkr
    Contributor

    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).

  9. alx9r commented on Jan 11, 2018

    @alx9r
    Author

    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 = [System.Collections.Queue]@('123','1234')
    $y.Length
    $y.Count

    $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.

  10. lzybkr commented on Jan 11, 2018

    @lzybkr
    Contributor

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue-Discussionthe issue may not have a clear classification yet. The issue may generate an RFC or may be reclassifResolution-AnsweredThe question is answered.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions