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

Cache the reflection call done for completions #736

Merged
merged 4 commits into from
Sep 17, 2018

Conversation

rjmholt
Copy link
Contributor

@rjmholt rjmholt commented Aug 31, 2018

I noticed that completion queries do a reflection call every time. This code caches instead, but it's not terribly elegant. So if you have any suggestions, please let me know.

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.

Good call! One possible optimization.

@@ -337,5 +326,28 @@ static public string[] FindDotSourcedIncludes(Ast scriptAst)

return dotSourcedVisitor.DotSourcedFiles.ToArray();
}

private static MethodInfo GetExtentCloneMethod(Ast scriptAst)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this would be better as just a static field. If your looking for how you could do that without an instance, you could do this:

private static MethodInfo s_extentCloneWithNewOffset =
    typeof(PSObject).Assembly.GetType("System.Management.Automation.InternalScriptExtent")
        .GetMethod(...

Maybe set in a static constructor because of the compiler directives.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I was wondering what the best way to do this was. Figured we'd find a better way in the PR. Thanks!

@rjmholt
Copy link
Contributor Author

rjmholt commented Sep 3, 2018

Ok, I've updated to use a better way to get the MethodInfo.

Once we're on netstandard2.0, we might be able to improve on the object[] allocation with each invocation by creating a delegate. I'm not quite sure if it's possible since InternalScriptPosition is internal and thus hard to pass around a Func<> for, but it can be created. See:

You can model what I'm imagining doing with this PowerShell:

$cwno = [psobject].GetTypeInfo().Assembly.GetType("System.Management.Automation.Language.InternalScriptPosition", $true).GetMethod("CloneWithNewOffset", [System.Reflection.BindingFlags]::Instance -bor [System.Reflection.BindingFlags]::NonPublic)

$t = [System.Linq.Expressions.Expression]::GetFuncType(@($cwno.ReturnType, [int], $cwno.ReturnType))

[System.Delegate]::CreateDelegate($t, $null, $cwno)   

That should create the open delegate we want. The problem is just being able to invoke it when we can't express the type of the Func<> statically.

@SeeminglyScience
Copy link
Collaborator

SeeminglyScience commented Sep 3, 2018

You could craft the delegate using LINQ expressions, e.g.

using namespace System.Linq.Expressions
using namespace System.Management.Automation.Language

$scriptPositionType = [psobject].Assembly.
    GetType('System.Management.Automation.Language.InternalScriptPosition')

$method = $scriptPositionType.
    GetMethod(
        'CloneWithNewOffset',
        [System.Reflection.BindingFlags]'NonPublic, Instance',
        $null,
        [type[]][int],
        @(1))

$source = [Expression]::Parameter([IScriptPosition], 'source')
$offset = [Expression]::Parameter([int], 'offset')

# (IScriptPosition)((InternalScriptPosition)source).CloneWithNewOffset(source, offset)
$delegateBody = 
    [Expression]::Convert(
        [Expression]::Call(
            [Expression]::Convert(
                $source,
                $scriptPositionType),
            $method,
            $offset),
        [IScriptPosition])

$delegate = [Expression]::Lambda(
    [Func[IScriptPosition, int, IScriptPosition]],
    $delegateBody,
    [ParameterExpression[]]($source, $offset)).
    Compile()

You could also write it in IL with DynamicMethod. That would be a little bit faster, but then you need to add the currently unlisted System.Reflection.Emit package, and readability would be pretty shot.

@rjmholt
Copy link
Contributor Author

rjmholt commented Sep 3, 2018

Yeah I thought about writing a compiled LINQ expression around it, especially since that might be the right way to get around having to provide an InternalScriptExtent as an input (we can add a cast like you have).

But as you say, it gets pretty out of hand. Especially if we're just trying to avoid an array allocation.

Copy link
Member

@TylerLeonhardt TylerLeonhardt left a comment

Choose a reason for hiding this comment

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

LGTM

@rjmholt
Copy link
Contributor Author

rjmholt commented Sep 6, 2018

Oooh, tests have found another breakage! Will look into it tomorrow.

Copy link
Contributor

@rkeithhill rkeithhill left a comment

Choose a reason for hiding this comment

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

I like it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants