diff --git a/RetailCoder.VBE/UI/SelectionChangeService.cs b/RetailCoder.VBE/UI/SelectionChangeService.cs index df4d85fd92..14f97377af 100644 --- a/RetailCoder.VBE/UI/SelectionChangeService.cs +++ b/RetailCoder.VBE/UI/SelectionChangeService.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Threading.Tasks; using Rubberduck.Parsing; using Rubberduck.Parsing.Symbols; using Rubberduck.VBEditor.Events; @@ -39,8 +40,11 @@ private void OnVbeSelectionChanged(object sender, SelectionChangedEventArgs e) return; } - var eventArgs = new DeclarationChangedEventArgs(e.CodePane, _parser.State.FindSelectedDeclaration(e.CodePane)); - DispatchSelectedDeclaration(eventArgs); + new Task(() => + { + var eventArgs = new DeclarationChangedEventArgs(e.CodePane, _parser.State.FindSelectedDeclaration(e.CodePane)); + DispatchSelectedDeclaration(eventArgs); + }).Start(); } private void OnVbeFocusChanged(object sender, WindowChangedEventArgs e) @@ -55,13 +59,13 @@ private void OnVbeFocusChanged(object sender, WindowChangedEventArgs e) { return; } - DispatchSelectedDesignerDeclaration(_vbe.SelectedVBComponent); + new Task(() => DispatchSelectedDesignerDeclaration(_vbe.SelectedVBComponent)).Start(); break; case WindowKind.CodeWindow: //Caret changed in a code pane. if (e.CodePane != null && !e.CodePane.IsWrappingNullReference) { - DispatchSelectedDeclaration(new DeclarationChangedEventArgs(e.CodePane, _parser.State.FindSelectedDeclaration(e.CodePane))); + new Task(() => DispatchSelectedDeclaration(new DeclarationChangedEventArgs(e.CodePane, _parser.State.FindSelectedDeclaration(e.CodePane)))).Start(); } break; } @@ -69,7 +73,7 @@ private void OnVbeFocusChanged(object sender, WindowChangedEventArgs e) else if (e.EventType == FocusType.ChildFocus) { //Treeview selection changed in project window. - DispatchSelectedProjectNodeDeclaration(_vbe.SelectedVBComponent); + new Task(() => DispatchSelectedProjectNodeDeclaration(_vbe.SelectedVBComponent)).Start(); } } diff --git a/Rubberduck.Parsing/Symbols/Declaration.cs b/Rubberduck.Parsing/Symbols/Declaration.cs index 6a149d9f62..bcd9bfa599 100644 --- a/Rubberduck.Parsing/Symbols/Declaration.cs +++ b/Rubberduck.Parsing/Symbols/Declaration.cs @@ -616,5 +616,12 @@ public void ClearReferences() { _references = new ConcurrentBag(); } + + public void RemoveReferencesFrom(ICollection modulesByWhichToRemoveReferences) + { + //This gets replaced with a new ConcurrentBag because one cannot remove specific items from a ConcurrentBag. + //Moreover, changing to a ConcurrentDictionary breaks all sorts of tests, for some obscure reason. + var newReferences = new ConcurrentBag(_references.Where(reference => !modulesByWhichToRemoveReferences.Contains(reference.QualifiedModuleName))); + } } } diff --git a/Rubberduck.Parsing/Symbols/DeclarationFinder.cs b/Rubberduck.Parsing/Symbols/DeclarationFinder.cs index 7da0ee93f9..39441a4d3a 100644 --- a/Rubberduck.Parsing/Symbols/DeclarationFinder.cs +++ b/Rubberduck.Parsing/Symbols/DeclarationFinder.cs @@ -30,7 +30,7 @@ public static IEnumerable AllValues( public static ConcurrentDictionary> ToConcurrentDictionary(this IEnumerable> source) { return new ConcurrentDictionary>(source.Select(x => new KeyValuePair>(x.Key, new ConcurrentBag(x)))); - } + } } public class DeclarationFinder @@ -42,8 +42,9 @@ public class DeclarationFinder private readonly AnnotationService _annotationService; private readonly ConcurrentDictionary> _declarationsByName; private readonly ConcurrentDictionary> _declarations; - private readonly ConcurrentDictionary> _undeclared; - private readonly ConcurrentBag _unresolved; + private readonly ConcurrentDictionary> _newUndeclared; + private readonly ConcurrentBag _newUnresolved; + private readonly List _unresolved; private readonly ConcurrentDictionary> _annotations; private readonly ConcurrentDictionary> _parametersByParent; private readonly ConcurrentDictionary> _userDeclarationsByType; @@ -54,7 +55,7 @@ public class DeclarationFinder private static readonly object ThreadLock = new object(); - public DeclarationFinder(IReadOnlyList declarations, IEnumerable annotations, IHostApplication hostApp = null) + public DeclarationFinder(IReadOnlyList declarations, IEnumerable annotations, IReadOnlyList unresolvedMemberDeclarations, IHostApplication hostApp = null) { _hostApp = hostApp; _annotations = annotations.GroupBy(node => node.QualifiedSelection.QualifiedName).ToConcurrentDictionary(); @@ -90,8 +91,10 @@ public DeclarationFinder(IReadOnlyList declarations, IEnumerable item.WithEventsField, item => item.Handlers.ToArray()) ), true); - _undeclared = new ConcurrentDictionary>(new Dictionary>()); - _unresolved = new ConcurrentBag(new List()); + _newUndeclared = new ConcurrentDictionary>(new Dictionary>()); + _newUnresolved = new ConcurrentBag(new List()); + _unresolved = unresolvedMemberDeclarations.ToList(); + _annotationService = new AnnotationService(this); var implementsInstructions = UserDeclarations(DeclarationType.ClassModule).SelectMany(cls => @@ -134,9 +137,9 @@ public DeclarationFinder(IReadOnlyList declarations, IEnumerable item.Context, item => item.Members)), true); } - public IEnumerable Undeclared + public IEnumerable FreshUndeclared { - get { return _undeclared.AllValues(); } + get { return _newUndeclared.AllValues(); } } public IEnumerable Members(Declaration module) @@ -209,14 +212,19 @@ public IEnumerable UserDeclarations(DeclarationType type) return result; } - public IEnumerable UnresolvedMemberDeclarations() + public IEnumerable FreshUnresolvedMemberDeclarations() { lock (ThreadLock) { - return _unresolved.ToArray(); + return _newUnresolved.ToArray(); } } + public IEnumerable UnresolvedMemberDeclarations() + { + return _unresolved.ToList(); + } + public IEnumerable FindHandlersForWithEventsField(Declaration field) { Declaration[] result; @@ -510,13 +518,13 @@ public Declaration OnUndeclaredVariable(Declaration enclosingProcedure, string i Accessibility.Implicit, DeclarationType.Variable, context, context.GetSelection(), false, null, false, annotations, null, true); - var hasUndeclared = _undeclared.ContainsKey(enclosingProcedure.QualifiedName); + var hasUndeclared = _newUndeclared.ContainsKey(enclosingProcedure.QualifiedName); if (hasUndeclared) { ConcurrentBag undeclared; - while (!_undeclared.TryGetValue(enclosingProcedure.QualifiedName, out undeclared)) + while (!_newUndeclared.TryGetValue(enclosingProcedure.QualifiedName, out undeclared)) { - _undeclared.TryGetValue(enclosingProcedure.QualifiedName, out undeclared); + _newUndeclared.TryGetValue(enclosingProcedure.QualifiedName, out undeclared); } var inScopeUndeclared = undeclared.FirstOrDefault(d => d.IdentifierName == identifierName); if (inScopeUndeclared != null) @@ -527,7 +535,7 @@ public Declaration OnUndeclaredVariable(Declaration enclosingProcedure, string i } else { - _undeclared.TryAdd(enclosingProcedure.QualifiedName, new ConcurrentBag { undeclaredLocal }); + _newUndeclared.TryAdd(enclosingProcedure.QualifiedName, new ConcurrentBag { undeclaredLocal }); } return undeclaredLocal; } @@ -550,7 +558,7 @@ public void AddUnboundContext(Declaration parentDeclaration, VBAParser.LExprCont (access is VBAParser.MemberAccessExprContext) ? (ParserRuleContext)access.children[0] : withExpression.Context, annotations); - _unresolved.Add(declaration); + _newUnresolved.Add(declaration); } public Declaration OnBracketedExpression(string expression, ParserRuleContext context) @@ -561,13 +569,13 @@ public Declaration OnBracketedExpression(string expression, ParserRuleContext co var qualifiedName = hostApp.QualifiedName.QualifiedModuleName.QualifyMemberName(expression); ConcurrentBag undeclared; - if (_undeclared.TryGetValue(qualifiedName, out undeclared)) + if (_newUndeclared.TryGetValue(qualifiedName, out undeclared)) { return undeclared.SingleOrDefault(); } var item = new Declaration(qualifiedName, hostApp, hostApp, Tokens.Variant, string.Empty, false, false, Accessibility.Global, DeclarationType.BracketedExpression, context, context.GetSelection(), false, null); - _undeclared.TryAdd(qualifiedName, new ConcurrentBag { item }); + _newUndeclared.TryAdd(qualifiedName, new ConcurrentBag { item }); return item; } diff --git a/Rubberduck.Parsing/VBA/ModuleState.cs b/Rubberduck.Parsing/VBA/ModuleState.cs index 9773b718ab..0fcee29caa 100644 --- a/Rubberduck.Parsing/VBA/ModuleState.cs +++ b/Rubberduck.Parsing/VBA/ModuleState.cs @@ -13,6 +13,7 @@ namespace Rubberduck.Parsing.VBA public class ModuleState { public ConcurrentDictionary Declarations { get; private set; } + public ConcurrentDictionary UnresolvedMemberDeclarations { get; private set; } public ITokenStream TokenStream { get; private set; } public IParseTree ParseTree { get; private set; } public ParserState State { get; private set; } @@ -29,7 +30,8 @@ public class ModuleState public ModuleState(ConcurrentDictionary declarations) { Declarations = declarations; - TokenStream = null; + UnresolvedMemberDeclarations = new ConcurrentDictionary(); + TokenStream = null;UnboundMemberDeclaration ParseTree = null; if (declarations.Any() && declarations.ElementAt(0).Key.QualifiedName.QualifiedModuleName.Component != null) @@ -55,6 +57,7 @@ public ModuleState(ConcurrentDictionary declarations) public ModuleState(ParserState state) { Declarations = new ConcurrentDictionary(); + UnresolvedMemberDeclarations = new ConcurrentDictionary(); TokenStream = null; ParseTree = null; State = state; @@ -72,6 +75,7 @@ public ModuleState(ParserState state) public ModuleState(SyntaxErrorException moduleException) { Declarations = new ConcurrentDictionary(); + UnresolvedMemberDeclarations = new ConcurrentDictionary(); TokenStream = null; ParseTree = null; State = ParserState.Error; @@ -89,6 +93,7 @@ public ModuleState(SyntaxErrorException moduleException) public ModuleState(IDictionary, Attributes> moduleAttributes) { Declarations = new ConcurrentDictionary(); + UnresolvedMemberDeclarations = new ConcurrentDictionary(); TokenStream = null; ParseTree = null; State = ParserState.None; diff --git a/Rubberduck.Parsing/VBA/ParseCoordinator.cs b/Rubberduck.Parsing/VBA/ParseCoordinator.cs index 22109be3ac..32886bb60d 100644 --- a/Rubberduck.Parsing/VBA/ParseCoordinator.cs +++ b/Rubberduck.Parsing/VBA/ParseCoordinator.cs @@ -31,6 +31,7 @@ public class ParseCoordinator : IParseCoordinator private const int _maxDegreeOfDeclarationResolverParallelism = -1; private const int _maxDegreeOfReferenceResolverParallelism = -1; private const int _maxDegreeOfModuleStateChangeParallelism = -1; + private const int _maxDegreeOfReferenceRemovalParallelism = -1; private const int _maxReferenceLoadingConcurrency = -1; private readonly IDictionary, Attributes>> _componentAttributes @@ -185,7 +186,7 @@ private void ClearComponentStateCacheForTests() } } - private void CleanUpComponentAttributes(List components) + private void CleanUpComponentAttributes(ICollection components) { foreach (var key in _componentAttributes.Keys) { @@ -214,10 +215,9 @@ private void ExecuteCommonParseActivities(List toParse, Cancellati token.ThrowIfCancellationRequested(); - _projectDeclarations.Clear(); - State.ClearAllReferences(); - - ClearModuleToModuleReferences(toParse); + var modulesToParse = toParse.Select(component => new QualifiedModuleName(component)).ToHashSet(); + var toResolveReferences = ModulesForWhichToResolveReferences(modulesToParse); + PerformPreParseCleanup(modulesToParse, toResolveReferences, token); ParseComponents(toParse, token); @@ -241,7 +241,7 @@ private void ExecuteCommonParseActivities(List toParse, Cancellati throw new OperationCanceledException(token); } - ResolveAllReferences(token); + ResolveAllReferences(toResolveReferences, token); if (token.IsCancellationRequested || State.Status >= ParserState.Error) { @@ -270,11 +270,48 @@ private void SetModuleStates(List components, ParserState parserSt } } - private void ClearModuleToModuleReferences(List toClear) + private ICollection ModulesForWhichToResolveReferences(ICollection modulesToParse) { - foreach (var component in toClear) + var toResolveReferences = modulesToParse.ToHashSet(); + foreach (var qmn in modulesToParse) + { + toResolveReferences.UnionWith(State.ModulesReferencing(qmn)); + } + return toResolveReferences; + } + + private void PerformPreParseCleanup(ICollection modulesToParse, ICollection toResolveReferences, CancellationToken token) + { + ClearModuleToModuleReferences(modulesToParse); + RemoveAllReferencesBy(toResolveReferences, modulesToParse, State.DeclarationFinder, token); //All declarations on the modulesToParse get destroyed anyway. + _projectDeclarations.Clear(); + } + + private void ClearModuleToModuleReferences(ICollection toClear) + { + foreach (var qmn in toClear) { - State.ClearModuleToModuleReferencesFromModule(new QualifiedModuleName(component)); + State.ClearModuleToModuleReferencesFromModule(qmn); + } + } + + //This doesn not live on the RubberduckParserState to keep concurrency haanlding out of it. + public void RemoveAllReferencesBy(ICollection referencesFromToRemove, ICollection modulesNotNeedingReferenceRemoval, DeclarationFinder finder, CancellationToken token) + { + var referencedModulesNeedingReferenceRemoval = State.ModulesReferencedBy(referencesFromToRemove).Where(qmn => !modulesNotNeedingReferenceRemoval.Contains(qmn)); + + var options = new ParallelOptions(); + options.CancellationToken = token; + options.MaxDegreeOfParallelism = _maxDegreeOfReferenceRemovalParallelism; + + Parallel.ForEach(referencedModulesNeedingReferenceRemoval, options, qmn => RemoveReferences(finder.Members(qmn), referencesFromToRemove)); + } + + private void RemoveReferences(IEnumerable declarations, ICollection referencesFromToRemove) + { + foreach (var declaration in declarations) + { + declaration.RemoveReferencesFrom(referencesFromToRemove); } } @@ -469,11 +506,11 @@ private Declaration CreateProjectDeclaration(QualifiedModuleName projectQualifie } - private void ResolveAllReferences(CancellationToken token) + private void ResolveAllReferences(ICollection toResolve, CancellationToken token) { token.ThrowIfCancellationRequested(); - var components = State.ParseTrees.Select(kvp => kvp.Key.Component).ToList(); + var components = toResolve.Select(qmn => qmn.Component).ToList(); token.ThrowIfCancellationRequested(); @@ -485,13 +522,15 @@ private void ResolveAllReferences(CancellationToken token) token.ThrowIfCancellationRequested(); + var parseTreesToResolve = State.ParseTrees.Where(kvp => toResolve.Contains(kvp.Key)).ToList(); + var options = new ParallelOptions(); options.CancellationToken = token; options.MaxDegreeOfParallelism = _maxDegreeOfReferenceResolverParallelism; try { - Parallel.For(0, State.ParseTrees.Count, options, - (index) => ResolveReferences(State.DeclarationFinder, State.ParseTrees[index].Key, State.ParseTrees[index].Value, token) + Parallel.For(0, parseTreesToResolve.Count, options, + (index) => ResolveReferences(State.DeclarationFinder, parseTreesToResolve[index].Key, parseTreesToResolve[index].Value, token) ); } catch (AggregateException exception) @@ -510,10 +549,11 @@ private void ResolveAllReferences(CancellationToken token) token.ThrowIfCancellationRequested(); - AddUndeclaredVariablesToDeclarations(); + AddNewUndeclaredVariablesToDeclarations(); + AddNewUnresolvedMemberDeclarations(); //This is here and not in the calling method because it has to happen before the ready state is reached. - //RefreshDeclarationFinder(); //Commented out because it breaks the unresolved and undeclared collections. + RefreshDeclarationFinder(); token.ThrowIfCancellationRequested(); @@ -604,15 +644,24 @@ private void AddModuleToModuleReferences(DeclarationFinder finder, QualifiedModu } } - private void AddUndeclaredVariablesToDeclarations() + private void AddNewUndeclaredVariablesToDeclarations() { - var undeclared = State.DeclarationFinder.Undeclared.ToList(); + var undeclared = State.DeclarationFinder.FreshUndeclared.ToList(); foreach (var declaration in undeclared) { State.AddDeclaration(declaration); } } + private void AddNewUnresolvedMemberDeclarations() + { + var unresolved = State.DeclarationFinder.FreshUnresolvedMemberDeclarations().ToList(); + foreach (var declaration in unresolved) + { + State.AddUnresolvedMemberDeclaration(declaration); + } + } + /// /// Starts parsing all components of all unprotected VBProjects associated with the VBE-Instance passed to the constructor of this parser instance. @@ -661,7 +710,7 @@ private void ParseAllInternal(object requestor, CancellationToken token) token.ThrowIfCancellationRequested(); - var componentsRemoved = CleanUpRemovedComponents(components); + var componentsRemoved = CleanUpRemovedComponents(components, token); token.ThrowIfCancellationRequested(); @@ -694,19 +743,24 @@ private void ParseAllInternal(object requestor, CancellationToken token) /// Clears state cache of removed components. /// Returns whether components have been removed. /// - private bool CleanUpRemovedComponents(List components) + private bool CleanUpRemovedComponents(ICollection components, CancellationToken token) { var removedModuledecalrations = RemovedModuleDeclarations(components); var componentRemoved = removedModuledecalrations.Any(); - foreach (var declaration in removedModuledecalrations) + var removedModules = removedModuledecalrations.Select(declaration => declaration.QualifiedName.QualifiedModuleName).ToHashSet(); + if (removedModules.Any()) { - State.ClearModuleToModuleReferencesFromModule(declaration.QualifiedName.QualifiedModuleName); - State.ClearStateCache(declaration.QualifiedName.QualifiedModuleName); + RemoveAllReferencesBy(removedModules, removedModules, State.DeclarationFinder, token); + foreach (var qmn in removedModules) + { + State.ClearModuleToModuleReferencesFromModule(qmn); + State.ClearStateCache(qmn); + } } return componentRemoved; } - private IEnumerable RemovedModuleDeclarations(List components) + private IEnumerable RemovedModuleDeclarations(ICollection components) { var moduleDeclarations = State.AllUserDeclarations.Where(declaration => declaration.DeclarationType.HasFlag(DeclarationType.Module)); var componentKeys = components.Select(component => new { name = component.Name, projectId = component.Collection.Parent.HelpFile }).ToHashSet(); diff --git a/Rubberduck.Parsing/VBA/RubberduckParserState.cs b/Rubberduck.Parsing/VBA/RubberduckParserState.cs index d5501d5ba4..092f197b40 100644 --- a/Rubberduck.Parsing/VBA/RubberduckParserState.cs +++ b/Rubberduck.Parsing/VBA/RubberduckParserState.cs @@ -69,7 +69,7 @@ public sealed class RubberduckParserState : IDisposable internal void RefreshFinder(IHostApplication host) { - DeclarationFinder = new DeclarationFinder(AllDeclarations, AllAnnotations, host); + DeclarationFinder = new DeclarationFinder(AllDeclarations, AllAnnotations, AllUnresolvedMemberDeclarations, host); } private readonly IVBE _vbe; @@ -629,6 +629,28 @@ public IReadOnlyList AllDeclarations } } + /// + /// Gets a copy of the unresolved member declarations. + /// + public IReadOnlyList AllUnresolvedMemberDeclarations + { + get + { + var declarations = new List(); + foreach (var state in _moduleStates.Values) + { + if (state.UnresolvedMemberDeclarations == null) + { + continue; + } + + declarations.AddRange(state.UnresolvedMemberDeclarations.Keys); + } + + return declarations; + } + } + private readonly ConcurrentBag _builtInDeclarationTrees = new ConcurrentBag(); public IProducerConsumerCollection BuiltInDeclarationTrees { get { return _builtInDeclarationTrees; } } @@ -694,6 +716,28 @@ public void AddDeclaration(Declaration declaration) } } + /// + /// Adds the specified to the collection (replaces existing). + /// + public void AddUnresolvedMemberDeclaration(UnboundMemberDeclaration declaration) + { + var key = declaration.QualifiedName.QualifiedModuleName; + var declarations = _moduleStates.GetOrAdd(key, new ModuleState(new ConcurrentDictionary())).UnresolvedMemberDeclarations; + + if (declarations.ContainsKey(declaration)) + { + byte _; + while (!declarations.TryRemove(declaration, out _)) + { + 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); + } + } + private void ClearStateCache(string projectId, bool notifyStateChanged = false) { try @@ -1137,6 +1181,16 @@ public HashSet ModulesReferencedBy(QualifiedModuleName refe return new HashSet(referencingModuleState.HasReferenceToModule.Keys); } + public HashSet ModulesReferencedBy(IEnumerable referencingModules) + { + var referencedModules = new HashSet(); + foreach (var referencingModule in referencedModules) + { + referencedModules.UnionWith(ModulesReferencedBy(referencingModule)); + } + return referencedModules; + } + public HashSet ModulesReferencing(QualifiedModuleName referencedModule) { ModuleState referencedModuleState; diff --git a/Rubberduck.VBEEditor/WindowsApi/SubclassingWindow.cs b/Rubberduck.VBEEditor/WindowsApi/SubclassingWindow.cs index 6cec5a9b79..340e7d253b 100644 --- a/Rubberduck.VBEEditor/WindowsApi/SubclassingWindow.cs +++ b/Rubberduck.VBEEditor/WindowsApi/SubclassingWindow.cs @@ -43,7 +43,7 @@ protected SubclassingWindow(IntPtr subclassId, IntPtr hWnd) public void Dispose() { ReleaseHandle(); - //GC.SuppressFinalize(this); + _thisHandle.Free(); } private void AssignHandle() @@ -86,14 +86,13 @@ public virtual int SubClassProc(IntPtr hWnd, IntPtr msg, IntPtr wParam, IntPtr l { if (!_listening) { - throw new Exception("State corrupted. Received window message while not listening."); + Debug.WriteLine("State corrupted. Received window message while not listening."); + return DefSubclassProc(hWnd, msg, wParam, lParam); } - Debug.Assert(IsWindow(_hwnd)); if ((uint)msg == (uint)WM.RUBBERDUCK_SINKING || (uint)msg == (uint)WM.DESTROY) { - ReleaseHandle(); - _thisHandle.Free(); + ReleaseHandle(); } return DefSubclassProc(hWnd, msg, wParam, lParam); }