Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions src/System.Management.Automation/engine/lang/scriptblock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -647,16 +647,17 @@ internal ReadOnlyCollection<PSTypeName> OutputType
/// <remarks>
/// This does normal array reduction in the case of a one-element array.
/// </remarks>
internal static object GetRawResult(List<object> result)
internal static object GetRawResult(List<object> result, bool wrapToPSObject)
{
switch (result.Count)
{
case 0:
return AutomationNull.Value;
case 1:
return LanguagePrimitives.AsPSObjectOrNull(result[0]);
return wrapToPSObject ? LanguagePrimitives.AsPSObjectOrNull(result[0]) : result[0];
default:
return LanguagePrimitives.AsPSObjectOrNull(result.ToArray());
object resultArray = result.ToArray();
return wrapToPSObject ? LanguagePrimitives.AsPSObjectOrNull(resultArray) : resultArray;
Comment on lines +659 to +660
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the converting to immutable array mandatory?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@iSazonov Sorry for the super long delay to respond to your comment.
The result.ToArray() is existing code, and it would be a breaking change to end users to change that because users may expect an object array as it's always be.

Copy link
Collaborator

@iSazonov iSazonov Mar 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the PR breaks an C# application in any case and developers have to fix their code we could weight removing result.ToArray() too. If we think about enumeration the replacing List<object> with Array does seem not break this. We could add test for the default case.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@daxian-dbw Could you please share your thoughts about my last comment?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this method is also used by DoInvokeReturnAsIs. The current change keeps the existing behavior of that API, but removing result.ToArray() definitely breaks it.
Also, there is no obvious benefit by removing result.ToArray() except for a bit less allocation that may not be noticeable, so I don't think a breaking change can be justified.

}
}

Expand Down Expand Up @@ -807,7 +808,7 @@ internal object InvokeAsDelegateHelper(object dollarUnder, object dollarThis, ob
outputPipe: outputPipe,
invocationInfo: null,
args: args);
return GetRawResult(rawResult);
return GetRawResult(rawResult, wrapToPSObject: false);
}

#endregion
Expand Down Expand Up @@ -934,7 +935,7 @@ internal object DoInvokeReturnAsIs(
outputPipe: outputPipe,
invocationInfo: null,
args: args);
return GetRawResult(result);
return GetRawResult(result, wrapToPSObject: true);
}

internal void InvokeWithPipe(
Expand Down
50 changes: 50 additions & 0 deletions test/powershell/engine/Api/LanguagePrimitive.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,54 @@ Describe "Language Primitive Tests" -Tags "CI" {
$val | Should -BeTrue
$result | Should -BeExactly $compareResult
}

It "Convert ScriptBlock to delegate type" {
$code = @'
using System;
namespace Test.API
{
public enum TestEnum
{
Music,
Video
}
public class LanguagePrimitivesTest
{
Func<string, object> _handlerReturnObject;
Func<string, TestEnum> _handlerReturnEnum;
public LanguagePrimitivesTest(Func<string, object> handlerReturnObject, Func<string, TestEnum> handlerReturnEnum)
{
_handlerReturnObject = handlerReturnObject;
_handlerReturnEnum = handlerReturnEnum;
}

public bool TestHandlerReturnEnum()
{
var value = _handlerReturnEnum("bar");
return value == TestEnum.Music;
}

public bool TestHandlerReturnObject()
{
object value = _handlerReturnObject("bar");
return value is TestEnum;
}
}
}
'@

if (-not ("Test.API.TestEnum" -as [type]))
{
Add-Type -TypeDefinition $code
}

# The script actually returns a enum value, and the converted delegate should return the boxed enum value.
$handlerReturnObject = [System.Func[string, object]] { param([string]$str) [Test.API.TestEnum]::Music }
# The script actually returns a string, and the converted delegate should return the corresponding enum value.
$handlerReturnEnum = [System.Func[string, Test.API.TestEnum]] { param([string]$str) "Music" }
$test = [Test.API.LanguagePrimitivesTest]::new($handlerReturnObject, $handlerReturnEnum)

$test.TestHandlerReturnEnum() | Should -BeTrue
$test.TestHandlerReturnObject() | Should -BeTrue
}
}