From c8c4319d34b044a75d7ccb475f8f26a9caee6f0f Mon Sep 17 00:00:00 2001 From: Max Doerner Date: Fri, 16 Aug 2019 22:26:35 +0200 Subject: [PATCH 01/32] Set up saving unbound default member accesses --- .../Symbols/IDeclarationFinderFactory.cs | 11 +++- .../Symbols/IdentifierReference.cs | 7 ++- ...oncurrentlyConstructedDeclarationFinder.cs | 10 +++- ...ntlyConstructedDeclarationFinderFactory.cs | 10 +++- .../DeclarationCaching/DeclarationFinder.cs | 47 ++++++++++++++-- .../DeclarationFinderFactory.cs | 9 ++- Rubberduck.Parsing/VBA/ModuleState.cs | 14 +++++ .../ReferenceResolveRunnerBase.cs | 16 +++++- .../VBA/RubberduckParserState.cs | 56 ++++++++++++++++++- .../Inspections/InspectionResultTests.cs | 4 +- 10 files changed, 162 insertions(+), 22 deletions(-) diff --git a/Rubberduck.Parsing/Symbols/IDeclarationFinderFactory.cs b/Rubberduck.Parsing/Symbols/IDeclarationFinderFactory.cs index 156fc2e2e4..8a6786a615 100644 --- a/Rubberduck.Parsing/Symbols/IDeclarationFinderFactory.cs +++ b/Rubberduck.Parsing/Symbols/IDeclarationFinderFactory.cs @@ -1,14 +1,19 @@ -using System; -using Rubberduck.Parsing.Annotations; +using Rubberduck.Parsing.Annotations; using System.Collections.Generic; using Rubberduck.Parsing.VBA.DeclarationCaching; +using Rubberduck.VBEditor; using Rubberduck.VBEditor.SafeComWrappers.Abstract; namespace Rubberduck.Parsing.Symbols { public interface IDeclarationFinderFactory { - DeclarationFinder Create(IReadOnlyList declarations, IEnumerable annotations, IReadOnlyList unresolvedMemberDeclarations, IHostApplication hostApp); + DeclarationFinder Create( + IReadOnlyList declarations, + IEnumerable annotations, + IReadOnlyList unresolvedMemberDeclarations, + IReadOnlyDictionary> unboundDefaultMemberAccesses, + IHostApplication hostApp); void Release(DeclarationFinder declarationFinder); } } diff --git a/Rubberduck.Parsing/Symbols/IdentifierReference.cs b/Rubberduck.Parsing/Symbols/IdentifierReference.cs index 182ad90e59..5cbf70fdee 100644 --- a/Rubberduck.Parsing/Symbols/IdentifierReference.cs +++ b/Rubberduck.Parsing/Symbols/IdentifierReference.cs @@ -125,7 +125,8 @@ public bool Equals(IdentifierReference other) return other != null && other.QualifiedModuleName.Equals(QualifiedModuleName) && other.Selection.Equals(Selection) - && other.Declaration.Equals(Declaration); + && (other.Declaration != null && other.Declaration.Equals(Declaration) + || other.Declaration == null && Declaration == null); } public override bool Equals(object obj) @@ -135,7 +136,9 @@ public override bool Equals(object obj) public override int GetHashCode() { - return HashCode.Compute(QualifiedModuleName, Selection, Declaration); + return Declaration != null + ? HashCode.Compute(QualifiedModuleName, Selection, Declaration) + : HashCode.Compute(QualifiedModuleName, Selection); } } } diff --git a/Rubberduck.Parsing/VBA/DeclarationCaching/ConcurrentlyConstructedDeclarationFinder.cs b/Rubberduck.Parsing/VBA/DeclarationCaching/ConcurrentlyConstructedDeclarationFinder.cs index 0def16d22a..eb72f31ece 100644 --- a/Rubberduck.Parsing/VBA/DeclarationCaching/ConcurrentlyConstructedDeclarationFinder.cs +++ b/Rubberduck.Parsing/VBA/DeclarationCaching/ConcurrentlyConstructedDeclarationFinder.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Rubberduck.Parsing.Annotations; using Rubberduck.Parsing.Symbols; +using Rubberduck.VBEditor; using Rubberduck.VBEditor.SafeComWrappers.Abstract; namespace Rubberduck.Parsing.VBA.DeclarationCaching @@ -11,8 +12,13 @@ public class ConcurrentlyConstructedDeclarationFinder : DeclarationFinder { private const int _maxDegreeOfConstructionParallelism = -1; - public ConcurrentlyConstructedDeclarationFinder(IReadOnlyList declarations, IEnumerable annotations, IReadOnlyList unresolvedMemberDeclarations, IHostApplication hostApp = null) - :base(declarations, annotations, unresolvedMemberDeclarations, hostApp) + public ConcurrentlyConstructedDeclarationFinder( + IReadOnlyList declarations, + IEnumerable annotations, + IReadOnlyList unresolvedMemberDeclarations, + IReadOnlyDictionary> unboundDefaultMemberAccesses, + IHostApplication hostApp = null) + :base(declarations, annotations, unresolvedMemberDeclarations, unboundDefaultMemberAccesses, hostApp) {} protected override void ExecuteCollectionConstructionActions(List collectionConstructionActions) diff --git a/Rubberduck.Parsing/VBA/DeclarationCaching/ConcurrentlyConstructedDeclarationFinderFactory.cs b/Rubberduck.Parsing/VBA/DeclarationCaching/ConcurrentlyConstructedDeclarationFinderFactory.cs index e228df4906..8dc8231d6e 100644 --- a/Rubberduck.Parsing/VBA/DeclarationCaching/ConcurrentlyConstructedDeclarationFinderFactory.cs +++ b/Rubberduck.Parsing/VBA/DeclarationCaching/ConcurrentlyConstructedDeclarationFinderFactory.cs @@ -1,15 +1,21 @@ using System.Collections.Generic; using Rubberduck.Parsing.Annotations; using Rubberduck.Parsing.Symbols; +using Rubberduck.VBEditor; using Rubberduck.VBEditor.SafeComWrappers.Abstract; namespace Rubberduck.Parsing.VBA.DeclarationCaching { public class ConcurrentlyConstructedDeclarationFinderFactory : IDeclarationFinderFactory { - public DeclarationFinder Create(IReadOnlyList declarations, IEnumerable annotations, IReadOnlyList unresolvedMemberDeclarations, IHostApplication hostApp) + public DeclarationFinder Create( + IReadOnlyList declarations, + IEnumerable annotations, + IReadOnlyList unresolvedMemberDeclarations, + IReadOnlyDictionary> unboundDefaultMemberAccesses, + IHostApplication hostApp) { - return new ConcurrentlyConstructedDeclarationFinder(declarations, annotations, unresolvedMemberDeclarations, hostApp); + return new ConcurrentlyConstructedDeclarationFinder(declarations, annotations, unresolvedMemberDeclarations, unboundDefaultMemberAccesses, hostApp); } public void Release(DeclarationFinder declarationFinder) diff --git a/Rubberduck.Parsing/VBA/DeclarationCaching/DeclarationFinder.cs b/Rubberduck.Parsing/VBA/DeclarationCaching/DeclarationFinder.cs index acca6cba9f..294b9d7221 100644 --- a/Rubberduck.Parsing/VBA/DeclarationCaching/DeclarationFinder.cs +++ b/Rubberduck.Parsing/VBA/DeclarationCaching/DeclarationFinder.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; using Antlr4.Runtime; using NLog; using Rubberduck.Parsing.Annotations; @@ -25,6 +24,7 @@ public class DeclarationFinder private readonly IHostApplication _hostApp; private IDictionary> _declarationsByName; private IDictionary> _declarations; + private readonly ConcurrentDictionary> _newUnboundDefaultMemberAccesses; private readonly ConcurrentDictionary> _newUndeclared; private readonly ConcurrentBag _newUnresolved; private List _unresolved; @@ -39,13 +39,15 @@ public class DeclarationFinder private IReadOnlyDictionary> _referencesByProjectId; private IDictionary> _referencesByMember; + private readonly IReadOnlyDictionary> _unboundDefaultMemberAccesses; + private Lazy>> _builtInDeclarationsByType; private Lazy>> _handlersByWithEventsField; private Lazy>> _implementingMembers; private Lazy>> _membersByImplementsContext; private Lazy>> _interfaceMembers; - private Lazy>> _interfaceImplementions; + private Lazy>> _interfaceImplementations; private Lazy>> _implementationsByMember; private Lazy> _nonBaseAsType; @@ -63,13 +65,19 @@ private static QualifiedSelection GetGroupingKey(Declaration declaration) : declaration.QualifiedSelection; } - public DeclarationFinder(IReadOnlyList declarations, IEnumerable annotations, - IReadOnlyList unresolvedMemberDeclarations, IHostApplication hostApp = null) + public DeclarationFinder( + IReadOnlyList declarations, + IEnumerable annotations, + IReadOnlyList unresolvedMemberDeclarations, + IReadOnlyDictionary> unboundDefaultMemberAccesses, + IHostApplication hostApp = null) { _hostApp = hostApp; + _unboundDefaultMemberAccesses = unboundDefaultMemberAccesses; _newUndeclared = new ConcurrentDictionary>(new Dictionary>()); _newUnresolved = new ConcurrentBag(new List()); + _newUnboundDefaultMemberAccesses = new ConcurrentDictionary>(); var collectionConstructionActions = CollectionConstructionActions(declarations, annotations, unresolvedMemberDeclarations); ExecuteCollectionConstructionActions(collectionConstructionActions); @@ -177,7 +185,7 @@ private void InitializeLazyCollections() _implementingMembers = new Lazy>>(FindAllImplementingMembers, true); _interfaceMembers = new Lazy>>(FindAllIinterfaceMembersByModule, true); _membersByImplementsContext = new Lazy>>(FindAllImplementingMembersByImplementsContext, true); - _interfaceImplementions = new Lazy>>(FindAllImplementionsByInterface, true); + _interfaceImplementations = new Lazy>>(FindAllImplementionsByInterface, true); _implementationsByMember = new Lazy>>(FindAllImplementingMembersByMember, true); } @@ -343,6 +351,8 @@ public IEnumerable FindDeclarationsForSelection(QualifiedSelection //This does not need a lock because enumerators over a ConcurrentBag uses a snapshot. public IEnumerable FreshUnresolvedMemberDeclarations => _newUnresolved.ToList(); + public IEnumerable FreshUnboundDefaultMemberAccesses => _newUnboundDefaultMemberAccesses.AllValues(); + public IEnumerable UnresolvedMemberDeclarations => _unresolved; public IEnumerable Members(Declaration module) @@ -458,7 +468,7 @@ public IEnumerable FindAllUserInterfaces() /// All classes implementing the interface. public IEnumerable FindAllImplementationsOfInterface(ClassModuleDeclaration interfaceDeclaration) { - var lookup = _interfaceImplementions.Value; + var lookup = _interfaceImplementations.Value; return lookup.TryGetValue(interfaceDeclaration, out var implementations) ? implementations : Enumerable.Empty(); @@ -1016,6 +1026,12 @@ public void AddUnboundContext(Declaration parentDeclaration, VBAParser.LExpressi _newUnresolved.Add(declaration); } + public void AddUnboundDefaultMemberAccess(IdentifierReference defaultMemberAccess) + { + var accesses = _newUnboundDefaultMemberAccesses.GetOrAdd(defaultMemberAccess.QualifiedModuleName, new ConcurrentBag()); + accesses.Add(defaultMemberAccess); + } + public Declaration OnBracketedExpression(string expression, ParserRuleContext context) { var hostApp = FindProject(_hostApp == null ? "VBA" : _hostApp.ApplicationName); @@ -1460,5 +1476,24 @@ public IEnumerable AllIdentifierReferences() { return _referencesByModule.Values.SelectMany(list => list); } + + /// + /// Gets the unbound default member calls in a module. + /// + public IReadOnlyCollection UnboundDefaultMemberAccesses(QualifiedModuleName module) + { + return _unboundDefaultMemberAccesses.TryGetValue(module, out var defaultMemberAccesses) + ? defaultMemberAccesses + : new HashSet(); + } + + /// + /// Gets all unbound default member calls. + /// + public IEnumerable AllUnboundDefaultMemberAccesses() + { + return _unboundDefaultMemberAccesses.Values + .SelectMany(defaultMemberAccess => defaultMemberAccess); + } } } diff --git a/Rubberduck.Parsing/VBA/DeclarationCaching/DeclarationFinderFactory.cs b/Rubberduck.Parsing/VBA/DeclarationCaching/DeclarationFinderFactory.cs index 47312ebb14..d357144e5d 100644 --- a/Rubberduck.Parsing/VBA/DeclarationCaching/DeclarationFinderFactory.cs +++ b/Rubberduck.Parsing/VBA/DeclarationCaching/DeclarationFinderFactory.cs @@ -1,15 +1,20 @@ using System.Collections.Generic; using Rubberduck.Parsing.Annotations; using Rubberduck.Parsing.Symbols; +using Rubberduck.VBEditor; using Rubberduck.VBEditor.SafeComWrappers.Abstract; namespace Rubberduck.Parsing.VBA.DeclarationCaching { public class DeclarationFinderFactory : IDeclarationFinderFactory { - public DeclarationFinder Create(IReadOnlyList declarations, IEnumerable annotations, IReadOnlyList unresolvedMemberDeclarations, IHostApplication hostApp) + public DeclarationFinder Create(IReadOnlyList declarations, + IEnumerable annotations, + IReadOnlyList unresolvedMemberDeclarations, + IReadOnlyDictionary> unboundDefaultMemberAccesses, + IHostApplication hostApp) { - return new DeclarationFinder(declarations, annotations, unresolvedMemberDeclarations, hostApp); + return new DeclarationFinder(declarations, annotations, unresolvedMemberDeclarations, unboundDefaultMemberAccesses, hostApp); } public void Release(DeclarationFinder declarationFinder) diff --git a/Rubberduck.Parsing/VBA/ModuleState.cs b/Rubberduck.Parsing/VBA/ModuleState.cs index e16162a62f..2c94180a97 100644 --- a/Rubberduck.Parsing/VBA/ModuleState.cs +++ b/Rubberduck.Parsing/VBA/ModuleState.cs @@ -25,10 +25,12 @@ public class ModuleState public SyntaxErrorException ModuleException { get; private set; } public IDictionary<(string scopeIdentifier, DeclarationType scopeType), Attributes> ModuleAttributes { get; private set; } public IDictionary<(string scopeIdentifier, DeclarationType scopeType), ParserRuleContext> MembersAllowingAttributes { get; private set; } + public IReadOnlyCollection UnboundDefaultMemberAccesses => _unboundDefaultMemberAccesses; public bool IsNew { get; private set; } public bool IsMarkedAsModified { get; private set; } + private readonly HashSet _unboundDefaultMemberAccesses = new HashSet(); public ModuleState(ConcurrentDictionary declarations) { @@ -151,6 +153,18 @@ public ModuleState SetAttributesTokenStream(ITokenStream attributesTokenStream) return this; } + public ModuleState AddUnboundDefaultMemberAccess(IdentifierReference defaultMemberAccess) + { + if (defaultMemberAccess.IsDefaultMemberAccess + && defaultMemberAccess.Declaration == null + && !_unboundDefaultMemberAccesses.Contains(defaultMemberAccess)) + { + _unboundDefaultMemberAccesses.Add(defaultMemberAccess); + } + + return this; + } + public void MarkAsModified() { IsMarkedAsModified = true; diff --git a/Rubberduck.Parsing/VBA/ReferenceManagement/ReferenceResolveRunnerBase.cs b/Rubberduck.Parsing/VBA/ReferenceManagement/ReferenceResolveRunnerBase.cs index 7c74eb2c59..d7ad032bc5 100644 --- a/Rubberduck.Parsing/VBA/ReferenceManagement/ReferenceResolveRunnerBase.cs +++ b/Rubberduck.Parsing/VBA/ReferenceManagement/ReferenceResolveRunnerBase.cs @@ -114,6 +114,7 @@ public void ResolveReferences(IReadOnlyCollection toResolve AddNewUndeclaredVariablesToDeclarations(); AddNewUnresolvedMemberDeclarations(); + AddNewUnboundDefaultMemberAccesses(); _toResolve.Clear(); } @@ -291,10 +292,21 @@ private void AddNewUndeclaredVariablesToDeclarations() private void AddNewUnresolvedMemberDeclarations() { - var unresolved = _state.DeclarationFinder.FreshUnresolvedMemberDeclarations; + var unresolved = _state.DeclarationFinder.FreshUnresolvedMemberDeclarations + .GroupBy(declaration => declaration.QualifiedModuleName); foreach (var declaration in unresolved) { - _state.AddUnresolvedMemberDeclaration(declaration); + _state.AddUnresolvedMemberDeclarations(declaration.Key, declaration); + } + } + + private void AddNewUnboundDefaultMemberAccesses() + { + var unboundDefaultMembers = _state.DeclarationFinder.FreshUnboundDefaultMemberAccesses + .GroupBy(access => access.QualifiedModuleName); ; + foreach (var access in unboundDefaultMembers) + { + _state.AddUnboundDefaultMemberAccesses(access.Key, access); } } } diff --git a/Rubberduck.Parsing/VBA/RubberduckParserState.cs b/Rubberduck.Parsing/VBA/RubberduckParserState.cs index b018d8845e..4834991ba1 100644 --- a/Rubberduck.Parsing/VBA/RubberduckParserState.cs +++ b/Rubberduck.Parsing/VBA/RubberduckParserState.cs @@ -19,6 +19,7 @@ using Rubberduck.VBEditor.SafeComWrappers.Abstract; using Rubberduck.Parsing.VBA.DeclarationCaching; using Rubberduck.Parsing.VBA.Parsing.ParsingExceptions; +using Rubberduck.VBEditor.Extensions; // ReSharper disable LoopCanBeConvertedToQuery @@ -189,7 +190,7 @@ public RubberduckParserState(IVBE vbe, IProjectsRepository projectRepository, ID private void RefreshFinder(IHostApplication host) { var oldDecalarationFinder = DeclarationFinder; - DeclarationFinder = _declarationFinderFactory.Create(AllDeclarationsFromModuleStates, AllAnnotations, AllUnresolvedMemberDeclarationsFromModulestates, host); + DeclarationFinder = _declarationFinderFactory.Create(AllDeclarationsFromModuleStates, AllAnnotations, AllUnresolvedMemberDeclarationsFromModulestates, AllUnboundDefaultMemberAccessesFromModuleStates, host); _declarationFinderFactory.Release(oldDecalarationFinder); } @@ -746,6 +747,23 @@ private IReadOnlyList AllUnresolvedMemberDeclarationsF } } + /// + /// Gets a copy of the unbound default member accesses directly from the module states. (Used for refreshing the DeclarationFinder.) + /// + private IReadOnlyDictionary> AllUnboundDefaultMemberAccessesFromModuleStates + { + get + { + var defaultMemberAccesses = new Dictionary>(); + foreach (var (module, state) in _moduleStates) + { + defaultMemberAccesses.Add(module, state.UnboundDefaultMemberAccesses); + } + + return defaultMemberAccesses; + } + } + /// /// Gets a copy of the collected declarations, excluding the built-in ones. /// @@ -804,6 +822,42 @@ public void AddUnresolvedMemberDeclaration(UnboundMemberDeclaration declaration) } } + public void AddUnresolvedMemberDeclarations(QualifiedModuleName module, IEnumerable unboundDeclarations) + { + var declarations = _moduleStates.GetOrAdd(module, new ModuleState(new ConcurrentDictionary())).UnresolvedMemberDeclarations; + + foreach (var declaration in unboundDeclarations) + { + if (declarations.ContainsKey(declaration)) + { + while (!declarations.TryRemove(declaration, out var _)) + { + Logger.Warn("Could not remove existing unresolved member declaration for '{0}' ({1}). Retrying.", declaration.IdentifierName, declaration.DeclarationType); + } + } + while (!declarations.TryAdd(declaration, 0) && !declarations.ContainsKey(declaration)) + { + Logger.Warn("Could not add unresolved member declaration '{0}' ({1}). Retrying.", declaration.IdentifierName, declaration.DeclarationType); + } + } + } + + public void AddUnboundDefaultMemberAccess(IdentifierReference defaultMemberAccess) + { + var key = defaultMemberAccess.QualifiedModuleName; + var moduleState = _moduleStates.GetOrAdd(key, new ModuleState(new ConcurrentDictionary())); + moduleState.AddUnboundDefaultMemberAccess(defaultMemberAccess); + } + + public void AddUnboundDefaultMemberAccesses(QualifiedModuleName module, IEnumerable defaultMemberAccesses) + { + var moduleState = _moduleStates.GetOrAdd(module, new ModuleState(new ConcurrentDictionary())); + foreach(var defaultMemberAccess in defaultMemberAccesses) + { + moduleState.AddUnboundDefaultMemberAccess(defaultMemberAccess); + } + } + public void ClearStateCache(string projectId) { try diff --git a/RubberduckTests/Inspections/InspectionResultTests.cs b/RubberduckTests/Inspections/InspectionResultTests.cs index 8266bb81e2..44053b3501 100644 --- a/RubberduckTests/Inspections/InspectionResultTests.cs +++ b/RubberduckTests/Inspections/InspectionResultTests.cs @@ -141,7 +141,7 @@ public void IdentifierRefereneceInspectionResultsAreDeemedInvalidatedIfTheModule var declarationFinderProviderMock = new Mock(); var declaratioFinder = new DeclarationFinder(new List(), new List(), - new List()); + new List(), new Dictionary>()); declarationFinderProviderMock.SetupGet(m => m.DeclarationFinder).Returns(declaratioFinder); var inspectionResult = new IdentifierReferenceInspectionResult(inspectionMock.Object, string.Empty, declarationFinderProviderMock.Object, identifierReference); @@ -170,7 +170,7 @@ public void IdentifierReferenceInspectionResultsAreNotDeemedInvalidatedIfNeither var declarationFinderProviderMock = new Mock(); var declaratioFinder = new DeclarationFinder(new List(), new List(), - new List()); + new List(), new Dictionary>()); declarationFinderProviderMock.SetupGet(m => m.DeclarationFinder).Returns(declaratioFinder); var inspectionResult = new IdentifierReferenceInspectionResult(inspectionMock.Object, string.Empty, declarationFinderProviderMock.Object, identifierReference); From 18c963c5c9340d83df189a1937d1507a2a71e5b0 Mon Sep 17 00:00:00 2001 From: Max Doerner Date: Fri, 16 Aug 2019 23:07:01 +0200 Subject: [PATCH 02/32] Save indexed unbound default member accesses --- .../BoundExpressionVisitor.cs | 32 +++++++++++++++++++ RubberduckTests/Grammar/ResolverTests.cs | 30 +++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs b/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs index 82e137559d..ae23f8ba39 100644 --- a/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs +++ b/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs @@ -181,6 +181,10 @@ private void Visit( { AddDefaultMemberReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); } + else + { + AddUnboundDefaultMemberReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); + } } else if (expression.Classification != ExpressionClassification.Unbound && expression.IsArrayAccess @@ -264,6 +268,34 @@ private void AddDefaultMemberReference( isDefaultMemberAccess: true); } + private void AddUnboundDefaultMemberReference( + IndexExpression expression, + QualifiedModuleName module, + Declaration scope, + Declaration parent, + bool isAssignmentTarget, + bool isSetAssignment, + bool hasExplicitLetStatement) + { + var callSiteContext = expression.LExpression.Context; + var identifier = expression.LExpression.Context.GetText(); + var callee = expression.ReferencedDeclaration; + var reference = new IdentifierReference( + module, + scope, + parent, + identifier, + callSiteContext.GetSelection(), + callSiteContext, + callee, + isAssignmentTarget, + hasExplicitLetStatement, + FindIdentifierAnnotations(module, callSiteContext.GetSelection().StartLine), + isSetAssignment, + isDefaultMemberAccess: true); + _declarationFinder.AddUnboundDefaultMemberAccess(reference); + } + private void Visit( DictionaryAccessExpression expression, QualifiedModuleName module, diff --git a/RubberduckTests/Grammar/ResolverTests.cs b/RubberduckTests/Grammar/ResolverTests.cs index 2de938dd6f..54f34551ee 100644 --- a/RubberduckTests/Grammar/ResolverTests.cs +++ b/RubberduckTests/Grammar/ResolverTests.cs @@ -3538,6 +3538,36 @@ End Function } } + [Category("Grammar")] + [Category("Resolver")] + [Test] + public void IndexExpressionWithUnboundDefaultMemberAccessYieldsUnboundDefaultMemberAccess() + { + var moduleCode = @" +Private Function Foo() As String + Dim cls As Object + Foo = cls(0) +End Function +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(4, 11, 4, 14); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var defaultMemberAccess = state.DeclarationFinder.UnboundDefaultMemberAccesses(module).First(); + + var expectedReferencedSelection = new QualifiedSelection(module, selection); + var actualReferencedSelection = new QualifiedSelection(defaultMemberAccess.QualifiedModuleName, defaultMemberAccess.Selection); + + Assert.AreEqual(expectedReferencedSelection, actualReferencedSelection); + Assert.IsTrue(defaultMemberAccess.IsDefaultMemberAccess); + } + } + [Category("Grammar")] [Category("Resolver")] [Test] From 6389d70f63a1802350b4af1db0b144c25cd6e33b Mon Sep 17 00:00:00 2001 From: Max Doerner Date: Sat, 17 Aug 2019 02:09:47 +0200 Subject: [PATCH 03/32] Add DefaultMemberRecursionDepth to IdentifierReference Also adds IsIndexedDefaultMemberAccess and IsNonIndexedDefaultMemberAccess. Moreover, default member recursion depth is now counted starting with 1 to sort non-default member references before default member references when sorting by recursion depth. Furthermore, fixes an issue that recursive indexed default member accesses had been attached to the wrong context. --- .../DictionaryAccessDefaultBinding.cs | 11 +- .../Binding/Bindings/IndexDefaultBinding.cs | 13 +-- .../Expressions/DictionaryAccessExpression.cs | 5 +- .../Binding/Expressions/IndexExpression.cs | 5 +- Rubberduck.Parsing/Symbols/Declaration.cs | 8 +- .../Symbols/IdentifierReference.cs | 13 ++- .../TypeResolvers/SetTypeResolver.cs | 11 +- .../DeclarationCaching/DeclarationFinder.cs | 11 +- .../BoundExpressionVisitor.cs | 9 +- RubberduckTests/Grammar/ResolverTests.cs | 101 +++++++++++++++++- 10 files changed, 153 insertions(+), 34 deletions(-) diff --git a/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs b/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs index 507c3e13ad..9a11f6e1eb 100644 --- a/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs @@ -75,7 +75,7 @@ private static IBoundExpression Resolve(IBoundExpression lExpression, ArgumentLi is classified as an unbound member with a declared type of Variant, referencing with no member name. */ ResolveArgumentList(lDeclaration, argumentList); - return new DictionaryAccessExpression(null, ExpressionClassification.Unbound, expression, lExpression, argumentList); + return new DictionaryAccessExpression(null, ExpressionClassification.Unbound, expression, lExpression, argumentList, 1); } if (lDeclaration == null) @@ -102,7 +102,7 @@ private static IBoundExpression CreateFailedExpression(IBoundExpression lExpress return failedExpr; } - private static IBoundExpression ResolveViaDefaultMember(IBoundExpression lExpression, string asTypeName, Declaration asTypeDeclaration, ArgumentList argumentList, ParserRuleContext expression, int recursionDepth = 0) + private static IBoundExpression ResolveViaDefaultMember(IBoundExpression lExpression, string asTypeName, Declaration asTypeDeclaration, ArgumentList argumentList, ParserRuleContext expression, int recursionDepth = 1) { if (Tokens.Variant.Equals(asTypeName, StringComparison.InvariantCultureIgnoreCase) || Tokens.Object.Equals(asTypeName, StringComparison.InvariantCultureIgnoreCase)) @@ -113,7 +113,7 @@ The declared type of is Object or Variant. a declared type of Variant, referencing with no member name. */ ResolveArgumentList(null, argumentList); - return new DictionaryAccessExpression(null, ExpressionClassification.Unbound, expression, lExpression, argumentList); + return new DictionaryAccessExpression(null, ExpressionClassification.Unbound, expression, lExpression, argumentList, recursionDepth); } /* @@ -141,11 +141,11 @@ The declared type of is Object or Variant. declared type. */ ResolveArgumentList(defaultMember, argumentList); - return new DictionaryAccessExpression(defaultMember, defaultMemberClassification, expression, lExpression, argumentList); + return new DictionaryAccessExpression(defaultMember, defaultMemberClassification, expression, lExpression, argumentList, recursionDepth); } if (parameters.Count(param => !param.IsOptional) == 0 - && DEFAULT_MEMBER_RECURSION_LIMIT > recursionDepth) + && DEFAULT_MEMBER_RECURSION_LIMIT >= recursionDepth) { /* This default member cannot accept any parameters. In this case, the static analysis restarts @@ -153,6 +153,7 @@ declared type. same . */ + //In contrast to the IndexDefaultBinding we pass the original expression context since the default member accesses will be attached to the exclamation mark. return ResolveRecursiveDefaultMember(defaultMember, defaultMemberClassification, argumentList, expression, recursionDepth); } diff --git a/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs b/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs index cfeb56821f..4c311a61f7 100644 --- a/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs @@ -58,7 +58,7 @@ public IBoundExpression Resolve() return Resolve(_lExpression, _argumentList, _expression); } - private static IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth = 0) + private static IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth = 1) { if (lExpression.Classification == ExpressionClassification.ResolutionFailed) { @@ -169,7 +169,7 @@ private static bool IsVariablePropertyFunctionWithoutParameters(IBoundExpression } } - private static IBoundExpression ResolveLExpressionIsIndexExpression(IndexExpression indexExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth = 0) + private static IBoundExpression ResolveLExpressionIsIndexExpression(IndexExpression indexExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth) { /* is classified as an index expression and the argument list is not empty. @@ -230,7 +230,7 @@ private static IBoundExpression ResolveDefaultMember(IBoundExpression lExpressio && !argumentList.HasNamedArguments) { ResolveArgumentList(null, argumentList); - return new IndexExpression(null, ExpressionClassification.Unbound, expression, lExpression, argumentList, isDefaultMemberAccess: true); + return new IndexExpression(null, ExpressionClassification.Unbound, expression, lExpression, argumentList, isDefaultMemberAccess: true, defaultMemberRecursionDepth: defaultMemberResolutionRecursionDepth); } /* The declared type of is a specific class, which has a public default Property @@ -254,7 +254,7 @@ declared type. if (ArgumentListIsCompatible(parameters, argumentList)) { ResolveArgumentList(defaultMember, argumentList); - return new IndexExpression(defaultMember, defaultMemberClassification, expression, lExpression, argumentList, isDefaultMemberAccess: true); + return new IndexExpression(defaultMember, defaultMemberClassification, expression, lExpression, argumentList, isDefaultMemberAccess: true, defaultMemberRecursionDepth: defaultMemberResolutionRecursionDepth); } /** @@ -263,9 +263,10 @@ declared type. same . */ if (parameters.Count(parameter => !parameter.IsOptional) == 0 - && DEFAULT_MEMBER_RECURSION_LIMIT > defaultMemberResolutionRecursionDepth) + && DEFAULT_MEMBER_RECURSION_LIMIT >= defaultMemberResolutionRecursionDepth) { - return ResolveRecursiveDefaultMember(defaultMember, defaultMemberClassification, argumentList, expression, defaultMemberResolutionRecursionDepth); + //We pass lExpression.Context as context because this is the part of the expression the final reference is supposed to be attached to. + return ResolveRecursiveDefaultMember(defaultMember, defaultMemberClassification, argumentList, lExpression.Context, defaultMemberResolutionRecursionDepth); } } diff --git a/Rubberduck.Parsing/Binding/Expressions/DictionaryAccessExpression.cs b/Rubberduck.Parsing/Binding/Expressions/DictionaryAccessExpression.cs index b8669e67f7..c3ec735256 100644 --- a/Rubberduck.Parsing/Binding/Expressions/DictionaryAccessExpression.cs +++ b/Rubberduck.Parsing/Binding/Expressions/DictionaryAccessExpression.cs @@ -11,15 +11,18 @@ public DictionaryAccessExpression( ExpressionClassification classification, ParserRuleContext context, IBoundExpression lExpression, - ArgumentList argumentList) + ArgumentList argumentList, + int defaultMemberResursionDepth) : base(referencedDeclaration, classification, context) { LExpression = lExpression; ArgumentList = argumentList; + DefaultMemberRecursionDepth = defaultMemberResursionDepth; } public IBoundExpression LExpression { get; } public ArgumentList ArgumentList { get; } + public int DefaultMemberRecursionDepth { get; } public ParserRuleContext DefaultMemberContext { diff --git a/Rubberduck.Parsing/Binding/Expressions/IndexExpression.cs b/Rubberduck.Parsing/Binding/Expressions/IndexExpression.cs index 7c54eef28a..647d4e9e3a 100644 --- a/Rubberduck.Parsing/Binding/Expressions/IndexExpression.cs +++ b/Rubberduck.Parsing/Binding/Expressions/IndexExpression.cs @@ -12,18 +12,21 @@ public IndexExpression( IBoundExpression lExpression, ArgumentList argumentList, bool isArrayAccess = false, - bool isDefaultMemberAccess = false) + bool isDefaultMemberAccess = false, + int defaultMemberRecursionDepth = 0) : base(referencedDeclaration, classification, context) { LExpression = lExpression; ArgumentList = argumentList; IsArrayAccess = isArrayAccess; IsDefaultMemberAccess = isDefaultMemberAccess; + DefaultMemberRecursionDepth = defaultMemberRecursionDepth; } public IBoundExpression LExpression { get; } public ArgumentList ArgumentList { get; } public bool IsArrayAccess { get; } public bool IsDefaultMemberAccess { get; } + public int DefaultMemberRecursionDepth { get; } } } diff --git a/Rubberduck.Parsing/Symbols/Declaration.cs b/Rubberduck.Parsing/Symbols/Declaration.cs index 84cd9d0751..43f73a78d6 100644 --- a/Rubberduck.Parsing/Symbols/Declaration.cs +++ b/Rubberduck.Parsing/Symbols/Declaration.cs @@ -363,7 +363,9 @@ public void AddReference( bool isAssignmentTarget = false, bool hasExplicitLetStatement = false, bool isSetAssigned = false, - bool isDefaultMemberAccess = false, + bool isIndexedDefaultMemberAccess = false, + bool isNonIndexedDefaultMemberAccess = false, + int defaultMemberRecursionDepth = 0, bool isArrayAccess = false ) { @@ -392,7 +394,9 @@ public void AddReference( hasExplicitLetStatement, annotations, isSetAssigned, - isDefaultMemberAccess, + isIndexedDefaultMemberAccess, + isNonIndexedDefaultMemberAccess, + defaultMemberRecursionDepth, isArrayAccess); _references.AddOrUpdate(newReference, 1, (key, value) => 1); } diff --git a/Rubberduck.Parsing/Symbols/IdentifierReference.cs b/Rubberduck.Parsing/Symbols/IdentifierReference.cs index 5cbf70fdee..2baf0ab8d2 100644 --- a/Rubberduck.Parsing/Symbols/IdentifierReference.cs +++ b/Rubberduck.Parsing/Symbols/IdentifierReference.cs @@ -24,7 +24,9 @@ public IdentifierReference( bool hasExplicitLetStatement = false, IEnumerable annotations = null, bool isSetAssigned = false, - bool isDefaultMemberAccess = false, + bool isIndexedDefaultMemberAccess = false, + bool isNonIndexedDefaultMemberAccess = false, + int defaultMemberRecursionDepth = 0, bool isArrayAccess = false) { ParentScoping = parentScopingDeclaration; @@ -37,7 +39,9 @@ public IdentifierReference( HasExplicitLetStatement = hasExplicitLetStatement; IsAssignment = isAssignmentTarget; IsSetAssignment = isSetAssigned; - IsDefaultMemberAccess = isDefaultMemberAccess; + IsIndexedDefaultMemberAccess = isIndexedDefaultMemberAccess; + IsNonIndexedDefaultMemberAccess = isNonIndexedDefaultMemberAccess; + DefaultMemberRecursionDepth = defaultMemberRecursionDepth; IsArrayAccess = isArrayAccess; Annotations = annotations ?? new List(); } @@ -64,7 +68,10 @@ public IdentifierReference( public bool IsSetAssignment { get; } - public bool IsDefaultMemberAccess { get; } + public bool IsIndexedDefaultMemberAccess { get; } + public bool IsNonIndexedDefaultMemberAccess { get; } + public bool IsDefaultMemberAccess => IsIndexedDefaultMemberAccess || IsNonIndexedDefaultMemberAccess; + public int DefaultMemberRecursionDepth { get; } public bool IsArrayAccess { get; } diff --git a/Rubberduck.Parsing/TypeResolvers/SetTypeResolver.cs b/Rubberduck.Parsing/TypeResolvers/SetTypeResolver.cs index d621873598..44f83616fa 100644 --- a/Rubberduck.Parsing/TypeResolvers/SetTypeResolver.cs +++ b/Rubberduck.Parsing/TypeResolvers/SetTypeResolver.cs @@ -201,10 +201,11 @@ private Declaration ResolveIndexExpressionAsMethod(VBAParser.LExpressionContext private Declaration ResolveIndexExpressionAsDefaultMemberAccess(VBAParser.LExpressionContext lExpressionOfIndexExpression, QualifiedModuleName containingModule, DeclarationFinder finder) { // A default member access references the entire lExpression. + // If there are multiple, the references are ordered by recursion depth. var qualifiedSelection = new QualifiedSelection(containingModule, lExpressionOfIndexExpression.GetSelection()); return finder .IdentifierReferences(qualifiedSelection) - .FirstOrDefault(reference => reference.IsDefaultMemberAccess) + .LastOrDefault(reference => reference.IsDefaultMemberAccess) ?.Declaration; } @@ -215,7 +216,7 @@ private Declaration ResolveIndexExpressionAsArrayAccess(VBAParser.LExpressionCon var qualifiedSelection = new QualifiedSelection(containingModule, actualIndexExpr.GetSelection()); return finder .IdentifierReferences(qualifiedSelection) - .FirstOrDefault(reference => reference.IsArrayAccess) + .LastOrDefault(reference => reference.IsArrayAccess) ?.Declaration; } @@ -223,7 +224,7 @@ private Declaration ResolveIndexExpressionAsArrayAccess(VBAParser.LExpressionCon { var declaration = finder.IdentifierReferences(identifier, containingModule) .Select(reference => reference.Declaration) - .FirstOrDefault(); + .LastOrDefault(); return (declaration, MightHaveSetType(declaration)); } @@ -231,7 +232,7 @@ private Declaration ResolveIndexExpressionAsArrayAccess(VBAParser.LExpressionCon { var declaration = finder.IdentifierReferences(identifier, containingModule) .Select(reference => reference.Declaration) - .FirstOrDefault(); + .LastOrDefault(); return (declaration, MightHaveSetType(declaration)); } @@ -240,7 +241,7 @@ private Declaration ResolveIndexExpressionAsArrayAccess(VBAParser.LExpressionCon var qualifiedSelection = new QualifiedSelection(containingModule, dictionaryAccess.GetSelection()); var declaration = finder.IdentifierReferences(qualifiedSelection) .Select(reference => reference.Declaration) - .FirstOrDefault(); + .LastOrDefault(); return (declaration, MightHaveSetType(declaration)); } diff --git a/Rubberduck.Parsing/VBA/DeclarationCaching/DeclarationFinder.cs b/Rubberduck.Parsing/VBA/DeclarationCaching/DeclarationFinder.cs index 294b9d7221..967f092848 100644 --- a/Rubberduck.Parsing/VBA/DeclarationCaching/DeclarationFinder.cs +++ b/Rubberduck.Parsing/VBA/DeclarationCaching/DeclarationFinder.cs @@ -1435,7 +1435,7 @@ public IEnumerable IdentifierReferences(VBAParser.Unrestric public IEnumerable IdentifierReferences(QualifiedSelection selection) { return _referencesBySelection.TryGetValue(selection, out var value) - ? value + ? value.OrderBy(reference => reference.DefaultMemberRecursionDepth) : Enumerable.Empty(); } @@ -1446,17 +1446,20 @@ public IEnumerable ContainedIdentifierReferences(QualifiedS { return IdentifierReferences(qualifiedSelection.QualifiedName) .Where(reference => qualifiedSelection.Selection.Contains(reference.Selection)) - .OrderBy(reference => reference.Selection); + .OrderBy(reference => reference.Selection) + .ThenBy(reference => reference.DefaultMemberRecursionDepth); } /// - /// Gets all identifier references containing a qualified selection, ordered by selection (start position, then length) + /// Gets all identifier references containing a qualified selection, ordered by selection (start position, then length). + /// Default member accesses with identical selections are ordered by call order. /// public IEnumerable ContainingIdentifierReferences(QualifiedSelection qualifiedSelection) { return IdentifierReferences(qualifiedSelection.QualifiedName) .Where(reference => reference.Selection.Contains(qualifiedSelection.Selection)) - .OrderBy(reference => reference.Selection); + .OrderBy(reference => reference.Selection) + .ThenBy(reference => reference.DefaultMemberRecursionDepth); } /// diff --git a/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs b/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs index ae23f8ba39..46dac201d1 100644 --- a/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs +++ b/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs @@ -265,7 +265,8 @@ private void AddDefaultMemberReference( isAssignmentTarget, hasExplicitLetStatement, isSetAssignment, - isDefaultMemberAccess: true); + isIndexedDefaultMemberAccess: true, + defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); } private void AddUnboundDefaultMemberReference( @@ -292,7 +293,8 @@ private void AddUnboundDefaultMemberReference( hasExplicitLetStatement, FindIdentifierAnnotations(module, callSiteContext.GetSelection().StartLine), isSetAssignment, - isDefaultMemberAccess: true); + isIndexedDefaultMemberAccess: true, + defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); _declarationFinder.AddUnboundDefaultMemberAccess(reference); } @@ -325,7 +327,8 @@ private void Visit( isAssignmentTarget, hasExplicitLetStatement, isSetAssignment, - isDefaultMemberAccess: true); + isIndexedDefaultMemberAccess: true, + defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); } // Argument List not affected by being unbound. foreach (var argument in expression.ArgumentList.Arguments) diff --git a/RubberduckTests/Grammar/ResolverTests.cs b/RubberduckTests/Grammar/ResolverTests.cs index 54f34551ee..ad22c74255 100644 --- a/RubberduckTests/Grammar/ResolverTests.cs +++ b/RubberduckTests/Grammar/ResolverTests.cs @@ -3400,14 +3400,15 @@ End Function { var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); var qualifiedSelection = new QualifiedSelection(module, selection); - var reference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).First(); + var reference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Last(); var referencedDeclaration = reference.Declaration; var expectedReferencedDeclarationName = "Class1.Foo"; var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); - Assert.IsTrue(reference.IsDefaultMemberAccess); + Assert.IsTrue(reference.IsIndexedDefaultMemberAccess); + Assert.AreEqual(2,reference.DefaultMemberRecursionDepth); } } @@ -3448,7 +3449,7 @@ End Function var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); - Assert.IsTrue(reference.IsDefaultMemberAccess); + Assert.IsTrue(reference.IsIndexedDefaultMemberAccess); } } @@ -3564,7 +3565,7 @@ End Function var actualReferencedSelection = new QualifiedSelection(defaultMemberAccess.QualifiedModuleName, defaultMemberAccess.Selection); Assert.AreEqual(expectedReferencedSelection, actualReferencedSelection); - Assert.IsTrue(defaultMemberAccess.IsDefaultMemberAccess); + Assert.IsTrue(defaultMemberAccess.IsIndexedDefaultMemberAccess); } } @@ -3636,5 +3637,97 @@ End Function Assert.AreEqual(DeclarationType.Parameter, referencedDeclaration.DeclarationType); } } + + [Category("Grammar")] + [Category("Resolver")] + [Test] + public void RecursiveIndexedDefaultMemberAccessHasReferenceToFinalDefaultMemberOnContextExcludingArguments_HasRecursionDepth() + { + var classCode = @" +Public Function Foo(bar As String) As Class1 +Attribute Foo.VB_UserMemId = 0 + Set Foo = New Class1 +End Function +"; + + var otherClassCode = @" +Public Function Baz() As Class1 +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class1 +End Function +"; + + var moduleCode = @" +Private Function Foo() As Class1 + Dim cls As new Class2 + Set Foo = cls(""newClassObject"") +End Function +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", classCode, ComponentType.ClassModule), + ("Class2", otherClassCode, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(4, 15, 4, 18); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var qualifiedSelection = new QualifiedSelection(module, selection); +var references = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).ToList(); + var reference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Last(); + var referencedDeclaration = reference.Declaration; + + var expectedReferencedDeclarationName = "Class1.Foo"; + var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; + + Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); + Assert.IsTrue(reference.IsIndexedDefaultMemberAccess); + Assert.AreEqual(2, reference.DefaultMemberRecursionDepth); + } + } + + [Category("Grammar")] + [Category("Resolver")] + [Test] + public void IndexedDefaultMemberAccessHasReferenceToDefaultMemberOnContextExcludingArguments() + { + var classCode = @" +Public Function Foo(bar As String) As Class1 +Attribute Foo.VB_UserMemId = 0 + Set Foo = New Class1 +End Function +"; + + var moduleCode = @" +Private Function Foo() As Class1 + Dim cls As new Class1 + Set Foo = cls(""newClassObject"") +End Function +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", classCode, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(4, 15, 4, 18); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var qualifiedSelection = new QualifiedSelection(module, selection); +var references = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).ToList(); + var reference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Last(); + var referencedDeclaration = reference.Declaration; + + var expectedReferencedDeclarationName = "Class1.Foo"; + var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; + + Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); + Assert.IsTrue(reference.IsIndexedDefaultMemberAccess); + Assert.AreEqual(1, reference.DefaultMemberRecursionDepth); + } + } } } From 463b7bfb901054f6d0a32130047d7d60b68e22de Mon Sep 17 00:00:00 2001 From: Max Doerner Date: Sat, 17 Aug 2019 03:33:50 +0200 Subject: [PATCH 04/32] Fix passing the wrong lExpression on recursive default member resolution in IndexDefaultBinding and DictionaryAccessDefaultBinding This fixes that no identifier references were generated for the original lExpression. --- .../DictionaryAccessDefaultBinding.cs | 15 +-- .../Binding/Bindings/IndexDefaultBinding.cs | 49 +++++----- RubberduckTests/Grammar/ResolverTests.cs | 97 ++++++++++++++++++- 3 files changed, 130 insertions(+), 31 deletions(-) diff --git a/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs b/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs index 9a11f6e1eb..af3749478c 100644 --- a/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs @@ -18,6 +18,10 @@ public sealed class DictionaryAccessDefaultBinding : IExpressionBinding //This is based on the spec at https://docs.microsoft.com/en-us/openspecs/microsoft_general_purpose_programming_languages/MS-VBAL/f20c9ebc-3365-4614-9788-1cd50a504574 + //We pass _lExpression to the expressions we create instead of passing it along the call chain because this simplifies the handling + //when resolving recursive default member calls. For these we use a fake bound simple name expression, which leads to the right resolution. + //However, using this on the returned expressions would lead to no identifier references being generated for the original lExpression. + public DictionaryAccessDefaultBinding( ParserRuleContext expression, IExpressionBinding lExpressionBinding, @@ -58,7 +62,7 @@ public IBoundExpression Resolve() return Resolve(_lExpression, _argumentList, _expression); } - private static IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression) + private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression) { if (lExpression.Classification == ExpressionClassification.ResolutionFailed) { @@ -152,9 +156,7 @@ declared type. recursively, as if this default member was specified instead for with the same . */ - - //In contrast to the IndexDefaultBinding we pass the original expression context since the default member accesses will be attached to the exclamation mark. - return ResolveRecursiveDefaultMember(defaultMember, defaultMemberClassification, argumentList, expression, recursionDepth); + return ResolveRecursiveDefaultMember(lExpression, defaultMember, defaultMemberClassification, argumentList, expression, recursionDepth); } ResolveArgumentList(null, argumentList); @@ -169,14 +171,13 @@ private static bool IsCompatibleWithOneStringArgument(List || Tokens.Variant.Equals(parameters[0].AsTypeName, StringComparison.InvariantCultureIgnoreCase)); } - private static IBoundExpression ResolveRecursiveDefaultMember(Declaration defaultMember, ExpressionClassification defaultMemberClassification, ArgumentList argumentList, ParserRuleContext expression, int recursionDepth) + private static IBoundExpression ResolveRecursiveDefaultMember(IBoundExpression lExpression, Declaration defaultMember, ExpressionClassification defaultMemberClassification, ArgumentList argumentList, ParserRuleContext expression, int recursionDepth) { - var defaultMemberAsLExpression = new SimpleNameExpression(defaultMember, defaultMemberClassification, expression); var defaultMemberAsTypeName = defaultMember.AsTypeName; var defaultMemberAsTypeDeclaration = defaultMember.AsTypeDeclaration; return ResolveViaDefaultMember( - defaultMemberAsLExpression, + lExpression, defaultMemberAsTypeName, defaultMemberAsTypeDeclaration, argumentList, diff --git a/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs b/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs index 4c311a61f7..2b6944a832 100644 --- a/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs @@ -18,6 +18,10 @@ public sealed class IndexDefaultBinding : IExpressionBinding //This is based on the spec at https://docs.microsoft.com/en-us/openspecs/microsoft_general_purpose_programming_languages/MS-VBAL/551030b2-72a4-4c95-9cb0-fb8f8c8774b4 + //We pass _lExpression to the expressions we create instead of passing it along the call chain because this simplifies the handling + //when resolving recursive default member calls. For these we use a fake bound simple name expression, which leads to the right resolution. + //However, using this on the returned expressions would lead to no identifier references being generated for the original lExpression. + public IndexDefaultBinding( ParserRuleContext expression, IExpressionBinding lExpressionBinding, @@ -58,7 +62,7 @@ public IBoundExpression Resolve() return Resolve(_lExpression, _argumentList, _expression); } - private static IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth = 1) + private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth = 1) { if (lExpression.Classification == ExpressionClassification.ResolutionFailed) { @@ -128,7 +132,7 @@ private static IBoundExpression CreateFailedExpression(IBoundExpression lExpress return failedExpr; } - private static IBoundExpression ResolveLExpressionIsVariablePropertyFunctionNoParameters(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth) + private IBoundExpression ResolveLExpressionIsVariablePropertyFunctionNoParameters(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth) { /* is classified as a variable, or is classified as a property or function @@ -152,10 +156,10 @@ with a parameter list that cannot accept any parameters and an t var asTypeName = indexedDeclaration.AsTypeName; var asTypeDeclaration = indexedDeclaration.AsTypeDeclaration; - return ResolveDefaultMember(lExpression, asTypeName, asTypeDeclaration, argumentList, expression, defaultMemberResolutionRecursionDepth); + return ResolveDefaultMember(asTypeName, asTypeDeclaration, argumentList, expression, defaultMemberResolutionRecursionDepth); } - private static bool IsVariablePropertyFunctionWithoutParameters(IBoundExpression lExpression) + private bool IsVariablePropertyFunctionWithoutParameters(IBoundExpression lExpression) { switch(lExpression.Classification) { @@ -169,7 +173,7 @@ private static bool IsVariablePropertyFunctionWithoutParameters(IBoundExpression } } - private static IBoundExpression ResolveLExpressionIsIndexExpression(IndexExpression indexExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth) + private IBoundExpression ResolveLExpressionIsIndexExpression(IndexExpression indexExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth) { /* is classified as an index expression and the argument list is not empty. @@ -193,10 +197,10 @@ private static IBoundExpression ResolveLExpressionIsIndexExpression(IndexExpress var asTypeName = indexedDeclaration.AsTypeName; var asTypeDeclaration = indexedDeclaration.AsTypeDeclaration; - return ResolveDefaultMember(indexExpression, asTypeName, asTypeDeclaration, argumentList, expression, defaultMemberResolutionRecursionDepth); + return ResolveDefaultMember(asTypeName, asTypeDeclaration, argumentList, expression, defaultMemberResolutionRecursionDepth); } - private static IBoundExpression ResolveLExpressionIsDictionaryAccessExpression(DictionaryAccessExpression dictionaryAccessExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth) + private IBoundExpression ResolveLExpressionIsDictionaryAccessExpression(DictionaryAccessExpression dictionaryAccessExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth) { //This is equivalent to the case in which the lExpression is an IndexExpression with the difference that it cannot be an array access. @@ -214,10 +218,10 @@ private static IBoundExpression ResolveLExpressionIsDictionaryAccessExpression(D var asTypeName = indexedDeclaration.AsTypeName; var asTypeDeclaration = indexedDeclaration.AsTypeDeclaration; - return ResolveDefaultMember(dictionaryAccessExpression, asTypeName, asTypeDeclaration, argumentList, expression, defaultMemberResolutionRecursionDepth); + return ResolveDefaultMember(asTypeName, asTypeDeclaration, argumentList, expression, defaultMemberResolutionRecursionDepth); } - private static IBoundExpression ResolveDefaultMember(IBoundExpression lExpression, string asTypeName, Declaration asTypeDeclaration, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth) + private IBoundExpression ResolveDefaultMember(string asTypeName, Declaration asTypeDeclaration, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth) { /* The declared type of is Object or Variant, and contains no @@ -230,7 +234,7 @@ private static IBoundExpression ResolveDefaultMember(IBoundExpression lExpressio && !argumentList.HasNamedArguments) { ResolveArgumentList(null, argumentList); - return new IndexExpression(null, ExpressionClassification.Unbound, expression, lExpression, argumentList, isDefaultMemberAccess: true, defaultMemberRecursionDepth: defaultMemberResolutionRecursionDepth); + return new IndexExpression(null, ExpressionClassification.Unbound, expression, _lExpression, argumentList, isDefaultMemberAccess: true, defaultMemberRecursionDepth: defaultMemberResolutionRecursionDepth); } /* The declared type of is a specific class, which has a public default Property @@ -254,7 +258,7 @@ declared type. if (ArgumentListIsCompatible(parameters, argumentList)) { ResolveArgumentList(defaultMember, argumentList); - return new IndexExpression(defaultMember, defaultMemberClassification, expression, lExpression, argumentList, isDefaultMemberAccess: true, defaultMemberRecursionDepth: defaultMemberResolutionRecursionDepth); + return new IndexExpression(defaultMember, defaultMemberClassification, expression, _lExpression, argumentList, isDefaultMemberAccess: true, defaultMemberRecursionDepth: defaultMemberResolutionRecursionDepth); } /** @@ -265,8 +269,7 @@ declared type. if (parameters.Count(parameter => !parameter.IsOptional) == 0 && DEFAULT_MEMBER_RECURSION_LIMIT >= defaultMemberResolutionRecursionDepth) { - //We pass lExpression.Context as context because this is the part of the expression the final reference is supposed to be attached to. - return ResolveRecursiveDefaultMember(defaultMember, defaultMemberClassification, argumentList, lExpression.Context, defaultMemberResolutionRecursionDepth); + return ResolveRecursiveDefaultMember(defaultMember, defaultMemberClassification, argumentList, expression, defaultMemberResolutionRecursionDepth); } } @@ -280,13 +283,13 @@ private static bool ArgumentListIsCompatible(ICollection p && parameters.Count(parameter => !parameter.IsOptional) <= argumentList.Arguments.Count; } - private static IBoundExpression ResolveRecursiveDefaultMember(Declaration defaultMember, ExpressionClassification defaultMemberClassification, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth) + private IBoundExpression ResolveRecursiveDefaultMember(Declaration defaultMember, ExpressionClassification defaultMemberClassification, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth) { var defaultMemberAsLExpression = new SimpleNameExpression(defaultMember, defaultMemberClassification, expression); return Resolve(defaultMemberAsLExpression, argumentList, expression, defaultMemberResolutionRecursionDepth + 1); } - private static ExpressionClassification DefaultMemberExpressionClassification(Declaration defaultMember) + private ExpressionClassification DefaultMemberExpressionClassification(Declaration defaultMember) { if (defaultMember.DeclarationType.HasFlag(DeclarationType.Property)) { @@ -301,7 +304,7 @@ private static ExpressionClassification DefaultMemberExpressionClassification(De return ExpressionClassification.Function; } - private static bool IsPropertyGetLetFunctionProcedure(Declaration declaration) + private bool IsPropertyGetLetFunctionProcedure(Declaration declaration) { var declarationType = declaration.DeclarationType; return declarationType == DeclarationType.PropertyGet @@ -318,7 +321,7 @@ private static bool IsPublic(Declaration declaration) || accessibility == Accessibility.Public; } - private static IBoundExpression ResolveLExpressionDeclaredTypeIsArray(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression) + private IBoundExpression ResolveLExpressionDeclaredTypeIsArray(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression) { var indexedDeclaration = lExpression.ReferencedDeclaration; if (indexedDeclaration == null @@ -340,7 +343,7 @@ takes on the classification and declared type of and references t array. */ ResolveArgumentList(indexedDeclaration, argumentList); - return new IndexExpression(indexedDeclaration, lExpression.Classification, expression, lExpression, argumentList); + return new IndexExpression(indexedDeclaration, lExpression.Classification, expression, _lExpression, argumentList); } if (!argumentList.HasNamedArguments) @@ -355,13 +358,13 @@ declared type of the array’s element type. */ ResolveArgumentList(indexedDeclaration.AsTypeDeclaration, argumentList); - return new IndexExpression(indexedDeclaration, ExpressionClassification.Variable, expression, lExpression, argumentList, isArrayAccess: true); + return new IndexExpression(indexedDeclaration, ExpressionClassification.Variable, expression, _lExpression, argumentList, isArrayAccess: true); } return null; } - private static IBoundExpression ResolveLExpressionIsPropertyFunctionSubroutine(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression) + private IBoundExpression ResolveLExpressionIsPropertyFunctionSubroutine(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression) { /* is classified as a property or function and its parameter list is compatible with @@ -375,17 +378,17 @@ and declared type. Note: We assume compatibility through enforcement by the VBE. */ ResolveArgumentList(lExpression.ReferencedDeclaration, argumentList); - return new IndexExpression(lExpression.ReferencedDeclaration, lExpression.Classification, expression, lExpression, argumentList); + return new IndexExpression(lExpression.ReferencedDeclaration, lExpression.Classification, expression, _lExpression, argumentList); } - private static IBoundExpression ResolveLExpressionIsUnbound(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression) + private IBoundExpression ResolveLExpressionIsUnbound(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression) { /* is classified as an unbound member. In this case, the index expression references , is classified as an unbound member and its declared type is Variant. */ ResolveArgumentList(lExpression.ReferencedDeclaration, argumentList); - return new IndexExpression(lExpression.ReferencedDeclaration, ExpressionClassification.Unbound, expression, lExpression, argumentList); + return new IndexExpression(lExpression.ReferencedDeclaration, ExpressionClassification.Unbound, expression, _lExpression, argumentList); } } } diff --git a/RubberduckTests/Grammar/ResolverTests.cs b/RubberduckTests/Grammar/ResolverTests.cs index ad22c74255..2e6eb69430 100644 --- a/RubberduckTests/Grammar/ResolverTests.cs +++ b/RubberduckTests/Grammar/ResolverTests.cs @@ -3675,7 +3675,6 @@ End Function { var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); var qualifiedSelection = new QualifiedSelection(module, selection); -var references = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).ToList(); var reference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Last(); var referencedDeclaration = reference.Declaration; @@ -3688,6 +3687,102 @@ End Function } } + [Category("Grammar")] + [Category("Resolver")] + [Test] + public void RecursiveIndexedDefaultMemberAccessLeavesReferencesOnPartsOflExpression() + { + var classCode = @" +Public Function Foo(bar As String) As Class2 +Attribute Foo.VB_UserMemId = 0 + Set Foo = New Class1 +End Function +"; + + var otherClassCode = @" +Public Function Baz() As Class1 +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class1 +End Function +"; + + var moduleCode = @" +Private Function Foo() As Class1 + Dim cls As new Class1 + Set Foo = cls.Foo(""Hello"")(""newClassObject"") +End Function +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", classCode, ComponentType.ClassModule), + ("Class2", otherClassCode, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(4, 15, 4, 18); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var qualifiedSelection = new QualifiedSelection(module, selection); + var reference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Last(); + var referencedDeclaration = reference.Declaration; + + var expectedReferencedDeclarationName = "Module1.cls"; + var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; + + Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); + Assert.IsFalse(reference.IsIndexedDefaultMemberAccess); + } + } + + [Category("Grammar")] + [Category("Resolver")] + [Test] + public void RecursiveDictionaryAccessExpressionDefaultMemberAccessLeavesReferencesOnPartsOflExpression() + { + var classCode = @" +Public Function Foo(bar As String) As Class2 +Attribute Foo.VB_UserMemId = 0 + Set Foo = New Class1 +End Function +"; + + var otherClassCode = @" +Public Function Baz() As Class1 +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class1 +End Function +"; + + var moduleCode = @" +Private Function Foo() As Class1 + Dim cls As new Class1 + Set Foo = cls.Foo(""Hello"")!newClassObject +End Function +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", classCode, ComponentType.ClassModule), + ("Class2", otherClassCode, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(4, 15, 4, 18); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var qualifiedSelection = new QualifiedSelection(module, selection); + var reference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Last(); + var referencedDeclaration = reference.Declaration; + + var expectedReferencedDeclarationName = "Module1.cls"; + var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; + + Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); + Assert.IsFalse(reference.IsIndexedDefaultMemberAccess); + } + } + [Category("Grammar")] [Category("Resolver")] [Test] From 2bd4e8ac8f14dca902f2503e8345268f64d38a4a Mon Sep 17 00:00:00 2001 From: Max Doerner Date: Sat, 17 Aug 2019 22:04:57 +0200 Subject: [PATCH 05/32] Fix that further accesses on recursive default member resolutions were not resolved correctly This reverts the resolution part for recursive default member accesses. Now they are contained in the DictionaryAccessExpression and IndexExpression instead of wrapping them. The previous method required too many adjustments of the further binding process; the new one requires no changes except in the bindings generating the RecursiveDefaultMemberAccessExpressions. --- .../DictionaryAccessDefaultBinding.cs | 36 ++- .../Binding/Bindings/IndexDefaultBinding.cs | 59 ++-- .../Expressions/DictionaryAccessExpression.cs | 23 +- .../Binding/Expressions/IndexExpression.cs | 11 +- .../RecursiveDefaultMemberAccessExpression.cs | 23 ++ .../BoundExpressionVisitor.cs | 77 ++++- RubberduckTests/Grammar/ResolverTests.cs | 304 +++++++++++++++++- 7 files changed, 462 insertions(+), 71 deletions(-) create mode 100644 Rubberduck.Parsing/Binding/Expressions/RecursiveDefaultMemberAccessExpression.cs diff --git a/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs b/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs index af3749478c..0248bbf4b4 100644 --- a/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs @@ -62,15 +62,17 @@ public IBoundExpression Resolve() return Resolve(_lExpression, _argumentList, _expression); } - private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression) + private static IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression) { - if (lExpression.Classification == ExpressionClassification.ResolutionFailed) + if (lExpression.Classification == ExpressionClassification.ResolutionFailed + || !(expression is VBAParser.LExpressionContext lExpressionContext)) { ResolveArgumentList(null, argumentList); return CreateFailedExpression(lExpression, argumentList); } var lDeclaration = lExpression.ReferencedDeclaration; + var defaultMemberContext = DefaultMemberReferenceContext(lExpressionContext); if (lExpression.Classification == ExpressionClassification.Unbound) { @@ -79,7 +81,7 @@ private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argu is classified as an unbound member with a declared type of Variant, referencing with no member name. */ ResolveArgumentList(lDeclaration, argumentList); - return new DictionaryAccessExpression(null, ExpressionClassification.Unbound, expression, lExpression, argumentList, 1); + return new DictionaryAccessExpression(null, ExpressionClassification.Unbound, expression, lExpression, argumentList, defaultMemberContext,1, null); } if (lDeclaration == null) @@ -91,7 +93,7 @@ private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argu var asTypeName = lDeclaration.AsTypeName; var asTypeDeclaration = lDeclaration.AsTypeDeclaration; - return ResolveViaDefaultMember(lExpression, asTypeName, asTypeDeclaration, argumentList, expression); + return ResolveViaDefaultMember(lExpression, asTypeName, asTypeDeclaration, argumentList, lExpressionContext, defaultMemberContext); } private static IBoundExpression CreateFailedExpression(IBoundExpression lExpression, ArgumentList argumentList) @@ -106,7 +108,7 @@ private static IBoundExpression CreateFailedExpression(IBoundExpression lExpress return failedExpr; } - private static IBoundExpression ResolveViaDefaultMember(IBoundExpression lExpression, string asTypeName, Declaration asTypeDeclaration, ArgumentList argumentList, ParserRuleContext expression, int recursionDepth = 1) + private static IBoundExpression ResolveViaDefaultMember(IBoundExpression lExpression, string asTypeName, Declaration asTypeDeclaration, ArgumentList argumentList, ParserRuleContext expression, ParserRuleContext defaultMemberContext, int recursionDepth = 1, RecursiveDefaultMemberAccessExpression containedExpression = null) { if (Tokens.Variant.Equals(asTypeName, StringComparison.InvariantCultureIgnoreCase) || Tokens.Object.Equals(asTypeName, StringComparison.InvariantCultureIgnoreCase)) @@ -117,7 +119,7 @@ The declared type of is Object or Variant. a declared type of Variant, referencing with no member name. */ ResolveArgumentList(null, argumentList); - return new DictionaryAccessExpression(null, ExpressionClassification.Unbound, expression, lExpression, argumentList, recursionDepth); + return new DictionaryAccessExpression(null, ExpressionClassification.Unbound, expression, lExpression, argumentList, defaultMemberContext, recursionDepth, containedExpression); } /* @@ -145,7 +147,7 @@ The declared type of is Object or Variant. declared type. */ ResolveArgumentList(defaultMember, argumentList); - return new DictionaryAccessExpression(defaultMember, defaultMemberClassification, expression, lExpression, argumentList, recursionDepth); + return new DictionaryAccessExpression(defaultMember, defaultMemberClassification, expression, lExpression, argumentList, defaultMemberContext, recursionDepth, containedExpression); } if (parameters.Count(param => !param.IsOptional) == 0 @@ -156,7 +158,7 @@ declared type. recursively, as if this default member was specified instead for with the same . */ - return ResolveRecursiveDefaultMember(lExpression, defaultMember, defaultMemberClassification, argumentList, expression, recursionDepth); + return ResolveRecursiveDefaultMember(lExpression, defaultMember, defaultMemberClassification, argumentList, expression, defaultMemberContext, recursionDepth, containedExpression); } ResolveArgumentList(null, argumentList); @@ -171,18 +173,22 @@ private static bool IsCompatibleWithOneStringArgument(List || Tokens.Variant.Equals(parameters[0].AsTypeName, StringComparison.InvariantCultureIgnoreCase)); } - private static IBoundExpression ResolveRecursiveDefaultMember(IBoundExpression lExpression, Declaration defaultMember, ExpressionClassification defaultMemberClassification, ArgumentList argumentList, ParserRuleContext expression, int recursionDepth) + private static IBoundExpression ResolveRecursiveDefaultMember(IBoundExpression lExpression, Declaration defaultMember, ExpressionClassification defaultMemberClassification, ArgumentList argumentList, ParserRuleContext expression, ParserRuleContext defaultMemberContext, int recursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) { var defaultMemberAsTypeName = defaultMember.AsTypeName; var defaultMemberAsTypeDeclaration = defaultMember.AsTypeDeclaration; + var defaultMemberExpression = new RecursiveDefaultMemberAccessExpression(defaultMember, defaultMemberClassification, defaultMemberContext, recursionDepth, containedExpression); + return ResolveViaDefaultMember( lExpression, defaultMemberAsTypeName, defaultMemberAsTypeDeclaration, argumentList, expression, - recursionDepth + 1); + defaultMemberContext, + recursionDepth + 1, + defaultMemberExpression); } private static bool IsPropertyGetLetFunctionProcedure(Declaration declaration) @@ -216,5 +222,15 @@ private static ExpressionClassification DefaultMemberClassification(Declaration return ExpressionClassification.Function; } + + private static ParserRuleContext DefaultMemberReferenceContext(VBAParser.LExpressionContext context) + { + if (context is VBAParser.DictionaryAccessExprContext dictionaryAccess) + { + return dictionaryAccess.dictionaryAccess(); + } + + return ((VBAParser.WithDictionaryAccessExprContext)context).dictionaryAccess(); + } } } \ No newline at end of file diff --git a/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs b/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs index 2b6944a832..b595309329 100644 --- a/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs @@ -62,7 +62,7 @@ public IBoundExpression Resolve() return Resolve(_lExpression, _argumentList, _expression); } - private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth = 1) + private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth = 1, RecursiveDefaultMemberAccessExpression containedExpression = null) { if (lExpression.Classification == ExpressionClassification.ResolutionFailed) { @@ -72,7 +72,7 @@ private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argu if (lExpression.Classification == ExpressionClassification.Unbound) { - return ResolveLExpressionIsUnbound(lExpression, argumentList, expression); + return ResolveLExpressionIsUnbound(lExpression, argumentList, expression, defaultMemberResolutionRecursionDepth, containedExpression); } if(lExpression.ReferencedDeclaration != null) @@ -82,7 +82,7 @@ private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argu switch (lExpression) { case IndexExpression indexExpression: - var doubleIndexExpression = ResolveLExpressionIsIndexExpression(indexExpression, argumentList, expression, defaultMemberResolutionRecursionDepth); + var doubleIndexExpression = ResolveLExpressionIsIndexExpression(indexExpression, argumentList, expression, defaultMemberResolutionRecursionDepth, containedExpression); if (doubleIndexExpression != null) { return doubleIndexExpression; @@ -90,7 +90,7 @@ private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argu break; case DictionaryAccessExpression dictionaryAccessExpression: - var indexOnBangExpression = ResolveLExpressionIsDictionaryAccessExpression(dictionaryAccessExpression, argumentList, expression, defaultMemberResolutionRecursionDepth); + var indexOnBangExpression = ResolveLExpressionIsDictionaryAccessExpression(dictionaryAccessExpression, argumentList, expression, defaultMemberResolutionRecursionDepth, containedExpression); if (indexOnBangExpression != null) { return indexOnBangExpression; @@ -102,7 +102,7 @@ private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argu if (IsVariablePropertyFunctionWithoutParameters(lExpression)) { - var parameterlessLExpressionAccess = ResolveLExpressionIsVariablePropertyFunctionNoParameters(lExpression, argumentList, expression, defaultMemberResolutionRecursionDepth); + var parameterlessLExpressionAccess = ResolveLExpressionIsVariablePropertyFunctionNoParameters(lExpression, argumentList, expression, defaultMemberResolutionRecursionDepth, containedExpression); if (parameterlessLExpressionAccess != null) { return parameterlessLExpressionAccess; @@ -114,7 +114,7 @@ private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argu || lExpression.Classification == ExpressionClassification.Function || lExpression.Classification == ExpressionClassification.Subroutine) { - return ResolveLExpressionIsPropertyFunctionSubroutine(lExpression, argumentList, expression); + return ResolveLExpressionIsPropertyFunctionSubroutine(lExpression, argumentList, expression, defaultMemberResolutionRecursionDepth, containedExpression); } ResolveArgumentList(null, argumentList); @@ -132,7 +132,7 @@ private static IBoundExpression CreateFailedExpression(IBoundExpression lExpress return failedExpr; } - private IBoundExpression ResolveLExpressionIsVariablePropertyFunctionNoParameters(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth) + private IBoundExpression ResolveLExpressionIsVariablePropertyFunctionNoParameters(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) { /* is classified as a variable, or is classified as a property or function @@ -150,13 +150,13 @@ with a parameter list that cannot accept any parameters and an t if (indexedDeclaration.IsArray) { - return ResolveLExpressionDeclaredTypeIsArray(lExpression, argumentList, expression); + return ResolveLExpressionDeclaredTypeIsArray(lExpression.ReferencedDeclaration, lExpression.Classification, argumentList, expression, defaultMemberResolutionRecursionDepth, containedExpression); } var asTypeName = indexedDeclaration.AsTypeName; var asTypeDeclaration = indexedDeclaration.AsTypeDeclaration; - return ResolveDefaultMember(asTypeName, asTypeDeclaration, argumentList, expression, defaultMemberResolutionRecursionDepth); + return ResolveDefaultMember(asTypeName, asTypeDeclaration, argumentList, expression, defaultMemberResolutionRecursionDepth, containedExpression); } private bool IsVariablePropertyFunctionWithoutParameters(IBoundExpression lExpression) @@ -173,7 +173,7 @@ private bool IsVariablePropertyFunctionWithoutParameters(IBoundExpression lExpre } } - private IBoundExpression ResolveLExpressionIsIndexExpression(IndexExpression indexExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth) + private IBoundExpression ResolveLExpressionIsIndexExpression(IndexExpression indexExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) { /* is classified as an index expression and the argument list is not empty. @@ -191,16 +191,16 @@ private IBoundExpression ResolveLExpressionIsIndexExpression(IndexExpression ind //via the default member path. if (indexedDeclaration.IsArray && !indexExpression.IsArrayAccess) { - return ResolveLExpressionDeclaredTypeIsArray(indexExpression, argumentList, expression); + return ResolveLExpressionDeclaredTypeIsArray(indexedDeclaration, indexExpression.Classification, argumentList, expression, defaultMemberResolutionRecursionDepth, containedExpression); } var asTypeName = indexedDeclaration.AsTypeName; var asTypeDeclaration = indexedDeclaration.AsTypeDeclaration; - return ResolveDefaultMember(asTypeName, asTypeDeclaration, argumentList, expression, defaultMemberResolutionRecursionDepth); + return ResolveDefaultMember(asTypeName, asTypeDeclaration, argumentList, expression, defaultMemberResolutionRecursionDepth, containedExpression); } - private IBoundExpression ResolveLExpressionIsDictionaryAccessExpression(DictionaryAccessExpression dictionaryAccessExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth) + private IBoundExpression ResolveLExpressionIsDictionaryAccessExpression(DictionaryAccessExpression dictionaryAccessExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) { //This is equivalent to the case in which the lExpression is an IndexExpression with the difference that it cannot be an array access. @@ -212,16 +212,16 @@ private IBoundExpression ResolveLExpressionIsDictionaryAccessExpression(Dictiona if (indexedDeclaration.IsArray) { - return ResolveLExpressionDeclaredTypeIsArray(dictionaryAccessExpression, argumentList, expression); + return ResolveLExpressionDeclaredTypeIsArray(indexedDeclaration, dictionaryAccessExpression.Classification, argumentList, expression, defaultMemberResolutionRecursionDepth, containedExpression); } var asTypeName = indexedDeclaration.AsTypeName; var asTypeDeclaration = indexedDeclaration.AsTypeDeclaration; - return ResolveDefaultMember(asTypeName, asTypeDeclaration, argumentList, expression, defaultMemberResolutionRecursionDepth); + return ResolveDefaultMember(asTypeName, asTypeDeclaration, argumentList, expression, defaultMemberResolutionRecursionDepth, containedExpression); } - private IBoundExpression ResolveDefaultMember(string asTypeName, Declaration asTypeDeclaration, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth) + private IBoundExpression ResolveDefaultMember(string asTypeName, Declaration asTypeDeclaration, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) { /* The declared type of is Object or Variant, and contains no @@ -234,7 +234,7 @@ private IBoundExpression ResolveDefaultMember(string asTypeName, Declaration asT && !argumentList.HasNamedArguments) { ResolveArgumentList(null, argumentList); - return new IndexExpression(null, ExpressionClassification.Unbound, expression, _lExpression, argumentList, isDefaultMemberAccess: true, defaultMemberRecursionDepth: defaultMemberResolutionRecursionDepth); + return new IndexExpression(null, ExpressionClassification.Unbound, expression, _lExpression, argumentList, isDefaultMemberAccess: true, defaultMemberRecursionDepth: defaultMemberResolutionRecursionDepth, containedDefaultMemberRecursionExpression: containedExpression); } /* The declared type of is a specific class, which has a public default Property @@ -258,7 +258,7 @@ declared type. if (ArgumentListIsCompatible(parameters, argumentList)) { ResolveArgumentList(defaultMember, argumentList); - return new IndexExpression(defaultMember, defaultMemberClassification, expression, _lExpression, argumentList, isDefaultMemberAccess: true, defaultMemberRecursionDepth: defaultMemberResolutionRecursionDepth); + return new IndexExpression(defaultMember, defaultMemberClassification, expression, _lExpression, argumentList, isDefaultMemberAccess: true, defaultMemberRecursionDepth: defaultMemberResolutionRecursionDepth, containedDefaultMemberRecursionExpression: containedExpression); } /** @@ -269,7 +269,7 @@ declared type. if (parameters.Count(parameter => !parameter.IsOptional) == 0 && DEFAULT_MEMBER_RECURSION_LIMIT >= defaultMemberResolutionRecursionDepth) { - return ResolveRecursiveDefaultMember(defaultMember, defaultMemberClassification, argumentList, expression, defaultMemberResolutionRecursionDepth); + return ResolveRecursiveDefaultMember(defaultMember, defaultMemberClassification, argumentList, expression, defaultMemberResolutionRecursionDepth, containedExpression); } } @@ -283,10 +283,12 @@ private static bool ArgumentListIsCompatible(ICollection p && parameters.Count(parameter => !parameter.IsOptional) <= argumentList.Arguments.Count; } - private IBoundExpression ResolveRecursiveDefaultMember(Declaration defaultMember, ExpressionClassification defaultMemberClassification, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth) + private IBoundExpression ResolveRecursiveDefaultMember(Declaration defaultMember, ExpressionClassification defaultMemberClassification, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) { + var defaultMemberRecursionExpression = new RecursiveDefaultMemberAccessExpression(defaultMember, defaultMemberClassification, _lExpression.Context, defaultMemberResolutionRecursionDepth, containedExpression); + var defaultMemberAsLExpression = new SimpleNameExpression(defaultMember, defaultMemberClassification, expression); - return Resolve(defaultMemberAsLExpression, argumentList, expression, defaultMemberResolutionRecursionDepth + 1); + return Resolve(defaultMemberAsLExpression, argumentList, expression, defaultMemberResolutionRecursionDepth + 1, defaultMemberRecursionExpression); } private ExpressionClassification DefaultMemberExpressionClassification(Declaration defaultMember) @@ -321,9 +323,8 @@ private static bool IsPublic(Declaration declaration) || accessibility == Accessibility.Public; } - private IBoundExpression ResolveLExpressionDeclaredTypeIsArray(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression) + private IBoundExpression ResolveLExpressionDeclaredTypeIsArray(Declaration indexedDeclaration, ExpressionClassification originalExpressionClassification, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberRecursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) { - var indexedDeclaration = lExpression.ReferencedDeclaration; if (indexedDeclaration == null || !indexedDeclaration.IsArray) { @@ -343,7 +344,7 @@ takes on the classification and declared type of and references t array. */ ResolveArgumentList(indexedDeclaration, argumentList); - return new IndexExpression(indexedDeclaration, lExpression.Classification, expression, _lExpression, argumentList); + return new IndexExpression(indexedDeclaration, originalExpressionClassification, expression, _lExpression, argumentList, defaultMemberRecursionDepth: defaultMemberRecursionDepth, containedDefaultMemberRecursionExpression: containedExpression); } if (!argumentList.HasNamedArguments) @@ -358,13 +359,13 @@ declared type of the array’s element type. */ ResolveArgumentList(indexedDeclaration.AsTypeDeclaration, argumentList); - return new IndexExpression(indexedDeclaration, ExpressionClassification.Variable, expression, _lExpression, argumentList, isArrayAccess: true); + return new IndexExpression(indexedDeclaration, ExpressionClassification.Variable, expression, _lExpression, argumentList, isArrayAccess: true, defaultMemberRecursionDepth: defaultMemberRecursionDepth, containedDefaultMemberRecursionExpression: containedExpression); } return null; } - private IBoundExpression ResolveLExpressionIsPropertyFunctionSubroutine(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression) + private IBoundExpression ResolveLExpressionIsPropertyFunctionSubroutine(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberRecursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) { /* is classified as a property or function and its parameter list is compatible with @@ -378,17 +379,17 @@ and declared type. Note: We assume compatibility through enforcement by the VBE. */ ResolveArgumentList(lExpression.ReferencedDeclaration, argumentList); - return new IndexExpression(lExpression.ReferencedDeclaration, lExpression.Classification, expression, _lExpression, argumentList); + return new IndexExpression(lExpression.ReferencedDeclaration, lExpression.Classification, expression, _lExpression, argumentList, defaultMemberRecursionDepth: defaultMemberRecursionDepth, containedDefaultMemberRecursionExpression: containedExpression); } - private IBoundExpression ResolveLExpressionIsUnbound(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression) + private IBoundExpression ResolveLExpressionIsUnbound(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) { /* is classified as an unbound member. In this case, the index expression references , is classified as an unbound member and its declared type is Variant. */ ResolveArgumentList(lExpression.ReferencedDeclaration, argumentList); - return new IndexExpression(lExpression.ReferencedDeclaration, ExpressionClassification.Unbound, expression, _lExpression, argumentList); + return new IndexExpression(lExpression.ReferencedDeclaration, ExpressionClassification.Unbound, expression, _lExpression, argumentList, defaultMemberRecursionDepth: defaultMemberResolutionRecursionDepth, containedDefaultMemberRecursionExpression: containedExpression); } } } diff --git a/Rubberduck.Parsing/Binding/Expressions/DictionaryAccessExpression.cs b/Rubberduck.Parsing/Binding/Expressions/DictionaryAccessExpression.cs index c3ec735256..d4ff903fb1 100644 --- a/Rubberduck.Parsing/Binding/Expressions/DictionaryAccessExpression.cs +++ b/Rubberduck.Parsing/Binding/Expressions/DictionaryAccessExpression.cs @@ -12,29 +12,22 @@ public DictionaryAccessExpression( ParserRuleContext context, IBoundExpression lExpression, ArgumentList argumentList, - int defaultMemberResursionDepth) + ParserRuleContext defaultMemberContext, + int defaultMemberRecursionDepth = 1, + RecursiveDefaultMemberAccessExpression containedDefaultMemberRecursionExpression = null) : base(referencedDeclaration, classification, context) { LExpression = lExpression; ArgumentList = argumentList; - DefaultMemberRecursionDepth = defaultMemberResursionDepth; + DefaultMemberRecursionDepth = defaultMemberRecursionDepth; + ContainedDefaultMemberRecursionExpression = containedDefaultMemberRecursionExpression; + DefaultMemberContext = defaultMemberContext; } public IBoundExpression LExpression { get; } public ArgumentList ArgumentList { get; } public int DefaultMemberRecursionDepth { get; } - - public ParserRuleContext DefaultMemberContext - { - get - { - if (Context is VBAParser.DictionaryAccessExprContext dictionaryAccess) - { - return dictionaryAccess.dictionaryAccess(); - } - - return ((VBAParser.WithDictionaryAccessExprContext) Context).dictionaryAccess(); - } - } + public ParserRuleContext DefaultMemberContext { get; } + public RecursiveDefaultMemberAccessExpression ContainedDefaultMemberRecursionExpression { get; } } } \ No newline at end of file diff --git a/Rubberduck.Parsing/Binding/Expressions/IndexExpression.cs b/Rubberduck.Parsing/Binding/Expressions/IndexExpression.cs index 647d4e9e3a..792d819544 100644 --- a/Rubberduck.Parsing/Binding/Expressions/IndexExpression.cs +++ b/Rubberduck.Parsing/Binding/Expressions/IndexExpression.cs @@ -5,15 +5,15 @@ namespace Rubberduck.Parsing.Binding { public sealed class IndexExpression : BoundExpression { - public IndexExpression( - Declaration referencedDeclaration, - ExpressionClassification classification, + public IndexExpression(Declaration referencedDeclaration, + ExpressionClassification classification, ParserRuleContext context, IBoundExpression lExpression, ArgumentList argumentList, bool isArrayAccess = false, bool isDefaultMemberAccess = false, - int defaultMemberRecursionDepth = 0) + int defaultMemberRecursionDepth = 0, + RecursiveDefaultMemberAccessExpression containedDefaultMemberRecursionExpression = null) : base(referencedDeclaration, classification, context) { LExpression = lExpression; @@ -21,6 +21,7 @@ public IndexExpression( IsArrayAccess = isArrayAccess; IsDefaultMemberAccess = isDefaultMemberAccess; DefaultMemberRecursionDepth = defaultMemberRecursionDepth; + ContainedDefaultMemberRecursionExpression = containedDefaultMemberRecursionExpression; } public IBoundExpression LExpression { get; } @@ -28,5 +29,7 @@ public IndexExpression( public bool IsArrayAccess { get; } public bool IsDefaultMemberAccess { get; } public int DefaultMemberRecursionDepth { get; } + + public RecursiveDefaultMemberAccessExpression ContainedDefaultMemberRecursionExpression { get; } } } diff --git a/Rubberduck.Parsing/Binding/Expressions/RecursiveDefaultMemberAccessExpression.cs b/Rubberduck.Parsing/Binding/Expressions/RecursiveDefaultMemberAccessExpression.cs new file mode 100644 index 0000000000..918540c09c --- /dev/null +++ b/Rubberduck.Parsing/Binding/Expressions/RecursiveDefaultMemberAccessExpression.cs @@ -0,0 +1,23 @@ +using Antlr4.Runtime; +using Rubberduck.Parsing.Symbols; + +namespace Rubberduck.Parsing.Binding +{ + public class RecursiveDefaultMemberAccessExpression : BoundExpression + { + public RecursiveDefaultMemberAccessExpression( + Declaration referencedDeclaration, + ExpressionClassification classification, + ParserRuleContext context, + int defaultMemberRecursionDepth = 0, + RecursiveDefaultMemberAccessExpression containedDefaultMemberRecursionExpression = null) + : base(referencedDeclaration, classification, context) + { + DefaultMemberRecursionDepth = defaultMemberRecursionDepth; + ContainedDefaultMemberRecursionExpression = containedDefaultMemberRecursionExpression; + } + + public int DefaultMemberRecursionDepth { get; } + public RecursiveDefaultMemberAccessExpression ContainedDefaultMemberRecursionExpression { get; } + } +} \ No newline at end of file diff --git a/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs b/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs index 46dac201d1..6499b25b03 100644 --- a/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs +++ b/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs @@ -79,6 +79,9 @@ private void Visit( break; case BuiltInTypeExpression builtInTypeExpression: break; + case RecursiveDefaultMemberAccessExpression recursiveDefaultMemberAccessExpression: + Visit(recursiveDefaultMemberAccessExpression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); + break; default: throw new NotSupportedException($"Unexpected bound expression type {boundExpression.GetType()}"); } @@ -172,9 +175,15 @@ private void Visit( bool hasExplicitLetStatement, bool isSetAssignment) { + var containedExpression = expression.ContainedDefaultMemberRecursionExpression; + if (containedExpression != null) + { + Visit(containedExpression, module, scope, parent, hasExplicitLetStatement: hasExplicitLetStatement); + } + if (expression.IsDefaultMemberAccess) { - Visit(expression.LExpression, module, scope, parent); + Visit(expression.LExpression, module, scope, parent, hasExplicitLetStatement: hasExplicitLetStatement); if (expression.Classification != ExpressionClassification.Unbound && expression.ReferencedDeclaration != null) @@ -190,7 +199,7 @@ private void Visit( && expression.IsArrayAccess && expression.ReferencedDeclaration != null) { - Visit(expression.LExpression, module, scope, parent); + Visit(expression.LExpression, module, scope, parent, hasExplicitLetStatement: hasExplicitLetStatement); AddArrayAccessReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); } else @@ -224,7 +233,8 @@ private void AddArrayAccessReference( bool isSetAssignment) { var callSiteContext = expression.Context; - var identifier = expression.Context.GetText(); + var identifier = callSiteContext.GetText(); + var selection = callSiteContext.GetSelection(); var callee = expression.ReferencedDeclaration; expression.ReferencedDeclaration.AddReference( module, @@ -233,8 +243,8 @@ private void AddArrayAccessReference( callSiteContext, identifier, callee, - callSiteContext.GetSelection(), - FindIdentifierAnnotations(module, callSiteContext.GetSelection().StartLine), + selection, + FindIdentifierAnnotations(module, selection.StartLine), isAssignmentTarget, hasExplicitLetStatement, isSetAssignment, @@ -251,7 +261,8 @@ private void AddDefaultMemberReference( bool hasExplicitLetStatement) { var callSiteContext = expression.LExpression.Context; - var identifier = expression.LExpression.Context.GetText(); + var identifier = callSiteContext.GetText(); + var selection = callSiteContext.GetSelection(); var callee = expression.ReferencedDeclaration; expression.ReferencedDeclaration.AddReference( module, @@ -260,8 +271,8 @@ private void AddDefaultMemberReference( callSiteContext, identifier, callee, - callSiteContext.GetSelection(), - FindIdentifierAnnotations(module, callSiteContext.GetSelection().StartLine), + selection, + FindIdentifierAnnotations(module, selection.StartLine), isAssignmentTarget, hasExplicitLetStatement, isSetAssignment, @@ -279,19 +290,20 @@ private void AddUnboundDefaultMemberReference( bool hasExplicitLetStatement) { var callSiteContext = expression.LExpression.Context; - var identifier = expression.LExpression.Context.GetText(); + var identifier = callSiteContext.GetText(); + var selection = callSiteContext.GetSelection(); var callee = expression.ReferencedDeclaration; var reference = new IdentifierReference( module, scope, parent, identifier, - callSiteContext.GetSelection(), + selection, callSiteContext, callee, isAssignmentTarget, hasExplicitLetStatement, - FindIdentifierAnnotations(module, callSiteContext.GetSelection().StartLine), + FindIdentifierAnnotations(module, selection.StartLine), isSetAssignment, isIndexedDefaultMemberAccess: true, defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); @@ -307,7 +319,13 @@ private void Visit( bool hasExplicitLetStatement, bool isSetAssignment) { - Visit(expression.LExpression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); + Visit(expression.LExpression, module, scope, parent, hasExplicitLetStatement: hasExplicitLetStatement); + + var containedExpression = expression.ContainedDefaultMemberRecursionExpression; + if (containedExpression != null) + { + Visit(containedExpression, module, scope, parent, hasExplicitLetStatement); + } if (expression.Classification != ExpressionClassification.Unbound && expression.ReferencedDeclaration != null) @@ -341,6 +359,41 @@ private void Visit( } } + private void Visit( + RecursiveDefaultMemberAccessExpression expression, + QualifiedModuleName module, + Declaration scope, + Declaration parent, + bool hasExplicitLetStatement) + { + var containedExpression = expression.ContainedDefaultMemberRecursionExpression; + if (containedExpression != null) + { + Visit(containedExpression, module, scope, parent, hasExplicitLetStatement: hasExplicitLetStatement); + } + + if (expression.Classification != ExpressionClassification.Unbound + && expression.ReferencedDeclaration != null) + { + var callSiteContext = expression.Context; + var identifier = callSiteContext.GetText(); + var selection = callSiteContext.GetSelection(); + var callee = expression.ReferencedDeclaration; + expression.ReferencedDeclaration.AddReference( + module, + scope, + parent, + callSiteContext, + identifier, + callee, + selection, + FindIdentifierAnnotations(module, selection.StartLine), + hasExplicitLetStatement: hasExplicitLetStatement, + isNonIndexedDefaultMemberAccess: true, + defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); + } + } + private void Visit( NewExpression expression, QualifiedModuleName module, diff --git a/RubberduckTests/Grammar/ResolverTests.cs b/RubberduckTests/Grammar/ResolverTests.cs index 2e6eb69430..e9a622043d 100644 --- a/RubberduckTests/Grammar/ResolverTests.cs +++ b/RubberduckTests/Grammar/ResolverTests.cs @@ -3285,6 +3285,61 @@ End Function } } + [Category("Grammar")] + [Category("Resolver")] + [Test] + public void RecursiveDictionaryAccessExpressionWithIndexedDefaultMemberAccessHasReferenceToDefaultMemberOnEntireContextExcludingFinalArguments() + { + var class1Code = @" +Public Function Foo(bar As String) As Class1 +Attribute Foo.VB_UserMemId = 0 + Set Foo = New Class1 +End Function +"; + + var class2Code = @" +Public Function Baz() As Class3 +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class3 +End Function +"; + + var class3Code = @" +Public Function Foo(bar As String) As Class1 +Attribute Foo.VB_UserMemId = 0 +End Function +"; + + var moduleCode = @" +Private Function Foo() As Class1 + Dim cls As new Class2 + Set Foo = cls!newClassObject(""whatever"") +End Function +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", class1Code, ComponentType.ClassModule), + ("Class2", class2Code, ComponentType.ClassModule), + ("Class3", class3Code, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(4, 15, 4, 33); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var qualifiedSelection = new QualifiedSelection(module, selection); + var reference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Last(); + var referencedDeclaration = reference.Declaration; + + var expectedReferencedDeclarationName = "Class1.Foo"; + var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; + + Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); + Assert.IsTrue(reference.IsIndexedDefaultMemberAccess); + } + } + [Category("Grammar")] [Category("Resolver")] [Test] @@ -3299,7 +3354,7 @@ End Function var moduleCode = @" Private Function Foo() As Class1 Dim cls As new Class1 - Set Foo = cls!newClassObject(""whatever"") + Set Foo = cls!newClassObject(0) End Function "; @@ -3363,6 +3418,52 @@ End Function } } + [Category("Grammar")] + [Category("Resolver")] + [Test] + public void RecursiveDictionaryAccessExpressionWithArrayAccessHasReferenceToDefaultMemberOnEntireContext() + { + var class1Code = @" +Public Function Foo(bar As String) As Class1() +Attribute Foo.VB_UserMemId = 0 +End Function +"; + + var class2Code = @" +Public Function Foo() As Class1 +Attribute Foo.VB_UserMemId = 0 +End Function +"; + + var moduleCode = @" +Private Function Foo() As Class1 + Dim cls As new Class2 + Set Foo = cls!newClassObject(0) +End Function +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", class1Code, ComponentType.ClassModule), + ("Class2", class2Code, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(4, 15, 4, 36); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var qualifiedSelection = new QualifiedSelection(module, selection); + var reference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Last(); + var referencedDeclaration = reference.Declaration; + + var expectedReferencedDeclarationName = "Class1.Foo"; + var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; + + Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); + Assert.IsTrue(reference.IsArrayAccess); + } + } + [Category("Grammar")] [Category("Resolver")] [Test] @@ -3412,6 +3513,54 @@ End Function } } + [Category("Grammar")] + [Category("Resolver")] + [Test] + public void RecursiveDictionaryAccessExpressionHasReferenceToIntermediateDefaultMemberAtExclamationMarkWIthLowerRecursionDepth() + { + var classCode = @" +Public Function Foo(bar As String) As Class1 +Attribute Foo.VB_UserMemId = 0 + Set Foo = New Class1 +End Function +"; + + var otherClassCode = @" +Public Function Baz() As Class1 +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class1 +End Function +"; + + var moduleCode = @" +Private Function Foo() As Class1 + Dim cls As new Class2 + Set Foo = cls!newClassObject +End Function +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", classCode, ComponentType.ClassModule), + ("Class2", otherClassCode, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(4, 18, 4, 19); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var qualifiedSelection = new QualifiedSelection(module, selection); + var defaultMemberReference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Single(reference => reference.DefaultMemberRecursionDepth == 1); + var referencedDeclaration = defaultMemberReference.Declaration; + + var expectedReferencedDeclarationName = "Class2.Baz"; + var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; + + Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); + Assert.IsTrue(defaultMemberReference.IsNonIndexedDefaultMemberAccess); + } + } + [Category("Grammar")] [Category("Resolver")] [Test] @@ -3687,6 +3836,159 @@ End Function } } + [Category("Grammar")] + [Category("Resolver")] + [Test] + public void DefaultMemberIndexExpressionOnRecursiveIndexedDefaultMemberAccessHasReferenceToFinalDefaultMemberOnContextExcludingArguments() + { + var class1Code = @" +Public Function Foo(bar As String) As Class3 +Attribute Foo.VB_UserMemId = 0 + Set Foo = New Class3 +End Function +"; + + var class2Code = @" +Public Function Baz() As Class1 +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class1 +End Function +"; + + var class3Code = @" +Public Function Foo(bar As String) As Class1 +Attribute Foo.VB_UserMemId = 0 + Set Foo = New Class1 +End Function +"; + + var moduleCode = @" +Private Function Foo() As Class1 + Dim cls As new Class2 + Set Foo = cls(""newClassObject"")(""Hello"") +End Function +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", class1Code, ComponentType.ClassModule), + ("Class2", class2Code, ComponentType.ClassModule), + ("Class3", class3Code, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(4, 15, 4, 36); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var qualifiedSelection = new QualifiedSelection(module, selection); + var reference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Last(); + var referencedDeclaration = reference.Declaration; + + var expectedReferencedDeclarationName = "Class3.Foo"; + var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; + + Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); + Assert.IsTrue(reference.IsIndexedDefaultMemberAccess); + Assert.AreEqual(1, reference.DefaultMemberRecursionDepth); + } + } + + [Category("Grammar")] + [Category("Resolver")] + [Test] + public void ArrayAccessOnRecursiveIndexedDefaultMemberAccessHasReferenceToFinalDefaultMemberOnEntireContext() + { + var class1Code = @" +Public Function Foo(bar As String) As Class1() +Attribute Foo.VB_UserMemId = 0 + Set Foo = New Class3 +End Function +"; + + var class2Code = @" +Public Function Baz() As Class1 +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class1 +End Function +"; + + var moduleCode = @" +Private Function Foo() As Class1 + Dim cls As new Class2 + Set Foo = cls(""newClassObject"")(0) +End Function +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", class1Code, ComponentType.ClassModule), + ("Class2", class2Code, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(4, 15, 4, 39); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var qualifiedSelection = new QualifiedSelection(module, selection); + var reference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Last(); + var referencedDeclaration = reference.Declaration; + + var expectedReferencedDeclarationName = "Class1.Foo"; + var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; + + Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); + Assert.IsTrue(reference.IsArrayAccess); + } + } + + [Category("Grammar")] + [Category("Resolver")] + [Test] + public void RecursiveIndexedDefaultMemberAccessHasReferenceToIntermediateDefaultMemberOnContextExcludingArgumentsWIthLowerRecursionDepth() + { + var classCode = @" +Public Function Foo(bar As String) As Class1 +Attribute Foo.VB_UserMemId = 0 + Set Foo = New Class1 +End Function +"; + + var otherClassCode = @" +Public Function Baz() As Class1 +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class1 +End Function +"; + + var moduleCode = @" +Private Function Foo() As Class1 + Dim cls As new Class2 + Set Foo = cls(""newClassObject"") +End Function +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", classCode, ComponentType.ClassModule), + ("Class2", otherClassCode, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(4, 15, 4, 18); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var qualifiedSelection = new QualifiedSelection(module, selection); + var defaultMemberReference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Single(reference => reference.DefaultMemberRecursionDepth == 1); + var referencedDeclaration = defaultMemberReference.Declaration; + + var expectedReferencedDeclarationName = "Class2.Baz"; + var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; + + Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); + Assert.IsTrue(defaultMemberReference.IsNonIndexedDefaultMemberAccess); + } + } + [Category("Grammar")] [Category("Resolver")] [Test] From ea7d8d67fc1f89d16cc30e082f6064da826ee241 Mon Sep 17 00:00:00 2001 From: Max Doerner Date: Sun, 18 Aug 2019 20:03:35 +0200 Subject: [PATCH 06/32] Add failing tests for implicit default member resolution This covers both procedure call resolution as well as let coercion resolution. Because of the large number of tests/cases to cover, this is its own commit and the changes required to make the tests pass will be implemented in the next commits. --- .../DictionaryAccessDefaultBinding.cs | 4 - RubberduckTests/Grammar/ResolverTests.cs | 752 +++++++++++++++++- 2 files changed, 751 insertions(+), 5 deletions(-) diff --git a/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs b/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs index 0248bbf4b4..be96e84c13 100644 --- a/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs @@ -18,10 +18,6 @@ public sealed class DictionaryAccessDefaultBinding : IExpressionBinding //This is based on the spec at https://docs.microsoft.com/en-us/openspecs/microsoft_general_purpose_programming_languages/MS-VBAL/f20c9ebc-3365-4614-9788-1cd50a504574 - //We pass _lExpression to the expressions we create instead of passing it along the call chain because this simplifies the handling - //when resolving recursive default member calls. For these we use a fake bound simple name expression, which leads to the right resolution. - //However, using this on the returned expressions would lead to no identifier references being generated for the original lExpression. - public DictionaryAccessDefaultBinding( ParserRuleContext expression, IExpressionBinding lExpressionBinding, diff --git a/RubberduckTests/Grammar/ResolverTests.cs b/RubberduckTests/Grammar/ResolverTests.cs index e9a622043d..aece46e8cb 100644 --- a/RubberduckTests/Grammar/ResolverTests.cs +++ b/RubberduckTests/Grammar/ResolverTests.cs @@ -3944,7 +3944,7 @@ End Function [Category("Grammar")] [Category("Resolver")] [Test] - public void RecursiveIndexedDefaultMemberAccessHasReferenceToIntermediateDefaultMemberOnContextExcludingArgumentsWIthLowerRecursionDepth() + public void RecursiveIndexedDefaultMemberAccessHasReferenceToIntermediateDefaultMemberOnContextExcludingArgumentsWithLowerRecursionDepth() { var classCode = @" Public Function Foo(bar As String) As Class1 @@ -4126,5 +4126,755 @@ End Function Assert.AreEqual(1, reference.DefaultMemberRecursionDepth); } } + + [Category("Grammar")] + [Category("Resolver")] + [Test] + public void RecursiveLetCoercionDefaultMemberAccessHasReferenceToFinalDefaultMemberOnEntireContext() + { + var class1Code = @" +Public Function Foo() As Long +Attribute Foo.VB_UserMemId = 0 +End Function +"; + + var class2Code = @" +Public Function Baz() As Class1 +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class1 +End Function +"; + + var class3Code = @" +Public Function Bar() As Class2 +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class2 +End Function +"; + + var moduleCode = @" +Private Function Foo() As Long + Dim cls As new Class3 + Foo = cls.Bar +End Function +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", class1Code, ComponentType.ClassModule), + ("Class2", class2Code, ComponentType.ClassModule), + ("Class3", class3Code, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(4, 11, 4, 18); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var qualifiedSelection = new QualifiedSelection(module, selection); + var defaultMemberReference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Last(); + var referencedDeclaration = defaultMemberReference.Declaration; + + var expectedReferencedDeclarationName = "Class1.Foo"; + var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; + + Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); + Assert.IsTrue(defaultMemberReference.IsNonIndexedDefaultMemberAccess); + Assert.AreEqual(2, defaultMemberReference.DefaultMemberRecursionDepth); + } + } + + [Category("Grammar")] + [Category("Resolver")] + [Test] + public void RecursiveLetCoercionDefaultMemberAccessHasReferenceToIntermediateDefaultMemberOnEntireContextWithLowerRecursionDepth() + { + var class1Code = @" +Public Function Foo() As Long +Attribute Foo.VB_UserMemId = 0 +End Function +"; + + var class2Code = @" +Public Function Baz() As Class1 +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class1 +End Function +"; + + var class3Code = @" +Public Function Bar() As Class2 +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class2 +End Function +"; + + var moduleCode = @" +Private Function Foo() As Long + Dim cls As new Class3 + Foo = cls.Bar +End Function +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", class1Code, ComponentType.ClassModule), + ("Class2", class2Code, ComponentType.ClassModule), + ("Class3", class3Code, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(4, 11, 4, 18); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var qualifiedSelection = new QualifiedSelection(module, selection); + var defaultMemberReference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Single(reference => reference.DefaultMemberRecursionDepth == 1); + var referencedDeclaration = defaultMemberReference.Declaration; + + var expectedReferencedDeclarationName = "Class2.Baz"; + var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; + + Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); + Assert.IsTrue(defaultMemberReference.IsNonIndexedDefaultMemberAccess); + } + } + + [Category("Grammar")] + [Category("Resolver")] + [Test] + public void RecursiveLetCoercionDefaultMemberAccessLeavesReferencesOnPartsOflExpression() + { + var class1Code = @" +Public Function Foo() As Long +Attribute Foo.VB_UserMemId = 0 +End Function +"; + + var class2Code = @" +Public Function Baz() As Class1 +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class1 +End Function +"; + + var class3Code = @" +Public Function Bar() As Class2 +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class2 +End Function +"; + + var moduleCode = @" +Private Function Foo() As Long + Dim cls As new Class3 + Foo = cls.Bar +End Function +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", class1Code, ComponentType.ClassModule), + ("Class2", class2Code, ComponentType.ClassModule), + ("Class3", class3Code, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(4, 11, 4, 14); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var qualifiedSelection = new QualifiedSelection(module, selection); + var reference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Last(); + var referencedDeclaration = reference.Declaration; + + var expectedReferencedDeclarationName = "Module1.cls"; + var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; + + Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); + Assert.IsFalse(reference.IsDefaultMemberAccess); + } + } + + [Test] + [Category("Grammar")] + [Category("Resolver")] + [TestCase(" Foo = cls.Baz", 11, 18)] + [TestCase(" Let Foo = cls.Baz", 15, 22)] + [TestCase(" Foo = cls.Baz + 42", 11, 18)] + [TestCase(" Foo = cls.Baz - 42", 11, 18)] + [TestCase(" Foo = cls.Baz * 42", 11, 18)] + [TestCase(" Foo = cls.Baz ^ 42", 11, 18)] + [TestCase(" Foo = cls.Baz \\ 42", 11, 18)] + [TestCase(" Foo = cls.Baz Mod 42", 11, 18)] + [TestCase(" Foo = cls.Baz & \" sheep\"", 11, 18)] + [TestCase(" Foo = cls.Baz And 42", 11, 18)] + [TestCase(" Foo = cls.Baz Or 42", 11, 18)] + [TestCase(" Foo = cls.Baz Xor 42", 11, 18)] + [TestCase(" Foo = cls.Baz Eqv 42", 11, 18)] + [TestCase(" Foo = cls.Baz Imp 42", 11, 18)] + [TestCase(" Foo = cls.Baz = 42", 11, 18)] + [TestCase(" Foo = cls.Baz < 42", 11, 18)] + [TestCase(" Foo = cls.Baz > 42", 11, 18)] + [TestCase(" Foo = cls.Baz <= 42", 11, 18)] + [TestCase(" Foo = cls.Baz =< 42", 11, 18)] + [TestCase(" Foo = cls.Baz >= 42", 11, 18)] + [TestCase(" Foo = cls.Baz => 42", 11, 18)] + [TestCase(" Foo = cls.Baz <> 42", 11, 18)] + [TestCase(" Foo = cls.Baz >< 42", 11, 18)] + [TestCase(" Foo = cls.Baz Like \"Hello\"", 11, 18)] + [TestCase(" Foo = 42 + cls.Baz", 16, 23)] + [TestCase(" Foo = 42 * cls.Baz", 16, 23)] + [TestCase(" Foo = 42 - cls.Baz", 16, 23)] + [TestCase(" Foo = 42 ^ cls.Baz", 16, 23)] + [TestCase(" Foo = 42 \\ cls.Baz", 17, 24)] + [TestCase(" Foo = 42 Mod cls.Baz", 18, 25)] + [TestCase(" Foo = \"sheep\" & cls.Baz", 21, 28)] + [TestCase(" Foo = 42 And cls.Baz", 18, 25)] + [TestCase(" Foo = 42 Or cls.Baz", 17, 24)] + [TestCase(" Foo = 42 Xor cls.Baz", 18, 25)] + [TestCase(" Foo = 42 Eqv cls.Baz", 18, 25)] + [TestCase(" Foo = 42 Imp cls.Baz", 18, 25)] + [TestCase(" Foo = 42 = cls.Baz", 17, 24)] + [TestCase(" Foo = 42 < cls.Baz", 17, 24)] + [TestCase(" Foo = 42 > cls.Baz", 17, 24)] + [TestCase(" Foo = 42 <= cls.Baz", 18, 25)] + [TestCase(" Foo = 42 =< cls.Baz", 18, 25)] + [TestCase(" Foo = 42 >= cls.Baz", 18, 25)] + [TestCase(" Foo = 42 => cls.Baz", 18, 25)] + [TestCase(" Foo = 42 <> cls.Baz", 18, 25)] + [TestCase(" Foo = 42 >< cls.Baz", 18, 25)] + [TestCase(" Foo = \"Hello\" Like cls.Baz", 24, 31)] + [TestCase(" Foo = -cls.Baz", 12, 19)] + [TestCase(" Foo = Not cls.Baz", 15, 22)] + [TestCase(" Bar cls.Baz", 9, 16)] + [TestCase(" Baz (cls.Baz)", 10, 17)] + [TestCase(" Debug.Print cls.Baz", 9, 16)] + [TestCase(" Debug.Print 42, cls.Baz", 13, 20)] + [TestCase(" Debug.Print 42; cls.Baz", 13, 20)] + [TestCase(" Debug.Print Spc(cls.Baz)", 13, 20)] + [TestCase(" Debug.Print 42, Spc(cls.Baz)", 17, 24)] + [TestCase(" Debug.Print 42; Spc(cls.Baz)", 17, 24)] + [TestCase(" Debug.Print Tab(cls.Baz)", 13, 20)] + [TestCase(" Debug.Print 42, Tab(cls.Baz)", 17, 24)] + [TestCase(" Debug.Print 42; Tab(cls.Baz)", 17, 24)] + [TestCase(" If cls.Baz Then Foo = 42", 8, 15)] + [TestCase(" If cls.Baz Then \r\n Foo = 42 \r\n End If", 8, 15)] + [TestCase(" If False Then : ElseIf cls.Baz Then\r\n Foo = 42 \r\n End If", 28, 35)] + [TestCase(" Do While cls.Baz\r\n Foo = 42 \r\n Loop", 14, 21)] + [TestCase(" Do Until cls.Baz\r\n Foo = 42 \r\n Loop", 14, 21)] + [TestCase(" Do : Foo = 42 : Loop While cls.Baz", 33, 40)] + [TestCase(" Do : Foo = 42 : Loop Until cls.Baz", 33, 40)] + [TestCase(" While cls.Baz\r\n Foo = 42 \r\n Wend", 11, 18)] + [TestCase(" For fooBar = cls.Baz To 42 Step 23\r\n Foo = 42 \r\n Next", 18, 25)] + [TestCase(" For fooBar = 42 To cls.Baz Step 23\r\n Foo = 42 \r\n Next", 24, 31)] + [TestCase(" For fooBar = 23 To 42 Step cls.Baz\r\n Foo = 42 \r\n Next", 32, 39)] + [TestCase(" Select Case cls.Baz : Case 42 : Foo = 42 : End Select", 17, 24)] + [TestCase(" Select Case 42 : Case cls.Baz : Foo = 42 : End Select", 27, 34)] + [TestCase(" Select Case 42 : Case 23, cls.Baz : Foo = 42 : End Select", 31, 38)] + [TestCase(" Select Case 42 : Case cls.Baz To 666 : Foo = 42 : End Select", 27, 34)] + [TestCase(" Select Case 42 : Case 23 To cls.Baz : Foo = 42 : End Select", 33, 40)] + [TestCase(" Select Case 42 : Case Is = cls.Baz : Foo = 42 : End Select", 32, 39)] + [TestCase(" Select Case 42 : Case Is < cls.Baz : Foo = 42 : End Select", 32, 39)] + [TestCase(" Select Case 42 : Case Is > cls.Baz : Foo = 42 : End Select", 32, 39)] + [TestCase(" Select Case 42 : Case Is <> cls.Baz : Foo = 42 : End Select", 33, 40)] + [TestCase(" Select Case 42 : Case Is >< cls.Baz : Foo = 42 : End Select", 33, 40)] + [TestCase(" Select Case 42 : Case Is <= cls.Baz : Foo = 42 : End Select", 33, 40)] + [TestCase(" Select Case 42 : Case Is =< cls.Baz : Foo = 42 : End Select", 33, 40)] + [TestCase(" Select Case 42 : Case Is >= cls.Baz : Foo = 42 : End Select", 33, 40)] + [TestCase(" Select Case 42 : Case Is => cls.Baz : Foo = 42 : End Select", 33, 40)] + [TestCase(" Select Case 42 : Case = cls.Baz : Foo = 42 : End Select", 29, 36)] + [TestCase(" Select Case 42 : Case < cls.Baz : Foo = 42 : End Select", 29, 36)] + [TestCase(" Select Case 42 : Case > cls.Baz : Foo = 42 : End Select", 29, 36)] + [TestCase(" Select Case 42 : Case <> cls.Baz : Foo = 42 : End Select", 30, 37)] + [TestCase(" Select Case 42 : Case >< cls.Baz : Foo = 42 : End Select", 30, 37)] + [TestCase(" Select Case 42 : Case <= cls.Baz : Foo = 42 : End Select", 30, 37)] + [TestCase(" Select Case 42 : Case =< cls.Baz : Foo = 42 : End Select", 30, 37)] + [TestCase(" Select Case 42 : Case >= cls.Baz : Foo = 42 : End Select", 30, 37)] + [TestCase(" Select Case 42 : Case => cls.Baz : Foo = 42 : End Select", 30, 37)] + [TestCase(" On cls.Baz GoTo label1, label2", 8, 15)] + [TestCase(" On cls.Baz GoSub label1, label2", 8, 15)] + [TestCase(" ReDim fooBar(cls.Baz To 42)", 18, 25)] + [TestCase(" ReDim fooBar(23 To cls.Baz)", 24, 31)] + [TestCase(" ReDim fooBar(23 To 42, cls.Baz To 42)", 28, 35)] + [TestCase(" ReDim fooBar(23 To 42, 23 To cls.Baz)", 34, 41)] + [TestCase(" ReDim fooBar(cls.Baz)", 18, 25)] + [TestCase(" ReDim fooBar(42, cls.Baz)", 22, 29)] + [TestCase(" Mid fooBar, cls.Baz, 42 = \"Hello\"", 28, 35)] + [TestCase(" Mid fooBar, 23, cls.Baz = \"Hello\"", 32, 39)] + [TestCase(" Mid fooBar, 23, 42 = cls.Baz", 37, 44)] + [TestCase(" LSet fooBar = cls.Baz", 19, 26)] + [TestCase(" RSet fooBar = cls.Baz", 19, 26)] + [TestCase(" Error cls.Baz", 11, 18)] + [TestCase(" Open cls.Baz As 42 Len = 23", 10, 17)] + [TestCase(" Open \"somePath\" As cls.Baz Len = 23", 24, 31)] + [TestCase(" Open \"somePath\" As #cls.Baz Len = 23", 25, 32)] + [TestCase(" Open \"somePath\" As 23 Len = cls.Baz", 33, 40)] + [TestCase(" Close cls.Baz, 23", 11, 18)] + [TestCase(" Close 23, #cls.Baz, 23", 16, 23)] + [TestCase(" Reset cls.Baz, 23", 11, 18)] + [TestCase(" Reset 23, #cls.Baz, 23", 16, 23)] + [TestCase(" Seek cls.Baz, 23", 10, 17)] + [TestCase(" Seek #cls.Baz, 23", 11, 18)] + [TestCase(" Seek 23, cls.Baz", 14, 21)] + [TestCase(" Lock cls.Baz, 23 To 42", 10, 17)] + [TestCase(" Lock #cls.Baz, 23 To 42", 11, 18)] + [TestCase(" Lock 23, cls.Baz To 42", 14, 21)] + [TestCase(" Lock 23, 42 To cls.Baz", 20, 27)] + [TestCase(" Unlock cls.Baz, 23 To 42", 12, 19)] + [TestCase(" Unlock #cls.Baz, 23 To 42", 13, 20)] + [TestCase(" Unlock 23, cls.Baz To 42", 16, 23)] + [TestCase(" Unlock 23, 42 To cls.Baz", 22, 29)] + [TestCase(" Line Input #cls.Baz, fooBar", 17, 24)] + [TestCase(" Width #cls.Baz, 42", 12, 19)] + [TestCase(" Width #23, cls.Baz", 16, 23)] + [TestCase(" Print #cls.Baz, 42", 12, 19)] + [TestCase(" Print #23, cls.Baz", 16, 23)] + [TestCase(" Print #23, 42, cls.Baz", 20, 27)] + [TestCase(" Print #23, 42; cls.Baz", 20, 27)] + [TestCase(" Print #23, Spc(cls.Baz)", 20, 27)] + [TestCase(" Print #23, 42, Spc(cls.Baz)", 24, 31)] + [TestCase(" Print #23, 42; Spc(cls.Baz)", 24, 31)] + [TestCase(" Print #23, Tab(cls.Baz)", 20, 27)] + [TestCase(" Print #23, 42, Tab(cls.Baz)", 24, 31)] + [TestCase(" Print #23, 42; Tab(cls.Baz)", 24, 31)] + [TestCase(" Input #cls.Baz, fooBar", 12, 17)] + [TestCase(" Put cls.Baz, 42, fooBar", 9, 16)] + [TestCase(" Put #cls.Baz, 42, fooBar", 10, 17)] + [TestCase(" Put 42, cls.Baz, fooBar", 13, 20)] + [TestCase(" Get cls.Baz, 42, fooBar", 9, 16)] + [TestCase(" Get #cls.Baz, 42, fooBar", 10, 17)] + [TestCase(" Get 42, cls.Baz, fooBar", 13, 20)] + public void LetCoercionDefaultMemberAccessHasReferenceToDefaultMemberOnEntireContext(string statement, int selectionStartColumn, int selectionEndColumn) + { + var class1Code = @" +Public Function Foo() As Long +Attribute Foo.VB_UserMemId = 0 +End Function +"; + + var class2Code = @" +Public Function Baz() As Class1 +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class1 +End Function +"; + + var moduleCode = $@" +Private Function Foo() As Variant + Dim cls As new Class2 + Dim fooBar As Variant +{statement} +End Function + +Private Sub Bar(arg As Long) +End Sub + +Private Sub Baz(arg As Variant) +End Sub +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", class1Code, ComponentType.ClassModule), + ("Class2", class2Code, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(5, selectionStartColumn, 5, selectionEndColumn); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var qualifiedSelection = new QualifiedSelection(module, selection); + var defaultMemberReference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Last(); + var referencedDeclaration = defaultMemberReference.Declaration; + + var expectedReferencedDeclarationName = "Class1.Foo"; + var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; + + Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); + Assert.IsTrue(defaultMemberReference.IsNonIndexedDefaultMemberAccess); + Assert.AreEqual(1, defaultMemberReference.DefaultMemberRecursionDepth); + } + } + + [Test] + [Category("Grammar")] + [Category("Resolver")] + public void LetCoercionDefaultMemberAssignmentHasAssignmentReferenceToDefaultMemberOnEntireContext() + { + var class1Code = @" +Public Property Let Foo(arg As Long) +Attribute Foo.VB_UserMemId = 0 +End Property +"; + + var class2Code = @" +Public Function Baz() As Class1 +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class1 +End Function +"; + + var moduleCode = $@" +Private Function Foo() As Variant + Dim cls As new Class2 + cls.Baz = 42 +End Function + +Private Sub Bar(arg As Long) +End Sub + +Private Sub Baz(arg As Variant) +End Sub +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", class1Code, ComponentType.ClassModule), + ("Class2", class2Code, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(4, 5, 4, 12); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var qualifiedSelection = new QualifiedSelection(module, selection); + var defaultMemberReference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Last(); + var referencedDeclaration = defaultMemberReference.Declaration; + + var expectedReferencedDeclarationName = "Class1.Foo"; + var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; + + Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); + Assert.IsTrue(defaultMemberReference.IsNonIndexedDefaultMemberAccess); + Assert.AreEqual(1, defaultMemberReference.DefaultMemberRecursionDepth); + Assert.IsTrue(defaultMemberReference.IsAssignment); + } + } + + [Test] + [Category("Grammar")] + [Category("Resolver")] + [TestCase(" Set cls.Baz = fooBar", 9, 16)] + [TestCase(" Set fooBar = cls.Baz", 18, 25)] + [TestCase(" Bar cls.Baz", 9, 16)] + [TestCase(" Baz cls.Baz", 18, 25)] + [TestCase(" For Each cls In fooBar : Foo = 42 : Next", 14, 17)] + [TestCase(" For Each fooBar In cls.Baz : Foo = 42 : Next", 24, 31)] + public void NonLetCoercionExpressionHasNoDefaultMemberAccess(string statement, int selectionStartColumn, int selectionEndColumn) + { + var class1Code = @" +Public Function Foo() As Long +Attribute Foo.VB_UserMemId = 0 +End Function +"; + + var class2Code = @" +Public Function Baz() As Class1 +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class1 +End Function +"; + + var moduleCode = $@" +Private Function Foo() As Variant + Dim cls As new Class2 + Dim fooBar As Variant +{statement} +End Function + +Private Sub Bar(arg As Object) +End Sub + +Private Sub Baz(arg As Variant) +End Sub +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", class1Code, ComponentType.ClassModule), + ("Class2", class2Code, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(5, selectionStartColumn, 5, selectionEndColumn); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var qualifiedSelection = new QualifiedSelection(module, selection); + var defaultMemberReference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Where(referemce => referemce.IsDefaultMemberAccess); + + Assert.IsFalse(defaultMemberReference.Any()); + } + } + + [Test] + [Category("Grammar")] + [Category("Resolver")] + public void LetCoercionDefaultMemberAssignmentHasNonAssignmentReferenceToAccessedMember() + { + var class1Code = @" +Public Property Let Foo(arg As Long) +Attribute Foo.VB_UserMemId = 0 +End Property +"; + + var class2Code = @" +Public Function Baz() As Class1 +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class1 +End Function +"; + + var moduleCode = $@" +Private Function Foo() As Variant + Dim cls As new Class2 + cls.Baz = 42 +End Function + +Private Sub Bar(arg As Long) +End Sub + +Private Sub Baz(arg As Variant) +End Sub +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", class1Code, ComponentType.ClassModule), + ("Class2", class2Code, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(4, 9, 4, 12); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var qualifiedSelection = new QualifiedSelection(module, selection); + var defaultMemberReference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).First(); + var referencedDeclaration = defaultMemberReference.Declaration; + + var expectedReferencedDeclarationName = "Class2.Baz"; + var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; + + Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); + Assert.IsFalse(defaultMemberReference.IsAssignment); + } + } + + [Test] + [Category("Grammar")] + [Category("Resolver")] + public void ParameterizedProcedureCoercionDefaultMemberAccessReferenceToDefaultMemberOnEntireContext() + { + var class1Code = @" +Public Sub Foo(arg As Long) +Attribute Foo.VB_UserMemId = 0 +End Sub +"; + + var class2Code = @" +Public Function Baz() As Class1 +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class1 +End Function +"; + + var moduleCode = $@" +Private Function Foo() As Variant + Dim cls As new Class2 + cls.Baz 42 +End Function + +Private Sub Bar(arg As Long) +End Sub + +Private Sub Baz(arg As Variant) +End Sub +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", class1Code, ComponentType.ClassModule), + ("Class2", class2Code, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(4, 5, 4, 12); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var qualifiedSelection = new QualifiedSelection(module, selection); + var defaultMemberReference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Last(); + var referencedDeclaration = defaultMemberReference.Declaration; + + var expectedReferencedDeclarationName = "Class1.Foo"; + var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; + + Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); + Assert.IsTrue(defaultMemberReference.IsIndexedDefaultMemberAccess); + Assert.AreEqual(1, defaultMemberReference.DefaultMemberRecursionDepth); + Assert.IsTrue(defaultMemberReference.IsAssignment); + } + } + + [Test] + [Category("Grammar")] + [Category("Resolver")] + public void NonParameterizedProcedureCoercionDefaultMemberAccessReferenceToDefaultMemberOnEntireContext() + { + var class1Code = @" +Public Sub Foo() +Attribute Foo.VB_UserMemId = 0 +End Sub +"; + + var class2Code = @" +Public Function Baz() As Class1 +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class1 +End Function +"; + + var moduleCode = $@" +Private Function Foo() As Variant + Dim cls As new Class2 + cls.Baz +End Function + +Private Sub Bar(arg As Long) +End Sub + +Private Sub Baz(arg As Variant) +End Sub +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", class1Code, ComponentType.ClassModule), + ("Class2", class2Code, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(4, 5, 4, 12); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var qualifiedSelection = new QualifiedSelection(module, selection); + var defaultMemberReference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Last(); + var referencedDeclaration = defaultMemberReference.Declaration; + + var expectedReferencedDeclarationName = "Class1.Foo"; + var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; + + Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); + Assert.IsTrue(defaultMemberReference.IsIndexedDefaultMemberAccess); + Assert.AreEqual(1, defaultMemberReference.DefaultMemberRecursionDepth); + Assert.IsTrue(defaultMemberReference.IsAssignment); + } + } + + [Test] + [Category("Grammar")] + [Category("Resolver")] + public void ParameterizedProcedureCoercionDefaultMemberAccessReferenceToDefaultMemberOnEntireContext_ExplicitCall() + { + var class1Code = @" +Public Sub Foo(arg As Long) +Attribute Foo.VB_UserMemId = 0 +End Sub +"; + + var class2Code = @" +Public Function Baz() As Class1 +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class1 +End Function +"; + + var moduleCode = $@" +Private Function Foo() As Variant + Dim cls As new Class2 + Call cls.Baz(42) +End Function + +Private Sub Bar(arg As Long) +End Sub + +Private Sub Baz(arg As Variant) +End Sub +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", class1Code, ComponentType.ClassModule), + ("Class2", class2Code, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(4, 5, 4, 12); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var qualifiedSelection = new QualifiedSelection(module, selection); + var defaultMemberReference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Last(); + var referencedDeclaration = defaultMemberReference.Declaration; + + var expectedReferencedDeclarationName = "Class1.Foo"; + var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; + + Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); + Assert.IsTrue(defaultMemberReference.IsIndexedDefaultMemberAccess); + Assert.AreEqual(1, defaultMemberReference.DefaultMemberRecursionDepth); + Assert.IsTrue(defaultMemberReference.IsAssignment); + } + } + + [Test] + [Category("Grammar")] + [Category("Resolver")] + public void NonParameterizedProcedureCoercionDefaultMemberAccessReferenceToDefaultMemberOnEntireContext_ExplicitCall() + { + var class1Code = @" +Public Sub Foo() +Attribute Foo.VB_UserMemId = 0 +End Sub +"; + + var class2Code = @" +Public Function Baz() As Class1 +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class1 +End Function +"; + + var moduleCode = $@" +Private Function Foo() As Variant + Dim cls As new Class2 + Call cls.Baz +End Function + +Private Sub Bar(arg As Long) +End Sub + +Private Sub Baz(arg As Variant) +End Sub +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", class1Code, ComponentType.ClassModule), + ("Class2", class2Code, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(4, 5, 4, 12); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var qualifiedSelection = new QualifiedSelection(module, selection); + var defaultMemberReference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Last(); + var referencedDeclaration = defaultMemberReference.Declaration; + + var expectedReferencedDeclarationName = "Class1.Foo"; + var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; + + Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); + Assert.IsTrue(defaultMemberReference.IsIndexedDefaultMemberAccess); + Assert.AreEqual(1, defaultMemberReference.DefaultMemberRecursionDepth); + Assert.IsTrue(defaultMemberReference.IsAssignment); + } + } } } From f913d7adde6014fdb78477dc076081d33e1df925 Mon Sep 17 00:00:00 2001 From: Max Doerner Date: Mon, 19 Aug 2019 00:51:18 +0200 Subject: [PATCH 07/32] Add let coercion default member access resolution (part 1) This commit contains the general setup for resolving default member accesses due to let coercion and covers all cases in which the coercion does not happen on a subexpression level. --- Rubberduck.Parsing/Binding/BindingService.cs | 4 +- .../Bindings/LetCoercionDefaultBinding.cs | 197 ++++++++++++++ .../Binding/DefaultBindingContext.cs | 12 +- ...etCoercionDefaultMemberAccessExpression.cs | 26 ++ Rubberduck.Parsing/Binding/IBindingContext.cs | 4 +- .../Binding/ProcedurePointerBindingContext.cs | 4 +- .../Binding/TypeBindingContext.cs | 4 +- .../Symbols/IdentifierReferenceListener.cs | 5 + .../Symbols/IdentifierReferenceResolver.cs | 241 ++++++++++-------- .../Symbols/ModuleBodyElementDeclaration.cs | 3 - .../Symbols/PropertyGetDeclaration.cs | 1 - .../BoundExpressionVisitor.cs | 224 ++++++++++++---- RubberduckTests/Grammar/ResolverTests.cs | 31 +-- 13 files changed, 574 insertions(+), 182 deletions(-) create mode 100644 Rubberduck.Parsing/Binding/Bindings/LetCoercionDefaultBinding.cs create mode 100644 Rubberduck.Parsing/Binding/Expressions/LetCoercionDefaultMemberAccessExpression.cs diff --git a/Rubberduck.Parsing/Binding/BindingService.cs b/Rubberduck.Parsing/Binding/BindingService.cs index 7ded51b57c..b901a7f757 100644 --- a/Rubberduck.Parsing/Binding/BindingService.cs +++ b/Rubberduck.Parsing/Binding/BindingService.cs @@ -34,9 +34,9 @@ public Declaration ResolveGoTo(Declaration procedure, string label) return _declarationFinder.FindLabel(procedure, label); } - public IBoundExpression ResolveDefault(Declaration module, Declaration parent, ParserRuleContext expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext) + public IBoundExpression ResolveDefault(Declaration module, Declaration parent, ParserRuleContext expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext, bool requiresLetCoercion, bool isLetAssignment = false) { - return _defaultBindingContext.Resolve(module, parent, expression, withBlockVariable, statementContext); + return _defaultBindingContext.Resolve(module, parent, expression, withBlockVariable, statementContext, requiresLetCoercion, isLetAssignment); } public IBoundExpression ResolveType(Declaration module, Declaration parent, ParserRuleContext expression) diff --git a/Rubberduck.Parsing/Binding/Bindings/LetCoercionDefaultBinding.cs b/Rubberduck.Parsing/Binding/Bindings/LetCoercionDefaultBinding.cs new file mode 100644 index 0000000000..2f79012d36 --- /dev/null +++ b/Rubberduck.Parsing/Binding/Bindings/LetCoercionDefaultBinding.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using Rubberduck.Parsing.Symbols; +using System.Linq; +using Antlr4.Runtime; +using Rubberduck.Parsing.Grammar; + +namespace Rubberduck.Parsing.Binding +{ + public sealed class LetCoercionDefaultBinding : IExpressionBinding + { + private readonly ParserRuleContext _expression; + private readonly IExpressionBinding _wrappedExpressionBinding; + private IBoundExpression _wrappedExpression; + private readonly bool _isAssignment; + + private const int DEFAULT_MEMBER_RECURSION_LIMIT = 32; + + //This is a wrapper used to model Let coercion for object types. + + public LetCoercionDefaultBinding( + ParserRuleContext expression, + IExpressionBinding wrappedExpressionBinding, + bool isAssignment = false) + : this( + expression, + (IBoundExpression)null, + isAssignment) + { + _wrappedExpressionBinding = wrappedExpressionBinding; + } + + public LetCoercionDefaultBinding( + ParserRuleContext expression, + IBoundExpression wrappedExpression, + bool isAssignment = false) + { + _expression = expression; + _wrappedExpression = wrappedExpression; + _isAssignment = isAssignment; + } + + public IBoundExpression Resolve() + { + if (_wrappedExpressionBinding != null) + { + _wrappedExpression = _wrappedExpressionBinding.Resolve(); + } + + return Resolve(_wrappedExpression, _expression, _isAssignment); + } + + private static IBoundExpression Resolve(IBoundExpression wrappedExpression, ParserRuleContext expression, bool isAssignment) + { + if (wrappedExpression.Classification == ExpressionClassification.ResolutionFailed) + { + return wrappedExpression; + } + + var wrappedDeclaration = wrappedExpression.ReferencedDeclaration; + + if (wrappedDeclaration == null + || !wrappedDeclaration.IsObject + && !(wrappedDeclaration.IsObjectArray + && wrappedExpression is IndexExpression indexExpression + && indexExpression.IsArrayAccess)) + { + return wrappedExpression; + } + + //The wrapped declaration is of a specific class type or Object. + + if (wrappedExpression.Classification == ExpressionClassification.Unbound) + { + //This should actually not be possible since an unbound expression cannot have a referenced declaration. + //Apart from this, we can only deal with the type Object. + return new LetCoercionDefaultMemberAccessExpression(null, ExpressionClassification.Unbound, expression, wrappedExpression, 1, null); + } + + var asTypeName = wrappedDeclaration.AsTypeName; + var asTypeDeclaration = wrappedDeclaration.AsTypeDeclaration; + + return ResolveViaDefaultMember(wrappedExpression, asTypeName, asTypeDeclaration, expression, isAssignment); + } + + private static IBoundExpression CreateFailedExpression(IBoundExpression lExpression) + { + var failedExpr = new ResolutionFailedExpression(); + failedExpr.AddSuccessfullyResolvedExpression(lExpression); + return failedExpr; + } + + private static IBoundExpression ResolveViaDefaultMember(IBoundExpression wrappedExpression, string asTypeName, Declaration asTypeDeclaration, ParserRuleContext expression, bool isAssignment, int recursionDepth = 1, RecursiveDefaultMemberAccessExpression containedExpression = null) + { + if (Tokens.Variant.Equals(asTypeName, StringComparison.InvariantCultureIgnoreCase) + || Tokens.Object.Equals(asTypeName, StringComparison.InvariantCultureIgnoreCase)) + { + // We cannot know the the default member in this case, so return an unbound member call. + return new LetCoercionDefaultMemberAccessExpression(null, ExpressionClassification.Unbound, expression, wrappedExpression, recursionDepth, containedExpression); + } + + var defaultMember = (asTypeDeclaration as ClassModuleDeclaration)?.DefaultMember; + if (defaultMember == null + || !IsPropertyGetLetFunctionProcedure(defaultMember) + || !IsPublic(defaultMember)) + { + return CreateFailedExpression(wrappedExpression); + } + + var defaultMemberClassification = DefaultMemberClassification(defaultMember); + + var parameters = ((IParameterizedDeclaration)defaultMember).Parameters.ToList(); + if (isAssignment + && defaultMember.DeclarationType == DeclarationType.PropertyLet + && IsCompatibleWithOneNonObjectParameter(parameters)) + { + //This is a Let assignment. So, finding a Property Let with one non object paramter means we are done. + return new LetCoercionDefaultMemberAccessExpression(defaultMember, defaultMemberClassification, expression, wrappedExpression, recursionDepth, containedExpression); + } + + if (parameters.All(parameter => parameter.IsOptional)) + { + if (!defaultMember.IsObject) + { + //We found a property Get of Function default member returning a value type. + //This might also be applicable in case of an assignment, because only the Get will be assigned as default member if both Get and Let exist. + return new LetCoercionDefaultMemberAccessExpression(defaultMember, defaultMemberClassification, expression, wrappedExpression, recursionDepth, containedExpression); + } + + if (DEFAULT_MEMBER_RECURSION_LIMIT >= recursionDepth) + { + //The default member returns an object type. So, we have to recurse. + return ResolveRecursiveDefaultMember(wrappedExpression, defaultMember, defaultMemberClassification, expression, isAssignment, recursionDepth, containedExpression); + } + } + + return CreateFailedExpression(wrappedExpression); + } + + private static IBoundExpression ResolveRecursiveDefaultMember(IBoundExpression wrappedExpression, Declaration defaultMember, ExpressionClassification defaultMemberClassification, ParserRuleContext expression, bool isAssignment, int recursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) + { + var defaultMemberAsTypeName = defaultMember.AsTypeName; + var defaultMemberAsTypeDeclaration = defaultMember.AsTypeDeclaration; + + var defaultMemberExpression = new RecursiveDefaultMemberAccessExpression(defaultMember, defaultMemberClassification, expression, recursionDepth, containedExpression); + + return ResolveViaDefaultMember( + wrappedExpression, + defaultMemberAsTypeName, + defaultMemberAsTypeDeclaration, + expression, + isAssignment, + recursionDepth + 1, + defaultMemberExpression); + } + + private static bool IsCompatibleWithOneNonObjectParameter(IReadOnlyCollection parameters) + { + return parameters.Count(parameter => !parameter.IsObject) == 1 + && parameters.Any(parameter => !parameter.IsOptional && !parameter.IsObject) + || parameters.All(parameter => parameter.IsOptional) + && parameters.Any(parameter => !parameter.IsObject); + } + + private static bool IsPropertyGetLetFunctionProcedure(Declaration declaration) + { + var declarationType = declaration.DeclarationType; + return declarationType == DeclarationType.PropertyGet + || declarationType == DeclarationType.PropertyLet + || declarationType == DeclarationType.Function + || declarationType == DeclarationType.Procedure; + } + + private static bool IsPublic(Declaration declaration) + { + var accessibility = declaration.Accessibility; + return accessibility == Accessibility.Global + || accessibility == Accessibility.Implicit + || accessibility == Accessibility.Public; + } + + private static ExpressionClassification DefaultMemberClassification(Declaration defaultMember) + { + if (defaultMember.DeclarationType.HasFlag(DeclarationType.Property)) + { + return ExpressionClassification.Property; + } + + if (defaultMember.DeclarationType == DeclarationType.Procedure) + { + return ExpressionClassification.Subroutine; + } + + return ExpressionClassification.Function; + } + } +} \ No newline at end of file diff --git a/Rubberduck.Parsing/Binding/DefaultBindingContext.cs b/Rubberduck.Parsing/Binding/DefaultBindingContext.cs index 44ed1e11b9..a933fd5561 100644 --- a/Rubberduck.Parsing/Binding/DefaultBindingContext.cs +++ b/Rubberduck.Parsing/Binding/DefaultBindingContext.cs @@ -23,14 +23,20 @@ public DefaultBindingContext( _procedurePointerBindingContext = procedurePointerBindingContext; } - public IBoundExpression Resolve(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext) + public IBoundExpression Resolve(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext, bool requiresLetCoercion, bool isLetAssignment) { - var bindingTree = BuildTree(module, parent, expression, withBlockVariable, statementContext); + var bindingTree = BuildTree(module, parent, expression, withBlockVariable, statementContext, requiresLetCoercion, isLetAssignment); return bindingTree?.Resolve(); } - public IExpressionBinding BuildTree(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext) + public IExpressionBinding BuildTree(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext, bool requiresLetCoercion = false, bool isLetAssignment = false) { + if (requiresLetCoercion && expression is ParserRuleContext context) + { + var innerExpressionBinding = BuildTree(module, parent, expression, withBlockVariable, statementContext); + return new LetCoercionDefaultBinding(context, innerExpressionBinding, isLetAssignment); + } + switch (expression) { case VBAParser.ExpressionContext expressionContext: diff --git a/Rubberduck.Parsing/Binding/Expressions/LetCoercionDefaultMemberAccessExpression.cs b/Rubberduck.Parsing/Binding/Expressions/LetCoercionDefaultMemberAccessExpression.cs new file mode 100644 index 0000000000..f3f2614c41 --- /dev/null +++ b/Rubberduck.Parsing/Binding/Expressions/LetCoercionDefaultMemberAccessExpression.cs @@ -0,0 +1,26 @@ +using Antlr4.Runtime; +using Rubberduck.Parsing.Symbols; + +namespace Rubberduck.Parsing.Binding +{ + public class LetCoercionDefaultMemberAccessExpression : BoundExpression + { + public LetCoercionDefaultMemberAccessExpression( + Declaration referencedDeclaration, + ExpressionClassification classification, + ParserRuleContext context, + IBoundExpression wrappedExpression, + int defaultMemberRecursionDepth = 0, + RecursiveDefaultMemberAccessExpression containedDefaultMemberRecursionExpression = null) + : base(referencedDeclaration, classification, context) + { + WrappedExpression = wrappedExpression; + DefaultMemberRecursionDepth = defaultMemberRecursionDepth; + ContainedDefaultMemberRecursionExpression = containedDefaultMemberRecursionExpression; + } + + public IBoundExpression WrappedExpression { get; } + public int DefaultMemberRecursionDepth { get; } + public RecursiveDefaultMemberAccessExpression ContainedDefaultMemberRecursionExpression { get; } + } +} \ No newline at end of file diff --git a/Rubberduck.Parsing/Binding/IBindingContext.cs b/Rubberduck.Parsing/Binding/IBindingContext.cs index a6c5089fa5..20f527b997 100644 --- a/Rubberduck.Parsing/Binding/IBindingContext.cs +++ b/Rubberduck.Parsing/Binding/IBindingContext.cs @@ -5,7 +5,7 @@ namespace Rubberduck.Parsing.Binding { public interface IBindingContext { - IBoundExpression Resolve(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext); - IExpressionBinding BuildTree(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext); + IBoundExpression Resolve(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext, bool requiresLetCoercion = false, bool isLetAssignment = false); + IExpressionBinding BuildTree(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext, bool requiresLetCoercion = false, bool isLetAssignment = false); } } diff --git a/Rubberduck.Parsing/Binding/ProcedurePointerBindingContext.cs b/Rubberduck.Parsing/Binding/ProcedurePointerBindingContext.cs index 4f5f3af893..b6390bb490 100644 --- a/Rubberduck.Parsing/Binding/ProcedurePointerBindingContext.cs +++ b/Rubberduck.Parsing/Binding/ProcedurePointerBindingContext.cs @@ -15,7 +15,7 @@ public ProcedurePointerBindingContext(DeclarationFinder declarationFinder) _declarationFinder = declarationFinder; } - public IBoundExpression Resolve(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext) + public IBoundExpression Resolve(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext, bool requiresLetCoercion = false, bool isLetAssignment = false) { IExpressionBinding bindingTree = BuildTree(module, parent, expression, withBlockVariable, statementContext); if (bindingTree != null) @@ -25,7 +25,7 @@ public IBoundExpression Resolve(Declaration module, Declaration parent, IParseTr return null; } - public IExpressionBinding BuildTree(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext) + public IExpressionBinding BuildTree(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext, bool requiresLetCoercion = false, bool isLetAssignment = false) { switch (expression) { diff --git a/Rubberduck.Parsing/Binding/TypeBindingContext.cs b/Rubberduck.Parsing/Binding/TypeBindingContext.cs index 19ef9c0824..69e440529d 100644 --- a/Rubberduck.Parsing/Binding/TypeBindingContext.cs +++ b/Rubberduck.Parsing/Binding/TypeBindingContext.cs @@ -15,13 +15,13 @@ public TypeBindingContext(DeclarationFinder declarationFinder) _declarationFinder = declarationFinder; } - public IBoundExpression Resolve(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext) + public IBoundExpression Resolve(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext, bool requiresLetCoercion = false, bool isLetAssignment = false) { IExpressionBinding bindingTree = BuildTree(module, parent, expression, withBlockVariable, statementContext); return bindingTree?.Resolve(); } - public IExpressionBinding BuildTree(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext) + public IExpressionBinding BuildTree(Declaration module, Declaration parent, IParseTree expression, IBoundExpression withBlockVariable, StatementResolutionContext statementContext, bool requiresLetCoercion = false, bool isLetAssignment = false) { switch (expression) { diff --git a/Rubberduck.Parsing/Symbols/IdentifierReferenceListener.cs b/Rubberduck.Parsing/Symbols/IdentifierReferenceListener.cs index d6607b87cb..851efacfb2 100644 --- a/Rubberduck.Parsing/Symbols/IdentifierReferenceListener.cs +++ b/Rubberduck.Parsing/Symbols/IdentifierReferenceListener.cs @@ -176,6 +176,11 @@ public override void EnterEraseStmt(VBAParser.EraseStmtContext context) _resolver.Resolve(context); } + public override void EnterMidStatement(VBAParser.MidStatementContext context) + { + _resolver.Resolve(context); + } + public override void EnterLsetStmt(VBAParser.LsetStmtContext context) { _resolver.Resolve(context); diff --git a/Rubberduck.Parsing/Symbols/IdentifierReferenceResolver.cs b/Rubberduck.Parsing/Symbols/IdentifierReferenceResolver.cs index e293c935b6..0d88307308 100644 --- a/Rubberduck.Parsing/Symbols/IdentifierReferenceResolver.cs +++ b/Rubberduck.Parsing/Symbols/IdentifierReferenceResolver.cs @@ -75,7 +75,8 @@ public void EnterWithBlock(VBAParser.WithStmtContext context) _currentParent, context.expression(), GetInnerMostWithExpression(), - StatementResolutionContext.Undefined); + StatementResolutionContext.Undefined, + false); _boundExpressionVisitor.AddIdentifierReferences(boundExpression, _qualifiedModuleName, _currentScope, _currentParent); // note: pushes null if unresolved _withBlockExpressions.Push(boundExpression); @@ -102,7 +103,7 @@ public void Resolve(VBAParser.ArgDefaultValueContext context) { return; } - ResolveDefault(expression); + ResolveDefault(expression, false); } public void Resolve(VBAParser.ArrayDimContext context) @@ -115,9 +116,9 @@ public void Resolve(VBAParser.ArrayDimContext context) { if (dimSpec.lowerBound() != null) { - ResolveDefault(dimSpec.lowerBound().constantExpression().expression()); + ResolveDefault(dimSpec.lowerBound().constantExpression().expression(), true); } - ResolveDefault(dimSpec.upperBound().constantExpression().expression()); + ResolveDefault(dimSpec.upperBound().constantExpression().expression(), true); } } @@ -132,7 +133,7 @@ public void Resolve(VBAParser.OnErrorStmtContext context) public void Resolve(VBAParser.ErrorStmtContext context) { - ResolveDefault(context.expression()); + ResolveDefault(context.expression(), true); } private void ResolveLabel(ParserRuleContext context, string label) @@ -163,6 +164,7 @@ private IEnumerable FindIdentifierAnnotations(QualifiedModuleName m private void ResolveDefault( ParserRuleContext expression, + bool requiresLetCoercion, StatementResolutionContext statementContext = StatementResolutionContext.Undefined, bool isAssignmentTarget = false, bool hasExplicitLetStatement = false, @@ -174,17 +176,19 @@ private void ResolveDefault( _currentParent, expression, withExpression, - statementContext); + statementContext, + requiresLetCoercion, + isAssignmentTarget); if (boundExpression.Classification == ExpressionClassification.ResolutionFailed) { - var lexpression = expression as VBAParser.LExpressionContext + var lExpression = expression as VBAParser.LExpressionContext ?? expression.GetChild(0) ?? (expression as VBAParser.LExprContext ?? expression.GetChild(0))?.lExpression(); - if (lexpression != null) + if (lExpression != null) { - _declarationFinder.AddUnboundContext(_currentParent, lexpression, withExpression); + _declarationFinder.AddUnboundContext(_currentParent, lExpression, withExpression); } else { @@ -220,7 +224,7 @@ public void Resolve(VBAParser.GoToStmtContext context) public void Resolve(VBAParser.OnGoToStmtContext context) { - ResolveDefault(context.expression()[0]); + ResolveDefault(context.expression()[0], true); for (int labelIndex = 1; labelIndex < context.expression().Length; labelIndex++) { ResolveLabel(context.expression()[labelIndex], context.expression()[labelIndex].GetText()); @@ -234,7 +238,7 @@ public void Resolve(VBAParser.GoSubStmtContext context) public void Resolve(VBAParser.OnGoSubStmtContext context) { - ResolveDefault(context.expression()[0]); + ResolveDefault(context.expression()[0], true); for (int labelIndex = 1; labelIndex < context.expression().Length; labelIndex++) { ResolveLabel(context.expression()[labelIndex], context.expression()[labelIndex].GetText()); @@ -243,10 +247,10 @@ public void Resolve(VBAParser.OnGoSubStmtContext context) public void Resolve(VBAParser.RedimStmtContext context) { - // TODO: Create local variable if no match for redim variable declaration. + // TODO: Create local variable if no match for ReDim variable declaration. foreach (var redimVariableDeclaration in context.redimDeclarationList().redimVariableDeclaration()) { - // We treat redim statements as index expressions to make it SLL. + // We treat ReDim statements as index expressions to make it SLL. var lExpr = ((VBAParser.LExprContext)redimVariableDeclaration.expression()).lExpression(); VBAParser.LExpressionContext indexedExpression; @@ -266,37 +270,37 @@ public void Resolve(VBAParser.RedimStmtContext context) // The indexedExpression is the array that is being resized. // We can't treat it as a normal index expression because the semantics are different. // It's not actually a function call but a special statement. - ResolveDefault(indexedExpression); + ResolveDefault(indexedExpression, false); if (argumentList.argument() != null) { foreach (var positionalArgument in argumentList.argument()) { if (positionalArgument.positionalArgument() != null) { - ResolveRedimArgument(positionalArgument.positionalArgument().argumentExpression()); + ResolveReDimArgument(positionalArgument.positionalArgument().argumentExpression()); } } } } } - private void ResolveRedimArgument(VBAParser.ArgumentExpressionContext argument) + private void ResolveReDimArgument(VBAParser.ArgumentExpressionContext argument) { - // Redim statements can either have "normal" positional argument expressions or lower + upper bounds arguments. + // ReDim statements can either have "normal" positional argument expressions or lower + upper bounds arguments. if (argument.lowerBoundArgumentExpression() != null) { - ResolveDefault(argument.lowerBoundArgumentExpression().expression()); - ResolveDefault(argument.upperBoundArgumentExpression().expression()); + ResolveDefault(argument.lowerBoundArgumentExpression().expression(), true); + ResolveDefault(argument.upperBoundArgumentExpression().expression(), true); } else { - ResolveDefault(argument.expression()); + ResolveDefault(argument.expression(), true); } } public void Resolve(VBAParser.WhileWendStmtContext context) { - ResolveDefault(context.expression()); + ResolveDefault(context.expression(), true); } public void Resolve(VBAParser.DoLoopStmtContext context) @@ -305,17 +309,17 @@ public void Resolve(VBAParser.DoLoopStmtContext context) { return; } - ResolveDefault(context.expression()); + ResolveDefault(context.expression(), true); } public void Resolve(VBAParser.IfStmtContext context) { - ResolveDefault(context.booleanExpression()); + ResolveDefault(context.booleanExpression(), true); if (context.elseIfBlock() != null) { foreach (var elseIfBlock in context.elseIfBlock()) { - ResolveDefault(elseIfBlock.booleanExpression()); + ResolveDefault(elseIfBlock.booleanExpression(), true); } } } @@ -326,12 +330,12 @@ public void Resolve(VBAParser.SingleLineIfStmtContext context) // single-line-if-statements, we do it here for better understanding. if (context.ifWithEmptyThen() != null) { - ResolveDefault(context.ifWithEmptyThen().booleanExpression()); + ResolveDefault(context.ifWithEmptyThen().booleanExpression(), true); ResolveListOrLabel(context.ifWithEmptyThen().singleLineElseClause().listOrLabel()); } else { - ResolveDefault(context.ifWithNonEmptyThen().booleanExpression()); + ResolveDefault(context.ifWithNonEmptyThen().booleanExpression(), true); ResolveListOrLabel(context.ifWithNonEmptyThen().listOrLabel()); if (context.ifWithNonEmptyThen().singleLineElseClause() != null) { @@ -351,7 +355,7 @@ private void ResolveListOrLabel(VBAParser.ListOrLabelContext listOrLabel) public void Resolve(VBAParser.SelectCaseStmtContext context) { - ResolveDefault(context.selectExpression().expression()); + ResolveDefault(context.selectExpression().expression(), true); if (context.caseClause() == null) { return; @@ -362,12 +366,12 @@ public void Resolve(VBAParser.SelectCaseStmtContext context) { if (rangeClause.expression() != null) { - ResolveDefault(rangeClause.expression()); + ResolveDefault(rangeClause.expression(), true); } else { - ResolveDefault(rangeClause.selectStartValue().expression()); - ResolveDefault(rangeClause.selectEndValue().expression()); + ResolveDefault(rangeClause.selectStartValue().expression(), true); + ResolveDefault(rangeClause.selectEndValue().expression(), true); } } } @@ -378,33 +382,35 @@ public void Resolve(VBAParser.LetStmtContext context) var letStatement = context.LET(); ResolveDefault( context.lExpression(), + true, StatementResolutionContext.LetStatement, true, letStatement != null); - ResolveDefault(context.expression()); + ResolveDefault(context.expression(), true); } public void Resolve(VBAParser.SetStmtContext context) { ResolveDefault( context.lExpression(), + false, StatementResolutionContext.SetStatement, true, false, true); - ResolveDefault(context.expression()); + ResolveDefault(context.expression(), false); } public void Resolve(VBAParser.CallStmtContext context) { - ResolveDefault(context); + ResolveDefault(context, false); } public void Resolve(VBAParser.ConstStmtContext context) { foreach (var constStmt in context.constSubStmt()) { - ResolveDefault(constStmt.expression()); + ResolveDefault(constStmt.expression(), false); } } @@ -412,7 +418,7 @@ public void Resolve(VBAParser.EraseStmtContext context) { foreach (var expr in context.expression()) { - ResolveDefault(expr); + ResolveDefault(expr, true); } } @@ -420,7 +426,7 @@ public void Resolve(VBAParser.NameStmtContext context) { foreach (var expr in context.expression()) { - ResolveDefault(expr); + ResolveDefault(expr, true); } } @@ -428,16 +434,17 @@ private void ResolveFileNumber(VBAParser.FileNumberContext fileNumber) { ResolveDefault(fileNumber.markedFileNumber() != null ? fileNumber.markedFileNumber().expression() - : fileNumber.unmarkedFileNumber().expression()); + : fileNumber.unmarkedFileNumber().expression(), + true); } public void Resolve(VBAParser.OpenStmtContext context) { - ResolveDefault(context.pathName().expression()); + ResolveDefault(context.pathName().expression(), true); ResolveFileNumber(context.fileNumber()); if (context.lenClause() != null) { - ResolveDefault(context.lenClause().recLength().expression()); + ResolveDefault(context.lenClause().recLength().expression(), true); } } @@ -455,7 +462,7 @@ public void Resolve(VBAParser.CloseStmtContext context) public void Resolve(VBAParser.SeekStmtContext context) { ResolveFileNumber(context.fileNumber()); - ResolveDefault(context.position().expression()); + ResolveDefault(context.position().expression(), true); } public void Resolve(VBAParser.LockStmtContext context) @@ -482,35 +489,35 @@ private void ResolveRecordRange(VBAParser.RecordRangeContext recordRange) } if (recordRange.startRecordNumber() != null) { - ResolveDefault(recordRange.startRecordNumber().expression()); + ResolveDefault(recordRange.startRecordNumber().expression(), true); } if (recordRange.endRecordNumber() != null) { - ResolveDefault(recordRange.endRecordNumber().expression()); + ResolveDefault(recordRange.endRecordNumber().expression(), true); } } public void Resolve(VBAParser.LineInputStmtContext context) { - ResolveDefault(context.markedFileNumber().expression()); - ResolveDefault(context.variableName().expression(), isAssignmentTarget: true); + ResolveDefault(context.markedFileNumber().expression(), true); + ResolveDefault(context.variableName().expression(),false , isAssignmentTarget: true); } public void Resolve(VBAParser.WidthStmtContext context) { - ResolveDefault(context.markedFileNumber().expression()); - ResolveDefault(context.lineWidth().expression()); + ResolveDefault(context.markedFileNumber().expression(), true); + ResolveDefault(context.lineWidth().expression(), true); } public void Resolve(VBAParser.PrintStmtContext context) { - ResolveDefault(context.markedFileNumber().expression()); + ResolveDefault(context.markedFileNumber().expression(), true); ResolveOutputList(context.outputList()); } public void Resolve(VBAParser.WriteStmtContext context) { - ResolveDefault(context.markedFileNumber().expression()); + ResolveDefault(context.markedFileNumber().expression(), true); ResolveOutputList(context.outputList()); } @@ -526,15 +533,15 @@ private void ResolveOutputList(VBAParser.OutputListContext outputList) { if (outputItem.outputClause().spcClause() != null) { - ResolveDefault(outputItem.outputClause().spcClause().spcNumber().expression()); + ResolveDefault(outputItem.outputClause().spcClause().spcNumber().expression(), true); } if (outputItem.outputClause().tabClause() != null && outputItem.outputClause().tabClause().tabNumberClause() != null) { - ResolveDefault(outputItem.outputClause().tabClause().tabNumberClause().tabNumber().expression()); + ResolveDefault(outputItem.outputClause().tabClause().tabNumberClause().tabNumber().expression(), true); } if (outputItem.outputClause().outputExpression() != null) { - ResolveDefault(outputItem.outputClause().outputExpression().expression()); + ResolveDefault(outputItem.outputClause().outputExpression().expression(), true); } } } @@ -542,10 +549,10 @@ private void ResolveOutputList(VBAParser.OutputListContext outputList) public void Resolve(VBAParser.InputStmtContext context) { - ResolveDefault(context.markedFileNumber().expression()); + ResolveDefault(context.markedFileNumber().expression(), true); foreach (var inputVariable in context.inputList().inputVariable()) { - ResolveDefault(inputVariable.expression(), isAssignmentTarget: true); + ResolveDefault(inputVariable.expression(), false, isAssignmentTarget: true); } } @@ -554,11 +561,11 @@ public void Resolve(VBAParser.PutStmtContext context) ResolveFileNumber(context.fileNumber()); if (context.recordNumber() != null) { - ResolveDefault(context.recordNumber().expression()); + ResolveDefault(context.recordNumber().expression(), true); } if (context.data() != null) { - ResolveDefault(context.data().expression()); + ResolveDefault(context.data().expression(), false); } } @@ -567,11 +574,11 @@ public void Resolve(VBAParser.GetStmtContext context) ResolveFileNumber(context.fileNumber()); if (context.recordNumber() != null) { - ResolveDefault(context.recordNumber().expression()); + ResolveDefault(context.recordNumber().expression(), true); } if (context.variable() != null) { - ResolveDefault(context.variable().expression(), isAssignmentTarget: true); + ResolveDefault(context.variable().expression(), false, isAssignmentTarget: true); } } @@ -579,7 +586,7 @@ public void Resolve(VBAParser.LsetStmtContext context) { foreach (var expr in context.expression()) { - ResolveDefault(expr); + ResolveDefault(expr, true); } } @@ -587,7 +594,17 @@ public void Resolve(VBAParser.RsetStmtContext context) { foreach (var expr in context.expression()) { - ResolveDefault(expr); + ResolveDefault(expr, true); + } + } + + public void Resolve(VBAParser.MidStatementContext context) + { + var variableExpression = context.lExpression(); + ResolveDefault(variableExpression, true); + foreach (var expr in context.expression()) + { + ResolveDefault(expr, true); } } @@ -606,7 +623,7 @@ public void Resolve(VBAParser.AsTypeClauseContext context) var length = context.fieldLength(); if (length?.identifierValue() != null) { - ResolveDefault(length.identifierValue()); + ResolveDefault(length.identifierValue(), false); } return; } @@ -623,12 +640,8 @@ public void Resolve(VBAParser.ForNextStmtContext context) _currentParent, lExpr, GetInnerMostWithExpression(), - StatementResolutionContext.Undefined); - //_boundExpressionVisitor.AddIdentifierReferences( - // firstExpression, - // _qualifiedModuleName, - // _currentScope, - // _currentParent); + StatementResolutionContext.Undefined, + true); if (firstExpression.Classification != ExpressionClassification.ResolutionFailed) { // each iteration counts as an assignment @@ -645,18 +658,28 @@ public void Resolve(VBAParser.ForNextStmtContext context) _currentParent, rExpr, GetInnerMostWithExpression(), - StatementResolutionContext.Undefined); + StatementResolutionContext.Undefined, + true); _boundExpressionVisitor.AddIdentifierReferences( secondExpression, _qualifiedModuleName, _currentScope, _currentParent); - for (int exprIndex = 1; exprIndex < context.expression().Length; exprIndex++) + + ResolveDefault(context.expression()[1], true); + + var stepStatement = context.stepStmt(); + if (stepStatement != null) { - ResolveDefault(context.expression()[exprIndex]); + Resolve(stepStatement); } } + private void Resolve(VBAParser.StepStmtContext context) + { + ResolveDefault(context.expression(), true); + } + public void Resolve(VBAParser.ForEachStmtContext context) { var firstExpression = _bindingService.ResolveDefault( @@ -664,7 +687,8 @@ public void Resolve(VBAParser.ForEachStmtContext context) _currentParent, context.expression()[0], GetInnerMostWithExpression(), - StatementResolutionContext.Undefined); + StatementResolutionContext.Undefined, + false); if (firstExpression.Classification == ExpressionClassification.ResolutionFailed) { @@ -683,15 +707,10 @@ public void Resolve(VBAParser.ForEachStmtContext context) _currentScope, _currentParent, true); - //_boundExpressionVisitor.AddIdentifierReferences( - // firstExpression, - // _qualifiedModuleName, - // _currentScope, - // _currentParent); } - for (int exprIndex = 1; exprIndex < context.expression().Length; exprIndex++) + for (var exprIndex = 1; exprIndex < context.expression().Length; exprIndex++) { - ResolveDefault(context.expression()[exprIndex]); + ResolveDefault(context.expression()[exprIndex], false); } } @@ -724,7 +743,7 @@ public void Resolve(VBAParser.RaiseEventStmtContext context) } foreach (var eventArgument in context.eventArgumentList().eventArgument()) { - ResolveDefault(eventArgument.expression()); + ResolveDefault(eventArgument.expression(), false); } } @@ -741,7 +760,7 @@ public void Resolve(VBAParser.LineSpecialFormContext context) { foreach (var expr in context.expression()) { - ResolveDefault(expr); + ResolveDefault(expr, true); } foreach (var tuple in context.tuple()) { @@ -753,7 +772,7 @@ public void Resolve(VBAParser.CircleSpecialFormContext context) { foreach (var expr in context.expression()) { - ResolveDefault(expr); + ResolveDefault(expr, true); } ResolveTuple(context.tuple()); } @@ -762,7 +781,7 @@ public void Resolve(VBAParser.ScaleSpecialFormContext context) { if (context.expression() != null) { - ResolveDefault(context.expression()); + ResolveDefault(context.expression(), true); } foreach (var tuple in context.tuple()) @@ -775,7 +794,7 @@ public void Resolve(VBAParser.PSetSpecialFormContext context) { foreach (var expr in context.expression()) { - ResolveDefault(expr); + ResolveDefault(expr, true); } ResolveTuple(context.tuple()); } @@ -784,7 +803,7 @@ private void ResolveTuple(VBAParser.TupleContext tuple) { foreach (var expr in tuple.expression()) { - ResolveDefault(expr); + ResolveDefault(expr, true); } } @@ -798,40 +817,46 @@ public void Resolve(VBAParser.EnumerationStmtContext context) { if (enumMember.expression() != null) { - ResolveDefault(enumMember.expression()); + ResolveDefault(enumMember.expression(), false); } } } public void Resolve(VBAParser.DebugPrintStmtContext context) { - if (DebugDeclarations.DebugPrint == null) + if (DebugDeclarations.DebugPrint != null) + { + // Because Debug.Print has a special argument (an output list) instead + // of normal arguments we can't treat it as a function call. + var debugPrint = DebugDeclarations.DebugPrint; + var debugModule = debugPrint.ParentDeclaration; + debugModule.AddReference( + _qualifiedModuleName, + _currentScope, + _currentParent, + context.debugPrint().debugModule(), + context.debugPrint().debugModule().GetText(), + debugModule, + context.debugPrint().debugModule().GetSelection(), + FindIdentifierAnnotations(_qualifiedModuleName, + context.debugPrint().debugModule().GetSelection().StartLine)); + debugPrint.AddReference( + _qualifiedModuleName, + _currentScope, + _currentParent, + context.debugPrint().debugPrintSub(), + context.debugPrint().debugPrintSub().GetText(), + debugPrint, + context.debugPrint().debugPrintSub().GetSelection(), + FindIdentifierAnnotations(_qualifiedModuleName, + context.debugPrint().debugPrintSub().GetSelection().StartLine)); + } + else { Logger.Warn("Debug.Print (custom declaration) has not been loaded, skipping resolving Debug.Print call."); - return; } - // Because Debug.Print has a special argument (an output list) instead - // of normal arguments we can't treat it as a function call. - var debugPrint = DebugDeclarations.DebugPrint; - var debugModule = debugPrint.ParentDeclaration; - debugModule.AddReference( - _qualifiedModuleName, - _currentScope, - _currentParent, - context.debugPrint().debugModule(), - context.debugPrint().debugModule().GetText(), - debugModule, - context.debugPrint().debugModule().GetSelection(), - FindIdentifierAnnotations(_qualifiedModuleName, context.debugPrint().debugModule().GetSelection().StartLine)); - debugPrint.AddReference( - _qualifiedModuleName, - _currentScope, - _currentParent, - context.debugPrint().debugPrintSub(), - context.debugPrint().debugPrintSub().GetText(), - debugPrint, - context.debugPrint().debugPrintSub().GetSelection(), - FindIdentifierAnnotations(_qualifiedModuleName, context.debugPrint().debugPrintSub().GetSelection().StartLine)); + + //The output list should be resolved no matter whether we have a declaration for Debug.Print or not. var outputList = context.outputList(); if (outputList != null) { diff --git a/Rubberduck.Parsing/Symbols/ModuleBodyElementDeclaration.cs b/Rubberduck.Parsing/Symbols/ModuleBodyElementDeclaration.cs index 4f86342a24..a2037c1f08 100644 --- a/Rubberduck.Parsing/Symbols/ModuleBodyElementDeclaration.cs +++ b/Rubberduck.Parsing/Symbols/ModuleBodyElementDeclaration.cs @@ -1,11 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Xml; using Antlr4.Runtime; using Rubberduck.Parsing.Annotations; -using Rubberduck.Parsing.Grammar; -using Rubberduck.Parsing.VBA; using Rubberduck.VBEditor; using static Rubberduck.Parsing.Grammar.VBAParser; diff --git a/Rubberduck.Parsing/Symbols/PropertyGetDeclaration.cs b/Rubberduck.Parsing/Symbols/PropertyGetDeclaration.cs index 5cd2a68b89..d6b094870e 100644 --- a/Rubberduck.Parsing/Symbols/PropertyGetDeclaration.cs +++ b/Rubberduck.Parsing/Symbols/PropertyGetDeclaration.cs @@ -1,7 +1,6 @@ using Antlr4.Runtime; using Rubberduck.Parsing.Annotations; using Rubberduck.Parsing.ComReflection; -using Rubberduck.Parsing.VBA; using Rubberduck.VBEditor; using System.Collections.Generic; using System.Linq; diff --git a/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs b/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs index 6499b25b03..d113f90daf 100644 --- a/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs +++ b/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs @@ -54,7 +54,6 @@ private void Visit( Visit(parenthesizedExpression, module, scope, parent); break; case LiteralExpression literalExpression: - Visit(literalExpression); break; case BinaryOpExpression binaryOpExpression: Visit(binaryOpExpression, module, scope, parent); @@ -80,7 +79,10 @@ private void Visit( case BuiltInTypeExpression builtInTypeExpression: break; case RecursiveDefaultMemberAccessExpression recursiveDefaultMemberAccessExpression: - Visit(recursiveDefaultMemberAccessExpression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); + Visit(recursiveDefaultMemberAccessExpression, module, scope, parent, hasExplicitLetStatement); + break; + case LetCoercionDefaultMemberAccessExpression letCoercionDefaultMemberAccessExpression: + Visit(letCoercionDefaultMemberAccessExpression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement); break; default: throw new NotSupportedException($"Unexpected bound expression type {boundExpression.GetType()}"); @@ -257,8 +259,8 @@ private void AddDefaultMemberReference( Declaration scope, Declaration parent, bool isAssignmentTarget, - bool isSetAssignment, - bool hasExplicitLetStatement) + bool hasExplicitLetStatement, + bool isSetAssignment) { var callSiteContext = expression.LExpression.Context; var identifier = callSiteContext.GetText(); @@ -286,8 +288,8 @@ private void AddUnboundDefaultMemberReference( Declaration scope, Declaration parent, bool isAssignmentTarget, - bool isSetAssignment, - bool hasExplicitLetStatement) + bool hasExplicitLetStatement, + bool isSetAssignment) { var callSiteContext = expression.LExpression.Context; var identifier = callSiteContext.GetText(); @@ -330,23 +332,11 @@ private void Visit( if (expression.Classification != ExpressionClassification.Unbound && expression.ReferencedDeclaration != null) { - var callSiteContext = expression.DefaultMemberContext; - var identifier = expression.ReferencedDeclaration.IdentifierName; - var callee = expression.ReferencedDeclaration; - expression.ReferencedDeclaration.AddReference( - module, - scope, - parent, - callSiteContext, - identifier, - callee, - callSiteContext.GetSelection(), - FindIdentifierAnnotations(module, callSiteContext.GetSelection().StartLine), - isAssignmentTarget, - hasExplicitLetStatement, - isSetAssignment, - isIndexedDefaultMemberAccess: true, - defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); + AddDefaultMemberReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); + } + else + { + AddUnboundDefaultMemberReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement, isSetAssignment); } // Argument List not affected by being unbound. foreach (var argument in expression.ArgumentList.Arguments) @@ -359,6 +349,64 @@ private void Visit( } } + private void AddDefaultMemberReference( + DictionaryAccessExpression expression, + QualifiedModuleName module, + Declaration scope, + Declaration parent, + bool isAssignmentTarget, + bool hasExplicitLetStatement, + bool isSetAssignment) + { + var callSiteContext = expression.DefaultMemberContext; + var identifier = expression.ReferencedDeclaration.IdentifierName; + var callee = expression.ReferencedDeclaration; + expression.ReferencedDeclaration.AddReference( + module, + scope, + parent, + callSiteContext, + identifier, + callee, + callSiteContext.GetSelection(), + FindIdentifierAnnotations(module, callSiteContext.GetSelection().StartLine), + isAssignmentTarget, + hasExplicitLetStatement, + isSetAssignment, + isIndexedDefaultMemberAccess: true, + defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); + } + + private void AddUnboundDefaultMemberReference( + DictionaryAccessExpression expression, + QualifiedModuleName module, + Declaration scope, + Declaration parent, + bool isAssignmentTarget, + bool hasExplicitLetStatement, + bool isSetAssignment) + { + var callSiteContext = expression.DefaultMemberContext; + var identifier = expression.Context.GetText(); + var selection = callSiteContext.GetSelection(); + var callee = expression.ReferencedDeclaration; + var reference = new IdentifierReference( + module, + scope, + parent, + identifier, + selection, + callSiteContext, + callee, + isAssignmentTarget, + hasExplicitLetStatement, + FindIdentifierAnnotations(module, selection.StartLine), + isSetAssignment, + isIndexedDefaultMemberAccess: true, + defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); + _declarationFinder.AddUnboundDefaultMemberAccess(reference); + } + private void Visit( RecursiveDefaultMemberAccessExpression expression, QualifiedModuleName module, @@ -375,25 +423,118 @@ private void Visit( if (expression.Classification != ExpressionClassification.Unbound && expression.ReferencedDeclaration != null) { - var callSiteContext = expression.Context; - var identifier = callSiteContext.GetText(); - var selection = callSiteContext.GetSelection(); - var callee = expression.ReferencedDeclaration; - expression.ReferencedDeclaration.AddReference( - module, - scope, - parent, - callSiteContext, - identifier, - callee, - selection, - FindIdentifierAnnotations(module, selection.StartLine), - hasExplicitLetStatement: hasExplicitLetStatement, - isNonIndexedDefaultMemberAccess: true, - defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); + AddDefaultMemberReference(expression, module, parent, scope, hasExplicitLetStatement); + } + } + + private void AddDefaultMemberReference( + RecursiveDefaultMemberAccessExpression expression, + QualifiedModuleName module, + Declaration scope, + Declaration parent, + bool hasExplicitLetStatement) + { + var callSiteContext = expression.Context; + var identifier = callSiteContext.GetText(); + var selection = callSiteContext.GetSelection(); + var callee = expression.ReferencedDeclaration; + expression.ReferencedDeclaration.AddReference( + module, + scope, + parent, + callSiteContext, + identifier, + callee, + selection, + FindIdentifierAnnotations(module, selection.StartLine), + hasExplicitLetStatement: hasExplicitLetStatement, + isNonIndexedDefaultMemberAccess: true, + defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); + } + + private void Visit( + LetCoercionDefaultMemberAccessExpression expression, + QualifiedModuleName module, + Declaration scope, + Declaration parent, + bool isAssignmentTarget = false, + bool hasExplicitLetStatement = false) + { + var containedExpression = expression.ContainedDefaultMemberRecursionExpression; + if (containedExpression != null) + { + Visit(containedExpression, module, scope, parent, hasExplicitLetStatement: hasExplicitLetStatement); + } + + Visit(expression.WrappedExpression, module, scope, parent); + + if (expression.Classification != ExpressionClassification.Unbound + && expression.ReferencedDeclaration != null) + { + AddDefaultMemberReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement); + } + else + { + AddUnboundDefaultMemberReference(expression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement); } } + private void AddDefaultMemberReference( + LetCoercionDefaultMemberAccessExpression expression, + QualifiedModuleName module, + Declaration scope, + Declaration parent, + bool isAssignmentTarget, + bool hasExplicitLetStatement) + { + var callSiteContext = expression.Context; + var identifier = callSiteContext.GetText(); + var selection = callSiteContext.GetSelection(); + var callee = expression.ReferencedDeclaration; + expression.ReferencedDeclaration.AddReference( + module, + scope, + parent, + callSiteContext, + identifier, + callee, + selection, + FindIdentifierAnnotations(module, selection.StartLine), + isAssignmentTarget, + hasExplicitLetStatement, + isNonIndexedDefaultMemberAccess: true, + defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); + } + + private void AddUnboundDefaultMemberReference( + LetCoercionDefaultMemberAccessExpression expression, + QualifiedModuleName module, + Declaration scope, + Declaration parent, + bool isAssignmentTarget, + bool hasExplicitLetStatement) + { + var callSiteContext = expression.Context; + var identifier = callSiteContext.GetText(); + var selection = callSiteContext.GetSelection(); + var callee = expression.ReferencedDeclaration; + var reference = new IdentifierReference( + module, + scope, + parent, + identifier, + selection, + callSiteContext, + callee, + isAssignmentTarget, + hasExplicitLetStatement, + FindIdentifierAnnotations(module, selection.StartLine), + false, + isNonIndexedDefaultMemberAccess: true, + defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); + _declarationFinder.AddUnboundDefaultMemberAccess(reference); + } + private void Visit( NewExpression expression, QualifiedModuleName module, @@ -443,11 +584,6 @@ private void Visit( Visit(expression.Expr, module, scope, parent); } - private void Visit(LiteralExpression expression) - { - // Nothing to do here. - } - private void Visit( InstanceExpression expression, QualifiedModuleName module, diff --git a/RubberduckTests/Grammar/ResolverTests.cs b/RubberduckTests/Grammar/ResolverTests.cs index aece46e8cb..1e9c4dc34a 100644 --- a/RubberduckTests/Grammar/ResolverTests.cs +++ b/RubberduckTests/Grammar/ResolverTests.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Threading; +using System.Windows.Controls; using NUnit.Framework; using Rubberduck.Parsing.Symbols; using Rubberduck.Parsing.VBA; @@ -4346,15 +4347,15 @@ End Function [TestCase(" Foo = Not cls.Baz", 15, 22)] [TestCase(" Bar cls.Baz", 9, 16)] [TestCase(" Baz (cls.Baz)", 10, 17)] - [TestCase(" Debug.Print cls.Baz", 9, 16)] - [TestCase(" Debug.Print 42, cls.Baz", 13, 20)] - [TestCase(" Debug.Print 42; cls.Baz", 13, 20)] - [TestCase(" Debug.Print Spc(cls.Baz)", 13, 20)] - [TestCase(" Debug.Print 42, Spc(cls.Baz)", 17, 24)] - [TestCase(" Debug.Print 42; Spc(cls.Baz)", 17, 24)] - [TestCase(" Debug.Print Tab(cls.Baz)", 13, 20)] - [TestCase(" Debug.Print 42, Tab(cls.Baz)", 17, 24)] - [TestCase(" Debug.Print 42; Tab(cls.Baz)", 17, 24)] + [TestCase(" Debug.Print cls.Baz", 17, 24)] + [TestCase(" Debug.Print 42, cls.Baz", 21, 28)] + [TestCase(" Debug.Print 42; cls.Baz", 21, 28)] + [TestCase(" Debug.Print Spc(cls.Baz)", 21, 28)] + [TestCase(" Debug.Print 42, Spc(cls.Baz)", 25, 32)] + [TestCase(" Debug.Print 42; Spc(cls.Baz)", 25, 32)] + [TestCase(" Debug.Print Tab(cls.Baz)", 21, 28)] + [TestCase(" Debug.Print 42, Tab(cls.Baz)", 25, 32)] + [TestCase(" Debug.Print 42; Tab(cls.Baz)", 25, 32)] [TestCase(" If cls.Baz Then Foo = 42", 8, 15)] [TestCase(" If cls.Baz Then \r\n Foo = 42 \r\n End If", 8, 15)] [TestCase(" If False Then : ElseIf cls.Baz Then\r\n Foo = 42 \r\n End If", 28, 35)] @@ -4397,9 +4398,9 @@ End Function [TestCase(" ReDim fooBar(23 To 42, 23 To cls.Baz)", 34, 41)] [TestCase(" ReDim fooBar(cls.Baz)", 18, 25)] [TestCase(" ReDim fooBar(42, cls.Baz)", 22, 29)] - [TestCase(" Mid fooBar, cls.Baz, 42 = \"Hello\"", 28, 35)] - [TestCase(" Mid fooBar, 23, cls.Baz = \"Hello\"", 32, 39)] - [TestCase(" Mid fooBar, 23, 42 = cls.Baz", 37, 44)] + [TestCase(" Mid(fooBar, cls.Baz, 42) = \"Hello\"", 17, 24)] + [TestCase(" Mid(fooBar, 23, cls.Baz) = \"Hello\"", 21, 28)] + [TestCase(" Mid(fooBar, 23, 42) = cls.Baz", 27, 34)] [TestCase(" LSet fooBar = cls.Baz", 19, 26)] [TestCase(" RSet fooBar = cls.Baz", 19, 26)] [TestCase(" Error cls.Baz", 11, 18)] @@ -4409,8 +4410,6 @@ End Function [TestCase(" Open \"somePath\" As 23 Len = cls.Baz", 33, 40)] [TestCase(" Close cls.Baz, 23", 11, 18)] [TestCase(" Close 23, #cls.Baz, 23", 16, 23)] - [TestCase(" Reset cls.Baz, 23", 11, 18)] - [TestCase(" Reset 23, #cls.Baz, 23", 16, 23)] [TestCase(" Seek cls.Baz, 23", 10, 17)] [TestCase(" Seek #cls.Baz, 23", 11, 18)] [TestCase(" Seek 23, cls.Baz", 14, 21)] @@ -4435,13 +4434,15 @@ End Function [TestCase(" Print #23, Tab(cls.Baz)", 20, 27)] [TestCase(" Print #23, 42, Tab(cls.Baz)", 24, 31)] [TestCase(" Print #23, 42; Tab(cls.Baz)", 24, 31)] - [TestCase(" Input #cls.Baz, fooBar", 12, 17)] + [TestCase(" Input #cls.Baz, fooBar", 12, 19)] [TestCase(" Put cls.Baz, 42, fooBar", 9, 16)] [TestCase(" Put #cls.Baz, 42, fooBar", 10, 17)] [TestCase(" Put 42, cls.Baz, fooBar", 13, 20)] [TestCase(" Get cls.Baz, 42, fooBar", 9, 16)] [TestCase(" Get #cls.Baz, 42, fooBar", 10, 17)] [TestCase(" Get 42, cls.Baz, fooBar", 13, 20)] + [TestCase(" Name \"somePath\" As cls.Baz", 24, 31)] + [TestCase(" Name cls.Baz As \"somePath\"", 10, 17)] public void LetCoercionDefaultMemberAccessHasReferenceToDefaultMemberOnEntireContext(string statement, int selectionStartColumn, int selectionEndColumn) { var class1Code = @" From 28fa90f43394749d0f10351342383c8a637005d9 Mon Sep 17 00:00:00 2001 From: Max Doerner Date: Tue, 20 Aug 2019 02:15:04 +0200 Subject: [PATCH 08/32] Add let coercion default member access resolution (part 2) This commit finishes the work started in part 1. There are still failing tests for procedure coercion and new failed test for functionalities working based on the old assumptions that there is no let coercion resolution. --- .../Binding/ArgumentListArgument.cs | 70 ++++++++++----- .../DictionaryAccessDefaultBinding.cs | 5 +- .../Binding/Bindings/IndexDefaultBinding.cs | 5 +- .../Binding/DefaultBindingContext.cs | 88 ++++++++++++------- .../Symbols/IdentifierReferenceResolver.cs | 10 +-- .../DeclarationCaching/DeclarationFinder.cs | 12 +++ RubberduckTests/Grammar/ResolverTests.cs | 22 ++--- 7 files changed, 135 insertions(+), 77 deletions(-) diff --git a/Rubberduck.Parsing/Binding/ArgumentListArgument.cs b/Rubberduck.Parsing/Binding/ArgumentListArgument.cs index f75bbc6329..70b8ce372d 100644 --- a/Rubberduck.Parsing/Binding/ArgumentListArgument.cs +++ b/Rubberduck.Parsing/Binding/ArgumentListArgument.cs @@ -1,59 +1,81 @@ using Rubberduck.Parsing.Symbols; using System; +using System.Linq; +using Antlr4.Runtime; +using Rubberduck.Parsing.Grammar; namespace Rubberduck.Parsing.Binding { public sealed class ArgumentListArgument { private readonly IExpressionBinding _binding; - private IBoundExpression _expression; - private IBoundExpression _namedArgumentExpression; - private readonly ArgumentListArgumentType _argumentType; + private readonly ParserRuleContext _context; private readonly Func _namedArgumentExpressionCreator; + private readonly bool _isAddressOfArgument; - public ArgumentListArgument(IExpressionBinding binding, ArgumentListArgumentType argumentType) - : this (binding, argumentType, calledProcedure => null) + public ArgumentListArgument(IExpressionBinding binding, ParserRuleContext context, ArgumentListArgumentType argumentType, bool isAddressOfArgument = false) + : this (binding, context, argumentType, calledProcedure => null, isAddressOfArgument) { } - public ArgumentListArgument(IExpressionBinding binding, ArgumentListArgumentType argumentType, Func namedArgumentExpressionCreator) + public ArgumentListArgument(IExpressionBinding binding, ParserRuleContext context, ArgumentListArgumentType argumentType, Func namedArgumentExpressionCreator, bool isAddressOfArgument = false) { _binding = binding; - _argumentType = argumentType; + _context = context; + ArgumentType = argumentType; _namedArgumentExpressionCreator = namedArgumentExpressionCreator; + _isAddressOfArgument = isAddressOfArgument; } - public ArgumentListArgumentType ArgumentType + public ArgumentListArgumentType ArgumentType { get; } + public IBoundExpression NamedArgumentExpression { get; private set; } + public IBoundExpression Expression { get; private set; } + + public void Resolve(Declaration calledProcedure, int parameterIndex) { - get + var binding = _binding; + if (calledProcedure != null) { - return _argumentType; + NamedArgumentExpression = _namedArgumentExpressionCreator(calledProcedure); + + if (!_isAddressOfArgument && !CanBeObject(calledProcedure, parameterIndex)) + { + binding = new LetCoercionDefaultBinding(_context, binding); + } } + + Expression = binding.Resolve(); } - public IBoundExpression NamedArgumentExpression + private bool CanBeObject(Declaration calledProcedure, int parameterIndex) { - get + if (NamedArgumentExpression != null) { - return _namedArgumentExpression; + var correspondingParameter = NamedArgumentExpression.ReferencedDeclaration as ParameterDeclaration; + return CanBeObject(correspondingParameter); } - } - public IBoundExpression Expression - { - get + if (parameterIndex >= 0 && calledProcedure is IParameterizedDeclaration parameterizedDeclaration) { - return _expression; + var parameters = parameterizedDeclaration.Parameters.ToList(); + if (parameterIndex >= parameters.Count) + { + return parameters.Any(param => param.IsParamArray); + } + + var correspondingParameter = parameters[parameterIndex]; + return CanBeObject(correspondingParameter); + } + + return true; } - public void Resolve(Declaration calledProcedure) + private bool CanBeObject(ParameterDeclaration parameter) { - _expression = _binding.Resolve(); - if (calledProcedure != null) - { - _namedArgumentExpression = _namedArgumentExpressionCreator(calledProcedure); - } + return parameter.IsObject + || Tokens.Variant.Equals(parameter.AsTypeName, StringComparison.InvariantCultureIgnoreCase) + && (!parameter.IsArray || parameter.IsParamArray); } } } diff --git a/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs b/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs index be96e84c13..0ee5c89133 100644 --- a/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs @@ -42,9 +42,10 @@ public DictionaryAccessDefaultBinding( private static void ResolveArgumentList(Declaration calledProcedure, ArgumentList argumentList) { - foreach (var argument in argumentList.Arguments) + var arguments = argumentList.Arguments; + for (var index = 0; index < arguments.Count; index++) { - argument.Resolve(calledProcedure); + arguments[index].Resolve(calledProcedure, index); } } diff --git a/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs b/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs index b595309329..60c22e3b03 100644 --- a/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs @@ -46,9 +46,10 @@ public IndexDefaultBinding( private static void ResolveArgumentList(Declaration calledProcedure, ArgumentList argumentList) { - foreach (var argument in argumentList.Arguments) + var arguments = argumentList.Arguments; + for (var index = 0; index < arguments.Count; index++) { - argument.Resolve(calledProcedure); + arguments[index].Resolve(calledProcedure, index); } } diff --git a/Rubberduck.Parsing/Binding/DefaultBindingContext.cs b/Rubberduck.Parsing/Binding/DefaultBindingContext.cs index a933fd5561..651d024404 100644 --- a/Rubberduck.Parsing/Binding/DefaultBindingContext.cs +++ b/Rubberduck.Parsing/Binding/DefaultBindingContext.cs @@ -49,6 +49,8 @@ public IExpressionBinding BuildTree(Declaration module, Declaration parent, IPar return Visit(module, parent, callExpression, withBlockVariable); case VBAParser.BooleanExpressionContext booleanExpressionContext: return Visit(module, parent, booleanExpressionContext, withBlockVariable); + case VBAParser.IntegerExpressionContext integerExpressionContext: + return Visit(module, parent, integerExpressionContext, withBlockVariable); default: throw new NotSupportedException($"Unexpected context type {expression.GetType()}"); } @@ -242,14 +244,18 @@ private ArgumentList VisitArgumentList(Declaration module, Declaration parent, V { if (expr.positionalArgument() != null) { - convertedList.AddArgument(new ArgumentListArgument( - VisitArgumentBinding(module, parent, expr.positionalArgument().argumentExpression(), withBlockVariable), ArgumentListArgumentType.Positional)); + var (binding, context, isAddressOfArgument) = VisitArgumentBinding(module, parent, expr.positionalArgument().argumentExpression(), withBlockVariable); + convertedList.AddArgument(new ArgumentListArgument(binding, context, ArgumentListArgumentType.Positional, isAddressOfArgument)); } else if (expr.namedArgument() != null) { + var (binding, context, isAddressOfArgument) = VisitArgumentBinding(module, parent, expr.namedArgument().argumentExpression(), withBlockVariable); convertedList.AddArgument(new ArgumentListArgument( - VisitArgumentBinding(module, parent, expr.namedArgument().argumentExpression(), withBlockVariable), ArgumentListArgumentType.Named, - CreateNamedArgumentExpressionCreator(expr.namedArgument().unrestrictedIdentifier().GetText(), expr.namedArgument().unrestrictedIdentifier()))); + binding, + context, + ArgumentListArgumentType.Named, + CreateNamedArgumentExpressionCreator(expr.namedArgument().unrestrictedIdentifier().GetText(), expr.namedArgument().unrestrictedIdentifier()), + isAddressOfArgument)); } } } @@ -260,40 +266,40 @@ private Func CreateNamedArgumentExpressionCreator { return calledProcedure => { - ExpressionClassification classification; - if (calledProcedure.DeclarationType == DeclarationType.Procedure) - { - classification = ExpressionClassification.Subroutine; - } - else if (calledProcedure.DeclarationType == DeclarationType.Function || calledProcedure.DeclarationType == DeclarationType.LibraryFunction || calledProcedure.DeclarationType == DeclarationType.LibraryProcedure) - { - classification = ExpressionClassification.Function; - } - else - { - classification = ExpressionClassification.Property; - } + var classification = ExpressionClassificationOfProcedure(calledProcedure); var parameter = _declarationFinder.FindParameter(calledProcedure, parameterName); - if (parameter != null) - { - return new SimpleNameExpression(parameter, classification, context); - } - - return null; + return parameter != null + ? new SimpleNameExpression(parameter, classification, context) + : null; }; } - private IExpressionBinding VisitArgumentBinding(Declaration module, Declaration parent, VBAParser.ArgumentExpressionContext argumentExpression, IBoundExpression withBlockVariable) + private static ExpressionClassification ExpressionClassificationOfProcedure(Declaration procedure) + { + switch (procedure.DeclarationType) + { + case DeclarationType.Procedure: + return ExpressionClassification.Subroutine; + case DeclarationType.Function: + case DeclarationType.LibraryFunction: + case DeclarationType.LibraryProcedure: + return ExpressionClassification.Function; + default: + return ExpressionClassification.Property; + } + } + + private (IExpressionBinding binding, ParserRuleContext context, bool isAddressOfArgument) VisitArgumentBinding(Declaration module, Declaration parent, VBAParser.ArgumentExpressionContext argumentExpression, IBoundExpression withBlockVariable) { if (argumentExpression.expression() != null) { var expr = argumentExpression.expression(); - return Visit(module, parent, expr, withBlockVariable, StatementResolutionContext.Undefined); + return (Visit(module, parent, expr, withBlockVariable, StatementResolutionContext.Undefined), expr, false); } else { var expr = argumentExpression.addressOfExpression(); - return Visit(module, parent, expr, withBlockVariable, StatementResolutionContext.Undefined); + return (Visit(module, parent, expr, withBlockVariable, StatementResolutionContext.Undefined), expr, true); } } @@ -319,7 +325,7 @@ declared type of String and a value equal to the name value of FindIdentifierAnnotations(QualifiedModuleName m private void ResolveDefault( ParserRuleContext expression, - bool requiresLetCoercion, + bool requiresLetCoercion = false, StatementResolutionContext statementContext = StatementResolutionContext.Undefined, bool isAssignmentTarget = false, bool hasExplicitLetStatement = false, @@ -314,12 +314,12 @@ public void Resolve(VBAParser.DoLoopStmtContext context) public void Resolve(VBAParser.IfStmtContext context) { - ResolveDefault(context.booleanExpression(), true); + ResolveDefault(context.booleanExpression()); if (context.elseIfBlock() != null) { foreach (var elseIfBlock in context.elseIfBlock()) { - ResolveDefault(elseIfBlock.booleanExpression(), true); + ResolveDefault(elseIfBlock.booleanExpression()); } } } @@ -330,12 +330,12 @@ public void Resolve(VBAParser.SingleLineIfStmtContext context) // single-line-if-statements, we do it here for better understanding. if (context.ifWithEmptyThen() != null) { - ResolveDefault(context.ifWithEmptyThen().booleanExpression(), true); + ResolveDefault(context.ifWithEmptyThen().booleanExpression()); ResolveListOrLabel(context.ifWithEmptyThen().singleLineElseClause().listOrLabel()); } else { - ResolveDefault(context.ifWithNonEmptyThen().booleanExpression(), true); + ResolveDefault(context.ifWithNonEmptyThen().booleanExpression()); ResolveListOrLabel(context.ifWithNonEmptyThen().listOrLabel()); if (context.ifWithNonEmptyThen().singleLineElseClause() != null) { diff --git a/Rubberduck.Parsing/VBA/DeclarationCaching/DeclarationFinder.cs b/Rubberduck.Parsing/VBA/DeclarationCaching/DeclarationFinder.cs index 967f092848..a62735cba9 100644 --- a/Rubberduck.Parsing/VBA/DeclarationCaching/DeclarationFinder.cs +++ b/Rubberduck.Parsing/VBA/DeclarationCaching/DeclarationFinder.cs @@ -516,6 +516,18 @@ public ParameterDeclaration FindParameter(Declaration parameterizedMember, strin : null; } + /// + /// Returns the parameter at index parameterIndex (0-based) + /// + public ParameterDeclaration FindParameter(Declaration parameterizedMember, int parameterIndex) + { + return parameterIndex >= 0 + && _parametersByParent.TryGetValue(parameterizedMember, out List parameters) + && parameterIndex < parameters.Count + ? parameters[parameterIndex] + : null; + } + public IEnumerable Parameters(Declaration parameterizedMember) { return _parametersByParent.TryGetValue(parameterizedMember, out List result) diff --git a/RubberduckTests/Grammar/ResolverTests.cs b/RubberduckTests/Grammar/ResolverTests.cs index 1e9c4dc34a..f247fc1e3d 100644 --- a/RubberduckTests/Grammar/ResolverTests.cs +++ b/RubberduckTests/Grammar/ResolverTests.cs @@ -4325,7 +4325,7 @@ End Function [TestCase(" Foo = 42 * cls.Baz", 16, 23)] [TestCase(" Foo = 42 - cls.Baz", 16, 23)] [TestCase(" Foo = 42 ^ cls.Baz", 16, 23)] - [TestCase(" Foo = 42 \\ cls.Baz", 17, 24)] + [TestCase(" Foo = 42 \\ cls.Baz", 16, 23)] [TestCase(" Foo = 42 Mod cls.Baz", 18, 25)] [TestCase(" Foo = \"sheep\" & cls.Baz", 21, 28)] [TestCase(" Foo = 42 And cls.Baz", 18, 25)] @@ -4333,15 +4333,15 @@ End Function [TestCase(" Foo = 42 Xor cls.Baz", 18, 25)] [TestCase(" Foo = 42 Eqv cls.Baz", 18, 25)] [TestCase(" Foo = 42 Imp cls.Baz", 18, 25)] - [TestCase(" Foo = 42 = cls.Baz", 17, 24)] - [TestCase(" Foo = 42 < cls.Baz", 17, 24)] - [TestCase(" Foo = 42 > cls.Baz", 17, 24)] - [TestCase(" Foo = 42 <= cls.Baz", 18, 25)] - [TestCase(" Foo = 42 =< cls.Baz", 18, 25)] - [TestCase(" Foo = 42 >= cls.Baz", 18, 25)] - [TestCase(" Foo = 42 => cls.Baz", 18, 25)] - [TestCase(" Foo = 42 <> cls.Baz", 18, 25)] - [TestCase(" Foo = 42 >< cls.Baz", 18, 25)] + [TestCase(" Foo = 42 = cls.Baz", 16, 23)] + [TestCase(" Foo = 42 < cls.Baz", 16, 23)] + [TestCase(" Foo = 42 > cls.Baz", 16, 23)] + [TestCase(" Foo = 42 <= cls.Baz", 17, 24)] + [TestCase(" Foo = 42 =< cls.Baz", 17, 24)] + [TestCase(" Foo = 42 >= cls.Baz", 17, 24)] + [TestCase(" Foo = 42 => cls.Baz", 17, 24)] + [TestCase(" Foo = 42 <> cls.Baz", 17, 24)] + [TestCase(" Foo = 42 >< cls.Baz", 17, 24)] [TestCase(" Foo = \"Hello\" Like cls.Baz", 24, 31)] [TestCase(" Foo = -cls.Baz", 12, 19)] [TestCase(" Foo = Not cls.Baz", 15, 22)] @@ -4559,6 +4559,8 @@ End Sub [TestCase(" Baz cls.Baz", 18, 25)] [TestCase(" For Each cls In fooBar : Foo = 42 : Next", 14, 17)] [TestCase(" For Each fooBar In cls.Baz : Foo = 42 : Next", 24, 31)] + [TestCase(" Foo = cls.Baz Is fooBar", 11, 18)] + [TestCase(" Foo = fooBar Is cls.Baz", 21, 28)] public void NonLetCoercionExpressionHasNoDefaultMemberAccess(string statement, int selectionStartColumn, int selectionEndColumn) { var class1Code = @" From 50aed01a06747a6e0542c2985a6bfa62db188c6e Mon Sep 17 00:00:00 2001 From: Max Doerner Date: Wed, 21 Aug 2019 01:57:41 +0200 Subject: [PATCH 09/32] Add procedure coercion default member access resolution There are still failing tests for functionalities working based on the old assumptions that there is no let or procedure coercion resolution. --- .../DictionaryAccessDefaultBinding.cs | 2 +- .../Binding/Bindings/IndexDefaultBinding.cs | 16 +- .../ProcedureCoercionDefaultBinding.cs | 140 ++++++++++++++++++ .../Binding/DefaultBindingContext.cs | 18 +-- .../ProcedureCoercionExpression.cs | 27 ++++ .../BoundExpressionVisitor.cs | 125 ++++++++++++---- RubberduckTests/Grammar/ResolverTests.cs | 129 ++++++++++++++-- 7 files changed, 401 insertions(+), 56 deletions(-) create mode 100644 Rubberduck.Parsing/Binding/Bindings/ProcedureCoercionDefaultBinding.cs create mode 100644 Rubberduck.Parsing/Binding/Expressions/ProcedureCoercionExpression.cs diff --git a/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs b/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs index 0ee5c89133..9c8a1726f9 100644 --- a/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs @@ -147,7 +147,7 @@ declared type. return new DictionaryAccessExpression(defaultMember, defaultMemberClassification, expression, lExpression, argumentList, defaultMemberContext, recursionDepth, containedExpression); } - if (parameters.Count(param => !param.IsOptional) == 0 + if (parameters.All(param => param.IsOptional) && DEFAULT_MEMBER_RECURSION_LIMIT >= recursionDepth) { /* diff --git a/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs b/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs index 60c22e3b03..8e34d0efd8 100644 --- a/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs @@ -63,7 +63,7 @@ public IBoundExpression Resolve() return Resolve(_lExpression, _argumentList, _expression); } - private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth = 1, RecursiveDefaultMemberAccessExpression containedExpression = null) + private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth = 0, RecursiveDefaultMemberAccessExpression containedExpression = null) { if (lExpression.Classification == ExpressionClassification.ResolutionFailed) { @@ -157,7 +157,7 @@ with a parameter list that cannot accept any parameters and an t var asTypeName = indexedDeclaration.AsTypeName; var asTypeDeclaration = indexedDeclaration.AsTypeDeclaration; - return ResolveDefaultMember(asTypeName, asTypeDeclaration, argumentList, expression, defaultMemberResolutionRecursionDepth, containedExpression); + return ResolveDefaultMember(asTypeName, asTypeDeclaration, argumentList, expression, defaultMemberResolutionRecursionDepth + 1, containedExpression); } private bool IsVariablePropertyFunctionWithoutParameters(IBoundExpression lExpression) @@ -198,7 +198,7 @@ private IBoundExpression ResolveLExpressionIsIndexExpression(IndexExpression ind var asTypeName = indexedDeclaration.AsTypeName; var asTypeDeclaration = indexedDeclaration.AsTypeDeclaration; - return ResolveDefaultMember(asTypeName, asTypeDeclaration, argumentList, expression, defaultMemberResolutionRecursionDepth, containedExpression); + return ResolveDefaultMember(asTypeName, asTypeDeclaration, argumentList, expression, defaultMemberResolutionRecursionDepth + 1, containedExpression); } private IBoundExpression ResolveLExpressionIsDictionaryAccessExpression(DictionaryAccessExpression dictionaryAccessExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) @@ -219,7 +219,7 @@ private IBoundExpression ResolveLExpressionIsDictionaryAccessExpression(Dictiona var asTypeName = indexedDeclaration.AsTypeName; var asTypeDeclaration = indexedDeclaration.AsTypeDeclaration; - return ResolveDefaultMember(asTypeName, asTypeDeclaration, argumentList, expression, defaultMemberResolutionRecursionDepth, containedExpression); + return ResolveDefaultMember(asTypeName, asTypeDeclaration, argumentList, expression, defaultMemberResolutionRecursionDepth + 1, containedExpression); } private IBoundExpression ResolveDefaultMember(string asTypeName, Declaration asTypeDeclaration, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) @@ -267,7 +267,7 @@ declared type. recursively, as if this default member was specified instead for with the same . */ - if (parameters.Count(parameter => !parameter.IsOptional) == 0 + if (parameters.All(parameter => parameter.IsOptional) && DEFAULT_MEMBER_RECURSION_LIMIT >= defaultMemberResolutionRecursionDepth) { return ResolveRecursiveDefaultMember(defaultMember, defaultMemberClassification, argumentList, expression, defaultMemberResolutionRecursionDepth, containedExpression); @@ -279,9 +279,9 @@ declared type. private static bool ArgumentListIsCompatible(ICollection parameters, ArgumentList argumentList) { - return (parameters.Count >= argumentList.Arguments.Count + return (parameters.Count >= (argumentList?.Arguments.Count ?? 0) || parameters.Any(parameter => parameter.IsParamArray)) - && parameters.Count(parameter => !parameter.IsOptional) <= argumentList.Arguments.Count; + && parameters.Count(parameter => !parameter.IsOptional) <= (argumentList?.Arguments.Count ?? 0); } private IBoundExpression ResolveRecursiveDefaultMember(Declaration defaultMember, ExpressionClassification defaultMemberClassification, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) @@ -289,7 +289,7 @@ private IBoundExpression ResolveRecursiveDefaultMember(Declaration defaultMember var defaultMemberRecursionExpression = new RecursiveDefaultMemberAccessExpression(defaultMember, defaultMemberClassification, _lExpression.Context, defaultMemberResolutionRecursionDepth, containedExpression); var defaultMemberAsLExpression = new SimpleNameExpression(defaultMember, defaultMemberClassification, expression); - return Resolve(defaultMemberAsLExpression, argumentList, expression, defaultMemberResolutionRecursionDepth + 1, defaultMemberRecursionExpression); + return Resolve(defaultMemberAsLExpression, argumentList, expression, defaultMemberResolutionRecursionDepth, defaultMemberRecursionExpression); } private ExpressionClassification DefaultMemberExpressionClassification(Declaration defaultMember) diff --git a/Rubberduck.Parsing/Binding/Bindings/ProcedureCoercionDefaultBinding.cs b/Rubberduck.Parsing/Binding/Bindings/ProcedureCoercionDefaultBinding.cs new file mode 100644 index 0000000000..bb043f4642 --- /dev/null +++ b/Rubberduck.Parsing/Binding/Bindings/ProcedureCoercionDefaultBinding.cs @@ -0,0 +1,140 @@ +using System; +using Rubberduck.Parsing.Symbols; +using System.Linq; +using Antlr4.Runtime; +using Rubberduck.Parsing.Grammar; + +namespace Rubberduck.Parsing.Binding +{ + public sealed class ProcedureCoercionDefaultBinding : IExpressionBinding + { + private readonly ParserRuleContext _expression; + private readonly IExpressionBinding _wrappedExpressionBinding; + private IBoundExpression _wrappedExpression; + + //This is a wrapper used to model procedure coercion in call statements without arguments. + //The one with arguments is basically an index expression and uses its binding. + + public ProcedureCoercionDefaultBinding( + ParserRuleContext expression, + IExpressionBinding wrappedExpressionBinding) + : this( + expression, + (IBoundExpression)null) + { + _wrappedExpressionBinding = wrappedExpressionBinding; + } + + public ProcedureCoercionDefaultBinding( + ParserRuleContext expression, + IBoundExpression wrappedExpression) + { + _expression = expression; + _wrappedExpression = wrappedExpression; + } + + public IBoundExpression Resolve() + { + if (_wrappedExpressionBinding != null) + { + _wrappedExpression = _wrappedExpressionBinding.Resolve(); + } + + return Resolve(_wrappedExpression, _expression); + } + + private static IBoundExpression Resolve(IBoundExpression wrappedExpression, ParserRuleContext expression) + { + //Procedure coercion only happens for expressions classified as variables. + if (wrappedExpression.Classification != ExpressionClassification.Variable) + { + return wrappedExpression; + } + + var wrappedDeclaration = wrappedExpression.ReferencedDeclaration; + if (wrappedDeclaration == null + || !wrappedDeclaration.IsObject + && !(wrappedDeclaration.IsObjectArray + && wrappedExpression is IndexExpression indexExpression + && indexExpression.IsArrayAccess)) + { + return wrappedExpression; + } + + //The wrapped declaration is of a specific class type or Object. + + var asTypeName = wrappedDeclaration.AsTypeName; + var asTypeDeclaration = wrappedDeclaration.AsTypeDeclaration; + + return ResolveViaDefaultMember(wrappedExpression, asTypeName, asTypeDeclaration, expression); + } + + private static IBoundExpression CreateFailedExpression(IBoundExpression lExpression) + { + var failedExpr = new ResolutionFailedExpression(); + failedExpr.AddSuccessfullyResolvedExpression(lExpression); + return failedExpr; + } + + private static IBoundExpression ResolveViaDefaultMember(IBoundExpression wrappedExpression, string asTypeName, Declaration asTypeDeclaration, ParserRuleContext expression) + { + if (Tokens.Variant.Equals(asTypeName, StringComparison.InvariantCultureIgnoreCase) + || Tokens.Object.Equals(asTypeName, StringComparison.InvariantCultureIgnoreCase)) + { + // We cannot know the the default member in this case, so return an unbound member call. + return new ProcedureCoercionExpression(null, ExpressionClassification.Unbound, expression, wrappedExpression); + } + + var defaultMember = (asTypeDeclaration as ClassModuleDeclaration)?.DefaultMember; + if (defaultMember == null + || !IsPropertyGetLetFunctionProcedure(defaultMember) + || !IsPublic(defaultMember)) + { + return CreateFailedExpression(wrappedExpression); + } + + var defaultMemberClassification = DefaultMemberClassification(defaultMember); + + var parameters = ((IParameterizedDeclaration)defaultMember).Parameters.ToList(); + if (parameters.All(parameter => parameter.IsOptional)) + { + //We found some default member accepting the empty argument list. So, we are done. + return new ProcedureCoercionExpression(defaultMember, defaultMemberClassification, expression, wrappedExpression); + } + + return CreateFailedExpression(wrappedExpression); + } + + private static bool IsPropertyGetLetFunctionProcedure(Declaration declaration) + { + var declarationType = declaration.DeclarationType; + return declarationType == DeclarationType.PropertyGet + || declarationType == DeclarationType.PropertyLet + || declarationType == DeclarationType.Function + || declarationType == DeclarationType.Procedure; + } + + private static bool IsPublic(Declaration declaration) + { + var accessibility = declaration.Accessibility; + return accessibility == Accessibility.Global + || accessibility == Accessibility.Implicit + || accessibility == Accessibility.Public; + } + + private static ExpressionClassification DefaultMemberClassification(Declaration defaultMember) + { + if (defaultMember.DeclarationType.HasFlag(DeclarationType.Property)) + { + return ExpressionClassification.Property; + } + + if (defaultMember.DeclarationType == DeclarationType.Procedure) + { + return ExpressionClassification.Subroutine; + } + + return ExpressionClassification.Function; + } + } +} \ No newline at end of file diff --git a/Rubberduck.Parsing/Binding/DefaultBindingContext.cs b/Rubberduck.Parsing/Binding/DefaultBindingContext.cs index 651d024404..c5fbd5755e 100644 --- a/Rubberduck.Parsing/Binding/DefaultBindingContext.cs +++ b/Rubberduck.Parsing/Binding/DefaultBindingContext.cs @@ -58,22 +58,20 @@ public IExpressionBinding BuildTree(Declaration module, Declaration parent, IPar private IExpressionBinding Visit(Declaration module, Declaration parent, VBAParser.CallStmtContext expression, IBoundExpression withBlockVariable) { - // Call statements always have an argument list. - // One of the reasons we're doing this is that an empty argument list could represent a call to a default member, - // which requires us to use an IndexDefaultBinding. var lExpression = expression.lExpression(); var lExpressionBinding = Visit(module, parent, lExpression, withBlockVariable, StatementResolutionContext.Undefined); - if (expression.CALL() != null) + if (expression.CALL() == null) { - return lExpressionBinding is IndexDefaultBinding indexDefaultBinding - ? indexDefaultBinding - : new IndexDefaultBinding(expression.lExpression(), lExpressionBinding, new ArgumentList()); + var argList = VisitArgumentList(module, parent, expression.argumentList(), withBlockVariable); + SetLeftMatch(lExpressionBinding, argList.Arguments.Count); + if (argList.HasArguments) + { + return new IndexDefaultBinding(expression.lExpression(), lExpressionBinding, argList); + } } - var argList = VisitArgumentList(module, parent, expression.argumentList(), withBlockVariable); - SetLeftMatch(lExpressionBinding, argList.Arguments.Count); - return new IndexDefaultBinding(expression.lExpression(), lExpressionBinding, argList); + return new ProcedureCoercionDefaultBinding(expression.lExpression(), lExpressionBinding); } private static void SetLeftMatch(IExpressionBinding binding, int argumentCount) diff --git a/Rubberduck.Parsing/Binding/Expressions/ProcedureCoercionExpression.cs b/Rubberduck.Parsing/Binding/Expressions/ProcedureCoercionExpression.cs new file mode 100644 index 0000000000..dd0abf1b3c --- /dev/null +++ b/Rubberduck.Parsing/Binding/Expressions/ProcedureCoercionExpression.cs @@ -0,0 +1,27 @@ +using Antlr4.Runtime; +using Rubberduck.Parsing.Symbols; + +namespace Rubberduck.Parsing.Binding +{ + public class ProcedureCoercionExpression : BoundExpression + { + public ProcedureCoercionExpression( + Declaration referencedDeclaration, + ExpressionClassification classification, + ParserRuleContext context, + IBoundExpression wrappedExpression) + : base(referencedDeclaration, classification, context) + { + WrappedExpression = wrappedExpression; + + //This works around a problem with the ordering of references between array accesses on (recursive) default member accesses + //and from subsequent procedure coercion. + DefaultMemberRecursionDepth = wrappedExpression is IndexExpression indexExpression + ? indexExpression.DefaultMemberRecursionDepth + 1 + : 1; + } + + public IBoundExpression WrappedExpression { get; } + public int DefaultMemberRecursionDepth { get; } + } +} \ No newline at end of file diff --git a/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs b/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs index d113f90daf..164660a07e 100644 --- a/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs +++ b/Rubberduck.Parsing/VBA/ReferenceManagement/BoundExpressionVisitor.cs @@ -84,6 +84,9 @@ private void Visit( case LetCoercionDefaultMemberAccessExpression letCoercionDefaultMemberAccessExpression: Visit(letCoercionDefaultMemberAccessExpression, module, scope, parent, isAssignmentTarget, hasExplicitLetStatement); break; + case ProcedureCoercionExpression procedureCoercionExpression: + Visit(procedureCoercionExpression, module, scope, parent); + break; default: throw new NotSupportedException($"Unexpected bound expression type {boundExpression.GetType()}"); } @@ -345,21 +348,22 @@ private void Visit( { Visit(argument.Expression, module, scope, parent); } - //Dictionary access arguments cannot be named. + if (argument.NamedArgumentExpression != null) + { + Visit(argument.NamedArgumentExpression, module, scope, parent); + } } } private void AddDefaultMemberReference( - DictionaryAccessExpression expression, + ProcedureCoercionExpression expression, QualifiedModuleName module, Declaration scope, - Declaration parent, - bool isAssignmentTarget, - bool hasExplicitLetStatement, - bool isSetAssignment) + Declaration parent) { - var callSiteContext = expression.DefaultMemberContext; - var identifier = expression.ReferencedDeclaration.IdentifierName; + var callSiteContext = expression.Context; + var identifier = callSiteContext.GetText(); + var selection = callSiteContext.GetSelection(); var callee = expression.ReferencedDeclaration; expression.ReferencedDeclaration.AddReference( module, @@ -368,26 +372,20 @@ private void AddDefaultMemberReference( callSiteContext, identifier, callee, - callSiteContext.GetSelection(), - FindIdentifierAnnotations(module, callSiteContext.GetSelection().StartLine), - isAssignmentTarget, - hasExplicitLetStatement, - isSetAssignment, - isIndexedDefaultMemberAccess: true, + selection, + FindIdentifierAnnotations(module, selection.StartLine), + isNonIndexedDefaultMemberAccess: true, defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); } private void AddUnboundDefaultMemberReference( - DictionaryAccessExpression expression, + ProcedureCoercionExpression expression, QualifiedModuleName module, Declaration scope, - Declaration parent, - bool isAssignmentTarget, - bool hasExplicitLetStatement, - bool isSetAssignment) + Declaration parent) { - var callSiteContext = expression.DefaultMemberContext; - var identifier = expression.Context.GetText(); + var callSiteContext = expression.Context; + var identifier = callSiteContext.GetText(); var selection = callSiteContext.GetSelection(); var callee = expression.ReferencedDeclaration; var reference = new IdentifierReference( @@ -398,11 +396,11 @@ private void AddUnboundDefaultMemberReference( selection, callSiteContext, callee, - isAssignmentTarget, - hasExplicitLetStatement, + false, + false, FindIdentifierAnnotations(module, selection.StartLine), - isSetAssignment, - isIndexedDefaultMemberAccess: true, + false, + isNonIndexedDefaultMemberAccess: true, defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); _declarationFinder.AddUnboundDefaultMemberAccess(reference); } @@ -535,6 +533,83 @@ private void AddUnboundDefaultMemberReference( _declarationFinder.AddUnboundDefaultMemberAccess(reference); } + private void Visit( + ProcedureCoercionExpression expression, + QualifiedModuleName module, + Declaration scope, + Declaration parent) + { + Visit(expression.WrappedExpression, module, scope, parent); + + if (expression.Classification != ExpressionClassification.Unbound + && expression.ReferencedDeclaration != null) + { + AddDefaultMemberReference(expression, module, scope, parent); + } + else + { + AddUnboundDefaultMemberReference(expression, module, scope, parent); + } + } + + private void AddDefaultMemberReference( + DictionaryAccessExpression expression, + QualifiedModuleName module, + Declaration scope, + Declaration parent, + bool isAssignmentTarget, + bool hasExplicitLetStatement, + bool isSetAssignment) + { + var callSiteContext = expression.DefaultMemberContext; + var identifier = expression.ReferencedDeclaration.IdentifierName; + var callee = expression.ReferencedDeclaration; + expression.ReferencedDeclaration.AddReference( + module, + scope, + parent, + callSiteContext, + identifier, + callee, + callSiteContext.GetSelection(), + FindIdentifierAnnotations(module, callSiteContext.GetSelection().StartLine), + isAssignmentTarget, + hasExplicitLetStatement, + isSetAssignment, + isIndexedDefaultMemberAccess: true, + defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); + } + + private void AddUnboundDefaultMemberReference( + DictionaryAccessExpression expression, + QualifiedModuleName module, + Declaration scope, + Declaration parent, + bool isAssignmentTarget, + bool hasExplicitLetStatement, + bool isSetAssignment) + { + var callSiteContext = expression.DefaultMemberContext; + var identifier = expression.Context.GetText(); + var selection = callSiteContext.GetSelection(); + var callee = expression.ReferencedDeclaration; + var reference = new IdentifierReference( + module, + scope, + parent, + identifier, + selection, + callSiteContext, + callee, + isAssignmentTarget, + hasExplicitLetStatement, + FindIdentifierAnnotations(module, selection.StartLine), + isSetAssignment, + isIndexedDefaultMemberAccess: true, + defaultMemberRecursionDepth: expression.DefaultMemberRecursionDepth); + _declarationFinder.AddUnboundDefaultMemberAccess(reference); + } + private void Visit( NewExpression expression, QualifiedModuleName module, diff --git a/RubberduckTests/Grammar/ResolverTests.cs b/RubberduckTests/Grammar/ResolverTests.cs index f247fc1e3d..b1b64c3294 100644 --- a/RubberduckTests/Grammar/ResolverTests.cs +++ b/RubberduckTests/Grammar/ResolverTests.cs @@ -4711,7 +4711,6 @@ End Sub Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); Assert.IsTrue(defaultMemberReference.IsIndexedDefaultMemberAccess); Assert.AreEqual(1, defaultMemberReference.DefaultMemberRecursionDepth); - Assert.IsTrue(defaultMemberReference.IsAssignment); } } @@ -4736,7 +4735,7 @@ End Function var moduleCode = $@" Private Function Foo() As Variant Dim cls As new Class2 - cls.Baz + cls End Function Private Sub Bar(arg As Long) @@ -4751,7 +4750,7 @@ End Sub ("Class2", class2Code, ComponentType.ClassModule), ("Module1", moduleCode, ComponentType.StandardModule)); - var selection = new Selection(4, 5, 4, 12); + var selection = new Selection(4, 5, 4, 8); using (var state = Resolve(vbe.Object)) { @@ -4760,13 +4759,12 @@ End Sub var defaultMemberReference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Last(); var referencedDeclaration = defaultMemberReference.Declaration; - var expectedReferencedDeclarationName = "Class1.Foo"; + var expectedReferencedDeclarationName = "Class2.Baz"; var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); - Assert.IsTrue(defaultMemberReference.IsIndexedDefaultMemberAccess); + Assert.IsTrue(defaultMemberReference.IsNonIndexedDefaultMemberAccess); Assert.AreEqual(1, defaultMemberReference.DefaultMemberRecursionDepth); - Assert.IsTrue(defaultMemberReference.IsAssignment); } } @@ -4806,7 +4804,7 @@ End Sub ("Class2", class2Code, ComponentType.ClassModule), ("Module1", moduleCode, ComponentType.StandardModule)); - var selection = new Selection(4, 5, 4, 12); + var selection = new Selection(4, 10, 4, 17); using (var state = Resolve(vbe.Object)) { @@ -4821,7 +4819,6 @@ End Sub Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); Assert.IsTrue(defaultMemberReference.IsIndexedDefaultMemberAccess); Assert.AreEqual(1, defaultMemberReference.DefaultMemberRecursionDepth); - Assert.IsTrue(defaultMemberReference.IsAssignment); } } @@ -4846,7 +4843,7 @@ End Function var moduleCode = $@" Private Function Foo() As Variant Dim cls As new Class2 - Call cls.Baz + Call cls End Function Private Sub Bar(arg As Long) @@ -4861,7 +4858,61 @@ End Sub ("Class2", class2Code, ComponentType.ClassModule), ("Module1", moduleCode, ComponentType.StandardModule)); - var selection = new Selection(4, 5, 4, 12); + var selection = new Selection(4, 10, 4, 13); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var qualifiedSelection = new QualifiedSelection(module, selection); + var defaultMemberReference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Last(); + var referencedDeclaration = defaultMemberReference.Declaration; + + var expectedReferencedDeclarationName = "Class2.Baz"; + var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; + + Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); + Assert.IsTrue(defaultMemberReference.IsNonIndexedDefaultMemberAccess); + Assert.AreEqual(1, defaultMemberReference.DefaultMemberRecursionDepth); + } + } + + [Test] + [Category("Grammar")] + [Category("Resolver")] + public void NonParameterizedProcedureCoercionDefaultMemberAccessOnArrayAccessReferenceToDefaultMemberOnEntireContext() + { + var class1Code = @" +Public Sub Foo() +Attribute Foo.VB_UserMemId = 0 +End Sub +"; + + var class2Code = @" +Public Function Baz() As Class1() +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class1 +End Function +"; + + var moduleCode = $@" +Private Function Foo() As Variant + Dim cls As new Class2 + cls.Baz(42) +End Function + +Private Sub Bar(arg As Long) +End Sub + +Private Sub Baz(arg As Variant) +End Sub +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", class1Code, ComponentType.ClassModule), + ("Class2", class2Code, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(4, 5, 4, 16); using (var state = Resolve(vbe.Object)) { @@ -4874,9 +4925,63 @@ End Sub var actualReferencedDeclarationName = $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); - Assert.IsTrue(defaultMemberReference.IsIndexedDefaultMemberAccess); + Assert.IsTrue(defaultMemberReference.IsNonIndexedDefaultMemberAccess); + Assert.AreEqual(1, defaultMemberReference.DefaultMemberRecursionDepth); + } + } + + [Test] + [Category("Grammar")] + [Category("Resolver")] + public void NonParameterizedProcedureCoercionDefaultMemberAccessOnArrayAccessReferenceToDefaultMemberOnEntireContext_ExplicitCall() + { + var class1Code = @" +Public Sub Foo() +Attribute Foo.VB_UserMemId = 0 +End Sub +"; + + var class2Code = @" +Public Function Baz() As Class1() +Attribute Baz.VB_UserMemId = 0 + Set Baz = New Class1 +End Function +"; + + var moduleCode = $@" +Private Function Foo() As Variant + Dim cls As new Class2 + Call cls.Baz(42) +End Function + +Private Sub Bar(arg As Long) +End Sub + +Private Sub Baz(arg As Variant) +End Sub +"; + + var vbe = MockVbeBuilder.BuildFromModules( + ("Class1", class1Code, ComponentType.ClassModule), + ("Class2", class2Code, ComponentType.ClassModule), + ("Module1", moduleCode, ComponentType.StandardModule)); + + var selection = new Selection(4, 10, 4, 21); + + using (var state = Resolve(vbe.Object)) + { + var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == "Module1"); + var qualifiedSelection = new QualifiedSelection(module, selection); + var defaultMemberReference = state.DeclarationFinder.IdentifierReferences(qualifiedSelection).Last(); + var referencedDeclaration = defaultMemberReference.Declaration; + + var expectedReferencedDeclarationName = "Class1.Foo"; + var actualReferencedDeclarationName = + $"{referencedDeclaration.ComponentName}.{referencedDeclaration.IdentifierName}"; + + Assert.AreEqual(expectedReferencedDeclarationName, actualReferencedDeclarationName); + Assert.IsTrue(defaultMemberReference.IsNonIndexedDefaultMemberAccess); Assert.AreEqual(1, defaultMemberReference.DefaultMemberRecursionDepth); - Assert.IsTrue(defaultMemberReference.IsAssignment); } } } From 9d9b2261386d1a94b94507f6e6435ee051b235a0 Mon Sep 17 00:00:00 2001 From: Max Doerner Date: Wed, 21 Aug 2019 21:29:35 +0200 Subject: [PATCH 10/32] Fix ImplicitDefaultMemberAssignmentInspection ObjectVariableNotSetInspection still needing an overhaul because of the new default member resolution. --- ...plicitDefaultMemberAssignmentInspection.cs | 58 +++++++++++-------- .../Inspections/InspectionResults.Designer.cs | 11 +++- .../Inspections/InspectionResults.cs.resx | 2 +- .../Inspections/InspectionResults.de.resx | 5 +- .../Inspections/InspectionResults.es.resx | 2 +- .../Inspections/InspectionResults.fr.resx | 2 +- .../Inspections/InspectionResults.resx | 7 ++- ...tDefaultMemberAssignmentInspectionTests.cs | 22 +++++++ 8 files changed, 80 insertions(+), 29 deletions(-) diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/ImplicitDefaultMemberAssignmentInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/ImplicitDefaultMemberAssignmentInspection.cs index ea7b5a1a16..301e0ebd53 100644 --- a/Rubberduck.CodeAnalysis/Inspections/Concrete/ImplicitDefaultMemberAssignmentInspection.cs +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/ImplicitDefaultMemberAssignmentInspection.cs @@ -1,10 +1,7 @@ using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using Rubberduck.Inspections.Abstract; using Rubberduck.Inspections.Results; -using Rubberduck.Parsing; -using Rubberduck.Parsing.Grammar; using Rubberduck.Parsing.Inspections.Abstract; using Rubberduck.Resources.Inspections; using Rubberduck.Parsing.Symbols; @@ -40,28 +37,43 @@ public ImplicitDefaultMemberAssignmentInspection(RubberduckParserState state) protected override IEnumerable DoGetInspectionResults() { - var interestingDeclarations = - State.AllDeclarations.Where(item => - item.AsTypeDeclaration != null - && ClassModuleDeclaration.HasDefaultMember(item.AsTypeDeclaration)); + var boundDefaultMemberAssignments = State.DeclarationFinder + .AllIdentifierReferences() + .Where(IsRelevantReference); - var interestingReferences = interestingDeclarations - .SelectMany(declaration => declaration.References) - .Where(reference => - { - var letStmtContext = reference.Context.GetAncestor(); - return reference.IsAssignment - && letStmtContext != null - && letStmtContext.LET() == null - && !reference.IsIgnoringInspectionResultFor(AnnotationName); - }); + var boundIssues = boundDefaultMemberAssignments + .Select(reference => new IdentifierReferenceInspectionResult( + this, + string.Format( + InspectionResults.ImplicitDefaultMemberAssignmentInspection, + reference.Context.GetText(), + reference.Declaration.IdentifierName, + reference.Declaration.QualifiedModuleName.ToString()), + State, + reference)); - return interestingReferences.Select(reference => new IdentifierReferenceInspectionResult(this, - string.Format(InspectionResults.ImplicitDefaultMemberAssignmentInspection, - reference.Declaration.IdentifierName, - reference.Declaration.AsTypeDeclaration.IdentifierName), - State, - reference)); + var unboundDefaultMemberAssignments = State.DeclarationFinder + .AllUnboundDefaultMemberAccesses() + .Where(IsRelevantReference); + + var unboundIssues = unboundDefaultMemberAssignments + .Select(reference => new IdentifierReferenceInspectionResult( + this, + string.Format( + InspectionResults.ImplicitDefaultMemberAssignmentInspection_Unbound, + reference.Context.GetText()), + State, + reference)); + + return boundIssues.Concat(unboundIssues); + } + + private bool IsRelevantReference(IdentifierReference reference) + { + return reference.IsAssignment + && reference.IsNonIndexedDefaultMemberAccess + && !reference.HasExplicitLetStatement + && !reference.IsIgnoringInspectionResultFor(AnnotationName); } } } \ No newline at end of file diff --git a/Rubberduck.Resources/Inspections/InspectionResults.Designer.cs b/Rubberduck.Resources/Inspections/InspectionResults.Designer.cs index 3d7bb8e5bf..563aab8ca2 100644 --- a/Rubberduck.Resources/Inspections/InspectionResults.Designer.cs +++ b/Rubberduck.Resources/Inspections/InspectionResults.Designer.cs @@ -340,7 +340,7 @@ public static string ImplicitByRefModifierInspection { } /// - /// Looks up a localized string similar to Assignment to '{0}' implicitly assigns default member of class '{1}'.. + /// Looks up a localized string similar to Assignment to '{0}' implicitly assigns to default member '{1}' of class '{2}'.. /// public static string ImplicitDefaultMemberAssignmentInspection { get { @@ -348,6 +348,15 @@ public static string ImplicitDefaultMemberAssignmentInspection { } } + /// + /// Looks up a localized string similar to Assignment to '{0}' implicitly assigns to an unbound default member.. + /// + public static string ImplicitDefaultMemberAssignmentInspection_Unbound { + get { + return ResourceManager.GetString("ImplicitDefaultMemberAssignmentInspection_Unbound", resourceCulture); + } + } + /// /// Looks up a localized string similar to Member '{0}' is implicitly public.. /// diff --git a/Rubberduck.Resources/Inspections/InspectionResults.cs.resx b/Rubberduck.Resources/Inspections/InspectionResults.cs.resx index 6f769c8a6a..458919958c 100644 --- a/Rubberduck.Resources/Inspections/InspectionResults.cs.resx +++ b/Rubberduck.Resources/Inspections/InspectionResults.cs.resx @@ -232,7 +232,7 @@ Lokální proměnná '{0}' není deklarována - Přidělení '{0}' implicitně přidělí výchozího člena třídy '{1}' + Přidělení '{0}' implicitně přidělí výchozího člena třídy '{2}' {0} ({1} výsledky) diff --git a/Rubberduck.Resources/Inspections/InspectionResults.de.resx b/Rubberduck.Resources/Inspections/InspectionResults.de.resx index d669c85319..61071d84d3 100644 --- a/Rubberduck.Resources/Inspections/InspectionResults.de.resx +++ b/Rubberduck.Resources/Inspections/InspectionResults.de.resx @@ -237,7 +237,7 @@ Laufzeitgebundene Nutzung des Application.{0} Members. - Zuweisung zu '{0}' weist implizit den Standard-Member von Klasse '{1}' zu + Zuweisung zu '{0}' weist implizit dem Standard-Member '{1}' von Klasse '{2}' zu Ausdruck '{0}' kann zur Kompilierzeit nicht validiert werden. @@ -407,4 +407,7 @@ In Memoriam, 1972-2018 Der Variablen '{0}' vom deklarierten Typ '{1}' wird eine Wert des inkompatiblen Typs '{2}' Set-zugewiesen. + + Zuweisung zu '{0}' weist implizit einem nicht gebundenen Standard-Member zu. + \ No newline at end of file diff --git a/Rubberduck.Resources/Inspections/InspectionResults.es.resx b/Rubberduck.Resources/Inspections/InspectionResults.es.resx index 9107f8edbc..39e688e2f8 100644 --- a/Rubberduck.Resources/Inspections/InspectionResults.es.resx +++ b/Rubberduck.Resources/Inspections/InspectionResults.es.resx @@ -232,7 +232,7 @@ La variable local '{0}' no está declarada. - La asignación a '{0}' asigna implícitamente al miembro predeterminado de la clase '{1}'. + La asignación a '{0}' asigna implícitamente al miembro predeterminado '{1}' de la clase '{2}'. {0} ({1} resultados). diff --git a/Rubberduck.Resources/Inspections/InspectionResults.fr.resx b/Rubberduck.Resources/Inspections/InspectionResults.fr.resx index e3aa5843c9..d797a1bcd0 100644 --- a/Rubberduck.Resources/Inspections/InspectionResults.fr.resx +++ b/Rubberduck.Resources/Inspections/InspectionResults.fr.resx @@ -232,7 +232,7 @@ Un paramètre était attendu dans l'annotation '{0}'. - Assignation de '{0}' réfère implicitement au membre par défaut de la classe '{1}' + Assignation de '{0}' réfère implicitement au membre par défaut '{1}' de la classe '{2}' {0} ({1} résultats) diff --git a/Rubberduck.Resources/Inspections/InspectionResults.resx b/Rubberduck.Resources/Inspections/InspectionResults.resx index b9892d92f7..7c1e2bbd39 100644 --- a/Rubberduck.Resources/Inspections/InspectionResults.resx +++ b/Rubberduck.Resources/Inspections/InspectionResults.resx @@ -237,7 +237,8 @@ {0} variable name - Assignment to '{0}' implicitly assigns default member of class '{1}'. + Assignment to '{0}' implicitly assigns to default member '{1}' of class '{2}'. + {0} expression; {1} default member name; {2} class of default member {0} ({1} results). @@ -437,4 +438,8 @@ In memoriam, 1972-2018 Interface class module contains a concrete implementation for {0} '{1}'. {0} Method kind, {1} Method name + + Assignment to '{0}' implicitly assigns to an unbound default member. + {0} expression + \ No newline at end of file diff --git a/RubberduckTests/Inspections/ImplicitDefaultMemberAssignmentInspectionTests.cs b/RubberduckTests/Inspections/ImplicitDefaultMemberAssignmentInspectionTests.cs index f3a664f7ed..e13409d5fb 100644 --- a/RubberduckTests/Inspections/ImplicitDefaultMemberAssignmentInspectionTests.cs +++ b/RubberduckTests/Inspections/ImplicitDefaultMemberAssignmentInspectionTests.cs @@ -35,6 +35,28 @@ End Sub Assert.AreEqual(1, inspectionResults.Count()); } + [Test] + [Category("Inspections")] + public void ImplicitDefaultMemberAssignmentOnObject_ReturnsResult() + { + const string defaultMemberClassCode = @" +Public Property Let Foo(bar As Long) +Attribute Foo.VB_UserMemId = 0 +End Property +"; + + const string inputCode = @" +Public Sub Foo() + Dim bar As Object + bar = 42 +End Sub +"; + + var inspectionResults = GetInspectionResults(defaultMemberClassCode, inputCode); + + Assert.AreEqual(1, inspectionResults.Count()); + } + [Test] [Category("Inspections")] public void ImplicitDefaultMemberAssignment_IgnoredDoesNotReturnResult() From dbfc88ca09912b834129060f414c1d0c69b8711d Mon Sep 17 00:00:00 2001 From: Max Doerner Date: Thu, 22 Aug 2019 00:21:07 +0200 Subject: [PATCH 11/32] Fix tests for ObjectVariableNotSetInspection This finally fixes all remaining tests broken by the introduction of default member resolution. --- .../AssignedByValParameterInspection.cs | 33 +---------------- .../Bindings/LetCoercionDefaultBinding.cs | 11 +++--- .../AssignedByValParameterInspectionTests.cs | 6 +-- .../ObjectVariableNotSetInspectionTests.cs | 37 ++++++++++++++++--- .../QuickFixes/IgnoreOnceQuickFixTests.cs | 4 +- ...KeywordForObjectAssignmentQuickFixTests.cs | 4 +- 6 files changed, 45 insertions(+), 50 deletions(-) diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/AssignedByValParameterInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/AssignedByValParameterInspection.cs index 721580b360..8ac979dc82 100644 --- a/Rubberduck.CodeAnalysis/Inspections/Concrete/AssignedByValParameterInspection.cs +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/AssignedByValParameterInspection.cs @@ -48,43 +48,12 @@ protected override IEnumerable DoGetInspectionResults() .Cast() .Where(item => !item.IsByRef && !item.IsIgnoringInspectionResultFor(AnnotationName) - && item.References.Any(IsAssignmentToDeclaration)); + && item.References.Any(reference => reference.IsAssignment)); return parameters .Select(param => new DeclarationInspectionResult(this, string.Format(InspectionResults.AssignedByValParameterInspection, param.IdentifierName), param)); } - - private static bool IsAssignmentToDeclaration(IdentifierReference reference) - { - //Todo: Review whether this is still needed once parameterless default member assignments are resolved correctly. - - if (!reference.IsAssignment) - { - return false; - } - - if (reference.IsSetAssignment) - { - return true; - } - - var declaration = reference.Declaration; - if (declaration == null) - { - return false; - } - - if (declaration.IsObject) - { - //This can only be legal with a default member access. - return false; - } - - //This is not perfect in case the referenced declaration is an unbound Variant. - //In that case, a default member access might occur after the run-time resolution. - return true; - } } } diff --git a/Rubberduck.Parsing/Binding/Bindings/LetCoercionDefaultBinding.cs b/Rubberduck.Parsing/Binding/Bindings/LetCoercionDefaultBinding.cs index 2f79012d36..b66aa76ac8 100644 --- a/Rubberduck.Parsing/Binding/Bindings/LetCoercionDefaultBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/LetCoercionDefaultBinding.cs @@ -83,11 +83,10 @@ private static IBoundExpression Resolve(IBoundExpression wrappedExpression, Pars return ResolveViaDefaultMember(wrappedExpression, asTypeName, asTypeDeclaration, expression, isAssignment); } - private static IBoundExpression CreateFailedExpression(IBoundExpression lExpression) + private static IBoundExpression ExpressionForResolutionFailure(IBoundExpression lExpression) { - var failedExpr = new ResolutionFailedExpression(); - failedExpr.AddSuccessfullyResolvedExpression(lExpression); - return failedExpr; + //We return the original expression because no default member resolution can have taken place as there is no appropriate one. + return lExpression; } private static IBoundExpression ResolveViaDefaultMember(IBoundExpression wrappedExpression, string asTypeName, Declaration asTypeDeclaration, ParserRuleContext expression, bool isAssignment, int recursionDepth = 1, RecursiveDefaultMemberAccessExpression containedExpression = null) @@ -104,7 +103,7 @@ private static IBoundExpression ResolveViaDefaultMember(IBoundExpression wrapped || !IsPropertyGetLetFunctionProcedure(defaultMember) || !IsPublic(defaultMember)) { - return CreateFailedExpression(wrappedExpression); + return ExpressionForResolutionFailure(wrappedExpression); } var defaultMemberClassification = DefaultMemberClassification(defaultMember); @@ -134,7 +133,7 @@ private static IBoundExpression ResolveViaDefaultMember(IBoundExpression wrapped } } - return CreateFailedExpression(wrappedExpression); + return ExpressionForResolutionFailure(wrappedExpression); } private static IBoundExpression ResolveRecursiveDefaultMember(IBoundExpression wrappedExpression, Declaration defaultMember, ExpressionClassification defaultMemberClassification, ParserRuleContext expression, bool isAssignment, int recursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) diff --git a/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs b/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs index cea5c81fdd..2797db112a 100644 --- a/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs +++ b/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs @@ -172,14 +172,14 @@ End Sub [Test] [Category("Inspections")] - public void AssignedByValParameter_NoResultForDefaultMembberAssignment() + public void AssignedByValParameter_NoResultForDefaultMemberAssignment() { var class1 = @" Public Property Get Something() As Long -Attribute Foo.VB_UserMemId = 0 +Attribute Something.VB_UserMemId = 0 End Property + Public Property Let Something(ByVal value As Long) -Attribute Foo.VB_UserMemId = 0 End Property "; var caller = @" diff --git a/RubberduckTests/Inspections/ObjectVariableNotSetInspectionTests.cs b/RubberduckTests/Inspections/ObjectVariableNotSetInspectionTests.cs index eedae28ffd..73113f90b9 100644 --- a/RubberduckTests/Inspections/ObjectVariableNotSetInspectionTests.cs +++ b/RubberduckTests/Inspections/ObjectVariableNotSetInspectionTests.cs @@ -115,9 +115,13 @@ public void ObjectVariableNotSet_GivenIndexerObjectAccess_ReturnsResult() var expectResultCount = 1; var input = @" +Public Function Item(index As Variant) As Class1 +Attribute Item.VB_UserMemId = 0 +End Function + Private Sub DoSomething() - Dim target As Object - target = CreateObject(""Scripting.Dictionary"") + Dim target As Class1 + Set target = New Class1 target(""foo"") = 42 End Sub "; @@ -155,6 +159,29 @@ testParam.Add 100 AssertInputCodeYieldsExpectedInspectionResultCount(input, expectResultCount, "VBA.4.2.xml"); } + [Test] + [Category("Inspections")] + //Let assignments to a variable with declared type Object resolve to an unbound default member call. + //Whether it is legal can only be determined at runtime. However, this creates results for other inspections. + public void ObjectVariableNotSet_GivenObjectVariableAssignedObject_ReturnsNoResult() + { + var expectResultCount = 0; + var input = + @" +Public Function Item() As Long +Attribute Item.VB_UserMemId = 0 +End Function + +Private Sub TestSub(ByRef testParam As Variant) + Dim target As Object + Dim foo As Class1 + Set foo = New Class1 + target = foo +End Sub +"; + AssertInputCodeYieldsExpectedInspectionResultCount(input, expectResultCount); + } + [Test] [Category("Inspections")] public void ObjectVariableNotSet_GivenVariantVariableAssignedNewObject_ReturnsResult() @@ -393,7 +420,7 @@ public void ObjectVariableNotSet_InsideForEachObject_ReturnsResult() @" Private Sub Test() Dim bar As Variant - Dim baz As Object + Dim baz As Class1 For Each foo In bar baz = foo Next @@ -521,7 +548,7 @@ Dim bar As Variant [Test] [Category("Inspections")] - public void ObjectVariableNotSet_AssignmentToVarirableWithDefaultMemberReturningAnObject_OneResult() + public void ObjectVariableNotSet_AssignmentToVariableWithDefaultMemberReturningAnObjectOfASpecificClassWithoutParameterlessDefaultMember_OneResult() { var expectResultCount = 1; var input = @@ -606,7 +633,7 @@ private void AssertInputCodeYieldsExpectedInspectionResultCount(string inputCode { var builder = new MockVbeBuilder(); var projectBuilder = builder.ProjectBuilder("TestProject1", "TestProject1", ProjectProtection.Unprotected) - .AddComponent("Module1", ComponentType.StandardModule, inputCode); + .AddComponent("Class1", ComponentType.ClassModule, inputCode); foreach (var testLibrary in testLibraries) { diff --git a/RubberduckTests/QuickFixes/IgnoreOnceQuickFixTests.cs b/RubberduckTests/QuickFixes/IgnoreOnceQuickFixTests.cs index 25a710eff1..b987e56f88 100644 --- a/RubberduckTests/QuickFixes/IgnoreOnceQuickFixTests.cs +++ b/RubberduckTests/QuickFixes/IgnoreOnceQuickFixTests.cs @@ -391,7 +391,7 @@ public void ObjectVariableNotSet_IgnoreQuickFixWorks() @" Private Sub DoSomething() - Dim target As Object + Dim target As Class1 target = New Class1 target.Value = ""forgot something?"" @@ -401,7 +401,7 @@ Dim target As Object @" Private Sub DoSomething() - Dim target As Object + Dim target As Class1 '@Ignore ObjectVariableNotSet target = New Class1 diff --git a/RubberduckTests/QuickFixes/UseSetKeywordForObjectAssignmentQuickFixTests.cs b/RubberduckTests/QuickFixes/UseSetKeywordForObjectAssignmentQuickFixTests.cs index 807f4fcbc4..09d6bfe25c 100644 --- a/RubberduckTests/QuickFixes/UseSetKeywordForObjectAssignmentQuickFixTests.cs +++ b/RubberduckTests/QuickFixes/UseSetKeywordForObjectAssignmentQuickFixTests.cs @@ -62,12 +62,12 @@ public void ObjectVariableNotSet_ForFunctionAssignment_ReturnsResult() { var inputCode = @" -Private Function ReturnObject(ByVal source As Object) As Object +Private Function ReturnObject(ByVal source As Object) As Class1 ReturnObject = source End Function"; var expectedCode = @" -Private Function ReturnObject(ByVal source As Object) As Object +Private Function ReturnObject(ByVal source As Object) As Class1 Set ReturnObject = source End Function"; From 31337e6d7fd4b1edaa1d24ae93b9afdd8286e2c5 Mon Sep 17 00:00:00 2001 From: Max Doerner Date: Thu, 22 Aug 2019 00:37:05 +0200 Subject: [PATCH 12/32] Make ImpliciteDefaultMemberAssignmentInspection also return results when the Let keyword is present --- .../Concrete/ImplicitDefaultMemberAssignmentInspection.cs | 1 - .../ImplicitDefaultMemberAssignmentInspectionTests.cs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/ImplicitDefaultMemberAssignmentInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/ImplicitDefaultMemberAssignmentInspection.cs index 301e0ebd53..ae368168d7 100644 --- a/Rubberduck.CodeAnalysis/Inspections/Concrete/ImplicitDefaultMemberAssignmentInspection.cs +++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/ImplicitDefaultMemberAssignmentInspection.cs @@ -72,7 +72,6 @@ private bool IsRelevantReference(IdentifierReference reference) { return reference.IsAssignment && reference.IsNonIndexedDefaultMemberAccess - && !reference.HasExplicitLetStatement && !reference.IsIgnoringInspectionResultFor(AnnotationName); } } diff --git a/RubberduckTests/Inspections/ImplicitDefaultMemberAssignmentInspectionTests.cs b/RubberduckTests/Inspections/ImplicitDefaultMemberAssignmentInspectionTests.cs index e13409d5fb..8a8f351944 100644 --- a/RubberduckTests/Inspections/ImplicitDefaultMemberAssignmentInspectionTests.cs +++ b/RubberduckTests/Inspections/ImplicitDefaultMemberAssignmentInspectionTests.cs @@ -102,7 +102,7 @@ End Sub [Test] [Category("Inspections")] - public void ImplicitDefaultMemberAssignment_ExplicitLetDoesNotReturnResult() + public void ImplicitDefaultMemberAssignment_ExplicitLetReturnsResult() { const string defaultMemberClassCode = @" Public Property Let Foo(bar As Long) @@ -118,7 +118,7 @@ End Sub var inspectionResults = GetInspectionResults(defaultMemberClassCode, inputCode); - Assert.AreEqual(0, inspectionResults.Count()); + Assert.AreEqual(1, inspectionResults.Count()); } [Test] From 20449ce14f72d8487786f9f3d0c73767159b12b5 Mon Sep 17 00:00:00 2001 From: Max Doerner Date: Thu, 22 Aug 2019 01:08:12 +0200 Subject: [PATCH 13/32] Fix SetTypeResolver not resolving type of index expressions with default member accesses and array accesses correctly --- .../TypeResolvers/SetTypeResolver.cs | 17 +- .../SetTypeResolverTests.cs | 173 +++++++++++++++++- 2 files changed, 180 insertions(+), 10 deletions(-) diff --git a/Rubberduck.Parsing/TypeResolvers/SetTypeResolver.cs b/Rubberduck.Parsing/TypeResolvers/SetTypeResolver.cs index 44f83616fa..5093ea7693 100644 --- a/Rubberduck.Parsing/TypeResolvers/SetTypeResolver.cs +++ b/Rubberduck.Parsing/TypeResolvers/SetTypeResolver.cs @@ -171,18 +171,17 @@ private static string FullObjectTypeName(Declaration setTypeDeterminingDeclarati throw new NotSupportedException("Called index expression resolution on expression, which is neither a properly built indexExpr nor a properly built whitespaceIndexExpr."); } - var declaration = ResolveIndexExpressionAsMethod(lExpressionOfIndexExpression, containingModule, finder) - ?? ResolveIndexExpressionAsDefaultMemberAccess(lExpressionOfIndexExpression, containingModule, finder); - - if (declaration != null) - { - return (declaration, MightHaveSetType(declaration)); - } - //Passing the indexExpr itself is correct. var arrayDeclaration = ResolveIndexExpressionAsArrayAccess(indexExpr, containingModule, finder); + if (arrayDeclaration != null) + { + return (arrayDeclaration, MightHaveSetTypeOnArrayAccess(arrayDeclaration)); + } - return (arrayDeclaration, MightHaveSetTypeOnArrayAccess(arrayDeclaration)); + var declaration = ResolveIndexExpressionAsDefaultMemberAccess(lExpressionOfIndexExpression, containingModule, finder) + ?? ResolveIndexExpressionAsMethod(lExpressionOfIndexExpression, containingModule, finder); + + return (declaration, MightHaveSetType(declaration)); } private Declaration ResolveIndexExpressionAsMethod(VBAParser.LExpressionContext lExpressionOfIndexExpression, QualifiedModuleName containingModule, DeclarationFinder finder) diff --git a/RubberduckTests/ExpressionResolving/SetTypeResolverTests.cs b/RubberduckTests/ExpressionResolving/SetTypeResolverTests.cs index 869e79baf3..762e5bf8ab 100644 --- a/RubberduckTests/ExpressionResolving/SetTypeResolverTests.cs +++ b/RubberduckTests/ExpressionResolving/SetTypeResolverTests.cs @@ -1189,7 +1189,6 @@ End Function [Test] [Category("ExpressionResolver")] - [Category("ExpressionResolver")] [TestCase("Object", null)] [TestCase("Class1", "TestProject.Class1")] [TestCase("Variant", null)] @@ -1230,6 +1229,178 @@ End Function Assert.AreEqual(expectedNameOfSetTypeDeclaration, actualNameOfSetTypeDeclaration); } + [Test] + [Category("ExpressionResolver")] + public void IndexedDefaultMemberCallOnFunctionReturnValueHasTypeOfDefaultMember_SetTypeName() + { + var class1 = + @" +Public Function Foo(baz As Long) As Class2 +Attribute Foo.VB_UserMemId = 0 +End Function +"; + var class2 = + @" +Private Function Foo() As Class1 +End Function +"; + + var module1 = + $@" +Private Sub Bar() + Dim baz As Variant + Dim foo As Class1 + Set baz = foo.Foo(42) +End Sub +"; + + var expressionSelection = new Selection(5, 15, 5, 26); + + var vbe = new MockVbeBuilder() + .ProjectBuilder("TestProject", ProjectProtection.Unprotected) + .AddComponent("Class1", ComponentType.ClassModule, class1) + .AddComponent("Class2", ComponentType.ClassModule, class2) + .AddComponent("Module1", ComponentType.StandardModule, module1) + .AddProjectToVbeBuilder() + .Build() + .Object; + + var expectedSetTypeName = "TestProject.Class2"; + var actualSetTypeName = ExpressionTypeName(vbe, "Module1", expressionSelection); + + Assert.AreEqual(expectedSetTypeName, actualSetTypeName); + } + + [Test] + [Category("ExpressionResolver")] + public void IndexedDefaultMemberCallOnFunctionReturnValueHasTypeOfDefaultMember_SetTypeDeclaration() + { + var class1 = + @" +Public Function Foo(baz As Long) As Class2 +Attribute Foo.VB_UserMemId = 0 +End Function +"; + var class2 = + @" +Private Function Foo() As Class1 +End Function +"; + + var module1 = + $@" +Private Sub Bar() + Dim baz As Variant + Dim foo As Class1 + Set baz = foo.Foo(42) +End Sub +"; + + var expressionSelection = new Selection(5, 15, 5, 26); + + var vbe = new MockVbeBuilder() + .ProjectBuilder("TestProject", ProjectProtection.Unprotected) + .AddComponent("Class1", ComponentType.ClassModule, class1) + .AddComponent("Class2", ComponentType.ClassModule, class2) + .AddComponent("Module1", ComponentType.StandardModule, module1) + .AddProjectToVbeBuilder() + .Build() + .Object; + + var setTypeDeclaration = ExpressionTypeDeclaration(vbe, "Module1", expressionSelection); + + var expectedNameOfSetTypeDeclaration = "TestProject.Class2"; + var actualNameOfSetTypeDeclaration = setTypeDeclaration?.QualifiedModuleName.ToString(); + + Assert.AreEqual(expectedNameOfSetTypeDeclaration, actualNameOfSetTypeDeclaration); + } + + [Test] + [Category("ExpressionResolver")] + public void ArrayAccessOnFunctionReturnValueHasTypeOfDefaultMember_SetTypeName() + { + var class1 = + @" +Public Function Foo() As Class2() +Attribute Foo.VB_UserMemId = 0 +End Function +"; + var class2 = + @" +Private Function Foo() As Class1 +End Function +"; + + var module1 = + $@" +Private Sub Bar() + Dim baz As Variant + Dim foo As Class1 + Set baz = foo.Foo(42) +End Sub +"; + + var expressionSelection = new Selection(5, 15, 5, 26); + + var vbe = new MockVbeBuilder() + .ProjectBuilder("TestProject", ProjectProtection.Unprotected) + .AddComponent("Class1", ComponentType.ClassModule, class1) + .AddComponent("Class2", ComponentType.ClassModule, class2) + .AddComponent("Module1", ComponentType.StandardModule, module1) + .AddProjectToVbeBuilder() + .Build() + .Object; + + var expectedSetTypeName = "TestProject.Class2"; + var actualSetTypeName = ExpressionTypeName(vbe, "Module1", expressionSelection); + + Assert.AreEqual(expectedSetTypeName, actualSetTypeName); + } + + [Test] + [Category("ExpressionResolver")] + public void ArrayAccessOnFunctionReturnValueHasTypeOfDefaultMember_SetTypeDeclaration() + { + var class1 = + @" +Public Function Foo() As Class2() +Attribute Foo.VB_UserMemId = 0 +End Function +"; + var class2 = + @" +Private Function Foo() As Class1 +End Function +"; + + var module1 = + $@" +Private Sub Bar() + Dim baz As Variant + Dim foo As Class1 + Set baz = foo.Foo(42) +End Sub +"; + + var expressionSelection = new Selection(5, 15, 5, 26); + + var vbe = new MockVbeBuilder() + .ProjectBuilder("TestProject", ProjectProtection.Unprotected) + .AddComponent("Class1", ComponentType.ClassModule, class1) + .AddComponent("Class2", ComponentType.ClassModule, class2) + .AddComponent("Module1", ComponentType.StandardModule, module1) + .AddProjectToVbeBuilder() + .Build() + .Object; + + var setTypeDeclaration = ExpressionTypeDeclaration(vbe, "Module1", expressionSelection); + + var expectedNameOfSetTypeDeclaration = "TestProject.Class2"; + var actualNameOfSetTypeDeclaration = setTypeDeclaration?.QualifiedModuleName.ToString(); + + Assert.AreEqual(expectedNameOfSetTypeDeclaration, actualNameOfSetTypeDeclaration); + } + private Declaration ExpressionTypeDeclaration(IVBE vbe, string componentName, Selection selection) { using (var state = MockParser.CreateAndParse(vbe)) From fa4e78c8351ad692552907113fb7221b58076d62 Mon Sep 17 00:00:00 2001 From: Max Doerner Date: Thu, 22 Aug 2019 01:29:11 +0200 Subject: [PATCH 14/32] Add tests covering fixed issues --- .../ConstantNotUsedInspectionTests.cs | 28 +++++++++++++++++++ .../ParameterNotUsedInspectionTests.cs | 27 ++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/RubberduckTests/Inspections/ConstantNotUsedInspectionTests.cs b/RubberduckTests/Inspections/ConstantNotUsedInspectionTests.cs index 97b4efd525..4200636757 100644 --- a/RubberduckTests/Inspections/ConstantNotUsedInspectionTests.cs +++ b/RubberduckTests/Inspections/ConstantNotUsedInspectionTests.cs @@ -100,6 +100,34 @@ Public Sub Goo(ByVal arg1 As Integer) } } + [Test] + [Category("Inspections")] + //See issue #4994 at https://github.com/rubberduck-vba/Rubberduck/issues/4994 + public void ConstantNotUsed_ConstantOnlyUsedInMidStatement_DoesNotReturnResult() + { + const string inputCode = + @"Function UnAccent(ByVal inputString As String) As String + Dim Index As Long, Position As Long + Const ACCENTED_CHARS As String = ""؟"" + Const ANSICHARACTERS As String = ""SZszYAAAAAACEEEEIIIIDNOOOOOUUUUYaaaaaaceeeeiiiidnooooouuuuyyoOYoO"" + For Index = 1 To Len(inputString) + Position = InStr(ACCENTED_CHARS, Mid$(inputString, Index, 1)) + If Position Then Mid$(inputString, Index) = Mid$(ANSICHARACTERS, Position, 1) + Next + UnAccent = inputString +End Function"; + + var vbe = MockVbeBuilder.BuildFromSingleStandardModule(inputCode, out _); + using (var state = MockParser.CreateAndParse(vbe.Object)) + { + + var inspection = new ConstantNotUsedInspection(state); + var inspectionResults = inspection.GetInspectionResults(CancellationToken.None); + + Assert.AreEqual(0, inspectionResults.Count()); + } + } + [Test] [Category("Inspections")] public void ConstantNotUsed_IgnoreModule_All_YieldsNoResult() diff --git a/RubberduckTests/Inspections/ParameterNotUsedInspectionTests.cs b/RubberduckTests/Inspections/ParameterNotUsedInspectionTests.cs index c33f693819..ca90173932 100644 --- a/RubberduckTests/Inspections/ParameterNotUsedInspectionTests.cs +++ b/RubberduckTests/Inspections/ParameterNotUsedInspectionTests.cs @@ -91,6 +91,33 @@ public void ParameterNotUsed_ReturnsResult_SomeParamsUsed() } } + [Test] + [Category("Inspections")] + //See issue #4496 at https://github.com/rubberduck-vba/Rubberduck/issues/4496 + public void ParameterNotUsed_RecursiveDefaultMemberAccess_ReturnsNoResult() + { + const string inputCode = + @"Public Sub Test(rst As ADODB.Recordset) + Debug.Print rst(""Field"") +End Sub"; + + var vbe = new MockVbeBuilder() + .ProjectBuilder("TestPRoject", ProjectProtection.Unprotected) + .AddComponent("Module1", ComponentType.StandardModule, inputCode) + .AddReference("ADODB", MockVbeBuilder.LibraryPathAdoDb, 6, 1) + .AddProjectToVbeBuilder() + .Build(); + + using (var state = MockParser.CreateAndParse(vbe.Object)) + { + + var inspection = new ParameterNotUsedInspection(state); + var inspectionResults = inspection.GetInspectionResults(CancellationToken.None); + + Assert.AreEqual(0, inspectionResults.Count()); + } + } + [Test] [Category("Inspections")] public void ParameterNotUsed_ReturnsResult_InterfaceImplementation() From da7acb4025909b646bc13972bf39219c33b5880e Mon Sep 17 00:00:00 2001 From: jonathan-rizk Date: Sun, 19 May 2019 14:12:34 -0500 Subject: [PATCH 15/32] added ExcelHotKeyAnnotation class. added test case for new attribute. --- .../Annotations/AnnotationType.cs | 4 +++- .../Annotations/ExcelHotKeyAnnotation.cs | 23 +++++++++++++++++++ .../AttributeAnnotationProviderTests.cs | 1 + 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 Rubberduck.Parsing/Annotations/ExcelHotKeyAnnotation.cs diff --git a/Rubberduck.Parsing/Annotations/AnnotationType.cs b/Rubberduck.Parsing/Annotations/AnnotationType.cs index 4ca22983b7..f7cecf4df7 100644 --- a/Rubberduck.Parsing/Annotations/AnnotationType.cs +++ b/Rubberduck.Parsing/Annotations/AnnotationType.cs @@ -72,7 +72,9 @@ public enum AnnotationType ModuleAttribute = 1 << 20 | Attribute | ModuleAnnotation, MemberAttribute = 1 << 21 | Attribute | MemberAnnotation | VariableAnnotation, [FlexibleAttributeValueAnnotation("VB_VarDescription", 1)] - VariableDescription = 1 << 13 | Attribute | VariableAnnotation + VariableDescription = 1 << 13 | Attribute | VariableAnnotation, + [FlexibleAttributeValueAnnotation("VB_ProcData.VB_Invoke_Func", 1)] + ExcelHotKey = 1 << 16 | Attribute | MemberAnnotation } [AttributeUsage(AttributeTargets.Field)] diff --git a/Rubberduck.Parsing/Annotations/ExcelHotKeyAnnotation.cs b/Rubberduck.Parsing/Annotations/ExcelHotKeyAnnotation.cs new file mode 100644 index 0000000000..eef87e3559 --- /dev/null +++ b/Rubberduck.Parsing/Annotations/ExcelHotKeyAnnotation.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Rubberduck.Parsing.Grammar; +using Rubberduck.VBEditor; + +namespace Rubberduck.Parsing.Annotations +{ + public sealed class ExcelHotKeyAnnotation : FlexibleAttributeValueAnnotationBase + { + public ExcelHotKeyAnnotation(AnnotationType annotationType, QualifiedSelection qualifiedSelection, VBAParser.AnnotationContext context, IEnumerable attributeValues) : + base(annotationType, qualifiedSelection, context, GetHotKeyAttributeValue(attributeValues)) + { } + + private static IEnumerable GetHotKeyAttributeValue(IEnumerable attributeValues) => + attributeValues.Take(1).Select(StripStringLiteralQuotes).Select(v => v[0] + @"\n14").ToList(); + + private static string StripStringLiteralQuotes(string value) => + value.StartsWith("\"") && value.EndsWith("\"") && value.Length > 2 + ? value.Substring(1, value.Length - 2) + : value; + } +} diff --git a/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs b/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs index a3819698aa..0d974ef2b6 100644 --- a/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs +++ b/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs @@ -56,6 +56,7 @@ public void ModuleAttributeAnnotationReturnsSpecializedAnnotationsWhereApplicabl AssertEqual(expectedValues, actualValues); } + [TestCase("VB_ProcData.VB_Invoke_Func", "A", AnnotationType.ExcelHotKey, @"A\n14")] [TestCase("VB_Description", "\"SomeDescription\"", AnnotationType.Description, "\"SomeDescription\"")] [TestCase("VB_VarDescription", "\"SomeDescription\"", AnnotationType.VariableDescription, "\"SomeDescription\"")] [TestCase("VB_UserMemId", "0", AnnotationType.DefaultMember)] From 7256d4dec188dfe1a7a42b5f143d962d97273c82 Mon Sep 17 00:00:00 2001 From: jonathan-rizk Date: Mon, 20 May 2019 18:24:10 -0500 Subject: [PATCH 16/32] swapped attribute value with annotation value in test case --- RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs b/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs index 0d974ef2b6..f67e4e2d84 100644 --- a/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs +++ b/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs @@ -56,7 +56,7 @@ public void ModuleAttributeAnnotationReturnsSpecializedAnnotationsWhereApplicabl AssertEqual(expectedValues, actualValues); } - [TestCase("VB_ProcData.VB_Invoke_Func", "A", AnnotationType.ExcelHotKey, @"A\n14")] + [TestCase("VB_ProcData.VB_Invoke_Func", @"A\n14", AnnotationType.ExcelHotKey, "A")] [TestCase("VB_Description", "\"SomeDescription\"", AnnotationType.Description, "\"SomeDescription\"")] [TestCase("VB_VarDescription", "\"SomeDescription\"", AnnotationType.VariableDescription, "\"SomeDescription\"")] [TestCase("VB_UserMemId", "0", AnnotationType.DefaultMember)] From d66c82f30ce16f35cfa3dffd7e3292f00aa7e9ce Mon Sep 17 00:00:00 2001 From: jonathan-rizk Date: Mon, 20 May 2019 18:42:52 -0500 Subject: [PATCH 17/32] concrete ctor now feeds specific AnnotationType to base --- Rubberduck.Parsing/Annotations/ExcelHotKeyAnnotation.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Rubberduck.Parsing/Annotations/ExcelHotKeyAnnotation.cs b/Rubberduck.Parsing/Annotations/ExcelHotKeyAnnotation.cs index eef87e3559..3c7dbae438 100644 --- a/Rubberduck.Parsing/Annotations/ExcelHotKeyAnnotation.cs +++ b/Rubberduck.Parsing/Annotations/ExcelHotKeyAnnotation.cs @@ -8,12 +8,12 @@ namespace Rubberduck.Parsing.Annotations { public sealed class ExcelHotKeyAnnotation : FlexibleAttributeValueAnnotationBase { - public ExcelHotKeyAnnotation(AnnotationType annotationType, QualifiedSelection qualifiedSelection, VBAParser.AnnotationContext context, IEnumerable attributeValues) : - base(annotationType, qualifiedSelection, context, GetHotKeyAttributeValue(attributeValues)) + public ExcelHotKeyAnnotation(QualifiedSelection qualifiedSelection, VBAParser.AnnotationContext context, IEnumerable parameters) : + base(AnnotationType.ExcelHotKey, qualifiedSelection, context, GetHotKeyAttributeValue(parameters)) { } - private static IEnumerable GetHotKeyAttributeValue(IEnumerable attributeValues) => - attributeValues.Take(1).Select(StripStringLiteralQuotes).Select(v => v[0] + @"\n14").ToList(); + private static IEnumerable GetHotKeyAttributeValue(IEnumerable parameters) => + parameters.Take(1).Select(StripStringLiteralQuotes).Select(v => v[0] + @"\n14").ToList(); private static string StripStringLiteralQuotes(string value) => value.StartsWith("\"") && value.EndsWith("\"") && value.Length > 2 From 9a094faa5652071860d65aa5aae75e50e610f5f4 Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Thu, 22 Aug 2019 20:54:35 +0200 Subject: [PATCH 18/32] Minor renaming to clarify some things --- .../Annotations/AnnotationType.cs | 2 +- .../Annotations/DescriptionAnnotation.cs | 2 +- .../Annotations/ExcelHotKeyAnnotation.cs | 4 ++-- .../FlexibleAttributeValueAnnotationBase.cs | 23 +++++++------------ .../Annotations/VBAParserAnnotationFactory.cs | 2 +- .../AttributeAnnotationProviderTests.cs | 7 +++--- RubberduckTests/Grammar/AnnotationTests.cs | 2 +- .../PostProcessing/AnnotationUpdaterTests.cs | 2 +- 8 files changed, 19 insertions(+), 25 deletions(-) diff --git a/Rubberduck.Parsing/Annotations/AnnotationType.cs b/Rubberduck.Parsing/Annotations/AnnotationType.cs index f7cecf4df7..c173abe579 100644 --- a/Rubberduck.Parsing/Annotations/AnnotationType.cs +++ b/Rubberduck.Parsing/Annotations/AnnotationType.cs @@ -57,7 +57,7 @@ public enum AnnotationType NoIndent = 1 << 18 | ModuleAnnotation, Interface = 1 << 19 | ModuleAnnotation, [FlexibleAttributeValueAnnotation("VB_Description", 1)] - Description = 1 << 13 | Attribute | MemberAnnotation, + MemberDescription = 1 << 13 | Attribute | MemberAnnotation, [FixedAttributeValueAnnotation("VB_UserMemId", "0")] DefaultMember = 1 << 14 | Attribute | MemberAnnotation, [FixedAttributeValueAnnotation("VB_UserMemId", "-4")] diff --git a/Rubberduck.Parsing/Annotations/DescriptionAnnotation.cs b/Rubberduck.Parsing/Annotations/DescriptionAnnotation.cs index d3e7e716f4..e9ee0ba4cd 100644 --- a/Rubberduck.Parsing/Annotations/DescriptionAnnotation.cs +++ b/Rubberduck.Parsing/Annotations/DescriptionAnnotation.cs @@ -10,7 +10,7 @@ namespace Rubberduck.Parsing.Annotations public sealed class DescriptionAnnotation : DescriptionAttributeAnnotationBase { public DescriptionAnnotation(QualifiedSelection qualifiedSelection, VBAParser.AnnotationContext context, IEnumerable parameters) - : base(AnnotationType.Description, qualifiedSelection, context, parameters) + : base(AnnotationType.MemberDescription, qualifiedSelection, context, parameters) {} } } \ No newline at end of file diff --git a/Rubberduck.Parsing/Annotations/ExcelHotKeyAnnotation.cs b/Rubberduck.Parsing/Annotations/ExcelHotKeyAnnotation.cs index 3c7dbae438..edae81792d 100644 --- a/Rubberduck.Parsing/Annotations/ExcelHotKeyAnnotation.cs +++ b/Rubberduck.Parsing/Annotations/ExcelHotKeyAnnotation.cs @@ -8,8 +8,8 @@ namespace Rubberduck.Parsing.Annotations { public sealed class ExcelHotKeyAnnotation : FlexibleAttributeValueAnnotationBase { - public ExcelHotKeyAnnotation(QualifiedSelection qualifiedSelection, VBAParser.AnnotationContext context, IEnumerable parameters) : - base(AnnotationType.ExcelHotKey, qualifiedSelection, context, GetHotKeyAttributeValue(parameters)) + public ExcelHotKeyAnnotation(QualifiedSelection qualifiedSelection, VBAParser.AnnotationContext context, IEnumerable annotationParameterValues) : + base(AnnotationType.ExcelHotKey, qualifiedSelection, context, GetHotKeyAttributeValue(annotationParameterValues)) { } private static IEnumerable GetHotKeyAttributeValue(IEnumerable parameters) => diff --git a/Rubberduck.Parsing/Annotations/FlexibleAttributeValueAnnotationBase.cs b/Rubberduck.Parsing/Annotations/FlexibleAttributeValueAnnotationBase.cs index 98b3ef22ba..2e00bd8e6d 100644 --- a/Rubberduck.Parsing/Annotations/FlexibleAttributeValueAnnotationBase.cs +++ b/Rubberduck.Parsing/Annotations/FlexibleAttributeValueAnnotationBase.cs @@ -8,26 +8,19 @@ namespace Rubberduck.Parsing.Annotations { public abstract class FlexibleAttributeValueAnnotationBase : AnnotationBase, IAttributeAnnotation { - protected FlexibleAttributeValueAnnotationBase(AnnotationType annotationType, QualifiedSelection qualifiedSelection, VBAParser.AnnotationContext context, IEnumerable parameters) + public string Attribute { get; } + public IReadOnlyList AttributeValues { get; } + + protected FlexibleAttributeValueAnnotationBase(AnnotationType annotationType, QualifiedSelection qualifiedSelection, VBAParser.AnnotationContext context, IEnumerable attributeValues) :base(annotationType, qualifiedSelection, context) { var flexibleAttributeValueInfo = FlexibleAttributeValueInfo(annotationType); - if (flexibleAttributeValueInfo == null) - { - Attribute = string.Empty; - AttributeValues = new List(); - return; - } - - Attribute = flexibleAttributeValueInfo.Value.attribute; - AttributeValues = parameters?.Take(flexibleAttributeValueInfo.Value.numberOfValues).ToList() ?? new List(); + Attribute = flexibleAttributeValueInfo.attribute; + AttributeValues = attributeValues?.Take(flexibleAttributeValueInfo.numberOfValues).ToList() ?? new List(); } - public string Attribute { get; } - public IReadOnlyList AttributeValues { get; } - - private static (string attribute, int numberOfValues)? FlexibleAttributeValueInfo(AnnotationType annotationType) + private static (string attribute, int numberOfValues) FlexibleAttributeValueInfo(AnnotationType annotationType) { var type = annotationType.GetType(); var name = Enum.GetName(type, annotationType); @@ -38,7 +31,7 @@ private static (string attribute, int numberOfValues)? FlexibleAttributeValueInf if (attribute == null) { - return null; + return ("", 0); } return (attribute.AttributeName, attribute.NumberOfParameters); diff --git a/Rubberduck.Parsing/Annotations/VBAParserAnnotationFactory.cs b/Rubberduck.Parsing/Annotations/VBAParserAnnotationFactory.cs index faf05034d5..020986ae5c 100644 --- a/Rubberduck.Parsing/Annotations/VBAParserAnnotationFactory.cs +++ b/Rubberduck.Parsing/Annotations/VBAParserAnnotationFactory.cs @@ -24,7 +24,7 @@ public VBAParserAnnotationFactory() _creators.Add(AnnotationType.Folder.ToString().ToUpperInvariant(), typeof(FolderAnnotation)); _creators.Add(AnnotationType.NoIndent.ToString().ToUpperInvariant(), typeof(NoIndentAnnotation)); _creators.Add(AnnotationType.Interface.ToString().ToUpperInvariant(), typeof(InterfaceAnnotation)); - _creators.Add(AnnotationType.Description.ToString().ToUpperInvariant(), typeof (DescriptionAnnotation)); + _creators.Add(AnnotationType.MemberDescription.ToString().ToUpperInvariant(), typeof (DescriptionAnnotation)); _creators.Add(AnnotationType.PredeclaredId.ToString().ToUpperInvariant(), typeof(PredeclaredIdAnnotation)); _creators.Add(AnnotationType.DefaultMember.ToString().ToUpperInvariant(), typeof(DefaultMemberAnnotation)); _creators.Add(AnnotationType.Enumerator.ToString().ToUpperInvariant(), typeof(EnumeratorMemberAnnotation)); diff --git a/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs b/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs index f67e4e2d84..6608095d62 100644 --- a/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs +++ b/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs @@ -5,6 +5,7 @@ namespace RubberduckTests.Annotations { [TestFixture] + [Category("Annotations")] public class AttributeAnnotationProviderTests { [Test] @@ -57,13 +58,13 @@ public void ModuleAttributeAnnotationReturnsSpecializedAnnotationsWhereApplicabl } [TestCase("VB_ProcData.VB_Invoke_Func", @"A\n14", AnnotationType.ExcelHotKey, "A")] - [TestCase("VB_Description", "\"SomeDescription\"", AnnotationType.Description, "\"SomeDescription\"")] + [TestCase("VB_Description", "\"SomeDescription\"", AnnotationType.MemberDescription, "\"SomeDescription\"")] [TestCase("VB_VarDescription", "\"SomeDescription\"", AnnotationType.VariableDescription, "\"SomeDescription\"")] [TestCase("VB_UserMemId", "0", AnnotationType.DefaultMember)] [TestCase("VB_UserMemId", "-4", AnnotationType.Enumerator)] - public void MemberAttributeAnnotationReturnsSpecializedAnnotationsWhereApplicable(string attributeName, string annotationValue, AnnotationType expectedAnnotationType, string expectedValue = null) + public void MemberAttributeAnnotationReturnsSpecializedAnnotationsWhereApplicable(string attributeName, string attributeValue, AnnotationType expectedAnnotationType, string expectedValue = null) { - var attributeValues = new List { annotationValue }; + var attributeValues = new List { attributeValue }; var expectedValues = expectedValue != null ? new List { expectedValue } : new List(); diff --git a/RubberduckTests/Grammar/AnnotationTests.cs b/RubberduckTests/Grammar/AnnotationTests.cs index 2b3b100257..45bdc99c77 100644 --- a/RubberduckTests/Grammar/AnnotationTests.cs +++ b/RubberduckTests/Grammar/AnnotationTests.cs @@ -139,7 +139,7 @@ public void MemberAttributeAnnotation_TypeIsMemberAttribute() public void DescriptionAnnotation_TypeIsDescription() { var annotation = new DescriptionAnnotation(new QualifiedSelection(), null, new[] { "Desc"}); - Assert.AreEqual(AnnotationType.Description, annotation.AnnotationType); + Assert.AreEqual(AnnotationType.MemberDescription, annotation.AnnotationType); } [Category("Grammar")] diff --git a/RubberduckTests/PostProcessing/AnnotationUpdaterTests.cs b/RubberduckTests/PostProcessing/AnnotationUpdaterTests.cs index bdad3e83bc..b458ced6e1 100644 --- a/RubberduckTests/PostProcessing/AnnotationUpdaterTests.cs +++ b/RubberduckTests/PostProcessing/AnnotationUpdaterTests.cs @@ -728,7 +728,7 @@ End Sub var fooDeclaration = state.DeclarationFinder .UserDeclarations(DeclarationType.Procedure) .First(decl => decl.IdentifierName == "Foo"); - var annotationToUpdate = fooDeclaration.Annotations.First(annotation => annotation.AnnotationType == AnnotationType.Description); + var annotationToUpdate = fooDeclaration.Annotations.First(annotation => annotation.AnnotationType == AnnotationType.MemberDescription); var annotationUpdater = new AnnotationUpdater(); annotationUpdater.UpdateAnnotation(rewriteSession, annotationToUpdate, newAnnotation, newAnnotationValues); From 73a3ffd735ecd0531c63d3c214c65e15851d3921 Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Thu, 22 Aug 2019 20:55:25 +0200 Subject: [PATCH 19/32] Add special case for ExcelHotkeyAnnotation in the AttributeAnnotationProvider --- .../Annotations/AttributeAnnotationProvider.cs | 5 +++++ Rubberduck.Parsing/Annotations/VBAParserAnnotationFactory.cs | 1 + 2 files changed, 6 insertions(+) diff --git a/Rubberduck.Parsing/Annotations/AttributeAnnotationProvider.cs b/Rubberduck.Parsing/Annotations/AttributeAnnotationProvider.cs index b2508239e8..52ae135696 100644 --- a/Rubberduck.Parsing/Annotations/AttributeAnnotationProvider.cs +++ b/Rubberduck.Parsing/Annotations/AttributeAnnotationProvider.cs @@ -33,6 +33,11 @@ public class AttributeAnnotationProvider : IAttributeAnnotationProvider var flexibleValueAttributeAnnotation = FirstMatchingFlexibleAttributeValueAnnotation(annotationTypes, attributeName, attributeValues.Count); if (flexibleValueAttributeAnnotation != default) { + // FIXME special cased bodge for ExcelHotKeyAnnotation to deal with the value transformation: + if (flexibleValueAttributeAnnotation == AnnotationType.ExcelHotKey) + { + return (flexibleValueAttributeAnnotation, attributeValues.Select(keySpec => keySpec.Substring(0, 1)).ToList()); + } return (flexibleValueAttributeAnnotation, attributeValues); } diff --git a/Rubberduck.Parsing/Annotations/VBAParserAnnotationFactory.cs b/Rubberduck.Parsing/Annotations/VBAParserAnnotationFactory.cs index 020986ae5c..d9860cc528 100644 --- a/Rubberduck.Parsing/Annotations/VBAParserAnnotationFactory.cs +++ b/Rubberduck.Parsing/Annotations/VBAParserAnnotationFactory.cs @@ -33,6 +33,7 @@ public VBAParserAnnotationFactory() _creators.Add(AnnotationType.ModuleAttribute.ToString().ToUpperInvariant(), typeof(ModuleAttributeAnnotation)); _creators.Add(AnnotationType.MemberAttribute.ToString().ToUpperInvariant(), typeof(MemberAttributeAnnotation)); _creators.Add(AnnotationType.ModuleDescription.ToString().ToUpperInvariant(), typeof(ModuleDescriptionAnnotation)); + _creators.Add(AnnotationType.ExcelHotKey.ToString().ToUpperInvariant(), typeof(ExcelHotKeyAnnotation)); } public IAnnotation Create(VBAParser.AnnotationContext context, QualifiedSelection qualifiedSelection) From 870cbdf8915dc7c0d5ec8de84430aea0ad8fb7f7 Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Thu, 22 Aug 2019 21:52:22 +0200 Subject: [PATCH 20/32] Revert Description -> MemberDescription rename because I was an idiot --- Rubberduck.Parsing/Annotations/AnnotationType.cs | 2 +- Rubberduck.Parsing/Annotations/DescriptionAnnotation.cs | 2 +- Rubberduck.Parsing/Annotations/VBAParserAnnotationFactory.cs | 2 +- RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs | 2 +- RubberduckTests/Grammar/AnnotationTests.cs | 2 +- RubberduckTests/PostProcessing/AnnotationUpdaterTests.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Rubberduck.Parsing/Annotations/AnnotationType.cs b/Rubberduck.Parsing/Annotations/AnnotationType.cs index c173abe579..f7cecf4df7 100644 --- a/Rubberduck.Parsing/Annotations/AnnotationType.cs +++ b/Rubberduck.Parsing/Annotations/AnnotationType.cs @@ -57,7 +57,7 @@ public enum AnnotationType NoIndent = 1 << 18 | ModuleAnnotation, Interface = 1 << 19 | ModuleAnnotation, [FlexibleAttributeValueAnnotation("VB_Description", 1)] - MemberDescription = 1 << 13 | Attribute | MemberAnnotation, + Description = 1 << 13 | Attribute | MemberAnnotation, [FixedAttributeValueAnnotation("VB_UserMemId", "0")] DefaultMember = 1 << 14 | Attribute | MemberAnnotation, [FixedAttributeValueAnnotation("VB_UserMemId", "-4")] diff --git a/Rubberduck.Parsing/Annotations/DescriptionAnnotation.cs b/Rubberduck.Parsing/Annotations/DescriptionAnnotation.cs index e9ee0ba4cd..d3e7e716f4 100644 --- a/Rubberduck.Parsing/Annotations/DescriptionAnnotation.cs +++ b/Rubberduck.Parsing/Annotations/DescriptionAnnotation.cs @@ -10,7 +10,7 @@ namespace Rubberduck.Parsing.Annotations public sealed class DescriptionAnnotation : DescriptionAttributeAnnotationBase { public DescriptionAnnotation(QualifiedSelection qualifiedSelection, VBAParser.AnnotationContext context, IEnumerable parameters) - : base(AnnotationType.MemberDescription, qualifiedSelection, context, parameters) + : base(AnnotationType.Description, qualifiedSelection, context, parameters) {} } } \ No newline at end of file diff --git a/Rubberduck.Parsing/Annotations/VBAParserAnnotationFactory.cs b/Rubberduck.Parsing/Annotations/VBAParserAnnotationFactory.cs index d9860cc528..46c8ca27ea 100644 --- a/Rubberduck.Parsing/Annotations/VBAParserAnnotationFactory.cs +++ b/Rubberduck.Parsing/Annotations/VBAParserAnnotationFactory.cs @@ -24,7 +24,7 @@ public VBAParserAnnotationFactory() _creators.Add(AnnotationType.Folder.ToString().ToUpperInvariant(), typeof(FolderAnnotation)); _creators.Add(AnnotationType.NoIndent.ToString().ToUpperInvariant(), typeof(NoIndentAnnotation)); _creators.Add(AnnotationType.Interface.ToString().ToUpperInvariant(), typeof(InterfaceAnnotation)); - _creators.Add(AnnotationType.MemberDescription.ToString().ToUpperInvariant(), typeof (DescriptionAnnotation)); + _creators.Add(AnnotationType.Description.ToString().ToUpperInvariant(), typeof (DescriptionAnnotation)); _creators.Add(AnnotationType.PredeclaredId.ToString().ToUpperInvariant(), typeof(PredeclaredIdAnnotation)); _creators.Add(AnnotationType.DefaultMember.ToString().ToUpperInvariant(), typeof(DefaultMemberAnnotation)); _creators.Add(AnnotationType.Enumerator.ToString().ToUpperInvariant(), typeof(EnumeratorMemberAnnotation)); diff --git a/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs b/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs index 6608095d62..236aed4ff4 100644 --- a/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs +++ b/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs @@ -58,7 +58,7 @@ public void ModuleAttributeAnnotationReturnsSpecializedAnnotationsWhereApplicabl } [TestCase("VB_ProcData.VB_Invoke_Func", @"A\n14", AnnotationType.ExcelHotKey, "A")] - [TestCase("VB_Description", "\"SomeDescription\"", AnnotationType.MemberDescription, "\"SomeDescription\"")] + [TestCase("VB_Description", "\"SomeDescription\"", AnnotationType.Description, "\"SomeDescription\"")] [TestCase("VB_VarDescription", "\"SomeDescription\"", AnnotationType.VariableDescription, "\"SomeDescription\"")] [TestCase("VB_UserMemId", "0", AnnotationType.DefaultMember)] [TestCase("VB_UserMemId", "-4", AnnotationType.Enumerator)] diff --git a/RubberduckTests/Grammar/AnnotationTests.cs b/RubberduckTests/Grammar/AnnotationTests.cs index 45bdc99c77..2b3b100257 100644 --- a/RubberduckTests/Grammar/AnnotationTests.cs +++ b/RubberduckTests/Grammar/AnnotationTests.cs @@ -139,7 +139,7 @@ public void MemberAttributeAnnotation_TypeIsMemberAttribute() public void DescriptionAnnotation_TypeIsDescription() { var annotation = new DescriptionAnnotation(new QualifiedSelection(), null, new[] { "Desc"}); - Assert.AreEqual(AnnotationType.MemberDescription, annotation.AnnotationType); + Assert.AreEqual(AnnotationType.Description, annotation.AnnotationType); } [Category("Grammar")] diff --git a/RubberduckTests/PostProcessing/AnnotationUpdaterTests.cs b/RubberduckTests/PostProcessing/AnnotationUpdaterTests.cs index b458ced6e1..bdad3e83bc 100644 --- a/RubberduckTests/PostProcessing/AnnotationUpdaterTests.cs +++ b/RubberduckTests/PostProcessing/AnnotationUpdaterTests.cs @@ -728,7 +728,7 @@ End Sub var fooDeclaration = state.DeclarationFinder .UserDeclarations(DeclarationType.Procedure) .First(decl => decl.IdentifierName == "Foo"); - var annotationToUpdate = fooDeclaration.Annotations.First(annotation => annotation.AnnotationType == AnnotationType.MemberDescription); + var annotationToUpdate = fooDeclaration.Annotations.First(annotation => annotation.AnnotationType == AnnotationType.Description); var annotationUpdater = new AnnotationUpdater(); annotationUpdater.UpdateAnnotation(rewriteSession, annotationToUpdate, newAnnotation, newAnnotationValues); From 13cffc4edaa3c3931a27091f095caa40536880af Mon Sep 17 00:00:00 2001 From: Mathieu Guindon Date: Thu, 22 Aug 2019 21:50:07 -0400 Subject: [PATCH 21/32] ignore the xmldoc export --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 535cccc51e..ada6e4eb90 100644 --- a/.gitignore +++ b/.gitignore @@ -186,3 +186,4 @@ CodeGraphData/ #Gradle /.gradle/ +/Rubberduck.CodeAnalysis/Rubberduck.CodeAnalysis.xml From d1cb84ab61a9bf5e53d0a4afce3efa65a3c1b664 Mon Sep 17 00:00:00 2001 From: Mathieu Guindon Date: Thu, 22 Aug 2019 22:44:54 -0400 Subject: [PATCH 22/32] Delete Rubberduck.CodeAnalysis.xml --- .../Rubberduck.CodeAnalysis.xml | 2468 ----------------- 1 file changed, 2468 deletions(-) delete mode 100644 Rubberduck.CodeAnalysis/Rubberduck.CodeAnalysis.xml diff --git a/Rubberduck.CodeAnalysis/Rubberduck.CodeAnalysis.xml b/Rubberduck.CodeAnalysis/Rubberduck.CodeAnalysis.xml deleted file mode 100644 index d1f8fd29ab..0000000000 --- a/Rubberduck.CodeAnalysis/Rubberduck.CodeAnalysis.xml +++ /dev/null @@ -1,2468 +0,0 @@ - - - - Rubberduck.CodeAnalysis - - - - - The name of the metric. Used for localization purposes as well as a uniquely identifying name to disambiguate between metrics. - - - - - The aggregation level that this metric applies to. - - - - - A CodeMetricsResult. Each result is attached to a Declaration. - Usually this declaration would be a Procedure (Function/Sub/Property). - Some metrics are only useful on Module level, some even on Project level. - - Some metrics may be aggregated to obtain a metric for a "higher hierarchy level" - - - - - The declaration that this result refers to. - - - - - The Metric kind that this result belongs to. Only results belonging to the **same** metric can be aggregated. - - - - - A string representation of the value. - - - - - Flags 'While...Wend' loops as obsolete. - - - 'While...Wend' loops were made obsolete when 'Do While...Loop' statements were introduced. - 'While...Wend' loops cannot be exited early without a GoTo jump; 'Do...Loop' statements can be conditionally exited with 'Exit Do'. - - - - - - - - - - - Locates assignments to object variables for which the RHS does not have a compatible declared type. - - - The VBA compiler does not check whether different object types are compatible. Instead there is a runtime error whenever the types are incompatible. - - - - - - - - - - - Default constructor required for XML serialization. - - - - - Gets a localized string representing a short name/description for the inspection. - - - - - Gets the type of inspection; used for regrouping inspections. - - - - - The inspection type name, obtained by reflection. - - - - - Inspection severity level. Can control whether an inspection is enabled. - - - - - Meta-information about why an inspection exists. - - - - - Gets a localized string representing the type of inspection. - - - - - - Gets a string representing the text that must be present in an - @Ignore annotation to disable the inspection at a given site. - - - - - Gets all declarations in the parser state without an @Ignore annotation for this inspection. - - - - - Gets all user declarations in the parser state without an @Ignore annotation for this inspection. - - - - - A method that inspects the parser state and returns all issues it can find. - - - - - - - Gets the information needed to select the target instruction in the VBE. - - - - - WARNING: This property can have side effects. It can change the ActiveVBProject if the result has a null Declaration, - which causes a flicker in the VBE. This should only be called if it is *absolutely* necessary. - - - - - Locates public User-Defined Function procedures accidentally named after a cell reference. - - - - Another good reason to avoid numeric suffixes: if the function is meant to be used as a UDF in a cell formula, - the worksheet cell by the same name takes precedence and gets the reference, and the function is never invoked. - - - - - - - - - - - Identifies uses of 'IsMissing' involving a non-parameter argument. - - - 'IsMissing' only returns True when an optional Variant parameter was not supplied as an argument. - This inspection flags uses that attempt to use 'IsMissing' for other purposes, resulting in conditions that are always False. - - - - - - - - - - - Warns about 'Declare' statements that are using the obsolete/unsupported 'CDecl' calling convention on Windows. - - - The CDecl calling convention is only implemented in VBA for Mac; if Rubberduck can see it (Rubberduck only runs on Windows), - then the declaration is using an unsupported (no-op) calling convention on Windows. - - - - - - - - - - - Flags usages of members marked as obsolete with an @Obsolete("justification") Rubberduck annotation. - - - Marking members as obsolete can help refactoring a legacy code base. This inspection is a tool that makes it easy to locate obsolete member calls. - - - - - - - - - - - - This inpection is flagging code we dubbed "ThunderCode", - code our friend Andrew Jackson would have written to confuse Rubberduck's parser and/or resolver. - While perfectly legal as Type or Enum member names, these identifiers should be avoided: - they need to be square-bracketed everywhere they are used. - - - - - - This inpection is flagging code we dubbed "ThunderCode", - code our friend Andrew Jackson would have written to confuse Rubberduck's parser and/or resolver. - While perfectly legal, these line continuations serve no purpose and should be removed. - - - Note that the inspection only checks a subset of possible "evil" line continatuions - for both simplicity and performance reasons. Exhaustive inspection would likely take too much effort. - - - - - - This inpection is flagging code we dubbed "ThunderCode", - code our friend Andrew Jackson would have written to confuse Rubberduck's parser and/or resolver. - The VBE does allow rather strange and unbelievable things to happen. - - - - - - This inpection is flagging code we dubbed "ThunderCode", - code our friend Andrew Jackson would have written to confuse Rubberduck's parser and/or resolver. - This inspection may accidentally reveal non-breaking spaces in code copied and pasted from a website. - - - - - - This inpection is flagging code we dubbed "ThunderCode", - code our friend Andrew Jackson would have written to confuse Rubberduck's parser and/or resolver. - 'On Error GoTo -1' is poorly documented and uselessly complicates error handling. - - - - - Checks a block of code for executable statments and returns true if are present. - - The block to inspect - Determines wheather Dim or Const statements should be considered executables - - - - - Warns about parameters passed by value being assigned a new value in the body of a procedure. - - - Debugging is easier if the procedure's initial state is preserved and accessible anywhere within its scope. - Mutating the inputs destroys the initial state, and makes the intent ambiguous: if the calling code is meant - to be able to access the modified values, then the parameter should be passed ByRef; the ByVal modifier might be a bug. - - - - - - - - - - - Warns about a variable that is assigned, and then re-assigned before the first assignment is read. - - - The first assignment is likely redundant, since it is being overwritten by the second. - - - - - - - - - - - Indicates that the value of a hidden VB attribute is out of sync with the corresponding Rubberduck annotation comment. - - - Keeping Rubberduck annotation comments in sync with the hidden VB attribute values, surfaces these hidden attributes in the VBE code panes; - Rubberduck can rewrite the attributes to match the corresponding annotation comment. - - - - - - - - - - - Identifies redundant Boolean expressions in conditionals. - - - A Boolean expression never needs to be compared to a Boolean literal in a conditional expression. - - - - - - - - - - - Locates 'Const' declarations that are never referenced. - - - Declarations that are never used should be removed. - - - - - - - - - - - This inspection means to indicate when the project has not been renamed. - - - VBA projects should be meaningfully named, to avoid namespace clashes when referencing other VBA projects. - - - - - Warns about Def[Type] statements. - - - These declarative statements make the first letter of identifiers determine the data type. - - - - - - - - Warns about duplicated annotations. - - - Rubberduck annotations should not be specified more than once for a given module, member, variable, or expression. - - - - - - - - - - - Identifies empty 'Case' blocks that can be safely removed. - - - Case blocks in VBA do not "fall through"; an empty 'Case' block might be hiding a bug. - - - 0 - Debug.Print foo ' does not run if foo is 0. - End Select - End Sub - ]]> - - - 0 - '...code... - End Select - End Sub - ]]> - - - - - Identifies empty 'Do...Loop While' blocks that can be safely removed. - - - Dead code should be removed. A loop without a body is usually redundant. - - - - - - - - - - - Identifies empty 'Else' blocks that can be safely removed. - - - Empty code blocks are redundant, dead code that should be removed. They can also be misleading about their intent: - an empty block may be signalling an unfinished thought or an oversight. - - - - - - - - - - - Identifies empty 'For Each...Next' blocks that can be safely removed. - - - Dead code should be removed. A loop without a body is usually redundant. - - - - - - - - - - - Identifies empty 'For...Next' blocks that can be safely removed. - - - Dead code should be removed. A loop without a body is usually redundant. - - - - - - - - - - - Identifies empty 'If' blocks. - - - Conditional expression is inverted; there would not be a need for an 'Else' block otherwise. - - - - - - - - - - - Identifies empty module member blocks. - - - Methods containing no executable statements are misleading as they appear to be doing something which they actually don't. - This might be the result of delaying the actual implementation for a later stage of development, and then forgetting all about that. - - - - - - - - - - - Flags empty code modules. - - - An empty module does not need to exist and can be safely removed. - - - - - Flags uses of an empty string literal (""). - - - Standard library constant 'vbNullString' is more explicit about its intent, and should be preferred to a string literal. - While the memory gain is meaningless, an empty string literal still takes up 2 bytes of memory, - but 'vbNullString' is a null string pointer, and doesn't. - - - - - - - - - - - Identifies empty 'While...Wend' blocks that can be safely removed. - - - Dead code should be removed. A loop without a body is usually redundant. - - - - - - - - - - - Flags publicly exposed instance fields. - - - Instance fields are the implementation details of a object's internal state; exposing them directly breaks encapsulation. - Often, an object only needs to expose a 'Get' procedure to expose an internal instance field. - - - - - - - - - - - Warns about late-bound WorksheetFunction calls made against the extended interface of the Application object. - - - - An early-bound, equivalent function exists in the object returned by the Application.WorksheetFunction property; - late-bound member calls will fail at run-time with error 438 if there is a typo (a typo fails to compile for an early-bound member call); - given invalid inputs, these late-bound member calls return a Variant/Error value that cannot be coerced into another type. - The equivalent early-bound member calls raise a more VB-idiomatic, trappable runtime error given the same invalid inputs: - trying to compare or assign a Variant/Error to another data type will throw error 13 "type mismatch" at run-time. - A Variant/Error value cannot be coerced into any other data type, be it for assignment or comparison. - - - - 15 Then - ' won't run, error 13 "type mismatch" will be thrown when Variant/Error is compared to an Integer. - End If - End Sub - ]]> - - - 15 Then ' raises error 1004 - ' won't run, error 1004 is raised when "ABC" is processed by WorksheetFunction.Sum, before it returns. - End If - End Sub - ]]> - - - - Locates instances of member calls made against the result of a Range.Find/FindNext/FindPrevious method, without prior validation. - - - Range.Find methods return a Range object reference that refers to the cell containing the search string; - this object reference will be Nothing if the search didn't turn up any results, and a member call against Nothing will raise run-time error 91. - - - - - - - - - - - Locates unqualified Worksheet.Range/Cells/Columns/Rows member calls that implicitly refer to ActiveSheet. - - - - Implicit references to the active worksheet rarely mean to be working with *whatever worksheet is currently active*. - By explicitly qualifying these member calls with a specific Worksheet object, the assumptions are removed, the code - is more robust, and will be less likely to throw run-time error 1004 or produce unexpected results - when the active sheet isn't the expected one. - - - - - - - - - - - Locates unqualified Workbook.Worksheets/Sheets/Names member calls that implicitly refer to ActiveWorkbook. - - - - Implicit references to the active workbook rarely mean to be working with *whatever workbook is currently active*. - By explicitly qualifying these member calls with a specific Workbook object, the assumptions are removed, the code - is more robust, and will be less likely to throw run-time error 1004 or produce unexpected results - when the active workbook isn't the expected one. - - - - - - - - - - - Locates ThisWorkbook.Worksheets and ThisWorkbook.Sheets calls that appear to be dereferencing a worksheet that is already accessible at compile-time with a global-scope identifier. - - - Sheet names can be changed by the user, as can a worksheet's index in ThisWorkbook.Worksheets. - Worksheets that exist in ThisWorkbook at compile-time are more reliably programmatically accessed using their CodeName, - which cannot be altered by the user without accessing the VBE and altering the VBA project. - - - - - For performance reasons, the inspection only evaluates hard-coded string literals; string-valued expressions evaluating into a sheet name are ignored. - - - - - - - - - - - Warns when a user function's return value is never used, at any of its call sites. - - - A 'Function' procedure normally means its return value to be captured and consumed by the calling code. - It's possible that not all call sites need the return value, but if the value is systematically discarded then this - means the function is side-effecting, and thus should probably be a 'Sub' procedure instead. - - - - - - - - - - - Warns about host-evaluated square-bracketed expressions. - - - Host-evaluated expressions should be implementable using the host application's object model. - If the expression yields an object, member calls against that object are late-bound. - - - - - - - - - - - Flags identifiers that use [Systems] Hungarian Notation prefixes. - - - Systems Hungarian (encoding data types in variable names) stemmed from a misunderstanding of what its inventor meant - when they described that prefixes identified the "kind" of variable in a naming scheme dubbed Apps Hungarian. - Modern naming conventions in all programming languages heavily discourage the use of Systems Hungarian prefixes. - - - - - - - - - - - Flags invalid Rubberduck annotation comments. - - - Rubberduck is correctly parsing an annotation, but that annotation is illegal in that context. - - - - - - - - - - - Identifies implemented members of class modules that are used as interfaces. - - - Interfaces provide a unified programmatic access to different objects, and therefore are rarely instantiated as concrete objects. - - - - - - - - - - - Highlights implicit ByRef modifiers in user code. - - - In modern VB (VB.NET), the implicit modifier is ByVal, as it is in most other programming languages. - Making the ByRef modifiers explicit can help surface potentially unexpected language defaults. - - - - - - - - - - - Identifies implicit default member calls. - - - Code should do what it says, and say what it does. Implicit default member calls generally do the opposite of that. - - - - - - - - - - - Highlights implicit Public access modifiers in user code. - - - In modern VB (VB.NET), the implicit access modifier is Private, as it is in most other programming languages. - Making the Public modifiers explicit can help surface potentially unexpected language defaults. - - - - - - - - - - - Warns about 'Function' and 'Property Get' procedures that don't have an explicit return type. - - - All functions return something, whether a type is specified or not. The implicit default is 'Variant'. - - - - - - - - - - - Identifies obsolete 16-bit integer variables. - - - Modern processors are optimized for processing 32-bit integers; internally, a 16-bit integer is still stored as a 32-bit value. - Unless code is interacting with APIs that require a 16-bit integer, a Long (32-bit integer) should be used instead. - - - - - - - - - - - Identifies uses of 'IsMissing' involving non-variant, non-optional, or array parameters. - - - 'IsMissing' only returns True when an optional Variant parameter was not supplied as an argument. - This inspection flags uses that attempt to use 'IsMissing' for other purposes, resulting in conditions that are always False. - - - - - - - - - - - Identifies line labels that are never referenced, and therefore superfluous. - - - Line labels are useful for GoTo, GoSub, Resume, and On Error statements; but the intent of a line label - can be confusing if it isn't referenced by any such instruction. - - - - - - - - - - - Warns about member calls against an extensible interface, that cannot be validated at compile-time. - - - Extensible COM types can have members attached at run-time; VBA cannot bind these member calls at compile-time. - If there is an early-bound alternative way to achieve the same result, it should be preferred. - - - - - - - - - - - Warns about a malformed Rubberduck annotation that is missing an argument. - - - Some annotations require arguments; if the argument isn't specified, the annotation is nothing more than an obscure comment. - - - - - - - - - - - Indicates that a Rubberduck annotation is documenting the presence of a VB attribute that is actually missing. - - - Rubberduck annotations mean to document the presence of hidden VB attributes; this inspection flags annotations that - do not have a corresponding VB attribute. - - - - - - - - - - - Indicates that a hidden VB attribute is present for a member, but no Rubberduck annotation is documenting it. - - - Rubberduck annotations mean to document the presence of hidden VB attributes; this inspection flags members that - do not have a Rubberduck annotation corresponding to the hidden VB attribute. - - - - - - - - - - - Indicates that a hidden VB attribute is present for a module, but no Rubberduck annotation is documenting it. - - - Rubberduck annotations mean to document the presence of hidden VB attributes; this inspection flags modules that - do not have a Rubberduck annotation corresponding to the hidden VB attribute. - - - - - - - - - - - Warns about module-level declarations made using the 'Dim' keyword. - - - Private module variables should be declared using the 'Private' keyword. While 'Dim' is also legal, it should preferably be - restricted to declarations of procedure-scoped local variables, for consistency, since public module variables are declared with the 'Public' keyword. - - - - - - - - - - - Indicates that a user module is missing a @Folder Rubberduck annotation. - - - Modules without a custom @Folder annotation will be grouped under the default folder in the Code Explorer toolwindow. - By specifying a custom @Folder annotation, modules can be organized by functionality rather than simply listed. - - - - - - - - - - - Locates module-level fields that can be moved to a smaller scope. - - - Module-level variables that are only used in a single procedure can often be declared in that procedure's scope. - Declaring variables closer to where they are used generally makes the code easier to follow. - - - - - - - - - - - Flags parameters declared across multiple physical lines of code. - - - When splitting a long list of parameters across multiple lines, care should be taken to avoid splitting a parameter declaration in two. - - - - - - - - - - - Flags declaration statements spanning multiple physical lines of code. - - - Declaration statements should generally declare a single variable. - - - - - - - - - - - Warns about 'Function' and 'Property Get' procedures whose return value is not assigned. - - - Both 'Function' and 'Property Get' accessors should always return something. Omitting the return assignment is likely a bug. - - - - - - - - - - - A visitor that visits a member's body and returns true if any LET statement (assignment) is assigning the specified name. - - - - - Warns about assignments that appear to be assigning an object reference without the 'Set' keyword. - - - Omitting the 'Set' keyword will Let-coerce the right-hand side (RHS) of the assignment expression. If the RHS is an object variable, - then the assignment is implicitly assigning to that object's default member, which may raise run-time error 91 at run-time. - - - - - - - - - - - Locates explicit 'Call' statements. - - - The 'Call' keyword is obsolete and redundant, since call statements are legal and generally more consistent without it. - - - - - - - - - - - Locates legacy 'Rem' comments. - - - Modern VB comments use a single quote character (') to denote the beginning of a comment: the legacy 'Rem' syntax is obsolete. - - - - - - - - - - - Locates legacy 'Error' statements. - - - The legacy syntax is obsolete; prefer 'Err.Raise' instead. - - - - - - - - - - - Locates legacy 'Global' declaration statements. - - - The legacy syntax is obsolete; use the 'Public' keyword instead. - - - - - - - - - - - Locates explicit 'Let' assignments. - - - The legacy syntax is obsolete/redundant; prefer implicit Let-coercion instead. - - - - - - - - - - - Flags declarations where a type hint is used in place of an 'As' clause. - - - Type hints were made obsolete when declaration syntax introduced the 'As' keyword. Prefer explicit type names over type hint symbols. - - - - - - - - - - - Flags obsolete 'On Local Error' statements. - - - All errors are "local" - the keyword is redundant/confusing and should be removed. - - - - - - - - - - - Flags modules that specify Option Base 1. - - - Implicit array lower bound is 0 by default, and Option Base 1 makes it 1. While compelling in a 1-based environment like the Excel object model, - having an implicit lower bound of 1 for implicitly-sized user arrays does not change the fact that arrays are always better off with explicit boundaries. - Because 0 is always the lower array bound in many other programming languages, this option may trip a reader/maintainer with a different background. - - - - - - - - - - - Flags modules that omit Option Explicit. - - - This option makes variable declarations mandatory. Without it, a typo gets compiled as a new on-the-spot Variant/Empty variable with a new name. - Omitting this option amounts to refusing the little help the VBE can provide with compile-time validation. - - - - - - - - - - - Flags parameters that are passed by reference (ByRef), but could be passed by value (ByVal). - - - Explicitly specifying a ByVal modifier on a parameter makes the intent explicit: this parameter is not meant to be assigned. In contrast, - a parameter that is passed by reference (implicitly, or explicitly ByRef) makes it ambiguous from the calling code's standpoint, whether the - procedure might re-assign these ByRef values and introduce a bug. - - - - - - - - - - - Identifies parameter declarations that are not used. - - - Declarations that are not used anywhere should probably be removed. - - - Not all unused parameters can/should be removed: ignore any inspection results for - event handler procedures and interface members that Rubberduck isn't recognizing as such. - - - - - - - - - - - Warns about 'Sub' procedures that could be refactored into a 'Function'. - - - Idiomatic VB code uses 'Function' procedures to return a single value. If the procedure isn't side-effecting, consider writing is as a - 'Function' rather than a 'Sub' the returns a result through a 'ByRef' parameter. - - - - - - - - - - - Locates procedures that are never invoked from user code. - - - Unused procedures are dead code that should probably be removed. Note, a procedure may be effectively "not used" in code, but attached to some - Shape object in the host document: in such cases the inspection result should be ignored. An event handler procedure that isn't being - resolved as such, may also wrongly trigger this inspection. - - - Not all unused procedures can/should be removed: ignore any inspection results for - event handler procedures and interface members that Rubberduck isn't recognizing as such. - - - - - - - - - - - We cannot determine whether exposed members of standard modules are called or not, - so we assume they are instead of flagging them as "never called". - - - - - Identifies redundant ByRef modifiers. - - - Out of convention or preference, explicit ByRef modifiers could be considered redundant since they are the implicit default. - This inspection can ensure the consistency of the convention. - - - - - - - - - - - Identifies redundant module options that are set to their implicit default. - - - Module options that are redundant can be safely removed. Disable this inspection if your convention is to explicitly specify them; a future - inspection may be used to enforce consistently explicit module options. - - - - - - - - - - - Identifies auto-assigned object declarations. - - - Auto-assigned objects are automatically re-created as soon as they are referenced. It is therefore impossible to set one such reference - to 'Nothing' and then verifying whether the object 'Is Nothing': it will never be. This behavior is potentially confusing and bug-prone. - - - - - - - - - - - Identifies identifiers that hide/"shadow" other identifiers otherwise accessible in that scope. - - - Global namespace contains a number of perfectly legal identifier names that user code can use. But using these names in user code - effectively "hides" the global ones. In general, avoid shadowing global-scope identifiers if possible. - - - - - - - - - - - Locates 'For' loops where the 'Step' token is omitted. - - - Out of convention or preference, explicit 'Step' specifiers could be considered mandatory; - this inspection can ensure the consistency of the convention. - - - - - - - - - - - Locates 'For' loops where the 'Step' token is specified with the default increment value (1). - - - Out of convention or preference, explicit 'Step 1' specifiers could be considered redundant; - this inspection can ensure the consistency of the convention. - - - - - - - - - - - Locates 'Stop' instructions in user code. - - - While a great debugging tool, 'Stop' instructions should not be reachable in production code; this inspection makes it easy to locate them all. - - - - - - - - - - - Warns when a variable is referenced prior to being assigned. - - - An uninitialized variable is being read, but since it's never assigned, the only value ever read would be the data type's default initial value. - Reading a variable that was never written to in any code path (especially if Option Explicit isn't specified), is likely to be a bug. - - - This inspection may produce false positives when the variable is an array, or if it's passed by reference (ByRef) to a procedure that assigns it. - - - - - - - - - - - Warns about implicit local variables that are used but never declared. - - - If this code compiles, then Option Explicit is omitted and compile-time validation is easily forfeited, even accidentally (e.g. typos). - - - - - - - - - - - Warns about public class members with an underscore in their names. - - - The public interface of any class module can be implemented by any other class module; if the public interface - contains names with underscores, other classes cannot implement it - the code will not compile. Avoid underscores; prefer PascalCase names. - - - - - - - - - - - Finds instances of 'On Error Resume Next' that don't have a corresponding 'On Error GoTo 0' to restore error handling. - - - 'On Error Resume Next' should be constrained to a limited number of instructions, otherwise it supresses error handling - for the rest of the procedure; 'On Error GoTo 0' reinstates error handling. - This inspection helps treating 'Resume Next' and 'GoTo 0' as a code block (similar to 'With...End With'), essentially. - - - - - - - - - - - Flags 'Case' blocks that are semantically unreachable. - - - Unreachable code is certainly unintended, and is probably either redundant, or a bug. - - - Not all unreachable 'Case' blocks may be flagged, depending on expression complexity. - - - - - - - - Flags uses of a number of specific string-centric but Variant-returning functions in various standard library modules. - - - Several functions in the standard library take a Variant parameter and return a Variant result, but an equivalent - string-returning function taking a string parameter exists and should probably be preferred. - - - - - - - - - - - Warns about identifiers that have names that are likely to be too short, disemvoweled, or appended with a numeric suffix. - - - Meaningful, pronounceable, unabbreviated names read better and leave less room for interpretation. - Moreover, names suffixed with a number can indicate the need to look into an array, collection, or dictionary data structure. - - - - - - - - - - - Warns about variables that are never assigned. - - - A variable that is never assigned is probably a sign of a bug. - This inspection may yield false positives if the variable is assigned through a ByRef parameter assignment, or - if UserForm controls fail to resolve, references to these controls in code-behind can be flagged as unassigned and undeclared variables. - - - - - - - - - - - Warns about variables that are never referenced. - - - A variable can be declared and even assigned, but if its value is never referenced, it's effectively an unused variable. - - - - - - - - - - - Warns about variables declared without an explicit data type. - - - A variable declared without an explicit data type is implicitly a Variant/Empty until it is assigned. - - - - - - - - - - - Warns about properties that don't expose a 'Property Get' accessor. - - - Write-only properties are suspicious: if the client code is able to set a property, it should be allowed to read that property as well. - Class design guidelines and best practices generally recommend against write-only properties. - - - - - - - - - - - Determines whether the 'Set' keyword is required (whether it's present or not) for the specified identifier reference. - - The identifier reference to analyze - The parser state - - - - A code inspection quickfix that removes an unused identifier declaration. - - - - From 1dec1e3495c2c16acd3b8fc9f775689622a5da73 Mon Sep 17 00:00:00 2001 From: Mathieu Guindon Date: Fri, 23 Aug 2019 00:04:02 -0400 Subject: [PATCH 23/32] hotfix for ExcelHotkey annotation; quickfix for MissingMemberAnnotation is broken for this annotation though, presumably because of the "\n" in the attribute value. --- .../CodeInspectionDefaults.Designer.cs | 203 +++++++++--------- .../CodeInspectionDefaults.settings | 1 + .../Annotations/ExcelHotKeyAnnotation.cs | 2 +- 3 files changed, 104 insertions(+), 102 deletions(-) diff --git a/Rubberduck.CodeAnalysis/Properties/CodeInspectionDefaults.Designer.cs b/Rubberduck.CodeAnalysis/Properties/CodeInspectionDefaults.Designer.cs index 9fdefbbe32..ef91c92378 100644 --- a/Rubberduck.CodeAnalysis/Properties/CodeInspectionDefaults.Designer.cs +++ b/Rubberduck.CodeAnalysis/Properties/CodeInspectionDefaults.Designer.cs @@ -45,109 +45,110 @@ public static CodeInspectionDefaults Default { "berduckOpportunities\" />\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n <" + + "CodeInspection Name=\"EmptyWhileWendBlockInspection\" Severity=\"Warning\" Inspectio" + + "nType=\"MaintainabilityAndReadabilityIssues\" />\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n " + + " \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n " + - " \r\n \r\n \r\n " + - "\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n" + - " \r\n \r\n \r\n \r\n \r\n \r\n \r" + - "\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n " + - " \r\n \r\n \r\n \r\n true\r\n")] + "spection Name=\"ObsoleteTypeHintInspection\" Severity=\"Suggestion\" InspectionType=" + + "\"LanguageOpportunities\" />\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n true\r\n")] public global::Rubberduck.CodeAnalysis.Settings.CodeInspectionSettings CodeInspectionSettings { get { return ((global::Rubberduck.CodeAnalysis.Settings.CodeInspectionSettings)(this["CodeInspectionSettings"])); diff --git a/Rubberduck.CodeAnalysis/Properties/CodeInspectionDefaults.settings b/Rubberduck.CodeAnalysis/Properties/CodeInspectionDefaults.settings index 7666b940eb..d5d13801c1 100644 --- a/Rubberduck.CodeAnalysis/Properties/CodeInspectionDefaults.settings +++ b/Rubberduck.CodeAnalysis/Properties/CodeInspectionDefaults.settings @@ -18,6 +18,7 @@ <CodeInspection Name="MissingAttributeInspection" Severity="Warning" InspectionType="RubberduckOpportunities" /> <CodeInspection Name="AttributeOutOfSyncInspection" Severity="Warning" InspectionType="RubberduckOpportunities" /> <CodeInspection Name="MissingAnnotationArgumentInspection" Severity="Error" InspectionType="CodeQualityIssues" /> + <CodeInspection Name="MissingMemberAnnotationInspection" Severity="Error" InspectionType="RubberduckOpportunities" /> <CodeInspection Name="ModuleScopeDimKeywordInspection" Severity="Suggestion" InspectionType="LanguageOpportunities" /> <CodeInspection Name="MultilineParameterInspection" Severity="Suggestion" InspectionType="MaintainabilityAndReadabilityIssues" /> <CodeInspection Name="MultipleDeclarationsInspection" Severity="Warning" InspectionType="MaintainabilityAndReadabilityIssues" /> diff --git a/Rubberduck.Parsing/Annotations/ExcelHotKeyAnnotation.cs b/Rubberduck.Parsing/Annotations/ExcelHotKeyAnnotation.cs index edae81792d..af72c18d9f 100644 --- a/Rubberduck.Parsing/Annotations/ExcelHotKeyAnnotation.cs +++ b/Rubberduck.Parsing/Annotations/ExcelHotKeyAnnotation.cs @@ -13,7 +13,7 @@ public ExcelHotKeyAnnotation(QualifiedSelection qualifiedSelection, VBAParser.An { } private static IEnumerable GetHotKeyAttributeValue(IEnumerable parameters) => - parameters.Take(1).Select(StripStringLiteralQuotes).Select(v => v[0] + @"\n14").ToList(); + parameters.Take(1).Select(StripStringLiteralQuotes).Select(v => @"""" + v[0] + @"\n14""").ToList(); private static string StripStringLiteralQuotes(string value) => value.StartsWith("\"") && value.EndsWith("\"") && value.Length > 2 From 06f5fb3d07e6cd59e95b586cfc4da0ba0a35e753 Mon Sep 17 00:00:00 2001 From: Mathieu Guindon Date: Fri, 23 Aug 2019 00:15:42 -0400 Subject: [PATCH 24/32] Update Attributions.md --- docs/Attributions.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/Attributions.md b/docs/Attributions.md index 6d0cf3dfe1..2fc27bee60 100644 --- a/docs/Attributions.md +++ b/docs/Attributions.md @@ -4,21 +4,17 @@ ### [ANTLR](http://www.antlr.org/) -As of v1.2, Rubberduck is empowered by the awesomeness of ANTLR. - -> **What is ANTLR?** +Since v1.2, tokenizing and parsing the VBA code is left to the parsing masters behind Antlr. We write the language's grammatical/syntactical rules into a file that Antlr processes to generate a lexer that can turn a string into a stream of tokens, a parser that turns that stream into a tree structure Rubberduck can work with. Everything starts with Antlr. > *ANTLR (ANother Tool for Language Recognition) is a powerful parser generator for reading, processing, executing, or translating structured text or binary files. It's widely used to build languages, tools, and frameworks. From a grammar, ANTLR generates a parser that can build and walk parse trees.* -We're not doing half of what we could be doing with this amazing tool. Try it, see for yourself! - ### [AvalonEdit](http://avalonedit.net) Source code looks a lot better with syntax highlighting, and AvalonEdit excels at it. > AvalonEdit is a WPF-based text editor component. It was written by [Daniel Grunwald](https://github.com/dgrunwald) for the [SharpDevelop](http://www.icsharpcode.net/OpenSource/SD/) IDE. Starting with version 5.0, AvalonEdit is released under the [MIT license](http://opensource.org/licenses/MIT). -We're currently only using a tiny bit of this code editor's functionality (more to come!). + ### [EasyHook](http://easyhook.github.io/index.html) @@ -34,6 +30,16 @@ This library makes localizing WPF applications at runtime using resx files a bre > Licensed under [The Code Project Open License](http://www.codeproject.com/info/cpol10.aspx) with the [author's permission](http://www.codeproject.com/Messages/5272045/Re-License.aspx) to re-release under the GPLv3. +### [Moq](https://github.com/moq) + +Moq has always been powering Rubberduck's own unit test mocks, but as of v2.5 our VBA unit testing API includes a wrapper that basically lets you use Moq for your VBA unit tests, to configure a mock implementation of any class or interface your VBA code might depend on. + +> **What is ANTLR?** + +> *ANTLR (ANother Tool for Language Recognition) is a powerful parser generator for reading, processing, executing, or translating structured text or binary files. It's widely used to build languages, tools, and frameworks. From a grammar, ANTLR generates a parser that can build and walk parse trees.* + +We're not doing half of what we could be doing with this amazing tool. Try it, see for yourself! + ## Icons We didn't come up with these icons ourselves! Here's who did what: From b8a37a3c69e14b0fef8c0d718ce5880b25944780 Mon Sep 17 00:00:00 2001 From: Mathieu Guindon Date: Fri, 23 Aug 2019 00:27:57 -0400 Subject: [PATCH 25/32] account for double quotes --- Rubberduck.Parsing/Annotations/AttributeAnnotationProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rubberduck.Parsing/Annotations/AttributeAnnotationProvider.cs b/Rubberduck.Parsing/Annotations/AttributeAnnotationProvider.cs index 52ae135696..23eba21b84 100644 --- a/Rubberduck.Parsing/Annotations/AttributeAnnotationProvider.cs +++ b/Rubberduck.Parsing/Annotations/AttributeAnnotationProvider.cs @@ -36,7 +36,7 @@ public class AttributeAnnotationProvider : IAttributeAnnotationProvider // FIXME special cased bodge for ExcelHotKeyAnnotation to deal with the value transformation: if (flexibleValueAttributeAnnotation == AnnotationType.ExcelHotKey) { - return (flexibleValueAttributeAnnotation, attributeValues.Select(keySpec => keySpec.Substring(0, 1)).ToList()); + return (flexibleValueAttributeAnnotation, attributeValues.Select(keySpec => keySpec.Substring(1, 1)).ToList()); } return (flexibleValueAttributeAnnotation, attributeValues); } From bdca75aa82c366a40d4006caaf280f6b9ecc0c4b Mon Sep 17 00:00:00 2001 From: Mathieu Guindon Date: Fri, 23 Aug 2019 00:41:46 -0400 Subject: [PATCH 26/32] fixed broken test (added the expected quotes) --- RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs b/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs index 236aed4ff4..87ee97f8c7 100644 --- a/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs +++ b/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs @@ -57,7 +57,7 @@ public void ModuleAttributeAnnotationReturnsSpecializedAnnotationsWhereApplicabl AssertEqual(expectedValues, actualValues); } - [TestCase("VB_ProcData.VB_Invoke_Func", @"A\n14", AnnotationType.ExcelHotKey, "A")] + [TestCase("VB_ProcData.VB_Invoke_Func", "\"A\n14\"", AnnotationType.ExcelHotKey, "A")] [TestCase("VB_Description", "\"SomeDescription\"", AnnotationType.Description, "\"SomeDescription\"")] [TestCase("VB_VarDescription", "\"SomeDescription\"", AnnotationType.VariableDescription, "\"SomeDescription\"")] [TestCase("VB_UserMemId", "0", AnnotationType.DefaultMember)] From 0a09d07af781f073a27ccd3af2380570e056f6f5 Mon Sep 17 00:00:00 2001 From: Mathieu Guindon Date: Fri, 23 Aug 2019 00:44:22 -0400 Subject: [PATCH 27/32] added quotes around annotation value --- Rubberduck.Parsing/Annotations/AttributeAnnotationProvider.cs | 2 +- RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Rubberduck.Parsing/Annotations/AttributeAnnotationProvider.cs b/Rubberduck.Parsing/Annotations/AttributeAnnotationProvider.cs index 23eba21b84..e33640418b 100644 --- a/Rubberduck.Parsing/Annotations/AttributeAnnotationProvider.cs +++ b/Rubberduck.Parsing/Annotations/AttributeAnnotationProvider.cs @@ -36,7 +36,7 @@ public class AttributeAnnotationProvider : IAttributeAnnotationProvider // FIXME special cased bodge for ExcelHotKeyAnnotation to deal with the value transformation: if (flexibleValueAttributeAnnotation == AnnotationType.ExcelHotKey) { - return (flexibleValueAttributeAnnotation, attributeValues.Select(keySpec => keySpec.Substring(1, 1)).ToList()); + return (flexibleValueAttributeAnnotation, attributeValues.Select(keySpec => '"' + keySpec.Substring(1, 1) + '"').ToList()); } return (flexibleValueAttributeAnnotation, attributeValues); } diff --git a/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs b/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs index 87ee97f8c7..552bfba8df 100644 --- a/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs +++ b/RubberduckTests/Annotations/AttributeAnnotationProviderTests.cs @@ -57,7 +57,7 @@ public void ModuleAttributeAnnotationReturnsSpecializedAnnotationsWhereApplicabl AssertEqual(expectedValues, actualValues); } - [TestCase("VB_ProcData.VB_Invoke_Func", "\"A\n14\"", AnnotationType.ExcelHotKey, "A")] + [TestCase("VB_ProcData.VB_Invoke_Func", "\"A\n14\"", AnnotationType.ExcelHotKey, "\"A\"")] [TestCase("VB_Description", "\"SomeDescription\"", AnnotationType.Description, "\"SomeDescription\"")] [TestCase("VB_VarDescription", "\"SomeDescription\"", AnnotationType.VariableDescription, "\"SomeDescription\"")] [TestCase("VB_UserMemId", "0", AnnotationType.DefaultMember)] From 6fbbdba7c0e5c9772396c9cc3560e025cd82d773 Mon Sep 17 00:00:00 2001 From: Max Doerner Date: Sat, 24 Aug 2019 15:52:12 +0200 Subject: [PATCH 28/32] Fix MemberNotOnInterfaceInspection Now we collect the unresolved expressions using a visitor, which enables us to get to the various branches on binary expressions and index expressions. --- .../Bindings/BinaryOpDefaultBinding.cs | 16 +- .../DictionaryAccessDefaultBinding.cs | 31 +- .../Binding/Bindings/IndexDefaultBinding.cs | 20 +- .../Bindings/MemberAccessDefaultBinding.cs | 8 +- .../MemberAccessProcedurePointerBinding.cs | 2 +- .../Bindings/MemberAccessTypeBinding.cs | 19 +- .../ProcedureCoercionDefaultBinding.cs | 8 +- .../SimpleNameProcedurePointerBinding.cs | 2 +- .../Binding/Bindings/SimpleNameTypeBinding.cs | 2 +- .../Bindings/TypeOfIsDefaultBinding.cs | 16 +- .../Expressions/ResolutionFailedExpression.cs | 47 ++- .../FailedResolutionVisitor.cs | 163 ++++++++++ .../IdentifierReferenceListener.cs | 3 +- .../IdentifierReferenceResolver.cs | 50 ++-- .../MemberNotOnInterfaceInspectionTests.cs | 279 +++++++++++++++++- 15 files changed, 578 insertions(+), 88 deletions(-) create mode 100644 Rubberduck.Parsing/VBA/ReferenceManagement/FailedResolutionVisitor.cs rename Rubberduck.Parsing/{Symbols => VBA/ReferenceManagement}/IdentifierReferenceListener.cs (99%) rename Rubberduck.Parsing/{Symbols => VBA/ReferenceManagement}/IdentifierReferenceResolver.cs (96%) diff --git a/Rubberduck.Parsing/Binding/Bindings/BinaryOpDefaultBinding.cs b/Rubberduck.Parsing/Binding/Bindings/BinaryOpDefaultBinding.cs index ed6ce317fe..d5ecddbd03 100644 --- a/Rubberduck.Parsing/Binding/Bindings/BinaryOpDefaultBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/BinaryOpDefaultBinding.cs @@ -22,13 +22,19 @@ public IBoundExpression Resolve() { var leftExpr = _left.Resolve(); var rightExpr = _right.Resolve(); - if (leftExpr.Classification == ExpressionClassification.ResolutionFailed || rightExpr.Classification == ExpressionClassification.ResolutionFailed) + + if (leftExpr.Classification == ExpressionClassification.ResolutionFailed) { - var failedExpr = new ResolutionFailedExpression(); - failedExpr.AddSuccessfullyResolvedExpression(leftExpr); - failedExpr.AddSuccessfullyResolvedExpression(rightExpr); - return failedExpr; + var failedExpr = (ResolutionFailedExpression) leftExpr; + return failedExpr.Join(rightExpr); } + + if (rightExpr.Classification == ExpressionClassification.ResolutionFailed) + { + var failedExpr = (ResolutionFailedExpression)rightExpr; + return failedExpr.Join(leftExpr); + } + return new BinaryOpExpression(null, _context, leftExpr, rightExpr); } } diff --git a/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs b/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs index 9c8a1726f9..a50294a770 100644 --- a/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/DictionaryAccessDefaultBinding.cs @@ -61,11 +61,19 @@ public IBoundExpression Resolve() private static IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression) { - if (lExpression.Classification == ExpressionClassification.ResolutionFailed - || !(expression is VBAParser.LExpressionContext lExpressionContext)) + if (lExpression.Classification == ExpressionClassification.ResolutionFailed) { + var failedExpression = (ResolutionFailedExpression) lExpression; + ResolveArgumentList(null, argumentList); - return CreateFailedExpression(lExpression, argumentList); + var argumentExpressions = argumentList.Arguments.Select(arg => arg.Expression); + return failedExpression.Join(argumentExpressions); + } + + if (!(expression is VBAParser.LExpressionContext lExpressionContext)) + { + ResolveArgumentList(null, argumentList); + return CreateFailedExpression(lExpression, argumentList, expression); } var lDeclaration = lExpression.ReferencedDeclaration; @@ -84,7 +92,7 @@ private static IBoundExpression Resolve(IBoundExpression lExpression, ArgumentLi if (lDeclaration == null) { ResolveArgumentList(null, argumentList); - return CreateFailedExpression(lExpression, argumentList); + return CreateFailedExpression(lExpression, argumentList, expression); } var asTypeName = lDeclaration.AsTypeName; @@ -93,16 +101,13 @@ private static IBoundExpression Resolve(IBoundExpression lExpression, ArgumentLi return ResolveViaDefaultMember(lExpression, asTypeName, asTypeDeclaration, argumentList, lExpressionContext, defaultMemberContext); } - private static IBoundExpression CreateFailedExpression(IBoundExpression lExpression, ArgumentList argumentList) + private static IBoundExpression CreateFailedExpression(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext context) { - var failedExpr = new ResolutionFailedExpression(); + var failedExpr = new ResolutionFailedExpression(context, true); failedExpr.AddSuccessfullyResolvedExpression(lExpression); - foreach (var arg in argumentList.Arguments) - { - failedExpr.AddSuccessfullyResolvedExpression(arg.Expression); - } - return failedExpr; + var argumentExpressions = argumentList.Arguments.Select(arg => arg.Expression); + return failedExpr.Join(argumentExpressions); } private static IBoundExpression ResolveViaDefaultMember(IBoundExpression lExpression, string asTypeName, Declaration asTypeDeclaration, ArgumentList argumentList, ParserRuleContext expression, ParserRuleContext defaultMemberContext, int recursionDepth = 1, RecursiveDefaultMemberAccessExpression containedExpression = null) @@ -129,7 +134,7 @@ The declared type of is Object or Variant. || !IsPublic(defaultMember)) { ResolveArgumentList(null, argumentList); - return CreateFailedExpression(lExpression, argumentList); + return CreateFailedExpression(lExpression, argumentList, expression); } var defaultMemberClassification = DefaultMemberClassification(defaultMember); @@ -159,7 +164,7 @@ declared type. } ResolveArgumentList(null, argumentList); - return CreateFailedExpression(lExpression, argumentList); + return CreateFailedExpression(lExpression, argumentList, expression); } private static bool IsCompatibleWithOneStringArgument(List parameters) diff --git a/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs b/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs index 8e34d0efd8..fee128604a 100644 --- a/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/IndexDefaultBinding.cs @@ -67,8 +67,11 @@ private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argu { if (lExpression.Classification == ExpressionClassification.ResolutionFailed) { + var failedExpression = (ResolutionFailedExpression) lExpression; + ResolveArgumentList(null, argumentList); - return CreateFailedExpression(lExpression, argumentList); + var argumentExpressions = argumentList.Arguments.Select(arg => arg.Expression); + return failedExpression.Join(argumentExpressions); } if (lExpression.Classification == ExpressionClassification.Unbound) @@ -119,18 +122,15 @@ private IBoundExpression Resolve(IBoundExpression lExpression, ArgumentList argu } ResolveArgumentList(null, argumentList); - return CreateFailedExpression(lExpression, argumentList); + return CreateFailedExpression(lExpression, argumentList, expression, defaultMemberResolutionRecursionDepth > 0); } - private static IBoundExpression CreateFailedExpression(IBoundExpression lExpression, ArgumentList argumentList) + private static IBoundExpression CreateFailedExpression(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext context, bool isDefaultMemberResolution) { - var failedExpr = new ResolutionFailedExpression(); + var failedExpr = new ResolutionFailedExpression(context, isDefaultMemberResolution); failedExpr.AddSuccessfullyResolvedExpression(lExpression); - foreach (var arg in argumentList.Arguments) - { - failedExpr.AddSuccessfullyResolvedExpression(arg.Expression); - } - return failedExpr; + var argumentExpressions = argumentList.Arguments.Select(arg => arg.Expression); + return failedExpr.Join(argumentExpressions); } private IBoundExpression ResolveLExpressionIsVariablePropertyFunctionNoParameters(IBoundExpression lExpression, ArgumentList argumentList, ParserRuleContext expression, int defaultMemberResolutionRecursionDepth, RecursiveDefaultMemberAccessExpression containedExpression) @@ -160,7 +160,7 @@ with a parameter list that cannot accept any parameters and an t return ResolveDefaultMember(asTypeName, asTypeDeclaration, argumentList, expression, defaultMemberResolutionRecursionDepth + 1, containedExpression); } - private bool IsVariablePropertyFunctionWithoutParameters(IBoundExpression lExpression) + private static bool IsVariablePropertyFunctionWithoutParameters(IBoundExpression lExpression) { switch(lExpression.Classification) { diff --git a/Rubberduck.Parsing/Binding/Bindings/MemberAccessDefaultBinding.cs b/Rubberduck.Parsing/Binding/Bindings/MemberAccessDefaultBinding.cs index 6e98f129b1..bc659c4692 100644 --- a/Rubberduck.Parsing/Binding/Bindings/MemberAccessDefaultBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/MemberAccessDefaultBinding.cs @@ -99,7 +99,7 @@ public IBoundExpression Resolve() { return boundExpression; } - return CreateFailedExpression(_lExpression); + return CreateFailedExpression(_lExpression, _context); } private IBoundExpression ResolveLExpressionIsVariablePropertyOrFunction() @@ -174,12 +174,12 @@ expression is classified as an unbound member and has a declared type of Variant return new MemberAccessExpression(subroutine, ExpressionClassification.Subroutine, _context, _unrestrictedNameContext, _lExpression); } // Assume that no match = failure on our side. - return CreateFailedExpression(_lExpression); + return CreateFailedExpression(_lExpression, _context); } - private IBoundExpression CreateFailedExpression(IBoundExpression expression) + private IBoundExpression CreateFailedExpression(IBoundExpression expression, ParserRuleContext context) { - var failedExpr = new ResolutionFailedExpression(); + var failedExpr = new ResolutionFailedExpression(context); failedExpr.AddSuccessfullyResolvedExpression(expression); return failedExpr; } diff --git a/Rubberduck.Parsing/Binding/Bindings/MemberAccessProcedurePointerBinding.cs b/Rubberduck.Parsing/Binding/Bindings/MemberAccessProcedurePointerBinding.cs index be814f6486..38768bcae4 100644 --- a/Rubberduck.Parsing/Binding/Bindings/MemberAccessProcedurePointerBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/MemberAccessProcedurePointerBinding.cs @@ -56,7 +56,7 @@ public IBoundExpression Resolve() { return boundExpression; } - var failedExpr = new ResolutionFailedExpression(); + var failedExpr = new ResolutionFailedExpression(_expression); failedExpr.AddSuccessfullyResolvedExpression(lExpression); return failedExpr; } diff --git a/Rubberduck.Parsing/Binding/Bindings/MemberAccessTypeBinding.cs b/Rubberduck.Parsing/Binding/Bindings/MemberAccessTypeBinding.cs index eb99efe795..d21a67fc2c 100644 --- a/Rubberduck.Parsing/Binding/Bindings/MemberAccessTypeBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/MemberAccessTypeBinding.cs @@ -12,8 +12,7 @@ public sealed class MemberAccessTypeBinding : IExpressionBinding private readonly Declaration _module; private readonly Declaration _parent; private readonly VBAParser.MemberAccessExprContext _expression; - private ParserRuleContext _unrestrictedNameContext; - private readonly IExpressionBinding _lExpressionBinding; + private readonly ParserRuleContext _unrestrictedNameContext; public MemberAccessTypeBinding( DeclarationFinder declarationFinder, @@ -29,22 +28,16 @@ public MemberAccessTypeBinding( _module = module; _parent = parent; _expression = expression; - _lExpressionBinding = lExpressionBinding; + LExpressionBinding = lExpressionBinding; _unrestrictedNameContext = unrestrictedNameContext; } - public IExpressionBinding LExpressionBinding - { - get - { - return _lExpressionBinding; - } - } + public IExpressionBinding LExpressionBinding { get; } public IBoundExpression Resolve() { IBoundExpression boundExpression = null; - var lExpression = _lExpressionBinding.Resolve(); + var lExpression = LExpressionBinding.Resolve(); if (lExpression.Classification == ExpressionClassification.ResolutionFailed) { return lExpression; @@ -60,7 +53,7 @@ public IBoundExpression Resolve() { return boundExpression; } - var failedExpr = new ResolutionFailedExpression(); + var failedExpr = new ResolutionFailedExpression(_expression); failedExpr.AddSuccessfullyResolvedExpression(lExpression); return failedExpr; } @@ -100,7 +93,7 @@ private IBoundExpression ResolveLExpressionIsProject(IBoundExpression lExpressio { return boundExpression; } - return boundExpression; + return null; } private IBoundExpression ResolveProject(IBoundExpression lExpression, string name) diff --git a/Rubberduck.Parsing/Binding/Bindings/ProcedureCoercionDefaultBinding.cs b/Rubberduck.Parsing/Binding/Bindings/ProcedureCoercionDefaultBinding.cs index bb043f4642..d42b812d5d 100644 --- a/Rubberduck.Parsing/Binding/Bindings/ProcedureCoercionDefaultBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/ProcedureCoercionDefaultBinding.cs @@ -69,9 +69,9 @@ private static IBoundExpression Resolve(IBoundExpression wrappedExpression, Pars return ResolveViaDefaultMember(wrappedExpression, asTypeName, asTypeDeclaration, expression); } - private static IBoundExpression CreateFailedExpression(IBoundExpression lExpression) + private static IBoundExpression CreateFailedExpression(IBoundExpression lExpression, ParserRuleContext context) { - var failedExpr = new ResolutionFailedExpression(); + var failedExpr = new ResolutionFailedExpression(context, true); failedExpr.AddSuccessfullyResolvedExpression(lExpression); return failedExpr; } @@ -90,7 +90,7 @@ private static IBoundExpression ResolveViaDefaultMember(IBoundExpression wrapped || !IsPropertyGetLetFunctionProcedure(defaultMember) || !IsPublic(defaultMember)) { - return CreateFailedExpression(wrappedExpression); + return CreateFailedExpression(wrappedExpression, expression); } var defaultMemberClassification = DefaultMemberClassification(defaultMember); @@ -102,7 +102,7 @@ private static IBoundExpression ResolveViaDefaultMember(IBoundExpression wrapped return new ProcedureCoercionExpression(defaultMember, defaultMemberClassification, expression, wrappedExpression); } - return CreateFailedExpression(wrappedExpression); + return CreateFailedExpression(wrappedExpression, expression); } private static bool IsPropertyGetLetFunctionProcedure(Declaration declaration) diff --git a/Rubberduck.Parsing/Binding/Bindings/SimpleNameProcedurePointerBinding.cs b/Rubberduck.Parsing/Binding/Bindings/SimpleNameProcedurePointerBinding.cs index fc867e66ff..38f691c64c 100644 --- a/Rubberduck.Parsing/Binding/Bindings/SimpleNameProcedurePointerBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/SimpleNameProcedurePointerBinding.cs @@ -45,7 +45,7 @@ public IBoundExpression Resolve() { return boundExpression; } - return new ResolutionFailedExpression(); + return new ResolutionFailedExpression(_expression); } private IBoundExpression ResolveEnclosingModule(string name) diff --git a/Rubberduck.Parsing/Binding/Bindings/SimpleNameTypeBinding.cs b/Rubberduck.Parsing/Binding/Bindings/SimpleNameTypeBinding.cs index 5c081ce686..13a809ec47 100644 --- a/Rubberduck.Parsing/Binding/Bindings/SimpleNameTypeBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/SimpleNameTypeBinding.cs @@ -67,7 +67,7 @@ private IBoundExpression ResolvePreferUdt(string name) { return boundExpression; } - return new ResolutionFailedExpression(); + return new ResolutionFailedExpression(_expression); } private IBoundExpression ResolvePreferProject(string name) diff --git a/Rubberduck.Parsing/Binding/Bindings/TypeOfIsDefaultBinding.cs b/Rubberduck.Parsing/Binding/Bindings/TypeOfIsDefaultBinding.cs index a65821c7f2..81830d6363 100644 --- a/Rubberduck.Parsing/Binding/Bindings/TypeOfIsDefaultBinding.cs +++ b/Rubberduck.Parsing/Binding/Bindings/TypeOfIsDefaultBinding.cs @@ -22,13 +22,19 @@ public IBoundExpression Resolve() { var expr = _expressionBinding.Resolve(); var typeExpr = _typeExpressionBinding.Resolve(); - if (expr.Classification == ExpressionClassification.ResolutionFailed || typeExpr.Classification == ExpressionClassification.ResolutionFailed) + + if (expr.Classification == ExpressionClassification.ResolutionFailed) { - var failedExpr = new ResolutionFailedExpression(); - failedExpr.AddSuccessfullyResolvedExpression(expr); - failedExpr.AddSuccessfullyResolvedExpression(typeExpr); - return failedExpr; + var failedExpr = (ResolutionFailedExpression)expr; + return failedExpr.Join(typeExpr); } + + if (typeExpr.Classification == ExpressionClassification.ResolutionFailed) + { + var failedExpr = (ResolutionFailedExpression)typeExpr; + return failedExpr.Join(expr); + } + return new TypeOfIsExpression(null, _context, expr, typeExpr); } } diff --git a/Rubberduck.Parsing/Binding/Expressions/ResolutionFailedExpression.cs b/Rubberduck.Parsing/Binding/Expressions/ResolutionFailedExpression.cs index 62b27031cb..ade0a79b3f 100644 --- a/Rubberduck.Parsing/Binding/Expressions/ResolutionFailedExpression.cs +++ b/Rubberduck.Parsing/Binding/Expressions/ResolutionFailedExpression.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Linq; +using Antlr4.Runtime; namespace Rubberduck.Parsing.Binding { @@ -6,15 +8,56 @@ public sealed class ResolutionFailedExpression : BoundExpression { private readonly List _successfullyResolvedExpressions = new List(); - public ResolutionFailedExpression() + public ResolutionFailedExpression(ParserRuleContext context, bool isDefaultMemberResolution = false) + : base(null, ExpressionClassification.ResolutionFailed, context) + { + IsDefaultMemberResolution = isDefaultMemberResolution; + IsJoinedExpression = false; + } + + public ResolutionFailedExpression(params ResolutionFailedExpression[] expressions) : base(null, ExpressionClassification.ResolutionFailed, null) - {} + { + IsDefaultMemberResolution = false; + IsJoinedExpression = true; + + AddSuccessfullyResolvedExpressions(expressions); + } public IReadOnlyList SuccessfullyResolvedExpressions => _successfullyResolvedExpressions; + public bool IsDefaultMemberResolution { get; } + public bool IsJoinedExpression { get; } public void AddSuccessfullyResolvedExpression(IBoundExpression expression) { _successfullyResolvedExpressions.Add(expression); } + + public void AddSuccessfullyResolvedExpressions(IEnumerable expressions) + { + _successfullyResolvedExpressions.AddRange(expressions); + } + } + + public static class ResolutionFailedExpressionExtensions + { + public static ResolutionFailedExpression Join(this ResolutionFailedExpression expression, params IBoundExpression[] otherExpressions) + { + return expression.Join((IEnumerable)otherExpressions); + } + + public static ResolutionFailedExpression Join(this ResolutionFailedExpression expression, IEnumerable otherExpressions) + { + var otherExprs = otherExpressions.ToList(); + + var failedExpressions = otherExprs.OfType().Concat(new []{expression}).ToArray(); + var failedExpression = new ResolutionFailedExpression(failedExpressions); + + var successfulExpressions = otherExprs.Where(expr => expr.Classification != ExpressionClassification.ResolutionFailed); + + failedExpression.AddSuccessfullyResolvedExpressions(successfulExpressions); + + return failedExpression; + } } } diff --git a/Rubberduck.Parsing/VBA/ReferenceManagement/FailedResolutionVisitor.cs b/Rubberduck.Parsing/VBA/ReferenceManagement/FailedResolutionVisitor.cs new file mode 100644 index 0000000000..c375b9bde9 --- /dev/null +++ b/Rubberduck.Parsing/VBA/ReferenceManagement/FailedResolutionVisitor.cs @@ -0,0 +1,163 @@ +using System; +using NLog; +using Rubberduck.Parsing.Binding; +using Rubberduck.Parsing.Grammar; +using Rubberduck.Parsing.Symbols; +using Rubberduck.Parsing.VBA.DeclarationCaching; + +namespace Rubberduck.Parsing.VBA.ReferenceManagement +{ + public sealed class FailedResolutionVisitor + { + private readonly DeclarationFinder _declarationFinder; + + private static Logger Logger = LogManager.GetCurrentClassLogger(); + + public FailedResolutionVisitor(DeclarationFinder declarationFinder) + { + _declarationFinder = declarationFinder; + } + + public void CollectUnresolved(IBoundExpression boundExpression, Declaration parent, IBoundExpression withExpression) + { + Visit(boundExpression, parent, withExpression); + } + + private void Visit(IBoundExpression boundExpression, Declaration parent, IBoundExpression withExpression) + { + switch (boundExpression) + { + case SimpleNameExpression simpleNameExpression: + break; + case MemberAccessExpression memberAccessExpression: + Visit(memberAccessExpression, parent, withExpression); + break; + case IndexExpression indexExpression: + Visit(indexExpression, parent, withExpression); + break; + case ParenthesizedExpression parenthesizedExpression: + Visit(parenthesizedExpression, parent, withExpression); + break; + case LiteralExpression literalExpression: + break; + case BinaryOpExpression binaryOpExpression: + Visit(binaryOpExpression, parent, withExpression); + break; + case UnaryOpExpression unaryOpExpression: + Visit(unaryOpExpression, parent, withExpression); + break; + case NewExpression newExpression: + Visit(newExpression, parent, withExpression); + break; + case InstanceExpression instanceExpression: + break; + case DictionaryAccessExpression dictionaryAccessExpression: + Visit(dictionaryAccessExpression, parent, withExpression); + break; + case TypeOfIsExpression typeOfIsExpression: + Visit(typeOfIsExpression, parent, withExpression); + break; + case ResolutionFailedExpression resolutionFailedExpression: + Visit(resolutionFailedExpression, parent, withExpression); + break; + case BuiltInTypeExpression builtInTypeExpression: + break; + case RecursiveDefaultMemberAccessExpression recursiveDefaultMemberAccessExpression: + break; + case LetCoercionDefaultMemberAccessExpression letCoercionDefaultMemberAccessExpression: + Visit(letCoercionDefaultMemberAccessExpression, parent, withExpression); + break; + case ProcedureCoercionExpression procedureCoercionExpression: + Visit(procedureCoercionExpression, parent, withExpression); + break; + default: + throw new NotSupportedException($"Unexpected bound expression type {boundExpression.GetType()}"); + } + } + + private void Visit(ResolutionFailedExpression expression, Declaration parent, IBoundExpression withExpression) + { + if (!expression.IsJoinedExpression) + { + SaveUnresolvedExpression(expression, parent, withExpression); + } + + foreach (var successfullyResolvedExpression in expression.SuccessfullyResolvedExpressions) + { + Visit(successfullyResolvedExpression, parent, withExpression); + } + } + + private void SaveUnresolvedExpression(ResolutionFailedExpression expression, Declaration parent, IBoundExpression withExpression) + { + if (expression.Context is VBAParser.LExpressionContext lExpression) + { + _declarationFinder.AddUnboundContext(parent, lExpression, withExpression); + } + else + { + Logger.Warn($"Default Context: Failed to resolve {expression.Context.GetText()}. Binding as much as we can."); + } + } + + private void Visit(MemberAccessExpression expression, Declaration parent, IBoundExpression withExpression) + { + Visit(expression.LExpression, parent, withExpression); + } + + private void Visit(IndexExpression expression, Declaration parent, IBoundExpression withExpression) + { + Visit(expression.LExpression, parent, withExpression); + + foreach (var argument in expression.ArgumentList.Arguments) + { + if (argument.Expression != null) + { + Visit(argument.Expression, parent, withExpression); + } + } + } + + private void Visit(DictionaryAccessExpression expression, Declaration parent, IBoundExpression withExpression) + { + Visit(expression.LExpression, parent, withExpression); + } + + private void Visit(LetCoercionDefaultMemberAccessExpression expression, Declaration parent, IBoundExpression withExpression) + { + Visit(expression.WrappedExpression, parent, withExpression); + } + + private void Visit(ProcedureCoercionExpression expression, Declaration parent, IBoundExpression withExpression) + { + Visit(expression.WrappedExpression, parent, withExpression); + } + + private void Visit(NewExpression expression, Declaration parent, IBoundExpression withExpression) + { + Visit(expression.TypeExpression, parent, withExpression); + } + + private void Visit(ParenthesizedExpression expression, Declaration parent, IBoundExpression withExpression) + { + Visit(expression.Expression, parent, withExpression); + } + + private void Visit(TypeOfIsExpression expression, Declaration parent, IBoundExpression withExpression) + { + Visit(expression.Expression, parent, withExpression); + Visit(expression.TypeExpression, parent, withExpression); + } + + private void Visit(BinaryOpExpression expression, Declaration parent, IBoundExpression withExpression) + { + Visit(expression.Left, parent, withExpression); + Visit(expression.Right, parent, withExpression); + } + + private void Visit(UnaryOpExpression expression, Declaration parent, IBoundExpression withExpression) + { + Visit(expression.Expr, parent, withExpression); + } + } +} \ No newline at end of file diff --git a/Rubberduck.Parsing/Symbols/IdentifierReferenceListener.cs b/Rubberduck.Parsing/VBA/ReferenceManagement/IdentifierReferenceListener.cs similarity index 99% rename from Rubberduck.Parsing/Symbols/IdentifierReferenceListener.cs rename to Rubberduck.Parsing/VBA/ReferenceManagement/IdentifierReferenceListener.cs index 851efacfb2..be6a325c46 100644 --- a/Rubberduck.Parsing/Symbols/IdentifierReferenceListener.cs +++ b/Rubberduck.Parsing/VBA/ReferenceManagement/IdentifierReferenceListener.cs @@ -1,7 +1,8 @@ using Antlr4.Runtime.Misc; using Rubberduck.Parsing.Grammar; +using Rubberduck.Parsing.Symbols; -namespace Rubberduck.Parsing.Symbols +namespace Rubberduck.Parsing.VBA.ReferenceManagement { public class IdentifierReferenceListener : VBAParserBaseListener { diff --git a/Rubberduck.Parsing/Symbols/IdentifierReferenceResolver.cs b/Rubberduck.Parsing/VBA/ReferenceManagement/IdentifierReferenceResolver.cs similarity index 96% rename from Rubberduck.Parsing/Symbols/IdentifierReferenceResolver.cs rename to Rubberduck.Parsing/VBA/ReferenceManagement/IdentifierReferenceResolver.cs index 926784df1a..e0c0ec64f8 100644 --- a/Rubberduck.Parsing/Symbols/IdentifierReferenceResolver.cs +++ b/Rubberduck.Parsing/VBA/ReferenceManagement/IdentifierReferenceResolver.cs @@ -1,17 +1,17 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; using Antlr4.Runtime; using NLog; using Rubberduck.Parsing.Annotations; using Rubberduck.Parsing.Binding; using Rubberduck.Parsing.Grammar; +using Rubberduck.Parsing.Symbols; using Rubberduck.Parsing.Symbols.DeclarationLoaders; -using Rubberduck.VBEditor; -using System.Collections.Generic; -using System.Linq; -using System.Threading; using Rubberduck.Parsing.VBA.DeclarationCaching; -using Rubberduck.Parsing.VBA.ReferenceManagement; +using Rubberduck.VBEditor; -namespace Rubberduck.Parsing.Symbols +namespace Rubberduck.Parsing.VBA.ReferenceManagement { public sealed class IdentifierReferenceResolver { @@ -23,6 +23,7 @@ public sealed class IdentifierReferenceResolver private Declaration _currentParent; private readonly BindingService _bindingService; private readonly BoundExpressionVisitor _boundExpressionVisitor; + private readonly FailedResolutionVisitor _failedResolutionVisitor; private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); public IdentifierReferenceResolver(QualifiedModuleName qualifiedModuleName, DeclarationFinder finder) @@ -44,6 +45,7 @@ public IdentifierReferenceResolver(QualifiedModuleName qualifiedModuleName, Decl typeBindingContext, procedurePointerBindingContext); _boundExpressionVisitor = new BoundExpressionVisitor(finder); + _failedResolutionVisitor = new FailedResolutionVisitor(finder); } public void SetCurrentScope() @@ -70,13 +72,15 @@ public void SetCurrentScope(string memberName, DeclarationType type) public void EnterWithBlock(VBAParser.WithStmtContext context) { + var withExpression = GetInnerMostWithExpression(); var boundExpression = _bindingService.ResolveDefault( _moduleDeclaration, _currentParent, context.expression(), - GetInnerMostWithExpression(), + withExpression, StatementResolutionContext.Undefined, false); + _failedResolutionVisitor.CollectUnresolved(boundExpression, _currentParent, withExpression); _boundExpressionVisitor.AddIdentifierReferences(boundExpression, _qualifiedModuleName, _currentScope, _currentParent); // note: pushes null if unresolved _withBlockExpressions.Push(boundExpression); @@ -179,23 +183,8 @@ private void ResolveDefault( statementContext, requiresLetCoercion, isAssignmentTarget); - if (boundExpression.Classification == ExpressionClassification.ResolutionFailed) - { - var lExpression = expression as VBAParser.LExpressionContext - ?? expression.GetChild(0) - ?? (expression as VBAParser.LExprContext - ?? expression.GetChild(0))?.lExpression(); - if (lExpression != null) - { - _declarationFinder.AddUnboundContext(_currentParent, lExpression, withExpression); - } - else - { - Logger.Warn( - $"Default Context: Failed to resolve {expression.GetText()}. Binding as much as we can."); - } - } + _failedResolutionVisitor.CollectUnresolved(boundExpression, _currentParent, withExpression); _boundExpressionVisitor.AddIdentifierReferences( boundExpression, @@ -214,6 +203,8 @@ private void ResolveType(ParserRuleContext expression) { Logger.Warn($"Type Context: Failed to resolve {expression.GetText()}. Binding as much as we can."); } + + _failedResolutionVisitor.CollectUnresolved(boundExpression, _currentParent, GetInnerMostWithExpression()); _boundExpressionVisitor.AddIdentifierReferences(boundExpression, _qualifiedModuleName, _currentScope, _currentParent); } @@ -635,16 +626,19 @@ public void Resolve(VBAParser.ForNextStmtContext context) // In "For expr1 = expr2" the "expr1 = expr2" part is treated as a single expression. var assignmentExpr = ((VBAParser.RelationalOpContext)context.expression()[0]); var lExpr = assignmentExpr.expression()[0]; + var withExpression = GetInnerMostWithExpression(); var firstExpression = _bindingService.ResolveDefault( _moduleDeclaration, _currentParent, lExpr, - GetInnerMostWithExpression(), + withExpression, StatementResolutionContext.Undefined, true); if (firstExpression.Classification != ExpressionClassification.ResolutionFailed) { // each iteration counts as an assignment + + _failedResolutionVisitor.CollectUnresolved(firstExpression, _currentParent, withExpression); _boundExpressionVisitor.AddIdentifierReferences( firstExpression, _qualifiedModuleName, @@ -657,9 +651,10 @@ public void Resolve(VBAParser.ForNextStmtContext context) _moduleDeclaration, _currentParent, rExpr, - GetInnerMostWithExpression(), + withExpression, StatementResolutionContext.Undefined, true); + _failedResolutionVisitor.CollectUnresolved(secondExpression, _currentParent, withExpression); _boundExpressionVisitor.AddIdentifierReferences( secondExpression, _qualifiedModuleName, @@ -682,16 +677,18 @@ private void Resolve(VBAParser.StepStmtContext context) public void Resolve(VBAParser.ForEachStmtContext context) { + var withExpression = GetInnerMostWithExpression(); var firstExpression = _bindingService.ResolveDefault( _moduleDeclaration, _currentParent, context.expression()[0], - GetInnerMostWithExpression(), + withExpression, StatementResolutionContext.Undefined, false); if (firstExpression.Classification == ExpressionClassification.ResolutionFailed) { + _failedResolutionVisitor.CollectUnresolved(firstExpression, _currentParent, withExpression); _boundExpressionVisitor.AddIdentifierReferences( firstExpression, _qualifiedModuleName, @@ -701,6 +698,7 @@ public void Resolve(VBAParser.ForEachStmtContext context) else { // each iteration counts as an assignment + _failedResolutionVisitor.CollectUnresolved(firstExpression, _currentParent, withExpression); _boundExpressionVisitor.AddIdentifierReferences( firstExpression, _qualifiedModuleName, diff --git a/RubberduckTests/Inspections/MemberNotOnInterfaceInspectionTests.cs b/RubberduckTests/Inspections/MemberNotOnInterfaceInspectionTests.cs index ecda3ab5af..f59f65a8ed 100644 --- a/RubberduckTests/Inspections/MemberNotOnInterfaceInspectionTests.cs +++ b/RubberduckTests/Inspections/MemberNotOnInterfaceInspectionTests.cs @@ -264,10 +264,9 @@ End With } } - //See https://github.com/rubberduck-vba/Rubberduck/issues/4308 [Test] [Category("Inspections")] - [Ignore("To be unignored in a PR fixing issue 4308.")] + //See https://github.com/rubberduck-vba/Rubberduck/issues/4308 public void MemberNotOnInterface_ProcedureArgument() { const string inputCode = @@ -289,6 +288,282 @@ Private Sub Bar(baz As Long) } } + [Test] + [Category("Inspections")] + public void MemberNotOnInterface_FunctionArgument() + { + const string inputCode = + @"Sub Foo() + Dim fooBaz As Dictionary + Dim fooBar As Variant + Set fooBaz = New Dictionary + fooBar = Bar(fooBaz.FooBar) +End Sub + +Private Function Bar(baz As Long) As Variant +End Function"; + + using (var state = ArrangeParserAndParse(inputCode)) + { + var inspection = new MemberNotOnInterfaceInspection(state); + var inspectionResults = inspection.GetInspectionResults(CancellationToken.None); + + Assert.AreEqual(1, inspectionResults.Count()); + } + } + + [Test] + [Category("Inspections")] + public void MemberNotOnInterface_Expression() + { + const string inputCode = + @"Sub Foo() + Dim fooBaz As Dictionary + Dim fooBar As Variant + Set fooBaz = New Dictionary + fooBar = 1 + fooBaz.FooBar +End Sub + +Private Function Bar(baz As Long) As Variant +End Function"; + + using (var state = ArrangeParserAndParse(inputCode)) + { + var inspection = new MemberNotOnInterfaceInspection(state); + var inspectionResults = inspection.GetInspectionResults(CancellationToken.None); + + Assert.AreEqual(1, inspectionResults.Count()); + } + } + + [Test] + [Category("Inspections")] + public void MemberNotOnInterface_Expression_BothSides() + { + const string inputCode = + @"Sub Foo() + Dim fooBaz As Dictionary + Dim fooBar As Variant + Set fooBaz = New Dictionary + fooBar = fooBaz.NotThere + fooBaz.FooBar +End Sub + +Private Function Bar(baz As Long) As Variant +End Function"; + + using (var state = ArrangeParserAndParse(inputCode)) + { + var inspection = new MemberNotOnInterfaceInspection(state); + var inspectionResults = inspection.GetInspectionResults(CancellationToken.None); + + Assert.AreEqual(2, inspectionResults.Count()); + } + } + + [Test] + [Category("Inspections")] + public void MemberNotOnInterface_DeepExpression() + { + const string inputCode = + @"Sub Foo() + Dim fooBaz As Dictionary + Dim fooBar As Variant + Set fooBaz = New Dictionary + fooBar = 1 + (1 + (1 + (1 + fooBaz.FooBar))) +End Sub + +Private Function Bar(baz As Long) As Variant +End Function"; + + using (var state = ArrangeParserAndParse(inputCode)) + { + var inspection = new MemberNotOnInterfaceInspection(state); + var inspectionResults = inspection.GetInspectionResults(CancellationToken.None); + + Assert.AreEqual(1, inspectionResults.Count()); + } + } + + [Test] + [Category("Inspections")] + public void MemberNotOnInterface_ExpressionInFunctionArgument() + { + const string inputCode = + @"Sub Foo() + Dim fooBaz As Dictionary + Dim fooBar As Variant + Set fooBaz = New Dictionary + fooBar = Bar(1 + fooBaz.FooBar) +End Sub + +Private Function Bar(baz As Long) As Variant +End Function"; + + using (var state = ArrangeParserAndParse(inputCode)) + { + var inspection = new MemberNotOnInterfaceInspection(state); + var inspectionResults = inspection.GetInspectionResults(CancellationToken.None); + + Assert.AreEqual(1, inspectionResults.Count()); + } + } + + [Test] + [Category("Inspections")] + public void MemberNotOnInterface_FunctionArgumentInFunctionArgument() + { + const string inputCode = + @"Sub Foo() + Dim fooBaz As Dictionary + Dim fooBar As Variant + Set fooBaz = New Dictionary + fooBar = Bar(Bar(fooBaz.FooBar)) +End Sub + +Private Function Bar(baz As Long) As Variant +End Function"; + + using (var state = ArrangeParserAndParse(inputCode)) + { + var inspection = new MemberNotOnInterfaceInspection(state); + var inspectionResults = inspection.GetInspectionResults(CancellationToken.None); + + Assert.AreEqual(1, inspectionResults.Count()); + } + } + + [Test] + [Category("Inspections")] + public void MemberNotOnInterface_FunctionArgumentInExpressionInFunctionArgument() + { + const string inputCode = + @"Sub Foo() + Dim fooBaz As Dictionary + Dim fooBar As Variant + Set fooBaz = New Dictionary + fooBar = Bar(1 + Bar(fooBaz.FooBar)) +End Sub + +Private Function Bar(baz As Long) As Variant +End Function"; + + using (var state = ArrangeParserAndParse(inputCode)) + { + var inspection = new MemberNotOnInterfaceInspection(state); + var inspectionResults = inspection.GetInspectionResults(CancellationToken.None); + + Assert.AreEqual(1, inspectionResults.Count()); + } + } + + [Test] + [Category("Inspections")] + public void MemberNotOnInterface_FunctionArgumentInExpressionInProcedureArgument() + { + const string inputCode = + @"Sub Foo() + Dim fooBaz As Dictionary + Dim fooBar As Variant + Set fooBaz = New Dictionary + Barr 1 + Bar(fooBaz.FooBar) +End Sub + +Private Function Bar(baz As Long) As Variant +End Function + +Private Sub Barr(baz As Long) +End Sub"; + + using (var state = ArrangeParserAndParse(inputCode)) + { + var inspection = new MemberNotOnInterfaceInspection(state); + var inspectionResults = inspection.GetInspectionResults(CancellationToken.None); + + Assert.AreEqual(1, inspectionResults.Count()); + } + } + + [Test] + [Category("Inspections")] + public void MemberNotOnInterface_FunctionArgumentInExpressionInProcedureArgument_ExplicitCall() + { + const string inputCode = + @"Sub Foo() + Dim fooBaz As Dictionary + Dim fooBar As Variant + Set fooBaz = New Dictionary + Call Barr(1 + Bar(fooBaz.FooBar)) +End Sub + +Private Function Bar(baz As Long) As Variant +End Function + +Private Sub Barr(baz As Long) +End Sub"; + + using (var state = ArrangeParserAndParse(inputCode)) + { + var inspection = new MemberNotOnInterfaceInspection(state); + var inspectionResults = inspection.GetInspectionResults(CancellationToken.None); + + Assert.AreEqual(1, inspectionResults.Count()); + } + } + + [Test] + [Category("Inspections")] + public void MemberNotOnInterface_InOutputList() + { + const string inputCode = + @"Sub Foo() + Dim fooBaz As Dictionary + Dim fooBar As Variant + Set fooBaz = New Dictionary + Debug.Print fooBaz.FooBar; Spc(fooBaz.NotThere); Tab(fooBaz.Neither) +End Sub + +Private Function Bar(baz As Long) As Variant +End Function + +Private Sub Barr(baz As Long) +End Sub"; + + using (var state = ArrangeParserAndParse(inputCode)) + { + var inspection = new MemberNotOnInterfaceInspection(state); + var inspectionResults = inspection.GetInspectionResults(CancellationToken.None); + + Assert.AreEqual(3, inspectionResults.Count()); + } + } + + [Test] + [Category("Inspections")] + public void MemberNotOnInterface_FunctionArgumentInExpressionInOutputList() + { + const string inputCode = + @"Sub Foo() + Dim fooBaz As Dictionary + Dim fooBar As Variant + Set fooBaz = New Dictionary + Debug.Print 1 + Bar(fooBaz.FooBar) +End Sub + +Private Function Bar(baz As Long) As Variant +End Function + +Private Sub Barr(baz As Long) +End Sub"; + + using (var state = ArrangeParserAndParse(inputCode)) + { + var inspection = new MemberNotOnInterfaceInspection(state); + var inspectionResults = inspection.GetInspectionResults(CancellationToken.None); + + Assert.AreEqual(1, inspectionResults.Count()); + } + } + [Test] [Category("Inspections")] public void MemberNotOnInterface_DoesNotReturnResult_WithNewBlockBangNotation() From 9ee0bf90c3c1ad08d0a4b35d26ef0f16d8802c0c Mon Sep 17 00:00:00 2001 From: Max Doerner Date: Sat, 24 Aug 2019 16:12:06 +0200 Subject: [PATCH 29/32] Make previously inconclusive tests pass or ignored --- .../CodeExplorerProjectViewModelTests.cs | 6 +++--- .../ThunderCode/ThunderCodeInspectionTests.cs | 21 +++++++++++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/RubberduckTests/CodeExplorer/CodeExplorerProjectViewModelTests.cs b/RubberduckTests/CodeExplorer/CodeExplorerProjectViewModelTests.cs index 569f2b120c..a8ea2490c6 100644 --- a/RubberduckTests/CodeExplorer/CodeExplorerProjectViewModelTests.cs +++ b/RubberduckTests/CodeExplorer/CodeExplorerProjectViewModelTests.cs @@ -127,8 +127,8 @@ public void IsNotFiltered(string filter) [Category("Code Explorer")] [TestCase(DeclarationType.Parameter)] [TestCase(DeclarationType.LineLabel)] - [TestCase(DeclarationType.UnresolvedMember)] // TODO: Inconclusive pending test setup that will actually create one :-/ - //[TestCase(DeclarationType.BracketedExpression)] // TODO: This causes a parser error in testing due to no host application. + [TestCase(DeclarationType.UnresolvedMember, Ignore = "Pending test setup that will actually create one")] + [TestCase(DeclarationType.BracketedExpression, Ignore = "This causes a parser error in testing due to no host application.")] public void TrackedDeclarations_ExcludesNonNodeTypes(DeclarationType excluded) { var declarations = CodeExplorerTestSetup.TestProjectOneDeclarations; @@ -148,7 +148,7 @@ public void TrackedDeclarations_ExcludesNonNodeTypes(DeclarationType excluded) [Category("Code Explorer")] [TestCase(DeclarationType.Variable)] [TestCase(DeclarationType.Control)] - [TestCase(DeclarationType.Constant)] // TODO: Inconclusive pending test setup that will actually create one :-/ + [TestCase(DeclarationType.Constant, Ignore = "Pending test setup that will actually create one.")] public void TrackedDeclarations_ExcludesMemberEnclosedTypes(DeclarationType excluded) { var declarations = CodeExplorerTestSetup.TestProjectOneDeclarations; diff --git a/RubberduckTests/Inspections/ThunderCode/ThunderCodeInspectionTests.cs b/RubberduckTests/Inspections/ThunderCode/ThunderCodeInspectionTests.cs index d061bd4618..a2fef0ec90 100644 --- a/RubberduckTests/Inspections/ThunderCode/ThunderCodeInspectionTests.cs +++ b/RubberduckTests/Inspections/ThunderCode/ThunderCodeInspectionTests.cs @@ -15,12 +15,11 @@ namespace RubberduckTests.Inspections.ThunderCode public class ThunderCodeInspectionTests { [Test] + [Category("Inspections")] [TestCase(1, "Public Sub foo\u00A0bar()" + @" End Sub")] [TestCase(0, @"Public Sub foo() End Sub")] - [TestCase(0, @"Public Sub foo bar() -End Sub")] // Correctly provokes a parser error public void NonBreakingSpaceIdentifier_ReturnsResult(int expectedCount, string inputCode) { var func = new Func(state => @@ -28,6 +27,24 @@ public void NonBreakingSpaceIdentifier_ReturnsResult(int expectedCount, string i ThunderCatsGo(func, inputCode, expectedCount); } + [Test] + [Category("Inspections")] + [TestCase(0, @"Public Sub foo bar() +End Sub")] + public void NonBreakingSpaceIdentifier_IllegalInputCausesParserError(int expectedCount, string inputCode) + { + var vbe = MockVbeBuilder.BuildFromSingleStandardModule(inputCode, out var component); + var parser = MockParser.Create(vbe.Object); + using (var state = parser.State) + { + parser.Parse(new CancellationTokenSource()); + var actualStatus = state.Status; + Assert.AreEqual(ParserState.Error, actualStatus); + } + } + + + [Test] [TestCase(2, @"Do")] [TestCase(2, @"Loop")] From 3a915a486b7eb3f92edbd136b12940916a737d1d Mon Sep 17 00:00:00 2001 From: Max Doerner Date: Sat, 24 Aug 2019 16:55:00 +0200 Subject: [PATCH 30/32] Add non-undeclared declarations for ReDimed array without dclaration --- .../DeclarationCaching/DeclarationFinder.cs | 14 +++++++-- .../UndeclaredVariableInspectionTests.cs | 29 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/Rubberduck.Parsing/VBA/DeclarationCaching/DeclarationFinder.cs b/Rubberduck.Parsing/VBA/DeclarationCaching/DeclarationFinder.cs index a62735cba9..de250d2443 100644 --- a/Rubberduck.Parsing/VBA/DeclarationCaching/DeclarationFinder.cs +++ b/Rubberduck.Parsing/VBA/DeclarationCaching/DeclarationFinder.cs @@ -974,6 +974,9 @@ public Declaration OnUndeclaredVariable(Declaration enclosingProcedure, string i { var annotations = FindAnnotations(enclosingProcedure.QualifiedName.QualifiedModuleName, context.Start.Line) .Where(annotation => annotation.AnnotationType.HasFlag(AnnotationType.IdentifierAnnotation)); + + var isReDimVariable = IsContainedInReDimedArrayName(context); + var undeclaredLocal = new Declaration( new QualifiedMemberName(enclosingProcedure.QualifiedName.QualifiedModuleName, identifierName), @@ -988,12 +991,12 @@ public Declaration OnUndeclaredVariable(Declaration enclosingProcedure, string i context, null, context.GetSelection(), - false, + isReDimVariable, null, true, annotations, null, - true); + !isReDimVariable); var hasUndeclared = _newUndeclared.ContainsKey(enclosingProcedure.QualifiedName); if (hasUndeclared) @@ -1017,6 +1020,13 @@ public Declaration OnUndeclaredVariable(Declaration enclosingProcedure, string i return undeclaredLocal; } + private static bool IsContainedInReDimedArrayName(ParserRuleContext context) + { + var enclosingReDimContext = context.GetAncestor(); + return enclosingReDimContext != null + && enclosingReDimContext.expression().GetSelection().Contains(context.GetSelection()); + } + public void AddUnboundContext(Declaration parentDeclaration, VBAParser.LExpressionContext context, IBoundExpression withExpression) { diff --git a/RubberduckTests/Inspections/UndeclaredVariableInspectionTests.cs b/RubberduckTests/Inspections/UndeclaredVariableInspectionTests.cs index 1033fd7aed..da610c9005 100644 --- a/RubberduckTests/Inspections/UndeclaredVariableInspectionTests.cs +++ b/RubberduckTests/Inspections/UndeclaredVariableInspectionTests.cs @@ -88,6 +88,35 @@ Debug.Print a } } + [Test] + [Category("Inspections")] + //ReDim acts as a declaration if the array is not declared already. + //See issue #2522 at https://github.com/rubberduck-vba/Rubberduck/issues/2522 + public void UndeclaredVariable_ReturnsNoResultForReDim() + { + const string inputCode = + @" +Sub Test() + Dim bar As Variant + ReDim arr(1 To 42) + bar = arr +End Sub"; + + var builder = new MockVbeBuilder(); + var project = builder.ProjectBuilder("VBAProject", ProjectProtection.Unprotected) + .AddComponent("MyClass", ComponentType.ClassModule, inputCode) + .Build(); + var vbe = builder.AddProject(project).Build(); + + using (var state = MockParser.CreateAndParse(vbe.Object)) + { + var inspection = new UndeclaredVariableInspection(state); + var inspectionResults = inspection.GetInspectionResults(CancellationToken.None); + + Assert.IsFalse(inspectionResults.Any()); + } + } + //https://github.com/rubberduck-vba/Rubberduck/issues/2525 [Test] [Category("Inspections")] From 617b27f07c3bc45890f3fff8194436b0b98888f7 Mon Sep 17 00:00:00 2001 From: Max Doerner Date: Sat, 24 Aug 2019 18:38:48 +0200 Subject: [PATCH 31/32] Strip parentheses from AsTypeName of array returning functions and properties --- .../DeclarationSymbolsListener.cs | 11 ++++- RubberduckTests/Grammar/ResolverTests.cs | 46 +++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/Rubberduck.Parsing/VBA/DeclarationResolving/DeclarationSymbolsListener.cs b/Rubberduck.Parsing/VBA/DeclarationResolving/DeclarationSymbolsListener.cs index f13ccbe09f..d7ff1e4652 100644 --- a/Rubberduck.Parsing/VBA/DeclarationResolving/DeclarationSymbolsListener.cs +++ b/Rubberduck.Parsing/VBA/DeclarationResolving/DeclarationSymbolsListener.cs @@ -93,6 +93,7 @@ private Declaration CreateDeclaration( var isByRef = argContext.BYREF() != null || argContext.BYVAL() == null; var isParamArray = argContext.PARAMARRAY() != null; + result = new ParameterDeclaration( new QualifiedMemberName(_qualifiedModuleName, identifierName), _parentDeclaration, @@ -385,9 +386,12 @@ public override void EnterFunctionStmt(VBAParser.FunctionStmtContext context) : asTypeClause.type().GetText() : SymbolList.TypeHintToTypeName[typeHint]; var isArray = asTypeName.EndsWith("()"); + var actualAsTypeName = isArray && asTypeName.EndsWith("()") + ? asTypeName.Substring(0, asTypeName.Length - 2) + : asTypeName; var declaration = CreateDeclaration( name, - asTypeName, + actualAsTypeName, accessibility, DeclarationType.Function, context, @@ -417,9 +421,12 @@ public override void EnterPropertyGetStmt(VBAParser.PropertyGetStmtContext conte : asTypeClause.type().GetText() : SymbolList.TypeHintToTypeName[typeHint]; var isArray = asTypeClause != null && asTypeClause.type().LPAREN() != null; + var actualAsTypeName = isArray && asTypeName.EndsWith("()") + ? asTypeName.Substring(0, asTypeName.Length - 2) + : asTypeName; var declaration = CreateDeclaration( name, - asTypeName, + actualAsTypeName, accessibility, DeclarationType.PropertyGet, context, diff --git a/RubberduckTests/Grammar/ResolverTests.cs b/RubberduckTests/Grammar/ResolverTests.cs index b1b64c3294..40fecb2169 100644 --- a/RubberduckTests/Grammar/ResolverTests.cs +++ b/RubberduckTests/Grammar/ResolverTests.cs @@ -2730,6 +2730,52 @@ End Sub } } + [Category("Grammar")] + [Category("Resolver")] + [Test] + public void FunctionTypeNameIsElementTypeName() + { + var code = @" +Public Function bar() As Long() +End Function +"; + using (var state = Resolve(code)) + { + + var declaration = state.DeclarationFinder + .UserDeclarations(DeclarationType.Function) + .Single(); + + var expectedTypeName = "Long"; + var actualAsTypeName = declaration.AsTypeName; + + Assert.AreEqual(expectedTypeName, actualAsTypeName); + } + } + + [Category("Grammar")] + [Category("Resolver")] + [Test] + public void PropertyGetTypeNameIsElementTypeName() + { + var code = @" +Public Property Get bar() As Long() +End Property +"; + using (var state = Resolve(code)) + { + + var declaration = state.DeclarationFinder + .UserDeclarations(DeclarationType.PropertyGet) + .Single(); + + var expectedTypeName = "Long"; + var actualAsTypeName = declaration.AsTypeName; + + Assert.AreEqual(expectedTypeName, actualAsTypeName); + } + } + [Category("Grammar")] [Category("Resolver")] [Test] From 02bda4f75e48f5296d51fe4cc696076bb8bd1ae0 Mon Sep 17 00:00:00 2001 From: Max Doerner Date: Sat, 24 Aug 2019 21:48:43 +0200 Subject: [PATCH 32/32] Fix formatting in RD command bar broken by prior commits This includes a complete refactoring of ContextFormatter and changes the behaviour to always show parentheses in in the type if the declaration is an array. Moreover, the parentheses following the identifier name are no longer added for array variables. (They are obviously arrays based on the new parentheses in the type name.) --- .../CommandBars/IContextFormatter.cs | 133 +++++++++--------- .../CodeExplorerProjectViewModelTests.cs | 4 +- 2 files changed, 70 insertions(+), 67 deletions(-) diff --git a/Rubberduck.Core/UI/Command/MenuItems/CommandBars/IContextFormatter.cs b/Rubberduck.Core/UI/Command/MenuItems/CommandBars/IContextFormatter.cs index 679a71b11e..729ce5ecef 100644 --- a/Rubberduck.Core/UI/Command/MenuItems/CommandBars/IContextFormatter.cs +++ b/Rubberduck.Core/UI/Command/MenuItems/CommandBars/IContextFormatter.cs @@ -1,7 +1,9 @@ +using System; using Rubberduck.Parsing; using Rubberduck.Parsing.Symbols; using Rubberduck.VBEditor.SafeComWrappers.Abstract; using Rubberduck.Resources; +using Rubberduck.VBEditor; namespace Rubberduck.UI.Command.MenuItems.CommandBars { @@ -46,96 +48,97 @@ public string Format(Declaration declaration, bool multipleControls) private string FormatDeclaration(Declaration declaration, bool multipleControls = false) { - var formattedDeclaration = string.Empty; var moduleName = declaration.QualifiedName.QualifiedModuleName; - var typeName = declaration.HasTypeHint - ? SymbolList.TypeHintToTypeName[declaration.TypeHint] - : declaration.AsTypeName; var declarationType = RubberduckUI.ResourceManager.GetString("DeclarationType_" + declaration.DeclarationType, Settings.Settings.Culture); - if (multipleControls) - { - typeName = RubberduckUI.ContextMultipleControlsSelection; - } - else if (declaration is ValuedDeclaration) - { - var valued = (ValuedDeclaration)declaration; - typeName = "(" + declarationType + (string.IsNullOrEmpty(typeName) ? string.Empty : ":" + typeName) + - (string.IsNullOrEmpty(valued.Expression) ? string.Empty : $" = {valued.Expression}") + ")"; + var typeName = TypeName(declaration, multipleControls, declarationType); + var formattedDeclaration = FormattedDeclaration(declaration, typeName, moduleName, declarationType); + return formattedDeclaration.Trim(); + } - } - else if (declaration is ParameterDeclaration) - { - var parameter = (ParameterDeclaration)declaration; - typeName = "(" + declarationType + (string.IsNullOrEmpty(typeName) ? string.Empty : ":" + typeName) + - (string.IsNullOrEmpty(parameter.DefaultValue) ? string.Empty : $" = {parameter.DefaultValue}") + ")"; - } - else + private static string FormattedDeclaration( + Declaration declaration, + string typeName, + QualifiedModuleName moduleName, + string declarationType) + { + if (declaration.ParentDeclaration != null) { - typeName = "(" + declarationType + (string.IsNullOrEmpty(typeName) ? string.Empty : ":" + typeName) + ")"; - } + if (declaration.ParentDeclaration.DeclarationType.HasFlag(DeclarationType.Member)) + { + // locals, parameters + return $"{declaration.ParentDeclaration.QualifiedName}:{declaration.IdentifierName} {typeName}"; + } + + if (declaration.ParentDeclaration.DeclarationType.HasFlag(DeclarationType.Module)) + { + // fields + var withEvents = declaration.IsWithEvents ? "(WithEvents) " : string.Empty; + return $"{withEvents}{moduleName}.{declaration.IdentifierName} {typeName}"; + } + } - if (declaration.DeclarationType.HasFlag(DeclarationType.Project) || declaration.DeclarationType == DeclarationType.BracketedExpression) - { - var filename = System.IO.Path.GetFileName(declaration.QualifiedName.QualifiedModuleName.ProjectPath); - formattedDeclaration = string.Format("{0}{1}{2} ({3})", filename, string.IsNullOrEmpty(filename) ? string.Empty : ";", declaration.IdentifierName, declarationType); - } - else if (declaration.DeclarationType.HasFlag(DeclarationType.Module)) - { - formattedDeclaration = moduleName + " (" + declarationType + ")"; - } - if (declaration.DeclarationType.HasFlag(DeclarationType.Member)) { - formattedDeclaration = declaration.QualifiedName.ToString(); + var formattedDeclaration = declaration.QualifiedName.ToString(); if (declaration.DeclarationType == DeclarationType.Function || declaration.DeclarationType == DeclarationType.PropertyGet) { formattedDeclaration += typeName; } + + return formattedDeclaration; } - if (declaration.DeclarationType == DeclarationType.Enumeration - || declaration.DeclarationType == DeclarationType.UserDefinedType) - { - formattedDeclaration = !declaration.IsUserDefined - // built-in enums & UDT's don't have a module - ? System.IO.Path.GetFileName(moduleName.ProjectPath) + ";" + moduleName.ProjectName + "." + declaration.IdentifierName - : moduleName.ToString(); - } - else if (declaration.DeclarationType == DeclarationType.EnumerationMember - || declaration.DeclarationType == DeclarationType.UserDefinedTypeMember) + if (declaration.DeclarationType.HasFlag(DeclarationType.Module)) { - formattedDeclaration = string.Format("{0}.{1}.{2} {3}", - !declaration.IsUserDefined - ? System.IO.Path.GetFileName(moduleName.ProjectPath) + ";" + moduleName.ProjectName - : moduleName.ToString(), - declaration.ParentDeclaration.IdentifierName, - declaration.IdentifierName, - typeName); + return $"{moduleName} ({declarationType})"; } - else if (declaration.DeclarationType == DeclarationType.ComAlias) + + switch (declaration.DeclarationType) { - formattedDeclaration = string.Format("{0};{1}.{2} (alias:{3})", - System.IO.Path.GetFileName(moduleName.ProjectPath), moduleName.ProjectName, - declaration.IdentifierName, declaration.AsTypeName); + case DeclarationType.Project: + case DeclarationType.BracketedExpression: + var filename = System.IO.Path.GetFileName(declaration.QualifiedName.QualifiedModuleName.ProjectPath); + return $"{filename}{(string.IsNullOrEmpty(filename) ? string.Empty : ";")}{declaration.IdentifierName} ({declarationType})"; + case DeclarationType.Enumeration: + case DeclarationType.UserDefinedType: + return !declaration.IsUserDefined + // built-in enums & UDT's don't have a module + ? $"{System.IO.Path.GetFileName(moduleName.ProjectPath)};{moduleName.ProjectName}.{declaration.IdentifierName}" + : moduleName.ToString(); + case DeclarationType.EnumerationMember: + case DeclarationType.UserDefinedTypeMember: + return declaration.IsUserDefined + ? $"{moduleName}.{declaration.ParentDeclaration.IdentifierName}.{declaration.IdentifierName} {typeName}" + : $"{System.IO.Path.GetFileName(moduleName.ProjectPath)};{moduleName.ProjectName}.{declaration.ParentDeclaration.IdentifierName}.{declaration.IdentifierName} {typeName}"; + case DeclarationType.ComAlias: + return $"{System.IO.Path.GetFileName(moduleName.ProjectPath)};{moduleName.ProjectName}.{declaration.IdentifierName} (alias:{declaration.AsTypeName})"; } - var subscripts = declaration.IsArray ? "()" : string.Empty; - if (declaration.ParentDeclaration != null && declaration.ParentDeclaration.DeclarationType.HasFlag(DeclarationType.Member)) + return string.Empty; + } + + private static string TypeName(Declaration declaration, bool multipleControls, string declarationType) + { + if (multipleControls) { - // locals, parameters - formattedDeclaration = string.Format("{0}:{1}{2} {3}", declaration.ParentDeclaration.QualifiedName, declaration.IdentifierName, subscripts, typeName); + return RubberduckUI.ContextMultipleControlsSelection; } - if (declaration.ParentDeclaration != null && declaration.ParentDeclaration.DeclarationType.HasFlag(DeclarationType.Module)) + var typeName = declaration.IsArray + ? $"{declaration.AsTypeName}()" + : declaration.AsTypeName; + + switch (declaration) { - // fields - var withEvents = declaration.IsWithEvents ? "(WithEvents) " : string.Empty; - formattedDeclaration = string.Format("{0}{1}.{2} {3}", withEvents, moduleName, declaration.IdentifierName, typeName); + case ValuedDeclaration valued: + return $"({declarationType}{(string.IsNullOrEmpty(typeName) ? string.Empty : ":" + typeName)}{(string.IsNullOrEmpty(valued.Expression) ? string.Empty : $" = {valued.Expression}")})"; + case ParameterDeclaration parameter: + return $"({declarationType}{(string.IsNullOrEmpty(typeName) ? string.Empty : ":" + typeName)}{(string.IsNullOrEmpty(parameter.DefaultValue) ? string.Empty : $" = {parameter.DefaultValue}")})"; + default: + return $"({declarationType}{(string.IsNullOrEmpty(typeName) ? string.Empty : ":" + typeName)})"; } - - return formattedDeclaration.Trim(); } } } \ No newline at end of file diff --git a/RubberduckTests/CodeExplorer/CodeExplorerProjectViewModelTests.cs b/RubberduckTests/CodeExplorer/CodeExplorerProjectViewModelTests.cs index a8ea2490c6..feed5398d0 100644 --- a/RubberduckTests/CodeExplorer/CodeExplorerProjectViewModelTests.cs +++ b/RubberduckTests/CodeExplorer/CodeExplorerProjectViewModelTests.cs @@ -147,8 +147,8 @@ public void TrackedDeclarations_ExcludesNonNodeTypes(DeclarationType excluded) [Test] [Category("Code Explorer")] [TestCase(DeclarationType.Variable)] - [TestCase(DeclarationType.Control)] - [TestCase(DeclarationType.Constant, Ignore = "Pending test setup that will actually create one.")] + [TestCase(DeclarationType.Control, Ignore = "Pending test setup that will actually create one.")] + [TestCase(DeclarationType.Constant)] public void TrackedDeclarations_ExcludesMemberEnclosedTypes(DeclarationType excluded) { var declarations = CodeExplorerTestSetup.TestProjectOneDeclarations;