From f9273352798672d6ce9aec890a3f00eb241e92ef Mon Sep 17 00:00:00 2001 From: Max Doerner Date: Fri, 23 Apr 2021 00:26:06 +0200 Subject: [PATCH] Add qualifying references to identifier references There are multiple pieces in the codebase that need information about the qualifying reference/declaration of a reference, e.g. to determine whether the reference is a member access on the return value of a specific member. Currently, checking this is achieved by navigating the parse tree each time, also taking into account with blocks. This commit moves the qualifier resolution to the BoundExpressionVisitor, which already has all the information present to easily determine the qualifying reference. The following qualifier structure has been implemented: - Member accesses are qualified by the reference for their lExpression. - Member accesses return their own reference as the one qualifying a potential access. - For (recursive) default member accesses the innermost access is qualified by the reference of the lExpression and each other access is qualified by the immediately contained one. - For (recursive) default member accesses the outermost access is returned as potentially qualifying. - Array accessed are qualified by the reference of their lExpression (potentially with default member accesses in between), i.e. by the reference of the array variable or array returning member. - Array accesses return themselves as potentially qualifying. (To get the array, one has to go up the chain again.) - Index accesses other than array and default member accesses are qualified by the reference of their lExpression. - Index accesses other than array and default member accesses return the reference of their lExpression as potentially qualifying. - Dictionary access expressions work like default member access expressions. - Other expressions neither take nor return a reference as (potentially) qualifying. --- Rubberduck.Parsing/Symbols/Declaration.cs | 10 +- .../Symbols/IdentifierReference.cs | 6 +- .../BoundExpressionVisitor.cs | 440 ++++++++++-------- RubberduckTests/Grammar/ResolverTests.cs | 84 ++++ 4 files changed, 337 insertions(+), 203 deletions(-) diff --git a/Rubberduck.Parsing/Symbols/Declaration.cs b/Rubberduck.Parsing/Symbols/Declaration.cs index 70c1dfb37b..5d303342ca 100644 --- a/Rubberduck.Parsing/Symbols/Declaration.cs +++ b/Rubberduck.Parsing/Symbols/Declaration.cs @@ -8,7 +8,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using Rubberduck.VBEditor.SafeComWrappers.Abstract; using Rubberduck.Parsing.Annotations.Concrete; namespace Rubberduck.Parsing.Symbols @@ -365,7 +364,7 @@ private bool IsObjectOrObjectArray } } - public void AddReference( + public IdentifierReference AddReference( QualifiedModuleName module, Declaration scope, Declaration parent, @@ -382,7 +381,8 @@ private bool IsObjectOrObjectArray int defaultMemberRecursionDepth = 0, bool isArrayAccess = false, bool isProcedureCoercion = false, - bool isInnerRecursiveDefaultMemberAccess = false + bool isInnerRecursiveDefaultMemberAccess = false, + IdentifierReference qualifyingReference = null ) { var oldReference = _references.FirstOrDefault(r => @@ -415,8 +415,10 @@ private bool IsObjectOrObjectArray defaultMemberRecursionDepth, isArrayAccess, isProcedureCoercion, - isInnerRecursiveDefaultMemberAccess); + isInnerRecursiveDefaultMemberAccess, + qualifyingReference); _references.AddOrUpdate(newReference, 1, (key, value) => 1); + return newReference; } /// diff --git a/Rubberduck.Parsing/Symbols/IdentifierReference.cs b/Rubberduck.Parsing/Symbols/IdentifierReference.cs index 31993145ec..a6953bf909 100644 --- a/Rubberduck.Parsing/Symbols/IdentifierReference.cs +++ b/Rubberduck.Parsing/Symbols/IdentifierReference.cs @@ -29,7 +29,8 @@ public class IdentifierReference : IEquatable int defaultMemberRecursionDepth = 0, bool isArrayAccess = false, bool isProcedureCoercion = false, - bool isInnerRecursiveDefaultMemberAccess = false) + bool isInnerRecursiveDefaultMemberAccess = false, + IdentifierReference qualifyingReference = null) { ParentScoping = parentScopingDeclaration; ParentNonScoping = parentNonScopingDeclaration; @@ -47,6 +48,7 @@ public class IdentifierReference : IEquatable IsProcedureCoercion = isProcedureCoercion; Annotations = annotations ?? new List(); IsInnerRecursiveDefaultMemberAccess = isInnerRecursiveDefaultMemberAccess; + QualifyingReference = qualifyingReference; } public QualifiedSelection QualifiedSelection { get; } @@ -67,6 +69,8 @@ public class IdentifierReference : IEquatable /// public Declaration ParentNonScoping { get; } + public IdentifierReference QualifyingReference { get; } + public bool IsAssignment { get; } public bool IsSetAssignment { get; } diff --git a/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs b/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs index ecdbf5f379..2dc81a8168 100644 --- a/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs +++ b/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs @@ -29,7 +29,20 @@ public BoundExpressionVisitor(DeclarationFinder declarationFinder) Visit(boundExpression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); } - private void Visit( + /// + /// Traverses the tree of expressions and adds identifier references to the referenced declarations. + /// It returns the reference that another directly depended reference would relate to, e.g. if that reference is a member access. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// The reference in an expression a member or array access on it would refer to. + private IdentifierReference Visit( IBoundExpression boundExpression, QualifiedModuleName module, Declaration scope, @@ -42,65 +55,49 @@ public BoundExpressionVisitor(DeclarationFinder declarationFinder) switch (boundExpression) { case SimpleNameExpression simpleNameExpression: - Visit(simpleNameExpression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); - break; + return Visit(simpleNameExpression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); case MemberAccessExpression memberAccessExpression: - Visit(memberAccessExpression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); - break; + return Visit(memberAccessExpression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); case IndexExpression indexExpression: - Visit(indexExpression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); - break; + return Visit(indexExpression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); case ParenthesizedExpression parenthesizedExpression: - Visit(parenthesizedExpression, module, scope, parent); - break; + return Visit(parenthesizedExpression, module, scope, parent); case LiteralExpression literalExpression: - break; + return null; case BinaryOpExpression binaryOpExpression: - Visit(binaryOpExpression, module, scope, parent); - break; + return Visit(binaryOpExpression, module, scope, parent); case UnaryOpExpression unaryOpExpression: - Visit(unaryOpExpression, module, scope, parent); - break; + return Visit(unaryOpExpression, module, scope, parent); case NewExpression newExpression: - Visit(newExpression, module, scope, parent); - break; + return Visit(newExpression, module, scope, parent); case InstanceExpression instanceExpression: - Visit(instanceExpression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); - break; + return Visit(instanceExpression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); case DictionaryAccessExpression dictionaryAccessExpression: - Visit(dictionaryAccessExpression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); - break; + return Visit(dictionaryAccessExpression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); case TypeOfIsExpression typeOfIsExpression: - Visit(typeOfIsExpression, module, scope, parent); - break; + return Visit(typeOfIsExpression, module, scope, parent); case ResolutionFailedExpression resolutionFailedExpression: - Visit(resolutionFailedExpression, module, scope, parent); - break; + return Visit(resolutionFailedExpression, module, scope, parent); case BuiltInTypeExpression builtInTypeExpression: - break; + return null; case RecursiveDefaultMemberAccessExpression recursiveDefaultMemberAccessExpression: - Visit(recursiveDefaultMemberAccessExpression, module, scope, parent, hasExplicitLetStatement, hasArrayAccess); - break; + return Visit(recursiveDefaultMemberAccessExpression, module, scope, parent, hasExplicitLetStatement, hasArrayAccess); case LetCoercionDefaultMemberAccessExpression letCoercionDefaultMemberAccessExpression: - Visit(letCoercionDefaultMemberAccessExpression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement); - break; + return Visit(letCoercionDefaultMemberAccessExpression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement); case ProcedureCoercionExpression procedureCoercionExpression: - Visit(procedureCoercionExpression, module, scope, parent); - break; + return Visit(procedureCoercionExpression, module, scope, parent); case MissingArgumentExpression missingArgumentExpression: - break; + return null; case OutputListExpression outputListExpression: - Visit(outputListExpression, module, scope, parent); - break; + return Visit(outputListExpression, module, scope, parent); case ObjectPrintExpression objectPrintExpression: - Visit(objectPrintExpression, module, scope, parent); - break; + return Visit(objectPrintExpression, module, scope, parent); default: throw new NotSupportedException($"Unexpected bound expression type {boundExpression.GetType()}"); } } - private void Visit( + private IdentifierReference Visit( ResolutionFailedExpression expression, QualifiedModuleName module, Declaration scope, @@ -111,9 +108,11 @@ public BoundExpressionVisitor(DeclarationFinder declarationFinder) { Visit(successfullyResolvedExpression, module, scope, parent); } + + return null; } - private void Visit( + private IdentifierReference Visit( SimpleNameExpression expression, QualifiedModuleName module, Declaration scope, @@ -126,7 +125,7 @@ public BoundExpressionVisitor(DeclarationFinder declarationFinder) var callee = expression.ReferencedDeclaration; var identifier = WithEnclosingBracketsRemoved(callSiteContext.GetText()); var selection = callSiteContext.GetSelection(); - expression.ReferencedDeclaration.AddReference( + return expression.ReferencedDeclaration.AddReference( module, scope, parent, @@ -145,7 +144,7 @@ private IEnumerable FindIdentifierAnnotations(QualifiedMod return _declarationFinder.FindAnnotations(module, line, AnnotationTarget.Identifier); } - private void Visit( + private IdentifierReference Visit( MemberAccessExpression expression, QualifiedModuleName module, Declaration scope, @@ -154,19 +153,19 @@ private IEnumerable FindIdentifierAnnotations(QualifiedMod bool hasExplicitLetStatement, bool isSetAssignment) { - Visit(expression.LExpression, module, scope, parent); + var qualifyingReference = Visit(expression.LExpression, module, scope, parent); // Expressions could be unbound thus not have a referenced declaration. The lexpression might still be bindable though. if (expression.Classification == ExpressionClassification.Unbound) { - return; + return null; } var callSiteContext = expression.UnrestrictedNameContext; var callee = expression.ReferencedDeclaration; var identifier = WithEnclosingBracketsRemoved(callSiteContext.GetText()); var selection = callSiteContext.GetSelection(); - expression.ReferencedDeclaration.AddReference( + return expression.ReferencedDeclaration.AddReference( module, scope, parent, @@ -177,7 +176,8 @@ private IEnumerable FindIdentifierAnnotations(QualifiedMod FindIdentifierAnnotations(module, selection.StartLine), isAssignmentTarget, hasExplicitLetStatement, - isSetAssignment); + isSetAssignment, + qualifyingReference: qualifyingReference); } private static string WithEnclosingBracketsRemoved(string identifierName) @@ -190,7 +190,7 @@ private static string WithEnclosingBracketsRemoved(string identifierName) return identifierName; } - private void Visit( + private IdentifierReference Visit( ObjectPrintExpression expression, QualifiedModuleName module, Declaration scope, @@ -203,9 +203,11 @@ private static string WithEnclosingBracketsRemoved(string identifierName) } Visit(expression.PrintMethodExpressions, module, scope, parent); + + return null; } - private void Visit( + private IdentifierReference Visit( OutputListExpression expression, QualifiedModuleName module, Declaration scope, @@ -215,9 +217,11 @@ private static string WithEnclosingBracketsRemoved(string identifierName) { Visit(itemExpression, module, scope, parent); } + + return null; } - private void Visit( + private IdentifierReference Visit( IndexExpression expression, QualifiedModuleName module, Declaration scope, @@ -226,69 +230,73 @@ private static string WithEnclosingBracketsRemoved(string identifierName) bool hasExplicitLetStatement, bool isSetAssignment) { - var containedExpression = expression.ContainedDefaultMemberRecursionExpression; - if (containedExpression != null) + // Argument lists are not affected by the resolution of the target of the index expression. + foreach (var argument in expression.ArgumentList.Arguments) { - Visit(containedExpression, module, scope, parent, hasExplicitLetStatement: hasExplicitLetStatement, hasArrayAccess: expression.IsArrayAccess); + if (argument.ReferencedParameter != null) + { + AddArgumentReference(argument, module, scope, parent); + } + + if (argument.Expression != null) + { + Visit(argument.Expression, module, scope, parent); + } + if (argument.NamedArgumentExpression != null) + { + Visit(argument.NamedArgumentExpression, module, scope, parent); + } } if (expression.IsDefaultMemberAccess) { - Visit(expression.LExpression, module, scope, parent, hasExplicitLetStatement: hasExplicitLetStatement); + var qualifyingReference = Visit(expression.LExpression, module, scope, parent, hasExplicitLetStatement: hasExplicitLetStatement); + + var containedExpression = expression.ContainedDefaultMemberRecursionExpression; + if (containedExpression != null) + { + qualifyingReference = Visit(containedExpression, module, scope, parent, hasExplicitLetStatement: hasExplicitLetStatement, hasArrayAccess: expression.IsArrayAccess, initialQualifyingReference: qualifyingReference); + } switch (expression.Classification) { case ExpressionClassification.ResolutionFailed: - AddFailedIndexedDefaultMemberReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, expression.ArgumentList.HasArguments); - break; + return AddFailedIndexedDefaultMemberReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, expression.ArgumentList.HasArguments, qualifyingReference); case ExpressionClassification.Unbound: - AddUnboundDefaultMemberReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); - break; + return AddUnboundDefaultMemberReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment, qualifyingReference); default: - if (expression.ReferencedDeclaration != null) - { - AddDefaultMemberReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); - } - - break; + return expression.ReferencedDeclaration != null + ? AddDefaultMemberReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment, qualifyingReference) + : null; } } - else if (expression.Classification != ExpressionClassification.Unbound + + if (expression.Classification != ExpressionClassification.Unbound && expression.IsArrayAccess && expression.ReferencedDeclaration != null) { - Visit(expression.LExpression, module, scope, parent, hasExplicitLetStatement: hasExplicitLetStatement); - AddArrayAccessReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); + var qualifyingReference = Visit(expression.LExpression, module, scope, parent, hasExplicitLetStatement: hasExplicitLetStatement); + + //The final default member might not have parameters and return an array. + var containedExpression = expression.ContainedDefaultMemberRecursionExpression; + if (containedExpression != null) + { + qualifyingReference = Visit(containedExpression, module, scope, parent, hasExplicitLetStatement: hasExplicitLetStatement, hasArrayAccess: expression.IsArrayAccess, initialQualifyingReference: qualifyingReference); + } + + return AddArrayAccessReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment, qualifyingReference); } - else if (expression.Classification == ExpressionClassification.Unbound + + if (expression.Classification == ExpressionClassification.Unbound && expression.ReferencedDeclaration == null) { Visit(expression.LExpression, module, scope, parent); + return null; } - else - { - // Index expressions are a bit special in that they can refer to parameterized properties and functions. - // In that case, the reference goes to the property or function. So, we pass on the assignment flags. - Visit(expression.LExpression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); - } - - // Argument lists are not affected by the resolution of the target of the index expression. - foreach (var argument in expression.ArgumentList.Arguments) - { - if (argument.ReferencedParameter != null) - { - AddArgumentReference(argument, module, scope, parent); - } - if (argument.Expression != null) - { - Visit(argument.Expression, module, scope, parent); - } - if (argument.NamedArgumentExpression != null) - { - Visit(argument.NamedArgumentExpression, module, scope, parent); - } - } + // Index expressions are a bit special in that they can refer to parameterized properties and functions. + // In that case, the reference goes to the property or function. So, we pass on the assignment flags. + return Visit(expression.LExpression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); } private void AddArgumentReference( @@ -314,20 +322,21 @@ Declaration parent FindIdentifierAnnotations(module, argumentSelection.StartLine)); } - private void AddArrayAccessReference( + private IdentifierReference AddArrayAccessReference( IndexExpression expression, QualifiedModuleName module, Declaration scope, Declaration parent, bool isAssignmentTarget, bool hasExplicitLetStatement, - bool isSetAssignment) + bool isSetAssignment, + IdentifierReference qualifyingReference) { var callSiteContext = expression.Context; var identifier = callSiteContext.GetText(); var selection = callSiteContext.GetSelection(); var callee = expression.ReferencedDeclaration; - expression.ReferencedDeclaration.AddReference( + return expression.ReferencedDeclaration.AddReference( module, scope, parent, @@ -339,23 +348,25 @@ Declaration parent isAssignmentTarget, hasExplicitLetStatement, isSetAssignment, - isArrayAccess: true); + isArrayAccess: true, + qualifyingReference: qualifyingReference); } - private void AddDefaultMemberReference( + private IdentifierReference AddDefaultMemberReference( IndexExpression expression, QualifiedModuleName module, Declaration scope, Declaration parent, bool isAssignmentTarget, bool hasExplicitLetStatement, - bool isSetAssignment) + bool isSetAssignment, + IdentifierReference qualifyingReference) { var callSiteContext = expression.LExpression.Context; var identifier = expression.Context.GetText(); var selection = callSiteContext.GetSelection(); var callee = expression.ReferencedDeclaration; - expression.ReferencedDeclaration.AddReference( + return expression.ReferencedDeclaration.AddReference( module, scope, parent, @@ -368,17 +379,19 @@ Declaration parent hasExplicitLetStatement, isSetAssignment, isIndexedDefaultMemberAccess: true, - defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); + defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth, + qualifyingReference: qualifyingReference); } - private void AddUnboundDefaultMemberReference( + private IdentifierReference AddUnboundDefaultMemberReference( IndexExpression expression, QualifiedModuleName module, Declaration scope, Declaration parent, bool isAssignmentTarget, bool hasExplicitLetStatement, - bool isSetAssignment) + bool isSetAssignment, + IdentifierReference qualifyingReference) { var callSiteContext = expression.LExpression.Context; var identifier = expression.Context.GetText(); @@ -397,18 +410,21 @@ Declaration parent FindIdentifierAnnotations(module, selection.StartLine), isSetAssignment, isIndexedDefaultMemberAccess: true, - defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); + defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth, + qualifyingReference: qualifyingReference); _declarationFinder.AddUnboundDefaultMemberAccess(reference); + return reference; } - private void AddFailedIndexedDefaultMemberReference( + private IdentifierReference AddFailedIndexedDefaultMemberReference( IndexExpression expression, QualifiedModuleName module, Declaration scope, Declaration parent, bool isAssignmentTarget, bool hasExplicitLetStatement, - bool hasArguments) + bool hasArguments, + IdentifierReference qualifyIdentifierReference) { var callSiteContext = expression.Context; var identifier = callSiteContext.GetText(); @@ -428,11 +444,13 @@ Declaration parent false, isIndexedDefaultMemberAccess: hasArguments, isNonIndexedDefaultMemberAccess: !hasArguments, - defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); + defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth, + qualifyingReference: qualifyIdentifierReference); _declarationFinder.AddFailedIndexedDefaultMemberResolution(reference); + return reference; } - private void Visit( + private IdentifierReference Visit( DictionaryAccessExpression expression, QualifiedModuleName module, Declaration scope, @@ -441,32 +459,7 @@ Declaration parent bool hasExplicitLetStatement, bool isSetAssignment) { - Visit(expression.LExpression, module, scope, parent, hasExplicitLetStatement: hasExplicitLetStatement); - - var containedExpression = expression.ContainedDefaultMemberRecursionExpression; - if (containedExpression != null) - { - Visit(containedExpression, module, scope, parent, hasExplicitLetStatement); - } - - switch (expression.Classification) - { - case ExpressionClassification.ResolutionFailed: - AddFailedIndexedDefaultMemberReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement); - break; - case ExpressionClassification.Unbound: - AddUnboundDefaultMemberReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); - break; - default: - if (expression.ReferencedDeclaration != null) - { - AddDefaultMemberReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); - } - - break; - } - - // Argument List not affected by being unbound. + // Argument List not affected by the resolution of the dictionary access. foreach (var argument in expression.ArgumentList.Arguments) { if (argument.ReferencedParameter != null) @@ -483,19 +476,40 @@ Declaration parent Visit(argument.NamedArgumentExpression, module, scope, parent); } } + + var qualifyingReference = Visit(expression.LExpression, module, scope, parent, hasExplicitLetStatement: hasExplicitLetStatement); + + var containedExpression = expression.ContainedDefaultMemberRecursionExpression; + if (containedExpression != null) + { + qualifyingReference = Visit(containedExpression, module, scope, parent, hasExplicitLetStatement, false, qualifyingReference); + } + + switch (expression.Classification) + { + case ExpressionClassification.ResolutionFailed: + return AddFailedIndexedDefaultMemberReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, qualifyingReference); + case ExpressionClassification.Unbound: + return AddUnboundDefaultMemberReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); + default: + return expression.ReferencedDeclaration != null + ? AddDefaultMemberReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment, qualifyingReference) + : null; + } } - private void AddDefaultMemberReference( + private IdentifierReference AddDefaultMemberReference( ProcedureCoercionExpression expression, QualifiedModuleName module, Declaration scope, - Declaration parent) + Declaration parent, + IdentifierReference qualifyingReference) { var callSiteContext = expression.Context; var identifier = callSiteContext.GetText(); var selection = callSiteContext.GetSelection(); var callee = expression.ReferencedDeclaration; - expression.ReferencedDeclaration.AddReference( + return expression.ReferencedDeclaration.AddReference( module, scope, parent, @@ -506,14 +520,16 @@ Declaration parent FindIdentifierAnnotations(module, selection.StartLine), isNonIndexedDefaultMemberAccess: true, defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth, - isProcedureCoercion: true); + isProcedureCoercion: true, + qualifyingReference: qualifyingReference); } - private void AddUnboundDefaultMemberReference( + private IdentifierReference AddUnboundDefaultMemberReference( ProcedureCoercionExpression expression, QualifiedModuleName module, Declaration scope, - Declaration parent) + Declaration parent, + IdentifierReference qualifyingReference) { var callSiteContext = expression.Context; var identifier = callSiteContext.GetText(); @@ -533,44 +549,56 @@ Declaration parent false, isNonIndexedDefaultMemberAccess: true, defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth, - isProcedureCoercion: true); + isProcedureCoercion: true, + qualifyingReference: qualifyingReference); _declarationFinder.AddUnboundDefaultMemberAccess(reference); + return reference; } - private void Visit( + private IdentifierReference Visit( RecursiveDefaultMemberAccessExpression expression, QualifiedModuleName module, Declaration scope, Declaration parent, bool hasExplicitLetStatement, - bool hasArrayAccess) + bool hasArrayAccess, + IdentifierReference initialQualifyingReference) { var containedExpression = expression.ContainedDefaultMemberRecursionExpression; - if (containedExpression != null) - { - Visit(containedExpression, module, scope, parent, hasExplicitLetStatement: hasExplicitLetStatement); - } + var qualifyingReference = containedExpression == null + ? initialQualifyingReference + : Visit( + containedExpression, + module, + scope, + parent, + hasExplicitLetStatement, + false, + initialQualifyingReference); if (expression.Classification != ExpressionClassification.Unbound && expression.ReferencedDeclaration != null) { - AddDefaultMemberReference(expression, module, parent, scope, hasExplicitLetStatement, !hasArrayAccess); + return AddDefaultMemberReference(expression, module, parent, scope, hasExplicitLetStatement, !hasArrayAccess, qualifyingReference); } + + return null; } - private void AddDefaultMemberReference( + private IdentifierReference AddDefaultMemberReference( RecursiveDefaultMemberAccessExpression expression, QualifiedModuleName module, Declaration scope, Declaration parent, bool hasExplicitLetStatement, - bool isInnerRecursiveDefaultMemberAccess) + bool isInnerRecursiveDefaultMemberAccess, + IdentifierReference qualifyingReference) { var callSiteContext = expression.Context; var identifier = callSiteContext.GetText(); var selection = callSiteContext.GetSelection(); var callee = expression.ReferencedDeclaration; - expression.ReferencedDeclaration.AddReference( + return expression.ReferencedDeclaration.AddReference( module, scope, parent, @@ -582,16 +610,18 @@ Declaration parent hasExplicitLetStatement: hasExplicitLetStatement, isNonIndexedDefaultMemberAccess: true, defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth, - isInnerRecursiveDefaultMemberAccess: isInnerRecursiveDefaultMemberAccess); + isInnerRecursiveDefaultMemberAccess: isInnerRecursiveDefaultMemberAccess, + qualifyingReference: qualifyingReference); } - private void AddFailedIndexedDefaultMemberReference( + private IdentifierReference AddFailedIndexedDefaultMemberReference( DictionaryAccessExpression expression, QualifiedModuleName module, Declaration scope, Declaration parent, bool isAssignmentTarget, - bool hasExplicitLetStatement) + bool hasExplicitLetStatement, + IdentifierReference qualifyingReference) { var callSiteContext = expression.Context; var identifier = callSiteContext.GetText(); @@ -610,11 +640,13 @@ Declaration parent FindIdentifierAnnotations(module, selection.StartLine), false, isIndexedDefaultMemberAccess: true, - defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); + defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth, + qualifyingReference: qualifyingReference); _declarationFinder.AddFailedIndexedDefaultMemberResolution(reference); + return reference; } - private void Visit( + private IdentifierReference Visit( LetCoercionDefaultMemberAccessExpression expression, QualifiedModuleName module, Declaration scope, @@ -622,45 +654,41 @@ Declaration parent bool isAssignmentTarget = false, bool hasExplicitLetStatement = false) { + var qualifyingReference = Visit(expression.WrappedExpression, module, scope, parent); + var containedExpression = expression.ContainedDefaultMemberRecursionExpression; if (containedExpression != null) { - Visit(containedExpression, module, scope, parent, hasExplicitLetStatement: hasExplicitLetStatement); + qualifyingReference = Visit(containedExpression, module, scope, parent, hasExplicitLetStatement: hasExplicitLetStatement, false, qualifyingReference); } - Visit(expression.WrappedExpression, module, scope, parent); - switch (expression.Classification) { case ExpressionClassification.ResolutionFailed: - AddFailedLetCoercionReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement); - break; + return AddFailedLetCoercionReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, qualifyingReference); case ExpressionClassification.Unbound: - AddUnboundDefaultMemberReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement); - break; + return AddUnboundDefaultMemberReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, qualifyingReference); default: - if (expression.ReferencedDeclaration != null) - { - AddDefaultMemberReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement); - } - - break; + return expression.ReferencedDeclaration != null + ? AddDefaultMemberReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, qualifyingReference) + : null; } } - private void AddDefaultMemberReference( + private IdentifierReference AddDefaultMemberReference( LetCoercionDefaultMemberAccessExpression expression, QualifiedModuleName module, Declaration scope, Declaration parent, bool isAssignmentTarget, - bool hasExplicitLetStatement) + bool hasExplicitLetStatement, + IdentifierReference qualifyingReference) { var callSiteContext = expression.Context; var identifier = callSiteContext.GetText(); var selection = callSiteContext.GetSelection(); var callee = expression.ReferencedDeclaration; - expression.ReferencedDeclaration.AddReference( + return expression.ReferencedDeclaration.AddReference( module, scope, parent, @@ -672,16 +700,18 @@ Declaration parent isAssignmentTarget, hasExplicitLetStatement, isNonIndexedDefaultMemberAccess: true, - defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); + defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth, + qualifyingReference: qualifyingReference); } - private void AddUnboundDefaultMemberReference( + private IdentifierReference AddUnboundDefaultMemberReference( LetCoercionDefaultMemberAccessExpression expression, QualifiedModuleName module, Declaration scope, Declaration parent, bool isAssignmentTarget, - bool hasExplicitLetStatement) + bool hasExplicitLetStatement, + IdentifierReference qualifyingReference) { var callSiteContext = expression.Context; var identifier = callSiteContext.GetText(); @@ -700,17 +730,20 @@ Declaration parent FindIdentifierAnnotations(module, selection.StartLine), false, isNonIndexedDefaultMemberAccess: true, - defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); + defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth, + qualifyingReference: qualifyingReference); _declarationFinder.AddUnboundDefaultMemberAccess(reference); + return reference; } - private void AddFailedLetCoercionReference( + private IdentifierReference AddFailedLetCoercionReference( LetCoercionDefaultMemberAccessExpression expression, QualifiedModuleName module, Declaration scope, Declaration parent, bool isAssignmentTarget, - bool hasExplicitLetStatement) + bool hasExplicitLetStatement, + IdentifierReference qualifyingReference) { var callSiteContext = expression.Context; var identifier = callSiteContext.GetText(); @@ -729,50 +762,52 @@ Declaration parent FindIdentifierAnnotations(module, selection.StartLine), false, isNonIndexedDefaultMemberAccess: true, - defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); + defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth, + qualifyingReference: qualifyingReference); _declarationFinder.AddFailedLetCoercionReference(reference); + return reference; } - private void Visit( + private IdentifierReference Visit( ProcedureCoercionExpression expression, QualifiedModuleName module, Declaration scope, Declaration parent) { - Visit(expression.WrappedExpression, module, scope, parent); + var qualifyingReference = Visit(expression.WrappedExpression, module, scope, parent); + //Although a procedure reference should never qualify anything, it does not hurt to return the reference here. switch (expression.Classification) { case ExpressionClassification.ResolutionFailed: - AddFailedProcedureCoercionReference(expression, module, scope, parent); - break; + return AddFailedProcedureCoercionReference(expression, module, scope, parent, qualifyingReference); case ExpressionClassification.Unbound: - AddUnboundDefaultMemberReference(expression, module, scope, parent); - break; + return AddUnboundDefaultMemberReference(expression, module, scope, parent, qualifyingReference); default: if (expression.ReferencedDeclaration != null) { - AddDefaultMemberReference(expression, module, scope, parent); + return AddDefaultMemberReference(expression, module, scope, parent, qualifyingReference); } - break; + return null; } } - private void AddDefaultMemberReference( + private IdentifierReference AddDefaultMemberReference( DictionaryAccessExpression expression, QualifiedModuleName module, Declaration scope, Declaration parent, bool isAssignmentTarget, bool hasExplicitLetStatement, - bool isSetAssignment) + bool isSetAssignment, + IdentifierReference qualifyingReference) { var callSiteContext = expression.DefaultMemberContext; var identifier = expression.Context.GetText(); var callee = expression.ReferencedDeclaration; var selection = callSiteContext.GetSelection(); - expression.ReferencedDeclaration.AddReference( + return expression.ReferencedDeclaration.AddReference( module, scope, parent, @@ -785,10 +820,11 @@ Declaration parent hasExplicitLetStatement, isSetAssignment, isIndexedDefaultMemberAccess: true, - defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); + defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth, + qualifyingReference: qualifyingReference); } - private void AddUnboundDefaultMemberReference( + private IdentifierReference AddUnboundDefaultMemberReference( DictionaryAccessExpression expression, QualifiedModuleName module, Declaration scope, @@ -816,13 +852,15 @@ Declaration parent isIndexedDefaultMemberAccess: true, defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); _declarationFinder.AddUnboundDefaultMemberAccess(reference); + return reference; } - private void AddFailedProcedureCoercionReference( + private IdentifierReference AddFailedProcedureCoercionReference( ProcedureCoercionExpression expression, QualifiedModuleName module, Declaration scope, - Declaration parent) + Declaration parent, + IdentifierReference qualifyingReference) { var callSiteContext = expression.Context; var identifier = callSiteContext.GetText(); @@ -841,11 +879,13 @@ Declaration parent FindIdentifierAnnotations(module, selection.StartLine), false, isNonIndexedDefaultMemberAccess: true, - defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); + defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth, + qualifyingReference: qualifyingReference); _declarationFinder.AddFailedProcedureCoercionReference(reference); + return reference; } - private void Visit( + private IdentifierReference Visit( NewExpression expression, QualifiedModuleName module, Declaration scope, @@ -854,18 +894,19 @@ Declaration parent // We don't need to add a reference to the NewExpression's referenced declaration since that's covered // with its TypeExpression. Visit(expression.TypeExpression, module, scope, parent); + return null; } - private void Visit( + private IdentifierReference Visit( ParenthesizedExpression expression, QualifiedModuleName module, Declaration scope, Declaration parent) { - Visit(expression.Expression, module, scope, parent); + return Visit(expression.Expression, module, scope, parent); } - private void Visit( + private IdentifierReference Visit( TypeOfIsExpression expression, QualifiedModuleName module, Declaration scope, @@ -873,9 +914,10 @@ Declaration parent { Visit(expression.Expression, module, scope, parent); Visit(expression.TypeExpression, module, scope, parent); + return null; } - private void Visit( + private IdentifierReference Visit( BinaryOpExpression expression, QualifiedModuleName module, Declaration scope, @@ -883,18 +925,20 @@ Declaration parent { Visit(expression.Left, module, scope, parent); Visit(expression.Right, module, scope, parent); + return null; } - private void Visit( + private IdentifierReference Visit( UnaryOpExpression expression, QualifiedModuleName module, Declaration scope, Declaration parent) { Visit(expression.Expr, module, scope, parent); + return null; } - private void Visit( + private IdentifierReference Visit( InstanceExpression expression, QualifiedModuleName module, Declaration scope, @@ -907,7 +951,7 @@ Declaration parent var identifier = expression.Context.GetText(); var selection = callSiteContext.GetSelection(); var callee = expression.ReferencedDeclaration; - expression.ReferencedDeclaration.AddReference( + return expression.ReferencedDeclaration.AddReference( module, scope, parent, diff --git a/RubberduckTests/Grammar/ResolverTests.cs b/RubberduckTests/Grammar/ResolverTests.cs index 77d9363f16..3f19ef592a 100644 --- a/RubberduckTests/Grammar/ResolverTests.cs +++ b/RubberduckTests/Grammar/ResolverTests.cs @@ -7374,5 +7374,89 @@ End Property Assert.AreEqual(4, undeclared.Count); } } + + [Category("Resolver")] + [Test] + public void SavesQualifyingReferenceForWithMemberAccess() + { + var classCode = @" +Public Function Bar As Variant +End Function +"; + + var moduleCode = @" +Private Sub Baz + Dim foo As Class1 + Dim fooBar As Variant + Set foo = New Class1 + With foo + fooBar = .Bar() + End With +End Sub +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Module1", moduleCode, ComponentType.StandardModule), + ("Class1", classCode, ComponentType.ClassModule)); + + using (var state = Resolve(vbe.Object)) + { + var finder = state.DeclarationFinder; + + var classModule = finder.UserDeclarations(DeclarationType.ClassModule).Single(); + var barDeclaration = finder.Members(classModule.QualifiedModuleName, DeclarationType.Function).Single(); + var barReference = barDeclaration.References.Single(); + + var module = finder.UserDeclarations(DeclarationType.ProceduralModule).Single(); + var fooDeclaration = finder.Members(module.QualifiedModuleName, DeclarationType.Variable).Single(declaration => declaration.IdentifierName.Equals("foo")); + var fooReference = fooDeclaration.References.Single(reference => !reference.IsAssignment); + + var barReferenceQualifyingReferenceSelectionSelection = barReference.QualifyingReference.Selection; + var fooReferenceSelection = fooReference.Selection; + + Assert.AreEqual(fooReferenceSelection, barReferenceQualifyingReferenceSelectionSelection); + } + } + + [Category("Resolver")] + [Test] + public void SavesQualifyingReferenceForMemberAccess() + { + var classCode = @" +Public Function Bar As Variant +End Function +"; + + var moduleCode = @" +Private Sub Baz + Dim foo As Class1 + Dim fooBar As Variant + Set foo = New Class1 + fooBar = foo.Bar() +End Sub +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Module1", moduleCode, ComponentType.StandardModule), + ("Class1", classCode, ComponentType.ClassModule)); + + using (var state = Resolve(vbe.Object)) + { + var finder = state.DeclarationFinder; + + var classModule = finder.UserDeclarations(DeclarationType.ClassModule).Single(); + var barDeclaration = finder.Members(classModule.QualifiedModuleName, DeclarationType.Function).Single(); + var barReference = barDeclaration.References.Single(); + + var module = finder.UserDeclarations(DeclarationType.ProceduralModule).Single(); + var fooDeclaration = finder.Members(module.QualifiedModuleName, DeclarationType.Variable).Single(declaration => declaration.IdentifierName.Equals("foo")); + var fooReference = fooDeclaration.References.Single(reference => !reference.IsAssignment); + + var barReferenceQualifyingReferenceSelectionSelection = barReference.QualifyingReference.Selection; + var fooReferenceSelection = fooReference.Selection; + + Assert.AreEqual(fooReferenceSelection, barReferenceQualifyingReferenceSelectionSelection); + } + } } }