@@ -108,22 +108,6 @@ let rec processStateEntry (queryTrie: QueryTrie) (state: FileContentQueryState)
108108 FoundDependencies = foundDependencies
109109 }
110110
111- /// Return all files contained in the trie.
112- let filesInTrie ( node : TrieNode ) : Set < FileIndex > =
113- let rec collect ( node : TrieNode ) ( continuation : FileIndex list -> FileIndex list ) : FileIndex list =
114- let continuations : (( FileIndex list -> FileIndex list ) -> FileIndex list ) list =
115- [
116- for node in node.Children.Values do
117- yield collect node
118- ]
119-
120- let finalContinuation indexes =
121- continuation [ yield ! node.Files; yield ! List.concat indexes ]
122-
123- Continuation.sequence continuations finalContinuation
124-
125- Set.ofList ( collect node id)
126-
127111/// <summary>
128112/// For a given file's content, collect all missing ("ghost") file dependencies that the core resolution algorithm didn't return,
129113/// but are required to satisfy the type-checker.
@@ -136,16 +120,16 @@ let filesInTrie (node: TrieNode) : Set<FileIndex> =
136120/// - the namespace does not contain any children that can be referenced implicitly (eg. by type inference),
137121/// then the main resolution algorithm does not create a link to any file defining the namespace.</para>
138122/// <para>However, to satisfy the type-checker, the namespace must be resolved.
139- /// This function returns a list of extra dependencies that makes sure that any such namespaces can be resolved (if it exists).
140- /// For each unused open namespace we return one or more file links that define it .</para>
123+ /// This function returns an array with a potential extra dependencies that makes sure that any such namespaces can be resolved (if they exists).
124+ /// For each unused namespace `open` we return at most one file that defines that namespace .</para>
141125/// </remarks>
142126let collectGhostDependencies ( fileIndex : FileIndex ) ( trie : TrieNode ) ( queryTrie : QueryTrie ) ( result : FileContentQueryState ) =
143- // Go over all open namespaces, and assert all those links eventually went anywhere
127+ // For each opened namespace, if none of already resolved dependencies define it, return the top-most file that defines it.
144128 Set.toArray result.OpenedNamespaces
145- |> Array.collect ( fun path ->
129+ |> Array.choose ( fun path ->
146130 match queryTrie path with
147131 | QueryTrieNodeResult.NodeExposesData _
148- | QueryTrieNodeResult.NodeDoesNotExist -> Array.empty
132+ | QueryTrieNodeResult.NodeDoesNotExist -> None
149133 | QueryTrieNodeResult.NodeDoesNotExposeData ->
150134 // At this point we are following up if an open namespace really lead nowhere.
151135 let node =
@@ -156,25 +140,23 @@ let collectGhostDependencies (fileIndex: FileIndex) (trie: TrieNode) (queryTrie:
156140
157141 find trie path
158142
159- let filesDefiningNamespace =
160- filesInTrie node |> Set.filter ( fun idx -> idx < fileIndex)
161-
162- let dependenciesDefiningNamespace =
163- Set.intersect result.FoundDependencies filesDefiningNamespace
164-
165- [|
166- if Set.isEmpty dependenciesDefiningNamespace then
167- // There is no existing dependency defining the namespace,
168- // so we need to add one.
169- if Set.isEmpty filesDefiningNamespace then
170- // No file defines inferrable symbols for this namespace, but the namespace might exist.
171- // Because we don't track what files define a namespace without any relevant content,
172- // the only way to ensure the namespace is in scope is to add a link to every preceding file.
173- yield ! [| 0 .. ( fileIndex - 1 ) |]
174- else
175- // At least one file defines the namespace - add a dependency to the first (top) one.
176- yield Seq.head filesDefiningNamespace
177- |])
143+ match node.Current with
144+ // Both Root and module would expose data, so we can ignore them.
145+ | Root _
146+ | Module _ -> None
147+ | Namespace ( filesDefiningNamespaceWithoutTypes = filesDefiningNamespaceWithoutTypes) ->
148+ if filesDefiningNamespaceWithoutTypes.Overlaps( result.FoundDependencies) then
149+ // The ghost dependency is already covered by a real dependency.
150+ None
151+ else
152+ // We are only interested in any file that contained the namespace when they came before the current file.
153+ // If the namespace is defined in a file after the current file then there is no way the current file can reference it.
154+ // Which means that namespace would come from a different assembly.
155+ filesDefiningNamespaceWithoutTypes
156+ |> Seq.sort
157+ |> Seq.tryFind ( fun connectedFileIdx ->
158+ // We pick the lowest file index from the namespace to satisfy the type-checker for the open statement.
159+ connectedFileIdx < fileIndex))
178160
179161let mkGraph ( compilingFSharpCore : bool ) ( filePairs : FilePairMap ) ( files : FileInProject array ) : Graph < FileIndex > =
180162 // We know that implementation files backed by signatures cannot be depended upon.
@@ -190,61 +172,71 @@ let mkGraph (compilingFSharpCore: bool) (filePairs: FilePairMap) (files: FileInP
190172 let trie = TrieMapping.mkTrie trieInput
191173 let queryTrie : QueryTrie = queryTrieMemoized trie
192174
193- let fileContents = files |> Array.Parallel.map FileContentMapping.mkFileContent
175+ let fileContents =
176+ files
177+ |> Array.Parallel.map ( fun file ->
178+ if file.Idx = 0 then
179+ List.empty
180+ else
181+ FileContentMapping.mkFileContent file)
194182
195183 let findDependencies ( file : FileInProject ) : FileIndex array =
196- let fileContent = fileContents[ file.Idx]
197-
198- let knownFiles = [ 0 .. ( file.Idx - 1 ) ] |> set
199- // File depends on all files above it that define accessible symbols at the root level (global namespace).
200- let filesFromRoot = trie.Files |> Set.filter ( fun rootIdx -> rootIdx < file.Idx)
201- // Start by listing root-level dependencies.
202- let initialDepsResult =
203- ( FileContentQueryState.Create file.Idx knownFiles filesFromRoot), fileContent
204- // Sequentially process all relevant entries of the file and keep updating the state and set of dependencies.
205- let depsResult =
206- initialDepsResult
207- // Seq is faster than List in this case.
208- ||> Seq.fold ( processStateEntry queryTrie)
209-
210- // Add missing links for cases where an unused open namespace did not create a link.
211- let ghostDependencies = collectGhostDependencies file.Idx trie queryTrie depsResult
212-
213- // Add a link from implementation files to their signature files.
214- let signatureDependency =
215- match filePairs.TryGetSignatureIndex file.Idx with
216- | None -> Array.empty
217- | Some sigIdx -> Array.singleton sigIdx
218-
219- // Files in FSharp.Core have an implicit dependency on `prim-types-prelude.fsi` - add it.
220- let fsharpCoreImplicitDependencies =
221- let filename = " prim-types-prelude.fsi"
222-
223- let implicitDepIdx =
224- files
225- |> Array.tryFindIndex ( fun f -> FileSystemUtils.fileNameOfPath f.FileName = filename)
226-
227- [|
228- if compilingFSharpCore then
229- match implicitDepIdx with
230- | Some idx ->
231- if file.Idx > idx then
232- yield idx
233- | None ->
234- exn $" Expected to find file '{filename}' during compilation of FSharp.Core, but it was not found."
235- |> raise
236- |]
237-
238- let allDependencies =
239- [|
240- yield ! depsResult.FoundDependencies
241- yield ! ghostDependencies
242- yield ! signatureDependency
243- yield ! fsharpCoreImplicitDependencies
244- |]
245- |> Array.distinct
246-
247- allDependencies
184+ if file.Idx = 0 then
185+ // First file cannot have any dependencies.
186+ Array.empty
187+ else
188+ let fileContent = fileContents[ file.Idx]
189+
190+ let knownFiles = [ 0 .. ( file.Idx - 1 ) ] |> set
191+ // File depends on all files above it that define accessible symbols at the root level (global namespace).
192+ let filesFromRoot = trie.Files |> Set.filter ( fun rootIdx -> rootIdx < file.Idx)
193+ // Start by listing root-level dependencies.
194+ let initialDepsResult =
195+ ( FileContentQueryState.Create file.Idx knownFiles filesFromRoot), fileContent
196+ // Sequentially process all relevant entries of the file and keep updating the state and set of dependencies.
197+ let depsResult =
198+ initialDepsResult
199+ // Seq is faster than List in this case.
200+ ||> Seq.fold ( processStateEntry queryTrie)
201+
202+ // Add missing links for cases where an unused open namespace did not create a link.
203+ let ghostDependencies = collectGhostDependencies file.Idx trie queryTrie depsResult
204+
205+ // Add a link from implementation files to their signature files.
206+ let signatureDependency =
207+ match filePairs.TryGetSignatureIndex file.Idx with
208+ | None -> Array.empty
209+ | Some sigIdx -> Array.singleton sigIdx
210+
211+ // Files in FSharp.Core have an implicit dependency on `prim-types-prelude.fsi` - add it.
212+ let fsharpCoreImplicitDependencies =
213+ let filename = " prim-types-prelude.fsi"
214+
215+ let implicitDepIdx =
216+ files
217+ |> Array.tryFindIndex ( fun f -> FileSystemUtils.fileNameOfPath f.FileName = filename)
218+
219+ [|
220+ if compilingFSharpCore then
221+ match implicitDepIdx with
222+ | Some idx ->
223+ if file.Idx > idx then
224+ yield idx
225+ | None ->
226+ exn $" Expected to find file '{filename}' during compilation of FSharp.Core, but it was not found."
227+ |> raise
228+ |]
229+
230+ let allDependencies =
231+ [|
232+ yield ! depsResult.FoundDependencies
233+ yield ! ghostDependencies
234+ yield ! signatureDependency
235+ yield ! fsharpCoreImplicitDependencies
236+ |]
237+ |> Array.distinct
238+
239+ allDependencies
248240
249241 files
250242 |> Array.Parallel.map ( fun file -> file.Idx, findDependencies file)
0 commit comments