@@ -52,6 +52,10 @@ module AssemblyReaderShim =
52
52
isSupportedProjectLanguage projectProperties.DefaultLanguage &&
53
53
isSupportedProjectKind projectProperties.ProjectKind
54
54
55
+ let isSupportedModule ( psiModule : IPsiModule ) =
56
+ let projectModule = psiModule.As< IProjectPsiModule>()
57
+ isNotNull projectModule && isSupportedProject projectModule.Project
58
+
55
59
let getProjectPsiModuleByOutputAssembly ( psiModules : IPsiModules ) path =
56
60
let projectAndTargetFrameworkId = psiModules.TryGetProjectAndTargetFrameworkIdByOutputAssembly( path)
57
61
if isNull projectAndTargetFrameworkId then null else
@@ -93,69 +97,151 @@ type AssemblyReaderShim(lifetime: Lifetime, changeManager: ChangeManager, psiMod
93
97
let assemblyReadersByPath = ConcurrentDictionary< FileSystemPath, ReferencedAssembly>()
94
98
let assemblyReadersByModule = ConcurrentDictionary< IPsiModule, ReferencedAssembly>()
95
99
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>()
98
102
99
- let dependenciesToModules = OneToSetMap< IPsiModule, IPsiModule>()
103
+ /// F# project module dependencies requiring non-lazy module readers.
104
+ let nonLazyDependenciesForModule = OneToSetMap< IPsiModule, IPsiModule>()
100
105
101
- let dirtyModules = OneToSetMap< IPsiModule, IClrTypeName >()
106
+ let dependenciesToReferencingModules = OneToSetMap< IPsiModule, IPsiModule >()
102
107
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
108
110
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 ) =
110
116
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
111
126
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
117
130
118
- let hasReference =
119
- referencedModule.Project.IsFSharp ||
120
- hasTransitiveReferencesToFSharpProjects referencedModule
131
+ visited.Add ( referencedModule ) |> ignore
132
+ loop referencedModule
133
+ | _ -> ())
121
134
122
- if hasReference then true else
135
+ loop psiModule
136
+ projectModules, hasFSharpReferences
123
137
124
- visited.Add ( referencedModule ) |> ignore
125
- false
138
+ let rec recordDependencies ( psiModule : IPsiModule ): unit =
139
+ if moduleDependenciesRecorded.Contains ( psiModule ) then () else
126
140
127
- | _ -> false )
128
- |> Seq.isEmpty
129
- |> not
141
+ if not ( psiModule :? IProjectPsiModule) then () else
130
142
131
- let recordDependencies ( psiModule : IPsiModule ) =
143
+ // todo: filter by primary module? test on web projects containing multiple modules
132
144
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
135
146
136
- let projectLanguage = referencedModule.Project.ProjectProperties.DefaultLanguage
137
- if not ( AssemblyReaderShim.isSupportedProjectLanguage projectLanguage) then () else
147
+ let referencedProjectModules , hasFSharpReferences = transitiveReferencedProjectModules referencedModule
138
148
139
- if not ( hasTransitiveReferencesToFSharpProjects referencedModule) then () else
149
+ if hasFSharpReferences then
150
+ nonLazyDependenciesForModule.Add( psiModule, referencedModule) |> ignore
140
151
141
- // todo: add transitive dependencies
142
- moduleDependencies.Add( psiModule, referencedModule) |> ignore
143
- dependenciesToModules.Add( referencedModule, psiModule) |> ignore
152
+ dependenciesToReferencingModules.Add( referencedModule, psiModule) |> ignore
144
153
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
148
158
149
- let reader = createReader path
159
+ let recordReader path reader =
150
160
assemblyReadersByPath.[ path] <- reader
151
161
152
162
match reader with
153
163
| ReferencedAssembly.ProjectOutput moduleReader ->
154
164
assemblyReadersByModule.[ moduleReader.PsiModule] <- reader
155
165
| _ -> ()
156
166
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
157
183
reader
158
184
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
+
159
245
new ( lifetime : Lifetime , changeManager : ChangeManager , psiModules : IPsiModules , cache : FcsModuleReaderCommonCache ,
160
246
assemblyInfoShim : AssemblyInfoShim , checkerService : FcsCheckerService , settingsStore : ISettingsStore ) =
161
247
let isEnabled = AssemblyReaderShim.isEnabled settingsStore
@@ -167,22 +253,22 @@ type AssemblyReaderShim(lifetime: Lifetime, changeManager: ChangeManager, psiMod
167
253
override this.GetLastWriteTime ( path ) =
168
254
if not ( this.IsEnabled && AssemblyReaderShim.isAssembly path) then base .GetLastWriteTime( path) else
169
255
170
- match getOrCreateReader path with
256
+ match getOrCreateReaderFromPath path with
171
257
| ReferencedAssembly.ProjectOutput reader -> reader.Timestamp
172
258
| _ -> base .GetLastWriteTime( path)
173
259
174
260
override this.ExistsFile ( path ) =
175
261
if not ( this.IsEnabled && AssemblyReaderShim.isAssembly path) then base .ExistsFile( path) else
176
262
177
- match getOrCreateReader path with
263
+ match getOrCreateReaderFromPath path with
178
264
| ReferencedAssembly.ProjectOutput _ -> true
179
265
| _ -> base .ExistsFile( path)
180
266
181
267
override this.GetModuleReader ( path , readerOptions ) =
182
268
if not ( this.IsEnabled && AssemblyReaderShim.isAssembly path) then
183
269
base .GetModuleReader( path, readerOptions) else
184
270
185
- match getOrCreateReader path with
271
+ match getOrCreateReaderFromPath path with
186
272
| ReferencedAssembly.Ignored -> base .GetModuleReader( path, readerOptions)
187
273
| ReferencedAssembly.ProjectOutput reader ->
188
274
@@ -203,51 +289,19 @@ type AssemblyReaderShim(lifetime: Lifetime, changeManager: ChangeManager, psiMod
203
289
let typeElement = typePart.TypeElement
204
290
let psiModule = typeElement.Module
205
291
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
238
295
239
296
interface IFcsAssemblyReaderShim with
240
297
member this.PrepareDependencies ( psiModule ) =
241
298
if not this.IsEnabled then () else
242
299
243
300
use lock = locker.UsingWriteLock()
244
301
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
251
305
252
306
253
307
[<SolutionComponent>]
0 commit comments