Skip to content

Commit f2837c9

Browse files
authored
Merge pull request #4413 from MDoerner/RefreshReferencesOnProjectPriorityChange
Refresh references on project priority change
2 parents 80896f2 + 70e796b commit f2837c9

File tree

7 files changed

+181
-90
lines changed

7 files changed

+181
-90
lines changed

Rubberduck.Parsing/Rubberduck.Parsing.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@
162162
<Compile Include="Symbols\IInterfaceExposable.cs" />
163163
<Compile Include="Symbols\ModuleBodyElementDeclaration.cs" />
164164
<Compile Include="Symbols\ModuleDeclaration.cs" />
165+
<Compile Include="VBA\Extensions\DictionaryExtensions.cs" />
165166
<Compile Include="VBA\Parsing\ParsingExceptions\ExceptionErrorListenerFactory.cs" />
166167
<Compile Include="VBA\Parsing\ParsingExceptions\PreprocessingParseErrorListenerFactory.cs" />
167168
<Compile Include="VBA\Parsing\ParsingExceptions\IRubberduckParserErrorListenerFactory.cs" />

Rubberduck.Parsing/VBA/ComReferenceLoading/COMReferenceSynchronizerBase.cs

Lines changed: 135 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using NLog;
99
using Rubberduck.Parsing.Common;
1010
using Rubberduck.Parsing.ComReflection;
11+
using Rubberduck.Parsing.Symbols;
1112
using Rubberduck.Parsing.VBA.Extensions;
1213
using Rubberduck.VBEditor;
1314
using Rubberduck.VBEditor.ComManagement;
@@ -23,7 +24,8 @@ public abstract class COMReferenceSynchronizerBase : ICOMReferenceSynchronizer,
2324
private readonly IProjectsProvider _projectsProvider;
2425
private readonly IReferencedDeclarationsCollector _referencedDeclarationsCollector;
2526

26-
private readonly List<QualifiedModuleName> _unloadedCOMReferences;
27+
private readonly List<string> _unloadedCOMReferences = new List<string>();
28+
private readonly List<(string projectId, string referencedProjectId)> _referencesAffectedByPriorityChanges = new List<(string projectId, string referencedProjectId)>();
2729

2830
private readonly Dictionary<(string identifierName, string fullPath), string> _projectIdsByFilePathAndProjectName = new Dictionary<(string identifierName, string fullPath), string>();
2931

@@ -53,15 +55,15 @@ protected COMReferenceSynchronizerBase(RubberduckParserState state, IParserState
5355
_parserStateManager = parserStateManager;
5456
_projectsProvider = projectsProvider;
5557
_referencedDeclarationsCollector = referencedDeclarationsCollector;
56-
_unloadedCOMReferences = new List<QualifiedModuleName>();
5758
}
5859

5960

6061
public bool LastSyncOfCOMReferencesLoadedReferences { get; private set; }
61-
public IEnumerable<QualifiedModuleName> COMReferencesUnloadedUnloadedInLastSync => _unloadedCOMReferences;
62+
public IEnumerable<string> COMReferencesUnloadedInLastSync => _unloadedCOMReferences;
63+
public IEnumerable<(string projectId, string referencedProjectId)> COMReferencesAffectedByPriorityChangesInLastSync => _referencesAffectedByPriorityChanges;
6264

63-
private readonly HashSet<ReferencePriorityMap> _projectReferences = new HashSet<ReferencePriorityMap>();
64-
public IReadOnlyCollection<ReferencePriorityMap> ProjectReferences => _projectReferences.AsReadOnly();
65+
private readonly IDictionary<string, ReferencePriorityMap> _projectReferences = new Dictionary<string, ReferencePriorityMap>();
66+
public IReadOnlyCollection<ReferencePriorityMap> ProjectReferences => _projectReferences.Values.AsReadOnly();
6567

6668

