Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Treating scalars implicitly as collections doesn't fully work with custom objects ([pscustomobject]) - lacks a .Count property #3671

Closed
mklement0 opened this issue May 1, 2017 · 13 comments · Fixed by #5756
Assignees
Labels
Issue-Bug Issue has been identified as a bug in the product Resolution-Fixed The issue is fixed. Up-for-Grabs Up-for-grabs issues are not high priorities, and may be opportunities for external contributors WG-Language parser, language semantics

Comments

@mklement0
Copy link
Contributor

mklement0 commented May 1, 2017

Treating scalars as if they were collections is a powerful feature that eliminated many bugs when it was introduced in PSv3: it means that even a single object returns meaningful values for .Count and .Length and allows indexing with [0].

Unfortunately, not all scalar objects behave this way, a notable exception being custom objects ([pscstomobject]), such as the instances created by Select-Object.

The problem is the absence of the .Count / .Length properties.
Similarly, using collection operators .Where() and .ForEach() also does not work.

By contrast, indexing custom objects with [0] does work.

I wonder if there are other cases where cmdlets return objects (other than [pscustomobject] instances) that behave the same way.

Steps to reproduce

$null.Count
(Get-Item /).Count
(Get-Item / | Select-Object Name).Count  # !! produces no output
([pscustomobject] @{ foo = 'bar' }).Count # !! produces no output

Expected behavior

0
1
1
1

Actual behavior

0
1

^ 3rd and 4th command didn't produce output, because the implicitly and explicitly created [pscustomobject] instances have no .Count and .Length properties.

Environment data

PowerShell Core v6.0.0-alpha (v6.0.0-alpha.18) on Darwin Kernel Version 16.5.0: Fri Mar  3 16:52:33 PST 2017; root:xnu-3789.51.2~3/RELEASE_X86_64
@iSazonov iSazonov added WG-Language parser, language semantics Issue-Question ideally support can be provided via other mechanisms, but sometimes folks do open an issue to get a labels May 2, 2017
@iSazonov
Copy link
Collaborator

iSazonov commented May 2, 2017

It is behavior of pscustomobject:

([PSCustomObject]@{q=1}).Count
<empty>

([PSObject]@{q=1}).Count
1

@mklement0 mklement0 changed the title Treating scalars implicitly as collections doesn't work with objects created by Select-Object Treating scalars implicitly as collections doesn't fully work with custom objects ([pscustomobject]) May 2, 2017
@mklement0
Copy link
Contributor Author

mklement0 commented May 2, 2017

@iSazonov

Thanks for the clarification - that simplifies the issue.
I've updated the title and the initial post accordingly.

@lzybkr lzybkr added Issue-Bug Issue has been identified as a bug in the product Up-for-Grabs Up-for-grabs issues are not high priorities, and may be opportunities for external contributors and removed Issue-Question ideally support can be provided via other mechanisms, but sometimes folks do open an issue to get a labels May 2, 2017
@SteveL-MSFT SteveL-MSFT added this to the 6.1.0 milestone May 2, 2017
@iSazonov
Copy link
Collaborator

iSazonov commented May 3, 2017

Where can I start?

@mklement0 mklement0 changed the title Treating scalars implicitly as collections doesn't fully work with custom objects ([pscustomobject]) Treating scalars implicitly as collections doesn't fully work with custom objects ([pscustomobject]) - lacks a .Count property Dec 17, 2017
@KevinMarquette
Copy link
Contributor

This is also an issue for foreach and where methods.

$test = [pscustomobject]@{a=1}
$test.foreach({$_})
Method invocation failed because [System.Management.Automation.PSCustomObject] does not contain a method named 'foreach'

Other single objects and null have support for this method.

$null.foreach({$_})

$date = get-date
$date.foreach({$_})
Wednesday, December 20, 2017 11:03:18 AM

I suspect they are the same underlying issue is why I didn't create a new issue for this.

@iSazonov
Copy link
Collaborator

@lzybkr Could you please help - where is the magic code?

@lzybkr
Copy link
Member

lzybkr commented Dec 21, 2017

I think the magic code is here but I'd need to debug to see what's going wrong because I don't see the bug with code inspection.

@iSazonov
Copy link
Collaborator

The code isn't called at all 😕

@lzybkr
Copy link
Member

lzybkr commented Dec 22, 2017

@iSazonov - I don't normally write code that isn't called :) It is possible that the code I pointed to isn't reachable for pscustomobject, but it is definitely reachable.

I will also point out - most of the code in binders.cs is generating code for an operation which is cached, so if some binding happened before you set a breakpoint, you'll never hit it again. For example, if you attach after startup, it's possible loading a module or even evaluating your prompt hit the code and you won't hit it again.

Starting the process under the debugger helps, or you can resort to adding some trace output or a message box so you can attach and debug.

@iSazonov
Copy link
Collaborator

😄 Yes, I meant pscustomobject context. And yes, it is cache - after first call the code isn't called for "Count".

I discover a difference in GetPSMemberInfo in line

  • ([PSObject]@{q=1}).Count - adapterSet is DotNetAdapter and canOptimize is true
  • ([PSCustomObject]@{q=1}).Count - adapterSet is PSObjectAdapter and canOptimize is false

If I change canOptimize to true in debuger I get right output - Count = 1.

I don't know that is right fix.

@lzybkr
Copy link
Member

lzybkr commented Dec 24, 2017

That is not the right fix.

I think the right fix is to change PSGetMemberBinder.GetAdaptedValue - before the check at the end to throw PropertyNotFoundException, check it the member name is Count and just return 1.

@iSazonov iSazonov self-assigned this Dec 26, 2017
iSazonov added a commit that referenced this issue Dec 28, 2017
Related #3671
•Add Count and Length properties to [PSCustomobject].
 Now following returns 1:
 ([pscustomobject] @{ foo = 'bar' }).Count
 ([pscustomobject] @{ foo = 'bar' }).Length
•Add tests
@iSazonov
Copy link
Collaborator

First part was fixed - ([pscustomobject] @{ foo = 'bar' }).Count returns 1.

@iSazonov
Copy link
Collaborator

@lzybkr I again don't know where right fix is for Foreach/Where.
I found that if I changed canOptimize to true in the line the ([PSCustomObject]@{q=1}).Foreach({$_}) works due to the line. Also we have a special case for $null.Where in the line
Could you please help?

@lzybkr
Copy link
Member

lzybkr commented Dec 28, 2017

@iSazonov - the change will be similar to what you did for Count, but it will be in PSInvokeMemberBinder.InvokeAdaptedMember and would return an empty Collection<object> in both cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Issue-Bug Issue has been identified as a bug in the product Resolution-Fixed The issue is fixed. Up-for-Grabs Up-for-grabs issues are not high priorities, and may be opportunities for external contributors WG-Language parser, language semantics
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants