Skip to content

Commit 06e052f

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

File tree

3 files changed

+30
-7
lines changed

3 files changed

+30
-7
lines changed

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: 17 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,14 @@ 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 (s_whereSearchValues.Contains(Name))
66856694
{
66866695
return InvokeWhereOnCollection(emptyEnumerator, args, argRestrictions).WriteToDebugLog(this);
66876696
}
66886697

6689-
if (string.Equals(Name, "ForEach", StringComparison.OrdinalIgnoreCase))
6698+
if (s_foreachSearchValues.Contains(Name))
6699+
// We need to pass the empty enumerator to the ForEach operator, so that it can return an empty collection.
6700+
// The ForEach operator will not be able to call the script block if the enumerator is empty.
66906701
{
66916702
return InvokeForEachOnCollection(emptyEnumerator, args, argRestrictions).WriteToDebugLog(this);
66926703
}
@@ -6866,12 +6877,12 @@ public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target,
68666877
if (!_static && !_nonEnumerating && target.Value != AutomationNull.Value)
68676878
{
68686879
// Invoking Where and ForEach operators on collections.
6869-
if (string.Equals(Name, "Where", StringComparison.OrdinalIgnoreCase))
6880+
if (s_whereSearchValues.Contains(Name))
68706881
{
68716882
return InvokeWhereOnCollection(target, args, restrictions).WriteToDebugLog(this);
68726883
}
68736884

6874-
if (string.Equals(Name, "ForEach", StringComparison.OrdinalIgnoreCase))
6885+
if (s_foreachSearchValues.Contains(Name))
68756886
{
68766887
return InvokeForEachOnCollection(target, args, restrictions).WriteToDebugLog(this);
68776888
}
@@ -7490,7 +7501,7 @@ internal static object InvokeAdaptedMember(object obj, string methodName, object
74907501
// As a last resort, we invoke 'Where' and 'ForEach' operators on singletons like
74917502
// ([pscustomobject]@{ foo = 'bar' }).Foreach({$_})
74927503
// ([pscustomobject]@{ foo = 'bar' }).Where({1})
7493-
if (string.Equals(methodName, "Where", StringComparison.OrdinalIgnoreCase))
7504+
if (s_whereSearchValues.Contains(methodName))
74947505
{
74957506
var enumerator = (new object[] { obj }).GetEnumerator();
74967507
switch (args.Length)
@@ -7506,7 +7517,7 @@ internal static object InvokeAdaptedMember(object obj, string methodName, object
75067517
}
75077518
}
75087519

7509-
if (string.Equals(methodName, "Foreach", StringComparison.OrdinalIgnoreCase))
7520+
if (s_foreachSearchValues.Contains(methodName))
75107521
{
75117522
var enumerator = (new object[] { obj }).GetEnumerator();
75127523
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)