Skip to content

Commit 1910d3c

Browse files
committed
Module reader: invalidate based on used type changes
1 parent c224232 commit 1910d3c

File tree

2 files changed

+188
-85
lines changed

2 files changed

+188
-85
lines changed

ReSharper.FSharp/src/FSharp.Common/src/Shim/AssemblyReader/AssemblyReaderShim.fs

Lines changed: 132 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ module AssemblyReaderShim =
5252
isSupportedProjectLanguage projectProperties.DefaultLanguage &&
5353
isSupportedProjectKind projectProperties.ProjectKind
5454

55+
let isSupportedModule (psiModule: IPsiModule) =
56+
let projectModule = psiModule.As<IProjectPsiModule>()
57+
isNotNull projectModule && isSupportedProject projectModule.Project
58+
5559
let getProjectPsiModuleByOutputAssembly (psiModules: IPsiModules) path =
5660
let projectAndTargetFrameworkId = psiModules.TryGetProjectAndTargetFrameworkIdByOutputAssembly(path)
5761
if isNull projectAndTargetFrameworkId then null else
@@ -93,69 +97,151 @@ type AssemblyReaderShim(lifetime: Lifetime, changeManager: ChangeManager, psiMod
9397
let assemblyReadersByPath = ConcurrentDictionary<FileSystemPath, ReferencedAssembly>()
9498
let assemblyReadersByModule = ConcurrentDictionary<IPsiModule, ReferencedAssembly>()
9599

96-
/// Dependencies that require non-lazy module readers.
97-
let moduleDependencies = OneToSetMap<IPsiModule, IPsiModule>()
100+
// todo: record empty set in nonLazyModuleDependencies somehow, remove this set
101+
let moduleDependenciesRecorded = HashSet<IPsiModule>()
98102

99-
let dependenciesToModules = OneToSetMap<IPsiModule, IPsiModule>()
103+
/// F# project module dependencies requiring non-lazy module readers.
104+
let nonLazyDependenciesForModule = OneToSetMap<IPsiModule, IPsiModule>()
100105

101-
let dirtyModules = OneToSetMap<IPsiModule, IClrTypeName>()
106+
let dependenciesToReferencingModules = OneToSetMap<IPsiModule, IPsiModule>()
102107

103-
let createReader (path: FileSystemPath) =
104-
use readLockCookie = ReadLockCookie.Create()
105-
match AssemblyReaderShim.getProjectPsiModuleByOutputAssembly psiModules path with
106-
| null -> ReferencedAssembly.Ignored
107-
| psiModule -> ReferencedAssembly.ProjectOutput(new ProjectFcsModuleReader(psiModule, cache))
108+
// todo: F#->F#->C# references
109+
// change in F#->F# is not seen by C# now
108110

109-
let rec hasTransitiveReferencesToFSharpProjects (psiModule: IPsiModule) =
111+
// todo: use short names?
112+
let dirtyTypesInModules = OneToSetMap<IPsiModule, IClrTypeName>()
113+
let allTypesCreated = HashSet<IPsiModule>()
114+
115+
let transitiveReferencedProjectModules (psiModule: IPsiModule) =
110116
let visited = HashSet()
117+
let projectModules = HashSet()
118+
let mutable hasFSharpReferences = false
119+
120+
let rec loop (psiModule: IPsiModule) =
121+
getReferencedModules psiModule
122+
|> Seq.iter (fun referencedModule ->
123+
match referencedModule with
124+
| :? IProjectPsiModule as referencedModule ->
125+
if visited.Contains(referencedModule) then () else
111126

112-
getReferencedModules psiModule
113-
|> Seq.filter (fun referencedModule ->
114-
match referencedModule with
115-
| :? IProjectPsiModule as referencedModule ->
116-
if visited.Contains(referencedModule) then false else
127+
projectModules.Add(referencedModule) |> ignore
128+
if referencedModule.Project.IsFSharp then
129+
hasFSharpReferences <- true
117130

118-
let hasReference =
119-
referencedModule.Project.IsFSharp ||
120-
hasTransitiveReferencesToFSharpProjects referencedModule
131+
visited.Add(referencedModule) |> ignore
132+
loop referencedModule
133+
| _ -> ())
121134

122-
if hasReference then true else
135+
loop psiModule
136+
projectModules, hasFSharpReferences
123137

124-
visited.Add(referencedModule) |> ignore
125-
false
138+
let rec recordDependencies (psiModule: IPsiModule): unit =
139+
if moduleDependenciesRecorded.Contains(psiModule) then () else
126140

127-
| _ -> false)
128-
|> Seq.isEmpty
129-
|> not
141+
if not (psiModule :? IProjectPsiModule) then () else
130142

131-
let recordDependencies (psiModule: IPsiModule) =
143+
// todo: filter by primary module? test on web projects containing multiple modules
132144
for referencedModule in getReferencedModules psiModule do
133-
let referencedModule = referencedModule.As<IProjectPsiModule>()
134-
if isNull referencedModule then () else
145+
if not (referencedModule :? IProjectPsiModule) then () else
135146

136-
let projectLanguage = referencedModule.Project.ProjectProperties.DefaultLanguage
137-
if not (AssemblyReaderShim.isSupportedProjectLanguage projectLanguage) then () else
147+
let referencedProjectModules, hasFSharpReferences = transitiveReferencedProjectModules referencedModule
138148

139-
if not (hasTransitiveReferencesToFSharpProjects referencedModule) then () else
149+
if hasFSharpReferences then
150+
nonLazyDependenciesForModule.Add(psiModule, referencedModule) |> ignore
140151

141-
// todo: add transitive dependencies
142-
moduleDependencies.Add(psiModule, referencedModule) |> ignore
143-
dependenciesToModules.Add(referencedModule, psiModule) |> ignore
152+
dependenciesToReferencingModules.Add(referencedModule, psiModule) |> ignore
144153

145-
let getOrCreateReader path =
146-
let mutable reader = Unchecked.defaultof<_>
147-
if assemblyReadersByPath.TryGetValue(path, &reader) then reader else
154+
for referencedProjectModule in referencedProjectModules do
155+
recordDependencies referencedProjectModule
156+
157+
moduleDependenciesRecorded.Add(psiModule) |> ignore
148158

149-
let reader = createReader path
159+
let recordReader path reader =
150160
assemblyReadersByPath.[path] <- reader
151161

152162
match reader with
153163
| ReferencedAssembly.ProjectOutput moduleReader ->
154164
assemblyReadersByModule.[moduleReader.PsiModule] <- reader
155165
| _ -> ()
156166

167+
let getOrCreateReaderFromModule (psiModule: IPsiModule) =
168+
let psiModule = psiModule.As<IProjectPsiModule>()
169+
if isNull psiModule then ReferencedAssembly.Ignored else
170+
171+
let mutable reader = Unchecked.defaultof<_>
172+
if assemblyReadersByModule.TryGetValue(psiModule, &reader) then reader else
173+
174+
use readLockCookie = ReadLockCookie.Create()
175+
176+
// todo: is getting primary module needed? should we also replace module->primaryModule everywhere else?
177+
// todo: test web project with multiple modules
178+
let path = psiModule.Project.GetOutputFilePath(psiModule.TargetFrameworkId)
179+
let psiModule = psiModules.GetPrimaryPsiModule(psiModule.Project, psiModule.TargetFrameworkId)
180+
let reader = ReferencedAssembly.ProjectOutput(new ProjectFcsModuleReader(psiModule, cache))
181+
182+
recordReader path reader
157183
reader
158184

185+
let getOrCreateReaderFromPath path =
186+
let mutable reader = Unchecked.defaultof<_>
187+
if assemblyReadersByPath.TryGetValue(path, &reader) then reader else
188+
189+
use readLockCookie = ReadLockCookie.Create()
190+
191+
let reader =
192+
match AssemblyReaderShim.getProjectPsiModuleByOutputAssembly psiModules path with
193+
| null -> ReferencedAssembly.Ignored
194+
| psiModule -> ReferencedAssembly.ProjectOutput(new ProjectFcsModuleReader(psiModule, cache))
195+
196+
recordReader path reader
197+
reader
198+
199+
let tryGetReaderFromModule (psiModule: IPsiModule) (result: outref<_>) =
200+
let mutable referencedAssembly = Unchecked.defaultof<_>
201+
if not (assemblyReadersByModule.TryGetValue(psiModule, &referencedAssembly)) then false else
202+
203+
match referencedAssembly with
204+
| ReferencedAssembly.Ignored -> false
205+
| ReferencedAssembly.ProjectOutput(reader) ->
206+
207+
result <- reader
208+
true
209+
210+
// todo: invalidate for particular referencing module only?
211+
let invalidateDirtyDependencies () =
212+
Assertion.Assert(locker.IsWriteLockHeld, "locker.IsWriteLockHeld")
213+
214+
for dirtyModule in dirtyTypesInModules.Keys do
215+
let mutable dirtyModuleReader = Unchecked.defaultof<_>
216+
if not (tryGetReaderFromModule dirtyModule &dirtyModuleReader) then () else
217+
218+
for typeName in dirtyTypesInModules.GetValuesSafe(dirtyModule) do
219+
dirtyModuleReader.InvalidateTypeDef(typeName)
220+
221+
for referencingModule in dependenciesToReferencingModules.GetValuesSafe(dirtyModule) do
222+
let mutable referencingModuleReader = Unchecked.defaultof<_>
223+
if not (tryGetReaderFromModule referencingModule &referencingModuleReader) then () else
224+
225+
for typeName in dirtyTypesInModules.GetValuesSafe(dirtyModule) do
226+
referencingModuleReader.InvalidateReferencingTypes(typeName.ShortName)
227+
228+
referencingModuleReader.InvalidateTypesReferencingFSharpModule(dirtyModule)
229+
230+
dirtyTypesInModules.Clear()
231+
232+
// todo: cache for referencing psi module?
233+
let rec createAllTypeDefsInDependencies (psiModule: IPsiModule) =
234+
for dependencyModule in nonLazyDependenciesForModule.GetValuesSafe(psiModule) do
235+
if allTypesCreated.Contains(dependencyModule) then () else
236+
237+
createAllTypeDefsInDependencies dependencyModule
238+
239+
match getOrCreateReaderFromModule dependencyModule with
240+
| ReferencedAssembly.ProjectOutput(reader) -> reader.CreateAllTypeDefs()
241+
| _ -> ()
242+
243+
allTypesCreated.Add(dependencyModule) |> ignore
244+
159245
new (lifetime: Lifetime, changeManager: ChangeManager, psiModules: IPsiModules, cache: FcsModuleReaderCommonCache,
160246
assemblyInfoShim: AssemblyInfoShim, checkerService: FcsCheckerService, settingsStore: ISettingsStore) =
161247
let isEnabled = AssemblyReaderShim.isEnabled settingsStore
@@ -167,22 +253,22 @@ type AssemblyReaderShim(lifetime: Lifetime, changeManager: ChangeManager, psiMod
167253
override this.GetLastWriteTime(path) =
168254
if not (this.IsEnabled && AssemblyReaderShim.isAssembly path) then base.GetLastWriteTime(path) else
169255

170-
match getOrCreateReader path with
256+
match getOrCreateReaderFromPath path with
171257
| ReferencedAssembly.ProjectOutput reader -> reader.Timestamp
172258
| _ -> base.GetLastWriteTime(path)
173259

174260
override this.ExistsFile(path) =
175261
if not (this.IsEnabled && AssemblyReaderShim.isAssembly path) then base.ExistsFile(path) else
176262

177-
match getOrCreateReader path with
263+
match getOrCreateReaderFromPath path with
178264
| ReferencedAssembly.ProjectOutput _ -> true
179265
| _ -> base.ExistsFile(path)
180266

181267
override this.GetModuleReader(path, readerOptions) =
182268
if not (this.IsEnabled && AssemblyReaderShim.isAssembly path) then
183269
base.GetModuleReader(path, readerOptions) else
184270

185-
match getOrCreateReader path with
271+
match getOrCreateReaderFromPath path with
186272
| ReferencedAssembly.Ignored -> base.GetModuleReader(path, readerOptions)
187273
| ReferencedAssembly.ProjectOutput reader ->
188274

@@ -203,51 +289,19 @@ type AssemblyReaderShim(lifetime: Lifetime, changeManager: ChangeManager, psiMod
203289
let typeElement = typePart.TypeElement
204290
let psiModule = typeElement.Module
205291

206-
if dependenciesToModules.ContainsKey(psiModule) then
207-
dirtyModules.Add(psiModule, typeElement.GetClrName().GetPersistent()) |> ignore
208-
209-
member this.InvalidateDirtyDependencies(psiModule: IPsiModule) =
210-
Assertion.Assert(locker.IsWriteLockHeld, "locker.IsWriteLockHeld")
211-
212-
if dirtyModules.IsEmpty() then () else
213-
214-
for KeyValue(dirtyModule, dirtyTypeNames) in List.ofSeq dirtyModules do
215-
// todo: always invalidate all?
216-
if not (moduleDependencies.ContainsPair(psiModule, dirtyModule)) then () else
217-
218-
let mutable referencedAssembly = Unchecked.defaultof<_>
219-
if not (assemblyReadersByModule.TryGetValue(dirtyModule, &referencedAssembly)) then () else
220-
221-
match referencedAssembly with
222-
| ReferencedAssembly.Ignored -> ()
223-
| ReferencedAssembly.ProjectOutput(reader) ->
224-
225-
for typeName in dirtyTypeNames do
226-
reader.InvalidateTypeDef(typeName)
227-
228-
dirtyModules.RemoveKey(dirtyModule) |> ignore
229-
230-
member this.ForceCreateTypeDefs(psiModule: IPsiModule): unit =
231-
let psiModule = psiModule.As<IProjectPsiModule>()
232-
if isNull psiModule then () else
233-
234-
let path = psiModule.Project.GetOutputFilePath(psiModule.TargetFrameworkId)
235-
match getOrCreateReader path with
236-
| ReferencedAssembly.ProjectOutput(reader) -> reader.ForceCreateTypeDefs()
237-
| _ -> ()
292+
if dependenciesToReferencingModules.ContainsKey(psiModule) then
293+
dirtyTypesInModules.Add(psiModule, typeElement.GetClrName().GetPersistent()) |> ignore
294+
allTypesCreated.Remove(psiModule) |> ignore
238295

239296
interface IFcsAssemblyReaderShim with
240297
member this.PrepareDependencies(psiModule) =
241298
if not this.IsEnabled then () else
242299

243300
use lock = locker.UsingWriteLock()
244301

245-
if not (moduleDependencies.ContainsKey(psiModule)) then
246-
recordDependencies psiModule
247-
248-
this.InvalidateDirtyDependencies(psiModule)
249-
for dependencyModule in moduleDependencies.GetValuesSafe(psiModule) do
250-
this.ForceCreateTypeDefs(dependencyModule)
302+
recordDependencies psiModule
303+
invalidateDirtyDependencies ()
304+
createAllTypeDefsInDependencies psiModule
251305

252306

253307
[<SolutionComponent>]

0 commit comments

Comments
 (0)