6769
protected abstract void LoadReferences(IEnumerable<ReferenceInfo> referencesToLoad, ConcurrentBag<ReferenceInfo> unmapped, CancellationToken token);
@@ -71,35 +73,133 @@ public void SyncComReferences(CancellationToken token)
7173
{
7274
var parsingStageTimer = ParsingStageTimer.StartNew();
7375

76+
var oldProjectReferences = _projectReferences.ToDictionary();
77+
_projectReferences.Clear();
78+
7479
LastSyncOfCOMReferencesLoadedReferences = false;
7580
_unloadedCOMReferences.Clear();
81+
_referencesAffectedByPriorityChanges.Clear();
7682
RefreshReferencedByProjectId();
7783

7884
var unmapped = new ConcurrentBag<ReferenceInfo>();
7985

8086
var referencesByProjectId = ReferencedByProjectId();
81-
var referencesToLoad = GetReferencesToLoadAndSaveReferencePriority(referencesByProjectId);
87+
var referencesToLoad = GetReferencesToLoadAndSaveReferencePriority(referencesByProjectId, oldProjectReferences);
8288

8389
if (referencesToLoad.Any())
8490
{
8591
LastSyncOfCOMReferencesLoadedReferences = true;
8692
LoadReferences(referencesToLoad, unmapped, token);
8793
}
8894

89-
var allReferences = referencesByProjectId.Values.SelectMany(references => references).ToHashSet();
90-
var notMappedReferences = NonMappedReferences(allReferences);
91-
foreach (var item in notMappedReferences)
95+
DetermineReferencesAffectedByPriorityChanges(_projectReferences, oldProjectReferences);
96+
UnloadNoLongerExistingReferences(_projectReferences, oldProjectReferences);
97+
98+
parsingStageTimer.Stop();
99+
parsingStageTimer.Log("Loaded and unloaded referenced libraries in {0}ms.");
100+
}
101+
102+
private void UnloadNoLongerExistingReferences(IDictionary<string, ReferencePriorityMap> newProjectReferences, IDictionary<string, ReferencePriorityMap> oldProjectReferences)
103+
{
104+
var noLongerReferencedProjectIds = oldProjectReferences.Keys
105+
.Where(projectId => !newProjectReferences.ContainsKey(projectId))
106+
.ToList();
107+
108+
foreach (var referencedProjectId in noLongerReferencedProjectIds)
92109
{
93-
unmapped.Add(item);
110+
UnloadComReference(referencedProjectId);
94111
}
112+
}
95113

96-
foreach (var reference in unmapped)
114+
private void DetermineReferencesAffectedByPriorityChanges(IDictionary<string, ReferencePriorityMap> newProjectReferences, IDictionary<string, ReferencePriorityMap> oldProjectReferences)
115+
{
116+
var referencePriorityChanges = ReferencePriorityChanges(newProjectReferences, oldProjectReferences);
117+
118+
foreach (var oldMap in oldProjectReferences.Values)
97119
{
98-
UnloadComReference(reference);
120+
foreach (var projectId in referencePriorityChanges.Keys)
121+
{
122+
if (oldMap.TryGetValue(projectId, out var priority)
123+
&& referencePriorityChanges[projectId]
124+
.Any(tpl => (tpl.oldPriority <= priority && tpl.newPriority >= priority)
125+
|| (tpl.oldPriority >= priority && tpl.newPriority <= priority)))
126+
{
127+
_referencesAffectedByPriorityChanges.Add((projectId, oldMap.ReferencedProjectId));
128+
}
129+
}
99130
}
131+
}
100132

