Add PSForEach and PSWhere as aliases for the PowerShell intrinsic methods Where and Foreach (#25511)

This commit is contained in:
Staffan Gustafsson
2025-05-05 17:13:02 -07:00
committed by GitHub
parent af831cde4a
commit 9cbbb8ff32
3 changed files with 30 additions and 7 deletions
@@ -6455,7 +6455,9 @@ namespace System.Management.Automation
new List<Tuple<string, string>>
{
new Tuple<string, string>("Where", "Where({ expression } [, mode [, numberToReturn]])"),
new Tuple<string, string>("ForEach", "ForEach(expression [, arguments...])")
new Tuple<string, string>("ForEach", "ForEach(expression [, arguments...])"),
new Tuple<string, string>("PSWhere", "PSWhere({ expression } [, mode [, numberToReturn]])"),
new Tuple<string, string>("PSForEach", "PSForEach(expression [, arguments...])"),
};
// List of DSC collection-value variables
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Buffers;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -6533,6 +6534,14 @@ namespace System.Management.Automation.Language
internal sealed class PSInvokeMemberBinder : InvokeMemberBinder
{
private static readonly SearchValues<string> s_whereSearchValues = SearchValues.Create(
["Where", "PSWhere"],
StringComparison.OrdinalIgnoreCase);
private static readonly SearchValues<string> s_foreachSearchValues = SearchValues.Create(
["ForEach", "PSForEach"],
StringComparison.OrdinalIgnoreCase);
internal enum MethodInvocationType
{
Ordinary,
@@ -6681,12 +6690,14 @@ namespace System.Management.Automation.Language
.WriteToDebugLog(this);
BindingRestrictions argRestrictions = args.Aggregate(BindingRestrictions.Empty, static (current, arg) => current.Merge(arg.PSGetMethodArgumentRestriction()));
if (string.Equals(Name, "Where", StringComparison.OrdinalIgnoreCase))
// We need to pass the empty enumerator to the ForEach/Where operators, so that they can return an empty collection.
// The ForEach/Where operators will not be able to call the script block if the enumerator is empty.
if (s_whereSearchValues.Contains(Name))
{
return InvokeWhereOnCollection(emptyEnumerator, args, argRestrictions).WriteToDebugLog(this);
}
if (string.Equals(Name, "ForEach", StringComparison.OrdinalIgnoreCase))
if (s_foreachSearchValues.Contains(Name))
{
return InvokeForEachOnCollection(emptyEnumerator, args, argRestrictions).WriteToDebugLog(this);
}
@@ -6866,12 +6877,12 @@ namespace System.Management.Automation.Language
if (!_static && !_nonEnumerating && target.Value != AutomationNull.Value)
{
// Invoking Where and ForEach operators on collections.
if (string.Equals(Name, "Where", StringComparison.OrdinalIgnoreCase))
if (s_whereSearchValues.Contains(Name))
{
return InvokeWhereOnCollection(target, args, restrictions).WriteToDebugLog(this);
}
if (string.Equals(Name, "ForEach", StringComparison.OrdinalIgnoreCase))
if (s_foreachSearchValues.Contains(Name))
{
return InvokeForEachOnCollection(target, args, restrictions).WriteToDebugLog(this);
}
@@ -7490,7 +7501,7 @@ namespace System.Management.Automation.Language
// As a last resort, we invoke 'Where' and 'ForEach' operators on singletons like
// ([pscustomobject]@{ foo = 'bar' }).Foreach({$_})
// ([pscustomobject]@{ foo = 'bar' }).Where({1})
if (string.Equals(methodName, "Where", StringComparison.OrdinalIgnoreCase))
if (s_whereSearchValues.Contains(methodName))
{
var enumerator = (new object[] { obj }).GetEnumerator();
switch (args.Length)
@@ -7506,7 +7517,7 @@ namespace System.Management.Automation.Language
}
}
if (string.Equals(methodName, "Foreach", StringComparison.OrdinalIgnoreCase))
if (s_foreachSearchValues.Contains(methodName))
{
var enumerator = (new object[] { obj }).GetEnumerator();
object[] argsToPass;
@@ -200,6 +200,11 @@ Describe "Adapter Tests" -tags "CI" {
# TODO: dynamic method calls
}
It "Can use PSForEach as an alias for the Foreach magic method" {
$x = 5
$x.PSForEach({$_}) | Should -Be 5
}
}
Context "Where Magic Method Adapter Tests" {
@@ -240,6 +245,11 @@ Describe "Adapter Tests" -tags "CI" {
} -PassThru -Force
$x.Where(5) | Should -Be 10
}
It "Can use PSWhere as an alias for the Where magic method" {
$x = 5
$x.PSWhere{$true} | Should -Be 5
}
}
}