Unwrap 'ValueFromRemainingArguments' if the single element is a collection (#5109)

Unwrapping of 'ValueFromRemainingArguments' was being performed only for 'object[]' arrays (which covers the most common PowerShell binding scenarios) but could be skipped if a collection of any other type were passed to the parameter. This change unwraps 'ValueFromRemainingArguments' if the single element is a collection.
This commit is contained in:
Dave Wyatt 2017-10-19 16:44:26 -07:00 committed by Dongbo Wang
parent 5913069e58
commit 1c469aaa22
3 changed files with 39 additions and 22 deletions

View file

@ -1690,8 +1690,8 @@ namespace System.Management.Automation
// Set-ClusterOwnerNode -Owners foo,bar
// Set-ClusterOwnerNode foo bar
// Set-ClusterOwnerNode foo,bar
// we unwrap our List, but only if there is a single argument of type object[].
if (valueFromRemainingArguments.Count == 1 && valueFromRemainingArguments[0] is object[])
// we unwrap our List, but only if there is a single argument which is a collection.
if (valueFromRemainingArguments.Count == 1 && LanguagePrimitives.IsObjectEnumerable(valueFromRemainingArguments[0]))
{
cpi.SetArgumentValue(UnboundArguments[0].ArgumentExtent, valueFromRemainingArguments[0]);
}

View file

@ -440,10 +440,24 @@ namespace System.Management.Automation
internal static bool IsTypeEnumerable(Type type)
{
if (type == null) { return false; }
GetEnumerableDelegate getEnumerable = GetOrCalculateEnumerable(type);
return (getEnumerable != LanguagePrimitives.ReturnNullEnumerable);
}
/// <summary>
/// Returns True if the language considers obj to be IEnumerable
/// </summary>
/// <param name="obj">
/// IEnumerable or IEnumerable-like object
/// </param>
[SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj", Justification = "Since V1 code is already shipped, excluding this message.")]
public static bool IsObjectEnumerable(object obj)
{
return IsTypeEnumerable(PSObject.Base(obj)?.GetType());
}
/// <summary>
/// Retrieves the IEnumerable of obj or null if the language does not consider obj to be IEnumerable
/// </summary>
@ -453,26 +467,9 @@ namespace System.Management.Automation
[SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj", Justification = "Since V1 code is already shipped, excluding this message.")]
public static IEnumerable GetEnumerable(object obj)
{
if (obj == null)
{
return null;
}
Type objectType = obj.GetType();
// if the object passed is an PSObject,
// look at the base object. Notice that, if the
// object has been serialized, the base object
// will be there as an ArrayList if the original
// object was IEnumerable
if (objectType == typeof(PSObject))
{
PSObject mshObj = (PSObject)obj;
obj = mshObj.BaseObject;
objectType = obj.GetType();
}
GetEnumerableDelegate getEnumerable = GetOrCalculateEnumerable(objectType);
obj = PSObject.Base(obj);
if (obj == null) { return null; }
GetEnumerableDelegate getEnumerable = GetOrCalculateEnumerable(obj.GetType());
return getEnumerable(obj);
}

View file

@ -422,5 +422,25 @@
$result.Value[1] | Should Be 2
$result.Value[2] | Should Be 3
}
It "Binds properly when collections of type other than object[] are used on an advanced function" {
$list = [Collections.Generic.List[int]](1..3)
$result = Test-BindingFunction $list
$result.ArgumentCount | Should Be 3
$result.Value[0] | Should Be 1
$result.Value[1] | Should Be 2
$result.Value[2] | Should Be 3
}
It "Binds properly when collections of type other than object[] are used on a cmdlet" {
$list = [Collections.Generic.List[int]](1..3)
$result = Test-BindingCmdlet $list
$result.ArgumentCount | Should Be 3
$result.Value[0] | Should Be 1
$result.Value[1] | Should Be 2
$result.Value[2] | Should Be 3
}
}
}