101-
parsingStageTimer.Stop();
102-
parsingStageTimer.Log("Loaded and unloaded referenced libraries in {0}ms.");
133+
private static IDictionary<string, List<(int newPriority, int oldPriority)>> ReferencePriorityChanges(IDictionary<string, ReferencePriorityMap> newProjectReferences, IDictionary<string, ReferencePriorityMap> oldProjectReferences)
134+
{
135+
var referencePriorityChanges = new Dictionary<string, List<(int newPriority, int oldPriority)>>();
136+
foreach (var referencedProjectId in oldProjectReferences.Keys)
137+
{
138+
var oldMap = oldProjectReferences[referencedProjectId];
139+
if (!newProjectReferences.TryGetValue(referencedProjectId, out var newMap))
140+
{
141+
foreach (var projectId in oldMap.Keys)
142+
{
143+
AddPriorityChangeToDictionary(projectId, int.MaxValue, oldMap[projectId], referencePriorityChanges);
144+
}
145+
}
146+
else
147+
{
148+
foreach (var projectId in oldMap.Keys)
149+
{
150+
var oldPriority = oldMap[projectId];
151+
if (!newMap.TryGetValue(referencedProjectId, out var newPriority))
152+
{
153+
newPriority = int.MaxValue;
154+
}
155+
156+
if (newPriority != oldPriority)
157+
{
158+
AddPriorityChangeToDictionary(projectId, newPriority, oldMap[projectId], referencePriorityChanges);
159+
}
160+
}
161+
162+
foreach (var projectId in newMap.Keys)
163+
{
164+
if (!oldMap.ContainsKey(projectId))
165+
{
166+
AddPriorityChangeToDictionary(projectId, newMap[projectId], int.MaxValue, referencePriorityChanges);
167+
}
168+
}
169+
}
170+
}
171+
172+
foreach (var referencedProjectId in newProjectReferences.Keys)
173+
{
174+
if (oldProjectReferences.ContainsKey(referencedProjectId))
175+
{
176+
continue;
177+
}
178+
179+
var newMap = newProjectReferences[referencedProjectId];
180+
foreach (var projectId in newMap.Keys)
181+
{
182+
AddPriorityChangeToDictionary(projectId, newMap[projectId], int.MaxValue, referencePriorityChanges);
183+
}
184+
}
185+
186+
return referencePriorityChanges;
187+
}
188+
189+
private static void AddPriorityChangeToDictionary(
190+
string projectId,
191+
int newPriority,
192+
int oldPriority,
193+
IDictionary<string, List<(int newPriority, int oldPriority)>> dict)
194+
{
195+
if (dict.TryGetValue(projectId, out var changeList))
196+
{
197+
changeList.Add((newPriority, oldPriority));
198+
}
199+
else
200+
{
201+
dict.Add(projectId, new List<(int newPriority, int oldPriority)> { (newPriority, oldPriority) });
202+
}
103203
}
104204

105205
private Dictionary<string, List<ReferenceInfo>> ReferencedByProjectId()
@@ -173,7 +273,7 @@ private static bool TryGetFullPath(IVBProject project, out string fullPath)
173273
return true;
174274
}
175275

