Skip to content

Commit 3efafd6

Browse files
committed
Adding PS* aliases for Where|Foreach magic methods
1 parent af831cd commit 3efafd6

3 files changed

Lines changed: 32 additions & 7 deletions

File tree

src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6455,7 +6455,9 @@ private static List<CompletionResult> CompleteCommentParameterValue(CompletionCo
64556455
new List<Tuple<string, string>>
64566456
{
64576457
new Tuple<string, string>("Where", "Where({ expression } [, mode [, numberToReturn]])"),
6458-
new Tuple<string, string>("ForEach", "ForEach(expression [, arguments...])")
6458+
new Tuple<string, string>("ForEach", "ForEach(expression [, arguments...])"),
6459+
new Tuple<string, string>("PSWhere", "PSWhere({ expression } [, mode [, numberToReturn]])"),
6460+
new Tuple<string, string>("PSForEach", "PSForEach(expression [, arguments...])"),
64596461
};
64606462

64616463
// List of DSC collection-value variables

src/System.Management.Automation/engine/runtime/Binding/Binders.cs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
using System.Buffers;
45
using System.Collections;
56
using System.Collections.Concurrent;
67
using System.Collections.Generic;
@@ -6533,6 +6534,14 @@ public override DynamicMetaObject FallbackInvoke(DynamicMetaObject target, Dynam
65336534

65346535
internal sealed class PSInvokeMemberBinder : InvokeMemberBinder
65356536
{
6537+
private const string WhereMagicMethodName = "Where";
6538+
private const string WhereMagicMethodPSName = $"PS{WhereMagicMethodName}";
6539+
private const string ForeachMagicMethodName = "Foreach";
6540+
private const string ForeachMagicMethodPSName = $"PS{ForeachMagicMethodName}";
6541+
6542+
private static readonly SearchValues<string> s_whereSearchValues = SearchValues.Create([WhereMagicMethodName, WhereMagicMethodPSName], StringComparison.OrdinalIgnoreCase);
6543+
private static readonly SearchValues<string> s_foreachSearchValues = SearchValues.Create([ForeachMagicMethodName, ForeachMagicMethodPSName], StringComparison.OrdinalIgnoreCase);
6544+
65366545
internal enum MethodInvocationType
65376546
{
65386547
Ordinary,
@@ -6681,12 +6690,16 @@ public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target,
66816690
.WriteToDebugLog(this);
66826691
BindingRestrictions argRestrictions = args.Aggregate(BindingRestrictions.Empty, static (current, arg) => current.Merge(arg.PSGetMethodArgumentRestriction()));
66836692

6684-
if (string.Equals(Name, "Where", StringComparison.OrdinalIgnoreCase))
6693+
if (string.Equals(Name, WhereMagicMethodName, StringComparison.OrdinalIgnoreCase)
6694+
|| string.Equals(Name, WhereMagicMethodPSName, StringComparison.OrdinalIgnoreCase))
66856695
{
66866696
return InvokeWhereOnCollection(emptyEnumerator, args, argRestrictions).WriteToDebugLog(this);
66876697
}
66886698

6689-
if (string.Equals(Name, "ForEach", StringComparison.OrdinalIgnoreCase))
6699+
if (string.Equals(Name, "Foreach", StringComparison.OrdinalIgnoreCase)
6700+
|| string.Equals(Name, "PSForeach", StringComparison.OrdinalIgnoreCase))
6701+
// We need to pass the empty enumerator to the ForEach operator, so that it can return an empty collection.
6702+
// The ForEach operator will not be able to call the script block if the enumerator is empty.
66906703
{
66916704
return InvokeForEachOnCollection(emptyEnumerator, args, argRestrictions).WriteToDebugLog(this);
66926705
}
@@ -6866,12 +6879,12 @@ public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target,
68666879
if (!_static && !_nonEnumerating && target.Value != AutomationNull.Value)
68676880
{
68686881
// Invoking Where and ForEach operators on collections.
6869-
if (string.Equals(Name, "Where", StringComparison.OrdinalIgnoreCase))
6882+
if (s_whereSearchValues.Contains(Name))
68706883
{
68716884
return InvokeWhereOnCollection(target, args, restrictions).WriteToDebugLog(this);
68726885
}
68736886

6874-
if (string.Equals(Name, "ForEach", StringComparison.OrdinalIgnoreCase))
6887+
if (s_foreachSearchValues.Contains(Name))
68756888
{
68766889
return InvokeForEachOnCollection(target, args, restrictions).WriteToDebugLog(this);
68776890
}
@@ -7490,7 +7503,7 @@ internal static object InvokeAdaptedMember(object obj, string methodName, object
74907503
// As a last resort, we invoke 'Where' and 'ForEach' operators on singletons like
74917504
// ([pscustomobject]@{ foo = 'bar' }).Foreach({$_})
74927505
// ([pscustomobject]@{ foo = 'bar' }).Where({1})
7493-
if (string.Equals(methodName, "Where", StringComparison.OrdinalIgnoreCase))
7506+
if (s_whereSearchValues.Contains(methodName))
74947507
{
74957508
var enumerator = (new object[] { obj }).GetEnumerator();
74967509
switch (args.Length)
@@ -7506,7 +7519,7 @@ internal static object InvokeAdaptedMember(object obj, string methodName, object
75067519
}
75077520
}
75087521

7509-
if (string.Equals(methodName, "Foreach", StringComparison.OrdinalIgnoreCase))
7522+
if (s_foreachSearchValues.Contains(methodName))
75107523
{
75117524
var enumerator = (new object[] { obj }).GetEnumerator();
75127525
object[] argsToPass;

test/powershell/engine/ETS/Adapter.Tests.ps1

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,11 @@ Describe "Adapter Tests" -tags "CI" {
200200

201201
# TODO: dynamic method calls
202202
}
203+
204+
It "Can use PSForEach as an alias for the Foreach magic method" {
205+
$x = 5
206+
$x.PSForEach({$_}) | Should -Be 5
207+
}
203208
}
204209

205210
Context "Where Magic Method Adapter Tests" {
@@ -240,6 +245,11 @@ Describe "Adapter Tests" -tags "CI" {
240245
} -PassThru -Force
241246
$x.Where(5) | Should -Be 10
242247
}
248+
249+
It "Can use PSWhere as an alias for the Where magic method" {
250+
$x = 5
251+
$x.PSWhere({$true}) | Should -Be 5
252+
}
243253
}
244254
}
245255

0 commit comments

Comments
 (0)