-
Notifications
You must be signed in to change notification settings - Fork 235
Go To Definition works with different Ast types #706
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great! Awesome work 😄
Just a couple optional suggestions for optimizing.
List<VariableExpressionAst> asts = FindMatchingAsts(assignmentStatementAst.Left, variableName); | ||
if (asts.Count > 0) | ||
{ | ||
FoundDeclaration = new SymbolReference(SymbolType.Variable, asts.FirstOrDefault().Extent); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this only check the first variable when the lhs is an array literal? If so we should enumerate until we find a match or the list is completed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May be worth looking at this method (in typical fashion, it's private, but still may be helpful):
https://github.com/PowerShell/PowerShell/blob/a3786158ca51cd65388743f900b69ec9e253c3d9/src/System.Management.Automation/engine/parser/ast.cs#L7367-L7379
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@SeeminglyScience that will grab the first in an array literal, I guess. But we probably want the LAST hit from an array literal.
$a, [string] $b = "asdf", 5, $b = 26, 5
$b
FindMatchingAsts for $b
will give the 2 hits in a list...
we want $b
to jump to $b = 26
not [string] $b = "asdf"
so I'll have it grab the last instead of the first - good catch.
@rjmholt looks like they went iterative. I like that, but it's a little extra code when we could just continue to recurse (since we're already doing it). Thanks for the reference though :) let me know if you feel strongly otherwise
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer the clarity of recursion actually. But thought it was worth noting that an implementation already exists
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tylerl0706 Initially I thought the method was just returning all VariableExpressionAst
's, so my comment doesn't make a lot of sense :P
That said, I would stick with getting the first assignment.
Take this example
$a, [string] $b, $b = 1, 2, 3
Here we know with certainty what the value of the RHS is. So we can assume that the value of $b
is going to be 3. However, the declaration is still the middle variable. If you run that snippet, $b
is typed as a string even though the value is 3
. We also won't know in most cases what the value or length of the RHS will be, so it's not a safe assumption that it will have any value.
It'll also save a little bit of processing time in some (very) fringe cases (assuming the switch to the yield
style generalized method)
variableName, | ||
StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
return new List<VariableExpressionAst>{ variableExprAst }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Neat! I didn't know you could omit the parenthesis 😃
|
||
// VaraibleExpressionAst case - aka base case. This will return a list with the variableExpressionAst in it or an empty list if it's not the right one. | ||
var variableExprAst = ast as VariableExpressionAst; | ||
if (variableExprAst != null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the variable will only be used within the if
block, an is assignment could be used here instead. (e.g. if (ast is VariableExpressionAst variableExprAst) {}
)
Or possibly an assignment switch
switch (ast)
{
case ConvertExpressionAst convertExpression:
{
FindMatchingAsts(convertExpressionAst);
break;
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I reckon a type switch is probably clearest here -- it's probably how the PowerShell compiler itself would work if it were written today.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I saw a bunch of articles that said this wasn't possible hmm - I'll give it a go
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The MS Docs seem to call it pattern matching. (Which is guess is approximately true...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ohhhh, but this may not be possible in all the .NET versions we support
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Historical note: We think this should work)
{ | ||
return AstVisitAction.Continue; | ||
return (arrayLiteralAst.Elements.SelectMany(e => FindMatchingAsts(e, variableName)).ToList()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't super important but this method might be a lot more efficient in assignments with a lot of decoration if it used yield.
If you change the return type to IEnumerable<VariableExpressionAst>
and change the other return statements to something like
foreach (var foundVariableAst in FindMatchingAsts(convertExprAst.Child))
{
yield return foundVariableAst;
}
yield break;
Then you could remove the variable name parameter and make this a more general utility function. You could then do the actual test in the caller while still iterating only as much as you need to.
List<VariableExpressionAst> asts = FindMatchingAsts(assignmentStatementAst.Left, variableName); | ||
if (asts.Count > 0) | ||
{ | ||
FoundDeclaration = new SymbolReference(SymbolType.Variable, asts.FirstOrDefault().Extent); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May be worth looking at this method (in typical fashion, it's private, but still may be helpful):
https://github.com/PowerShell/PowerShell/blob/a3786158ca51cd65388743f900b69ec9e253c3d9/src/System.Management.Automation/engine/parser/ast.cs#L7367-L7379
|
||
// VaraibleExpressionAst case - aka base case. This will return a list with the variableExpressionAst in it or an empty list if it's not the right one. | ||
var variableExprAst = ast as VariableExpressionAst; | ||
if (variableExprAst != null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I reckon a type switch is probably clearest here -- it's probably how the PowerShell compiler itself would work if it were written today.
/// <param name="ast">An ExpressionAst</param> | ||
/// <param name="variableName">The name of the variable we are trying to find</param> | ||
/// <returns>A list of ExpressionAsts that match the variable name provided</returns> | ||
private static List<VariableExpressionAst> FindMatchingAsts (ExpressionAst ast, string variableName) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May want to remove the space between the method name and the opening paren here
return AstVisitAction.StopVisit; | ||
// The ConvertExpressionAst (static casting for example `[string]$foo = "asdf"`) could contain a VariableExpressionAst so we recurse down | ||
var convertExprAst = ast as ConvertExpressionAst; | ||
if (convertExprAst != null && convertExprAst.Child != null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally we're moving to the switch, but the null check here could be compressed to convertExprAst?.Child != null
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can actually skip the null on Child
, it shouldn't be null.
Incidentally, I didn't realize ConvertExpressionAst
inherited AttributedExpressionAst
, so @tylerl0706 you probably only need to check AttributedExpressionAst
and ArrayLiteralAst
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh but apparently this works
($a, $b) = 1, 2
So replace it with ParenExpressionAst
:P
@rjmholt + @SeeminglyScience pretty big refactor in that last commit - can you both re-review? I switched to using an AstVisitor (Thanks Patrick for helping me! :) ) |
FoundDeclaration = new SymbolReference(SymbolType.Variable, variableExprAst.Extent); | ||
return AstVisitAction.StopVisit; | ||
// The AssignmentStatementAst could contain either of the following Ast types: | ||
// VariableExpressionAst, ArrayLiteralAst, ConvertExpressionAst, AttributedExpressionAst |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gah comment is wrong
{ | ||
if(variableExpressionAst.VariablePath.UserPath.Equals(variableName, StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
// TODO also find instances of set-variable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice link - we'd pretty much need that entire class and rework it to do what we do. I've grabbed a few quick wins from it and applied them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Set-Variable is particularly annoying because it's not an assignmentstatementast same with dot sourcing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah exactly. I think we'll solve 99% with this, no reason to overcomplicate it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome job! This is much cleaner :)
One possible optimization and one nit pick.
return AstVisitAction.StopVisit; | ||
// We want to check VariableExpressionAsts from within this AssignmentStatementAst so we visit it. | ||
FindDeclarationVariableExpressionVisitor visitor = new FindDeclarationVariableExpressionVisitor(symbolRef); | ||
assignmentStatementAst.Visit(visitor); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to make this assignmentStatementAst.Left.Visit(visitor)
and drop the other methods in FindDeclarationVariableExpressionVisitor
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also if you go this route, add an override for VisitMemberExpression
and have it return skip children. If I remember correctly that should be the only type of expression that can be on the lhs and also contain a child variable expression that would be invalid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually also VisitIndexExpression
/// or a decision to continue if it wasn't found</returns> | ||
public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) | ||
{ | ||
if(variableExpressionAst.VariablePath.UserPath.Equals(variableName, StringComparison.OrdinalIgnoreCase)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Space after if
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
/// </summary> | ||
/// <param name="variableExpressionAst">A VariableExpressionAst</param> | ||
/// <returns>A decision to stop searching if the right VariableExpressionAst was found, | ||
/// or a decision to continue if it wasn't found</returns> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this just GH's diff viewer or does the comment above not align with the method below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM other than comment about "comment" alignment.
fixes PowerShell/vscode-powershell#1439
The issue was that when you hit F12 (aka go to definition) on any of the following scenarios except for the first one, they would fail to find the definition.
All these scenarios now work with this PR.
We needed to recurse the AST to find the VariableExpressionAst because it could be nested in another Ast type.