176-
private ICollection<ReferenceInfo> GetReferencesToLoadAndSaveReferencePriority(Dictionary<string, List<ReferenceInfo>> referencedByProjectId)
276+
private ICollection<ReferenceInfo> GetReferencesToLoadAndSaveReferencePriority(Dictionary<string, List<ReferenceInfo>> referencedByProjectId, IDictionary<string, ReferencePriorityMap> oldProjectReferences)
177277
{
178278
var referencesToLoad = new List<ReferenceInfo>();
179279

@@ -188,13 +288,15 @@ private ICollection<ReferenceInfo> GetReferencesToLoadAndSaveReferencePriority(D
188288
// todo: figure out why Rubberduck.tlb *sometimes* throws
189289

190290
var referencedProjectId = GetReferenceProjectId(reference);
191-
var map = _projectReferences.FirstOrDefault(item =>
192-
item.ReferencedProjectId == referencedProjectId);
193-
194-
if (map == null)
291+
if (!_projectReferences.TryGetValue(referencedProjectId, out var map))
195292
{
196293
map = new ReferencePriorityMap(referencedProjectId) { { projectId, priority } };
197-
_projectReferences.Add(map);
294+
_projectReferences.Add(referencedProjectId, map);
295+
296+
if (oldProjectReferences.TryGetValue(referencedProjectId, out var oldMap))
297+
{
298+
map.IsLoaded = oldMap.IsLoaded;
299+
}
198300
}
199301
else
200302
{
@@ -252,43 +354,18 @@ protected void LoadReference(ReferenceInfo reference, ConcurrentBag<ReferenceInf
252354

253355
private IEnumerable<ReferenceInfo> NonMappedReferences(ICollection<ReferenceInfo> references)
254356
{
255-
var mappedIds = _projectReferences.Select(item => item.ReferencedProjectId).ToHashSet();
256-
return references.Where(item => !mappedIds.Contains(GetReferenceProjectId(item))).ToList();
357+
return references.Where(item => !_projectReferences.ContainsKey(GetReferenceProjectId(item))).ToList();
257358
}
258359

259-
private void UnloadComReference(ReferenceInfo reference)
360+
private void UnloadComReference(string referencedProjectId)
260361
{
261-
var referencedProjectId = GetReferenceProjectId(reference);
262-
263-
ReferencePriorityMap map = null;
264-
try
265-
{
266-
map = _projectReferences.SingleOrDefault(item => item.ReferencedProjectId == referencedProjectId);
267-
}
268-
catch (InvalidOperationException exception)
362+
//There is nothing to unload for a user project.
363+
if (!IsUserProjectProjectId(referencedProjectId))
269364
{
270-
//There are multiple maps with the same referencedProjectId. That should not happen. (ghost?).
271-
Logger.Error(exception, "Failed To unload com reference with referencedProjectID {0} because RD stores multiple instances of it.", referencedProjectId);
272-
return;
273-
}
365+
_unloadedCOMReferences.Add(referencedProjectId);
274366

275-
if (map == null || !map.IsLoaded)
276-
{
277-
Logger.Warn("Tried to unload untracked project reference."); //This shouldn't happen.
278-
return;
279-
}
280-
281-
map.Remove(referencedProjectId);
282-
if (map.Count == 0)
283-
{
284-
_projectReferences.Remove(map);
285-
286-
//There is nothing to unload for a user project.
287-
if (!IsUserProjectProjectId(referencedProjectId))
288-
{
289-
AddUnloadedReferenceToUnloadedReferences(reference);
290-
_state.RemoveBuiltInDeclarations(reference);
291-
}
367+
var projectQMN = ProjectQMNFromBuildInProjectId(referencedProjectId);
368+
_state.RemoveBuiltInDeclarations(projectQMN);
292369
}
293370
}
294371

@@ -297,10 +374,13 @@ private bool IsUserProjectProjectId(string projectId)
297374
return _projectIdsByFilePathAndProjectName.Values.Contains(projectId);
298375
}
299376

300-
private void AddUnloadedReferenceToUnloadedReferences(ReferenceInfo reference)
377+
private QualifiedModuleName ProjectQMNFromBuildInProjectId(string projectId)
301378
{
302-
var projectQMN = new QualifiedModuleName(reference);
303-
_unloadedCOMReferences.Add(projectQMN);
379+
return _state.DeclarationFinder
380+
.BuiltInDeclarations(DeclarationType.Project)
381+
.First(declaration => declaration.ProjectId == projectId)
382+
.QualifiedModuleName;
383+
304384
}
305385
}
306386
}

Rubberduck.Parsing/VBA/ComReferenceLoading/ICOMReferenceSynchronizer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
using System.Collections.Generic;
22
using System.Threading;
3-
using Rubberduck.VBEditor;
43

54
namespace Rubberduck.Parsing.VBA.ComReferenceLoading
65
{
76
public interface ICOMReferenceSynchronizer
87
{
98
bool LastSyncOfCOMReferencesLoadedReferences { get; }
10-
IEnumerable<QualifiedModuleName> COMReferencesUnloadedUnloadedInLastSync { get; }
9+
IEnumerable<string> COMReferencesUnloadedInLastSync { get; }
10+
IEnumerable<(string projectId, string referencedProjectId)> COMReferencesAffectedByPriorityChangesInLastSync { get; }
1111

1212
void SyncComReferences(CancellationToken token);
1313
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System.Collections.Generic;
2+
3+
namespace Rubberduck.Parsing.VBA.Extensions
4+
{
5+
public static class DictionaryExtensions
6+
{
7+
public static IDictionary<TKey, TValue>ToDictionary<TKey, TValue>(this IDictionary<TKey, TValue> source)
8+
{
9+
return new Dictionary<TKey, TValue>(source);
10+
}
11+
}
12+
}

Rubberduck.Parsing/VBA/ParseCoordinator.cs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -224,15 +224,34 @@ private void ExecuteCommonParseActivities(IReadOnlyCollection<QualifiedModuleNam
224224
token.ThrowIfCancellationRequested();
225225

226226
_parsingStageService.SyncComReferences(token);
227-
if (_parsingStageService.LastSyncOfCOMReferencesLoadedReferences || _parsingStageService.COMReferencesUnloadedUnloadedInLastSync.Any())
227+
if (_parsingStageService.LastSyncOfCOMReferencesLoadedReferences || _parsingStageService.COMReferencesUnloadedInLastSync.Any())
228228
{
229-
var unloadedReferences = _parsingStageService.COMReferencesUnloadedUnloadedInLastSync;
230-
var additionalModulesToBeReresolved = OtherModulesReferencingAnyNotToBeParsed(unloadedReferences.ToHashSet().AsReadOnly(), toParse);
229+
var unloadedReferences = _parsingStageService.COMReferencesUnloadedInLastSync.ToHashSet();
230+
var unloadedModules =
231+
_parsingCacheService.DeclarationFinder.AllModules
232+
.Where(qmn => unloadedReferences.Contains(qmn.ProjectId))
233+
.ToHashSet();
234+
var additionalModulesToBeReresolved = OtherModulesReferencingAnyNotToBeParsed(unloadedModules.AsReadOnly(), toParse);
231235
toReresolveReferences.UnionWith(additionalModulesToBeReresolved);
232236
_parserStateManager.SetModuleStates(additionalModulesToBeReresolved, ParserState.ResolvingReferences, token);
233-
ClearModuleToModuleReferences(unloadedReferences);
237+
ClearModuleToModuleReferences(unloadedModules);
234238
RefreshDeclarationFinder();
235239
}
240+
241+
if (_parsingStageService.COMReferencesAffectedByPriorityChangesInLastSync.Any())
242+
{
243+
//We only use the referencedProjectId because that simplifies the reference management immensely.
244+
var affectedReferences = _parsingStageService.COMReferencesAffectedByPriorityChangesInLastSync
245+
.Select(tpl => tpl.referencedProjectId)
246+
.ToHashSet();
247+
var referenceModules =
248+
_parsingCacheService.DeclarationFinder.AllModules
249+
.Where(qmn => affectedReferences.Contains(qmn.ProjectId))
250+
.ToHashSet();
251+
var additionalModulesToBeReresolved = OtherModulesReferencingAnyNotToBeParsed(referenceModules.AsReadOnly(), toParse);
252+
toReresolveReferences.UnionWith(additionalModulesToBeReresolved);
253+
_parserStateManager.SetModuleStates(additionalModulesToBeReresolved, ParserState.ResolvingReferences, token);
254+
}
236255
token.ThrowIfCancellationRequested();
237256

238257
_parsingStageService.LoadBuitInDeclarations();

Rubberduck.Parsing/VBA/ParsingStageService.cs

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -52,30 +52,10 @@ public ParsingStageService(
5252
_referenceResolver = referenceResolver;
5353
}
5454

55-
56-
public bool LastLoadOfBuiltInDeclarationsLoadedDeclarations
57-
{
58-
get
59-
{
60-
return _builtInDeclarationLoader.LastLoadOfBuiltInDeclarationsLoadedDeclarations;
61-
}
62-
}
63-
64-
public bool LastSyncOfCOMReferencesLoadedReferences
65-
{
66-
get
67-
{
68-
return _comSynchronizer.LastSyncOfCOMReferencesLoadedReferences;
69-
}
70-
}
71-
72-
public IEnumerable<QualifiedModuleName> COMReferencesUnloadedUnloadedInLastSync
73-
{
74-
get
75-
{
76-
return _comSynchronizer.COMReferencesUnloadedUnloadedInLastSync;
77-
}
78-
}
55+
public bool LastLoadOfBuiltInDeclarationsLoadedDeclarations => _builtInDeclarationLoader.LastLoadOfBuiltInDeclarationsLoadedDeclarations;
56+
public bool LastSyncOfCOMReferencesLoadedReferences => _comSynchronizer.LastSyncOfCOMReferencesLoadedReferences;
57+
public IEnumerable<string> COMReferencesUnloadedInLastSync => _comSynchronizer.COMReferencesUnloadedInLastSync;
58+
public IEnumerable<(string projectId, string referencedProjectId)>COMReferencesAffectedByPriorityChangesInLastSync =>_comSynchronizer.COMReferencesAffectedByPriorityChangesInLastSync;
7959

8060
public void LoadBuitInDeclarations()
8161
{

0 commit comments

Comments
 (0)