diff --git a/eng/Versions.props b/eng/Versions.props
index b27ad4ce92d..3d89b4531b0 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -168,7 +168,7 @@
1.0.0
1.1.33
- 0.13.2
+ 0.13.10
2.16.5
4.3.0.0
1.0.31
diff --git a/src/Compiler/Driver/GraphChecking/Graph.fs b/src/Compiler/Driver/GraphChecking/Graph.fs
index dd51ea190a2..dbe4c6b6cc7 100644
--- a/src/Compiler/Driver/GraphChecking/Graph.fs
+++ b/src/Compiler/Driver/GraphChecking/Graph.fs
@@ -27,26 +27,43 @@ module internal Graph =
|> Array.map (fun (KeyValue(k, v)) -> k, v)
|> readOnlyDict
- let transitive<'Node when 'Node: equality> (graph: Graph<'Node>) : Graph<'Node> =
- /// Find transitive dependencies of a single node.
- let transitiveDeps (node: 'Node) =
- let visited = HashSet<'Node>()
+ let nodes (graph: Graph<'Node>) : Set<'Node> =
+ graph.Values |> Seq.collect id |> Seq.append graph.Keys |> Set
+
+ /// Find transitive dependencies of a single node.
+ let transitiveDeps (node: 'Node) (graph: Graph<'Node>) =
+ let visited = HashSet<'Node>()
- let rec dfs (node: 'Node) =
- graph[node]
- // Add direct dependencies.
- // Use HashSet.Add return value semantics to filter out those that were added previously.
- |> Array.filter visited.Add
- |> Array.iter dfs
+ let rec dfs (node: 'Node) =
+ graph[node]
+ // Add direct dependencies.
+ // Use HashSet.Add return value semantics to filter out those that were added previously.
+ |> Array.filter visited.Add
+ |> Array.iter dfs
- dfs node
- visited |> Seq.toArray
+ dfs node
+ visited |> Seq.toArray
+ let transitive<'Node when 'Node: equality> (graph: Graph<'Node>) : Graph<'Node> =
graph.Keys
|> Seq.toArray
- |> Array.Parallel.map (fun node -> node, transitiveDeps node)
+ |> Array.Parallel.map (fun node -> node, graph |> transitiveDeps node)
|> readOnlyDict
+ // TODO: optimize
+ /// Get subgraph of the given graph that contains only nodes that are reachable from the given node.
+ let subGraphFor node graph =
+ let allDeps = graph |> transitiveDeps node
+ let relevant n = n = node || allDeps |> Array.contains n
+
+ graph
+ |> Seq.choose (fun (KeyValue(src, deps)) ->
+ if relevant src then
+ Some(src, deps |> Array.filter relevant)
+ else
+ None)
+ |> make
+
/// Create a reverse of the graph
let reverse (originalGraph: Graph<'Node>) : Graph<'Node> =
originalGraph
@@ -69,7 +86,7 @@ module internal Graph =
let print (graph: Graph<'Node>) : unit =
printCustom graph (fun node -> node.ToString())
- let serialiseToMermaid path (graph: Graph) =
+ let serialiseToMermaid (graph: Graph) =
let sb = StringBuilder()
let appendLine (line: string) = sb.AppendLine(line) |> ignore
@@ -84,8 +101,10 @@ module internal Graph =
appendLine $" %i{idx} --> %i{depIdx}"
appendLine "```"
+ sb.ToString()
+ let writeMermaidToFile path (graph: Graph) =
use out =
FileSystem.OpenFileForWriteShim(path, fileMode = System.IO.FileMode.Create)
- out.WriteAllText(sb.ToString())
+ graph |> serialiseToMermaid |> out.WriteAllText
diff --git a/src/Compiler/Driver/GraphChecking/Graph.fsi b/src/Compiler/Driver/GraphChecking/Graph.fsi
index 95542470d8a..a93e429d2fe 100644
--- a/src/Compiler/Driver/GraphChecking/Graph.fsi
+++ b/src/Compiler/Driver/GraphChecking/Graph.fsi
@@ -10,12 +10,18 @@ module internal Graph =
/// Build the graph.
val make: nodeDeps: seq<'Node * 'Node array> -> Graph<'Node> when 'Node: equality
val map<'T, 'U when 'U: equality> : f: ('T -> 'U) -> graph: Graph<'T> -> Graph<'U>
+ /// Get all nodes of the graph.
+ val nodes: graph: Graph<'Node> -> Set<'Node>
/// Create a transitive closure of the graph in O(n^2) time (but parallelize it).
/// The resulting graph contains edge A -> C iff the input graph contains a (directed) non-zero length path from A to C.
val transitive<'Node when 'Node: equality> : graph: Graph<'Node> -> Graph<'Node>
+ /// Get a sub-graph of the graph containing only the nodes reachable from the given node.
+ val subGraphFor: node: 'Node -> graph: Graph<'Node> -> Graph<'Node> when 'Node: equality
/// Create a reverse of the graph.
val reverse<'Node when 'Node: equality> : originalGraph: Graph<'Node> -> Graph<'Node>
/// Print the contents of the graph to the standard output.
val print: graph: Graph<'Node> -> unit
+ /// Create a simple Mermaid graph
+ val serialiseToMermaid: graph: Graph -> string
/// Create a simple Mermaid graph and save it under the path specified.
- val serialiseToMermaid: path: string -> graph: Graph -> unit
+ val writeMermaidToFile: path: string -> graph: Graph -> unit
diff --git a/src/Compiler/Driver/GraphChecking/GraphProcessing.fs b/src/Compiler/Driver/GraphChecking/GraphProcessing.fs
index 47993e00862..afe491b4b74 100644
--- a/src/Compiler/Driver/GraphChecking/GraphProcessing.fs
+++ b/src/Compiler/Driver/GraphChecking/GraphProcessing.fs
@@ -1,6 +1,9 @@
module internal FSharp.Compiler.GraphChecking.GraphProcessing
open System.Threading
+open FSharp.Compiler.GraphChecking
+open System.Threading.Tasks
+open System
/// Information about the node in a graph, describing its relation with other nodes.
type NodeInfo<'Item> =
@@ -32,6 +35,9 @@ type ProcessedNode<'Item, 'Result> =
Result: 'Result
}
+type GraphProcessingException(msg, ex: System.Exception) =
+ inherit exn(msg, ex)
+
let processGraph<'Item, 'Result when 'Item: equality and 'Item: comparison>
(graph: Graph<'Item>)
(work: ('Item -> ProcessedNode<'Item, 'Result>) -> NodeInfo<'Item> -> 'Result)
@@ -150,7 +156,7 @@ let processGraph<'Item, 'Result when 'Item: equality and 'Item: comparison>
// If we stopped early due to an exception, reraise it.
match getExn () with
| None -> ()
- | Some(item, ex) -> raise (System.Exception($"Encountered exception when processing item '{item}'", ex))
+ | Some(item, ex) -> raise (GraphProcessingException($"Encountered exception when processing item '{item}'", ex))
// All calculations succeeded - extract the results and sort in input order.
nodes.Values
@@ -162,3 +168,131 @@ let processGraph<'Item, 'Result when 'Item: equality and 'Item: comparison>
node.Info.Item, result)
|> Seq.sortBy fst
|> Seq.toArray
+
+let processGraphAsync<'Item, 'Result when 'Item: equality and 'Item: comparison>
+ (graph: Graph<'Item>)
+ (work: ('Item -> ProcessedNode<'Item, 'Result>) -> NodeInfo<'Item> -> Async<'Result>)
+ : Async<('Item * 'Result)[]> =
+ async {
+ let transitiveDeps = graph |> Graph.transitive
+ let dependants = graph |> Graph.reverse
+ // Cancellation source used to signal either an exception in one of the items or end of processing.
+ let! parentCt = Async.CancellationToken
+ use localCts = new CancellationTokenSource()
+
+ let completionSignal = TaskCompletionSource()
+
+ use _ = parentCt.Register(fun () -> completionSignal.TrySetCanceled() |> ignore)
+
+ use cts = CancellationTokenSource.CreateLinkedTokenSource(parentCt, localCts.Token)
+
+ let makeNode (item: 'Item) : GraphNode<'Item, 'Result> =
+ let info =
+ let exists = graph.ContainsKey item
+
+ if
+ not exists
+ || not (transitiveDeps.ContainsKey item)
+ || not (dependants.ContainsKey item)
+ then
+ printfn $"Unexpected inconsistent state of the graph for item '{item}'"
+
+ {
+ Item = item
+ Deps = graph[item]
+ TransitiveDeps = transitiveDeps[item]
+ Dependants = dependants[item]
+ }
+
+ {
+ Info = info
+ Result = None
+ ProcessedDepsCount = IncrementableInt(0)
+ }
+
+ let nodes = graph.Keys |> Seq.map (fun item -> item, makeNode item) |> readOnlyDict
+
+ let lookupMany items =
+ items |> Array.map (fun item -> nodes[item])
+
+ let leaves =
+ nodes.Values |> Seq.filter (fun n -> n.Info.Deps.Length = 0) |> Seq.toArray
+
+ let getItemPublicNode item =
+ let node = nodes[item]
+
+ {
+ ProcessedNode.Info = node.Info
+ ProcessedNode.Result =
+ node.Result
+ |> Option.defaultWith (fun () -> failwith $"Results for item '{node.Info.Item}' are not yet available")
+ }
+
+ let processedCount = IncrementableInt(0)
+
+ let raiseExn (item, ex: exn) =
+ localCts.Cancel()
+
+ match ex with
+ | :? OperationCanceledException -> completionSignal.TrySetCanceled()
+ | _ ->
+ completionSignal.TrySetException(
+ GraphProcessingException($"[*] Encountered exception when processing item '{item}': {ex.Message}", ex)
+ )
+ |> ignore
+
+ let incrementProcessedNodesCount () =
+ if processedCount.Increment() = nodes.Count then
+ completionSignal.TrySetResult() |> ignore
+
+ let rec queueNode node =
+ Async.Start(
+ async {
+ let! res = processNode node |> Async.Catch
+
+ match res with
+ | Choice1Of2() -> ()
+ | Choice2Of2 ex -> raiseExn (node.Info.Item, ex)
+ },
+ cts.Token
+ )
+
+ and processNode (node: GraphNode<'Item, 'Result>) : Async =
+ async {
+
+ let info = node.Info
+
+ let! singleRes = work getItemPublicNode info
+ node.Result <- Some singleRes
+
+ let unblockedDependants =
+ node.Info.Dependants
+ |> lookupMany
+ // For every dependant, increment its number of processed dependencies,
+ // and filter dependants which now have all dependencies processed (but didn't before).
+ |> Array.filter (fun dependant ->
+ let pdc = dependant.ProcessedDepsCount.Increment()
+ // Note: We cannot read 'dependant.ProcessedDepsCount' again to avoid returning the same item multiple times.
+ pdc = dependant.Info.Deps.Length)
+
+ unblockedDependants |> Array.iter queueNode
+ incrementProcessedNodesCount ()
+ }
+
+ leaves |> Array.iter queueNode
+
+ // Wait for end of processing, an exception, or an external cancellation request.
+ do! completionSignal.Task |> Async.AwaitTask
+
+ // All calculations succeeded - extract the results and sort in input order.
+ return
+ nodes.Values
+ |> Seq.map (fun node ->
+ let result =
+ node.Result
+ |> Option.defaultWith (fun () -> failwith $"Unexpected lack of result for item '{node.Info.Item}'")
+
+ node.Info.Item, result)
+ |> Seq.sortBy fst
+ |> Seq.toArray
+ }
diff --git a/src/Compiler/Driver/GraphChecking/GraphProcessing.fsi b/src/Compiler/Driver/GraphChecking/GraphProcessing.fsi
index cb9a95a59f8..585daa52fd7 100644
--- a/src/Compiler/Driver/GraphChecking/GraphProcessing.fsi
+++ b/src/Compiler/Driver/GraphChecking/GraphProcessing.fsi
@@ -15,6 +15,10 @@ type ProcessedNode<'Item, 'Result> =
{ Info: NodeInfo<'Item>
Result: 'Result }
+type GraphProcessingException =
+ inherit exn
+ new: msg: string * ex: System.Exception -> GraphProcessingException
+
///
/// A generic method to generate results for a graph of work items in parallel.
/// Processes leaves first, and after each node has been processed, schedules any now unblocked dependants.
@@ -33,3 +37,8 @@ val processGraph<'Item, 'Result when 'Item: equality and 'Item: comparison> :
work: (('Item -> ProcessedNode<'Item, 'Result>) -> NodeInfo<'Item> -> 'Result) ->
parentCt: CancellationToken ->
('Item * 'Result)[]
+
+val processGraphAsync<'Item, 'Result when 'Item: equality and 'Item: comparison> :
+ graph: Graph<'Item> ->
+ work: (('Item -> ProcessedNode<'Item, 'Result>) -> NodeInfo<'Item> -> Async<'Result>) ->
+ Async<('Item * 'Result)[]>
diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fs b/src/Compiler/Driver/ParseAndCheckInputs.fs
index a9fc59b66d3..5a23c95ca7b 100644
--- a/src/Compiler/Driver/ParseAndCheckInputs.fs
+++ b/src/Compiler/Driver/ParseAndCheckInputs.fs
@@ -1483,7 +1483,7 @@ let CheckOneInputWithCallback
prefixPathOpt,
tcSink,
tcState: TcState,
- inp: ParsedInput,
+ input: ParsedInput,
_skipImplIfSigExists: bool):
(unit -> bool) * TcConfig * TcImports * TcGlobals * LongIdent option * TcResultsSink * TcState * ParsedInput * bool)
: Cancellable> =
@@ -1491,7 +1491,7 @@ let CheckOneInputWithCallback
try
CheckSimulateException tcConfig
- let m = inp.Range
+ let m = input.Range
let amap = tcImports.GetImportMap()
let conditionalDefines =
@@ -1500,7 +1500,7 @@ let CheckOneInputWithCallback
else
Some tcConfig.conditionalDefines
- match inp with
+ match input with
| ParsedInput.SigFile file ->
let qualNameOfFile = file.QualifiedName
@@ -1740,6 +1740,43 @@ module private TypeCheckingGraphProcessing =
finalFileResults, state
+let TransformDependencyGraph (graph: Graph, filePairs: FilePairMap) =
+ let mkArtificialImplFile n = NodeToTypeCheck.ArtificialImplFile n
+ let mkPhysicalFile n = NodeToTypeCheck.PhysicalFile n
+
+ /// Map any signature dependencies to the ArtificialImplFile counterparts,
+ /// unless the signature dependency is the backing file of the current (implementation) file.
+ let mapDependencies idx deps =
+ Array.map
+ (fun dep ->
+ if filePairs.IsSignature dep then
+ let implIdx = filePairs.GetImplementationIndex dep
+
+ if implIdx = idx then
+ // This is the matching signature for the implementation.
+ // Retain the direct dependency onto the signature file.
+ mkPhysicalFile dep
+ else
+ mkArtificialImplFile dep
+ else
+ mkPhysicalFile dep)
+ deps
+
+ // Transform the graph to include ArtificialImplFile nodes when necessary.
+ graph
+ |> Seq.collect (fun (KeyValue(fileIdx, deps)) ->
+ if filePairs.IsSignature fileIdx then
+ // Add an additional ArtificialImplFile node for the signature file.
+ [|
+ // Mark the current file as physical and map the dependencies.
+ mkPhysicalFile fileIdx, mapDependencies fileIdx deps
+ // Introduce a new node that depends on the signature.
+ mkArtificialImplFile fileIdx, [| mkPhysicalFile fileIdx |]
+ |]
+ else
+ [| mkPhysicalFile fileIdx, mapDependencies fileIdx deps |])
+ |> Graph.make
+
/// Constructs a file dependency graph and type-checks the files in parallel where possible.
let CheckMultipleInputsUsingGraphMode
((ctok, checkForErrors, tcConfig: TcConfig, tcImports: TcImports, tcGlobals, prefixPathOpt, tcState, eagerFormat, inputs):
@@ -1768,42 +1805,7 @@ let CheckMultipleInputsUsingGraphMode
let filePairs = FilePairMap(sourceFiles)
let graph, trie = DependencyResolution.mkGraph filePairs sourceFiles
- let nodeGraph =
- let mkArtificialImplFile n = NodeToTypeCheck.ArtificialImplFile n
- let mkPhysicalFile n = NodeToTypeCheck.PhysicalFile n
-
- /// Map any signature dependencies to the ArtificialImplFile counterparts,
- /// unless the signature dependency is the backing file of the current (implementation) file.
- let mapDependencies idx deps =
- Array.map
- (fun dep ->
- if filePairs.IsSignature dep then
- let implIdx = filePairs.GetImplementationIndex dep
-
- if implIdx = idx then
- // This is the matching signature for the implementation.
- // Retain the direct dependency onto the signature file.
- mkPhysicalFile dep
- else
- mkArtificialImplFile dep
- else
- mkPhysicalFile dep)
- deps
-
- // Transform the graph to include ArtificialImplFile nodes when necessary.
- graph
- |> Seq.collect (fun (KeyValue(fileIdx, deps)) ->
- if filePairs.IsSignature fileIdx then
- // Add an additional ArtificialImplFile node for the signature file.
- [|
- // Mark the current file as physical and map the dependencies.
- mkPhysicalFile fileIdx, mapDependencies fileIdx deps
- // Introduce a new node that depends on the signature.
- mkArtificialImplFile fileIdx, [| mkPhysicalFile fileIdx |]
- |]
- else
- [| mkPhysicalFile fileIdx, mapDependencies fileIdx deps |])
- |> Graph.make
+ let nodeGraph = TransformDependencyGraph(graph, filePairs)
// Persist the graph to a Mermaid diagram if specified.
if tcConfig.typeCheckingConfig.DumpGraph then
@@ -1823,7 +1825,7 @@ let CheckMultipleInputsUsingGraphMode
.TrimStart([| '\\'; '/' |])
(idx, friendlyFileName))
- |> Graph.serialiseToMermaid graphFile)
+ |> Graph.writeMermaidToFile graphFile)
let _ = ctok // TODO Use it
let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger
diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fsi b/src/Compiler/Driver/ParseAndCheckInputs.fsi
index 745afa51be4..875be616a8e 100644
--- a/src/Compiler/Driver/ParseAndCheckInputs.fsi
+++ b/src/Compiler/Driver/ParseAndCheckInputs.fsi
@@ -13,12 +13,44 @@ open FSharp.Compiler.CompilerImports
open FSharp.Compiler.Diagnostics
open FSharp.Compiler.DependencyManager
open FSharp.Compiler.DiagnosticsLogger
+open FSharp.Compiler.GraphChecking
+open FSharp.Compiler.NameResolution
open FSharp.Compiler.Syntax
open FSharp.Compiler.TcGlobals
open FSharp.Compiler.Text
open FSharp.Compiler.TypedTree
open FSharp.Compiler.UnicodeLexing
+/// Auxiliary type for re-using signature information in TcEnvFromImpls.
+///
+/// TcState has two typing environments: TcEnvFromSignatures && TcEnvFromImpls
+/// When type checking a file, depending on the type (implementation or signature), it will use one of these typing environments (TcEnv).
+/// Checking a file will populate the respective TcEnv.
+///
+/// When a file has a dependencies, the information of the signature file in case a pair (implementation file backed by a signature) will suffice to type-check that file.
+/// Example: if `B.fs` has a dependency on `A`, the information of `A.fsi` is enough for `B.fs` to type-check, on condition that information is available in the TcEnvFromImpls.
+/// We introduce a special ArtificialImplFile node in the graph to satisfy this. `B.fs -> [ A.fsi ]` becomes `B.fs -> [ ArtificialImplFile A ].
+/// The `ArtificialImplFile A` node will duplicate the signature information which A.fsi provided earlier.
+/// Processing a `ArtificialImplFile` node will add the information from the TcEnvFromSignatures to the TcEnvFromImpls.
+/// This means `A` will be known in both TcEnvs and therefor `B.fs` can be type-checked.
+/// By doing this, we can speed up the graph processing as type checking a signature file is less expensive than its implementation counterpart.
+///
+/// When we need to actually type-check an implementation file backed by a signature, we cannot have the duplicate information of the signature file present in TcEnvFromImpls.
+/// Example `A.fs -> [ A.fsi ]`. An implementation file always depends on its signature.
+/// Type-checking `A.fs` will add the actual information to TcEnvFromImpls and we do not depend on the `ArtificialImplFile A` for `A.fs`.
+///
+/// In order to deal correctly with the `ArtificialImplFile` logic, we need to transform the resolved graph to contain the additional pair nodes.
+/// After we have type-checked the graph, we exclude the ArtificialImplFile nodes as they are not actual physical files in our project.
+[]
+type NodeToTypeCheck =
+ /// A real physical file in the current project.
+ /// This can be either an implementation or a signature file.
+ | PhysicalFile of fileIndex: FileIndex
+ /// An artificial node that will add the earlier processed signature information to the TcEnvFromImpls.
+ /// Dependants on this type of node will perceive that a file is known in both TcEnvFromSignatures and TcEnvFromImpls.
+ /// Even though the actual implementation file was not type-checked.
+ | ArtificialImplFile of signatureFileIndex: FileIndex
+
val IsScript: string -> bool
val ComputeQualifiedNameOfFileFromUniquePath: range * string list -> QualifiedNameOfFile
@@ -131,6 +163,8 @@ type TcState =
member CreatesGeneratedProvidedTypes: bool
+type PartialResult = TcEnv * TopAttribs * CheckedImplFile option * ModuleOrNamespaceType
+
/// Get the initial type checking state for a set of inputs
val GetInitialTcState: range * string * TcConfig * TcGlobals * TcImports * TcEnv * OpenDeclaration list -> TcState
@@ -151,6 +185,42 @@ val CheckOneInput:
input: ParsedInput ->
Cancellable<(TcEnv * TopAttribs * CheckedImplFile option * ModuleOrNamespaceType) * TcState>
+val CheckOneInputWithCallback:
+ node: NodeToTypeCheck ->
+ checkForErrors: (unit -> bool) *
+ tcConfig: TcConfig *
+ tcImports: TcImports *
+ tcGlobals: TcGlobals *
+ prefixPathOpt: LongIdent option *
+ tcSink: TcResultsSink *
+ tcState: TcState *
+ input: ParsedInput *
+ _skipImplIfSigExists: bool ->
+ Cancellable>
+
+val AddCheckResultsToTcState:
+ tcGlobals: TcGlobals *
+ amap: Import.ImportMap *
+ hadSig: bool *
+ prefixPathOpt: LongIdent option *
+ tcSink: TcResultsSink *
+ tcImplEnv: TcEnv *
+ qualNameOfFile: QualifiedNameOfFile *
+ implFileSigType: ModuleOrNamespaceType ->
+ tcState: TcState ->
+ ModuleOrNamespaceType * TcState
+
+val AddSignatureResultToTcImplEnv:
+ tcImports: TcImports *
+ tcGlobals: TcGlobals *
+ prefixPathOpt: LongIdent option *
+ tcSink: TcResultsSink *
+ tcState: TcState *
+ input: ParsedInput ->
+ (TcState -> PartialResult * TcState)
+
+val TransformDependencyGraph: graph: Graph * filePairs: FilePairMap -> Graph
+
/// Finish the checking of multiple inputs
val CheckMultipleInputsFinish:
(TcEnv * TopAttribs * 'T option * 'U) list * TcState -> (TcEnv * TopAttribs * 'T list * 'U list) * TcState
diff --git a/src/Compiler/FSharp.Compiler.Service.fsproj b/src/Compiler/FSharp.Compiler.Service.fsproj
index 687bc269233..86389d684b1 100644
--- a/src/Compiler/FSharp.Compiler.Service.fsproj
+++ b/src/Compiler/FSharp.Compiler.Service.fsproj
@@ -21,6 +21,7 @@
$(OtherFlags) --warnon:3218
$(OtherFlags) --warnon:3390
+
true
$(IntermediateOutputPath)$(TargetFramework)\
$(IntermediateOutputPath)$(TargetFramework)\
@@ -76,7 +77,8 @@
-
+
+
@@ -90,6 +92,7 @@
FSStrings.resx
FSStrings.resources
+
@@ -124,6 +127,8 @@
+
+
@@ -144,6 +149,8 @@
+
+
@@ -156,6 +163,8 @@
+
+
@@ -492,7 +501,12 @@
-
+
+
+
+
+
+
diff --git a/src/Compiler/Facilities/AsyncMemoize.fs b/src/Compiler/Facilities/AsyncMemoize.fs
new file mode 100644
index 00000000000..ad798140cb9
--- /dev/null
+++ b/src/Compiler/Facilities/AsyncMemoize.fs
@@ -0,0 +1,611 @@
+namespace Internal.Utilities.Collections
+
+open System
+open System.Collections.Generic
+open System.Diagnostics
+open System.IO
+open System.Threading
+open System.Threading.Tasks
+
+open FSharp.Compiler
+open FSharp.Compiler.BuildGraph
+open FSharp.Compiler.Diagnostics
+open FSharp.Compiler.DiagnosticsLogger
+open System.Runtime.CompilerServices
+
+[]
+module internal Utils =
+
+ /// Return file name with one directory above it
+ let shortPath path =
+ let dirPath = Path.GetDirectoryName path
+
+ let dir =
+ dirPath.Split Path.DirectorySeparatorChar
+ |> Array.tryLast
+ |> Option.map (sprintf "%s/")
+ |> Option.defaultValue ""
+
+ $"{dir}{Path.GetFileName path}"
+
+ let replayDiagnostics (logger: DiagnosticsLogger) = Seq.iter ((<|) logger.DiagnosticSink)
+
+ let (|TaskCancelled|_|) (ex: exn) =
+ match ex with
+ | :? System.Threading.Tasks.TaskCanceledException as tce -> Some tce
+ //| :? System.AggregateException as ae ->
+ // if ae.InnerExceptions |> Seq.forall (fun e -> e :? System.Threading.Tasks.TaskCanceledException) then
+ // ae.InnerExceptions |> Seq.tryHead |> Option.map (fun e -> e :?> System.Threading.Tasks.TaskCanceledException)
+ // else
+ // None
+ | _ -> None
+
+type internal StateUpdate<'TValue> =
+ | CancelRequest
+ | OriginatorCanceled
+ | JobCompleted of 'TValue * (PhasedDiagnostic * FSharpDiagnosticSeverity) list
+ | JobFailed of exn * (PhasedDiagnostic * FSharpDiagnosticSeverity) list
+
+type internal MemoizeReply<'TValue> =
+ | New of CancellationToken
+ | Existing of Task<'TValue>
+
+type internal MemoizeRequest<'TValue> = GetOrCompute of NodeCode<'TValue> * CancellationToken
+
+[]
+type internal Job<'TValue> =
+ | Running of TaskCompletionSource<'TValue> * CancellationTokenSource * NodeCode<'TValue> * DateTime * ResizeArray
+ | Completed of 'TValue * (PhasedDiagnostic * FSharpDiagnosticSeverity) list
+ | Canceled of DateTime
+ | Failed of DateTime * exn // TODO: probably we don't need to keep this
+
+ member this.DebuggerDisplay =
+ match this with
+ | Running(_, cts, _, ts, _) ->
+ let cancellation =
+ if cts.IsCancellationRequested then
+ " ! Cancellation Requested"
+ else
+ ""
+
+ $"Running since {ts.ToShortTimeString()}{cancellation}"
+ | Completed(value, diags) -> $"Completed {value}" + (if diags.Length > 0 then $" ({diags.Length})" else "")
+ | Canceled _ -> "Canceled"
+ | Failed(_, ex) -> $"Failed {ex}"
+
+type internal JobEvent =
+ | Started
+ | Finished
+ | Canceled
+ | Evicted
+ | Collected
+ | Weakened
+ | Strengthened
+ | Failed
+ | Cleared
+
+type internal ICacheKey<'TKey, 'TVersion> =
+ abstract member GetKey: unit -> 'TKey
+ abstract member GetVersion: unit -> 'TVersion
+ abstract member GetLabel: unit -> string
+
+[]
+type Extensions =
+
+ []
+ static member internal WithExtraVersion(cacheKey: ICacheKey<_, _>, extraVersion) =
+ { new ICacheKey<_, _> with
+ member _.GetLabel() = cacheKey.GetLabel()
+ member _.GetKey() = cacheKey.GetKey()
+ member _.GetVersion() = cacheKey.GetVersion(), extraVersion
+ }
+
+type private KeyData<'TKey, 'TVersion> =
+ {
+ Label: string
+ Key: 'TKey
+ Version: 'TVersion
+ }
+
+type internal AsyncLock() =
+
+ let semaphore = new SemaphoreSlim(1, 1)
+
+ member _.Semaphore = semaphore
+
+ member _.Do(f) =
+ task {
+ do! semaphore.WaitAsync()
+
+ try
+ return! f ()
+ finally
+ semaphore.Release() |> ignore
+ }
+
+ interface IDisposable with
+ member _.Dispose() = semaphore.Dispose()
+
+type internal CachingDiagnosticsLogger(originalLogger: DiagnosticsLogger option) =
+ inherit DiagnosticsLogger($"CachingDiagnosticsLogger")
+
+ let capturedDiagnostics = ResizeArray()
+
+ override _.ErrorCount =
+ originalLogger
+ |> Option.map (fun x -> x.ErrorCount)
+ |> Option.defaultValue capturedDiagnostics.Count
+
+ override _.DiagnosticSink(diagnostic: PhasedDiagnostic, severity: FSharpDiagnosticSeverity) =
+ originalLogger |> Option.iter (fun x -> x.DiagnosticSink(diagnostic, severity))
+ capturedDiagnostics.Add(diagnostic, severity)
+
+ member _.CapturedDiagnostics = capturedDiagnostics |> Seq.toList
+
+[]
+type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'TVersion: equality>
+ (?keepStrongly, ?keepWeakly, ?name: string, ?cancelDuplicateRunningJobs: bool) =
+
+ let name = defaultArg name "N/A"
+ let cancelDuplicateRunningJobs = defaultArg cancelDuplicateRunningJobs false
+
+ let event = Event<_>()
+
+ let mutable errors = 0
+ let mutable hits = 0
+ let mutable started = 0
+ let mutable completed = 0
+ let mutable canceled = 0
+ let mutable restarted = 0
+ let mutable failed = 0
+ let mutable evicted = 0
+ let mutable collected = 0
+ let mutable strengthened = 0
+ let mutable cleared = 0
+
+ let mutable cancel_ct_registration_original = 0
+ let mutable cancel_exception_original = 0
+ let mutable cancel_original_processed = 0
+ let mutable cancel_ct_registration_subsequent = 0
+ let mutable cancel_exception_subsequent = 0
+ let mutable cancel_subsequent_processed = 0
+
+ let failures = ResizeArray()
+ let mutable avgDurationMs = 0.0
+
+ let cache =
+ LruCache<'TKey, 'TVersion, Job<'TValue>>(
+ keepStrongly = defaultArg keepStrongly 100,
+ keepWeakly = defaultArg keepWeakly 200,
+ requiredToKeep =
+ (function
+ | Running _ -> true
+ | Job.Canceled at when at > DateTime.Now.AddMinutes -5.0 -> true
+ | Job.Failed(at, _) when at > DateTime.Now.AddMinutes -5.0 -> true
+ | _ -> false),
+ event =
+ (function
+ | CacheEvent.Evicted ->
+ (fun k ->
+ Interlocked.Increment &evicted |> ignore
+ event.Trigger(JobEvent.Evicted, k))
+ | CacheEvent.Collected ->
+ (fun k ->
+ Interlocked.Increment &collected |> ignore
+ event.Trigger(JobEvent.Collected, k))
+ | CacheEvent.Weakened -> (fun k -> event.Trigger(JobEvent.Weakened, k))
+ | CacheEvent.Strengthened ->
+ (fun k ->
+ Interlocked.Increment &strengthened |> ignore
+ event.Trigger(JobEvent.Strengthened, k))
+ | CacheEvent.Cleared ->
+ (fun k ->
+ Interlocked.Increment &cleared |> ignore
+ event.Trigger(JobEvent.Cleared, k)))
+ )
+
+ let requestCounts = Dictionary, int>()
+ let cancellationRegistrations = Dictionary<_, _>()
+
+ let saveRegistration key registration =
+ cancellationRegistrations[key] <-
+ match cancellationRegistrations.TryGetValue key with
+ | true, registrations -> registration :: registrations
+ | _ -> [ registration ]
+
+ let cancelRegistration key =
+ match cancellationRegistrations.TryGetValue key with
+ | true, registrations ->
+ for r: CancellationTokenRegistration in registrations do
+ r.Dispose()
+
+ cancellationRegistrations.Remove key |> ignore
+ | _ -> ()
+
+ let incrRequestCount key =
+ requestCounts[key] <-
+ if requestCounts.ContainsKey key then
+ requestCounts[key] + 1
+ else
+ 1
+
+ let decrRequestCount key =
+ if requestCounts.ContainsKey key then
+ requestCounts[key] <- requestCounts[key] - 1
+
+ let log (eventType, keyData: KeyData<_, _>) =
+ event.Trigger(eventType, (keyData.Label, keyData.Key, keyData.Version))
+
+ let lock = new AsyncLock()
+
+ let processRequest post (key: KeyData<_, _>, msg) diagnosticLogger =
+
+ lock.Do(fun () ->
+ task {
+
+ let cached, otherVersions = cache.GetAll(key.Key, key.Version)
+
+ return
+ match msg, cached with
+ | GetOrCompute _, Some(Completed(result, diags)) ->
+ Interlocked.Increment &hits |> ignore
+ diags |> replayDiagnostics diagnosticLogger
+ Existing(Task.FromResult result)
+ | GetOrCompute(_, ct), Some(Running(tcs, _, _, _, loggers)) ->
+ Interlocked.Increment &hits |> ignore
+ incrRequestCount key
+
+ ct.Register(fun _ ->
+ let _name = name
+ Interlocked.Increment &cancel_ct_registration_subsequent |> ignore
+ post (key, CancelRequest))
+ |> saveRegistration key
+
+ loggers.Add diagnosticLogger
+
+ Existing tcs.Task
+
+ | GetOrCompute(computation, ct), None
+ | GetOrCompute(computation, ct), Some(Job.Canceled _)
+ | GetOrCompute(computation, ct), Some(Job.Failed _) ->
+ Interlocked.Increment &started |> ignore
+ incrRequestCount key
+
+ ct.Register(fun _ ->
+ let _name = name
+ Interlocked.Increment &cancel_ct_registration_original |> ignore
+ post (key, OriginatorCanceled))
+ |> saveRegistration key
+
+ let cts = new CancellationTokenSource()
+
+ cache.Set(
+ key.Key,
+ key.Version,
+ key.Label,
+ (Running(TaskCompletionSource(), cts, computation, DateTime.Now, ResizeArray()))
+ )
+
+ otherVersions
+ |> Seq.choose (function
+ | v, Running(_tcs, cts, _, _, _) -> Some(v, cts)
+ | _ -> None)
+ |> Seq.iter (fun (_v, cts) ->
+ use _ = Activity.start $"{name}: Duplicate running job" [| "key", key.Label |]
+ //System.Diagnostics.Trace.TraceWarning($"{name} Duplicate {key.Label}")
+ if cancelDuplicateRunningJobs then
+ //System.Diagnostics.Trace.TraceWarning("Canceling")
+ cts.Cancel())
+
+ New cts.Token
+ })
+
+ let internalError key message =
+ let ex = exn (message)
+ failures.Add(key, ex)
+ Interlocked.Increment &errors |> ignore
+ // raise ex -- Suppose there's no need to raise here - where does it even go?
+
+ let processStateUpdate post (key: KeyData<_, _>, action: StateUpdate<_>) =
+ task {
+ do! Task.Delay 0
+
+ do!
+ lock.Do(fun () ->
+ task {
+
+ let cached = cache.TryGet(key.Key, key.Version)
+
+ match action, cached with
+
+ | OriginatorCanceled, Some(Running(tcs, cts, computation, _, _)) ->
+
+ Interlocked.Increment &cancel_original_processed |> ignore
+
+ decrRequestCount key
+
+ if requestCounts[key] < 1 then
+ cancelRegistration key
+ cts.Cancel()
+ tcs.TrySetCanceled() |> ignore
+ // Remember the job in case it completes after cancellation
+ cache.Set(key.Key, key.Version, key.Label, Job.Canceled DateTime.Now)
+ requestCounts.Remove key |> ignore
+ log (Canceled, key)
+ Interlocked.Increment &canceled |> ignore
+ use _ = Activity.start $"{name}: Canceled job" [| "key", key.Label |]
+ ()
+
+ else
+ // We need to restart the computation
+ Task.Run(fun () ->
+ Async.StartAsTask(
+ async {
+
+ let cachingLogger = new CachingDiagnosticsLogger(None)
+
+ try
+ // TODO: Should unify starting and restarting
+ use _ = Cancellable.UsingToken(cts.Token)
+ log (Started, key)
+ Interlocked.Increment &restarted |> ignore
+ System.Diagnostics.Trace.TraceInformation $"{name} Restarted {key.Label}"
+ let currentLogger = DiagnosticsThreadStatics.DiagnosticsLogger
+ DiagnosticsThreadStatics.DiagnosticsLogger <- cachingLogger
+
+ try
+ let! result = computation |> Async.AwaitNodeCode
+ post (key, (JobCompleted(result, cachingLogger.CapturedDiagnostics)))
+ return ()
+ finally
+ DiagnosticsThreadStatics.DiagnosticsLogger <- currentLogger
+ with
+ | TaskCancelled _ ->
+ Interlocked.Increment &cancel_exception_subsequent |> ignore
+ post (key, CancelRequest)
+ ()
+ | ex -> post (key, (JobFailed(ex, cachingLogger.CapturedDiagnostics)))
+ }
+ ),
+ cts.Token)
+ |> ignore
+
+ | CancelRequest, Some(Running(tcs, cts, _c, _, _)) ->
+
+ Interlocked.Increment &cancel_subsequent_processed |> ignore
+
+ decrRequestCount key
+
+ if requestCounts[key] < 1 then
+ cancelRegistration key
+ cts.Cancel()
+ tcs.TrySetCanceled() |> ignore
+ // Remember the job in case it completes after cancellation
+ cache.Set(key.Key, key.Version, key.Label, Job.Canceled DateTime.Now)
+ requestCounts.Remove key |> ignore
+ log (Canceled, key)
+ Interlocked.Increment &canceled |> ignore
+ use _ = Activity.start $"{name}: Canceled job" [| "key", key.Label |]
+ ()
+
+ // Probably in some cases cancellation can be fired off even after we just unregistered it
+ | CancelRequest, None
+ | CancelRequest, Some(Completed _)
+ | CancelRequest, Some(Job.Canceled _)
+ | CancelRequest, Some(Job.Failed _)
+ | OriginatorCanceled, None
+ | OriginatorCanceled, Some(Completed _)
+ | OriginatorCanceled, Some(Job.Canceled _)
+ | OriginatorCanceled, Some(Job.Failed _) -> ()
+
+ | JobFailed(ex, diags), Some(Running(tcs, _cts, _c, _ts, loggers)) ->
+ cancelRegistration key
+ cache.Set(key.Key, key.Version, key.Label, Job.Failed(DateTime.Now, ex))
+ requestCounts.Remove key |> ignore
+ log (Failed, key)
+ Interlocked.Increment &failed |> ignore
+ failures.Add(key.Label, ex)
+
+ for logger in loggers do
+ diags |> replayDiagnostics logger
+
+ tcs.TrySetException ex |> ignore
+
+ | JobCompleted(result, diags), Some(Running(tcs, _cts, _c, started, loggers)) ->
+ cancelRegistration key
+ cache.Set(key.Key, key.Version, key.Label, (Completed(result, diags)))
+ requestCounts.Remove key |> ignore
+ log (Finished, key)
+ Interlocked.Increment &completed |> ignore
+ let duration = float (DateTime.Now - started).Milliseconds
+
+ avgDurationMs <-
+ if completed < 2 then
+ duration
+ else
+ avgDurationMs + (duration - avgDurationMs) / float completed
+
+ for logger in loggers do
+ diags |> replayDiagnostics logger
+
+ if tcs.TrySetResult result = false then
+ internalError key.Label "Invalid state: Completed job already completed"
+
+ // Sometimes job can be canceled but it still manages to complete (or fail)
+ | JobFailed _, Some(Job.Canceled _)
+ | JobCompleted _, Some(Job.Canceled _) -> ()
+
+ // Job can't be evicted from cache while it's running because then subsequent requesters would be waiting forever
+ | JobFailed _, None -> internalError key.Label "Invalid state: Running job missing in cache (failed)"
+
+ | JobCompleted _, None -> internalError key.Label "Invalid state: Running job missing in cache (completed)"
+
+ | JobFailed(ex, _diags), Some(Completed(_job, _diags2)) ->
+ internalError key.Label $"Invalid state: Failed Completed job \n%A{ex}"
+
+ | JobCompleted(_result, _diags), Some(Completed(_job, _diags2)) ->
+ internalError key.Label "Invalid state: Double-Completed job"
+
+ | JobFailed(ex, _diags), Some(Job.Failed(_, ex2)) ->
+ internalError key.Label $"Invalid state: Double-Failed job \n%A{ex} \n%A{ex2}"
+
+ | JobCompleted(_result, _diags), Some(Job.Failed(_, ex2)) ->
+ internalError key.Label $"Invalid state: Completed Failed job \n%A{ex2}"
+ })
+ }
+
+ let rec post msg =
+ Task.Run(fun () -> processStateUpdate post msg :> Task) |> ignore
+
+ member this.Get'(key, computation) =
+
+ let wrappedKey =
+ { new ICacheKey<_, _> with
+ member _.GetKey() = key
+ member _.GetVersion() = Unchecked.defaultof<_>
+ member _.GetLabel() = key.ToString()
+ }
+
+ this.Get(wrappedKey, computation)
+
+ member _.Get(key: ICacheKey<_, _>, computation) =
+
+ let key =
+ {
+ Label = key.GetLabel()
+ Key = key.GetKey()
+ Version = key.GetVersion()
+ }
+
+ node {
+ let! ct = NodeCode.CancellationToken
+
+ let callerDiagnosticLogger = DiagnosticsThreadStatics.DiagnosticsLogger
+
+ match!
+ processRequest post (key, GetOrCompute(computation, ct)) callerDiagnosticLogger
+ |> NodeCode.AwaitTask
+ with
+ | New internalCt ->
+
+ let linkedCtSource = CancellationTokenSource.CreateLinkedTokenSource(ct, internalCt)
+ let cachingLogger = new CachingDiagnosticsLogger(Some callerDiagnosticLogger)
+
+ try
+ return!
+ Async.StartAsTask(
+ async {
+ // TODO: Should unify starting and restarting
+ let currentLogger = DiagnosticsThreadStatics.DiagnosticsLogger
+ DiagnosticsThreadStatics.DiagnosticsLogger <- cachingLogger
+ use _ = Cancellable.UsingToken(internalCt)
+ log (Started, key)
+
+ try
+ let! result = computation |> Async.AwaitNodeCode
+ post (key, (JobCompleted(result, cachingLogger.CapturedDiagnostics)))
+ return result
+ finally
+ DiagnosticsThreadStatics.DiagnosticsLogger <- currentLogger
+ },
+ cancellationToken = linkedCtSource.Token
+ )
+ |> NodeCode.AwaitTask
+ with
+ | TaskCancelled ex ->
+ // TODO: do we need to do anything else here? Presumably it should be done by the registration on
+ // the cancellation token or before we triggered our own cancellation
+
+ // Let's send this again just in case. It seems sometimes it's not triggered from the registration?
+
+ Interlocked.Increment &cancel_exception_original |> ignore
+
+ post (key, (OriginatorCanceled))
+ return raise ex
+ | ex ->
+ post (key, (JobFailed(ex, cachingLogger.CapturedDiagnostics)))
+ return raise ex
+
+ | Existing job -> return! job |> NodeCode.AwaitTask
+
+ }
+
+ member _.Clear() = cache.Clear()
+
+ member _.Clear predicate = cache.Clear predicate
+
+ member val Event = event.Publish
+
+ member this.OnEvent = this.Event.Add
+
+ member _.Locked = lock.Semaphore.CurrentCount < 1
+
+ member _.Running =
+ cache.GetValues()
+ |> Seq.filter (function
+ | _, _, Running _ -> true
+ | _ -> false)
+ |> Seq.toArray
+
+ member this.DebuggerDisplay =
+ let locked = if this.Locked then " [LOCKED]" else ""
+
+ let valueStats =
+ cache.GetValues()
+ |> Seq.countBy (function
+ | _, _, Running _ -> "Running"
+ | _, _, Completed _ -> "Completed"
+ | _, _, Job.Canceled _ -> "Canceled"
+ | _, _, Job.Failed _ -> "Failed")
+ |> Map
+
+ let running =
+ valueStats.TryFind "Running"
+ |> Option.map (sprintf " Running: %d ")
+ |> Option.defaultValue ""
+
+ let avgDuration = avgDurationMs |> sprintf "| Avg: %.0f ms"
+
+ let hitRatio =
+ if started > 0 then
+ $" (%.0f{float hits / (float (started + hits)) * 100.0} %%)"
+ else
+ ""
+
+ let stats =
+ [|
+ if errors + failed > 0 then
+ " (_!_) "
+ if errors > 0 then $"| ERRORS: {errors} " else ""
+ if failed > 0 then $"| FAILED: {failed} " else ""
+ $"| hits: {hits}{hitRatio} "
+ if started > 0 then $"| started: {started} " else ""
+ if completed > 0 then $"| completed: {completed} " else ""
+ if canceled > 0 then $"| canceled: {canceled} " else ""
+ if restarted > 0 then $"| restarted: {restarted} " else ""
+ if evicted > 0 then $"| evicted: {evicted} " else ""
+ if collected > 0 then $"| collected: {collected} " else ""
+ if cleared > 0 then $"| cleared: {cleared} " else ""
+ if strengthened > 0 then
+ $"| strengthened: {strengthened} "
+ else
+ ""
+ |]
+ |> String.concat ""
+
+ $"{locked}{running}{cache.DebuggerDisplay} {stats}{avgDuration}"
+
+/// A drop-in replacement for AsyncMemoize that disables caching and just runs the computation every time.
+[]
+type internal AsyncMemoizeDisabled<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'TVersion: equality>
+ (?keepStrongly, ?keepWeakly, ?name: string, ?cancelDuplicateRunningJobs: bool) =
+
+ do ignore (keepStrongly, keepWeakly, name, cancelDuplicateRunningJobs)
+
+ let mutable requests = 0
+
+ member _.Get(_key: ICacheKey<_, _>, computation) =
+ Interlocked.Increment &requests |> ignore
+ computation
+
+ member _.DebuggerDisplay = $"(disabled) requests: {requests}"
diff --git a/src/Compiler/Facilities/AsyncMemoize.fsi b/src/Compiler/Facilities/AsyncMemoize.fsi
new file mode 100644
index 00000000000..a142605ac22
--- /dev/null
+++ b/src/Compiler/Facilities/AsyncMemoize.fsi
@@ -0,0 +1,81 @@
+namespace Internal.Utilities.Collections
+
+open System.Threading.Tasks
+open FSharp.Compiler.BuildGraph
+
+[]
+module internal Utils =
+
+ /// Return file name with one directory above it
+ val shortPath: path: string -> string
+
+ val (|TaskCancelled|_|): ex: exn -> TaskCanceledException option
+
+type internal JobEvent =
+ | Started
+ | Finished
+ | Canceled
+ | Evicted
+ | Collected
+ | Weakened
+ | Strengthened
+ | Failed
+ | Cleared
+
+type internal ICacheKey<'TKey, 'TVersion> =
+
+ abstract GetKey: unit -> 'TKey
+
+ abstract GetLabel: unit -> string
+
+ abstract GetVersion: unit -> 'TVersion
+
+[]
+type Extensions =
+
+ []
+ static member internal WithExtraVersion: cacheKey: ICacheKey<'a, 'b> * extraVersion: 'c -> ICacheKey<'a, ('b * 'c)>
+
+type internal AsyncLock =
+ interface System.IDisposable
+
+ new: unit -> AsyncLock
+
+ member Do: f: (unit -> #Task<'b>) -> Task<'b>
+
+///
+/// A cache/memoization for computations that makes sure that the same computation wil only be computed once even if it's needed
+/// at multiple places/times.
+///
+/// Strongly holds at most one result per key.
+///
+type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'TVersion: equality> =
+
+ /// Maximum number of strongly held results to keep in the cache
+ /// Maximum number of weakly held results to keep in the cache
+ /// Name of the cache - used in tracing messages
+ /// If true, when a job is started, all other jobs with the same key will be canceled.
+ new:
+ ?keepStrongly: int * ?keepWeakly: int * ?name: string * ?cancelDuplicateRunningJobs: bool ->
+ AsyncMemoize<'TKey, 'TVersion, 'TValue>
+
+ member Clear: unit -> unit
+
+ member Clear: predicate: ('TKey -> bool) -> unit
+
+ member Get: key: ICacheKey<'TKey, 'TVersion> * computation: NodeCode<'TValue> -> NodeCode<'TValue>
+
+ member Get': key: 'TKey * computation: NodeCode<'TValue> -> NodeCode<'TValue>
+
+ member Event: IEvent
+
+ member OnEvent: ((JobEvent * (string * 'TKey * 'TVersion) -> unit) -> unit)
+
+/// A drop-in replacement for AsyncMemoize that disables caching and just runs the computation every time.
+type internal AsyncMemoizeDisabled<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'TVersion: equality> =
+
+ new:
+ ?keepStrongly: obj * ?keepWeakly: obj * ?name: string * ?cancelDuplicateRunningJobs: bool ->
+ AsyncMemoizeDisabled<'TKey, 'TVersion, 'TValue>
+
+ member Get: _key: ICacheKey<'a, 'b> * computation: 'c -> 'c
diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs
index 24f6ebf2e6d..08a46d1a25d 100644
--- a/src/Compiler/Facilities/DiagnosticsLogger.fs
+++ b/src/Compiler/Facilities/DiagnosticsLogger.fs
@@ -374,7 +374,7 @@ type CapturingDiagnosticsLogger(nm, ?eagerFormat) =
let errors = diagnostics.ToArray()
errors |> Array.iter diagnosticsLogger.DiagnosticSink
-/// Type holds thread-static globals for use by the compile.
+/// Type holds thread-static globals for use by the compiler.
type internal DiagnosticsThreadStatics =
[]
static val mutable private buildPhase: BuildPhase
diff --git a/src/Compiler/Facilities/Hashing.fs b/src/Compiler/Facilities/Hashing.fs
new file mode 100644
index 00000000000..2dfbb38b7ee
--- /dev/null
+++ b/src/Compiler/Facilities/Hashing.fs
@@ -0,0 +1,81 @@
+namespace Internal.Utilities.Hashing
+
+open System
+open System.Threading
+
+/// Tools for hashing things with MD5 into a string that can be used as a cache key.
+module internal Md5StringHasher =
+
+ let private md5 =
+ new ThreadLocal<_>(fun () -> System.Security.Cryptography.MD5.Create())
+
+ let private computeHash (bytes: byte array) = md5.Value.ComputeHash(bytes)
+
+ let hashString (s: string) =
+ System.Text.Encoding.UTF8.GetBytes(s) |> computeHash
+
+ let empty = String.Empty
+
+ let addBytes (bytes: byte array) (s: string) =
+ let sbytes = s |> hashString
+
+ Array.append sbytes bytes
+ |> computeHash
+ |> System.BitConverter.ToString
+ |> (fun x -> x.Replace("-", ""))
+
+ let addString (s: string) (s2: string) =
+ s |> System.Text.Encoding.UTF8.GetBytes |> addBytes <| s2
+
+ let addSeq<'item> (items: 'item seq) (addItem: 'item -> string -> string) (s: string) =
+ items |> Seq.fold (fun s a -> addItem a s) s
+
+ let addStrings strings = addSeq strings addString
+
+ // If we use this make it an extension method?
+ //let addVersions<'a, 'b when 'a :> ICacheKey<'b, string>> (versions: 'a seq) (s: string) =
+ // versions |> Seq.map (fun x -> x.GetVersion()) |> addStrings <| s
+
+ let addBool (b: bool) (s: string) =
+ b |> BitConverter.GetBytes |> addBytes <| s
+
+ let addDateTime (dt: System.DateTime) (s: string) = dt.Ticks.ToString() |> addString <| s
+
+module internal Md5Hasher =
+
+ let private md5 =
+ new ThreadLocal<_>(fun () -> System.Security.Cryptography.MD5.Create())
+
+ let computeHash (bytes: byte array) = md5.Value.ComputeHash(bytes)
+
+ let empty = Array.empty
+
+ let hashString (s: string) =
+ s |> System.Text.Encoding.UTF8.GetBytes |> computeHash
+
+ let addBytes (bytes: byte array) (s: byte array) =
+
+ Array.append s bytes |> computeHash
+
+ let addString (s: string) (s2: byte array) =
+ s |> System.Text.Encoding.UTF8.GetBytes |> addBytes <| s2
+
+ let addSeq<'item> (items: 'item seq) (addItem: 'item -> byte array -> byte array) (s: byte array) =
+ items |> Seq.fold (fun s a -> addItem a s) s
+
+ let addStrings strings = addSeq strings addString
+ let addBytes' bytes = addSeq bytes addBytes
+
+ // If we use this make it an extension method?
+ //let addVersions<'a, 'b when 'a :> ICacheKey<'b, string>> (versions: 'a seq) (s: string) =
+ // versions |> Seq.map (fun x -> x.GetVersion()) |> addStrings <| s
+
+ let addBool (b: bool) (s: byte array) =
+ b |> BitConverter.GetBytes |> addBytes <| s
+
+ let addDateTime (dt: System.DateTime) (s: byte array) =
+ dt.Ticks |> BitConverter.GetBytes |> addBytes <| s
+
+ let addDateTimes (dts: System.DateTime seq) (s: byte array) = s |> addSeq dts addDateTime
+
+ let toString (bytes: byte array) = bytes |> System.BitConverter.ToString
diff --git a/src/Compiler/Facilities/Hashing.fsi b/src/Compiler/Facilities/Hashing.fsi
new file mode 100644
index 00000000000..121afb29eb2
--- /dev/null
+++ b/src/Compiler/Facilities/Hashing.fsi
@@ -0,0 +1,46 @@
+namespace Internal.Utilities.Hashing
+
+/// Tools for hashing things with MD5 into a string that can be used as a cache key.
+module internal Md5StringHasher =
+
+ val hashString: s: string -> byte array
+
+ val empty: string
+
+ val addBytes: bytes: byte array -> s: string -> string
+
+ val addString: s: string -> s2: string -> string
+
+ val addSeq: items: 'item seq -> addItem: ('item -> string -> string) -> s: string -> string
+
+ val addStrings: strings: string seq -> (string -> string)
+
+ val addBool: b: bool -> s: string -> string
+
+ val addDateTime: dt: System.DateTime -> s: string -> string
+
+module internal Md5Hasher =
+
+ val computeHash: bytes: byte array -> byte array
+
+ val empty: 'a array
+
+ val hashString: s: string -> byte array
+
+ val addBytes: bytes: byte array -> s: byte array -> byte array
+
+ val addString: s: string -> s2: byte array -> byte array
+
+ val addSeq: items: 'item seq -> addItem: ('item -> byte array -> byte array) -> s: byte array -> byte array
+
+ val addStrings: strings: string seq -> (byte array -> byte array)
+
+ val addBytes': bytes: byte array seq -> (byte array -> byte array)
+
+ val addBool: b: bool -> s: byte array -> byte array
+
+ val addDateTime: dt: System.DateTime -> s: byte array -> byte array
+
+ val addDateTimes: dts: System.DateTime seq -> s: byte array -> byte array
+
+ val toString: bytes: byte array -> string
diff --git a/src/Compiler/Facilities/prim-lexing.fs b/src/Compiler/Facilities/prim-lexing.fs
index 5951c8338e4..6b927ef4a96 100644
--- a/src/Compiler/Facilities/prim-lexing.fs
+++ b/src/Compiler/Facilities/prim-lexing.fs
@@ -6,8 +6,12 @@ namespace FSharp.Compiler.Text
open System
open System.IO
+open System.Collections.Immutable
open Internal.Utilities.Library
+open Internal.Utilities.Collections
+open Internal.Utilities.Hashing
+
type ISourceText =
abstract Item: index: int -> char with get
@@ -30,6 +34,11 @@ type ISourceText =
abstract GetSubTextFromRange: range: range -> string
+type ISourceTextNew =
+ inherit ISourceText
+
+ abstract GetChecksum: unit -> System.Collections.Immutable.ImmutableArray
+
[]
type StringText(str: string) =
@@ -67,7 +76,7 @@ type StringText(str: string) =
override _.ToString() = str
- interface ISourceText with
+ interface ISourceTextNew with
member _.Item
with get index = str[index]
@@ -145,9 +154,45 @@ type StringText(str: string) =
let lastLine = sourceText.GetLineString(range.EndLine - 1)
sb.Append(lastLine.Substring(0, range.EndColumn)).ToString()
+ member _.GetChecksum() =
+ str |> Md5Hasher.hashString |> ImmutableArray.Create
+
module SourceText =
let ofString str = StringText(str) :> ISourceText
+
+module SourceTextNew =
+
+ let ofString str = StringText(str) :> ISourceTextNew
+
+ let ofISourceText (sourceText: ISourceText) =
+ { new ISourceTextNew with
+ member _.Item
+ with get index = sourceText[index]
+
+ member _.GetLineString(x) = sourceText.GetLineString(x)
+
+ member _.GetLineCount() = sourceText.GetLineCount()
+
+ member _.GetLastCharacterPosition() = sourceText.GetLastCharacterPosition()
+
+ member _.GetSubTextString(x, y) = sourceText.GetSubTextString(x, y)
+
+ member _.SubTextEquals(x, y) = sourceText.SubTextEquals(x, y)
+
+ member _.Length = sourceText.Length
+
+ member _.ContentEquals(x) = sourceText.ContentEquals(x)
+
+ member _.CopyTo(a, b, c, d) = sourceText.CopyTo(a, b, c, d)
+
+ member _.GetSubTextFromRange(x) = sourceText.GetSubTextFromRange(x)
+
+ member _.GetChecksum() =
+ // TODO: something better...
+ sourceText.ToString() |> Md5Hasher.hashString |> ImmutableArray.Create
+ }
+
// NOTE: the code in this file is a drop-in replacement runtime for Lexing.fs from the FsLexYacc repository
namespace Internal.Utilities.Text.Lexing
diff --git a/src/Compiler/Facilities/prim-lexing.fsi b/src/Compiler/Facilities/prim-lexing.fsi
index 6e5f6da4f25..ff13f96c9e1 100644
--- a/src/Compiler/Facilities/prim-lexing.fsi
+++ b/src/Compiler/Facilities/prim-lexing.fsi
@@ -39,12 +39,23 @@ type ISourceText =
/// Throws an exception when the input range is outside the file boundaries.
abstract GetSubTextFromRange: range: range -> string
+/// Just like ISourceText, but with a checksum. Added as a separate type to avoid breaking changes.
+type ISourceTextNew =
+ inherit ISourceText
+
+ abstract GetChecksum: unit -> System.Collections.Immutable.ImmutableArray
+
/// Functions related to ISourceText objects
module SourceText =
/// Creates an ISourceText object from the given string
val ofString: string -> ISourceText
+module SourceTextNew =
+
+ val ofString: string -> ISourceTextNew
+ val ofISourceText: ISourceText -> ISourceTextNew
+
//
// NOTE: the code in this file is a drop-in replacement runtime for Lexing.fsi from the FsLexYacc repository
// and is referenced by generated code for the three FsLex generated lexers in the F# compiler.
diff --git a/src/Compiler/Service/BackgroundCompiler.fs b/src/Compiler/Service/BackgroundCompiler.fs
new file mode 100644
index 00000000000..514bb8e45c5
--- /dev/null
+++ b/src/Compiler/Service/BackgroundCompiler.fs
@@ -0,0 +1,1708 @@
+namespace FSharp.Compiler.CodeAnalysis
+
+open FSharp.Compiler.Text
+open FSharp.Compiler.BuildGraph
+
+open System
+open System.Diagnostics
+open System.IO
+open System.Reflection
+open System.Reflection.Emit
+open System.Threading
+open Internal.Utilities.Collections
+open Internal.Utilities.Library
+open Internal.Utilities.Library.Extras
+open FSharp.Compiler
+open FSharp.Compiler.AbstractIL
+open FSharp.Compiler.AbstractIL.IL
+open FSharp.Compiler.AbstractIL.ILBinaryReader
+open FSharp.Compiler.AbstractIL.ILDynamicAssemblyWriter
+open FSharp.Compiler.CodeAnalysis
+open FSharp.Compiler.CompilerConfig
+open FSharp.Compiler.CompilerDiagnostics
+open FSharp.Compiler.CompilerImports
+open FSharp.Compiler.CompilerOptions
+open FSharp.Compiler.DependencyManager
+open FSharp.Compiler.Diagnostics
+open FSharp.Compiler.Driver
+open FSharp.Compiler.DiagnosticsLogger
+open FSharp.Compiler.IO
+open FSharp.Compiler.ParseAndCheckInputs
+open FSharp.Compiler.ScriptClosure
+open FSharp.Compiler.Symbols
+open FSharp.Compiler.Syntax
+open FSharp.Compiler.Tokenization
+open FSharp.Compiler.Text
+open FSharp.Compiler.Text.Range
+open FSharp.Compiler.TcGlobals
+open FSharp.Compiler.BuildGraph
+open FSharp.Compiler.CodeAnalysis.ProjectSnapshot
+
+type SourceTextHash = int64
+type CacheStamp = int64
+type FileName = string
+type FilePath = string
+type ProjectPath = string
+type FileVersion = int
+
+type FSharpProjectSnapshot = FSharp.Compiler.CodeAnalysis.ProjectSnapshot.FSharpProjectSnapshot
+
+type internal IBackgroundCompiler =
+
+ /// Type-check the result obtained by parsing. Force the evaluation of the antecedent type checking context if needed.
+ abstract member CheckFileInProject:
+ parseResults: FSharpParseFileResults *
+ fileName: string *
+ fileVersion: int *
+ sourceText: ISourceText *
+ options: FSharpProjectOptions *
+ userOpName: string ->
+ NodeCode
+
+ /// Type-check the result obtained by parsing, but only if the antecedent type checking context is available.
+ abstract member CheckFileInProjectAllowingStaleCachedResults:
+ parseResults: FSharpParseFileResults *
+ fileName: string *
+ fileVersion: int *
+ sourceText: ISourceText *
+ options: FSharpProjectOptions *
+ userOpName: string ->
+ NodeCode
+
+ abstract member ClearCache: options: seq * userOpName: string -> unit
+
+ abstract member ClearCache: projects: ProjectSnapshot.FSharpProjectIdentifier seq * userOpName: string -> unit
+
+ abstract member ClearCaches: unit -> unit
+
+ abstract member DownsizeCaches: unit -> unit
+
+ abstract member FindReferencesInFile:
+ fileName: string *
+ options: FSharpProjectOptions *
+ symbol: FSharp.Compiler.Symbols.FSharpSymbol *
+ canInvalidateProject: bool *
+ userOpName: string ->
+ NodeCode>
+
+ abstract member FindReferencesInFile:
+ fileName: string * projectSnapshot: FSharpProjectSnapshot * symbol: FSharp.Compiler.Symbols.FSharpSymbol * userOpName: string ->
+ NodeCode>
+
+ abstract member GetAssemblyData:
+ options: FSharpProjectOptions * outputFileName: string * userOpName: string ->
+ NodeCode
+
+ abstract member GetAssemblyData:
+ projectSnapshot: FSharpProjectSnapshot * outputFileName: string * userOpName: string ->
+ NodeCode
+
+ /// Fetch the check information from the background compiler (which checks w.r.t. the FileSystem API)
+ abstract member GetBackgroundCheckResultsForFileInProject:
+ fileName: string * options: FSharpProjectOptions * userOpName: string -> NodeCode
+
+ /// Fetch the parse information from the background compiler (which checks w.r.t. the FileSystem API)
+ abstract member GetBackgroundParseResultsForFileInProject:
+ fileName: string * options: FSharpProjectOptions * userOpName: string -> NodeCode
+
+ abstract member GetCachedCheckFileResult:
+ builder: IncrementalBuilder * fileName: string * sourceText: ISourceText * options: FSharpProjectOptions ->
+ NodeCode<(FSharpParseFileResults * FSharpCheckFileResults) option>
+
+ abstract member GetProjectOptionsFromScript:
+ fileName: string *
+ sourceText: ISourceText *
+ previewEnabled: bool option *
+ loadedTimeStamp: System.DateTime option *
+ otherFlags: string array option *
+ useFsiAuxLib: bool option *
+ useSdkRefs: bool option *
+ sdkDirOverride: string option *
+ assumeDotNetFramework: bool option *
+ optionsStamp: int64 option *
+ userOpName: string ->
+ Async
+
+ abstract member GetSemanticClassificationForFile:
+ fileName: string * options: FSharpProjectOptions * userOpName: string ->
+ NodeCode
+
+ abstract member GetSemanticClassificationForFile:
+ fileName: string * snapshot: FSharpProjectSnapshot * userOpName: string ->
+ NodeCode
+
+ abstract member InvalidateConfiguration: options: FSharpProjectOptions * userOpName: string -> unit
+
+ abstract member NotifyFileChanged: fileName: string * options: FSharpProjectOptions * userOpName: string -> NodeCode
+
+ abstract member NotifyProjectCleaned: options: FSharpProjectOptions * userOpName: string -> Async
+
+ /// Parses and checks the source file and returns untyped AST and check results.
+ abstract member ParseAndCheckFileInProject:
+ fileName: string * fileVersion: int * sourceText: ISourceText * options: FSharpProjectOptions * userOpName: string ->
+ NodeCode
+
+ abstract member ParseAndCheckFileInProject:
+ fileName: string * projectSnapshot: FSharpProjectSnapshot * userOpName: string ->
+ NodeCode
+
+ /// Parse and typecheck the whole project.
+ abstract member ParseAndCheckProject: options: FSharpProjectOptions * userOpName: string -> NodeCode
+
+ abstract member ParseAndCheckProject: projectSnapshot: FSharpProjectSnapshot * userOpName: string -> NodeCode
+
+ abstract member ParseFile:
+ fileName: string * sourceText: ISourceText * options: FSharpParsingOptions * cache: bool * flatErrors: bool * userOpName: string ->
+ Async
+
+ abstract member ParseFile:
+ fileName: string * projectSnapshot: FSharpProjectSnapshot * userOpName: string -> Async
+
+ /// Try to get recent approximate type check results for a file.
+ abstract member TryGetRecentCheckResultsForFile:
+ fileName: string * options: FSharpProjectOptions * sourceText: ISourceText option * userOpName: string ->
+ (FSharpParseFileResults * FSharpCheckFileResults * SourceTextHash) option
+
+ abstract member BeforeBackgroundFileCheck: IEvent
+
+ abstract member FileChecked: IEvent
+
+ abstract member FileParsed: IEvent
+
+ abstract member FrameworkImportsCache: FrameworkImportsCache
+
+ abstract member ProjectChecked: IEvent
+
+type internal ParseCacheLockToken() =
+ interface LockToken
+
+type CheckFileCacheKey = FileName * SourceTextHash * FSharpProjectOptions
+type CheckFileCacheValue = FSharpParseFileResults * FSharpCheckFileResults * SourceTextHash * DateTime
+
+[]
+module internal EnvMisc =
+ let braceMatchCacheSize = GetEnvInteger "FCS_BraceMatchCacheSize" 5
+ let parseFileCacheSize = GetEnvInteger "FCS_ParseFileCacheSize" 2
+ let checkFileInProjectCacheSize = GetEnvInteger "FCS_CheckFileInProjectCacheSize" 10
+
+ let projectCacheSizeDefault = GetEnvInteger "FCS_ProjectCacheSizeDefault" 3
+
+ let frameworkTcImportsCacheStrongSize =
+ GetEnvInteger "FCS_frameworkTcImportsCacheStrongSizeDefault" 8
+
+[]
+module internal Helpers =
+
+ /// Determine whether two (fileName,options) keys are identical w.r.t. affect on checking
+ let AreSameForChecking2 ((fileName1: string, options1: FSharpProjectOptions), (fileName2, options2)) =
+ (fileName1 = fileName2)
+ && FSharpProjectOptions.AreSameForChecking(options1, options2)
+
+ /// Determine whether two (fileName,options) keys should be identical w.r.t. resource usage
+ let AreSubsumable2 ((fileName1: string, o1: FSharpProjectOptions), (fileName2: string, o2: FSharpProjectOptions)) =
+ (fileName1 = fileName2) && FSharpProjectOptions.UseSameProject(o1, o2)
+
+ /// Determine whether two (fileName,sourceText,options) keys should be identical w.r.t. parsing
+ let AreSameForParsing ((fileName1: string, source1Hash: int64, options1), (fileName2, source2Hash, options2)) =
+ fileName1 = fileName2 && options1 = options2 && source1Hash = source2Hash
+
+ let AreSimilarForParsing ((fileName1, _, _), (fileName2, _, _)) = fileName1 = fileName2
+
+ /// Determine whether two (fileName,sourceText,options) keys should be identical w.r.t. checking
+ let AreSameForChecking3 ((fileName1: string, source1Hash: int64, options1: FSharpProjectOptions), (fileName2, source2Hash, options2)) =
+ (fileName1 = fileName2)
+ && FSharpProjectOptions.AreSameForChecking(options1, options2)
+ && source1Hash = source2Hash
+
+ /// Determine whether two (fileName,sourceText,options) keys should be identical w.r.t. resource usage
+ let AreSubsumable3 ((fileName1: string, _, o1: FSharpProjectOptions), (fileName2: string, _, o2: FSharpProjectOptions)) =
+ (fileName1 = fileName2) && FSharpProjectOptions.UseSameProject(o1, o2)
+
+ /// If a symbol is an attribute check if given set of names contains its name without the Attribute suffix
+ let rec NamesContainAttribute (symbol: FSharpSymbol) names =
+ match symbol with
+ | :? FSharpMemberOrFunctionOrValue as mofov ->
+ mofov.DeclaringEntity
+ |> Option.map (fun entity -> NamesContainAttribute entity names)
+ |> Option.defaultValue false
+ | :? FSharpEntity as entity when entity.IsAttributeType && symbol.DisplayNameCore.EndsWithOrdinal "Attribute" ->
+ let nameWithoutAttribute = String.dropSuffix symbol.DisplayNameCore "Attribute"
+ names |> Set.contains nameWithoutAttribute
+ | _ -> false
+
+// There is only one instance of this type, held in FSharpChecker
+type internal BackgroundCompiler
+ (
+ legacyReferenceResolver,
+ projectCacheSize,
+ keepAssemblyContents,
+ keepAllBackgroundResolutions,
+ tryGetMetadataSnapshot,
+ suggestNamesForErrors,
+ keepAllBackgroundSymbolUses,
+ enableBackgroundItemKeyStoreAndSemanticClassification,
+ enablePartialTypeChecking,
+ parallelReferenceResolution,
+ captureIdentifiersWhenParsing,
+ getSource: (string -> Async) option,
+ useChangeNotifications,
+ useSyntaxTreeCache
+ ) as self =
+
+ let beforeFileChecked = Event()
+ let fileParsed = Event()
+ let fileChecked = Event()
+ let projectChecked = Event()
+
+ // STATIC ROOT: FSharpLanguageServiceTestable.FSharpChecker.backgroundCompiler.scriptClosureCache
+ /// Information about the derived script closure.
+ let scriptClosureCache =
+ MruCache(
+ projectCacheSize,
+ areSame = FSharpProjectOptions.AreSameForChecking,
+ areSimilar = FSharpProjectOptions.UseSameProject
+ )
+
+ let frameworkTcImportsCache =
+ FrameworkImportsCache(frameworkTcImportsCacheStrongSize)
+
+ // We currently share one global dependency provider for all scripts for the FSharpChecker.
+ // For projects, one is used per project.
+ //
+ // Sharing one for all scripts is necessary for good performance from GetProjectOptionsFromScript,
+ // which requires a dependency provider to process through the project options prior to working out
+ // if the cached incremental builder can be used for the project.
+ let dependencyProviderForScripts = new DependencyProvider()
+
+ let getProjectReferences (options: FSharpProjectOptions) userOpName =
+ [
+ for r in options.ReferencedProjects do
+
+ match r with
+ | FSharpReferencedProject.FSharpReference(nm, opts) ->
+ // Don't use cross-project references for FSharp.Core, since various bits of code
+ // require a concrete FSharp.Core to exist on-disk. The only solutions that have
+ // these cross-project references to FSharp.Core are VisualFSharp.sln and FSharp.sln. The ramification
+ // of this is that you need to build FSharp.Core to get intellisense in those projects.
+
+ if
+ (try
+ Path.GetFileNameWithoutExtension(nm)
+ with _ ->
+ "")
+ <> GetFSharpCoreLibraryName()
+ then
+ { new IProjectReference with
+ member x.EvaluateRawContents() =
+ node {
+ Trace.TraceInformation("FCS: {0}.{1} ({2})", userOpName, "GetAssemblyData", nm)
+ return! self.GetAssemblyData(opts, userOpName + ".CheckReferencedProject(" + nm + ")")
+ }
+
+ member x.TryGetLogicalTimeStamp(cache) =
+ self.TryGetLogicalTimeStampForProject(cache, opts)
+
+ member x.FileName = nm
+ }
+
+ | FSharpReferencedProject.PEReference(getStamp, delayedReader) ->
+ { new IProjectReference with
+ member x.EvaluateRawContents() =
+ node {
+ let! ilReaderOpt = delayedReader.TryGetILModuleReader() |> NodeCode.FromCancellable
+
+ match ilReaderOpt with
+ | Some ilReader ->
+ let ilModuleDef, ilAsmRefs = ilReader.ILModuleDef, ilReader.ILAssemblyRefs
+ let data = RawFSharpAssemblyData(ilModuleDef, ilAsmRefs) :> IRawFSharpAssemblyData
+ return ProjectAssemblyDataResult.Available data
+ | _ ->
+ // Note 'false' - if a PEReference doesn't find an ILModuleReader then we don't
+ // continue to try to use an on-disk DLL
+ return ProjectAssemblyDataResult.Unavailable false
+ }
+
+ member x.TryGetLogicalTimeStamp _ = getStamp () |> Some
+ member x.FileName = delayedReader.OutputFile
+ }
+
+ | FSharpReferencedProject.ILModuleReference(nm, getStamp, getReader) ->
+ { new IProjectReference with
+ member x.EvaluateRawContents() =
+ cancellable {
+ let ilReader = getReader ()
+ let ilModuleDef, ilAsmRefs = ilReader.ILModuleDef, ilReader.ILAssemblyRefs
+ let data = RawFSharpAssemblyData(ilModuleDef, ilAsmRefs) :> IRawFSharpAssemblyData
+ return ProjectAssemblyDataResult.Available data
+ }
+ |> NodeCode.FromCancellable
+
+ member x.TryGetLogicalTimeStamp _ = getStamp () |> Some
+ member x.FileName = nm
+ }
+ ]
+
+ /// CreateOneIncrementalBuilder (for background type checking). Note that fsc.fs also
+ /// creates an incremental builder used by the command line compiler.
+ let CreateOneIncrementalBuilder (options: FSharpProjectOptions, userOpName) =
+ node {
+ use _ =
+ Activity.start "BackgroundCompiler.CreateOneIncrementalBuilder" [| Activity.Tags.project, options.ProjectFileName |]
+
+ Trace.TraceInformation("FCS: {0}.{1} ({2})", userOpName, "CreateOneIncrementalBuilder", options.ProjectFileName)
+ let projectReferences = getProjectReferences options userOpName
+
+ let loadClosure = scriptClosureCache.TryGet(AnyCallerThread, options)
+
+ let dependencyProvider =
+ if options.UseScriptResolutionRules then
+ Some dependencyProviderForScripts
+ else
+ None
+
+ let! builderOpt, diagnostics =
+ IncrementalBuilder.TryCreateIncrementalBuilderForProjectOptions(
+ legacyReferenceResolver,
+ FSharpCheckerResultsSettings.defaultFSharpBinariesDir,
+ frameworkTcImportsCache,
+ loadClosure,
+ Array.toList options.SourceFiles,
+ Array.toList options.OtherOptions,
+ projectReferences,
+ options.ProjectDirectory,
+ options.UseScriptResolutionRules,
+ keepAssemblyContents,
+ keepAllBackgroundResolutions,
+ tryGetMetadataSnapshot,
+ suggestNamesForErrors,
+ keepAllBackgroundSymbolUses,
+ enableBackgroundItemKeyStoreAndSemanticClassification,
+ enablePartialTypeChecking,
+ dependencyProvider,
+ parallelReferenceResolution,
+ captureIdentifiersWhenParsing,
+ getSource,
+ useChangeNotifications,
+ useSyntaxTreeCache
+ )
+
+ match builderOpt with
+ | None -> ()
+ | Some builder ->
+
+#if !NO_TYPEPROVIDERS
+ // Register the behaviour that responds to CCUs being invalidated because of type
+ // provider Invalidate events. This invalidates the configuration in the build.
+ builder.ImportsInvalidatedByTypeProvider.Add(fun () -> self.InvalidateConfiguration(options, userOpName))
+#endif
+
+ // Register the callback called just before a file is typechecked by the background builder (without recording
+ // errors or intellisense information).
+ //
+ // This indicates to the UI that the file type check state is dirty. If the file is open and visible then
+ // the UI will sooner or later request a typecheck of the file, recording errors and intellisense information.
+ builder.BeforeFileChecked.Add(fun file -> beforeFileChecked.Trigger(file, options))
+ builder.FileParsed.Add(fun file -> fileParsed.Trigger(file, options))
+ builder.FileChecked.Add(fun file -> fileChecked.Trigger(file, options))
+ builder.ProjectChecked.Add(fun () -> projectChecked.Trigger options)
+
+ return (builderOpt, diagnostics)
+ }
+
+ let parseCacheLock = Lock()
+
+ // STATIC ROOT: FSharpLanguageServiceTestable.FSharpChecker.parseFileInProjectCache. Most recently used cache for parsing files.
+ let parseFileCache =
+ MruCache(
+ parseFileCacheSize,
+ areSimilar = AreSimilarForParsing,
+ areSame = AreSameForParsing
+ )
+
+ // STATIC ROOT: FSharpLanguageServiceTestable.FSharpChecker.checkFileInProjectCache
+ //
+ /// Cache which holds recently seen type-checks.
+ /// This cache may hold out-of-date entries, in two senses
+ /// - there may be a more recent antecedent state available because the background build has made it available
+ /// - the source for the file may have changed
+
+ // Also keyed on source. This can only be out of date if the antecedent is out of date
+ let checkFileInProjectCache =
+ MruCache>(
+ keepStrongly = checkFileInProjectCacheSize,
+ areSame = AreSameForChecking3,
+ areSimilar = AreSubsumable3
+ )
+
+ // STATIC ROOT: FSharpLanguageServiceTestable.FSharpChecker.backgroundCompiler.incrementalBuildersCache. This root typically holds more
+ // live information than anything else in the F# Language Service, since it holds up to 3 (projectCacheStrongSize) background project builds
+ // strongly.
+ //
+ /// Cache of builds keyed by options.
+ let gate = obj ()
+
+ let incrementalBuildersCache =
+ MruCache>(
+ keepStrongly = projectCacheSize,
+ keepMax = projectCacheSize,
+ areSame = FSharpProjectOptions.AreSameForChecking,
+ areSimilar = FSharpProjectOptions.UseSameProject
+ )
+
+ let tryGetBuilderNode options =
+ incrementalBuildersCache.TryGet(AnyCallerThread, options)
+
+ let tryGetBuilder options : NodeCode option =
+ tryGetBuilderNode options |> Option.map (fun x -> x.GetOrComputeValue())
+
+ let tryGetSimilarBuilder options : NodeCode option =
+ incrementalBuildersCache.TryGetSimilar(AnyCallerThread, options)
+ |> Option.map (fun x -> x.GetOrComputeValue())
+
+ let tryGetAnyBuilder options : NodeCode option =
+ incrementalBuildersCache.TryGetAny(AnyCallerThread, options)
+ |> Option.map (fun x -> x.GetOrComputeValue())
+
+ let createBuilderNode (options, userOpName, ct: CancellationToken) =
+ lock gate (fun () ->
+ if ct.IsCancellationRequested then
+ GraphNode.FromResult(None, [||])
+ else
+ let getBuilderNode = GraphNode(CreateOneIncrementalBuilder(options, userOpName))
+ incrementalBuildersCache.Set(AnyCallerThread, options, getBuilderNode)
+ getBuilderNode)
+
+ let createAndGetBuilder (options, userOpName) =
+ node {
+ let! ct = NodeCode.CancellationToken
+ let getBuilderNode = createBuilderNode (options, userOpName, ct)
+ return! getBuilderNode.GetOrComputeValue()
+ }
+
+ let getOrCreateBuilder (options, userOpName) : NodeCode =
+ match tryGetBuilder options with
+ | Some getBuilder ->
+ node {
+ match! getBuilder with
+ | builderOpt, creationDiags when builderOpt.IsNone || not builderOpt.Value.IsReferencesInvalidated ->
+ return builderOpt, creationDiags
+ | _ ->
+ // The builder could be re-created,
+ // clear the check file caches that are associated with it.
+ // We must do this in order to not return stale results when references
+ // in the project get changed/added/removed.
+ parseCacheLock.AcquireLock(fun ltok ->
+ options.SourceFiles
+ |> Array.iter (fun sourceFile ->
+ let key = (sourceFile, 0L, options)
+ checkFileInProjectCache.RemoveAnySimilar(ltok, key)))
+
+ return! createAndGetBuilder (options, userOpName)
+ }
+ | _ -> createAndGetBuilder (options, userOpName)
+
+ let getSimilarOrCreateBuilder (options, userOpName) =
+ match tryGetSimilarBuilder options with
+ | Some res -> res
+ // The builder does not exist at all. Create it.
+ | None -> getOrCreateBuilder (options, userOpName)
+
+ let getOrCreateBuilderWithInvalidationFlag (options, canInvalidateProject, userOpName) =
+ if canInvalidateProject then
+ getOrCreateBuilder (options, userOpName)
+ else
+ getSimilarOrCreateBuilder (options, userOpName)
+
+ let getAnyBuilder (options, userOpName) =
+ match tryGetAnyBuilder options with
+ | Some getBuilder -> getBuilder
+ | _ -> getOrCreateBuilder (options, userOpName)
+
+ static let mutable actualParseFileCount = 0
+
+ static let mutable actualCheckFileCount = 0
+
+ /// Should be a fast operation. Ensures that we have only one async lazy object per file and its hash.
+ let getCheckFileNode (parseResults, sourceText, fileName, options, _fileVersion, builder, tcPrior, tcInfo, creationDiags) =
+
+ // Here we lock for the creation of the node, not its execution
+ parseCacheLock.AcquireLock(fun ltok ->
+ let key = (fileName, sourceText.GetHashCode() |> int64, options)
+
+ match checkFileInProjectCache.TryGet(ltok, key) with
+ | Some res -> res
+ | _ ->
+ let res =
+ GraphNode(
+ node {
+ let! res =
+ self.CheckOneFileImplAux(
+ parseResults,
+ sourceText,
+ fileName,
+ options,
+ builder,
+ tcPrior,
+ tcInfo,
+ creationDiags
+ )
+
+ Interlocked.Increment(&actualCheckFileCount) |> ignore
+ return res
+ }
+ )
+
+ checkFileInProjectCache.Set(ltok, key, res)
+ res)
+
+ member _.ParseFile
+ (
+ fileName: string,
+ sourceText: ISourceText,
+ options: FSharpParsingOptions,
+ cache: bool,
+ flatErrors: bool,
+ userOpName: string
+ ) =
+ async {
+ use _ =
+ Activity.start
+ "BackgroundCompiler.ParseFile"
+ [|
+ Activity.Tags.fileName, fileName
+ Activity.Tags.userOpName, userOpName
+ Activity.Tags.cache, cache.ToString()
+ |]
+
+ let! ct = Async.CancellationToken
+ use _ = Cancellable.UsingToken(ct)
+
+ if cache then
+ let hash = sourceText.GetHashCode() |> int64
+
+ match parseCacheLock.AcquireLock(fun ltok -> parseFileCache.TryGet(ltok, (fileName, hash, options))) with
+ | Some res -> return res
+ | None ->
+ Interlocked.Increment(&actualParseFileCount) |> ignore
+ let! ct = Async.CancellationToken
+
+ let parseDiagnostics, parseTree, anyErrors =
+ ParseAndCheckFile.parseFile (
+ sourceText,
+ fileName,
+ options,
+ userOpName,
+ suggestNamesForErrors,
+ flatErrors,
+ captureIdentifiersWhenParsing,
+ ct
+ )
+
+ let res =
+ FSharpParseFileResults(parseDiagnostics, parseTree, anyErrors, options.SourceFiles)
+
+ parseCacheLock.AcquireLock(fun ltok -> parseFileCache.Set(ltok, (fileName, hash, options), res))
+ return res
+ else
+ let! ct = Async.CancellationToken
+
+ let parseDiagnostics, parseTree, anyErrors =
+ ParseAndCheckFile.parseFile (
+ sourceText,
+ fileName,
+ options,
+ userOpName,
+ false,
+ flatErrors,
+ captureIdentifiersWhenParsing,
+ ct
+ )
+
+ return FSharpParseFileResults(parseDiagnostics, parseTree, anyErrors, options.SourceFiles)
+ }
+
+ /// Fetch the parse information from the background compiler (which checks w.r.t. the FileSystem API)
+ member _.GetBackgroundParseResultsForFileInProject(fileName, options, userOpName) =
+ node {
+ use _ =
+ Activity.start
+ "BackgroundCompiler.GetBackgroundParseResultsForFileInProject"
+ [| Activity.Tags.fileName, fileName; Activity.Tags.userOpName, userOpName |]
+
+ let! ct = NodeCode.CancellationToken
+ use _ = Cancellable.UsingToken(ct)
+
+ let! builderOpt, creationDiags = getOrCreateBuilder (options, userOpName)
+
+ match builderOpt with
+ | None ->
+ let parseTree = EmptyParsedInput(fileName, (false, false))
+ return FSharpParseFileResults(creationDiags, parseTree, true, [||])
+ | Some builder ->
+ let parseTree, _, _, parseDiagnostics = builder.GetParseResultsForFile fileName
+
+ let parseDiagnostics =
+ DiagnosticHelpers.CreateDiagnostics(
+ builder.TcConfig.diagnosticsOptions,
+ false,
+ fileName,
+ parseDiagnostics,
+ suggestNamesForErrors,
+ builder.TcConfig.flatErrors,
+ None
+ )
+
+ let diagnostics = [| yield! creationDiags; yield! parseDiagnostics |]
+
+ let parseResults =
+ FSharpParseFileResults(
+ diagnostics = diagnostics,
+ input = parseTree,
+ parseHadErrors = false,
+ dependencyFiles = builder.AllDependenciesDeprecated
+ )
+
+ return parseResults
+ }
+
+ member _.GetCachedCheckFileResult(builder: IncrementalBuilder, fileName, sourceText: ISourceText, options) =
+ node {
+ use _ =
+ Activity.start "BackgroundCompiler.GetCachedCheckFileResult" [| Activity.Tags.fileName, fileName |]
+
+ let hash = sourceText.GetHashCode() |> int64
+ let key = (fileName, hash, options)
+
+ let cachedResultsOpt =
+ parseCacheLock.AcquireLock(fun ltok -> checkFileInProjectCache.TryGet(ltok, key))
+
+ match cachedResultsOpt with
+ | Some cachedResults ->
+ match! cachedResults.GetOrComputeValue() with
+ | parseResults, checkResults, _, priorTimeStamp when
+ (match builder.GetCheckResultsBeforeFileInProjectEvenIfStale fileName with
+ | None -> false
+ | Some(tcPrior) ->
+ tcPrior.ProjectTimeStamp = priorTimeStamp
+ && builder.AreCheckResultsBeforeFileInProjectReady(fileName))
+ ->
+ return Some(parseResults, checkResults)
+ | _ ->
+ parseCacheLock.AcquireLock(fun ltok -> checkFileInProjectCache.RemoveAnySimilar(ltok, key))
+ return None
+ | _ -> return None
+ }
+
+ member private _.CheckOneFileImplAux
+ (
+ parseResults: FSharpParseFileResults,
+ sourceText: ISourceText,
+ fileName: string,
+ options: FSharpProjectOptions,
+ builder: IncrementalBuilder,
+ tcPrior: PartialCheckResults,
+ tcInfo: TcInfo,
+ creationDiags: FSharpDiagnostic[]
+ ) : NodeCode =
+
+ node {
+ // Get additional script #load closure information if applicable.
+ // For scripts, this will have been recorded by GetProjectOptionsFromScript.
+ let tcConfig = tcPrior.TcConfig
+ let loadClosure = scriptClosureCache.TryGet(AnyCallerThread, options)
+
+ let! checkAnswer =
+ FSharpCheckFileResults.CheckOneFile(
+ parseResults,
+ sourceText,
+ fileName,
+ options.ProjectFileName,
+ tcConfig,
+ tcPrior.TcGlobals,
+ tcPrior.TcImports,
+ tcInfo.tcState,
+ tcInfo.moduleNamesDict,
+ loadClosure,
+ tcInfo.TcDiagnostics,
+ options.IsIncompleteTypeCheckEnvironment,
+ options,
+ Some builder,
+ Array.ofList tcInfo.tcDependencyFiles,
+ creationDiags,
+ parseResults.Diagnostics,
+ keepAssemblyContents,
+ suggestNamesForErrors
+ )
+ |> NodeCode.FromCancellable
+
+ GraphNode.SetPreferredUILang tcConfig.preferredUiLang
+ return (parseResults, checkAnswer, sourceText.GetHashCode() |> int64, tcPrior.ProjectTimeStamp)
+ }
+
+ member private bc.CheckOneFileImpl
+ (
+ parseResults: FSharpParseFileResults,
+ sourceText: ISourceText,
+ fileName: string,
+ options: FSharpProjectOptions,
+ fileVersion: int,
+ builder: IncrementalBuilder,
+ tcPrior: PartialCheckResults,
+ tcInfo: TcInfo,
+ creationDiags: FSharpDiagnostic[]
+ ) =
+
+ node {
+ match! bc.GetCachedCheckFileResult(builder, fileName, sourceText, options) with
+ | Some(_, results) -> return FSharpCheckFileAnswer.Succeeded results
+ | _ ->
+ let lazyCheckFile =
+ getCheckFileNode (parseResults, sourceText, fileName, options, fileVersion, builder, tcPrior, tcInfo, creationDiags)
+
+ let! _, results, _, _ = lazyCheckFile.GetOrComputeValue()
+ return FSharpCheckFileAnswer.Succeeded results
+ }
+
+ /// Type-check the result obtained by parsing, but only if the antecedent type checking context is available.
+ member bc.CheckFileInProjectAllowingStaleCachedResults
+ (
+ parseResults: FSharpParseFileResults,
+ fileName,
+ fileVersion,
+ sourceText: ISourceText,
+ options,
+ userOpName
+ ) =
+ node {
+ use _ =
+ Activity.start
+ "BackgroundCompiler.CheckFileInProjectAllowingStaleCachedResults"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.fileName, fileName
+ Activity.Tags.userOpName, userOpName
+ |]
+
+ let! ct = NodeCode.CancellationToken
+ use _ = Cancellable.UsingToken(ct)
+
+ let! cachedResults =
+ node {
+ let! builderOpt, creationDiags = getAnyBuilder (options, userOpName)
+
+ match builderOpt with
+ | Some builder ->
+ match! bc.GetCachedCheckFileResult(builder, fileName, sourceText, options) with
+ | Some(_, checkResults) -> return Some(builder, creationDiags, Some(FSharpCheckFileAnswer.Succeeded checkResults))
+ | _ -> return Some(builder, creationDiags, None)
+ | _ -> return None // the builder wasn't ready
+ }
+
+ match cachedResults with
+ | None -> return None
+ | Some(_, _, Some x) -> return Some x
+ | Some(builder, creationDiags, None) ->
+ Trace.TraceInformation("FCS: {0}.{1} ({2})", userOpName, "CheckFileInProjectAllowingStaleCachedResults.CacheMiss", fileName)
+
+ match builder.GetCheckResultsBeforeFileInProjectEvenIfStale fileName with
+ | Some tcPrior ->
+ match tcPrior.TryPeekTcInfo() with
+ | Some tcInfo ->
+ let! checkResults =
+ bc.CheckOneFileImpl(
+ parseResults,
+ sourceText,
+ fileName,
+ options,
+ fileVersion,
+ builder,
+ tcPrior,
+ tcInfo,
+ creationDiags
+ )
+
+ return Some checkResults
+ | None -> return None
+ | None -> return None // the incremental builder was not up to date
+ }
+
+ /// Type-check the result obtained by parsing. Force the evaluation of the antecedent type checking context if needed.
+ member bc.CheckFileInProject
+ (
+ parseResults: FSharpParseFileResults,
+ fileName,
+ fileVersion,
+ sourceText: ISourceText,
+ options,
+ userOpName
+ ) =
+ node {
+ use _ =
+ Activity.start
+ "BackgroundCompiler.CheckFileInProject"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.fileName, fileName
+ Activity.Tags.userOpName, userOpName
+ |]
+
+ let! ct = NodeCode.CancellationToken
+ use _ = Cancellable.UsingToken(ct)
+
+ let! builderOpt, creationDiags = getOrCreateBuilder (options, userOpName)
+
+ match builderOpt with
+ | None ->
+ return FSharpCheckFileAnswer.Succeeded(FSharpCheckFileResults.MakeEmpty(fileName, creationDiags, keepAssemblyContents))
+ | Some builder ->
+ // Check the cache. We can only use cached results when there is no work to do to bring the background builder up-to-date
+ let! cachedResults = bc.GetCachedCheckFileResult(builder, fileName, sourceText, options)
+
+ match cachedResults with
+ | Some(_, checkResults) -> return FSharpCheckFileAnswer.Succeeded checkResults
+ | _ ->
+ let! tcPrior = builder.GetCheckResultsBeforeFileInProject fileName
+ let! tcInfo = tcPrior.GetOrComputeTcInfo()
+
+ return!
+ bc.CheckOneFileImpl(
+ parseResults,
+ sourceText,
+ fileName,
+ options,
+ fileVersion,
+ builder,
+ tcPrior,
+ tcInfo,
+ creationDiags
+ )
+ }
+
+ /// Parses and checks the source file and returns untyped AST and check results.
+ member bc.ParseAndCheckFileInProject
+ (
+ fileName: string,
+ fileVersion,
+ sourceText: ISourceText,
+ options: FSharpProjectOptions,
+ userOpName
+ ) =
+ node {
+ use _ =
+ Activity.start
+ "BackgroundCompiler.ParseAndCheckFileInProject"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.fileName, fileName
+ Activity.Tags.userOpName, userOpName
+ |]
+
+ let! ct = NodeCode.CancellationToken
+ use _ = Cancellable.UsingToken(ct)
+
+ let! builderOpt, creationDiags = getOrCreateBuilder (options, userOpName)
+
+ match builderOpt with
+ | None ->
+ let parseTree = EmptyParsedInput(fileName, (false, false))
+ let parseResults = FSharpParseFileResults(creationDiags, parseTree, true, [||])
+ return (parseResults, FSharpCheckFileAnswer.Aborted)
+
+ | Some builder ->
+ let! cachedResults = bc.GetCachedCheckFileResult(builder, fileName, sourceText, options)
+
+ match cachedResults with
+ | Some(parseResults, checkResults) -> return (parseResults, FSharpCheckFileAnswer.Succeeded checkResults)
+ | _ ->
+ let! tcPrior = builder.GetCheckResultsBeforeFileInProject fileName
+ let! tcInfo = tcPrior.GetOrComputeTcInfo()
+ // Do the parsing.
+ let parsingOptions =
+ FSharpParsingOptions.FromTcConfig(
+ builder.TcConfig,
+ Array.ofList builder.SourceFiles,
+ options.UseScriptResolutionRules
+ )
+
+ GraphNode.SetPreferredUILang tcPrior.TcConfig.preferredUiLang
+ let! ct = NodeCode.CancellationToken
+
+ let parseDiagnostics, parseTree, anyErrors =
+ ParseAndCheckFile.parseFile (
+ sourceText,
+ fileName,
+ parsingOptions,
+ userOpName,
+ suggestNamesForErrors,
+ builder.TcConfig.flatErrors,
+ captureIdentifiersWhenParsing,
+ ct
+ )
+
+ let parseResults =
+ FSharpParseFileResults(parseDiagnostics, parseTree, anyErrors, builder.AllDependenciesDeprecated)
+
+ let! checkResults =
+ bc.CheckOneFileImpl(
+ parseResults,
+ sourceText,
+ fileName,
+ options,
+ fileVersion,
+ builder,
+ tcPrior,
+ tcInfo,
+ creationDiags
+ )
+
+ return (parseResults, checkResults)
+ }
+
+ member _.NotifyFileChanged(fileName, options, userOpName) =
+ node {
+ use _ =
+ Activity.start
+ "BackgroundCompiler.NotifyFileChanged"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.fileName, fileName
+ Activity.Tags.userOpName, userOpName
+ |]
+
+ let! ct = NodeCode.CancellationToken
+ use _ = Cancellable.UsingToken(ct)
+
+ let! builderOpt, _ = getOrCreateBuilder (options, userOpName)
+
+ match builderOpt with
+ | None -> return ()
+ | Some builder -> do! builder.NotifyFileChanged(fileName, DateTime.UtcNow)
+ }
+
+ /// Fetch the check information from the background compiler (which checks w.r.t. the FileSystem API)
+ member _.GetBackgroundCheckResultsForFileInProject(fileName, options, userOpName) =
+ node {
+ use _ =
+ Activity.start
+ "BackgroundCompiler.ParseAndCheckFileInProject"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.fileName, fileName
+ Activity.Tags.userOpName, userOpName
+ |]
+
+ let! ct = NodeCode.CancellationToken
+ use _ = Cancellable.UsingToken(ct)
+
+ let! builderOpt, creationDiags = getOrCreateBuilder (options, userOpName)
+
+ match builderOpt with
+ | None ->
+ let parseTree = EmptyParsedInput(fileName, (false, false))
+ let parseResults = FSharpParseFileResults(creationDiags, parseTree, true, [||])
+ let typedResults = FSharpCheckFileResults.MakeEmpty(fileName, creationDiags, true)
+ return (parseResults, typedResults)
+ | Some builder ->
+ let parseTree, _, _, parseDiagnostics = builder.GetParseResultsForFile fileName
+ let! tcProj = builder.GetFullCheckResultsAfterFileInProject fileName
+
+ let! tcInfo, tcInfoExtras = tcProj.GetOrComputeTcInfoWithExtras()
+
+ let tcResolutions = tcInfoExtras.tcResolutions
+ let tcSymbolUses = tcInfoExtras.tcSymbolUses
+ let tcOpenDeclarations = tcInfoExtras.tcOpenDeclarations
+ let latestCcuSigForFile = tcInfo.latestCcuSigForFile
+ let tcState = tcInfo.tcState
+ let tcEnvAtEnd = tcInfo.tcEnvAtEndOfFile
+ let latestImplementationFile = tcInfoExtras.latestImplFile
+ let tcDependencyFiles = tcInfo.tcDependencyFiles
+ let tcDiagnostics = tcInfo.TcDiagnostics
+ let diagnosticsOptions = builder.TcConfig.diagnosticsOptions
+
+ let symbolEnv =
+ SymbolEnv(tcProj.TcGlobals, tcInfo.tcState.Ccu, Some tcInfo.tcState.CcuSig, tcProj.TcImports)
+ |> Some
+
+ let parseDiagnostics =
+ DiagnosticHelpers.CreateDiagnostics(
+ diagnosticsOptions,
+ false,
+ fileName,
+ parseDiagnostics,
+ suggestNamesForErrors,
+ builder.TcConfig.flatErrors,
+ None
+ )
+
+ let parseDiagnostics = [| yield! creationDiags; yield! parseDiagnostics |]
+
+ let tcDiagnostics =
+ DiagnosticHelpers.CreateDiagnostics(
+ diagnosticsOptions,
+ false,
+ fileName,
+ tcDiagnostics,
+ suggestNamesForErrors,
+ builder.TcConfig.flatErrors,
+ symbolEnv
+ )
+
+ let tcDiagnostics = [| yield! creationDiags; yield! tcDiagnostics |]
+
+ let parseResults =
+ FSharpParseFileResults(
+ diagnostics = parseDiagnostics,
+ input = parseTree,
+ parseHadErrors = false,
+ dependencyFiles = builder.AllDependenciesDeprecated
+ )
+
+ let loadClosure = scriptClosureCache.TryGet(AnyCallerThread, options)
+
+ let typedResults =
+ FSharpCheckFileResults.Make(
+ fileName,
+ options.ProjectFileName,
+ tcProj.TcConfig,
+ tcProj.TcGlobals,
+ options.IsIncompleteTypeCheckEnvironment,
+ Some builder,
+ options,
+ Array.ofList tcDependencyFiles,
+ creationDiags,
+ parseResults.Diagnostics,
+ tcDiagnostics,
+ keepAssemblyContents,
+ Option.get latestCcuSigForFile,
+ tcState.Ccu,
+ tcProj.TcImports,
+ tcEnvAtEnd.AccessRights,
+ tcResolutions,
+ tcSymbolUses,
+ tcEnvAtEnd.NameEnv,
+ loadClosure,
+ latestImplementationFile,
+ tcOpenDeclarations
+ )
+
+ return (parseResults, typedResults)
+ }
+
+ member _.FindReferencesInFile
+ (
+ fileName: string,
+ options: FSharpProjectOptions,
+ symbol: FSharpSymbol,
+ canInvalidateProject: bool,
+ userOpName: string
+ ) =
+ node {
+ use _ =
+ Activity.start
+ "BackgroundCompiler.FindReferencesInFile"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.fileName, fileName
+ Activity.Tags.userOpName, userOpName
+ "symbol", symbol.FullName
+ |]
+
+ let! builderOpt, _ = getOrCreateBuilderWithInvalidationFlag (options, canInvalidateProject, userOpName)
+
+ match builderOpt with
+ | None -> return Seq.empty
+ | Some builder ->
+ if builder.ContainsFile fileName then
+ let! checkResults = builder.GetFullCheckResultsAfterFileInProject fileName
+ let! keyStoreOpt = checkResults.GetOrComputeItemKeyStoreIfEnabled()
+
+ match keyStoreOpt with
+ | None -> return Seq.empty
+ | Some reader -> return reader.FindAll symbol.Item
+ else
+ return Seq.empty
+ }
+
+ member _.GetSemanticClassificationForFile(fileName: string, options: FSharpProjectOptions, userOpName: string) =
+ node {
+ use _ =
+ Activity.start
+ "BackgroundCompiler.GetSemanticClassificationForFile"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.fileName, fileName
+ Activity.Tags.userOpName, userOpName
+ |]
+
+ let! ct = NodeCode.CancellationToken
+ use _ = Cancellable.UsingToken(ct)
+
+ let! builderOpt, _ = getOrCreateBuilder (options, userOpName)
+
+ match builderOpt with
+ | None -> return None
+ | Some builder ->
+ let! checkResults = builder.GetFullCheckResultsAfterFileInProject fileName
+ let! scopt = checkResults.GetOrComputeSemanticClassificationIfEnabled()
+
+ match scopt with
+ | None -> return None
+ | Some sc -> return Some(sc.GetView())
+ }
+
+ /// Try to get recent approximate type check results for a file.
+ member _.TryGetRecentCheckResultsForFile
+ (
+ fileName: string,
+ options: FSharpProjectOptions,
+ sourceText: ISourceText option,
+ _userOpName: string
+ ) =
+ use _ =
+ Activity.start
+ "BackgroundCompiler.GetSemanticClassificationForFile"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.fileName, fileName
+ Activity.Tags.userOpName, _userOpName
+ |]
+
+ match sourceText with
+ | Some sourceText ->
+ let hash = sourceText.GetHashCode() |> int64
+
+ let resOpt =
+ parseCacheLock.AcquireLock(fun ltok -> checkFileInProjectCache.TryGet(ltok, (fileName, hash, options)))
+
+ match resOpt with
+ | Some res ->
+ match res.TryPeekValue() with
+ | ValueSome(a, b, c, _) -> Some(a, b, c)
+ | ValueNone -> None
+ | None -> None
+ | None -> None
+
+ /// Parse and typecheck the whole project (the implementation, called recursively as project graph is evaluated)
+ member private _.ParseAndCheckProjectImpl(options, userOpName) =
+ node {
+ let! ct = NodeCode.CancellationToken
+ use _ = Cancellable.UsingToken(ct)
+
+ let! builderOpt, creationDiags = getOrCreateBuilder (options, userOpName)
+
+ match builderOpt with
+ | None ->
+ let emptyResults =
+ FSharpCheckProjectResults(options.ProjectFileName, None, keepAssemblyContents, creationDiags, None)
+
+ return emptyResults
+ | Some builder ->
+ let! tcProj, ilAssemRef, tcAssemblyDataOpt, tcAssemblyExprOpt = builder.GetFullCheckResultsAndImplementationsForProject()
+ let diagnosticsOptions = tcProj.TcConfig.diagnosticsOptions
+ let fileName = DummyFileNameForRangesWithoutASpecificLocation
+
+ // Although we do not use 'tcInfoExtras', computing it will make sure we get an extra info.
+ let! tcInfo, _tcInfoExtras = tcProj.GetOrComputeTcInfoWithExtras()
+
+ let topAttribs = tcInfo.topAttribs
+ let tcState = tcInfo.tcState
+ let tcEnvAtEnd = tcInfo.tcEnvAtEndOfFile
+ let tcDiagnostics = tcInfo.TcDiagnostics
+ let tcDependencyFiles = tcInfo.tcDependencyFiles
+
+ let symbolEnv =
+ SymbolEnv(tcProj.TcGlobals, tcInfo.tcState.Ccu, Some tcInfo.tcState.CcuSig, tcProj.TcImports)
+ |> Some
+
+ let tcDiagnostics =
+ DiagnosticHelpers.CreateDiagnostics(
+ diagnosticsOptions,
+ true,
+ fileName,
+ tcDiagnostics,
+ suggestNamesForErrors,
+ builder.TcConfig.flatErrors,
+ symbolEnv
+ )
+
+ let diagnostics = [| yield! creationDiags; yield! tcDiagnostics |]
+
+ let getAssemblyData () =
+ match tcAssemblyDataOpt with
+ | ProjectAssemblyDataResult.Available data -> Some data
+ | _ -> None
+
+ let details =
+ (tcProj.TcGlobals,
+ tcProj.TcImports,
+ tcState.Ccu,
+ tcState.CcuSig,
+ Choice1Of2 builder,
+ topAttribs,
+ getAssemblyData,
+ ilAssemRef,
+ tcEnvAtEnd.AccessRights,
+ tcAssemblyExprOpt,
+ Array.ofList tcDependencyFiles,
+ options)
+
+ let results =
+ FSharpCheckProjectResults(
+ options.ProjectFileName,
+ Some tcProj.TcConfig,
+ keepAssemblyContents,
+ diagnostics,
+ Some details
+ )
+
+ return results
+ }
+
+ member _.GetAssemblyData(options, userOpName) =
+ node {
+ use _ =
+ Activity.start
+ "BackgroundCompiler.GetAssemblyData"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.userOpName, userOpName
+ |]
+
+ let! builderOpt, _ = getOrCreateBuilder (options, userOpName)
+
+ match builderOpt with
+ | None -> return ProjectAssemblyDataResult.Unavailable true
+ | Some builder ->
+ let! _, _, tcAssemblyDataOpt, _ = builder.GetCheckResultsAndImplementationsForProject()
+ return tcAssemblyDataOpt
+ }
+
+ /// Get the timestamp that would be on the output if fully built immediately
+ member private _.TryGetLogicalTimeStampForProject(cache, options) =
+ match tryGetBuilderNode options with
+ | Some lazyWork ->
+ match lazyWork.TryPeekValue() with
+ | ValueSome(Some builder, _) -> Some(builder.GetLogicalTimeStampForProject(cache))
+ | _ -> None
+ | _ -> None
+
+ /// Parse and typecheck the whole project.
+ member bc.ParseAndCheckProject(options, userOpName) =
+ use _ =
+ Activity.start
+ "BackgroundCompiler.ParseAndCheckProject"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.userOpName, userOpName
+ |]
+
+ bc.ParseAndCheckProjectImpl(options, userOpName)
+
+ member _.GetProjectOptionsFromScript
+ (
+ fileName,
+ sourceText,
+ previewEnabled,
+ loadedTimeStamp,
+ otherFlags,
+ useFsiAuxLib: bool option,
+ useSdkRefs: bool option,
+ sdkDirOverride: string option,
+ assumeDotNetFramework: bool option,
+ optionsStamp: int64 option,
+ _userOpName
+ ) =
+ use _ =
+ Activity.start
+ "BackgroundCompiler.GetProjectOptionsFromScript"
+ [| Activity.Tags.fileName, fileName; Activity.Tags.userOpName, _userOpName |]
+
+ cancellable {
+ // Do we add a reference to FSharp.Compiler.Interactive.Settings by default?
+ let useFsiAuxLib = defaultArg useFsiAuxLib true
+ let useSdkRefs = defaultArg useSdkRefs true
+ let reduceMemoryUsage = ReduceMemoryFlag.Yes
+ let previewEnabled = defaultArg previewEnabled false
+
+ // Do we assume .NET Framework references for scripts?
+ let assumeDotNetFramework = defaultArg assumeDotNetFramework true
+
+ let! ct = Cancellable.token ()
+ use _ = Cancellable.UsingToken(ct)
+
+ let extraFlags =
+ if previewEnabled then
+ [| "--langversion:preview" |]
+ else
+ [||]
+
+ let otherFlags = defaultArg otherFlags extraFlags
+
+ use diagnostics = new DiagnosticsScope(otherFlags |> Array.contains "--flaterrors")
+
+ let useSimpleResolution =
+ otherFlags |> Array.exists (fun x -> x = "--simpleresolution")
+
+ let loadedTimeStamp = defaultArg loadedTimeStamp DateTime.MaxValue // Not 'now', we don't want to force reloading
+
+ let applyCompilerOptions tcConfigB =
+ let fsiCompilerOptions = GetCoreFsiCompilerOptions tcConfigB
+ ParseCompilerOptions(ignore, fsiCompilerOptions, Array.toList otherFlags)
+
+ let loadClosure =
+ LoadClosure.ComputeClosureOfScriptText(
+ legacyReferenceResolver,
+ FSharpCheckerResultsSettings.defaultFSharpBinariesDir,
+ fileName,
+ sourceText,
+ CodeContext.Editing,
+ useSimpleResolution,
+ useFsiAuxLib,
+ useSdkRefs,
+ sdkDirOverride,
+ Lexhelp.LexResourceManager(),
+ applyCompilerOptions,
+ assumeDotNetFramework,
+ tryGetMetadataSnapshot,
+ reduceMemoryUsage,
+ dependencyProviderForScripts
+ )
+
+ let otherFlags =
+ [|
+ yield "--noframework"
+ yield "--warn:3"
+ yield! otherFlags
+ for r in loadClosure.References do
+ yield "-r:" + fst r
+ for code, _ in loadClosure.NoWarns do
+ yield "--nowarn:" + code
+ |]
+
+ let options =
+ {
+ ProjectFileName = fileName + ".fsproj" // Make a name that is unique in this directory.
+ ProjectId = None
+ SourceFiles = loadClosure.SourceFiles |> List.map fst |> List.toArray
+ OtherOptions = otherFlags
+ ReferencedProjects = [||]
+ IsIncompleteTypeCheckEnvironment = false
+ UseScriptResolutionRules = true
+ LoadTime = loadedTimeStamp
+ UnresolvedReferences = Some(FSharpUnresolvedReferencesSet(loadClosure.UnresolvedReferences))
+ OriginalLoadReferences = loadClosure.OriginalLoadReferences
+ Stamp = optionsStamp
+ }
+
+ scriptClosureCache.Set(AnyCallerThread, options, loadClosure) // Save the full load closure for later correlation.
+
+ let diags =
+ loadClosure.LoadClosureRootFileDiagnostics
+ |> List.map (fun (exn, isError) ->
+ FSharpDiagnostic.CreateFromException(
+ exn,
+ isError,
+ range.Zero,
+ false,
+ options.OtherOptions |> Array.contains "--flaterrors",
+ None
+ ))
+
+ return options, (diags @ diagnostics.Diagnostics)
+ }
+ |> Cancellable.toAsync
+
+ member bc.InvalidateConfiguration(options: FSharpProjectOptions, userOpName) =
+ use _ =
+ Activity.start
+ "BackgroundCompiler.InvalidateConfiguration"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.userOpName, userOpName
+ |]
+
+ if incrementalBuildersCache.ContainsSimilarKey(AnyCallerThread, options) then
+ parseCacheLock.AcquireLock(fun ltok ->
+ for sourceFile in options.SourceFiles do
+ checkFileInProjectCache.RemoveAnySimilar(ltok, (sourceFile, 0L, options)))
+
+ let _ = createBuilderNode (options, userOpName, CancellationToken.None)
+ ()
+
+ member bc.ClearCache(options: seq, _userOpName) =
+ use _ =
+ Activity.start "BackgroundCompiler.ClearCache" [| Activity.Tags.userOpName, _userOpName |]
+
+ lock gate (fun () ->
+ options
+ |> Seq.iter (fun options ->
+ incrementalBuildersCache.RemoveAnySimilar(AnyCallerThread, options)
+
+ parseCacheLock.AcquireLock(fun ltok ->
+ for sourceFile in options.SourceFiles do
+ checkFileInProjectCache.RemoveAnySimilar(ltok, (sourceFile, 0L, options)))))
+
+ member _.NotifyProjectCleaned(options: FSharpProjectOptions, userOpName) =
+ use _ =
+ Activity.start
+ "BackgroundCompiler.NotifyProjectCleaned"
+ [|
+ Activity.Tags.project, options.ProjectFileName
+ Activity.Tags.userOpName, userOpName
+ |]
+
+ async {
+ let! ct = Async.CancellationToken
+ use _ = Cancellable.UsingToken(ct)
+
+ let! ct = Async.CancellationToken
+ // If there was a similar entry (as there normally will have been) then re-establish an empty builder . This
+ // is a somewhat arbitrary choice - it will have the effect of releasing memory associated with the previous
+ // builder, but costs some time.
+ if incrementalBuildersCache.ContainsSimilarKey(AnyCallerThread, options) then
+ let _ = createBuilderNode (options, userOpName, ct)
+ ()
+ }
+
+ member _.BeforeBackgroundFileCheck = beforeFileChecked.Publish
+
+ member _.FileParsed = fileParsed.Publish
+
+ member _.FileChecked = fileChecked.Publish
+
+ member _.ProjectChecked = projectChecked.Publish
+
+ member _.ClearCaches() =
+ use _ = Activity.startNoTags "BackgroundCompiler.ClearCaches"
+
+ lock gate (fun () ->
+ parseCacheLock.AcquireLock(fun ltok ->
+ checkFileInProjectCache.Clear(ltok)
+ parseFileCache.Clear(ltok))
+
+ incrementalBuildersCache.Clear(AnyCallerThread)
+ frameworkTcImportsCache.Clear()
+ scriptClosureCache.Clear AnyCallerThread)
+
+ member _.DownsizeCaches() =
+ use _ = Activity.startNoTags "BackgroundCompiler.DownsizeCaches"
+
+ lock gate (fun () ->
+ parseCacheLock.AcquireLock(fun ltok ->
+ checkFileInProjectCache.Resize(ltok, newKeepStrongly = 1)
+ parseFileCache.Resize(ltok, newKeepStrongly = 1))
+
+ incrementalBuildersCache.Resize(AnyCallerThread, newKeepStrongly = 1, newKeepMax = 1)
+ frameworkTcImportsCache.Downsize()
+ scriptClosureCache.Resize(AnyCallerThread, newKeepStrongly = 1, newKeepMax = 1))
+
+ member _.FrameworkImportsCache = frameworkTcImportsCache
+
+ static member ActualParseFileCount = actualParseFileCount
+
+ static member ActualCheckFileCount = actualCheckFileCount
+
+ interface IBackgroundCompiler with
+
+ member _.BeforeBackgroundFileCheck = self.BeforeBackgroundFileCheck
+
+ member _.CheckFileInProject
+ (
+ parseResults: FSharpParseFileResults,
+ fileName: string,
+ fileVersion: int,
+ sourceText: ISourceText,
+ options: FSharpProjectOptions,
+ userOpName: string
+ ) : NodeCode =
+ self.CheckFileInProject(parseResults, fileName, fileVersion, sourceText, options, userOpName)
+
+ member _.CheckFileInProjectAllowingStaleCachedResults
+ (
+ parseResults: FSharpParseFileResults,
+ fileName: string,
+ fileVersion: int,
+ sourceText: ISourceText,
+ options: FSharpProjectOptions,
+ userOpName: string
+ ) : NodeCode =
+ self.CheckFileInProjectAllowingStaleCachedResults(parseResults, fileName, fileVersion, sourceText, options, userOpName)
+
+ member _.ClearCache(options: seq, userOpName: string) : unit = self.ClearCache(options, userOpName)
+
+ member _.ClearCache(projects: ProjectSnapshot.FSharpProjectIdentifier seq, userOpName: string) = ignore (projects, userOpName)
+
+ member _.ClearCaches() : unit = self.ClearCaches()
+ member _.DownsizeCaches() : unit = self.DownsizeCaches()
+ member _.FileChecked: IEvent = self.FileChecked
+ member _.FileParsed: IEvent = self.FileParsed
+
+ member _.FindReferencesInFile
+ (
+ fileName: string,
+ options: FSharpProjectOptions,
+ symbol: FSharpSymbol,
+ canInvalidateProject: bool,
+ userOpName: string
+ ) : NodeCode> =
+ self.FindReferencesInFile(fileName, options, symbol, canInvalidateProject, userOpName)
+
+ member this.FindReferencesInFile(fileName, projectSnapshot, symbol, userOpName) =
+ this.FindReferencesInFile(fileName, projectSnapshot.ToOptions(), symbol, true, userOpName)
+
+ member _.FrameworkImportsCache: FrameworkImportsCache = self.FrameworkImportsCache
+
+ member _.GetAssemblyData
+ (
+ options: FSharpProjectOptions,
+ _fileName: string,
+ userOpName: string
+ ) : NodeCode =
+ self.GetAssemblyData(options, userOpName)
+
+ member _.GetAssemblyData
+ (
+ projectSnapshot: FSharpProjectSnapshot,
+ _fileName: string,
+ userOpName: string
+ ) : NodeCode =
+ self.GetAssemblyData(projectSnapshot.ToOptions(), userOpName)
+
+ member _.GetBackgroundCheckResultsForFileInProject
+ (
+ fileName: string,
+ options: FSharpProjectOptions,
+ userOpName: string
+ ) : NodeCode =
+ self.GetBackgroundCheckResultsForFileInProject(fileName, options, userOpName)
+
+ member _.GetBackgroundParseResultsForFileInProject
+ (
+ fileName: string,
+ options: FSharpProjectOptions,
+ userOpName: string
+ ) : NodeCode =
+ self.GetBackgroundParseResultsForFileInProject(fileName, options, userOpName)
+
+ member _.GetCachedCheckFileResult
+ (
+ builder: IncrementalBuilder,
+ fileName: string,
+ sourceText: ISourceText,
+ options: FSharpProjectOptions
+ ) : NodeCode<(FSharpParseFileResults * FSharpCheckFileResults) option> =
+ self.GetCachedCheckFileResult(builder, fileName, sourceText, options)
+
+ member _.GetProjectOptionsFromScript
+ (
+ fileName: string,
+ sourceText: ISourceText,
+ previewEnabled: bool option,
+ loadedTimeStamp: DateTime option,
+ otherFlags: string array option,
+ useFsiAuxLib: bool option,
+ useSdkRefs: bool option,
+ sdkDirOverride: string option,
+ assumeDotNetFramework: bool option,
+ optionsStamp: int64 option,
+ userOpName: string
+ ) : Async =
+ self.GetProjectOptionsFromScript(
+ fileName,
+ sourceText,
+ previewEnabled,
+ loadedTimeStamp,
+ otherFlags,
+ useFsiAuxLib,
+ useSdkRefs,
+ sdkDirOverride,
+ assumeDotNetFramework,
+ optionsStamp,
+ userOpName
+ )
+
+ member _.GetSemanticClassificationForFile
+ (
+ fileName: string,
+ options: FSharpProjectOptions,
+ userOpName: string
+ ) : NodeCode =
+ self.GetSemanticClassificationForFile(fileName, options, userOpName)
+
+ member _.GetSemanticClassificationForFile
+ (
+ fileName: string,
+ snapshot: FSharpProjectSnapshot,
+ userOpName: string
+ ) : NodeCode =
+ self.GetSemanticClassificationForFile(fileName, snapshot.ToOptions(), userOpName)
+
+ member _.InvalidateConfiguration(options: FSharpProjectOptions, userOpName: string) : unit =
+ self.InvalidateConfiguration(options, userOpName)
+
+ member _.NotifyFileChanged(fileName: string, options: FSharpProjectOptions, userOpName: string) : NodeCode =
+ self.NotifyFileChanged(fileName, options, userOpName)
+
+ member _.NotifyProjectCleaned(options: FSharpProjectOptions, userOpName: string) : Async =
+ self.NotifyProjectCleaned(options, userOpName)
+
+ member _.ParseAndCheckFileInProject
+ (
+ fileName: string,
+ fileVersion: int,
+ sourceText: ISourceText,
+ options: FSharpProjectOptions,
+ userOpName: string
+ ) : NodeCode =
+ self.ParseAndCheckFileInProject(fileName, fileVersion, sourceText, options, userOpName)
+
+ member _.ParseAndCheckFileInProject
+ (
+ fileName: string,
+ projectSnapshot: FSharpProjectSnapshot,
+ userOpName: string
+ ) : NodeCode =
+ node {
+ let fileSnapshot =
+ projectSnapshot.ProjectSnapshot.SourceFiles
+ |> Seq.find (fun f -> f.FileName = fileName)
+
+ let! sourceText = fileSnapshot.GetSource() |> NodeCode.AwaitTask
+ let options = projectSnapshot.ToOptions()
+
+ return! self.ParseAndCheckFileInProject(fileName, 0, sourceText, options, userOpName)
+ }
+
+ member _.ParseAndCheckProject(options: FSharpProjectOptions, userOpName: string) : NodeCode =
+ self.ParseAndCheckProject(options, userOpName)
+
+ member _.ParseAndCheckProject(projectSnapshot: FSharpProjectSnapshot, userOpName: string) : NodeCode =
+ self.ParseAndCheckProject(projectSnapshot.ToOptions(), userOpName)
+
+ member _.ParseFile
+ (
+ fileName: string,
+ sourceText: ISourceText,
+ options: FSharpParsingOptions,
+ cache: bool,
+ flatErrors: bool,
+ userOpName: string
+ ) =
+ self.ParseFile(fileName, sourceText, options, cache, flatErrors, userOpName)
+
+ member _.ParseFile(fileName: string, projectSnapshot: FSharpProjectSnapshot, userOpName: string) =
+ let options = projectSnapshot.ToOptions()
+
+ self.GetBackgroundParseResultsForFileInProject(fileName, options, userOpName)
+ |> Async.AwaitNodeCode
+
+ member _.ProjectChecked: IEvent = self.ProjectChecked
+
+ member _.TryGetRecentCheckResultsForFile
+ (
+ fileName: string,
+ options: FSharpProjectOptions,
+ sourceText: ISourceText option,
+ userOpName: string
+ ) : (FSharpParseFileResults * FSharpCheckFileResults * SourceTextHash) option =
+ self.TryGetRecentCheckResultsForFile(fileName, options, sourceText, userOpName)
diff --git a/src/Compiler/Service/BackgroundCompiler.fsi b/src/Compiler/Service/BackgroundCompiler.fsi
new file mode 100644
index 00000000000..f3bf3c96ccc
--- /dev/null
+++ b/src/Compiler/Service/BackgroundCompiler.fsi
@@ -0,0 +1,224 @@
+namespace FSharp.Compiler.CodeAnalysis
+
+open FSharp.Compiler.Text
+open FSharp.Compiler.BuildGraph
+
+open System.Reflection
+open FSharp.Compiler.CodeAnalysis
+open FSharp.Compiler.CompilerConfig
+open FSharp.Compiler.Diagnostics
+
+type SourceTextHash = int64
+
+type CacheStamp = int64
+
+type FileName = string
+
+type FilePath = string
+
+type ProjectPath = string
+
+type FileVersion = int
+
+type FSharpProjectSnapshot = ProjectSnapshot.FSharpProjectSnapshot
+
+type internal IBackgroundCompiler =
+
+ /// Type-check the result obtained by parsing. Force the evaluation of the antecedent type checking context if needed.
+ abstract CheckFileInProject:
+ parseResults: FSharpParseFileResults *
+ fileName: string *
+ fileVersion: int *
+ sourceText: ISourceText *
+ options: FSharpProjectOptions *
+ userOpName: string ->
+ NodeCode
+
+ /// Type-check the result obtained by parsing, but only if the antecedent type checking context is available.
+ abstract CheckFileInProjectAllowingStaleCachedResults:
+ parseResults: FSharpParseFileResults *
+ fileName: string *
+ fileVersion: int *
+ sourceText: ISourceText *
+ options: FSharpProjectOptions *
+ userOpName: string ->
+ NodeCode
+
+ abstract ClearCache: options: FSharpProjectOptions seq * userOpName: string -> unit
+
+ abstract ClearCache: projects: ProjectSnapshot.FSharpProjectIdentifier seq * userOpName: string -> unit
+
+ abstract ClearCaches: unit -> unit
+
+ abstract DownsizeCaches: unit -> unit
+
+ abstract FindReferencesInFile:
+ fileName: string *
+ projectSnapshot: FSharpProjectSnapshot *
+ symbol: FSharp.Compiler.Symbols.FSharpSymbol *
+ userOpName: string ->
+ NodeCode
+
+ abstract FindReferencesInFile:
+ fileName: string *
+ options: FSharpProjectOptions *
+ symbol: FSharp.Compiler.Symbols.FSharpSymbol *
+ canInvalidateProject: bool *
+ userOpName: string ->
+ NodeCode
+
+ abstract GetAssemblyData:
+ projectSnapshot: FSharpProjectSnapshot * outputFileName: string * userOpName: string ->
+ NodeCode
+
+ abstract GetAssemblyData:
+ options: FSharpProjectOptions * outputFileName: string * userOpName: string ->
+ NodeCode
+
+ /// Fetch the check information from the background compiler (which checks w.r.t. the FileSystem API)
+ abstract GetBackgroundCheckResultsForFileInProject:
+ fileName: string * options: FSharpProjectOptions * userOpName: string ->
+ NodeCode
+
+ /// Fetch the parse information from the background compiler (which checks w.r.t. the FileSystem API)
+ abstract GetBackgroundParseResultsForFileInProject:
+ fileName: string * options: FSharpProjectOptions * userOpName: string -> NodeCode
+
+ abstract GetCachedCheckFileResult:
+ builder: IncrementalBuilder * fileName: string * sourceText: ISourceText * options: FSharpProjectOptions ->
+ NodeCode<(FSharpParseFileResults * FSharpCheckFileResults) option>
+
+ abstract GetProjectOptionsFromScript:
+ fileName: string *
+ sourceText: ISourceText *
+ previewEnabled: bool option *
+ loadedTimeStamp: System.DateTime option *
+ otherFlags: string array option *
+ useFsiAuxLib: bool option *
+ useSdkRefs: bool option *
+ sdkDirOverride: string option *
+ assumeDotNetFramework: bool option *
+ optionsStamp: int64 option *
+ userOpName: string ->
+ Async
+
+ abstract GetSemanticClassificationForFile:
+ fileName: string * snapshot: FSharpProjectSnapshot * userOpName: string ->
+ NodeCode
+
+ abstract GetSemanticClassificationForFile:
+ fileName: string * options: FSharpProjectOptions * userOpName: string ->
+ NodeCode
+
+ abstract InvalidateConfiguration: options: FSharpProjectOptions * userOpName: string -> unit
+
+ abstract NotifyFileChanged: fileName: string * options: FSharpProjectOptions * userOpName: string -> NodeCode
+
+ abstract NotifyProjectCleaned: options: FSharpProjectOptions * userOpName: string -> Async
+
+ abstract ParseAndCheckFileInProject:
+ fileName: string * projectSnapshot: FSharpProjectSnapshot * userOpName: string ->
+ NodeCode
+
+ /// Parses and checks the source file and returns untyped AST and check results.
+ abstract ParseAndCheckFileInProject:
+ fileName: string *
+ fileVersion: int *
+ sourceText: ISourceText *
+ options: FSharpProjectOptions *
+ userOpName: string ->
+ NodeCode
+
+ abstract ParseAndCheckProject:
+ projectSnapshot: FSharpProjectSnapshot * userOpName: string -> NodeCode
+
+ /// Parse and typecheck the whole project.
+ abstract ParseAndCheckProject:
+ options: FSharpProjectOptions * userOpName: string -> NodeCode
+
+ abstract ParseFile:
+ fileName: string * projectSnapshot: FSharpProjectSnapshot * userOpName: string -> Async
+
+ abstract ParseFile:
+ fileName: string *
+ sourceText: ISourceText *
+ options: FSharpParsingOptions *
+ cache: bool *
+ flatErrors: bool *
+ userOpName: string ->
+ Async
+
+ /// Try to get recent approximate type check results for a file.
+ abstract TryGetRecentCheckResultsForFile:
+ fileName: string * options: FSharpProjectOptions * sourceText: ISourceText option * userOpName: string ->
+ (FSharpParseFileResults * FSharpCheckFileResults * SourceTextHash) option
+
+ abstract BeforeBackgroundFileCheck: IEvent
+
+ abstract FileChecked: IEvent
+
+ abstract FileParsed: IEvent
+
+ abstract FrameworkImportsCache: FrameworkImportsCache
+
+ abstract ProjectChecked: IEvent
+
+[]
+module internal EnvMisc =
+
+ val braceMatchCacheSize: int
+
+ val parseFileCacheSize: int
+
+ val checkFileInProjectCacheSize: int
+
+ val projectCacheSizeDefault: int
+
+ val frameworkTcImportsCacheStrongSize: int
+
+[]
+module internal Helpers =
+
+ /// Determine whether two (fileName,options) keys are identical w.r.t. affect on checking
+ val AreSameForChecking2: (string * FSharpProjectOptions) * (string * FSharpProjectOptions) -> bool
+
+ /// Determine whether two (fileName,options) keys should be identical w.r.t. resource usage
+ val AreSubsumable2: (string * FSharpProjectOptions) * (string * FSharpProjectOptions) -> bool
+
+ /// Determine whether two (fileName,sourceText,options) keys should be identical w.r.t. parsing
+ val AreSameForParsing: (string * int64 * 'a) * (string * int64 * 'a) -> bool when 'a: equality
+
+ val AreSimilarForParsing: ('a * 'b * 'c) * ('a * 'd * 'e) -> bool when 'a: equality
+
+ /// Determine whether two (fileName,sourceText,options) keys should be identical w.r.t. checking
+ val AreSameForChecking3: (string * int64 * FSharpProjectOptions) * (string * int64 * FSharpProjectOptions) -> bool
+
+ /// Determine whether two (fileName,sourceText,options) keys should be identical w.r.t. resource usage
+ val AreSubsumable3: (string * 'a * FSharpProjectOptions) * (string * 'b * FSharpProjectOptions) -> bool
+
+ /// If a symbol is an attribute check if given set of names contains its name without the Attribute suffix
+ val NamesContainAttribute: symbol: FSharp.Compiler.Symbols.FSharpSymbol -> names: Set -> bool
+
+type internal BackgroundCompiler =
+ interface IBackgroundCompiler
+
+ new:
+ legacyReferenceResolver: LegacyReferenceResolver *
+ projectCacheSize: int *
+ keepAssemblyContents: bool *
+ keepAllBackgroundResolutions: bool *
+ tryGetMetadataSnapshot: FSharp.Compiler.AbstractIL.ILBinaryReader.ILReaderTryGetMetadataSnapshot *
+ suggestNamesForErrors: bool *
+ keepAllBackgroundSymbolUses: bool *
+ enableBackgroundItemKeyStoreAndSemanticClassification: bool *
+ enablePartialTypeChecking: bool *
+ parallelReferenceResolution: ParallelReferenceResolution *
+ captureIdentifiersWhenParsing: bool *
+ getSource: (string -> Async) option *
+ useChangeNotifications: bool *
+ useSyntaxTreeCache: bool ->
+ BackgroundCompiler
+
+ static member ActualCheckFileCount: int
+
+ static member ActualParseFileCount: int
diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs
index 37ae0083298..48b9875c9f0 100644
--- a/src/Compiler/Service/FSharpCheckerResults.fs
+++ b/src/Compiler/Service/FSharpCheckerResults.fs
@@ -6,6 +6,7 @@
namespace FSharp.Compiler.CodeAnalysis
open System
+open System.Collections.Generic
open System.Diagnostics
open System.IO
open System.Reflection
@@ -53,6 +54,9 @@ open FSharp.Compiler.TypedTreeOps
open Internal.Utilities
open Internal.Utilities.Collections
open FSharp.Compiler.AbstractIL.ILBinaryReader
+open System.Threading.Tasks
+open System.Runtime.CompilerServices
+open Internal.Utilities.Hashing
type FSharpUnresolvedReferencesSet = FSharpUnresolvedReferencesSet of UnresolvedAssemblyReference list
@@ -2515,6 +2519,11 @@ module internal ParseAndCheckFile =
member _.AnyErrors = errorCount > 0
+ member _.CollectedPhasedDiagnostics =
+ [|
+ for struct (diagnostic, severity) in diagnosticsCollector -> diagnostic, severity
+ |]
+
member _.CollectedDiagnostics(symbolEnv: SymbolEnv option) =
[|
for struct (diagnostic, severity) in diagnosticsCollector do
@@ -3270,7 +3279,7 @@ type FSharpCheckFileResults
tcConfig,
tcGlobals,
isIncompleteTypeCheckEnvironment: bool,
- builder: IncrementalBuilder,
+ builder: IncrementalBuilder option,
projectOptions,
dependencyFiles,
creationErrors: FSharpDiagnostic[],
@@ -3311,7 +3320,7 @@ type FSharpCheckFileResults
let errors =
FSharpCheckFileResults.JoinErrors(isIncompleteTypeCheckEnvironment, creationErrors, parseErrors, tcErrors)
- FSharpCheckFileResults(mainInputFileName, errors, Some tcFileInfo, dependencyFiles, Some builder, keepAssemblyContents)
+ FSharpCheckFileResults(mainInputFileName, errors, Some tcFileInfo, dependencyFiles, builder, keepAssemblyContents)
static member CheckOneFile
(
@@ -3328,7 +3337,7 @@ type FSharpCheckFileResults
backgroundDiagnostics: (PhasedDiagnostic * FSharpDiagnosticSeverity)[],
isIncompleteTypeCheckEnvironment: bool,
projectOptions: FSharpProjectOptions,
- builder: IncrementalBuilder,
+ builder: IncrementalBuilder option,
dependencyFiles: string[],
creationErrors: FSharpDiagnostic[],
parseErrors: FSharpDiagnostic[],
@@ -3357,7 +3366,7 @@ type FSharpCheckFileResults
FSharpCheckFileResults.JoinErrors(isIncompleteTypeCheckEnvironment, creationErrors, parseErrors, tcErrors)
let results =
- FSharpCheckFileResults(mainInputFileName, errors, Some tcFileInfo, dependencyFiles, Some builder, keepAssemblyContents)
+ FSharpCheckFileResults(mainInputFileName, errors, Some tcFileInfo, dependencyFiles, builder, keepAssemblyContents)
return results
}
@@ -3375,7 +3384,7 @@ type FSharpCheckProjectResults
TcImports *
CcuThunk *
ModuleOrNamespaceType *
- Choice *
+ Choice> *
TopAttribs option *
(unit -> IRawFSharpAssemblyData option) *
ILAssemblyRef *
@@ -3413,6 +3422,7 @@ type FSharpCheckProjectResults
FSharpAssemblySignature(tcGlobals, thisCcu, ccuSig, tcImports, topAttribs, ccuSig)
+ // TODO: Looks like we don't need this
member _.TypedImplementationFiles =
if not keepAssemblyContents then
invalidOp
@@ -3473,6 +3483,7 @@ type FSharpCheckProjectResults
FSharpAssemblyContents(tcGlobals, thisCcu, Some ccuSig, tcImports, mimpls)
// Not, this does not have to be a SyncOp, it can be called from any thread
+ // TODO: this should be async
member _.GetUsesOfSymbol(symbol: FSharpSymbol, ?cancellationToken: CancellationToken) =
let _, _, _, _, builderOrSymbolUses, _, _, _, _, _, _, _ = getDetails ()
@@ -3488,7 +3499,20 @@ type FSharpCheckProjectResults
| Some(_, tcInfoExtras) -> tcInfoExtras.TcSymbolUses.GetUsesOfSymbol symbol.Item
| _ -> [||]
| _ -> [||])
- | Choice2Of2 tcSymbolUses -> tcSymbolUses.GetUsesOfSymbol symbol.Item
+ |> Array.toSeq
+ | Choice2Of2 task ->
+ Async.RunSynchronously(
+ async {
+ let! tcSymbolUses = task
+
+ return
+ seq {
+ for symbolUses in tcSymbolUses do
+ yield! symbolUses.GetUsesOfSymbol symbol.Item
+ }
+ },
+ ?cancellationToken = cancellationToken
+ )
results
|> Seq.filter (fun symbolUse -> symbolUse.ItemOccurence <> ItemOccurence.RelatedText)
@@ -3500,6 +3524,7 @@ type FSharpCheckProjectResults
|> Seq.toArray
// Not, this does not have to be a SyncOp, it can be called from any thread
+ // TODO: this should be async
member _.GetAllUsesOfAllSymbols(?cancellationToken: CancellationToken) =
let tcGlobals, tcImports, thisCcu, ccuSig, builderOrSymbolUses, _, _, _, _, _, _, _ =
getDetails ()
@@ -3518,7 +3543,8 @@ type FSharpCheckProjectResults
| Some(_, tcInfoExtras) -> tcInfoExtras.TcSymbolUses
| _ -> TcSymbolUses.Empty
| _ -> TcSymbolUses.Empty)
- | Choice2Of2 tcSymbolUses -> [| tcSymbolUses |]
+ |> Array.toSeq
+ | Choice2Of2 tcSymbolUses -> Async.RunSynchronously(tcSymbolUses, ?cancellationToken = cancellationToken)
[|
for r in tcSymbolUses do
@@ -3657,7 +3683,7 @@ type FsiInteractiveChecker(legacyReferenceResolver, tcConfig: TcConfig, tcGlobal
tcImports,
tcFileInfo.ThisCcu,
tcFileInfo.CcuSigForFile,
- Choice2Of2 tcFileInfo.ScopeSymbolUses,
+ Choice2Of2(tcFileInfo.ScopeSymbolUses |> Seq.singleton |> async.Return),
None,
(fun () -> None),
mkSimpleAssemblyRef "stdin",
diff --git a/src/Compiler/Service/FSharpCheckerResults.fsi b/src/Compiler/Service/FSharpCheckerResults.fsi
index 8cdb304c18a..26781c4356e 100644
--- a/src/Compiler/Service/FSharpCheckerResults.fsi
+++ b/src/Compiler/Service/FSharpCheckerResults.fsi
@@ -3,8 +3,10 @@
namespace FSharp.Compiler.CodeAnalysis
open System
+open System.Collections.Generic
open System.IO
open System.Threading
+open System.Threading.Tasks
open Internal.Utilities.Library
open FSharp.Compiler.AbstractIL.IL
open FSharp.Compiler.AbstractIL.ILBinaryReader
@@ -26,6 +28,8 @@ open FSharp.Compiler.TypedTreeOps
open FSharp.Compiler.TcGlobals
open FSharp.Compiler.Text
+open Internal.Utilities.Collections
+
/// Delays the creation of an ILModuleReader
[]
type DelayedILModuleReader =
@@ -443,7 +447,7 @@ type public FSharpCheckFileResults =
tcConfig: TcConfig *
tcGlobals: TcGlobals *
isIncompleteTypeCheckEnvironment: bool *
- builder: IncrementalBuilder *
+ builder: IncrementalBuilder option *
projectOptions: FSharpProjectOptions *
dependencyFiles: string[] *
creationErrors: FSharpDiagnostic[] *
@@ -477,7 +481,7 @@ type public FSharpCheckFileResults =
backgroundDiagnostics: (PhasedDiagnostic * FSharpDiagnosticSeverity)[] *
isIncompleteTypeCheckEnvironment: bool *
projectOptions: FSharpProjectOptions *
- builder: IncrementalBuilder *
+ builder: IncrementalBuilder option *
dependencyFiles: string[] *
creationErrors: FSharpDiagnostic[] *
parseErrors: FSharpDiagnostic[] *
@@ -537,7 +541,7 @@ type public FSharpCheckProjectResults =
TcImports *
CcuThunk *
ModuleOrNamespaceType *
- Choice *
+ Choice> *
TopAttribs option *
(unit -> IRawFSharpAssemblyData option) *
ILAssemblyRef *
@@ -569,6 +573,29 @@ module internal ParseAndCheckFile =
ct: CancellationToken ->
(range * range)[]
+ /// Diagnostics handler for parsing & type checking while processing a single file
+ type DiagnosticsHandler =
+ new:
+ reportErrors: bool *
+ mainInputFileName: string *
+ diagnosticsOptions: FSharpDiagnosticOptions *
+ sourceText: ISourceText *
+ suggestNamesForErrors: bool *
+ flatErrors: bool ->
+ DiagnosticsHandler
+
+ member DiagnosticsLogger: DiagnosticsLogger
+
+ member ErrorCount: int
+
+ member DiagnosticOptions: FSharpDiagnosticOptions with set
+
+ member AnyErrors: bool
+
+ member CollectedPhasedDiagnostics: (PhasedDiagnostic * FSharpDiagnosticSeverity) array
+
+ member CollectedDiagnostics: symbolEnv: SymbolEnv option -> FSharpDiagnostic array
+
// An object to typecheck source in a given typechecking environment.
// Used internally to provide intellisense over F# Interactive.
type internal FsiInteractiveChecker =
diff --git a/src/Compiler/Service/FSharpProjectSnapshot.fs b/src/Compiler/Service/FSharpProjectSnapshot.fs
new file mode 100644
index 00000000000..259948dc706
--- /dev/null
+++ b/src/Compiler/Service/FSharpProjectSnapshot.fs
@@ -0,0 +1,626 @@
+// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
+
+module FSharp.Compiler.CodeAnalysis.ProjectSnapshot
+
+open System
+open System.Collections.Generic
+open System.IO
+open System.Reflection
+open FSharp.Compiler.IO
+open Internal.Utilities.Library.Extras
+open FSharp.Core.Printf
+open FSharp.Compiler.Text
+
+open Internal.Utilities.Collections
+open System.Threading.Tasks
+open Internal.Utilities.Hashing
+open System.Collections.Immutable
+open System.Runtime.CompilerServices
+open FSharp.Compiler.Syntax
+open FSharp.Compiler.Diagnostics
+open FSharp.Compiler.DiagnosticsLogger
+
+type internal ProjectIdentifier = string * string
+
+/// A common interface for an F# source file snapshot that can be used accross all stages (lazy, source loaded, parsed)
+type internal IFileSnapshot =
+ abstract member FileName: string
+ abstract member Version: byte array
+ abstract member IsSignatureFile: bool
+
+[]
+module internal Helpers =
+
+ let isSignatureFile (fileName: string) =
+ // TODO: is this robust enough?
+ fileName[fileName.Length - 1] = 'i'
+
+ let addFileName (file: IFileSnapshot) = Md5Hasher.addString file.FileName
+
+ let addFileNameAndVersion (file: IFileSnapshot) =
+ addFileName file >> Md5Hasher.addBytes file.Version
+
+ let signatureHash projectCoreVersion (sourceFiles: IFileSnapshot seq) =
+ let mutable lastFile = ""
+
+ ((projectCoreVersion, Set.empty), sourceFiles)
+ ||> Seq.fold (fun (res, sigs) file ->
+ if file.IsSignatureFile then
+ lastFile <- file.FileName
+ res |> addFileNameAndVersion file, sigs |> Set.add file.FileName
+ else
+ let sigFileName = $"{file.FileName}i"
+
+ if sigs.Contains sigFileName then
+ res |> addFileName file, sigs |> Set.remove sigFileName
+ else
+ lastFile <- file.FileName
+ res |> addFileNameAndVersion file, sigs)
+ |> fst,
+ lastFile
+
+ let findOutputFileName options =
+ options
+ |> Seq.tryFind (fun (x: string) -> x.StartsWith("-o:"))
+ |> Option.map (fun x -> x.Substring(3))
+
+/// A snapshot of an F# source file.
+[]
+type FSharpFileSnapshot(FileName: string, Version: string, GetSource: unit -> Task) =
+
+ static member Create(fileName: string, version: string, getSource: unit -> Task) =
+ FSharpFileSnapshot(fileName, version, getSource)
+
+ static member CreateFromFileSystem(fileName: string) =
+ FSharpFileSnapshot(
+ fileName,
+ FileSystem.GetLastWriteTimeShim(fileName).Ticks.ToString(),
+ fun () ->
+ FileSystem.OpenFileForReadShim(fileName).ReadAllText()
+ |> SourceTextNew.ofString
+ |> Task.FromResult
+ )
+
+ member public _.FileName = FileName
+ member _.Version = Version
+ member _.GetSource() = GetSource()
+
+ member val IsSignatureFile = FileName |> isSignatureFile
+
+ member _.GetFileName() = FileName
+
+ override this.Equals(o) =
+ match o with
+ | :? FSharpFileSnapshot as o -> o.FileName = this.FileName && o.Version = this.Version
+ | _ -> false
+
+ override this.GetHashCode() =
+ this.FileName.GetHashCode() + this.Version.GetHashCode()
+
+ interface IFileSnapshot with
+ member this.FileName = this.FileName
+ member this.Version = this.Version |> System.Text.Encoding.UTF8.GetBytes
+ member this.IsSignatureFile = this.IsSignatureFile
+
+/// A source file snapshot with loaded source text.
+type internal FSharpFileSnapshotWithSource
+ (FileName: string, SourceHash: ImmutableArray, Source: ISourceText, IsLastCompiland: bool, IsExe: bool) =
+
+ let version = lazy (SourceHash.ToBuilder().ToArray())
+ let stringVersion = lazy (version.Value |> BitConverter.ToString)
+
+ member val Version = version.Value
+ member val StringVersion = stringVersion.Value
+ member val IsSignatureFile = FileName |> isSignatureFile
+
+ member _.FileName = FileName
+ member _.Source = Source
+ member _.IsLastCompiland = IsLastCompiland
+ member _.IsExe = IsExe
+
+ interface IFileSnapshot with
+ member this.FileName = this.FileName
+ member this.Version = this.Version
+ member this.IsSignatureFile = this.IsSignatureFile
+
+/// A source file snapshot with parsed syntax tree
+type internal FSharpParsedFile
+ (
+ FileName: string,
+ SyntaxTreeHash: byte array,
+ SourceText: ISourceText,
+ ParsedInput: ParsedInput,
+ ParseErrors: (PhasedDiagnostic * FSharpDiagnosticSeverity)[]
+ ) =
+
+ member _.FileName = FileName
+ member _.SourceText = SourceText
+ member _.ParsedInput = ParsedInput
+ member _.ParseErrors = ParseErrors
+
+ member val IsSignatureFile = FileName |> isSignatureFile
+
+ interface IFileSnapshot with
+ member this.FileName = this.FileName
+ member this.Version = SyntaxTreeHash
+ member this.IsSignatureFile = this.IsSignatureFile
+
+/// An on-disk reference needed for project compilation.
+[]
+type ReferenceOnDisk =
+ { Path: string; LastModified: DateTime }
+
+/// A snapshot of an F# project. The source file type can differ based on which stage of compilation the snapshot is used for.
+type internal ProjectSnapshotBase<'T when 'T :> IFileSnapshot>(projectCore: ProjectCore, sourceFiles: 'T list) =
+
+ let noFileVersionsHash =
+ lazy
+ (projectCore.Version
+ |> Md5Hasher.addStrings (sourceFiles |> Seq.map (fun x -> x.FileName)))
+
+ let noFileVersionsKey =
+ lazy
+ ({ new ICacheKey<_, _> with
+ member _.GetLabel() = projectCore.Label
+ member _.GetKey() = projectCore.Identifier
+
+ member _.GetVersion() =
+ noFileVersionsHash.Value |> Md5Hasher.toString
+
+ })
+
+ let fullHash =
+ lazy
+ (projectCore.Version
+ |> Md5Hasher.addStrings (
+ sourceFiles
+ |> Seq.collect (fun x ->
+ seq {
+ x.FileName
+ x.Version |> Md5Hasher.toString
+ })
+ ))
+
+ let fullKey =
+ lazy
+ ({ new ICacheKey<_, _> with
+ member _.GetLabel() = projectCore.Label
+ member _.GetKey() = projectCore.Identifier
+ member _.GetVersion() = fullHash.Value |> Md5Hasher.toString
+ })
+
+ let addHash (file: 'T) hash =
+ hash |> Md5Hasher.addString file.FileName |> Md5Hasher.addBytes file.Version
+
+ let signatureHash =
+ lazy (signatureHash projectCore.Version (sourceFiles |> Seq.map (fun x -> x :> IFileSnapshot)))
+
+ let signatureKey =
+ lazy (projectCore.CacheKeyWith("Signature", signatureHash.Value |> fst |> Md5Hasher.toString))
+
+ let lastFileHash =
+ lazy
+ (let lastFile = sourceFiles |> List.last
+ let sigHash, f = signatureHash.Value
+
+ (if f = lastFile.FileName then
+ sigHash
+ else
+ sigHash |> Md5Hasher.addBytes lastFile.Version),
+ lastFile)
+
+ let lastFileKey =
+ lazy
+ (let hash, f = lastFileHash.Value
+
+ { new ICacheKey<_, _> with
+ member _.GetLabel() = $"{f.FileName} ({projectCore.Label})"
+ member _.GetKey() = f.FileName, projectCore.Identifier
+ member _.GetVersion() = hash |> Md5Hasher.toString
+ })
+
+ let sourceFileNames = lazy (sourceFiles |> List.map (fun x -> x.FileName))
+
+ member _.ProjectFileName = projectCore.ProjectFileName
+ member _.ProjectId = projectCore.ProjectId
+ member _.Identifier = projectCore.Identifier
+ member _.ReferencesOnDisk = projectCore.ReferencesOnDisk
+ member _.OtherOptions = projectCore.OtherOptions
+ member _.ReferencedProjects = projectCore.ReferencedProjects
+
+ member _.IsIncompleteTypeCheckEnvironment =
+ projectCore.IsIncompleteTypeCheckEnvironment
+
+ member _.UseScriptResolutionRules = projectCore.UseScriptResolutionRules
+ member _.LoadTime = projectCore.LoadTime
+ member _.UnresolvedReferences = projectCore.UnresolvedReferences
+ member _.OriginalLoadReferences = projectCore.OriginalLoadReferences
+ member _.Stamp = projectCore.Stamp
+ member _.CommandLineOptions = projectCore.CommandLineOptions
+ member _.ProjectDirectory = projectCore.ProjectDirectory
+
+ member _.OutputFileName = projectCore.OutputFileName
+
+ member _.ProjectCore = projectCore
+
+ member _.SourceFiles = sourceFiles
+
+ member _.SourceFileNames = sourceFileNames.Value
+
+ member _.Label = projectCore.Label
+
+ member _.IndexOf fileName =
+ sourceFiles
+ |> List.tryFindIndex (fun x -> x.FileName = fileName)
+ |> Option.defaultWith (fun () -> failwith (sprintf "Unable to find file %s in project %s" fileName projectCore.ProjectFileName))
+
+ member private _.With(sourceFiles: 'T list) =
+ ProjectSnapshotBase(projectCore, sourceFiles)
+
+ /// Create a new snapshot with given source files replacing files in this snapshot with the same name. Other files remain unchanged.
+ member this.Replace(changedSourceFiles: 'T list) =
+ // TODO: validate if changed files are not present in the original list?
+
+ let sourceFiles =
+ sourceFiles
+ |> List.map (fun x ->
+ match changedSourceFiles |> List.tryFind (fun y -> y.FileName = x.FileName) with
+ | Some y -> y
+ | None -> x)
+
+ this.With sourceFiles
+
+ /// Create a new snapshot with source files only up to the given index (inclusive)
+ member this.UpTo fileIndex = this.With sourceFiles[..fileIndex]
+
+ /// Create a new snapshot with source files only up to the given file name (inclusive)
+ member this.UpTo fileName = this.UpTo(this.IndexOf fileName)
+
+ /// Create a new snapshot with only source files at the given indexes
+ member this.OnlyWith fileIndexes =
+ this.With(
+ fileIndexes
+ |> Set.toList
+ |> List.sort
+ |> List.choose (fun x -> sourceFiles |> List.tryItem x)
+ )
+
+ override this.ToString() =
+ Path.GetFileNameWithoutExtension this.ProjectFileName
+ |> sprintf "FSharpProjectSnapshot(%s)"
+
+ /// The newest last modified time of any file in this snapshot including the project file
+ member _.GetLastModifiedTimeOnDisk() =
+ seq {
+ projectCore.ProjectFileName
+
+ yield!
+ sourceFiles
+ |> Seq.filter (fun x -> not (x.FileName.EndsWith(".AssemblyInfo.fs"))) // TODO: is this safe? any better way of doing this?
+ |> Seq.filter (fun x -> not (x.FileName.EndsWith(".AssemblyAttributes.fs")))
+ |> Seq.map (fun x -> x.FileName)
+ }
+ |> Seq.map FileSystem.GetLastWriteTimeShim
+ |> Seq.max
+
+ member _.FullVersion = fullHash.Value
+ member _.SignatureVersion = signatureHash.Value |> fst
+ member _.LastFileVersion = lastFileHash.Value |> fst
+
+ /// Version for parsing - doesn't include any references because they don't affect parsing (...right?)
+ member _.ParsingVersion = projectCore.VersionForParsing |> Md5Hasher.toString
+
+ /// A key for this snapshot but without file versions. So it will be the same across any in-file changes.
+ member _.NoFileVersionsKey = noFileVersionsKey.Value
+
+ /// A full key for this snapshot, any change will cause this to change.
+ member _.FullKey = fullKey.Value
+
+ /// A key including the public surface or signature for this snapshot
+ member _.SignatureKey = signatureKey.Value
+
+ /// A key including the public surface or signature for this snapshot and the last file (even if it's not a signature file)
+ member _.LastFileKey = lastFileKey.Value
+
+ //TODO: cache it here?
+ member this.FileKey(fileName: string) = this.UpTo(fileName).LastFileKey
+ member this.FileKey(index: FileIndex) = this.UpTo(index).LastFileKey
+
+/// Project snapshot with filenames and versions given as initial input
+and internal ProjectSnapshot = ProjectSnapshotBase
+
+/// Project snapshot with file sources loaded
+and internal ProjectSnapshotWithSources = ProjectSnapshotBase
+
+/// All required information for compiling a project except the source files. It's kept separate so it can be reused
+/// for different stages of a project snapshot and also between changes to the source files.
+and internal ProjectCore
+ (
+ ProjectFileName: string,
+ ProjectId: string option,
+ ReferencesOnDisk: ReferenceOnDisk list,
+ OtherOptions: string list,
+ ReferencedProjects: FSharpReferencedProjectSnapshot list,
+ IsIncompleteTypeCheckEnvironment: bool,
+ UseScriptResolutionRules: bool,
+ LoadTime: DateTime,
+ UnresolvedReferences: FSharpUnresolvedReferencesSet option,
+ OriginalLoadReferences: (range * string * string) list,
+ Stamp: int64 option
+ ) as self =
+
+ let hashForParsing =
+ lazy
+ (Md5Hasher.empty
+ |> Md5Hasher.addString ProjectFileName
+ |> Md5Hasher.addStrings OtherOptions
+ |> Md5Hasher.addBool IsIncompleteTypeCheckEnvironment
+ |> Md5Hasher.addBool UseScriptResolutionRules)
+
+ let fullHash =
+ lazy
+ (hashForParsing.Value
+ |> Md5Hasher.addStrings (ReferencesOnDisk |> Seq.map (fun r -> r.Path))
+ |> Md5Hasher.addDateTimes (ReferencesOnDisk |> Seq.map (fun r -> r.LastModified))
+ |> Md5Hasher.addBytes' (
+ ReferencedProjects
+ |> Seq.map (fun (FSharpReference(_name, p)) -> p.ProjectSnapshot.SignatureVersion)
+ ))
+
+ let fullHashString = lazy (fullHash.Value |> Md5Hasher.toString)
+
+ let commandLineOptions =
+ lazy
+ (seq {
+ for r in ReferencesOnDisk do
+ $"-r:{r.Path}"
+
+ yield! OtherOptions
+ }
+ |> Seq.toList)
+
+ let outputFileName = lazy (OtherOptions |> findOutputFileName)
+
+ let key = lazy (ProjectFileName, outputFileName.Value |> Option.defaultValue "")
+
+ let cacheKey =
+ lazy
+ ({ new ICacheKey<_, _> with
+ member _.GetLabel() = self.Label
+ member _.GetKey() = self.Identifier
+ member _.GetVersion() = fullHashString.Value
+ })
+
+ member val ProjectDirectory = Path.GetDirectoryName(ProjectFileName)
+ member _.OutputFileName = outputFileName.Value
+ member _.Identifier: ProjectIdentifier = key.Value
+ member _.Version = fullHash.Value
+ member _.Label = ProjectFileName |> shortPath
+ member _.VersionForParsing = hashForParsing.Value
+
+ member _.CommandLineOptions = commandLineOptions.Value
+
+ member _.ProjectFileName = ProjectFileName
+ member _.ProjectId = ProjectId
+ member _.ReferencesOnDisk = ReferencesOnDisk
+ member _.OtherOptions = OtherOptions
+ member _.ReferencedProjects = ReferencedProjects
+ member _.IsIncompleteTypeCheckEnvironment = IsIncompleteTypeCheckEnvironment
+ member _.UseScriptResolutionRules = UseScriptResolutionRules
+ member _.LoadTime = LoadTime
+ member _.UnresolvedReferences = UnresolvedReferences
+ member _.OriginalLoadReferences = OriginalLoadReferences
+ member _.Stamp = Stamp
+
+ member _.CacheKeyWith(label, version) =
+ { new ICacheKey<_, _> with
+ member _.GetLabel() = $"{label} ({self.Label})"
+ member _.GetKey() = self.Identifier
+ member _.GetVersion() = fullHashString.Value, version
+ }
+
+ member _.CacheKeyWith(label, key, version) =
+ { new ICacheKey<_, _> with
+ member _.GetLabel() = $"{label} ({self.Label})"
+ member _.GetKey() = key, self.Identifier
+ member _.GetVersion() = fullHashString.Value, version
+ }
+
+ member _.CacheKey = cacheKey.Value
+
+and [] FSharpReferencedProjectSnapshot =
+ | FSharpReference of projectOutputFile: string * options: FSharpProjectSnapshot
+ //| PEReference of projectOutputFile: string * getStamp: (unit -> DateTime) * delayedReader: DelayedILModuleReader
+ //| ILModuleReference of
+ // projectOutputFile: string *
+ // getStamp: (unit -> DateTime) *
+ // getReader: (unit -> ILModuleReader)
+
+ ///
+ /// The fully qualified path to the output of the referenced project. This should be the same value as the -r
+ /// reference in the project options for this referenced project.
+ ///
+ member this.OutputFile =
+ match this with
+ | FSharpReference(projectOutputFile, _) -> projectOutputFile
+
+ ///
+ /// Creates a reference for an F# project. The physical data for it is stored/cached inside of the compiler service.
+ ///
+ /// The fully qualified path to the output of the referenced project. This should be the same value as the -r reference in the project options for this referenced project.
+ /// The project snapshot for this F# project
+ static member CreateFSharp(projectOutputFile, snapshot: FSharpProjectSnapshot) =
+ FSharpReference(projectOutputFile, snapshot)
+
+ override this.Equals(o) =
+ match o with
+ | :? FSharpReferencedProjectSnapshot as o ->
+ match this, o with
+ | FSharpReference(projectOutputFile1, options1), FSharpReference(projectOutputFile2, options2) ->
+ projectOutputFile1 = projectOutputFile2 && options1 = options2
+
+ | _ -> false
+
+ override this.GetHashCode() = this.OutputFile.GetHashCode()
+
+/// An identifier of an F# project. This serves to identify the same project as it changes over time and enables us to clear obsolete data from caches.
+and [] FSharpProjectIdentifier =
+ | FSharpProjectIdentifier of projectFileName: string * outputFileName: string
+
+/// A snapshot of an F# project. This type contains all the necessary information for type checking a project.
+and [] FSharpProjectSnapshot internal (projectSnapshot) =
+
+ member internal _.ProjectSnapshot: ProjectSnapshot = projectSnapshot
+
+ /// Create a new snapshot with given source files replacing files in this snapshot with the same name. Other files remain unchanged.
+ member _.Replace(changedSourceFiles: FSharpFileSnapshot list) =
+ projectSnapshot.Replace(changedSourceFiles) |> FSharpProjectSnapshot
+
+ member _.Label = projectSnapshot.Label
+ member _.Identifier = FSharpProjectIdentifier projectSnapshot.ProjectCore.Identifier
+
+ static member Create
+ (
+ projectFileName: string,
+ projectId: string option,
+ sourceFiles: FSharpFileSnapshot list,
+ referencesOnDisk: ReferenceOnDisk list,
+ otherOptions: string list,
+ referencedProjects: FSharpReferencedProjectSnapshot list,
+ isIncompleteTypeCheckEnvironment: bool,
+ useScriptResolutionRules: bool,
+ loadTime: DateTime,
+ unresolvedReferences: FSharpUnresolvedReferencesSet option,
+ originalLoadReferences: (range * string * string) list,
+ stamp: int64 option
+ ) =
+
+ let projectCore =
+ ProjectCore(
+ projectFileName,
+ projectId,
+ referencesOnDisk,
+ otherOptions,
+ referencedProjects,
+ isIncompleteTypeCheckEnvironment,
+ useScriptResolutionRules,
+ loadTime,
+ unresolvedReferences,
+ originalLoadReferences,
+ stamp
+ )
+
+ ProjectSnapshotBase(projectCore, sourceFiles) |> FSharpProjectSnapshot
+
+ static member FromOptions(options: FSharpProjectOptions, getFileSnapshot, ?snapshotAccumulator) =
+ let snapshotAccumulator = defaultArg snapshotAccumulator (Dictionary())
+
+ async {
+
+ // TODO: check if options is a good key here
+ if not (snapshotAccumulator.ContainsKey options) then
+
+ let! sourceFiles = options.SourceFiles |> Seq.map (getFileSnapshot options) |> Async.Parallel
+
+ let! referencedProjects =
+ options.ReferencedProjects
+ |> Seq.choose (function
+ | FSharpReferencedProject.FSharpReference(outputName, options) ->
+ Some(
+ async {
+ let! snapshot = FSharpProjectSnapshot.FromOptions(options, getFileSnapshot, snapshotAccumulator)
+
+ return FSharpReferencedProjectSnapshot.FSharpReference(outputName, snapshot)
+ }
+ )
+ // TODO: other types
+ | _ -> None)
+ |> Async.Sequential
+
+ let referencesOnDisk, otherOptions =
+ options.OtherOptions
+ |> Array.partition (fun x -> x.StartsWith("-r:"))
+ |> map1Of2 (
+ Array.map (fun x ->
+ let path = x.Substring(3)
+
+ {
+ Path = path
+ LastModified = FileSystem.GetLastWriteTimeShim(path)
+ })
+ )
+
+ let snapshot =
+ FSharpProjectSnapshot.Create(
+ projectFileName = options.ProjectFileName,
+ projectId = options.ProjectId,
+ sourceFiles = (sourceFiles |> List.ofArray),
+ referencesOnDisk = (referencesOnDisk |> List.ofArray),
+ otherOptions = (otherOptions |> List.ofArray),
+ referencedProjects = (referencedProjects |> List.ofArray),
+ isIncompleteTypeCheckEnvironment = options.IsIncompleteTypeCheckEnvironment,
+ useScriptResolutionRules = options.UseScriptResolutionRules,
+ loadTime = options.LoadTime,
+ unresolvedReferences = options.UnresolvedReferences,
+ originalLoadReferences = options.OriginalLoadReferences,
+ stamp = options.Stamp
+ )
+
+ snapshotAccumulator.Add(options, snapshot)
+
+ return snapshotAccumulator[options]
+ }
+
+ static member internal GetFileSnapshotFromDisk _ fileName =
+ FSharpFileSnapshot.CreateFromFileSystem fileName |> async.Return
+
+ static member FromOptions(options: FSharpProjectOptions) =
+ FSharpProjectSnapshot.FromOptions(options, FSharpProjectSnapshot.GetFileSnapshotFromDisk)
+
+ static member FromOptions(options: FSharpProjectOptions, fileName: string, fileVersion: int, sourceText: ISourceText) =
+
+ let getFileSnapshot _ fName =
+ if fName = fileName then
+ FSharpFileSnapshot.Create(
+ fileName,
+ $"{fileVersion}{sourceText.GetHashCode().ToString()}",
+ fun () -> Task.FromResult(SourceTextNew.ofISourceText sourceText)
+ )
+ else
+ FSharpFileSnapshot.CreateFromFileSystem fName
+ |> async.Return
+
+ FSharpProjectSnapshot.FromOptions(options, getFileSnapshot)
+
+let rec internal snapshotToOptions (projectSnapshot: ProjectSnapshotBase<_>) =
+ {
+ ProjectFileName = projectSnapshot.ProjectFileName
+ ProjectId = projectSnapshot.ProjectId
+ SourceFiles = projectSnapshot.SourceFiles |> Seq.map (fun x -> x.FileName) |> Seq.toArray
+ OtherOptions = projectSnapshot.CommandLineOptions |> List.toArray
+ ReferencedProjects =
+ projectSnapshot.ReferencedProjects
+ |> Seq.map (function
+ | FSharpReference(name, opts) -> FSharpReferencedProject.FSharpReference(name, opts.ProjectSnapshot |> snapshotToOptions))
+ |> Seq.toArray
+ IsIncompleteTypeCheckEnvironment = projectSnapshot.IsIncompleteTypeCheckEnvironment
+ UseScriptResolutionRules = projectSnapshot.UseScriptResolutionRules
+ LoadTime = projectSnapshot.LoadTime
+ UnresolvedReferences = projectSnapshot.UnresolvedReferences
+ OriginalLoadReferences = projectSnapshot.OriginalLoadReferences
+ Stamp = projectSnapshot.Stamp
+ }
+
+[]
+type internal Extensions =
+
+ []
+ static member ToOptions(this: ProjectSnapshot) = this |> snapshotToOptions
+
+ []
+ static member ToOptions(this: FSharpProjectSnapshot) =
+ this.ProjectSnapshot |> snapshotToOptions
+
+ []
+ static member GetProjectIdentifier(this: FSharpProjectOptions) : ProjectIdentifier =
+ this.ProjectFileName, this.OtherOptions |> findOutputFileName |> Option.defaultValue ""
diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs
index f6289a283ac..f59a1e9b6a5 100644
--- a/src/Compiler/Service/IncrementalBuild.fs
+++ b/src/Compiler/Service/IncrementalBuild.fs
@@ -485,10 +485,19 @@ type BoundModel private (
syntaxTreeOpt
)
-
/// Global service state
-type FrameworkImportsCacheKey = FrameworkImportsCacheKey of resolvedpath: string list * assemblyName: string * targetFrameworkDirectories: string list * fsharpBinaries: string * langVersion: decimal
+type FrameworkImportsCacheKey =
+ | FrameworkImportsCacheKey of resolvedpath: string list * assemblyName: string * targetFrameworkDirectories: string list * fsharpBinaries: string * langVersion: decimal
+
+ interface ICacheKey with
+ member this.GetKey() =
+ this |> function FrameworkImportsCacheKey(assemblyName=a) -> a
+ member this.GetLabel() =
+ this |> function FrameworkImportsCacheKey(assemblyName=a) -> a
+
+ member this.GetVersion() = this
+
/// Represents a cache of 'framework' references that can be shared between multiple incremental builds
type FrameworkImportsCache(size) =
@@ -593,6 +602,7 @@ module Utilities =
/// Constructs the build data (IRawFSharpAssemblyData) representing the assembly when used
/// as a cross-assembly reference. Note the assembly has not been generated on disk, so this is
/// a virtualized view of the assembly contents as computed by background checking.
+[]
type RawFSharpAssemblyDataBackedByLanguageService (tcConfig, tcGlobals, generatedCcu: CcuThunk, outfile, topAttrs, assemblyName, ilAssemRef) =
let exportRemapping = MakeExportRemapping generatedCcu generatedCcu.Contents
diff --git a/src/Compiler/Service/IncrementalBuild.fsi b/src/Compiler/Service/IncrementalBuild.fsi
index b4e60d403f0..0dedfb02948 100644
--- a/src/Compiler/Service/IncrementalBuild.fsi
+++ b/src/Compiler/Service/IncrementalBuild.fsi
@@ -22,6 +22,17 @@ open FSharp.Compiler.TcGlobals
open FSharp.Compiler.Text
open FSharp.Compiler.TypedTree
open FSharp.Compiler.BuildGraph
+open Internal.Utilities.Collections
+
+type internal FrameworkImportsCacheKey =
+ | FrameworkImportsCacheKey of
+ resolvedpath: string list *
+ assemblyName: string *
+ targetFrameworkDirectories: string list *
+ fsharpBinaries: string *
+ langVersion: decimal
+
+ interface ICacheKey
/// Lookup the global static cache for building the FrameworkTcImports
type internal FrameworkImportsCache =
@@ -132,6 +143,20 @@ type internal PartialCheckResults =
member TimeStamp: DateTime
+[]
+type internal RawFSharpAssemblyDataBackedByLanguageService =
+ new:
+ tcConfig: TcConfig *
+ tcGlobals: TcGlobals *
+ generatedCcu: CcuThunk *
+ outfile: string *
+ topAttrs: TopAttribs *
+ assemblyName: string *
+ ilAssemRef: FSharp.Compiler.AbstractIL.IL.ILAssemblyRef ->
+ RawFSharpAssemblyDataBackedByLanguageService
+
+ interface IRawFSharpAssemblyData
+
/// Manages an incremental build graph for the build of an F# project
[]
type internal IncrementalBuilder =
diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs
new file mode 100644
index 00000000000..fe82627483f
--- /dev/null
+++ b/src/Compiler/Service/TransparentCompiler.fs
@@ -0,0 +1,2105 @@
+namespace FSharp.Compiler.CodeAnalysis.TransparentCompiler
+
+open System
+open System.Collections.Generic
+open System.Runtime.CompilerServices
+open System.Diagnostics
+open System.IO
+
+open Internal.Utilities.Collections
+open Internal.Utilities.Library
+
+open FSharp.Compiler
+open FSharp.Compiler.AbstractIL.IL
+open FSharp.Compiler.AbstractIL.ILBinaryReader
+open FSharp.Compiler.BuildGraph
+open FSharp.Compiler.CodeAnalysis
+open FSharp.Compiler.CompilerConfig
+open FSharp.Compiler.CompilerImports
+open FSharp.Compiler.CompilerOptions
+open FSharp.Compiler.CheckBasics
+open FSharp.Compiler.DependencyManager
+open FSharp.Compiler.Diagnostics
+open FSharp.Compiler.DiagnosticsLogger
+open FSharp.Compiler.IO
+open FSharp.Compiler.ScriptClosure
+open FSharp.Compiler.Symbols
+open FSharp.Compiler.TcGlobals
+open FSharp.Compiler.Text
+open FSharp.Compiler.Text.Range
+open FSharp.Compiler.Xml
+open System.Threading.Tasks
+open FSharp.Compiler.ParseAndCheckInputs
+open FSharp.Compiler.GraphChecking
+open FSharp.Compiler.Syntax
+open FSharp.Compiler.CompilerDiagnostics
+open FSharp.Compiler.NameResolution
+open Internal.Utilities.Library.Extras
+open FSharp.Compiler.TypedTree
+open FSharp.Compiler.CheckDeclarations
+open FSharp.Compiler.EditorServices
+open FSharp.Compiler.CodeAnalysis
+open FSharp.Compiler.CreateILModule
+open FSharp.Compiler.TypedTreeOps
+open System.Threading
+open Internal.Utilities.Hashing
+
+open FSharp.Compiler.CodeAnalysis.ProjectSnapshot
+
+/// Accumulated results of type checking. The minimum amount of state in order to continue type-checking following files.
+[]
+type internal TcInfo =
+ {
+ tcState: TcState
+ tcEnvAtEndOfFile: TcEnv
+
+ /// Disambiguation table for module names
+ moduleNamesDict: ModuleNamesDict
+
+ topAttribs: TopAttribs option
+
+ latestCcuSigForFile: ModuleOrNamespaceType option
+
+ /// Accumulated diagnostics, last file first
+ tcDiagnosticsRev: (PhasedDiagnostic * FSharpDiagnosticSeverity)[] list
+
+ tcDependencyFiles: string list
+
+ sigNameOpt: (string * QualifiedNameOfFile) option
+
+ graphNode: NodeToTypeCheck option
+
+ stateContainsNodes: Set
+
+ sink: TcResultsSinkImpl list
+ }
+
+ member x.TcDiagnostics = Array.concat (List.rev x.tcDiagnosticsRev)
+
+[]
+type internal TcIntermediate =
+ {
+ finisher: Finisher
+ //tcEnvAtEndOfFile: TcEnv
+
+ /// Disambiguation table for module names
+ moduleNamesDict: ModuleNamesDict
+
+ /// Accumulated diagnostics, last file first
+ tcDiagnosticsRev: (PhasedDiagnostic * FSharpDiagnosticSeverity)[] list
+
+ tcDependencyFiles: string list
+
+ sink: TcResultsSinkImpl
+ }
+
+/// Things we need to start parsing and checking files for a given project snapshot
+type internal BootstrapInfo =
+ {
+ // Each instance gets an Id on creation, unfortunately partial type check results using different instances are not compatible
+ // So if this needs to be recreated for whatever reason then we need to re type check all files
+ Id: int
+
+ AssemblyName: string
+ OutFile: string
+ TcConfig: TcConfig
+ TcImports: TcImports
+ TcGlobals: TcGlobals
+ InitialTcInfo: TcInfo
+
+ // TODO: Figure out how these work and if they need to be added to the snapshot...
+ LoadedSources: (range * FSharpFileSnapshot) list
+
+ // TODO: Might be a bit more complicated if we want to support adding files to the project via OtherOptions
+ // ExtraSourceFilesAfter: FSharpFileSnapshot list
+
+ LoadClosure: LoadClosure option
+ LastFileName: string
+ }
+
+type internal TcIntermediateResult = TcInfo * TcResultsSinkImpl * CheckedImplFile option * string
+
+[]
+type internal DependencyGraphType =
+ /// A dependency graph for a single file - it will be missing files which this file does not depend on
+ | File
+ /// A dependency graph for a project - it will contain all files in the project
+ | Project
+
+[]
+type internal Extensions =
+ []
+ static member Key<'T when 'T :> IFileSnapshot>(fileSnapshots: 'T list, ?extraKeyFlag) =
+
+ { new ICacheKey<_, _> with
+ member _.GetLabel() =
+ let lastFile =
+ fileSnapshots
+ |> List.tryLast
+ |> Option.map (fun f -> f.FileName |> shortPath)
+ |> Option.defaultValue "[no file]"
+
+ $"%d{fileSnapshots.Length} files ending with {lastFile}"
+
+ member _.GetKey() =
+ Md5Hasher.empty
+ |> Md5Hasher.addStrings (fileSnapshots |> Seq.map (fun f -> f.FileName))
+ |> pair extraKeyFlag
+
+ member _.GetVersion() =
+ Md5Hasher.empty
+ |> Md5Hasher.addBytes' (fileSnapshots |> Seq.map (fun f -> f.Version))
+ |> Md5Hasher.toString
+ }
+
+[]
+module private TypeCheckingGraphProcessing =
+ open FSharp.Compiler.GraphChecking.GraphProcessing
+
+ // TODO Do we need to suppress some error logging if we
+ // TODO apply the same partial results multiple times?
+ // TODO Maybe we can enable logging only for the final fold
+ ///
+ /// Combine type-checking results of dependencies needed to type-check a 'higher' node in the graph
+ ///
+ /// Initial state
+ /// Direct dependencies of a node
+ /// Transitive dependencies of a node
+ /// A way to fold a single result into existing state
+ let private combineResults
+ (emptyState: TcInfo)
+ (deps: ProcessedNode> array)
+ (transitiveDeps: ProcessedNode> array)
+ (folder: TcInfo -> Finisher -> TcInfo)
+ : TcInfo =
+ match deps with
+ | [||] -> emptyState
+ | _ ->
+ // Instead of starting with empty state,
+ // reuse state produced by the dependency with the biggest number of transitive dependencies.
+ // This is to reduce the number of folds required to achieve the final state.
+ let biggestDependency =
+ let sizeMetric (node: ProcessedNode<_, _>) = node.Info.TransitiveDeps.Length
+ deps |> Array.maxBy sizeMetric
+
+ let firstState = biggestDependency.Result |> fst
+
+ // Find items not already included in the state.
+ let itemsPresent =
+ set
+ [|
+ yield! biggestDependency.Info.TransitiveDeps
+ yield biggestDependency.Info.Item
+ |]
+
+ let resultsToAdd =
+ transitiveDeps
+ |> Array.filter (fun dep -> itemsPresent.Contains dep.Info.Item = false)
+ |> Array.distinctBy (fun dep -> dep.Info.Item)
+ |> Array.sortWith (fun a b ->
+ // We preserve the order in which items are folded to the state.
+ match a.Info.Item, b.Info.Item with
+ | NodeToTypeCheck.PhysicalFile aIdx, NodeToTypeCheck.PhysicalFile bIdx
+ | NodeToTypeCheck.ArtificialImplFile aIdx, NodeToTypeCheck.ArtificialImplFile bIdx -> aIdx.CompareTo bIdx
+ | NodeToTypeCheck.PhysicalFile _, NodeToTypeCheck.ArtificialImplFile _ -> -1
+ | NodeToTypeCheck.ArtificialImplFile _, NodeToTypeCheck.PhysicalFile _ -> 1)
+ |> Array.map (fun dep -> dep.Result |> snd)
+
+ // Fold results not already included and produce the final state
+ let state = Array.fold folder firstState resultsToAdd
+ state
+
+ ///
+ /// Process a graph of items.
+ /// A version of 'GraphProcessing.processGraph' with a signature specific to type-checking.
+ ///
+ let processTypeCheckingGraph
+ (graph: Graph)
+ (work: NodeToTypeCheck -> TcInfo -> Async>)
+ (emptyState: TcInfo)
+ : Async<(int * PartialResult) list * TcInfo> =
+ async {
+
+ let workWrapper
+ (getProcessedNode:
+ NodeToTypeCheck -> ProcessedNode>)
+ (node: NodeInfo)
+ : Async> =
+ async {
+ let folder (state: TcInfo) (Finisher(finisher = finisher)) : TcInfo = finisher state |> snd
+ let deps = node.Deps |> Array.except [| node.Item |] |> Array.map getProcessedNode
+
+ let transitiveDeps =
+ node.TransitiveDeps
+ |> Array.except [| node.Item |]
+ |> Array.map getProcessedNode
+
+ let inputState = combineResults emptyState deps transitiveDeps folder
+
+ let! singleRes = work node.Item inputState
+ let state = folder inputState singleRes
+ return state, singleRes
+ }
+
+ let! results = processGraphAsync graph workWrapper
+
+ let finalFileResults, state =
+ (([], emptyState),
+ results
+ |> Array.choose (fun (item, res) ->
+ match item with
+ | NodeToTypeCheck.ArtificialImplFile _ -> None
+ | NodeToTypeCheck.PhysicalFile file -> Some(file, res)))
+ ||> Array.fold (fun (fileResults, state) (item, (_, Finisher(finisher = finisher))) ->
+ let fileResult, state = finisher state
+ (item, fileResult) :: fileResults, state)
+
+ return finalFileResults, state
+ }
+
+type internal CompilerCaches(sizeFactor: int) =
+
+ let sf = sizeFactor
+
+ member _.SizeFactor = sf
+
+ member val ParseFile = AsyncMemoize(keepStrongly = 50 * sf, keepWeakly = 20 * sf, name = "ParseFile")
+
+ member val ParseAndCheckFileInProject = AsyncMemoize(sf, 2 * sf, name = "ParseAndCheckFileInProject")
+
+ member val ParseAndCheckAllFilesInProject = AsyncMemoizeDisabled(sf, 2 * sf, name = "ParseAndCheckFullProject")
+
+ member val ParseAndCheckProject = AsyncMemoize(sf, 2 * sf, name = "ParseAndCheckProject")
+
+ member val FrameworkImports = AsyncMemoize(sf, 2 * sf, name = "FrameworkImports")
+
+ member val BootstrapInfoStatic = AsyncMemoize(sf, 2 * sf, name = "BootstrapInfoStatic")
+
+ member val BootstrapInfo = AsyncMemoize(sf, 2 * sf, name = "BootstrapInfo")
+
+ member val TcLastFile = AsyncMemoizeDisabled(sf, 2 * sf, name = "TcLastFile")
+
+ member val TcIntermediate = AsyncMemoize(20 * sf, 20 * sf, name = "TcIntermediate")
+
+ member val DependencyGraph = AsyncMemoize(sf, 2 * sf, name = "DependencyGraph")
+
+ member val ProjectExtras = AsyncMemoizeDisabled(sf, 2 * sf, name = "ProjectExtras")
+
+ member val AssemblyData = AsyncMemoize(sf, 2 * sf, name = "AssemblyData")
+
+ member val SemanticClassification = AsyncMemoize(sf, 2 * sf, name = "SemanticClassification")
+
+ member val ItemKeyStore = AsyncMemoize(sf, 2 * sf, name = "ItemKeyStore")
+
+ member this.Clear(projects: Set) =
+ let shouldClear project = projects |> Set.contains project
+
+ this.ParseFile.Clear(fst >> shouldClear)
+ this.ParseAndCheckFileInProject.Clear(snd >> shouldClear)
+ this.ParseAndCheckProject.Clear(shouldClear)
+ this.BootstrapInfoStatic.Clear(shouldClear)
+ this.BootstrapInfo.Clear(shouldClear)
+ this.TcIntermediate.Clear(snd >> shouldClear)
+ this.AssemblyData.Clear(shouldClear)
+ this.SemanticClassification.Clear(snd >> shouldClear)
+ this.ItemKeyStore.Clear(snd >> shouldClear)
+
+type internal TransparentCompiler
+ (
+ legacyReferenceResolver,
+ projectCacheSize,
+ keepAssemblyContents,
+ keepAllBackgroundResolutions,
+ tryGetMetadataSnapshot,
+ suggestNamesForErrors,
+ keepAllBackgroundSymbolUses,
+ enableBackgroundItemKeyStoreAndSemanticClassification,
+ enablePartialTypeChecking,
+ parallelReferenceResolution,
+ captureIdentifiersWhenParsing,
+ getSource: (string -> Async) option,
+ useChangeNotifications,
+ useSyntaxTreeCache
+ ) as self =
+
+ // Is having just one of these ok?
+ let lexResourceManager = Lexhelp.LexResourceManager()
+
+ // Mutable so we can easily clear them by creating a new instance
+ let mutable caches = CompilerCaches(100)
+
+ // TODO: do we need this?
+ //let maxTypeCheckingParallelism = max 1 (Environment.ProcessorCount / 2)
+ //let maxParallelismSemaphore = new SemaphoreSlim(maxTypeCheckingParallelism)
+
+ // We currently share one global dependency provider for all scripts for the FSharpChecker.
+ // For projects, one is used per project.
+ //
+ // Sharing one for all scripts is necessary for good performance from GetProjectOptionsFromScript,
+ // which requires a dependency provider to process through the project options prior to working out
+ // if the cached incremental builder can be used for the project.
+ let dependencyProviderForScripts = new DependencyProvider()
+
+ // Legacy events, they're used in tests... eventually they should go away
+ let beforeFileChecked = Event()
+ let fileParsed = Event()
+ let fileChecked = Event()
+ let projectChecked = Event()
+
+ // use this to process not-yet-implemented tasks
+ let backgroundCompiler =
+ BackgroundCompiler(
+ legacyReferenceResolver,
+ projectCacheSize,
+ keepAssemblyContents,
+ keepAllBackgroundResolutions,
+ tryGetMetadataSnapshot,
+ suggestNamesForErrors,
+ keepAllBackgroundSymbolUses,
+ enableBackgroundItemKeyStoreAndSemanticClassification,
+ enablePartialTypeChecking,
+ parallelReferenceResolution,
+ captureIdentifiersWhenParsing,
+ getSource,
+ useChangeNotifications,
+ useSyntaxTreeCache
+ )
+ :> IBackgroundCompiler
+
+ let ComputeFrameworkImports (tcConfig: TcConfig) frameworkDLLs nonFrameworkResolutions =
+ let frameworkDLLsKey =
+ frameworkDLLs
+ |> List.map (fun ar -> ar.resolvedPath) // The cache key. Just the minimal data.
+ |> List.sort // Sort to promote cache hits.
+
+ // The data elements in this key are very important. There should be nothing else in the TcConfig that logically affects
+ // the import of a set of framework DLLs into F# CCUs. That is, the F# CCUs that result from a set of DLLs (including
+ // FSharp.Core.dll and mscorlib.dll) must be logically invariant of all the other compiler configuration parameters.
+ let key =
+ FrameworkImportsCacheKey(
+ frameworkDLLsKey,
+ tcConfig.primaryAssembly.Name,
+ tcConfig.GetTargetFrameworkDirectories(),
+ tcConfig.fsharpBinariesDir,
+ tcConfig.langVersion.SpecifiedVersion
+ )
+
+ caches.FrameworkImports.Get(
+ key,
+ node {
+ use _ = Activity.start "ComputeFrameworkImports" []
+ let tcConfigP = TcConfigProvider.Constant tcConfig
+
+ return! TcImports.BuildFrameworkTcImports(tcConfigP, frameworkDLLs, nonFrameworkResolutions)
+ }
+ )
+
+ // Link all the assemblies together and produce the input typecheck accumulator
+ let CombineImportedAssembliesTask
+ (
+ assemblyName,
+ tcConfig: TcConfig,
+ tcConfigP,
+ tcGlobals,
+ frameworkTcImports,
+ nonFrameworkResolutions,
+ unresolvedReferences,
+ dependencyProvider,
+ loadClosureOpt: LoadClosure option,
+ basicDependencies,
+ importsInvalidatedByTypeProvider: Event
+ ) =
+
+ node {
+ let diagnosticsLogger =
+ CompilationDiagnosticLogger("CombineImportedAssembliesTask", tcConfig.diagnosticsOptions)
+
+ use _ = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Parameter)
+
+ let! tcImports =
+ node {
+ try
+ let! tcImports =
+ TcImports.BuildNonFrameworkTcImports(
+ tcConfigP,
+ frameworkTcImports,
+ nonFrameworkResolutions,
+ unresolvedReferences,
+ dependencyProvider
+ )
+#if !NO_TYPEPROVIDERS
+ // TODO: review and handle the event
+ tcImports.GetCcusExcludingBase()
+ |> Seq.iter (fun ccu ->
+ // When a CCU reports an invalidation, merge them together and just report a
+ // general "imports invalidated". This triggers a rebuild.
+ //
+ // We are explicit about what the handler closure captures to help reason about the
+ // lifetime of captured objects, especially in case the type provider instance gets leaked
+ // or keeps itself alive mistakenly, e.g. via some global state in the type provider instance.
+ //
+ // The handler only captures
+ // 1. a weak reference to the importsInvalidated event.
+ //
+ // The IncrementalBuilder holds the strong reference the importsInvalidated event.
+ //
+ // In the invalidation handler we use a weak reference to allow the IncrementalBuilder to
+ // be collected if, for some reason, a TP instance is not disposed or not GC'd.
+ let capturedImportsInvalidated = WeakReference<_>(importsInvalidatedByTypeProvider)
+
+ ccu.Deref.InvalidateEvent.Add(fun _ ->
+ match capturedImportsInvalidated.TryGetTarget() with
+ | true, tg -> tg.Trigger()
+ | _ -> ()))
+#endif
+#if NO_TYPEPROVIDERS
+ ignore importsInvalidatedByTypeProvider
+#endif
+ return tcImports
+ with
+ | :? OperationCanceledException ->
+ // if it's been canceled then it shouldn't be needed anymore
+ return frameworkTcImports
+ | exn ->
+ Debug.Assert(false, sprintf "Could not BuildAllReferencedDllTcImports %A" exn)
+ diagnosticsLogger.Warning exn
+ return frameworkTcImports
+ }
+
+ let tcInitial, openDecls0 =
+ GetInitialTcEnv(assemblyName, rangeStartup, tcConfig, tcImports, tcGlobals)
+
+ let tcState =
+ GetInitialTcState(rangeStartup, assemblyName, tcConfig, tcGlobals, tcImports, tcInitial, openDecls0)
+
+ let loadClosureErrors =
+ [
+ match loadClosureOpt with
+ | None -> ()
+ | Some loadClosure ->
+ for inp in loadClosure.Inputs do
+ yield! inp.MetaCommandDiagnostics
+ ]
+
+ let initialErrors =
+ Array.append (Array.ofList loadClosureErrors) (diagnosticsLogger.GetDiagnostics())
+
+ let tcInfo =
+ {
+ tcState = tcState
+ tcEnvAtEndOfFile = tcInitial
+ topAttribs = None
+ latestCcuSigForFile = None
+ tcDiagnosticsRev = [ initialErrors ]
+ moduleNamesDict = Map.empty
+ tcDependencyFiles = basicDependencies
+ sigNameOpt = None
+ graphNode = None
+ stateContainsNodes = Set.empty
+ sink = []
+ }
+
+ return tcImports, tcInfo
+ }
+
+ let getProjectReferences (project: ProjectSnapshotBase<_>) userOpName =
+ [
+ for r in project.ReferencedProjects do
+
+ match r with
+ | FSharpReferencedProjectSnapshot.FSharpReference(nm, projectSnapshot) ->
+ // Don't use cross-project references for FSharp.Core, since various bits of code
+ // require a concrete FSharp.Core to exist on-disk. The only solutions that have
+ // these cross-project references to FSharp.Core are VisualFSharp.sln and FSharp.sln. The ramification
+ // of this is that you need to build FSharp.Core to get intellisense in those projects.
+
+ if
+ (try
+ Path.GetFileNameWithoutExtension(nm)
+ with _ ->
+ "")
+ <> GetFSharpCoreLibraryName()
+ then
+ { new IProjectReference with
+ member x.EvaluateRawContents() =
+ node {
+ Trace.TraceInformation("FCS: {0}.{1} ({2})", userOpName, "GetAssemblyData", nm)
+
+ return!
+ self.GetAssemblyData(
+ projectSnapshot.ProjectSnapshot,
+ nm,
+ userOpName + ".CheckReferencedProject(" + nm + ")"
+ )
+ }
+
+ member x.TryGetLogicalTimeStamp(cache) =
+ // TODO:
+ None
+
+ member x.FileName = nm
+ }
+ ]
+
+ let ComputeTcConfigBuilder (projectSnapshot: ProjectSnapshotBase<_>) =
+
+ let useSimpleResolutionSwitch = "--simpleresolution"
+ let commandLineArgs = projectSnapshot.CommandLineOptions
+ let defaultFSharpBinariesDir = FSharpCheckerResultsSettings.defaultFSharpBinariesDir
+ let useScriptResolutionRules = projectSnapshot.UseScriptResolutionRules
+
+ let projectReferences =
+ getProjectReferences projectSnapshot "ComputeTcConfigBuilder"
+
+ // TODO: script support
+ let loadClosureOpt: LoadClosure option = None
+
+ let getSwitchValue (switchString: string) =
+ match commandLineArgs |> List.tryFindIndex (fun s -> s.StartsWithOrdinal switchString) with
+ | Some idx -> Some(commandLineArgs[idx].Substring(switchString.Length))
+ | _ -> None
+
+ let sdkDirOverride =
+ match loadClosureOpt with
+ | None -> None
+ | Some loadClosure -> loadClosure.SdkDirOverride
+
+ // see also fsc.fs: runFromCommandLineToImportingAssemblies(), as there are many similarities to where the PS creates a tcConfigB
+ let tcConfigB =
+ TcConfigBuilder.CreateNew(
+ legacyReferenceResolver,
+ defaultFSharpBinariesDir,
+ implicitIncludeDir = projectSnapshot.ProjectDirectory,
+ reduceMemoryUsage = ReduceMemoryFlag.Yes,
+ isInteractive = useScriptResolutionRules,
+ isInvalidationSupported = true,
+ defaultCopyFSharpCore = CopyFSharpCoreFlag.No,
+ tryGetMetadataSnapshot = tryGetMetadataSnapshot,
+ sdkDirOverride = sdkDirOverride,
+ rangeForErrors = range0
+ )
+
+ tcConfigB.primaryAssembly <-
+ match loadClosureOpt with
+ | None -> PrimaryAssembly.Mscorlib
+ | Some loadClosure ->
+ if loadClosure.UseDesktopFramework then
+ PrimaryAssembly.Mscorlib
+ else
+ PrimaryAssembly.System_Runtime
+
+ tcConfigB.resolutionEnvironment <- (LegacyResolutionEnvironment.EditingOrCompilation true)
+
+ tcConfigB.conditionalDefines <-
+ let define =
+ if useScriptResolutionRules then
+ "INTERACTIVE"
+ else
+ "COMPILED"
+
+ define :: tcConfigB.conditionalDefines
+
+ tcConfigB.projectReferences <- projectReferences
+
+ tcConfigB.useSimpleResolution <- (getSwitchValue useSimpleResolutionSwitch) |> Option.isSome
+
+ // Apply command-line arguments and collect more source files if they are in the arguments
+ let sourceFilesNew =
+ ApplyCommandLineArgs(tcConfigB, projectSnapshot.SourceFileNames, commandLineArgs)
+
+ // Never open PDB files for the language service, even if --standalone is specified
+ tcConfigB.openDebugInformationForLaterStaticLinking <- false
+
+ tcConfigB.xmlDocInfoLoader <-
+ { new IXmlDocumentationInfoLoader with
+ /// Try to load xml documentation associated with an assembly by the same file path with the extension ".xml".
+ member _.TryLoad(assemblyFileName) =
+ let xmlFileName = Path.ChangeExtension(assemblyFileName, ".xml")
+
+ // REVIEW: File IO - Will eventually need to change this to use a file system interface of some sort.
+ XmlDocumentationInfo.TryCreateFromFile(xmlFileName)
+ }
+ |> Some
+
+ tcConfigB.parallelReferenceResolution <- parallelReferenceResolution
+ tcConfigB.captureIdentifiersWhenParsing <- captureIdentifiersWhenParsing
+
+ tcConfigB, sourceFilesNew, loadClosureOpt
+
+ let mutable BootstrapInfoIdCounter = 0
+
+ /// Bootstrap info that does not depend source files
+ let ComputeBootstrapInfoStatic (projectSnapshot: ProjectCore, tcConfig: TcConfig, assemblyName: string, loadClosureOpt) =
+
+ caches.BootstrapInfoStatic.Get(
+ projectSnapshot.CacheKeyWith("BootstrapInfoStatic", assemblyName),
+ node {
+ use _ =
+ Activity.start
+ "ComputeBootstrapInfoStatic"
+ [|
+ Activity.Tags.project, projectSnapshot.ProjectFileName |> Path.GetFileName
+ "references", projectSnapshot.ReferencedProjects.Length.ToString()
+ |]
+
+ // Resolve assemblies and create the framework TcImports. This caches a level of "system" references. No type providers are
+ // included in these references.
+
+ let frameworkDLLs, nonFrameworkResolutions, unresolvedReferences =
+ TcAssemblyResolutions.SplitNonFoundationalResolutions(tcConfig)
+
+ // Prepare the frameworkTcImportsCache
+ let! tcGlobals, frameworkTcImports = ComputeFrameworkImports tcConfig frameworkDLLs nonFrameworkResolutions
+
+ // Note we are not calling diagnosticsLogger.GetDiagnostics() anywhere for this task.
+ // This is ok because not much can actually go wrong here.
+ let diagnosticsLogger =
+ CompilationDiagnosticLogger("nonFrameworkAssemblyInputs", tcConfig.diagnosticsOptions)
+
+ use _ = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Parameter)
+
+ let tcConfigP = TcConfigProvider.Constant tcConfig
+
+ let importsInvalidatedByTypeProvider = Event()
+
+ let basicDependencies =
+ [
+ for UnresolvedAssemblyReference(referenceText, _) in unresolvedReferences do
+ // Exclude things that are definitely not a file name
+ if not (FileSystem.IsInvalidPathShim referenceText) then
+ let file =
+ if FileSystem.IsPathRootedShim referenceText then
+ referenceText
+ else
+ Path.Combine(projectSnapshot.ProjectDirectory, referenceText)
+
+ yield file
+
+ for r in nonFrameworkResolutions do
+ yield r.resolvedPath
+ ]
+
+ // For scripts, the dependency provider is already available.
+ // For projects create a fresh one for the project.
+ let dependencyProvider =
+ if projectSnapshot.UseScriptResolutionRules then
+ dependencyProviderForScripts
+ else
+ new DependencyProvider()
+
+ let! tcImports, initialTcInfo =
+ CombineImportedAssembliesTask(
+ assemblyName,
+ tcConfig,
+ tcConfigP,
+ tcGlobals,
+ frameworkTcImports,
+ nonFrameworkResolutions,
+ unresolvedReferences,
+ dependencyProvider,
+ loadClosureOpt,
+ basicDependencies,
+ importsInvalidatedByTypeProvider
+ )
+
+ let bootstrapId = Interlocked.Increment &BootstrapInfoIdCounter
+
+ return bootstrapId, tcImports, tcGlobals, initialTcInfo, importsInvalidatedByTypeProvider
+ }
+ )
+
+ let computeBootstrapInfoInner (projectSnapshot: ProjectSnapshot) =
+ node {
+
+ let tcConfigB, sourceFiles, loadClosureOpt = ComputeTcConfigBuilder projectSnapshot
+
+ // If this is a builder for a script, re-apply the settings inferred from the
+ // script and its load closure to the configuration.
+ //
+ // NOTE: it would probably be cleaner and more accurate to re-run the load closure at this point.
+ let setupConfigFromLoadClosure () =
+ match loadClosureOpt with
+ | Some loadClosure ->
+ let dllReferences =
+ [
+ for reference in tcConfigB.referencedDLLs do
+ // If there's (one or more) resolutions of closure references then yield them all
+ match
+ loadClosure.References
+ |> List.tryFind (fun (resolved, _) -> resolved = reference.Text)
+ with
+ | Some(resolved, closureReferences) ->
+ for closureReference in closureReferences do
+ yield AssemblyReference(closureReference.originalReference.Range, resolved, None)
+ | None -> yield reference
+ ]
+
+ tcConfigB.referencedDLLs <- []
+
+ tcConfigB.primaryAssembly <-
+ (if loadClosure.UseDesktopFramework then
+ PrimaryAssembly.Mscorlib
+ else
+ PrimaryAssembly.System_Runtime)
+ // Add one by one to remove duplicates
+ dllReferences
+ |> List.iter (fun dllReference -> tcConfigB.AddReferencedAssemblyByPath(dllReference.Range, dllReference.Text))
+
+ tcConfigB.knownUnresolvedReferences <- loadClosure.UnresolvedReferences
+ | None -> ()
+
+ setupConfigFromLoadClosure ()
+
+ let tcConfig = TcConfig.Create(tcConfigB, validate = true)
+ let outFile, _, assemblyName = tcConfigB.DecideNames sourceFiles
+
+ let! bootstrapId, tcImports, tcGlobals, initialTcInfo, _importsInvalidatedByTypeProvider =
+ ComputeBootstrapInfoStatic(projectSnapshot.ProjectCore, tcConfig, assemblyName, loadClosureOpt)
+
+ // Check for the existence of loaded sources and prepend them to the sources list if present.
+ let loadedSources =
+ tcConfig.GetAvailableLoadedSources()
+ |> List.map (fun (m, fileName) -> m, FSharpFileSnapshot.CreateFromFileSystem(fileName))
+
+ return
+ Some
+ {
+ Id = bootstrapId
+ AssemblyName = assemblyName
+ OutFile = outFile
+ TcConfig = tcConfig
+ TcImports = tcImports
+ TcGlobals = tcGlobals
+ InitialTcInfo = initialTcInfo
+ LoadedSources = loadedSources
+ LoadClosure = loadClosureOpt
+ LastFileName = sourceFiles |> List.last
+ //ImportsInvalidatedByTypeProvider = importsInvalidatedByTypeProvider
+ }
+ }
+
+ let ComputeBootstrapInfo (projectSnapshot: ProjectSnapshot) =
+
+ caches.BootstrapInfo.Get(
+ projectSnapshot.NoFileVersionsKey,
+ node {
+ use _ =
+ Activity.start "ComputeBootstrapInfo" [| Activity.Tags.project, projectSnapshot.ProjectFileName |> Path.GetFileName |]
+
+ // Trap and report diagnostics from creation.
+ let delayedLogger = CapturingDiagnosticsLogger("IncrementalBuilderCreation")
+ use _ = new CompilationGlobalsScope(delayedLogger, BuildPhase.Parameter)
+
+ let! bootstrapInfoOpt =
+ node {
+ try
+ return! computeBootstrapInfoInner projectSnapshot
+ with exn ->
+ errorRecoveryNoRange exn
+ return None
+ }
+
+ let diagnostics =
+ match bootstrapInfoOpt with
+ | Some bootstrapInfo ->
+ let diagnosticsOptions = bootstrapInfo.TcConfig.diagnosticsOptions
+
+ let diagnosticsLogger =
+ CompilationDiagnosticLogger("IncrementalBuilderCreation", diagnosticsOptions)
+
+ delayedLogger.CommitDelayedDiagnostics diagnosticsLogger
+ diagnosticsLogger.GetDiagnostics()
+ | _ -> Array.ofList delayedLogger.Diagnostics
+ |> Array.map (fun (diagnostic, severity) ->
+ let flatErrors =
+ bootstrapInfoOpt
+ |> Option.map (fun bootstrapInfo -> bootstrapInfo.TcConfig.flatErrors)
+ |> Option.defaultValue false // TODO: do we need to figure this out?
+
+ FSharpDiagnostic.CreateFromException(diagnostic, severity, range.Zero, suggestNamesForErrors, flatErrors, None))
+
+ return bootstrapInfoOpt, diagnostics
+ }
+ )
+
+ // TODO: Not sure if we should cache this. For VS probably not. Maybe it can be configurable by FCS user.
+ let LoadSource (file: FSharpFileSnapshot) isExe isLastCompiland =
+ node {
+ let! source = file.GetSource() |> NodeCode.AwaitTask
+
+ return
+ FSharpFileSnapshotWithSource(
+ FileName = file.FileName,
+ Source = source,
+ SourceHash = source.GetChecksum(),
+ IsLastCompiland = isLastCompiland,
+ IsExe = isExe
+ )
+ }
+
+ let LoadSources (bootstrapInfo: BootstrapInfo) (projectSnapshot: ProjectSnapshot) =
+ node {
+ let isExe = bootstrapInfo.TcConfig.target.IsExe
+
+ let! sources =
+ projectSnapshot.SourceFiles
+ |> Seq.map (fun f -> LoadSource f isExe (f.FileName = bootstrapInfo.LastFileName))
+ |> NodeCode.Parallel
+
+ return ProjectSnapshotWithSources(projectSnapshot.ProjectCore, sources |> Array.toList)
+
+ }
+
+ let ComputeParseFile (projectSnapshot: ProjectSnapshotBase<_>) (tcConfig: TcConfig) (file: FSharpFileSnapshotWithSource) =
+
+ let key =
+ { new ICacheKey<_, _> with
+ member _.GetLabel() = file.FileName |> shortPath
+
+ member _.GetKey() =
+ projectSnapshot.ProjectCore.Identifier, file.FileName
+
+ member _.GetVersion() =
+ projectSnapshot.ParsingVersion,
+ file.StringVersion,
+ // TODO: is there a situation where this is not enough and we need to have them separate?
+ file.IsLastCompiland && file.IsExe
+ }
+
+ caches.ParseFile.Get(
+ key,
+ node {
+ use _ =
+ Activity.start
+ "ComputeParseFile"
+ [|
+ Activity.Tags.fileName, file.FileName |> shortPath
+ Activity.Tags.version, file.StringVersion
+ |]
+
+ let diagnosticsLogger =
+ CompilationDiagnosticLogger("Parse", tcConfig.diagnosticsOptions)
+ // Return the disposable object that cleans up
+ use _holder = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Parse)
+
+ let flags = file.IsLastCompiland, file.IsExe
+ let fileName = file.FileName
+ let sourceText = file.Source
+
+ let input =
+ ParseOneInputSourceText(tcConfig, lexResourceManager, fileName, flags, diagnosticsLogger, sourceText)
+
+ // TODO: Hashing of syntax tree
+ let inputHash = file.Version
+
+ fileParsed.Trigger(fileName, Unchecked.defaultof<_>)
+
+ return FSharpParsedFile(fileName, inputHash, sourceText, input, diagnosticsLogger.GetDiagnostics())
+ }
+ )
+
+ // In case we don't want to use any parallel processing
+ let mkLinearGraph count : Graph =
+ seq {
+ 0, [||]
+
+ yield!
+ [ 0 .. count - 1 ]
+ |> Seq.rev
+ |> Seq.pairwise
+ |> Seq.map (fun (a, b) -> a, [| b |])
+ }
+ |> Graph.make
+
+ let computeDependencyGraph (tcConfig: TcConfig) parsedInputs (processGraph: Graph -> Graph) =
+ node {
+ let sourceFiles: FileInProject array =
+ parsedInputs
+ |> Seq.toArray
+ |> Array.mapi (fun idx (input: ParsedInput) ->
+ {
+ Idx = idx
+ FileName = input.FileName
+ ParsedInput = input
+ })
+
+ use _ =
+ Activity.start "ComputeDependencyGraph" [| Activity.Tags.fileName, (sourceFiles |> Array.last).FileName |]
+
+ let filePairs = FilePairMap(sourceFiles)
+
+ // TODO: we will probably want to cache and re-use larger graphs if available
+
+ let graph =
+ if tcConfig.compilingFSharpCore then
+ mkLinearGraph sourceFiles.Length
+ else
+ DependencyResolution.mkGraph filePairs sourceFiles |> fst |> processGraph
+
+ let nodeGraph = TransformDependencyGraph(graph, filePairs)
+
+ let fileNames =
+ parsedInputs
+ |> Seq.mapi (fun idx input -> idx, Path.GetFileName input.FileName)
+ |> Map.ofSeq
+
+ let debugGraph =
+ nodeGraph
+ |> Graph.map (function
+ | NodeToTypeCheck.PhysicalFile i -> i, $"[{i}] {fileNames[i]}"
+ | NodeToTypeCheck.ArtificialImplFile i -> -(i + 1), $"AIF [{i}] : {fileNames[i]}")
+ |> Graph.serialiseToMermaid
+
+ //Trace.TraceInformation("\n" + debugGraph)
+
+ if Activity.Current <> null then
+ Activity.Current.AddTag("graph", debugGraph) |> ignore
+
+ return nodeGraph, graph
+ }
+
+ let removeImplFilesThatHaveSignatures (projectSnapshot: ProjectSnapshot) (graph: Graph) =
+
+ let removeIndexes =
+ projectSnapshot.SourceFileNames
+ |> Seq.mapi pair
+ |> Seq.groupBy (
+ snd
+ >> (fun fileName ->
+ if fileName.EndsWith(".fsi") then
+ fileName.Substring(0, fileName.Length - 1)
+ else
+ fileName)
+ )
+ |> Seq.map (snd >> (Seq.toList))
+ |> Seq.choose (function
+ | [ idx1, _; idx2, _ ] -> max idx1 idx2 |> Some
+ | _ -> None)
+ |> Set
+
+ graph
+ |> Seq.filter (fun x -> not (removeIndexes.Contains x.Key))
+ |> Seq.map (fun x -> x.Key, x.Value |> Array.filter (fun node -> not (removeIndexes.Contains node)))
+ |> Graph.make
+
+ let removeImplFilesThatHaveSignaturesExceptLastOne (projectSnapshot: ProjectSnapshotBase<_>) (graph: Graph) =
+
+ let removeIndexes =
+ projectSnapshot.SourceFileNames
+ |> Seq.mapi pair
+ |> Seq.groupBy (
+ snd
+ >> (fun fileName ->
+ if fileName.EndsWith(".fsi") then
+ fileName.Substring(0, fileName.Length - 1)
+ else
+ fileName)
+ )
+ |> Seq.map (snd >> (Seq.toList))
+ |> Seq.choose (function
+ | [ idx1, _; idx2, _ ] -> max idx1 idx2 |> Some
+ | _ -> None)
+ |> Set
+ // Don't remove the last file
+ |> Set.remove (projectSnapshot.SourceFiles.Length - 1)
+
+ graph
+ |> Seq.filter (fun x -> not (removeIndexes.Contains x.Key))
+ |> Seq.map (fun x -> x.Key, x.Value |> Array.filter (fun node -> not (removeIndexes.Contains node)))
+ |> Graph.make
+
+ let ComputeDependencyGraphForFile (tcConfig: TcConfig) (priorSnapshot: ProjectSnapshotBase) =
+ let key = priorSnapshot.SourceFiles.Key(DependencyGraphType.File)
+ //let lastFileIndex = (parsedInputs |> Array.length) - 1
+ //caches.DependencyGraph.Get(key, computeDependencyGraph parsedInputs (Graph.subGraphFor lastFileIndex))
+ caches.DependencyGraph.Get(
+ key,
+ computeDependencyGraph
+ tcConfig
+ (priorSnapshot.SourceFiles |> Seq.map (fun f -> f.ParsedInput))
+ (removeImplFilesThatHaveSignaturesExceptLastOne priorSnapshot)
+ )
+
+ let ComputeDependencyGraphForProject (tcConfig: TcConfig) (projectSnapshot: ProjectSnapshotBase) =
+
+ let key = projectSnapshot.SourceFiles.Key(DependencyGraphType.Project)
+ //caches.DependencyGraph.Get(key, computeDependencyGraph parsedInputs (removeImplFilesThatHaveSignatures projectSnapshot))
+ caches.DependencyGraph.Get(
+ key,
+ computeDependencyGraph tcConfig (projectSnapshot.SourceFiles |> Seq.map (fun f -> f.ParsedInput)) id
+ )
+
+ let ComputeTcIntermediate
+ (projectSnapshot: ProjectSnapshotBase)
+ (dependencyGraph: Graph)
+ (index: FileIndex)
+ (nodeToCheck: NodeToTypeCheck)
+ bootstrapInfo
+ (prevTcInfo: TcInfo)
+ =
+
+ ignore dependencyGraph
+
+ let key = projectSnapshot.FileKey(index).WithExtraVersion(bootstrapInfo.Id)
+
+ let _label, _k, _version = key.GetLabel(), key.GetKey(), key.GetVersion()
+
+ caches.TcIntermediate.Get(
+ key,
+ node {
+
+ let file = projectSnapshot.SourceFiles[index]
+
+ let input = file.ParsedInput
+ let fileName = file.FileName
+
+ use _ =
+ Activity.start
+ "ComputeTcIntermediate"
+ [|
+ Activity.Tags.fileName, fileName |> Path.GetFileName
+ "key", key.GetLabel()
+ "version", "-" // key.GetVersion()
+ |]
+
+ beforeFileChecked.Trigger(fileName, Unchecked.defaultof<_>)
+
+ let tcConfig = bootstrapInfo.TcConfig
+ let tcGlobals = bootstrapInfo.TcGlobals
+ let tcImports = bootstrapInfo.TcImports
+
+ let mainInputFileName = file.FileName
+ let sourceText = file.SourceText
+ let parsedMainInput = file.ParsedInput
+
+ // Initialize the error handler
+ let errHandler =
+ ParseAndCheckFile.DiagnosticsHandler(
+ true,
+ mainInputFileName,
+ tcConfig.diagnosticsOptions,
+ sourceText,
+ suggestNamesForErrors,
+ tcConfig.flatErrors
+ )
+
+ use _ =
+ new CompilationGlobalsScope(errHandler.DiagnosticsLogger, BuildPhase.TypeCheck)
+
+ // Apply nowarns to tcConfig (may generate errors, so ensure diagnosticsLogger is installed)
+ let tcConfig =
+ ApplyNoWarnsToTcConfig(tcConfig, parsedMainInput, Path.GetDirectoryName mainInputFileName)
+
+ // update the error handler with the modified tcConfig
+ errHandler.DiagnosticOptions <- tcConfig.diagnosticsOptions
+
+ let diagnosticsLogger = errHandler.DiagnosticsLogger
+
+ //let capturingDiagnosticsLogger = CapturingDiagnosticsLogger("TypeCheck")
+
+ //let diagnosticsLogger =
+ // GetDiagnosticsLoggerFilteringByScopedPragmas(
+ // false,
+ // input.ScopedPragmas,
+ // tcConfig.diagnosticsOptions,
+ // capturingDiagnosticsLogger
+ // )
+
+ //use _ = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.TypeCheck)
+
+ //beforeFileChecked.Trigger fileName
+
+ ApplyMetaCommandsFromInputToTcConfig(tcConfig, input, Path.GetDirectoryName fileName, tcImports.DependencyProvider)
+ |> ignore
+
+ let sink = TcResultsSinkImpl(tcGlobals)
+
+ let hadParseErrors = not (Array.isEmpty file.ParseErrors)
+
+ let input, moduleNamesDict =
+ DeduplicateParsedInputModuleName prevTcInfo.moduleNamesDict input
+
+ //let! ct = NodeCode.CancellationToken
+
+ try
+ //do! maxParallelismSemaphore.WaitAsync(ct) |> NodeCode.AwaitTask
+
+ let! finisher =
+ CheckOneInputWithCallback
+ nodeToCheck
+ ((fun () -> hadParseErrors || diagnosticsLogger.ErrorCount > 0),
+ tcConfig,
+ tcImports,
+ tcGlobals,
+ None,
+ TcResultsSink.WithSink sink,
+ prevTcInfo.tcState,
+ input,
+ true)
+ |> Cancellable.toAsync
+ |> NodeCode.AwaitAsync
+
+ //fileChecked.Trigger fileName
+
+ let newErrors =
+ Array.append file.ParseErrors (errHandler.CollectedPhasedDiagnostics)
+
+ fileChecked.Trigger(fileName, Unchecked.defaultof<_>)
+
+ return
+ {
+ finisher = finisher
+ moduleNamesDict = moduleNamesDict
+ tcDiagnosticsRev = [ newErrors ]
+ tcDependencyFiles = [ fileName ]
+ sink = sink
+ }
+ finally
+ ()
+ //maxParallelismSemaphore.Release() |> ignore
+ }
+ )
+
+ let processGraphNode projectSnapshot bootstrapInfo dependencyFiles collectSinks (fileNode: NodeToTypeCheck) tcInfo =
+ // TODO: should this be node?
+ async {
+ match fileNode with
+ | NodeToTypeCheck.PhysicalFile index ->
+
+ let! tcIntermediate =
+ ComputeTcIntermediate projectSnapshot dependencyFiles index fileNode bootstrapInfo tcInfo
+ |> Async.AwaitNodeCode
+
+ let (Finisher(node = node; finisher = finisher)) = tcIntermediate.finisher
+
+ return
+ Finisher(
+ node,
+ (fun tcInfo ->
+
+ if tcInfo.stateContainsNodes |> Set.contains fileNode then
+ failwith $"Oops!"
+
+ //if
+ // tcInfo.stateContainsNodes
+ // Signature files don't have to be right above the impl file... if we need this check then
+ // we need to do it differently
+ // |> Set.contains (NodeToTypeCheck.ArtificialImplFile(index - 1))
+ //then
+ // failwith $"Oops???"
+
+ let partialResult, tcState = finisher tcInfo.tcState
+
+ let tcEnv, topAttribs, _checkImplFileOpt, ccuSigForFile = partialResult
+
+ let tcEnvAtEndOfFile =
+ if keepAllBackgroundResolutions then
+ tcEnv
+ else
+ tcState.TcEnvFromImpls
+
+ partialResult,
+ { tcInfo with
+ tcState = tcState
+ tcEnvAtEndOfFile = tcEnvAtEndOfFile
+ moduleNamesDict = tcIntermediate.moduleNamesDict
+ topAttribs = Some topAttribs
+ tcDiagnosticsRev = tcIntermediate.tcDiagnosticsRev @ tcInfo.tcDiagnosticsRev
+ tcDependencyFiles = tcIntermediate.tcDependencyFiles @ tcInfo.tcDependencyFiles
+ latestCcuSigForFile = Some ccuSigForFile
+ graphNode = Some node
+ stateContainsNodes = tcInfo.stateContainsNodes |> Set.add node
+ sink =
+ if collectSinks then
+ tcIntermediate.sink :: tcInfo.sink
+ else
+ [ tcIntermediate.sink ]
+ })
+ )
+
+ | NodeToTypeCheck.ArtificialImplFile index ->
+ return
+ Finisher(
+ fileNode,
+ (fun tcInfo ->
+
+ if tcInfo.stateContainsNodes |> Set.contains fileNode then
+ failwith $"Oops!"
+
+ if
+ tcInfo.stateContainsNodes
+ |> Set.contains (NodeToTypeCheck.PhysicalFile(index + 1))
+ then
+ failwith $"Oops!!!"
+
+ let parsedInput = projectSnapshot.SourceFiles[index].ParsedInput
+ let prefixPathOpt = None
+ // Retrieve the type-checked signature information and add it to the TcEnvFromImpls.
+ let partialResult, tcState =
+ AddSignatureResultToTcImplEnv
+ (bootstrapInfo.TcImports,
+ bootstrapInfo.TcGlobals,
+ prefixPathOpt,
+ TcResultsSink.NoSink,
+ tcInfo.tcState,
+ parsedInput)
+ tcInfo.tcState
+
+ let tcEnv, topAttribs, _checkImplFileOpt, ccuSigForFile = partialResult
+
+ let tcEnvAtEndOfFile =
+ if keepAllBackgroundResolutions then
+ tcEnv
+ else
+ tcState.TcEnvFromImpls
+
+ partialResult,
+ { tcInfo with
+ tcState = tcState
+ tcEnvAtEndOfFile = tcEnvAtEndOfFile
+ topAttribs = Some topAttribs
+ latestCcuSigForFile = Some ccuSigForFile
+ graphNode = Some fileNode
+ stateContainsNodes = tcInfo.stateContainsNodes |> Set.add fileNode
+ })
+ )
+
+ }
+
+ let parseSourceFiles (projectSnapshot: ProjectSnapshotWithSources) tcConfig =
+ node {
+ let! parsedInputs =
+ projectSnapshot.SourceFiles
+ |> Seq.map (ComputeParseFile projectSnapshot tcConfig)
+ |> NodeCode.Parallel
+
+ return ProjectSnapshotBase<_>(projectSnapshot.ProjectCore, parsedInputs |> Array.toList)
+ }
+
+ // Type check file and all its dependencies
+ let ComputeTcLastFile (bootstrapInfo: BootstrapInfo) (projectSnapshot: ProjectSnapshotWithSources) =
+ let fileName = projectSnapshot.SourceFiles |> List.last |> (fun f -> f.FileName)
+
+ caches.TcLastFile.Get(
+ projectSnapshot.FileKey fileName,
+ node {
+ let file = projectSnapshot.SourceFiles |> List.last
+
+ use _ =
+ Activity.start "ComputeTcLastFile" [| Activity.Tags.fileName, file.FileName |> Path.GetFileName |]
+
+ let! projectSnapshot = parseSourceFiles projectSnapshot bootstrapInfo.TcConfig
+
+ let! graph, dependencyFiles = ComputeDependencyGraphForFile bootstrapInfo.TcConfig projectSnapshot
+
+ let! results, tcInfo =
+ processTypeCheckingGraph
+ graph
+ (processGraphNode projectSnapshot bootstrapInfo dependencyFiles false)
+ bootstrapInfo.InitialTcInfo
+ |> NodeCode.AwaitAsync
+
+ let lastResult = results |> List.head |> snd
+
+ return lastResult, tcInfo
+ }
+ )
+
+ let getParseResult (projectSnapshot: ProjectSnapshot) creationDiags file (tcConfig: TcConfig) =
+ node {
+ let! parsedFile = ComputeParseFile projectSnapshot tcConfig file
+
+ let parseDiagnostics =
+ DiagnosticHelpers.CreateDiagnostics(
+ tcConfig.diagnosticsOptions,
+ false,
+ file.FileName,
+ parsedFile.ParseErrors,
+ suggestNamesForErrors,
+ tcConfig.flatErrors,
+ None
+ )
+
+ let diagnostics = [| yield! creationDiags; yield! parseDiagnostics |]
+
+ return
+ FSharpParseFileResults(
+ diagnostics = diagnostics,
+ input = parsedFile.ParsedInput,
+ parseHadErrors = (parseDiagnostics.Length > 0),
+ // TODO: check if we really need this in parse results
+ dependencyFiles = [||]
+ )
+ }
+
+ let emptyParseResult fileName diagnostics =
+ let parseTree = EmptyParsedInput(fileName, (false, false))
+ FSharpParseFileResults(diagnostics, parseTree, true, [||])
+
+ let ComputeParseAndCheckFileInProject (fileName: string) (projectSnapshot: ProjectSnapshot) =
+ caches.ParseAndCheckFileInProject.Get(
+ projectSnapshot.FileKey fileName,
+ node {
+
+ use _ =
+ Activity.start "ComputeParseAndCheckFileInProject" [| Activity.Tags.fileName, fileName |> Path.GetFileName |]
+
+ match! ComputeBootstrapInfo projectSnapshot with
+ | None, creationDiags -> return emptyParseResult fileName creationDiags, FSharpCheckFileAnswer.Aborted
+
+ | Some bootstrapInfo, creationDiags ->
+
+ let priorSnapshot = projectSnapshot.UpTo fileName
+ let! snapshotWithSources = LoadSources bootstrapInfo priorSnapshot
+ let file = snapshotWithSources.SourceFiles |> List.last
+
+ let! parseResults = getParseResult projectSnapshot creationDiags file bootstrapInfo.TcConfig
+
+ let! result, tcInfo = ComputeTcLastFile bootstrapInfo snapshotWithSources
+
+ let (tcEnv, _topAttribs, checkedImplFileOpt, ccuSigForFile) = result
+
+ let tcState = tcInfo.tcState
+
+ let sink = tcInfo.sink.Head // TODO: don't use head
+
+ let tcResolutions = sink.GetResolutions()
+ let tcSymbolUses = sink.GetSymbolUses()
+ let tcOpenDeclarations = sink.GetOpenDeclarations()
+
+ let tcDependencyFiles = [] // TODO add as a set to TcIntermediate
+
+ // TODO: Apparently creating diagnostics can produce further diagnostics. So let's capture those too. Hopefully there is a more elegant solution...
+ // Probably diagnostics need to be evaluated during typecheck anyway for proper formatting, which might take care of this too.
+ let extraLogger = CapturingDiagnosticsLogger("DiagnosticsWhileCreatingDiagnostics")
+ use _ = new CompilationGlobalsScope(extraLogger, BuildPhase.TypeCheck)
+
+ // Apply nowarns to tcConfig (may generate errors, so ensure diagnosticsLogger is installed)
+ let tcConfig =
+ ApplyNoWarnsToTcConfig(bootstrapInfo.TcConfig, parseResults.ParseTree, Path.GetDirectoryName fileName)
+
+ let diagnosticsOptions = tcConfig.diagnosticsOptions
+
+ let symbolEnv =
+ SymbolEnv(bootstrapInfo.TcGlobals, tcState.Ccu, Some tcState.CcuSig, bootstrapInfo.TcImports)
+
+ let tcDiagnostics =
+ DiagnosticHelpers.CreateDiagnostics(
+ diagnosticsOptions,
+ false,
+ fileName,
+ tcInfo.TcDiagnostics,
+ suggestNamesForErrors,
+ bootstrapInfo.TcConfig.flatErrors,
+ Some symbolEnv
+ )
+
+ let extraDiagnostics =
+ DiagnosticHelpers.CreateDiagnostics(
+ diagnosticsOptions,
+ false,
+ fileName,
+ extraLogger.Diagnostics,
+ suggestNamesForErrors,
+ bootstrapInfo.TcConfig.flatErrors,
+ Some symbolEnv
+ )
+
+ let tcDiagnostics =
+ [| yield! creationDiags; yield! extraDiagnostics; yield! tcDiagnostics |]
+
+ let loadClosure = None // TODO: script support
+
+ let typedResults =
+ FSharpCheckFileResults.Make(
+ fileName,
+ projectSnapshot.ProjectFileName,
+ bootstrapInfo.TcConfig,
+ bootstrapInfo.TcGlobals,
+ projectSnapshot.IsIncompleteTypeCheckEnvironment,
+ None,
+ projectSnapshot.ToOptions(),
+ Array.ofList tcDependencyFiles,
+ creationDiags,
+ parseResults.Diagnostics,
+ tcDiagnostics,
+ keepAssemblyContents,
+ ccuSigForFile,
+ tcState.Ccu,
+ bootstrapInfo.TcImports,
+ tcEnv.AccessRights,
+ tcResolutions,
+ tcSymbolUses,
+ tcEnv.NameEnv,
+ loadClosure,
+ checkedImplFileOpt,
+ tcOpenDeclarations
+ )
+
+ return (parseResults, FSharpCheckFileAnswer.Succeeded typedResults)
+ }
+ )
+
+ let ComputeParseAndCheckAllFilesInProject (bootstrapInfo: BootstrapInfo) (projectSnapshot: ProjectSnapshotWithSources) =
+ caches.ParseAndCheckAllFilesInProject.Get(
+ projectSnapshot.FullKey,
+ node {
+ use _ =
+ Activity.start
+ "ComputeParseAndCheckAllFilesInProject"
+ [| Activity.Tags.project, projectSnapshot.ProjectFileName |> Path.GetFileName |]
+
+ let! projectSnapshot = parseSourceFiles projectSnapshot bootstrapInfo.TcConfig
+
+ let! graph, dependencyFiles = ComputeDependencyGraphForProject bootstrapInfo.TcConfig projectSnapshot
+
+ return!
+ processTypeCheckingGraph
+ graph
+ (processGraphNode projectSnapshot bootstrapInfo dependencyFiles true)
+ bootstrapInfo.InitialTcInfo
+ |> NodeCode.AwaitAsync
+ }
+ )
+
+ let ComputeProjectExtras (bootstrapInfo: BootstrapInfo) (projectSnapshot: ProjectSnapshotWithSources) =
+ caches.ProjectExtras.Get(
+ projectSnapshot.SignatureKey,
+ node {
+
+ let! results, finalInfo = ComputeParseAndCheckAllFilesInProject bootstrapInfo projectSnapshot
+
+ let assemblyName = bootstrapInfo.AssemblyName
+ let tcConfig = bootstrapInfo.TcConfig
+ let tcGlobals = bootstrapInfo.TcGlobals
+
+ let results = results |> Seq.sortBy fst |> Seq.map snd |> Seq.toList
+
+ // Finish the checking
+ let (_tcEnvAtEndOfLastFile, topAttrs, checkedImplFiles, _), tcState =
+ CheckMultipleInputsFinish(results, finalInfo.tcState)
+
+ let tcState, _, ccuContents = CheckClosedInputSetFinish([], tcState)
+
+ let generatedCcu = tcState.Ccu.CloneWithFinalizedContents(ccuContents)
+
+ // Compute the identity of the generated assembly based on attributes, options etc.
+ // Some of this is duplicated from fsc.fs
+ let ilAssemRef =
+ let publicKey =
+ try
+ let signingInfo = ValidateKeySigningAttributes(tcConfig, tcGlobals, topAttrs)
+
+ match GetStrongNameSigner signingInfo with
+ | None -> None
+ | Some s -> Some(PublicKey.KeyAsToken(s.PublicKey))
+ with exn ->
+ errorRecoveryNoRange exn
+ None
+
+ let locale =
+ TryFindFSharpStringAttribute
+ tcGlobals
+ (tcGlobals.FindSysAttrib "System.Reflection.AssemblyCultureAttribute")
+ topAttrs.assemblyAttrs
+
+ let assemVerFromAttrib =
+ TryFindFSharpStringAttribute
+ tcGlobals
+ (tcGlobals.FindSysAttrib "System.Reflection.AssemblyVersionAttribute")
+ topAttrs.assemblyAttrs
+ |> Option.bind (fun v ->
+ try
+ Some(parseILVersion v)
+ with _ ->
+ None)
+
+ let ver =
+ match assemVerFromAttrib with
+ | None -> tcConfig.version.GetVersionInfo(tcConfig.implicitIncludeDir)
+ | Some v -> v
+
+ ILAssemblyRef.Create(assemblyName, None, publicKey, false, Some ver, locale)
+
+ let assemblyDataResult =
+ try
+ // Assemblies containing type provider components can not successfully be used via cross-assembly references.
+ // We return 'None' for the assembly portion of the cross-assembly reference
+ let hasTypeProviderAssemblyAttrib =
+ topAttrs.assemblyAttrs
+ |> List.exists (fun (Attrib(tcref, _, _, _, _, _, _)) ->
+ let nm = tcref.CompiledRepresentationForNamedType.BasicQualifiedName
+
+ nm = typeof.FullName)
+
+ if tcState.CreatesGeneratedProvidedTypes || hasTypeProviderAssemblyAttrib then
+ ProjectAssemblyDataResult.Unavailable true
+ else
+ ProjectAssemblyDataResult.Available(
+ RawFSharpAssemblyDataBackedByLanguageService(
+ bootstrapInfo.TcConfig,
+ bootstrapInfo.TcGlobals,
+ generatedCcu,
+ bootstrapInfo.OutFile,
+ topAttrs,
+ bootstrapInfo.AssemblyName,
+ ilAssemRef
+ )
+ :> IRawFSharpAssemblyData
+ )
+ with exn ->
+ errorRecoveryNoRange exn
+ ProjectAssemblyDataResult.Unavailable true
+
+ return finalInfo, ilAssemRef, assemblyDataResult, checkedImplFiles
+ }
+ )
+
+ let ComputeAssemblyData (projectSnapshot: ProjectSnapshot) fileName =
+ caches.AssemblyData.Get(
+ projectSnapshot.SignatureKey,
+ node {
+
+ try
+
+ let availableOnDiskModifiedTime =
+ if FileSystem.FileExistsShim fileName then
+ Some <| FileSystem.GetLastWriteTimeShim fileName
+ else
+ None
+
+ // TODO: This kinda works, but the problem is that in order to switch a project to "in-memory" mode
+ // - some file needs to be edited (this tirggers a re-check, but LastModifiedTimeOnDisk won't change)
+ // - saved (this will not trigger anything)
+ // - and then another change has to be made (to any file buffer) - so that recheck is triggered and we get here again
+ // Until that sequence happens the project will be used from disk (if available).
+ // To get around it we probably need to detect changes made in the editor and record a timestamp for them.
+ let shouldUseOnDisk =
+ availableOnDiskModifiedTime
+ |> Option.exists (fun t -> t >= projectSnapshot.GetLastModifiedTimeOnDisk())
+
+ let name = projectSnapshot.ProjectFileName |> Path.GetFileNameWithoutExtension
+
+ if shouldUseOnDisk then
+ Trace.TraceInformation($"Using assembly on disk: {name}")
+ return ProjectAssemblyDataResult.Unavailable true
+ else
+ match! ComputeBootstrapInfo projectSnapshot with
+ | None, _ ->
+ Trace.TraceInformation($"Using assembly on disk (unintentionally): {name}")
+ return ProjectAssemblyDataResult.Unavailable true
+ | Some bootstrapInfo, _creationDiags ->
+
+ let! snapshotWithSources = LoadSources bootstrapInfo projectSnapshot
+
+ let! _, _, assemblyDataResult, _ = ComputeProjectExtras bootstrapInfo snapshotWithSources
+ Trace.TraceInformation($"Using in-memory project reference: {name}")
+
+ return assemblyDataResult
+ with
+ | TaskCancelled ex -> return raise ex
+ | ex ->
+ errorR (exn ($"Error while computing assembly data for project {projectSnapshot.Label}: {ex}"))
+ return ProjectAssemblyDataResult.Unavailable true
+ }
+ )
+
+ let ComputeParseAndCheckProject (projectSnapshot: ProjectSnapshot) =
+ caches.ParseAndCheckProject.Get(
+ projectSnapshot.FullKey,
+ node {
+
+ match! ComputeBootstrapInfo projectSnapshot with
+ | None, creationDiags ->
+ return FSharpCheckProjectResults(projectSnapshot.ProjectFileName, None, keepAssemblyContents, creationDiags, None)
+ | Some bootstrapInfo, creationDiags ->
+
+ let! snapshotWithSources = LoadSources bootstrapInfo projectSnapshot
+
+ let! tcInfo, ilAssemRef, assemblyDataResult, checkedImplFiles = ComputeProjectExtras bootstrapInfo snapshotWithSources
+
+ let diagnosticsOptions = bootstrapInfo.TcConfig.diagnosticsOptions
+ let fileName = DummyFileNameForRangesWithoutASpecificLocation
+
+ let topAttribs = tcInfo.topAttribs
+ let tcState = tcInfo.tcState
+ let tcEnvAtEnd = tcInfo.tcEnvAtEndOfFile
+ let tcDiagnostics = tcInfo.TcDiagnostics
+ let tcDependencyFiles = tcInfo.tcDependencyFiles
+
+ let symbolEnv =
+ SymbolEnv(bootstrapInfo.TcGlobals, tcInfo.tcState.Ccu, Some tcInfo.tcState.CcuSig, bootstrapInfo.TcImports)
+ |> Some
+
+ let tcDiagnostics =
+ DiagnosticHelpers.CreateDiagnostics(
+ diagnosticsOptions,
+ true,
+ fileName,
+ tcDiagnostics,
+ suggestNamesForErrors,
+ bootstrapInfo.TcConfig.flatErrors,
+ symbolEnv
+ )
+
+ let diagnostics = [| yield! creationDiags; yield! tcDiagnostics |]
+
+ let getAssemblyData () =
+ match assemblyDataResult with
+ | ProjectAssemblyDataResult.Available data -> Some data
+ | _ -> None
+
+ let symbolUses = tcInfo.sink |> Seq.map (fun sink -> sink.GetSymbolUses())
+
+ let details =
+ (bootstrapInfo.TcGlobals,
+ bootstrapInfo.TcImports,
+ tcState.Ccu,
+ tcState.CcuSig,
+ Choice2Of2(async.Return symbolUses),
+ topAttribs,
+ getAssemblyData,
+ ilAssemRef,
+ tcEnvAtEnd.AccessRights,
+ Some checkedImplFiles,
+ Array.ofList tcDependencyFiles,
+ projectSnapshot.ToOptions())
+
+ let results =
+ FSharpCheckProjectResults(
+ projectSnapshot.ProjectFileName,
+ Some bootstrapInfo.TcConfig,
+ keepAssemblyContents,
+ diagnostics,
+ Some details
+ )
+
+ return results
+ }
+ )
+
+ let tryGetSink (fileName: string) (projectSnapshot: ProjectSnapshot) =
+ node {
+ match! ComputeBootstrapInfo projectSnapshot with
+ | None, _ -> return None
+ | Some bootstrapInfo, _creationDiags ->
+
+ let! snapshotWithSources = projectSnapshot.UpTo fileName |> LoadSources bootstrapInfo
+
+ let! _, tcInfo = ComputeTcLastFile bootstrapInfo snapshotWithSources
+
+ return tcInfo.sink |> List.tryHead |> Option.map (fun sink -> sink, bootstrapInfo)
+ }
+
+ let ComputeSemanticClassification (fileName: string, projectSnapshot: ProjectSnapshot) =
+ caches.SemanticClassification.Get(
+ projectSnapshot.FileKey fileName,
+ node {
+ use _ =
+ Activity.start "ComputeSemanticClassification" [| Activity.Tags.fileName, fileName |> Path.GetFileName |]
+
+ let! sinkOpt = tryGetSink fileName projectSnapshot
+
+ return
+ sinkOpt
+ |> Option.bind (fun (sink, bootstrapInfo) ->
+ let sResolutions = sink.GetResolutions()
+
+ let semanticClassification =
+ sResolutions.GetSemanticClassification(
+ bootstrapInfo.TcGlobals,
+ bootstrapInfo.TcImports.GetImportMap(),
+ sink.GetFormatSpecifierLocations(),
+ None
+ )
+
+ let sckBuilder = SemanticClassificationKeyStoreBuilder()
+ sckBuilder.WriteAll semanticClassification
+
+ sckBuilder.TryBuildAndReset())
+ |> Option.map (fun sck -> sck.GetView())
+ }
+ )
+
+ let ComputeItemKeyStore (fileName: string, projectSnapshot: ProjectSnapshot) =
+ caches.ItemKeyStore.Get(
+ projectSnapshot.FileKey fileName,
+ node {
+ use _ =
+ Activity.start "ComputeItemKeyStore" [| Activity.Tags.fileName, fileName |> Path.GetFileName |]
+
+ let! sinkOpt = tryGetSink fileName projectSnapshot
+
+ return
+ sinkOpt
+ |> Option.bind (fun (sink, { TcGlobals = g }) ->
+ let sResolutions = sink.GetResolutions()
+
+ let builder = ItemKeyStoreBuilder(g)
+
+ let preventDuplicates =
+ HashSet(
+ { new IEqualityComparer with
+ member _.Equals((s1, e1): struct (pos * pos), (s2, e2): struct (pos * pos)) =
+ Position.posEq s1 s2 && Position.posEq e1 e2
+
+ member _.GetHashCode o = o.GetHashCode()
+ }
+ )
+
+ sResolutions.CapturedNameResolutions
+ |> Seq.iter (fun cnr ->
+ let r = cnr.Range
+
+ if preventDuplicates.Add struct (r.Start, r.End) then
+ builder.Write(cnr.Range, cnr.Item))
+
+ builder.TryBuildAndReset())
+ }
+ )
+
+ member _.ParseFile(fileName, projectSnapshot: ProjectSnapshot, _userOpName) =
+ node {
+ //use _ =
+ // Activity.start "ParseFile" [| Activity.Tags.fileName, fileName |> Path.GetFileName |]
+ let! ct = NodeCode.CancellationToken
+ use _ = Cancellable.UsingToken(ct)
+
+ // TODO: might need to deal with exceptions here:
+ let tcConfigB, sourceFileNames, _ = ComputeTcConfigBuilder projectSnapshot
+
+ let tcConfig = TcConfig.Create(tcConfigB, validate = true)
+
+ let _index, fileSnapshot =
+ projectSnapshot.SourceFiles
+ |> Seq.mapi pair
+ |> Seq.tryFind (fun (_, f) -> f.FileName = fileName)
+ |> Option.defaultWith (fun () -> failwith $"File not found: {fileName}")
+
+ let isExe = tcConfig.target.IsExe
+ let isLastCompiland = fileName = (sourceFileNames |> List.last)
+
+ let! file = LoadSource fileSnapshot isExe isLastCompiland
+ let! parseResult = getParseResult projectSnapshot Seq.empty file tcConfig
+ return parseResult
+ }
+
+ member _.ParseAndCheckFileInProject(fileName: string, projectSnapshot: ProjectSnapshot, userOpName: string) =
+ ignore userOpName
+ ComputeParseAndCheckFileInProject fileName projectSnapshot
+
+ member _.FindReferencesInFile(fileName: string, projectSnapshot: ProjectSnapshot, symbol: FSharpSymbol, userOpName: string) =
+ ignore userOpName
+
+ node {
+ match! ComputeItemKeyStore(fileName, projectSnapshot) with
+ | None -> return Seq.empty
+ | Some itemKeyStore -> return itemKeyStore.FindAll symbol.Item
+ }
+
+ member _.GetAssemblyData(projectSnapshot: ProjectSnapshot, fileName, _userOpName) =
+ ComputeAssemblyData projectSnapshot fileName
+
+ member _.Caches = caches
+
+ member _.SetCacheSizeFactor(sizeFactor: int) =
+ if sizeFactor <> caches.SizeFactor then
+ caches <- CompilerCaches(sizeFactor)
+
+ interface IBackgroundCompiler with
+
+ member this.CheckFileInProject
+ (
+ parseResults: FSharpParseFileResults,
+ fileName: string,
+ fileVersion: int,
+ sourceText: ISourceText,
+ options: FSharpProjectOptions,
+ userOpName: string
+ ) : NodeCode =
+ node {
+ let! ct = NodeCode.CancellationToken
+ use _ = Cancellable.UsingToken(ct)
+
+ let! snapshot =
+ FSharpProjectSnapshot.FromOptions(options, fileName, fileVersion, sourceText)
+ |> NodeCode.AwaitAsync
+
+ ignore parseResults
+
+ let! _, result = this.ParseAndCheckFileInProject(fileName, snapshot.ProjectSnapshot, userOpName)
+
+ return result
+ }
+
+ member this.CheckFileInProjectAllowingStaleCachedResults
+ (
+ parseResults: FSharpParseFileResults,
+ fileName: string,
+ fileVersion: int,
+ sourceText: ISourceText,
+ options: FSharpProjectOptions,
+ userOpName: string
+ ) : NodeCode =
+ node {
+ let! ct = NodeCode.CancellationToken
+ use _ = Cancellable.UsingToken(ct)
+
+ let! snapshot =
+ FSharpProjectSnapshot.FromOptions(options, fileName, fileVersion, sourceText)
+ |> NodeCode.AwaitAsync
+
+ ignore parseResults
+
+ let! _, result = this.ParseAndCheckFileInProject(fileName, snapshot.ProjectSnapshot, userOpName)
+
+ return Some result
+ }
+
+ member this.ClearCache(projects: FSharpProjectIdentifier seq, userOpName: string) : unit =
+ use _ =
+ Activity.start "TransparentCompiler.ClearCache" [| Activity.Tags.userOpName, userOpName |]
+
+ this.Caches.Clear(
+ projects
+ |> Seq.map (function
+ | FSharpProjectIdentifier(x, y) -> (x, y))
+ |> Set
+ )
+
+ member this.ClearCache(options: seq, userOpName: string) : unit =
+ use _ =
+ Activity.start "TransparentCompiler.ClearCache" [| Activity.Tags.userOpName, userOpName |]
+
+ backgroundCompiler.ClearCache(options, userOpName)
+ this.Caches.Clear(options |> Seq.map (fun o -> o.GetProjectIdentifier()) |> Set)
+
+ member _.ClearCaches() : unit =
+ backgroundCompiler.ClearCaches()
+ caches <- CompilerCaches(100) // TODO: check
+
+ member _.DownsizeCaches() : unit = backgroundCompiler.DownsizeCaches()
+
+ member _.BeforeBackgroundFileCheck = beforeFileChecked.Publish
+
+ member _.FileParsed = fileParsed.Publish
+
+ member _.FileChecked = fileChecked.Publish
+
+ member _.ProjectChecked = projectChecked.Publish
+
+ member this.FindReferencesInFile
+ (
+ fileName: string,
+ options: FSharpProjectOptions,
+ symbol: FSharpSymbol,
+ canInvalidateProject: bool,
+ userOpName: string
+ ) : NodeCode> =
+ node {
+ let! ct = NodeCode.CancellationToken
+ use _ = Cancellable.UsingToken(ct)
+
+ ignore canInvalidateProject
+ let! snapshot = FSharpProjectSnapshot.FromOptions options |> NodeCode.AwaitAsync
+
+ return! this.FindReferencesInFile(fileName, snapshot.ProjectSnapshot, symbol, userOpName)
+ }
+
+ member this.FindReferencesInFile(fileName, projectSnapshot, symbol, userOpName) =
+ this.FindReferencesInFile(fileName, projectSnapshot.ProjectSnapshot, symbol, userOpName)
+
+ member _.FrameworkImportsCache: FrameworkImportsCache =
+ backgroundCompiler.FrameworkImportsCache
+
+ member this.GetAssemblyData(options: FSharpProjectOptions, fileName, userOpName: string) : NodeCode =
+ node {
+ let! ct = NodeCode.CancellationToken
+ use _ = Cancellable.UsingToken(ct)
+
+ let! snapshot = FSharpProjectSnapshot.FromOptions options |> NodeCode.AwaitAsync
+ return! this.GetAssemblyData(snapshot.ProjectSnapshot, fileName, userOpName)
+ }
+
+ member this.GetAssemblyData
+ (
+ projectSnapshot: FSharpProjectSnapshot,
+ fileName,
+ userOpName: string
+ ) : NodeCode =
+ this.GetAssemblyData(projectSnapshot.ProjectSnapshot, fileName, userOpName)
+
+ member this.GetBackgroundCheckResultsForFileInProject
+ (
+ fileName: string,
+ options: FSharpProjectOptions,
+ userOpName: string
+ ) : NodeCode =
+ node {
+ let! ct = NodeCode.CancellationToken
+ use _ = Cancellable.UsingToken(ct)
+
+ let! snapshot = FSharpProjectSnapshot.FromOptions options |> NodeCode.AwaitAsync
+
+ match! this.ParseAndCheckFileInProject(fileName, snapshot.ProjectSnapshot, userOpName) with
+ | parseResult, FSharpCheckFileAnswer.Succeeded checkResult -> return parseResult, checkResult
+ | parseResult, FSharpCheckFileAnswer.Aborted -> return parseResult, FSharpCheckFileResults.MakeEmpty(fileName, [||], true)
+ }
+
+ member this.GetBackgroundParseResultsForFileInProject
+ (
+ fileName: string,
+ options: FSharpProjectOptions,
+ userOpName: string
+ ) : NodeCode =
+ node {
+ let! ct = NodeCode.CancellationToken
+ use _ = Cancellable.UsingToken(ct)
+
+ let! snapshot = FSharpProjectSnapshot.FromOptions options |> NodeCode.AwaitAsync
+ return! this.ParseFile(fileName, snapshot.ProjectSnapshot, userOpName)
+ }
+
+ member this.GetCachedCheckFileResult
+ (
+ builder: IncrementalBuilder,
+ fileName: string,
+ sourceText: ISourceText,
+ options: FSharpProjectOptions
+ ) : NodeCode<(FSharpParseFileResults * FSharpCheckFileResults) option> =
+ node {
+ ignore builder
+ let! ct = NodeCode.CancellationToken
+ use _ = Cancellable.UsingToken(ct)
+
+ let! snapshot =
+ FSharpProjectSnapshot.FromOptions(options, fileName, 1, sourceText)
+ |> NodeCode.AwaitAsync
+
+ match! this.ParseAndCheckFileInProject(fileName, snapshot.ProjectSnapshot, "GetCachedCheckFileResult") with
+ | parseResult, FSharpCheckFileAnswer.Succeeded checkResult -> return Some(parseResult, checkResult)
+ | _, FSharpCheckFileAnswer.Aborted -> return None
+ }
+
+ member this.GetProjectOptionsFromScript
+ (
+ fileName: string,
+ sourceText: ISourceText,
+ previewEnabled: bool option,
+ loadedTimeStamp: DateTime option,
+ otherFlags: string array option,
+ useFsiAuxLib: bool option,
+ useSdkRefs: bool option,
+ sdkDirOverride: string option,
+ assumeDotNetFramework: bool option,
+ optionsStamp: int64 option,
+ userOpName: string
+ ) : Async =
+ backgroundCompiler.GetProjectOptionsFromScript(
+ fileName,
+ sourceText,
+ previewEnabled,
+ loadedTimeStamp,
+ otherFlags,
+ useFsiAuxLib,
+ useSdkRefs,
+ sdkDirOverride,
+ assumeDotNetFramework,
+ optionsStamp,
+ userOpName
+ )
+
+ member this.GetSemanticClassificationForFile(fileName: string, snapshot: FSharpProjectSnapshot, userOpName: string) =
+ node {
+ ignore userOpName
+ return! ComputeSemanticClassification(fileName, snapshot.ProjectSnapshot)
+ }
+
+ member this.GetSemanticClassificationForFile
+ (
+ fileName: string,
+ options: FSharpProjectOptions,
+ userOpName: string
+ ) : NodeCode =
+ node {
+ ignore userOpName
+ let! ct = NodeCode.CancellationToken
+ use _ = Cancellable.UsingToken(ct)
+
+ let! snapshot = FSharpProjectSnapshot.FromOptions options |> NodeCode.AwaitAsync
+ return! ComputeSemanticClassification(fileName, snapshot.ProjectSnapshot)
+ }
+
+ member this.InvalidateConfiguration(options: FSharpProjectOptions, userOpName: string) : unit =
+ backgroundCompiler.InvalidateConfiguration(options, userOpName)
+
+ member this.NotifyFileChanged(fileName: string, options: FSharpProjectOptions, userOpName: string) : NodeCode =
+ backgroundCompiler.NotifyFileChanged(fileName, options, userOpName)
+
+ member this.NotifyProjectCleaned(options: FSharpProjectOptions, userOpName: string) : Async =
+ backgroundCompiler.NotifyProjectCleaned(options, userOpName)
+
+ member this.ParseAndCheckFileInProject
+ (
+ fileName: string,
+ fileVersion: int,
+ sourceText: ISourceText,
+ options: FSharpProjectOptions,
+ userOpName: string
+ ) : NodeCode =
+ node {
+ let! ct = NodeCode.CancellationToken
+ use _ = Cancellable.UsingToken(ct)
+
+ let! snapshot =
+ FSharpProjectSnapshot.FromOptions(options, fileName, fileVersion, sourceText)
+ |> NodeCode.AwaitAsync
+
+ return! this.ParseAndCheckFileInProject(fileName, snapshot.ProjectSnapshot, userOpName)
+ }
+
+ member this.ParseAndCheckFileInProject(fileName: string, projectSnapshot: FSharpProjectSnapshot, userOpName: string) =
+ this.ParseAndCheckFileInProject(fileName, projectSnapshot.ProjectSnapshot, userOpName)
+
+ member this.ParseAndCheckProject(options: FSharpProjectOptions, userOpName: string) : NodeCode =
+ node {
+ let! ct = NodeCode.CancellationToken
+ use _ = Cancellable.UsingToken(ct)
+
+ ignore userOpName
+ let! snapshot = FSharpProjectSnapshot.FromOptions options |> NodeCode.AwaitAsync
+ return! ComputeParseAndCheckProject snapshot.ProjectSnapshot
+ }
+
+ member this.ParseAndCheckProject(projectSnapshot: FSharpProjectSnapshot, userOpName: string) : NodeCode =
+ node {
+ let! ct = NodeCode.CancellationToken
+ use _ = Cancellable.UsingToken(ct)
+
+ ignore userOpName
+ return! ComputeParseAndCheckProject projectSnapshot.ProjectSnapshot
+ }
+
+ member this.ParseFile(fileName, projectSnapshot, userOpName) =
+ this.ParseFile(fileName, projectSnapshot.ProjectSnapshot, userOpName)
+ |> Async.AwaitNodeCode
+
+ member this.ParseFile
+ (
+ fileName: string,
+ sourceText: ISourceText,
+ options: FSharpParsingOptions,
+ cache: bool,
+ flatErrors: bool,
+ userOpName: string
+ ) =
+ backgroundCompiler.ParseFile(fileName, sourceText, options, cache, flatErrors, userOpName)
+
+ member this.TryGetRecentCheckResultsForFile
+ (
+ fileName: string,
+ options: FSharpProjectOptions,
+ sourceText: ISourceText option,
+ userOpName: string
+ ) : (FSharpParseFileResults * FSharpCheckFileResults * SourceTextHash) option =
+ backgroundCompiler.TryGetRecentCheckResultsForFile(fileName, options, sourceText, userOpName)
diff --git a/src/Compiler/Service/TransparentCompiler.fsi b/src/Compiler/Service/TransparentCompiler.fsi
new file mode 100644
index 00000000000..00167ccc67f
--- /dev/null
+++ b/src/Compiler/Service/TransparentCompiler.fsi
@@ -0,0 +1,175 @@
+namespace FSharp.Compiler.CodeAnalysis.TransparentCompiler
+
+open Internal.Utilities.Collections
+
+open FSharp.Compiler.AbstractIL.ILBinaryReader
+open FSharp.Compiler.BuildGraph
+open FSharp.Compiler.CodeAnalysis
+open FSharp.Compiler.CompilerConfig
+open FSharp.Compiler.CompilerImports
+open FSharp.Compiler.CheckBasics
+open FSharp.Compiler.Diagnostics
+open FSharp.Compiler.DiagnosticsLogger
+open FSharp.Compiler.ScriptClosure
+open FSharp.Compiler.Symbols
+open FSharp.Compiler.TcGlobals
+open FSharp.Compiler.Text
+open FSharp.Compiler.ParseAndCheckInputs
+open FSharp.Compiler.GraphChecking
+open FSharp.Compiler.Syntax
+open FSharp.Compiler.NameResolution
+open FSharp.Compiler.TypedTree
+open FSharp.Compiler.CheckDeclarations
+open FSharp.Compiler.EditorServices
+
+/// Accumulated results of type checking. The minimum amount of state in order to continue type-checking following files.
+[]
+type internal TcInfo =
+ {
+ tcState: TcState
+ tcEnvAtEndOfFile: TcEnv
+
+ /// Disambiguation table for module names
+ moduleNamesDict: ModuleNamesDict
+
+ topAttribs: TopAttribs option
+
+ latestCcuSigForFile: ModuleOrNamespaceType option
+
+ /// Accumulated diagnostics, last file first
+ tcDiagnosticsRev: (PhasedDiagnostic * FSharpDiagnosticSeverity)[] list
+
+ tcDependencyFiles: string list
+
+ sigNameOpt: (string * QualifiedNameOfFile) option
+
+ graphNode: NodeToTypeCheck option
+
+ stateContainsNodes: Set
+
+ sink: TcResultsSinkImpl list
+ }
+
+[]
+type internal TcIntermediate =
+ {
+ finisher: Finisher
+
+ /// Disambiguation table for module names
+ moduleNamesDict: ModuleNamesDict
+
+ /// Accumulated diagnostics, last file first
+ tcDiagnosticsRev: (PhasedDiagnostic * FSharpDiagnosticSeverity) array list
+ tcDependencyFiles: string list
+ sink: TcResultsSinkImpl
+ }
+
+/// Things we need to start parsing and checking files for a given project snapshot
+type internal BootstrapInfo =
+ { Id: int
+ AssemblyName: string
+ OutFile: string
+ TcConfig: TcConfig
+ TcImports: TcImports
+ TcGlobals: TcGlobals
+ InitialTcInfo: TcInfo
+ LoadedSources: (range * ProjectSnapshot.FSharpFileSnapshot) list
+ LoadClosure: LoadClosure option
+ LastFileName: string }
+
+type internal TcIntermediateResult = TcInfo * TcResultsSinkImpl * CheckedImplFile option * string
+
+[]
+type internal DependencyGraphType =
+
+ /// A dependency graph for a single file - it will be missing files which this file does not depend on
+ | File
+
+ /// A dependency graph for a project - it will contain all files in the project
+ | Project
+
+[]
+type internal Extensions =
+
+ []
+ static member Key:
+ fileSnapshots: #ProjectSnapshot.IFileSnapshot list * ?extraKeyFlag: DependencyGraphType ->
+ ICacheKey<(DependencyGraphType option * byte array), string>
+
+type internal CompilerCaches =
+
+ new: sizeFactor: int -> CompilerCaches
+
+ member AssemblyData: AsyncMemoize<(string * string), (string * string), ProjectAssemblyDataResult>
+
+ member BootstrapInfo: AsyncMemoize<(string * string), string, (BootstrapInfo option * FSharpDiagnostic array)>
+
+ member BootstrapInfoStatic:
+ AsyncMemoize<(string * string), (string * string), (int * TcImports * TcGlobals * TcInfo * Event)>
+
+ member DependencyGraph:
+ AsyncMemoize<(DependencyGraphType option * byte array), string, (Graph * Graph)>
+
+ member FrameworkImports: AsyncMemoize
+
+ member ItemKeyStore: AsyncMemoize<(string * (string * string)), string, ItemKeyStore option>
+
+ member ParseAndCheckAllFilesInProject: AsyncMemoizeDisabled
+
+ member ParseAndCheckFileInProject:
+ AsyncMemoize<(string * (string * string)), string, (FSharpParseFileResults * FSharpCheckFileAnswer)>
+
+ member ParseAndCheckProject: AsyncMemoize<(string * string), string, FSharpCheckProjectResults>
+
+ member ParseFile:
+ AsyncMemoize<((string * string) * string), (string * string * bool), ProjectSnapshot.FSharpParsedFile>
+
+ member ProjectExtras: AsyncMemoizeDisabled
+
+ member SemanticClassification: AsyncMemoize<(string * (string * string)), string, SemanticClassificationView option>
+
+ member SizeFactor: int
+
+ member TcIntermediate: AsyncMemoize<(string * (string * string)), (string * int), TcIntermediate>
+
+ member TcLastFile: AsyncMemoizeDisabled
+
+type internal TransparentCompiler =
+ interface IBackgroundCompiler
+
+ new:
+ legacyReferenceResolver: LegacyReferenceResolver *
+ projectCacheSize: int *
+ keepAssemblyContents: bool *
+ keepAllBackgroundResolutions: bool *
+ tryGetMetadataSnapshot: ILReaderTryGetMetadataSnapshot *
+ suggestNamesForErrors: bool *
+ keepAllBackgroundSymbolUses: bool *
+ enableBackgroundItemKeyStoreAndSemanticClassification: bool *
+ enablePartialTypeChecking: bool *
+ parallelReferenceResolution: ParallelReferenceResolution *
+ captureIdentifiersWhenParsing: bool *
+ getSource: (string -> Async) option *
+ useChangeNotifications: bool *
+ useSyntaxTreeCache: bool ->
+ TransparentCompiler
+
+ member FindReferencesInFile:
+ fileName: string * projectSnapshot: ProjectSnapshot.ProjectSnapshot * symbol: FSharpSymbol * userOpName: string ->
+ NodeCode
+
+ member GetAssemblyData:
+ projectSnapshot: ProjectSnapshot.ProjectSnapshot * fileName: string * _userOpName: string ->
+ NodeCode
+
+ member ParseAndCheckFileInProject:
+ fileName: string * projectSnapshot: ProjectSnapshot.ProjectSnapshot * userOpName: string ->
+ NodeCode
+
+ member ParseFile:
+ fileName: string * projectSnapshot: ProjectSnapshot.ProjectSnapshot * _userOpName: 'a ->
+ NodeCode
+
+ member SetCacheSizeFactor: sizeFactor: int -> unit
+
+ member Caches: CompilerCaches
diff --git a/src/Compiler/Service/service.fs b/src/Compiler/Service/service.fs
index c5b6e64ffdc..b79254d7935 100644
--- a/src/Compiler/Service/service.fs
+++ b/src/Compiler/Service/service.fs
@@ -17,6 +17,7 @@ open FSharp.Compiler.AbstractIL.IL
open FSharp.Compiler.AbstractIL.ILBinaryReader
open FSharp.Compiler.AbstractIL.ILDynamicAssemblyWriter
open FSharp.Compiler.CodeAnalysis
+open FSharp.Compiler.CodeAnalysis.TransparentCompiler
open FSharp.Compiler.CompilerConfig
open FSharp.Compiler.CompilerDiagnostics
open FSharp.Compiler.CompilerImports
@@ -36,19 +37,6 @@ open FSharp.Compiler.Text.Range
open FSharp.Compiler.TcGlobals
open FSharp.Compiler.BuildGraph
-[]
-module EnvMisc =
- let braceMatchCacheSize = GetEnvInteger "FCS_BraceMatchCacheSize" 5
- let parseFileCacheSize = GetEnvInteger "FCS_ParseFileCacheSize" 2
- let checkFileInProjectCacheSize = GetEnvInteger "FCS_CheckFileInProjectCacheSize" 10
-
- let projectCacheSizeDefault = GetEnvInteger "FCS_ProjectCacheSizeDefault" 3
- let frameworkTcImportsCacheStrongSize = GetEnvInteger "FCS_frameworkTcImportsCacheStrongSizeDefault" 8
-
-//----------------------------------------------------------------------------
-// BackgroundCompiler
-//
-
[]
type DocumentSource =
| FileSystem
@@ -58,46 +46,6 @@ type DocumentSource =
[]
type IsResultObsolete = IsResultObsolete of (unit -> bool)
-[]
-module Helpers =
-
- /// Determine whether two (fileName,options) keys are identical w.r.t. affect on checking
- let AreSameForChecking2 ((fileName1: string, options1: FSharpProjectOptions), (fileName2, options2)) =
- (fileName1 = fileName2)
- && FSharpProjectOptions.AreSameForChecking(options1, options2)
-
- /// Determine whether two (fileName,options) keys should be identical w.r.t. resource usage
- let AreSubsumable2 ((fileName1: string, o1: FSharpProjectOptions), (fileName2: string, o2: FSharpProjectOptions)) =
- (fileName1 = fileName2) && FSharpProjectOptions.UseSameProject(o1, o2)
-
- /// Determine whether two (fileName,sourceText,options) keys should be identical w.r.t. parsing
- let AreSameForParsing ((fileName1: string, source1Hash: int64, options1), (fileName2, source2Hash, options2)) =
- fileName1 = fileName2 && options1 = options2 && source1Hash = source2Hash
-
- let AreSimilarForParsing ((fileName1, _, _), (fileName2, _, _)) = fileName1 = fileName2
-
- /// Determine whether two (fileName,sourceText,options) keys should be identical w.r.t. checking
- let AreSameForChecking3 ((fileName1: string, source1Hash: int64, options1: FSharpProjectOptions), (fileName2, source2Hash, options2)) =
- (fileName1 = fileName2)
- && FSharpProjectOptions.AreSameForChecking(options1, options2)
- && source1Hash = source2Hash
-
- /// Determine whether two (fileName,sourceText,options) keys should be identical w.r.t. resource usage
- let AreSubsumable3 ((fileName1: string, _, o1: FSharpProjectOptions), (fileName2: string, _, o2: FSharpProjectOptions)) =
- (fileName1 = fileName2) && FSharpProjectOptions.UseSameProject(o1, o2)
-
- /// If a symbol is an attribute check if given set of names contains its name without the Attribute suffix
- let rec NamesContainAttribute (symbol: FSharpSymbol) names =
- match symbol with
- | :? FSharpMemberOrFunctionOrValue as mofov ->
- mofov.DeclaringEntity
- |> Option.map (fun entity -> NamesContainAttribute entity names)
- |> Option.defaultValue false
- | :? FSharpEntity as entity when entity.IsAttributeType && symbol.DisplayNameCore.EndsWithOrdinal "Attribute" ->
- let nameWithoutAttribute = String.dropSuffix symbol.DisplayNameCore "Attribute"
- names |> Set.contains nameWithoutAttribute
- | _ -> false
-
module CompileHelpers =
let mkCompilationDiagnosticsHandlers (flatErrors) =
let diagnostics = ResizeArray<_>()
@@ -165,1190 +113,6 @@ module CompileHelpers =
Console.SetError error
| None -> ()
-type SourceTextHash = int64
-type CacheStamp = int64
-type FileName = string
-type FilePath = string
-type ProjectPath = string
-type FileVersion = int
-
-type ParseCacheLockToken() =
- interface LockToken
-
-type ScriptClosureCacheToken() =
- interface LockToken
-
-type CheckFileCacheKey = FileName * SourceTextHash * FSharpProjectOptions
-type CheckFileCacheValue = FSharpParseFileResults * FSharpCheckFileResults * SourceTextHash * DateTime
-
-// There is only one instance of this type, held in FSharpChecker
-type BackgroundCompiler
- (
- legacyReferenceResolver,
- projectCacheSize,
- keepAssemblyContents,
- keepAllBackgroundResolutions,
- tryGetMetadataSnapshot,
- suggestNamesForErrors,
- keepAllBackgroundSymbolUses,
- enableBackgroundItemKeyStoreAndSemanticClassification,
- enablePartialTypeChecking,
- parallelReferenceResolution,
- captureIdentifiersWhenParsing,
- getSource: (string -> Async) option,
- useChangeNotifications,
- useSyntaxTreeCache
- ) as self =
-
- let beforeFileChecked = Event()
- let fileParsed = Event()
- let fileChecked = Event()
- let projectChecked = Event()
-
- // STATIC ROOT: FSharpLanguageServiceTestable.FSharpChecker.backgroundCompiler.scriptClosureCache
- /// Information about the derived script closure.
- let scriptClosureCache =
- MruCache(
- projectCacheSize,
- areSame = FSharpProjectOptions.AreSameForChecking,
- areSimilar = FSharpProjectOptions.UseSameProject
- )
-
- let frameworkTcImportsCache = FrameworkImportsCache(frameworkTcImportsCacheStrongSize)
-
- // We currently share one global dependency provider for all scripts for the FSharpChecker.
- // For projects, one is used per project.
- //
- // Sharing one for all scripts is necessary for good performance from GetProjectOptionsFromScript,
- // which requires a dependency provider to process through the project options prior to working out
- // if the cached incremental builder can be used for the project.
- let dependencyProviderForScripts = new DependencyProvider()
-
- let getProjectReferences (options: FSharpProjectOptions) userOpName =
- [
- for r in options.ReferencedProjects do
-
- match r with
- | FSharpReferencedProject.FSharpReference(nm, opts) ->
- // Don't use cross-project references for FSharp.Core, since various bits of code
- // require a concrete FSharp.Core to exist on-disk. The only solutions that have
- // these cross-project references to FSharp.Core are VisualFSharp.sln and FSharp.sln. The ramification
- // of this is that you need to build FSharp.Core to get intellisense in those projects.
-
- if
- (try
- Path.GetFileNameWithoutExtension(nm)
- with _ ->
- "")
- <> GetFSharpCoreLibraryName()
- then
- { new IProjectReference with
- member x.EvaluateRawContents() =
- node {
- Trace.TraceInformation("FCS: {0}.{1} ({2})", userOpName, "GetAssemblyData", nm)
- return! self.GetAssemblyData(opts, userOpName + ".CheckReferencedProject(" + nm + ")")
- }
-
- member x.TryGetLogicalTimeStamp(cache) =
- self.TryGetLogicalTimeStampForProject(cache, opts)
-
- member x.FileName = nm
- }
-
- | FSharpReferencedProject.PEReference(getStamp, delayedReader) ->
- { new IProjectReference with
- member x.EvaluateRawContents() =
- node {
- let! ilReaderOpt = delayedReader.TryGetILModuleReader() |> NodeCode.FromCancellable
-
- match ilReaderOpt with
- | Some ilReader ->
- let ilModuleDef, ilAsmRefs = ilReader.ILModuleDef, ilReader.ILAssemblyRefs
- let data = RawFSharpAssemblyData(ilModuleDef, ilAsmRefs) :> IRawFSharpAssemblyData
- return ProjectAssemblyDataResult.Available data
- | _ ->
- // Note 'false' - if a PEReference doesn't find an ILModuleReader then we don't
- // continue to try to use an on-disk DLL
- return ProjectAssemblyDataResult.Unavailable false
- }
-
- member x.TryGetLogicalTimeStamp _ = getStamp () |> Some
- member x.FileName = delayedReader.OutputFile
- }
-
- | FSharpReferencedProject.ILModuleReference(nm, getStamp, getReader) ->
- { new IProjectReference with
- member x.EvaluateRawContents() =
- cancellable {
- let ilReader = getReader ()
- let ilModuleDef, ilAsmRefs = ilReader.ILModuleDef, ilReader.ILAssemblyRefs
- let data = RawFSharpAssemblyData(ilModuleDef, ilAsmRefs) :> IRawFSharpAssemblyData
- return ProjectAssemblyDataResult.Available data
- }
- |> NodeCode.FromCancellable
-
- member x.TryGetLogicalTimeStamp _ = getStamp () |> Some
- member x.FileName = nm
- }
- ]
-
- /// CreateOneIncrementalBuilder (for background type checking). Note that fsc.fs also
- /// creates an incremental builder used by the command line compiler.
- let CreateOneIncrementalBuilder (options: FSharpProjectOptions, userOpName) =
- node {
- use _ =
- Activity.start "BackgroundCompiler.CreateOneIncrementalBuilder" [| Activity.Tags.project, options.ProjectFileName |]
-
- Trace.TraceInformation("FCS: {0}.{1} ({2})", userOpName, "CreateOneIncrementalBuilder", options.ProjectFileName)
- let projectReferences = getProjectReferences options userOpName
-
- let loadClosure = scriptClosureCache.TryGet(AnyCallerThread, options)
-
- let dependencyProvider =
- if options.UseScriptResolutionRules then
- Some dependencyProviderForScripts
- else
- None
-
- let! builderOpt, diagnostics =
- IncrementalBuilder.TryCreateIncrementalBuilderForProjectOptions(
- legacyReferenceResolver,
- FSharpCheckerResultsSettings.defaultFSharpBinariesDir,
- frameworkTcImportsCache,
- loadClosure,
- Array.toList options.SourceFiles,
- Array.toList options.OtherOptions,
- projectReferences,
- options.ProjectDirectory,
- options.UseScriptResolutionRules,
- keepAssemblyContents,
- keepAllBackgroundResolutions,
- tryGetMetadataSnapshot,
- suggestNamesForErrors,
- keepAllBackgroundSymbolUses,
- enableBackgroundItemKeyStoreAndSemanticClassification,
- enablePartialTypeChecking,
- dependencyProvider,
- parallelReferenceResolution,
- captureIdentifiersWhenParsing,
- getSource,
- useChangeNotifications,
- useSyntaxTreeCache
- )
-
- match builderOpt with
- | None -> ()
- | Some builder ->
-
-#if !NO_TYPEPROVIDERS
- // Register the behaviour that responds to CCUs being invalidated because of type
- // provider Invalidate events. This invalidates the configuration in the build.
- builder.ImportsInvalidatedByTypeProvider.Add(fun () -> self.InvalidateConfiguration(options, userOpName))
-#endif
-
- // Register the callback called just before a file is typechecked by the background builder (without recording
- // errors or intellisense information).
- //
- // This indicates to the UI that the file type check state is dirty. If the file is open and visible then
- // the UI will sooner or later request a typecheck of the file, recording errors and intellisense information.
- builder.BeforeFileChecked.Add(fun file -> beforeFileChecked.Trigger(file, options))
- builder.FileParsed.Add(fun file -> fileParsed.Trigger(file, options))
- builder.FileChecked.Add(fun file -> fileChecked.Trigger(file, options))
- builder.ProjectChecked.Add(fun () -> projectChecked.Trigger options)
-
- return (builderOpt, diagnostics)
- }
-
- let parseCacheLock = Lock()
-
- // STATIC ROOT: FSharpLanguageServiceTestable.FSharpChecker.parseFileInProjectCache. Most recently used cache for parsing files.
- let parseFileCache =
- MruCache(parseFileCacheSize, areSimilar = AreSimilarForParsing, areSame = AreSameForParsing)
-
- // STATIC ROOT: FSharpLanguageServiceTestable.FSharpChecker.checkFileInProjectCache
- //
- /// Cache which holds recently seen type-checks.
- /// This cache may hold out-of-date entries, in two senses
- /// - there may be a more recent antecedent state available because the background build has made it available
- /// - the source for the file may have changed
-
- // Also keyed on source. This can only be out of date if the antecedent is out of date
- let checkFileInProjectCache =
- MruCache>(
- keepStrongly = checkFileInProjectCacheSize,
- areSame = AreSameForChecking3,
- areSimilar = AreSubsumable3
- )
-
- // STATIC ROOT: FSharpLanguageServiceTestable.FSharpChecker.backgroundCompiler.incrementalBuildersCache. This root typically holds more
- // live information than anything else in the F# Language Service, since it holds up to 3 (projectCacheStrongSize) background project builds
- // strongly.
- //
- /// Cache of builds keyed by options.
- let gate = obj ()
-
- let incrementalBuildersCache =
- MruCache>(
- keepStrongly = projectCacheSize,
- keepMax = projectCacheSize,
- areSame = FSharpProjectOptions.AreSameForChecking,
- areSimilar = FSharpProjectOptions.UseSameProject
- )
-
- let tryGetBuilderNode options =
- incrementalBuildersCache.TryGet(AnyCallerThread, options)
-
- let tryGetBuilder options : NodeCode option =
- tryGetBuilderNode options |> Option.map (fun x -> x.GetOrComputeValue())
-
- let tryGetSimilarBuilder options : NodeCode option =
- incrementalBuildersCache.TryGetSimilar(AnyCallerThread, options)
- |> Option.map (fun x -> x.GetOrComputeValue())
-
- let tryGetAnyBuilder options : NodeCode option =
- incrementalBuildersCache.TryGetAny(AnyCallerThread, options)
- |> Option.map (fun x -> x.GetOrComputeValue())
-
- let createBuilderNode (options, userOpName, ct: CancellationToken) =
- lock gate (fun () ->
- if ct.IsCancellationRequested then
- GraphNode.FromResult(None, [||])
- else
- let getBuilderNode = GraphNode(CreateOneIncrementalBuilder(options, userOpName))
- incrementalBuildersCache.Set(AnyCallerThread, options, getBuilderNode)
- getBuilderNode)
-
- let createAndGetBuilder (options, userOpName) =
- node {
- let! ct = NodeCode.CancellationToken
- let getBuilderNode = createBuilderNode (options, userOpName, ct)
- return! getBuilderNode.GetOrComputeValue()
- }
-
- let getOrCreateBuilder (options, userOpName) : NodeCode =
- match tryGetBuilder options with
- | Some getBuilder ->
- node {
- match! getBuilder with
- | builderOpt, creationDiags when builderOpt.IsNone || not builderOpt.Value.IsReferencesInvalidated -> return builderOpt, creationDiags
- | _ ->
- // The builder could be re-created,
- // clear the check file caches that are associated with it.
- // We must do this in order to not return stale results when references
- // in the project get changed/added/removed.
- parseCacheLock.AcquireLock(fun ltok ->
- options.SourceFiles
- |> Array.iter (fun sourceFile ->
- let key = (sourceFile, 0L, options)
- checkFileInProjectCache.RemoveAnySimilar(ltok, key)))
-
- return! createAndGetBuilder (options, userOpName)
- }
- | _ -> createAndGetBuilder (options, userOpName)
-
- let getSimilarOrCreateBuilder (options, userOpName) =
- match tryGetSimilarBuilder options with
- | Some res -> res
- // The builder does not exist at all. Create it.
- | None -> getOrCreateBuilder (options, userOpName)
-
- let getOrCreateBuilderWithInvalidationFlag (options, canInvalidateProject, userOpName) =
- if canInvalidateProject then
- getOrCreateBuilder (options, userOpName)
- else
- getSimilarOrCreateBuilder (options, userOpName)
-
- let getAnyBuilder (options, userOpName) =
- match tryGetAnyBuilder options with
- | Some getBuilder -> getBuilder
- | _ -> getOrCreateBuilder (options, userOpName)
-
- static let mutable actualParseFileCount = 0
-
- static let mutable actualCheckFileCount = 0
-
- /// Should be a fast operation. Ensures that we have only one async lazy object per file and its hash.
- let getCheckFileNode (parseResults, sourceText, fileName, options, _fileVersion, builder, tcPrior, tcInfo, creationDiags) =
-
- // Here we lock for the creation of the node, not its execution
- parseCacheLock.AcquireLock(fun ltok ->
- let key = (fileName, sourceText.GetHashCode() |> int64, options)
-
- match checkFileInProjectCache.TryGet(ltok, key) with
- | Some res -> res
- | _ ->
- let res =
- GraphNode(
- node {
- let! res = self.CheckOneFileImplAux(parseResults, sourceText, fileName, options, builder, tcPrior, tcInfo, creationDiags)
- Interlocked.Increment(&actualCheckFileCount) |> ignore
- return res
- }
- )
-
- checkFileInProjectCache.Set(ltok, key, res)
- res)
-
- member _.ParseFile(fileName: string, sourceText: ISourceText, options: FSharpParsingOptions, cache: bool, flatErrors: bool, userOpName: string) =
- async {
- use _ =
- Activity.start
- "BackgroundCompiler.ParseFile"
- [|
- Activity.Tags.fileName, fileName
- Activity.Tags.userOpName, userOpName
- Activity.Tags.cache, cache.ToString()
- |]
-
- let! ct = Async.CancellationToken
- use _ = Cancellable.UsingToken(ct)
-
- if cache then
- let hash = sourceText.GetHashCode() |> int64
-
- match parseCacheLock.AcquireLock(fun ltok -> parseFileCache.TryGet(ltok, (fileName, hash, options))) with
- | Some res -> return res
- | None ->
- Interlocked.Increment(&actualParseFileCount) |> ignore
- let! ct = Async.CancellationToken
-
- let parseDiagnostics, parseTree, anyErrors =
- ParseAndCheckFile.parseFile (
- sourceText,
- fileName,
- options,
- userOpName,
- suggestNamesForErrors,
- flatErrors,
- captureIdentifiersWhenParsing,
- ct
- )
-
- let res = FSharpParseFileResults(parseDiagnostics, parseTree, anyErrors, options.SourceFiles)
- parseCacheLock.AcquireLock(fun ltok -> parseFileCache.Set(ltok, (fileName, hash, options), res))
- return res
- else
- let! ct = Async.CancellationToken
-
- let parseDiagnostics, parseTree, anyErrors =
- ParseAndCheckFile.parseFile (sourceText, fileName, options, userOpName, false, flatErrors, captureIdentifiersWhenParsing, ct)
-
- return FSharpParseFileResults(parseDiagnostics, parseTree, anyErrors, options.SourceFiles)
- }
-
- /// Fetch the parse information from the background compiler (which checks w.r.t. the FileSystem API)
- member _.GetBackgroundParseResultsForFileInProject(fileName, options, userOpName) =
- node {
- use _ =
- Activity.start
- "BackgroundCompiler.GetBackgroundParseResultsForFileInProject"
- [| Activity.Tags.fileName, fileName; Activity.Tags.userOpName, userOpName |]
-
- let! ct = NodeCode.CancellationToken
- use _ = Cancellable.UsingToken(ct)
-
- let! builderOpt, creationDiags = getOrCreateBuilder (options, userOpName)
-
- match builderOpt with
- | None ->
- let parseTree = EmptyParsedInput(fileName, (false, false))
- return FSharpParseFileResults(creationDiags, parseTree, true, [||])
- | Some builder ->
- let parseTree, _, _, parseDiagnostics = builder.GetParseResultsForFile fileName
-
- let parseDiagnostics =
- DiagnosticHelpers.CreateDiagnostics(
- builder.TcConfig.diagnosticsOptions,
- false,
- fileName,
- parseDiagnostics,
- suggestNamesForErrors,
- builder.TcConfig.flatErrors,
- None
- )
-
- let diagnostics = [| yield! creationDiags; yield! parseDiagnostics |]
-
- let parseResults =
- FSharpParseFileResults(
- diagnostics = diagnostics,
- input = parseTree,
- parseHadErrors = false,
- dependencyFiles = builder.AllDependenciesDeprecated
- )
-
- return parseResults
- }
-
- member _.GetCachedCheckFileResult(builder: IncrementalBuilder, fileName, sourceText: ISourceText, options) =
- node {
- use _ =
- Activity.start "BackgroundCompiler.GetCachedCheckFileResult" [| Activity.Tags.fileName, fileName |]
-
- let hash = sourceText.GetHashCode() |> int64
- let key = (fileName, hash, options)
- let cachedResultsOpt = parseCacheLock.AcquireLock(fun ltok -> checkFileInProjectCache.TryGet(ltok, key))
-
- match cachedResultsOpt with
- | Some cachedResults ->
- match! cachedResults.GetOrComputeValue() with
- | parseResults, checkResults, _, priorTimeStamp when
- (match builder.GetCheckResultsBeforeFileInProjectEvenIfStale fileName with
- | None -> false
- | Some(tcPrior) ->
- tcPrior.ProjectTimeStamp = priorTimeStamp
- && builder.AreCheckResultsBeforeFileInProjectReady(fileName))
- ->
- return Some(parseResults, checkResults)
- | _ ->
- parseCacheLock.AcquireLock(fun ltok -> checkFileInProjectCache.RemoveAnySimilar(ltok, key))
- return None
- | _ -> return None
- }
-
- member private _.CheckOneFileImplAux
- (
- parseResults: FSharpParseFileResults,
- sourceText: ISourceText,
- fileName: string,
- options: FSharpProjectOptions,
- builder: IncrementalBuilder,
- tcPrior: PartialCheckResults,
- tcInfo: TcInfo,
- creationDiags: FSharpDiagnostic[]
- ) : NodeCode =
-
- node {
- // Get additional script #load closure information if applicable.
- // For scripts, this will have been recorded by GetProjectOptionsFromScript.
- let tcConfig = tcPrior.TcConfig
- let loadClosure = scriptClosureCache.TryGet(AnyCallerThread, options)
-
- let! checkAnswer =
- FSharpCheckFileResults.CheckOneFile(
- parseResults,
- sourceText,
- fileName,
- options.ProjectFileName,
- tcConfig,
- tcPrior.TcGlobals,
- tcPrior.TcImports,
- tcInfo.tcState,
- tcInfo.moduleNamesDict,
- loadClosure,
- tcInfo.TcDiagnostics,
- options.IsIncompleteTypeCheckEnvironment,
- options,
- builder,
- Array.ofList tcInfo.tcDependencyFiles,
- creationDiags,
- parseResults.Diagnostics,
- keepAssemblyContents,
- suggestNamesForErrors
- )
- |> NodeCode.FromCancellable
-
- GraphNode.SetPreferredUILang tcConfig.preferredUiLang
- return (parseResults, checkAnswer, sourceText.GetHashCode() |> int64, tcPrior.ProjectTimeStamp)
- }
-
- member private bc.CheckOneFileImpl
- (
- parseResults: FSharpParseFileResults,
- sourceText: ISourceText,
- fileName: string,
- options: FSharpProjectOptions,
- fileVersion: int,
- builder: IncrementalBuilder,
- tcPrior: PartialCheckResults,
- tcInfo: TcInfo,
- creationDiags: FSharpDiagnostic[]
- ) =
-
- node {
- match! bc.GetCachedCheckFileResult(builder, fileName, sourceText, options) with
- | Some(_, results) -> return FSharpCheckFileAnswer.Succeeded results
- | _ ->
- let lazyCheckFile =
- getCheckFileNode (parseResults, sourceText, fileName, options, fileVersion, builder, tcPrior, tcInfo, creationDiags)
-
- let! _, results, _, _ = lazyCheckFile.GetOrComputeValue()
- return FSharpCheckFileAnswer.Succeeded results
- }
-
- /// Type-check the result obtained by parsing, but only if the antecedent type checking context is available.
- member bc.CheckFileInProjectAllowingStaleCachedResults
- (
- parseResults: FSharpParseFileResults,
- fileName,
- fileVersion,
- sourceText: ISourceText,
- options,
- userOpName
- ) =
- node {
- use _ =
- Activity.start
- "BackgroundCompiler.CheckFileInProjectAllowingStaleCachedResults"
- [|
- Activity.Tags.project, options.ProjectFileName
- Activity.Tags.fileName, fileName
- Activity.Tags.userOpName, userOpName
- |]
-
- let! ct = NodeCode.CancellationToken
- use _ = Cancellable.UsingToken(ct)
-
- let! cachedResults =
- node {
- let! builderOpt, creationDiags = getAnyBuilder (options, userOpName)
-
- match builderOpt with
- | Some builder ->
- match! bc.GetCachedCheckFileResult(builder, fileName, sourceText, options) with
- | Some(_, checkResults) -> return Some(builder, creationDiags, Some(FSharpCheckFileAnswer.Succeeded checkResults))
- | _ -> return Some(builder, creationDiags, None)
- | _ -> return None // the builder wasn't ready
- }
-
- match cachedResults with
- | None -> return None
- | Some(_, _, Some x) -> return Some x
- | Some(builder, creationDiags, None) ->
- Trace.TraceInformation("FCS: {0}.{1} ({2})", userOpName, "CheckFileInProjectAllowingStaleCachedResults.CacheMiss", fileName)
-
- match builder.GetCheckResultsBeforeFileInProjectEvenIfStale fileName with
- | Some tcPrior ->
- match tcPrior.TryPeekTcInfo() with
- | Some tcInfo ->
- let! checkResults =
- bc.CheckOneFileImpl(parseResults, sourceText, fileName, options, fileVersion, builder, tcPrior, tcInfo, creationDiags)
-
- return Some checkResults
- | None -> return None
- | None -> return None // the incremental builder was not up to date
- }
-
- /// Type-check the result obtained by parsing. Force the evaluation of the antecedent type checking context if needed.
- member bc.CheckFileInProject(parseResults: FSharpParseFileResults, fileName, fileVersion, sourceText: ISourceText, options, userOpName) =
- node {
- use _ =
- Activity.start
- "BackgroundCompiler.CheckFileInProject"
- [|
- Activity.Tags.project, options.ProjectFileName
- Activity.Tags.fileName, fileName
- Activity.Tags.userOpName, userOpName
- |]
-
- let! ct = NodeCode.CancellationToken
- use _ = Cancellable.UsingToken(ct)
-
- let! builderOpt, creationDiags = getOrCreateBuilder (options, userOpName)
-
- match builderOpt with
- | None -> return FSharpCheckFileAnswer.Succeeded(FSharpCheckFileResults.MakeEmpty(fileName, creationDiags, keepAssemblyContents))
- | Some builder ->
- // Check the cache. We can only use cached results when there is no work to do to bring the background builder up-to-date
- let! cachedResults = bc.GetCachedCheckFileResult(builder, fileName, sourceText, options)
-
- match cachedResults with
- | Some(_, checkResults) -> return FSharpCheckFileAnswer.Succeeded checkResults
- | _ ->
- let! tcPrior = builder.GetCheckResultsBeforeFileInProject fileName
- let! tcInfo = tcPrior.GetOrComputeTcInfo()
- return! bc.CheckOneFileImpl(parseResults, sourceText, fileName, options, fileVersion, builder, tcPrior, tcInfo, creationDiags)
- }
-
- /// Parses and checks the source file and returns untyped AST and check results.
- member bc.ParseAndCheckFileInProject(fileName: string, fileVersion, sourceText: ISourceText, options: FSharpProjectOptions, userOpName) =
- node {
- use _ =
- Activity.start
- "BackgroundCompiler.ParseAndCheckFileInProject"
- [|
- Activity.Tags.project, options.ProjectFileName
- Activity.Tags.fileName, fileName
- Activity.Tags.userOpName, userOpName
- |]
-
- let! ct = NodeCode.CancellationToken
- use _ = Cancellable.UsingToken(ct)
-
- let! builderOpt, creationDiags = getOrCreateBuilder (options, userOpName)
-
- match builderOpt with
- | None ->
- let parseTree = EmptyParsedInput(fileName, (false, false))
- let parseResults = FSharpParseFileResults(creationDiags, parseTree, true, [||])
- return (parseResults, FSharpCheckFileAnswer.Aborted)
-
- | Some builder ->
- let! cachedResults = bc.GetCachedCheckFileResult(builder, fileName, sourceText, options)
-
- match cachedResults with
- | Some(parseResults, checkResults) -> return (parseResults, FSharpCheckFileAnswer.Succeeded checkResults)
- | _ ->
- let! tcPrior = builder.GetCheckResultsBeforeFileInProject fileName
- let! tcInfo = tcPrior.GetOrComputeTcInfo()
- // Do the parsing.
- let parsingOptions =
- FSharpParsingOptions.FromTcConfig(builder.TcConfig, Array.ofList builder.SourceFiles, options.UseScriptResolutionRules)
-
- GraphNode.SetPreferredUILang tcPrior.TcConfig.preferredUiLang
- let! ct = NodeCode.CancellationToken
-
- let parseDiagnostics, parseTree, anyErrors =
- ParseAndCheckFile.parseFile (
- sourceText,
- fileName,
- parsingOptions,
- userOpName,
- suggestNamesForErrors,
- builder.TcConfig.flatErrors,
- captureIdentifiersWhenParsing,
- ct
- )
-
- let parseResults =
- FSharpParseFileResults(parseDiagnostics, parseTree, anyErrors, builder.AllDependenciesDeprecated)
-
- let! checkResults =
- bc.CheckOneFileImpl(parseResults, sourceText, fileName, options, fileVersion, builder, tcPrior, tcInfo, creationDiags)
-
- return (parseResults, checkResults)
- }
-
- member _.NotifyFileChanged(fileName, options, userOpName) =
- node {
- use _ =
- Activity.start
- "BackgroundCompiler.NotifyFileChanged"
- [|
- Activity.Tags.project, options.ProjectFileName
- Activity.Tags.fileName, fileName
- Activity.Tags.userOpName, userOpName
- |]
-
- let! ct = NodeCode.CancellationToken
- use _ = Cancellable.UsingToken(ct)
-
- let! builderOpt, _ = getOrCreateBuilder (options, userOpName)
-
- match builderOpt with
- | None -> return ()
- | Some builder -> do! builder.NotifyFileChanged(fileName, DateTime.UtcNow)
- }
-
- /// Fetch the check information from the background compiler (which checks w.r.t. the FileSystem API)
- member _.GetBackgroundCheckResultsForFileInProject(fileName, options, userOpName) =
- node {
- use _ =
- Activity.start
- "BackgroundCompiler.ParseAndCheckFileInProject"
- [|
- Activity.Tags.project, options.ProjectFileName
- Activity.Tags.fileName, fileName
- Activity.Tags.userOpName, userOpName
- |]
-
- let! ct = NodeCode.CancellationToken
- use _ = Cancellable.UsingToken(ct)
-
- let! builderOpt, creationDiags = getOrCreateBuilder (options, userOpName)
-
- match builderOpt with
- | None ->
- let parseTree = EmptyParsedInput(fileName, (false, false))
- let parseResults = FSharpParseFileResults(creationDiags, parseTree, true, [||])
- let typedResults = FSharpCheckFileResults.MakeEmpty(fileName, creationDiags, true)
- return (parseResults, typedResults)
- | Some builder ->
- let parseTree, _, _, parseDiagnostics = builder.GetParseResultsForFile fileName
- let! tcProj = builder.GetFullCheckResultsAfterFileInProject fileName
-
- let! tcInfo, tcInfoExtras = tcProj.GetOrComputeTcInfoWithExtras()
-
- let tcResolutions = tcInfoExtras.tcResolutions
- let tcSymbolUses = tcInfoExtras.tcSymbolUses
- let tcOpenDeclarations = tcInfoExtras.tcOpenDeclarations
- let latestCcuSigForFile = tcInfo.latestCcuSigForFile
- let tcState = tcInfo.tcState
- let tcEnvAtEnd = tcInfo.tcEnvAtEndOfFile
- let latestImplementationFile = tcInfoExtras.latestImplFile
- let tcDependencyFiles = tcInfo.tcDependencyFiles
- let tcDiagnostics = tcInfo.TcDiagnostics
- let diagnosticsOptions = builder.TcConfig.diagnosticsOptions
-
- let symbolEnv =
- SymbolEnv(tcProj.TcGlobals, tcInfo.tcState.Ccu, Some tcInfo.tcState.CcuSig, tcProj.TcImports)
- |> Some
-
- let parseDiagnostics =
- DiagnosticHelpers.CreateDiagnostics(
- diagnosticsOptions,
- false,
- fileName,
- parseDiagnostics,
- suggestNamesForErrors,
- builder.TcConfig.flatErrors,
- None
- )
-
- let parseDiagnostics = [| yield! creationDiags; yield! parseDiagnostics |]
-
- let tcDiagnostics =
- DiagnosticHelpers.CreateDiagnostics(
- diagnosticsOptions,
- false,
- fileName,
- tcDiagnostics,
- suggestNamesForErrors,
- builder.TcConfig.flatErrors,
- symbolEnv
- )
-
- let tcDiagnostics = [| yield! creationDiags; yield! tcDiagnostics |]
-
- let parseResults =
- FSharpParseFileResults(
- diagnostics = parseDiagnostics,
- input = parseTree,
- parseHadErrors = false,
- dependencyFiles = builder.AllDependenciesDeprecated
- )
-
- let loadClosure = scriptClosureCache.TryGet(AnyCallerThread, options)
-
- let typedResults =
- FSharpCheckFileResults.Make(
- fileName,
- options.ProjectFileName,
- tcProj.TcConfig,
- tcProj.TcGlobals,
- options.IsIncompleteTypeCheckEnvironment,
- builder,
- options,
- Array.ofList tcDependencyFiles,
- creationDiags,
- parseResults.Diagnostics,
- tcDiagnostics,
- keepAssemblyContents,
- Option.get latestCcuSigForFile,
- tcState.Ccu,
- tcProj.TcImports,
- tcEnvAtEnd.AccessRights,
- tcResolutions,
- tcSymbolUses,
- tcEnvAtEnd.NameEnv,
- loadClosure,
- latestImplementationFile,
- tcOpenDeclarations
- )
-
- return (parseResults, typedResults)
- }
-
- member _.FindReferencesInFile
- (
- fileName: string,
- options: FSharpProjectOptions,
- symbol: FSharpSymbol,
- canInvalidateProject: bool,
- userOpName: string
- ) =
- node {
- use _ =
- Activity.start
- "BackgroundCompiler.FindReferencesInFile"
- [|
- Activity.Tags.project, options.ProjectFileName
- Activity.Tags.fileName, fileName
- Activity.Tags.userOpName, userOpName
- "symbol", symbol.FullName
- |]
-
- let! builderOpt, _ = getOrCreateBuilderWithInvalidationFlag (options, canInvalidateProject, userOpName)
-
- match builderOpt with
- | None -> return Seq.empty
- | Some builder ->
- if builder.ContainsFile fileName then
- let! checkResults = builder.GetFullCheckResultsAfterFileInProject fileName
- let! keyStoreOpt = checkResults.GetOrComputeItemKeyStoreIfEnabled()
-
- match keyStoreOpt with
- | None -> return Seq.empty
- | Some reader -> return reader.FindAll symbol.Item
- else
- return Seq.empty
- }
-
- member _.GetSemanticClassificationForFile(fileName: string, options: FSharpProjectOptions, userOpName: string) =
- node {
- use _ =
- Activity.start
- "BackgroundCompiler.GetSemanticClassificationForFile"
- [|
- Activity.Tags.project, options.ProjectFileName
- Activity.Tags.fileName, fileName
- Activity.Tags.userOpName, userOpName
- |]
-
- let! ct = NodeCode.CancellationToken
- use _ = Cancellable.UsingToken(ct)
-
- let! builderOpt, _ = getOrCreateBuilder (options, userOpName)
-
- match builderOpt with
- | None -> return None
- | Some builder ->
- let! checkResults = builder.GetFullCheckResultsAfterFileInProject fileName
- let! scopt = checkResults.GetOrComputeSemanticClassificationIfEnabled()
-
- match scopt with
- | None -> return None
- | Some sc -> return Some(sc.GetView())
- }
-
- /// Try to get recent approximate type check results for a file.
- member _.TryGetRecentCheckResultsForFile(fileName: string, options: FSharpProjectOptions, sourceText: ISourceText option, _userOpName: string) =
- use _ =
- Activity.start
- "BackgroundCompiler.GetSemanticClassificationForFile"
- [|
- Activity.Tags.project, options.ProjectFileName
- Activity.Tags.fileName, fileName
- Activity.Tags.userOpName, _userOpName
- |]
-
- match sourceText with
- | Some sourceText ->
- let hash = sourceText.GetHashCode() |> int64
-
- let resOpt =
- parseCacheLock.AcquireLock(fun ltok -> checkFileInProjectCache.TryGet(ltok, (fileName, hash, options)))
-
- match resOpt with
- | Some res ->
- match res.TryPeekValue() with
- | ValueSome(a, b, c, _) -> Some(a, b, c)
- | ValueNone -> None
- | None -> None
- | None -> None
-
- /// Parse and typecheck the whole project (the implementation, called recursively as project graph is evaluated)
- member private _.ParseAndCheckProjectImpl(options, userOpName) =
- node {
- let! ct = NodeCode.CancellationToken
- use _ = Cancellable.UsingToken(ct)
-
- let! builderOpt, creationDiags = getOrCreateBuilder (options, userOpName)
-
- match builderOpt with
- | None ->
- let emptyResults =
- FSharpCheckProjectResults(options.ProjectFileName, None, keepAssemblyContents, creationDiags, None)
-
- return emptyResults
- | Some builder ->
- let! tcProj, ilAssemRef, tcAssemblyDataOpt, tcAssemblyExprOpt = builder.GetFullCheckResultsAndImplementationsForProject()
- let diagnosticsOptions = tcProj.TcConfig.diagnosticsOptions
- let fileName = DummyFileNameForRangesWithoutASpecificLocation
-
- // Although we do not use 'tcInfoExtras', computing it will make sure we get an extra info.
- let! tcInfo, _tcInfoExtras = tcProj.GetOrComputeTcInfoWithExtras()
-
- let topAttribs = tcInfo.topAttribs
- let tcState = tcInfo.tcState
- let tcEnvAtEnd = tcInfo.tcEnvAtEndOfFile
- let tcDiagnostics = tcInfo.TcDiagnostics
- let tcDependencyFiles = tcInfo.tcDependencyFiles
-
- let symbolEnv =
- SymbolEnv(tcProj.TcGlobals, tcInfo.tcState.Ccu, Some tcInfo.tcState.CcuSig, tcProj.TcImports)
- |> Some
-
- let tcDiagnostics =
- DiagnosticHelpers.CreateDiagnostics(
- diagnosticsOptions,
- true,
- fileName,
- tcDiagnostics,
- suggestNamesForErrors,
- builder.TcConfig.flatErrors,
- symbolEnv
- )
-
- let diagnostics = [| yield! creationDiags; yield! tcDiagnostics |]
-
- let getAssemblyData () =
- match tcAssemblyDataOpt with
- | ProjectAssemblyDataResult.Available data -> Some data
- | _ -> None
-
- let details =
- (tcProj.TcGlobals,
- tcProj.TcImports,
- tcState.Ccu,
- tcState.CcuSig,
- Choice1Of2 builder,
- topAttribs,
- getAssemblyData,
- ilAssemRef,
- tcEnvAtEnd.AccessRights,
- tcAssemblyExprOpt,
- Array.ofList tcDependencyFiles,
- options)
-
- let results =
- FSharpCheckProjectResults(options.ProjectFileName, Some tcProj.TcConfig, keepAssemblyContents, diagnostics, Some details)
-
- return results
- }
-
- member _.GetAssemblyData(options, userOpName) =
- node {
- use _ =
- Activity.start
- "BackgroundCompiler.GetAssemblyData"
- [|
- Activity.Tags.project, options.ProjectFileName
- Activity.Tags.userOpName, userOpName
- |]
-
- let! builderOpt, _ = getOrCreateBuilder (options, userOpName)
-
- match builderOpt with
- | None -> return ProjectAssemblyDataResult.Unavailable true
- | Some builder ->
- let! _, _, tcAssemblyDataOpt, _ = builder.GetCheckResultsAndImplementationsForProject()
- return tcAssemblyDataOpt
- }
-
- /// Get the timestamp that would be on the output if fully built immediately
- member private _.TryGetLogicalTimeStampForProject(cache, options) =
- match tryGetBuilderNode options with
- | Some lazyWork ->
- match lazyWork.TryPeekValue() with
- | ValueSome(Some builder, _) -> Some(builder.GetLogicalTimeStampForProject(cache))
- | _ -> None
- | _ -> None
-
- /// Parse and typecheck the whole project.
- member bc.ParseAndCheckProject(options, userOpName) =
- use _ =
- Activity.start
- "BackgroundCompiler.ParseAndCheckProject"
- [|
- Activity.Tags.project, options.ProjectFileName
- Activity.Tags.userOpName, userOpName
- |]
-
- bc.ParseAndCheckProjectImpl(options, userOpName)
-
- member _.GetProjectOptionsFromScript
- (
- fileName,
- sourceText,
- previewEnabled,
- loadedTimeStamp,
- otherFlags,
- useFsiAuxLib: bool option,
- useSdkRefs: bool option,
- sdkDirOverride: string option,
- assumeDotNetFramework: bool option,
- optionsStamp: int64 option,
- _userOpName
- ) =
- use _ =
- Activity.start
- "BackgroundCompiler.GetProjectOptionsFromScript"
- [| Activity.Tags.fileName, fileName; Activity.Tags.userOpName, _userOpName |]
-
- cancellable {
- // Do we add a reference to FSharp.Compiler.Interactive.Settings by default?
- let useFsiAuxLib = defaultArg useFsiAuxLib true
- let useSdkRefs = defaultArg useSdkRefs true
- let reduceMemoryUsage = ReduceMemoryFlag.Yes
- let previewEnabled = defaultArg previewEnabled false
-
- // Do we assume .NET Framework references for scripts?
- let assumeDotNetFramework = defaultArg assumeDotNetFramework true
-
- let! ct = Cancellable.token ()
- use _ = Cancellable.UsingToken(ct)
-
- let extraFlags =
- if previewEnabled then
- [| "--langversion:preview" |]
- else
- [||]
-
- let otherFlags = defaultArg otherFlags extraFlags
-
- use diagnostics = new DiagnosticsScope(otherFlags |> Array.contains "--flaterrors")
-
- let useSimpleResolution = otherFlags |> Array.exists (fun x -> x = "--simpleresolution")
-
- let loadedTimeStamp = defaultArg loadedTimeStamp DateTime.MaxValue // Not 'now', we don't want to force reloading
-
- let applyCompilerOptions tcConfigB =
- let fsiCompilerOptions = GetCoreFsiCompilerOptions tcConfigB
- ParseCompilerOptions(ignore, fsiCompilerOptions, Array.toList otherFlags)
-
- let loadClosure =
- LoadClosure.ComputeClosureOfScriptText(
- legacyReferenceResolver,
- FSharpCheckerResultsSettings.defaultFSharpBinariesDir,
- fileName,
- sourceText,
- CodeContext.Editing,
- useSimpleResolution,
- useFsiAuxLib,
- useSdkRefs,
- sdkDirOverride,
- Lexhelp.LexResourceManager(),
- applyCompilerOptions,
- assumeDotNetFramework,
- tryGetMetadataSnapshot,
- reduceMemoryUsage,
- dependencyProviderForScripts
- )
-
- let otherFlags =
- [|
- yield "--noframework"
- yield "--warn:3"
- yield! otherFlags
- for r in loadClosure.References do
- yield "-r:" + fst r
- for code, _ in loadClosure.NoWarns do
- yield "--nowarn:" + code
- |]
-
- let options =
- {
- ProjectFileName = fileName + ".fsproj" // Make a name that is unique in this directory.
- ProjectId = None
- SourceFiles = loadClosure.SourceFiles |> List.map fst |> List.toArray
- OtherOptions = otherFlags
- ReferencedProjects = [||]
- IsIncompleteTypeCheckEnvironment = false
- UseScriptResolutionRules = true
- LoadTime = loadedTimeStamp
- UnresolvedReferences = Some(FSharpUnresolvedReferencesSet(loadClosure.UnresolvedReferences))
- OriginalLoadReferences = loadClosure.OriginalLoadReferences
- Stamp = optionsStamp
- }
-
- scriptClosureCache.Set(AnyCallerThread, options, loadClosure) // Save the full load closure for later correlation.
-
- let diags =
- loadClosure.LoadClosureRootFileDiagnostics
- |> List.map (fun (exn, isError) ->
- FSharpDiagnostic.CreateFromException(
- exn,
- isError,
- range.Zero,
- false,
- options.OtherOptions |> Array.contains "--flaterrors",
- None
- ))
-
- return options, (diags @ diagnostics.Diagnostics)
- }
- |> Cancellable.toAsync
-
- member bc.InvalidateConfiguration(options: FSharpProjectOptions, userOpName) =
- use _ =
- Activity.start
- "BackgroundCompiler.InvalidateConfiguration"
- [|
- Activity.Tags.project, options.ProjectFileName
- Activity.Tags.userOpName, userOpName
- |]
-
- if incrementalBuildersCache.ContainsSimilarKey(AnyCallerThread, options) then
- parseCacheLock.AcquireLock(fun ltok ->
- for sourceFile in options.SourceFiles do
- checkFileInProjectCache.RemoveAnySimilar(ltok, (sourceFile, 0L, options)))
-
- let _ = createBuilderNode (options, userOpName, CancellationToken.None)
- ()
-
- member bc.ClearCache(options: seq, _userOpName) =
- use _ = Activity.start "BackgroundCompiler.ClearCache" [| Activity.Tags.userOpName, _userOpName |]
-
- lock gate (fun () ->
- options
- |> Seq.iter (fun options ->
- incrementalBuildersCache.RemoveAnySimilar(AnyCallerThread, options)
-
- parseCacheLock.AcquireLock(fun ltok ->
- for sourceFile in options.SourceFiles do
- checkFileInProjectCache.RemoveAnySimilar(ltok, (sourceFile, 0L, options)))))
-
- member _.NotifyProjectCleaned(options: FSharpProjectOptions, userOpName) =
- use _ =
- Activity.start
- "BackgroundCompiler.NotifyProjectCleaned"
- [|
- Activity.Tags.project, options.ProjectFileName
- Activity.Tags.userOpName, userOpName
- |]
-
- async {
- let! ct = Async.CancellationToken
- use _ = Cancellable.UsingToken(ct)
-
- let! ct = Async.CancellationToken
- // If there was a similar entry (as there normally will have been) then re-establish an empty builder . This
- // is a somewhat arbitrary choice - it will have the effect of releasing memory associated with the previous
- // builder, but costs some time.
- if incrementalBuildersCache.ContainsSimilarKey(AnyCallerThread, options) then
- let _ = createBuilderNode (options, userOpName, ct)
- ()
- }
-
- member _.BeforeBackgroundFileCheck = beforeFileChecked.Publish
-
- member _.FileParsed = fileParsed.Publish
-
- member _.FileChecked = fileChecked.Publish
-
- member _.ProjectChecked = projectChecked.Publish
-
- member _.ClearCaches() =
- use _ = Activity.startNoTags "BackgroundCompiler.ClearCaches"
-
- lock gate (fun () ->
- parseCacheLock.AcquireLock(fun ltok ->
- checkFileInProjectCache.Clear(ltok)
- parseFileCache.Clear(ltok))
-
- incrementalBuildersCache.Clear(AnyCallerThread)
- frameworkTcImportsCache.Clear()
- scriptClosureCache.Clear AnyCallerThread)
-
- member _.DownsizeCaches() =
- use _ = Activity.startNoTags "BackgroundCompiler.DownsizeCaches"
-
- lock gate (fun () ->
- parseCacheLock.AcquireLock(fun ltok ->
- checkFileInProjectCache.Resize(ltok, newKeepStrongly = 1)
- parseFileCache.Resize(ltok, newKeepStrongly = 1))
-
- incrementalBuildersCache.Resize(AnyCallerThread, newKeepStrongly = 1, newKeepMax = 1)
- frameworkTcImportsCache.Downsize()
- scriptClosureCache.Resize(AnyCallerThread, newKeepStrongly = 1, newKeepMax = 1))
-
- member _.FrameworkImportsCache = frameworkTcImportsCache
-
- static member ActualParseFileCount = actualParseFileCount
-
- static member ActualCheckFileCount = actualCheckFileCount
-
[]
// There is typically only one instance of this type in an IDE process.
type FSharpChecker
@@ -1366,26 +130,47 @@ type FSharpChecker
captureIdentifiersWhenParsing,
getSource,
useChangeNotifications,
- useSyntaxTreeCache
+ useSyntaxTreeCache,
+ useTransparentCompiler
) =
let backgroundCompiler =
- BackgroundCompiler(
- legacyReferenceResolver,
- projectCacheSize,
- keepAssemblyContents,
- keepAllBackgroundResolutions,
- tryGetMetadataSnapshot,
- suggestNamesForErrors,
- keepAllBackgroundSymbolUses,
- enableBackgroundItemKeyStoreAndSemanticClassification,
- enablePartialTypeChecking,
- parallelReferenceResolution,
- captureIdentifiersWhenParsing,
- getSource,
- useChangeNotifications,
- useSyntaxTreeCache
- )
+ if useTransparentCompiler = Some true then
+ TransparentCompiler(
+ legacyReferenceResolver,
+ projectCacheSize,
+ keepAssemblyContents,
+ keepAllBackgroundResolutions,
+ tryGetMetadataSnapshot,
+ suggestNamesForErrors,
+ keepAllBackgroundSymbolUses,
+ enableBackgroundItemKeyStoreAndSemanticClassification,
+ enablePartialTypeChecking,
+ parallelReferenceResolution,
+ captureIdentifiersWhenParsing,
+ getSource,
+ useChangeNotifications,
+ useSyntaxTreeCache
+ )
+ :> IBackgroundCompiler
+ else
+ BackgroundCompiler(
+ legacyReferenceResolver,
+ projectCacheSize,
+ keepAssemblyContents,
+ keepAllBackgroundResolutions,
+ tryGetMetadataSnapshot,
+ suggestNamesForErrors,
+ keepAllBackgroundSymbolUses,
+ enableBackgroundItemKeyStoreAndSemanticClassification,
+ enablePartialTypeChecking,
+ parallelReferenceResolution,
+ captureIdentifiersWhenParsing,
+ getSource,
+ useChangeNotifications,
+ useSyntaxTreeCache
+ )
+ :> IBackgroundCompiler
static let globalInstance = lazy FSharpChecker.Create()
@@ -1429,7 +214,8 @@ type FSharpChecker
?parallelReferenceResolution: bool,
?captureIdentifiersWhenParsing: bool,
?documentSource: DocumentSource,
- ?useSyntaxTreeCache: bool
+ ?useSyntaxTreeCache: bool,
+ ?useTransparentCompiler: bool
) =
use _ = Activity.startNoTags "FSharpChecker.Create"
@@ -1480,9 +266,19 @@ type FSharpChecker
| Some(DocumentSource.Custom f) -> Some f
| _ -> None),
useChangeNotifications,
- useSyntaxTreeCache
+ useSyntaxTreeCache,
+ useTransparentCompiler
)
+ member _.UsesTransparentCompiler = useTransparentCompiler = Some true
+
+ member _.TransparentCompiler =
+ match useTransparentCompiler with
+ | Some true -> backgroundCompiler :?> TransparentCompiler
+ | _ -> failwith "Transparent Compiler is not enabled."
+
+ member this.Caches = this.TransparentCompiler.Caches
+
member _.ReferenceResolver = legacyReferenceResolver
member _.MatchBraces(fileName, sourceText: ISourceText, options: FSharpParsingOptions, ?userOpName: string) =
@@ -1521,6 +317,11 @@ type FSharpChecker
let userOpName = defaultArg userOpName "Unknown"
backgroundCompiler.ParseFile(fileName, sourceText, options, cache, false, userOpName)
+ member _.ParseFile(fileName, projectSnapshot, ?userOpName) =
+ let userOpName = defaultArg userOpName "Unknown"
+
+ backgroundCompiler.ParseFile(fileName, projectSnapshot, userOpName)
+
member ic.ParseFileInProject(fileName, source: string, options, ?cache: bool, ?userOpName: string) =
let parsingOptions, _ = ic.GetParsingOptionsFromProjectOptions(options)
ic.ParseFile(fileName, SourceText.ofString source, parsingOptions, ?cache = cache, ?userOpName = userOpName)
@@ -1564,7 +365,6 @@ type FSharpChecker
backgroundCompiler.ClearCaches()
ClearAllILModuleReaderCache()
- // This is for unit testing only
member ic.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients() =
use _ =
Activity.startNoTags "FsharpChecker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients"
@@ -1585,6 +385,10 @@ type FSharpChecker
let userOpName = defaultArg userOpName "Unknown"
backgroundCompiler.ClearCache(options, userOpName)
+ member _.ClearCache(projects: ProjectSnapshot.FSharpProjectIdentifier seq, ?userOpName: string) =
+ let userOpName = defaultArg userOpName "Unknown"
+ backgroundCompiler.ClearCache(projects, userOpName)
+
/// This function is called when a project has been cleaned, and thus type providers should be refreshed.
member _.NotifyProjectCleaned(options: FSharpProjectOptions, ?userOpName: string) =
let userOpName = defaultArg userOpName "Unknown"
@@ -1650,12 +454,24 @@ type FSharpChecker
backgroundCompiler.ParseAndCheckFileInProject(fileName, fileVersion, sourceText, options, userOpName)
|> Async.AwaitNodeCode
- member _.ParseAndCheckProject(options, ?userOpName: string) =
+ member _.ParseAndCheckFileInProject(fileName: string, projectSnapshot: FSharpProjectSnapshot, ?userOpName: string) =
+ let userOpName = defaultArg userOpName "Unknown"
+
+ backgroundCompiler.ParseAndCheckFileInProject(fileName, projectSnapshot, userOpName)
+ |> Async.AwaitNodeCode
+
+ member _.ParseAndCheckProject(options: FSharpProjectOptions, ?userOpName: string) =
let userOpName = defaultArg userOpName "Unknown"
backgroundCompiler.ParseAndCheckProject(options, userOpName)
|> Async.AwaitNodeCode
+ member _.ParseAndCheckProject(projectSnapshot: FSharpProjectSnapshot, ?userOpName: string) =
+ let userOpName = defaultArg userOpName "Unknown"
+
+ backgroundCompiler.ParseAndCheckProject(projectSnapshot, userOpName)
+ |> Async.AwaitNodeCode
+
member _.FindBackgroundReferencesInFile
(
fileName: string,
@@ -1687,12 +503,36 @@ type FSharpChecker
}
|> Async.AwaitNodeCode
+ member _.FindBackgroundReferencesInFile(fileName: string, projectSnapshot: FSharpProjectSnapshot, symbol: FSharpSymbol, ?userOpName: string) =
+ let userOpName = defaultArg userOpName "Unknown"
+
+ node {
+ let! parseResults =
+ backgroundCompiler.ParseFile(fileName, projectSnapshot, userOpName)
+ |> NodeCode.AwaitAsync
+
+ if
+ parseResults.ParseTree.Identifiers |> Set.contains symbol.DisplayNameCore
+ || parseResults.ParseTree.Identifiers |> NamesContainAttribute symbol
+ then
+ return! backgroundCompiler.FindReferencesInFile(fileName, projectSnapshot, symbol, userOpName)
+ else
+ return Seq.empty
+ }
+ |> Async.AwaitNodeCode
+
member _.GetBackgroundSemanticClassificationForFile(fileName: string, options: FSharpProjectOptions, ?userOpName) =
let userOpName = defaultArg userOpName "Unknown"
backgroundCompiler.GetSemanticClassificationForFile(fileName, options, userOpName)
|> Async.AwaitNodeCode
+ member _.GetBackgroundSemanticClassificationForFile(fileName: string, snapshot: FSharpProjectSnapshot, ?userOpName) =
+ let userOpName = defaultArg userOpName "Unknown"
+
+ backgroundCompiler.GetSemanticClassificationForFile(fileName, snapshot, userOpName)
+ |> Async.AwaitNodeCode
+
/// For a given script file, get the ProjectOptions implied by the #load closure
member _.GetProjectOptionsFromScript
(
diff --git a/src/Compiler/Service/service.fsi b/src/Compiler/Service/service.fsi
index 297d7b367bc..14124fbda6b 100644
--- a/src/Compiler/Service/service.fsi
+++ b/src/Compiler/Service/service.fsi
@@ -6,8 +6,11 @@ namespace FSharp.Compiler.CodeAnalysis
open System
open System.IO
+open System.Threading
+open System.Threading.Tasks
open FSharp.Compiler.AbstractIL.ILBinaryReader
open FSharp.Compiler.CodeAnalysis
+open FSharp.Compiler.CodeAnalysis.TransparentCompiler
open FSharp.Compiler.CompilerConfig
open FSharp.Compiler.Diagnostics
open FSharp.Compiler.EditorServices
@@ -42,6 +45,7 @@ type public FSharpChecker =
/// When set to true we create a set of all identifiers for each parsed file which can be used to speed up finding references.
/// Default: FileSystem. You can use Custom source to provide a function that will return the source for a given file path instead of reading it from the file system. Note that with this option the FSharpChecker will also not monitor the file system for file changes. It will expect to be notified of changes via the NotifyFileChanged method.
/// Default: true. Indicates whether to keep parsing results in a cache.
+ /// Default: false. Indicates whether we use a new experimental background compiler. This does not yet support all features
static member Create:
?projectCacheSize: int *
?keepAssemblyContents: bool *
@@ -57,9 +61,13 @@ type public FSharpChecker =
[] ?documentSource:
DocumentSource *
[] ?useSyntaxTreeCache:
+ bool *
+ [] ?useTransparentCompiler:
bool ->
FSharpChecker
+ member internal UsesTransparentCompiler: bool
+
///
/// Parse a source code file, returning information about brace matching in the file.
/// Return an enumeration of the matching parenthetical tokens in the file.
@@ -100,6 +108,10 @@ type public FSharpChecker =
fileName: string * sourceText: ISourceText * options: FSharpParsingOptions * ?cache: bool * ?userOpName: string ->
Async
+ []
+ member ParseFile:
+ fileName: string * projectSnapshot: FSharpProjectSnapshot * ?userOpName: string -> Async
+
///
/// Parses a source code for a file. Returns an AST that can be traversed for various features.
///
@@ -193,6 +205,11 @@ type public FSharpChecker =
?userOpName: string ->
Async
+ []
+ member ParseAndCheckFileInProject:
+ fileName: string * projectSnapshot: FSharpProjectSnapshot * ?userOpName: string ->
+ Async
+
///
/// Parse and typecheck all files in a project.
/// All files are read from the FileSystem API
@@ -203,6 +220,10 @@ type public FSharpChecker =
/// An optional string used for tracing compiler operations associated with this request.
member ParseAndCheckProject: options: FSharpProjectOptions * ?userOpName: string -> Async
+ []
+ member ParseAndCheckProject:
+ projectSnapshot: FSharpProjectSnapshot * ?userOpName: string -> Async
+
///
/// For a given script file, get the FSharpProjectOptions implied by the #load closure.
/// All files are read from the FileSystem API, except the file being checked.
@@ -323,6 +344,11 @@ type public FSharpChecker =
?userOpName: string ->
Async
+ [