Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not wrap return result to PSObject when converting ScriptBlock to delegate #10619

Merged
merged 3 commits into from
Mar 30, 2020

Conversation

daxian-dbw
Copy link
Member

@daxian-dbw daxian-dbw commented Sep 24, 2019

PR Summary

Avoid wrapping return result to PSObject when converting ScriptBlock to delegate.
When a ScriptBlock is converted to a delegate type, it's supposed to be used in C# context, and wrapping the result to PSObject brings unneeded troubles:

  • When the value can be converted to the delegate return type, the PSObject essentially gets unwrapped, so we basically creates PSObject in vain.
  • When the delegate return type is object, the PSObject is returned, making it hard to work with the true return value in C# code.

This is a breaking change for the delegate types with the object return type:

  • Before this change, the returned object will always be an PSObject instance.
  • After this change, the returned object is the underlying object, which could still be an PSObject if that's what the script actually returns.

But I think this would fall in the Bucket 3 Grey Area.
With this change, when setting AddToHistoryHandler to { param($line) $line -notmatch '^\s+|ToSecureString|AsPlainText' }, the time to load history entries from the history file reduces about 100 ms on my dev machine (from ~1750 to ~1650 ) for 213226 history records in the file.

PR Context

PR Checklist

@daxian-dbw daxian-dbw added the Breaking-Change breaking change that may affect users label Sep 24, 2019
Copy link
Member

@lzybkr lzybkr left a comment

Choose a reason for hiding this comment

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

This could be a nightmare to track down if it breaks any real world code. It's not the sort of thing that is easy to search for.

@daxian-dbw
Copy link
Member Author

It's fine to reject this PR if we consider it too risky.
I submit the PR only because the change is so simple.

@iSazonov
Copy link
Collaborator

it's supposed to be used in C# context,

Can a (enhanced) telemetry help to discover where (projects) and how it is used?
Our PR template mentions only PowerShell Editor Services, Completions, PSScriptAnalyzer, EditorSyntax. What other projects could be impacted by this change? These should be projects already ported to .Net Core and PowerShell Core. Have MSFT such projects internally to test the change and weight a impact?
I guess that there are not many projects that we could break. In addition, it will be a new major SDK version, which suggests the admissibility of such breaking changes - developers will in any case test new version.

@daxian-dbw
Copy link
Member Author

The only case I can think of that will be affected is that the target delegate type has the return type object, and the user is casting the return value to PSObject directly, like var psobj = (PSObject) MyHandler().
I think for someone using it this way, he/she might actually assume the script block to write out PSObject instead of knowing that the output is always wrapped into PSObject.

@iSazonov
Copy link
Collaborator

iSazonov commented Sep 26, 2019

Looking the code

Expression call = Expression.Call(
Expression.Constant(this),
CachedReflectionInfo.ScriptBlock_InvokeAsDelegateHelper,
dollarUnderExpr,
dollarThisExpr,
Expression.NewArrayInit(typeof(object), parameterExprs.Select(p => p.Cast(typeof(object)))));
if (returnsSomething)
{
call = DynamicExpression.Dynamic(
PSConvertBinder.Get(invokeMethod.ReturnType),
invokeMethod.ReturnType,
call);
}

What does the code return still unwrapped object or already PSObject? (I mean PSConvertBinder)

@daxian-dbw
Copy link
Member Author

@iSazonov Can you please rephrase your question? I'm not sure I understand it.

@iSazonov
Copy link
Collaborator

iSazonov commented Sep 26, 2019

The code I referenced calls a code you modified in the PR. I guess that PSConvertBinder.Get(invokeMethod.ReturnType) wrap results in PSObject too. If so your change is not a breaking change.

@daxian-dbw
Copy link
Member Author

PSConvertBinder doesn't wrap results in PSObject. It does the right thing :)

@TravisEz13 TravisEz13 added this to the rc.1-consider milestone Dec 3, 2019
@adityapatwardhan
Copy link
Member

@daxian-dbw Is this ready?

@daxian-dbw
Copy link
Member Author

No, this is not ready. I don't think this should be in rc.1. Nobody really reviews it and it might be rejected by the committee.

@daxian-dbw daxian-dbw added the Review - Committee The PR/Issue needs a review from the PowerShell Committee label Dec 10, 2019
@iSazonov
Copy link
Collaborator

I think it is right direction. Delegates look good for future optimizations and this cleanup once can simplify our lives.

@daxian-dbw
Copy link
Member Author

@PowerShell/powershell-committee reviewed this PR and believes the possible breaking case described in #10619 (comment) is unlikely and it falls into the Bucket 3: Unlikely Grey Area. Therefore, this PR is approved.

@daxian-dbw daxian-dbw added Committee-Reviewed PS-Committee has reviewed this and made a decision and removed Review - Committee The PR/Issue needs a review from the PowerShell Committee labels Dec 12, 2019
Comment on lines +659 to +660
object resultArray = result.ToArray();
return wrapToPSObject ? LanguagePrimitives.AsPSObjectOrNull(resultArray) : resultArray;
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.

@daxian-dbw
Copy link
Member Author

@iSazonov I just reviewed this PR and think it's ready for official review. Can you please take a look when you have time? Thanks!

@iSazonov
Copy link
Collaborator

@SeeminglyScience Perhaps you are interesting to review the PR.

@daxian-dbw daxian-dbw requested a review from rjmholt March 27, 2020 05:07
@daxian-dbw
Copy link
Member Author

@rjmholt please also review this PR. Thanks!

Copy link
Collaborator

@SeeminglyScience SeeminglyScience left a comment

Choose a reason for hiding this comment

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

LGTM

@daxian-dbw daxian-dbw assigned iSazonov and unassigned anmenaga Mar 27, 2020
Copy link
Collaborator

@rjmholt rjmholt left a comment

Choose a reason for hiding this comment

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

This seems reasonable. I take the point about real world code depending on this, but it's also something that makes more sense this way and is probably not very widely used, so seems less dangerous than we might think

@daxian-dbw
Copy link
Member Author

@iSazonov I think this is ready to merge. Thanks!

@iSazonov iSazonov merged commit c0c17de into PowerShell:master Mar 30, 2020
@iSazonov
Copy link
Collaborator

Should we have a doc issue?

@iSazonov iSazonov added CL-BreakingChange Indicates that a PR should be marked as a breaking change in the Change Log CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log Documentation Needed in this repo Documentation is needed in this repo and removed Breaking-Change breaking change that may affect users labels Mar 30, 2020
@daxian-dbw daxian-dbw deleted the sbToDelegate branch March 30, 2020 18:04
@daxian-dbw
Copy link
Member Author

Good point. I don't think we have a doc about the ScriptBlock-casting-to-delegate behavior, but I could be wrong.

@ghost
Copy link

ghost commented Apr 23, 2020

🎉v7.1.0-preview.2 has been released which incorporates this pull request.:tada:

Handy links:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CL-BreakingChange Indicates that a PR should be marked as a breaking change in the Change Log CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log Committee-Reviewed PS-Committee has reviewed this and made a decision Documentation Needed in this repo Documentation is needed in this repo
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants