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 + [] + member FindBackgroundReferencesInFile: + fileName: string * projectSnapshot: FSharpProjectSnapshot * symbol: FSharpSymbol * ?userOpName: string -> + Async + /// /// Get semantic classification for a file. /// All files are read from the FileSystem API, including the file being checked. @@ -336,6 +362,18 @@ type public FSharpChecker = fileName: string * options: FSharpProjectOptions * ?userOpName: string -> Async + /// + /// Get semantic classification for a file. + /// + /// + /// The file name for the file. + /// The project snapshot for which we want to get the semantic classification. + /// An optional string used for tracing compiler operations associated with this request. + [] + member GetBackgroundSemanticClassificationForFile: + fileName: string * snapshot: FSharpProjectSnapshot * ?userOpName: string -> + Async + /// /// Compile using the given flags. Source files names are resolved via the FileSystem API. /// The output file must be given by a -o flag. @@ -377,6 +415,8 @@ type public FSharpChecker = /// An optional string used for tracing compiler operations associated with this request. member ClearCache: options: FSharpProjectOptions seq * ?userOpName: string -> unit + member ClearCache: projects: ProjectSnapshot.FSharpProjectIdentifier seq * ?userOpName: string -> unit + /// Report a statistic for testability static member ActualParseFileCount: int @@ -421,6 +461,10 @@ type public FSharpChecker = /// The event may be raised on a background thread. member ProjectChecked: IEvent + member internal TransparentCompiler: TransparentCompiler + + member internal Caches: CompilerCaches + [] static member Instance: FSharpChecker diff --git a/src/Compiler/Utilities/Activity.fs b/src/Compiler/Utilities/Activity.fs index 79697eae494..5f1d9c3354f 100644 --- a/src/Compiler/Utilities/Activity.fs +++ b/src/Compiler/Utilities/Activity.fs @@ -33,6 +33,7 @@ module internal Activity = let gc2 = "gc2" let outputDllFile = "outputDllFile" let buildPhase = "buildPhase" + let version = "version" let AllKnownTags = [| diff --git a/src/Compiler/Utilities/Activity.fsi b/src/Compiler/Utilities/Activity.fsi index 94784c97f00..afce0f3b554 100644 --- a/src/Compiler/Utilities/Activity.fsi +++ b/src/Compiler/Utilities/Activity.fsi @@ -1,5 +1,4 @@ // Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - namespace FSharp.Compiler.Diagnostics open System @@ -29,6 +28,7 @@ module internal Activity = val length: string val cache: string val buildPhase: string + val version: string module Events = val cacheHit: string diff --git a/src/Compiler/Utilities/LruCache.fs b/src/Compiler/Utilities/LruCache.fs new file mode 100644 index 00000000000..c75ed1d88cf --- /dev/null +++ b/src/Compiler/Utilities/LruCache.fs @@ -0,0 +1,273 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Internal.Utilities.Collections + +open System +open System.Collections.Generic +open System.Diagnostics + +open Internal.Utilities.Library.Extras + +[] +type internal CacheEvent = + | Evicted + | Collected + | Weakened + | Strengthened + | Cleared + +[] +type internal ValueLink<'T when 'T: not struct> = + | Strong of 'T + | Weak of WeakReference<'T> + +[] +type internal LruCache<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'TVersion: equality and 'TValue: not struct> + (keepStrongly, ?keepWeakly, ?requiredToKeep, ?event) = + + let keepWeakly = defaultArg keepWeakly 100 + let requiredToKeep = defaultArg requiredToKeep (fun _ -> false) + let event = defaultArg event (fun _ _ -> ()) + + let dictionary = Dictionary<'TKey, Dictionary<'TVersion, _>>() + + // Lists to keep track of when items were last accessed. First item is most recently accessed. + let strongList = LinkedList<'TKey * 'TVersion * string * ValueLink<'TValue>>() + let weakList = LinkedList<'TKey * 'TVersion * string * ValueLink<'TValue>>() + + let rec removeCollected (node: LinkedListNode<_>) = + if node <> null then + let key, version, label, value = node.Value + + match value with + | Weak w -> + let next = node.Next + + match w.TryGetTarget() with + | false, _ -> + weakList.Remove node + dictionary[key].Remove version |> ignore + + if dictionary[key].Count = 0 then + dictionary.Remove key |> ignore + + event CacheEvent.Collected (label, key, version) + | _ -> () + + removeCollected next + | _ -> failwith "Illegal state, strong reference in weak list" + + let cutWeakListIfTooLong () = + if weakList.Count > keepWeakly then + removeCollected weakList.First + + let mutable node = weakList.Last + + while weakList.Count > keepWeakly && node <> null do + let previous = node.Previous + let key, version, label, _ = node.Value + weakList.Remove node + dictionary[key].Remove version |> ignore + + if dictionary[key].Count = 0 then + dictionary.Remove key |> ignore + + event CacheEvent.Evicted (label, key, version) + node <- previous + + let cutStrongListIfTooLong () = + let mutable node = strongList.Last + + let mutable anythingWeakened = false + + while strongList.Count > keepStrongly && node <> null do + let previous = node.Previous + + match node.Value with + | _, _, _, Strong v when requiredToKeep v -> () + | key, version, label, Strong v -> + strongList.Remove node + node.Value <- key, version, label, Weak(WeakReference<_> v) + weakList.AddFirst node + event CacheEvent.Weakened (label, key, version) + anythingWeakened <- true + | _key, _version, _label, _ -> failwith "Invalid state, weak reference in strong list" + + node <- previous + + if anythingWeakened then + cutWeakListIfTooLong () + + let pushNodeToTop (node: LinkedListNode<_>) = + match node.Value with + | _, _, _, Strong _ -> + strongList.AddFirst node + cutStrongListIfTooLong () + | _, _, _, Weak _ -> failwith "Invalid operation, pushing weak reference to strong list" + + let pushValueToTop key version label value = + strongList.AddFirst(value = (key, version, label, Strong value)) + + member _.DebuggerDisplay = $"Cache(S:{strongList.Count} W:{weakList.Count})" + + member _.Set(key, version, label, value) = + match dictionary.TryGetValue key with + | true, versionDict -> + + if versionDict.ContainsKey version then + // TODO this is normal for unversioned cache; + // failwith "Suspicious - overwriting existing version" + + let node: LinkedListNode<_> = versionDict[version] + + match node.Value with + | _, _, _, Strong _ -> strongList.Remove node + | _, _, _, Weak _ -> + weakList.Remove node + event CacheEvent.Strengthened (label, key, version) + + node.Value <- key, version, label, Strong value + pushNodeToTop node + + else + let node = pushValueToTop key version label value + versionDict[version] <- node + // weaken all other versions (unless they're required to be kept) + let versionsToWeaken = versionDict.Keys |> Seq.filter ((<>) version) |> Seq.toList + + let mutable anythingWeakened = false + + for otherVersion in versionsToWeaken do + let node = versionDict[otherVersion] + + match node.Value with + | _, _, _, Strong value when not (requiredToKeep value) -> + strongList.Remove node + node.Value <- key, otherVersion, label, Weak(WeakReference<_> value) + weakList.AddFirst node + event CacheEvent.Weakened (label, key, otherVersion) + anythingWeakened <- true + | _ -> () + + if anythingWeakened then + cutWeakListIfTooLong () + else + cutStrongListIfTooLong () + + | false, _ -> + let node = pushValueToTop key version label value + cutStrongListIfTooLong () + dictionary[key] <- Dictionary() + dictionary[key][version] <- node + + member this.Set(key, version, value) = + this.Set(key, version, "[no label]", value) + + member _.TryGet(key, version) = + + match dictionary.TryGetValue key with + | false, _ -> None + | true, versionDict -> + match versionDict.TryGetValue version with + | false, _ -> None + | true, node -> + match node.Value with + | _, _, _, Strong v -> + strongList.Remove node + pushNodeToTop node + Some v + + | _, _, label, Weak w -> + match w.TryGetTarget() with + | true, value -> + weakList.Remove node + let node = pushValueToTop key version label value + event CacheEvent.Strengthened (label, key, version) + cutStrongListIfTooLong () + versionDict[version] <- node + Some value + | _ -> + weakList.Remove node + versionDict.Remove version |> ignore + + if versionDict.Count = 0 then + dictionary.Remove key |> ignore + + event CacheEvent.Collected (label, key, version) + None + + /// Returns an option of a value for given key and version, and also a list of all other versions for given key + member this.GetAll(key, version) = + this.TryGet(key, version), + + match dictionary.TryGetValue key with + | false, _ -> [] + | true, versionDict -> + versionDict.Values + |> Seq.map (fun node -> node.Value) + |> Seq.filter (p24 >> ((<>) version)) + |> Seq.choose (function + | _, ver, _, Strong v -> Some(ver, v) + | _, ver, _, Weak r -> + match r.TryGetTarget() with + | true, x -> Some(ver, x) + | _ -> None) + |> Seq.toList + + member _.Remove(key, version) = + match dictionary.TryGetValue key with + | false, _ -> () + | true, versionDict -> + match versionDict.TryGetValue version with + | true, node -> + versionDict.Remove version |> ignore + + if versionDict.Count = 0 then + dictionary.Remove key |> ignore + + match node.Value with + | _, _, _, Strong _ -> strongList.Remove node + | _, _, _, Weak _ -> weakList.Remove node + | _ -> () + + member this.Set(key, value) = + this.Set(key, Unchecked.defaultof<_>, value) + + member this.TryGet(key) = + this.TryGet(key, Unchecked.defaultof<_>) + + member this.Remove(key) = + this.Remove(key, Unchecked.defaultof<_>) + + member _.Clear() = + dictionary.Clear() + strongList.Clear() + weakList.Clear() + + member _.Clear(predicate) = + let keysToRemove = dictionary.Keys |> Seq.filter predicate |> Seq.toList + + for key in keysToRemove do + match dictionary.TryGetValue key with + | true, versionDict -> + versionDict.Values + |> Seq.iter (fun node -> + match node.Value with + | _, _, _, Strong _ -> strongList.Remove node + | _, _, _, Weak _ -> weakList.Remove node + + match node.Value with + | key, version, label, _ -> event CacheEvent.Cleared (label, key, version)) + + dictionary.Remove key |> ignore + | _ -> () + + member _.GetValues() = + strongList + |> Seq.append weakList + |> Seq.choose (function + | _k, version, label, Strong value -> Some(label, version, value) + | _k, version, label, Weak w -> + match w.TryGetTarget() with + | true, value -> Some(label, version, value) + | _ -> None) diff --git a/src/Compiler/Utilities/LruCache.fsi b/src/Compiler/Utilities/LruCache.fsi new file mode 100644 index 00000000000..d9aefd2a240 --- /dev/null +++ b/src/Compiler/Utilities/LruCache.fsi @@ -0,0 +1,52 @@ +namespace Internal.Utilities.Collections + +[] +type internal CacheEvent = + | Evicted + | Collected + | Weakened + | Strengthened + | Cleared + +/// A cache where least recently used items are removed when the cache is full. +/// +/// It's also versioned, meaning each key can have multiple versions and only the latest one is kept strongly. +/// Older versions are kept weakly and can be collected by GC. +type internal LruCache<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'TVersion: equality and 'TValue: not struct> = + + /// Maximum number of strongly held results to keep in the cache + /// Maximum number of weakly held results to keep in the cache + /// A predicate that determines if a value should be kept strongly (no matter what) + /// An event that is called when an item is evicted, collected, weakened or strengthened + new: + keepStrongly: int * + ?keepWeakly: int * + ?requiredToKeep: ('TValue -> bool) * + ?event: (CacheEvent -> string * 'TKey * 'TVersion -> unit) -> + LruCache<'TKey, 'TVersion, 'TValue> + + member Clear: unit -> unit + + /// Clear any keys that match the given predicate + member Clear: predicate: ('TKey -> bool) -> unit + + /// Returns an option of a value for given key and version, and also a list of all other versions for given key + member GetAll: key: 'TKey * version: 'TVersion -> 'TValue option * ('TVersion * 'TValue) list + + member GetValues: unit -> (string * 'TVersion * 'TValue) seq + + member Remove: key: 'TKey -> unit + + member Remove: key: 'TKey * version: 'TVersion -> unit + + member Set: key: 'TKey * value: 'TValue -> unit + + member Set: key: 'TKey * version: 'TVersion * value: 'TValue -> unit + + member Set: key: 'TKey * version: 'TVersion * label: string * value: 'TValue -> unit + + member TryGet: key: 'TKey -> 'TValue option + + member TryGet: key: 'TKey * version: 'TVersion -> 'TValue option + + member DebuggerDisplay: string diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncLock.fs b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncLock.fs new file mode 100644 index 00000000000..ef4b69a3910 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncLock.fs @@ -0,0 +1,26 @@ +module CompilerService.AsyncLock + +open Internal.Utilities.Collections + +open Xunit +open System.Threading.Tasks + + +[] +let ``Async lock works`` () = + task { + use lock = new AsyncLock() + + let mutable x = 0 + + let job () = task { + let y = x + do! Task.Delay(10) + x <- y + 1 + } + + let jobs = [ for _ in 1..100 -> lock.Do job ] + let! _ = Task.WhenAll(jobs) + + Assert.Equal(100, x) + } \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs new file mode 100644 index 00000000000..817a3b8c70a --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs @@ -0,0 +1,577 @@ +module CompilerService.AsyncMemoize + +open System +open System.Threading +open Xunit +open Internal.Utilities.Collections +open System.Threading.Tasks +open System.Diagnostics +open System.Collections.Concurrent +open FSharp.Compiler.DiagnosticsLogger +open FSharp.Compiler.Diagnostics +open FSharp.Compiler.BuildGraph + +[] +let ``Stack trace`` () = + + let memoize = AsyncMemoize() + + let computation key = node { + // do! Async.Sleep 1 |> NodeCode.AwaitAsync + + let! result = memoize.Get'(key * 2, node { + //do! Async.Sleep 1 |> NodeCode.AwaitAsync + return key * 5 + }) + + return result * 2 + } + + //let _r2 = computation 10 + + let result = memoize.Get'(1, computation 1) |> NodeCode.RunImmediateWithoutCancellation + + Assert.Equal(10, result) + + +[] +let ``Basics``() = + + let computation key = node { + do! Async.Sleep 1 |> NodeCode.AwaitAsync + return key * 2 + } + + let eventLog = ConcurrentBag() + + let memoize = AsyncMemoize() + memoize.OnEvent(fun (e, (_label, k, _version)) -> eventLog.Add (e, k)) + + let result = + seq { + memoize.Get'(5, computation 5) + memoize.Get'(5, computation 5) + memoize.Get'(2, computation 2) + memoize.Get'(5, computation 5) + memoize.Get'(3, computation 3) + memoize.Get'(2, computation 2) + } + |> NodeCode.Parallel + |> NodeCode.RunImmediateWithoutCancellation + + let expected = [| 10; 10; 4; 10; 6; 4|] + + Assert.Equal(expected, result) + + let groups = eventLog |> Seq.groupBy snd |> Seq.toList + Assert.Equal(3, groups.Length) + for key, events in groups do + Assert.Equal>(Set [ Started, key; Finished, key ], Set events) + +[] +let ``We can cancel a job`` () = + task { + + let jobStarted = new ManualResetEvent(false) + + let computation key = node { + jobStarted.Set() |> ignore + do! Async.Sleep 1000 |> NodeCode.AwaitAsync + failwith "Should be canceled before it gets here" + return key * 2 + } + + let eventLog = ResizeArray() + let memoize = AsyncMemoize() + memoize.OnEvent(fun (e, (_label, k, _version)) -> eventLog.Add (e, k)) + + use cts1 = new CancellationTokenSource() + use cts2 = new CancellationTokenSource() + use cts3 = new CancellationTokenSource() + + let key = 1 + + let _task1 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts1.Token) + + jobStarted.WaitOne() |> ignore + jobStarted.Reset() |> ignore + + let _task2 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts2.Token) + let _task3 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts3.Token) + + Assert.Equal<(JobEvent * int) array>([| Started, key |], eventLog |> Seq.toArray ) + + do! Task.Delay 100 + + cts1.Cancel() + cts2.Cancel() + + do! Task.Delay 100 + + cts3.Cancel() + + do! Task.Delay 100 + + Assert.Equal<(JobEvent * int) array>([| Started, key; Started, key; Canceled, key |], eventLog |> Seq.toArray ) + } + +[] +let ``Job is restarted if first requestor cancels`` () = + task { + let jobStarted = new ManualResetEvent(false) + + let computation key = node { + jobStarted.Set() |> ignore + + for _ in 1 .. 5 do + do! Async.Sleep 100 |> NodeCode.AwaitAsync + + return key * 2 + } + + let eventLog = ConcurrentBag() + let memoize = AsyncMemoize() + memoize.OnEvent(fun (e, (_, k, _version)) -> eventLog.Add (DateTime.Now.Ticks, (e, k))) + + use cts1 = new CancellationTokenSource() + use cts2 = new CancellationTokenSource() + use cts3 = new CancellationTokenSource() + + let key = 1 + + let _task1 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts1.Token) + jobStarted.WaitOne() |> ignore + + let _task2 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts2.Token) + let _task3 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts3.Token) + + do! Task.Delay 100 + + cts1.Cancel() + + do! Task.Delay 100 + cts3.Cancel() + + let! result = _task2 + Assert.Equal(2, result) + + Assert.Equal(TaskStatus.Canceled, _task1.Status) + + let orderedLog = eventLog |> Seq.sortBy fst |> Seq.map snd |> Seq.toList + let expected = [ Started, key; Started, key; Finished, key ] + + Assert.Equal<_ list>(expected, orderedLog) + } + +// [] - if we decide to enable that +let ``Job keeps running if the first requestor cancels`` () = + task { + let jobStarted = new ManualResetEvent(false) + + let computation key = node { + jobStarted.Set() |> ignore + + for _ in 1 .. 5 do + do! Async.Sleep 100 |> NodeCode.AwaitAsync + + return key * 2 + } + + let eventLog = ConcurrentBag() + let memoize = AsyncMemoize() + memoize.OnEvent(fun (e, (_label, k, _version)) -> eventLog.Add (DateTime.Now.Ticks, (e, k))) + + use cts1 = new CancellationTokenSource() + use cts2 = new CancellationTokenSource() + use cts3 = new CancellationTokenSource() + + let key = 1 + + let _task1 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts1.Token) + jobStarted.WaitOne() |> ignore + + let _task2 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts2.Token) + let _task3 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts3.Token) + + jobStarted.WaitOne() |> ignore + + cts1.Cancel() + + do! Task.Delay 100 + cts3.Cancel() + + let! result = _task2 + Assert.Equal(2, result) + + Assert.Equal(TaskStatus.Canceled, _task1.Status) + + let orderedLog = eventLog |> Seq.sortBy fst |> Seq.map snd |> Seq.toList + let expected = [ Started, key; Finished, key ] + + Assert.Equal<_ list>(expected, orderedLog) + } + +[] +let ``Job is restarted if first requestor cancels but keeps running if second requestor cancels`` () = + task { + let jobStarted = new ManualResetEvent(false) + + let computation key = node { + jobStarted.Set() |> ignore + + for _ in 1 .. 5 do + do! Async.Sleep 100 |> NodeCode.AwaitAsync + + return key * 2 + } + + let eventLog = ConcurrentBag() + let memoize = AsyncMemoize() + memoize.OnEvent(fun (e, (_label, k, _version)) -> eventLog.Add (DateTime.Now.Ticks, (e, k))) + + use cts1 = new CancellationTokenSource() + use cts2 = new CancellationTokenSource() + use cts3 = new CancellationTokenSource() + + let key = 1 + + let _task1 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts1.Token) + + jobStarted.WaitOne() |> ignore + jobStarted.Reset() |> ignore + let _task2 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts2.Token) + let _task3 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts3.Token) + + cts1.Cancel() + + jobStarted.WaitOne() |> ignore + + cts2.Cancel() + + let! result = _task3 + Assert.Equal(2, result) + + Assert.Equal(TaskStatus.Canceled, _task1.Status) + + let orderedLog = eventLog |> Seq.sortBy fst |> Seq.map snd |> Seq.toList + let expected = [ Started, key; Started, key; Finished, key ] + + Assert.Equal<_ list>(expected, orderedLog) + } + + +type ExpectedException() = + inherit Exception() + +[] +let ``Stress test`` () = + + let seed = System.Random().Next() + + let rng = System.Random seed + let threads = 30 + let iterations = 30 + let maxDuration = 100 + let minTimeout = 0 + let maxTimeout = 500 + let exceptionProbability = 0.01 + let gcProbability = 0.1 + let stepMs = 10 + let keyCount = rng.Next(5, 200) + let keys = [| 1 .. keyCount |] + + let testTimeoutMs = threads * iterations * maxDuration + + let intenseComputation durationMs result = + async { + if rng.NextDouble() < exceptionProbability then + raise (ExpectedException()) + let s = Stopwatch.StartNew() + let mutable number = 0 + while (int s.ElapsedMilliseconds) < durationMs do + number <- number + 1 % 12345 + return [result] + } |> NodeCode.AwaitAsync + + let rec sleepyComputation durationMs result = + node { + if rng.NextDouble() < (exceptionProbability / (float durationMs / float stepMs)) then + raise (ExpectedException()) + if durationMs > 0 then + do! Async.Sleep (min stepMs durationMs) |> NodeCode.AwaitAsync + return! sleepyComputation (durationMs - stepMs) result + else + return [result] + } + + let rec mixedComputation durationMs result = + node { + if durationMs > 0 then + if rng.NextDouble() < 0.5 then + let! _ = intenseComputation (min stepMs durationMs) () + () + else + let! _ = sleepyComputation (min stepMs durationMs) () + () + return! mixedComputation (durationMs - stepMs) result + else + return [result] + } + + let computations = [| + intenseComputation + sleepyComputation + mixedComputation + |] + + let cache = AsyncMemoize(keepStrongly=5, keepWeakly=10) + + let mutable started = 0 + let mutable canceled = 0 + let mutable timeout = 0 + let mutable failed = 0 + let mutable completed = 0 + + let test = + seq { + for _ in 1..threads do + let rec loop iteration = + task { + if gcProbability > rng.NextDouble() then + GC.Collect(2, GCCollectionMode.Forced, false) + + let computation = computations[rng.Next computations.Length] + let durationMs = rng.Next maxDuration + let timeoutMs = rng.Next(minTimeout, maxTimeout) + let key = keys[rng.Next keys.Length] + let result = key * 2 + let job = cache.Get'(key, computation durationMs result) + let cts = new CancellationTokenSource() + let runningJob = NodeCode.StartAsTask_ForTesting(job, ct = cts.Token) + cts.CancelAfter timeoutMs + Interlocked.Increment &started |> ignore + try + let! actual = runningJob + Assert.Equal(result, actual.Head) + Interlocked.Increment &completed |> ignore + with + | :? TaskCanceledException as _e -> + Interlocked.Increment &canceled |> ignore + | :? OperationCanceledException as _e -> + Interlocked.Increment &canceled |> ignore + | :? TimeoutException -> Interlocked.Increment &timeout |> ignore + | :? ExpectedException -> Interlocked.Increment &failed |> ignore + | :? AggregateException as ex when + ex.Flatten().InnerExceptions |> Seq.exists (fun e -> e :? ExpectedException) -> + Interlocked.Increment &failed |> ignore + | e -> + failwith $"Seed {seed} failed on iteration {iteration}: %A{e}" + if iteration < iterations then + return! loop (iteration + 1) + return () + } + loop 1 + } + |> Task.WhenAll + + if not (test.Wait testTimeoutMs) then failwith "Test timed out - most likely deadlocked" + + Assert.Equal (threads * iterations, started) + // Assert.Equal((0,0,0,0,0),(started, completed, canceled, failed, timeout)) + Assert.Equal (started, completed + canceled + failed + timeout) + + Assert.True ((float completed) > ((float started) * 0.1), "Less than 10 % completed jobs") + + +[] +[] +[] +let ``Cancel running jobs with the same key`` cancelDuplicate expectFinished = + task { + let cache = AsyncMemoize(cancelDuplicateRunningJobs=cancelDuplicate) + + let mutable started = 0 + let mutable finished = 0 + + let work () = node { + Interlocked.Increment &started |> ignore + for _ in 1..10 do + do! Async.Sleep 10 |> NodeCode.AwaitAsync + Interlocked.Increment &finished |> ignore + } + + let key1 = + { new ICacheKey<_, _> with + member _.GetKey() = 1 + member _.GetVersion() = 1 + member _.GetLabel() = "key1" } + + cache.Get(key1, work()) |> Async.AwaitNodeCode |> Async.Start + + do! Task.Delay 50 + + let key2 = + { new ICacheKey<_, _> with + member _.GetKey() = key1.GetKey() + member _.GetVersion() = key1.GetVersion() + 1 + member _.GetLabel() = "key2" } + + cache.Get(key2, work()) |> Async.AwaitNodeCode |> Async.Start + + do! Task.Delay 500 + + Assert.Equal((2, expectFinished), (started, finished)) + } + + +type DummyException(msg) = + inherit Exception(msg) + +[] +let ``Preserve thread static diagnostics`` () = + + let seed = System.Random().Next() + + let rng = System.Random seed + + let job1Cache = AsyncMemoize() + let job2Cache = AsyncMemoize() + + let job1 (input: string) = node { + let! _ = Async.Sleep (rng.Next(1, 30)) |> NodeCode.AwaitAsync + let ex = DummyException("job1 error") + DiagnosticsThreadStatics.DiagnosticsLogger.ErrorR(ex) + return Ok input + } + + let job2 (input: int) = node { + + DiagnosticsThreadStatics.DiagnosticsLogger.Warning(DummyException("job2 error 1")) + + let! _ = Async.Sleep (rng.Next(1, 30)) |> NodeCode.AwaitAsync + + let key = { new ICacheKey<_, _> with + member _.GetKey() = "job1" + member _.GetVersion() = input + member _.GetLabel() = "job1" } + + let! result = job1Cache.Get(key, job1 "${input}" ) + + DiagnosticsThreadStatics.DiagnosticsLogger.Warning(DummyException("job2 error 2")) + + return input, result + + } + + let tasks = seq { + for i in 1 .. 100 do + + task { + let diagnosticsLogger = + CompilationDiagnosticLogger($"Testing task {i}", FSharpDiagnosticOptions.Default) + + use _ = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Optimize) + + DiagnosticsThreadStatics.DiagnosticsLogger.Warning(DummyException("task error")) + + + let key = { new ICacheKey<_, _> with + member _.GetKey() = "job2" + member _.GetVersion() = rng.Next(1, 10) + member _.GetLabel() = "job2" } + + let! result = job2Cache.Get(key, job2 (i % 10)) |> Async.AwaitNodeCode + + let diagnostics = diagnosticsLogger.GetDiagnostics() + + //Assert.Equal(3, diagnostics.Length) + + return result, diagnostics + } + } + + let results = (Task.WhenAll tasks).Result + + let _diagnosticCounts = results |> Seq.map snd |> Seq.map Array.length |> Seq.groupBy id |> Seq.map (fun (k, v) -> k, v |> Seq.length) |> Seq.sortBy fst |> Seq.toList + + //Assert.Equal<(int * int) list>([4, 100], diagnosticCounts) + + let diagnosticMessages = results |> Seq.map snd |> Seq.map (Array.map (fun (d, _) -> d.Exception.Message) >> Array.toList) |> Set + + Assert.Equal>(Set [["task error"; "job2 error 1"; "job1 error"; "job2 error 2"; ]], diagnosticMessages) + + +[] +let ``Preserve thread static diagnostics already completed job`` () = + + let cache = AsyncMemoize() + + let key = { new ICacheKey<_, _> with + member _.GetKey() = "job1" + member _.GetVersion() = 1 + member _.GetLabel() = "job1" } + + let job (input: string) = node { + let ex = DummyException($"job {input} error") + DiagnosticsThreadStatics.DiagnosticsLogger.ErrorR(ex) + return Ok input + } + + async { + + let diagnosticsLogger = CompilationDiagnosticLogger($"Testing", FSharpDiagnosticOptions.Default) + + use _ = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Optimize) + + let! _ = cache.Get(key, job "1" ) |> Async.AwaitNodeCode + let! _ = cache.Get(key, job "2" ) |> Async.AwaitNodeCode + + let diagnosticMessages = diagnosticsLogger.GetDiagnostics() |> Array.map (fun (d, _) -> d.Exception.Message) |> Array.toList + + Assert.Equal>(["job 1 error"; "job 1 error"], diagnosticMessages) + + } + |> Async.StartAsTask + + +[] +let ``We get diagnostics from the job that failed`` () = + + let cache = AsyncMemoize() + + let key = { new ICacheKey<_, _> with + member _.GetKey() = "job1" + member _.GetVersion() = 1 + member _.GetLabel() = "job1" } + + let job (input: int) = node { + let ex = DummyException($"job {input} error") + do! Async.Sleep 100 |> NodeCode.AwaitAsync + DiagnosticsThreadStatics.DiagnosticsLogger.Error(ex) + return 5 + } + + let result = + [1; 2] + |> Seq.map (fun i -> + async { + let diagnosticsLogger = CompilationDiagnosticLogger($"Testing", FSharpDiagnosticOptions.Default) + + use _ = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Optimize) + try + let! _ = cache.Get(key, job i ) |> Async.AwaitNodeCode + () + with _ -> + () + let diagnosticMessages = diagnosticsLogger.GetDiagnostics() |> Array.map (fun (d, _) -> d.Exception.Message) |> Array.toList + + return diagnosticMessages + }) + |> Async.Parallel + |> Async.StartAsTask + |> (fun t -> t.Result) + |> Array.toList + + Assert.True( + result = [["job 1 error"]; ["job 1 error"]] || + result = [["job 2 error"]; ["job 2 error"]] ) diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerService/LruCache.fs b/tests/FSharp.Compiler.ComponentTests/CompilerService/LruCache.fs new file mode 100644 index 00000000000..a477f7e6f7c --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/CompilerService/LruCache.fs @@ -0,0 +1,197 @@ +module CompilerService.LruCache +open Internal.Utilities.Collections + +open Xunit +open System + +[] +let ``Adding an item to the cache should make it retrievable``() = + let cache = new LruCache(keepStrongly = 2) + cache.Set(1, "one") + let result = cache.TryGet(1) + Assert.Equal("one", result.Value) + +[] +let ``Adding an item to the cache should evict the least recently used item if the cache is full``() = + let cache = new LruCache(keepStrongly = 2, keepWeakly = 0) + cache.Set(1, "one") + cache.Set(2, "two") + cache.Set(3, "three") + let result = cache.TryGet(1) + Assert.Null(result) + +[] +let ``Adding an item to the cache should not evict a required item``() = + let cache = new LruCache(keepStrongly = 2, requiredToKeep = (fun v -> v = "one")) + cache.Set(1, "one") + cache.Set(2, "two") + cache.Set(3, "three") + let result = cache.TryGet(1) + Assert.Equal("one", result.Value) + +[] +let ``Adding an item to the cache should not evict a strongly kept item``() = + let cache = new LruCache(keepStrongly = 2, keepWeakly = 0) + cache.Set(1, "one") + cache.Set(2, "two") + cache.Set(1, "one") + cache.Set(3, "three") + let result = cache.TryGet(1) + Assert.Equal("one", result.Value) + +[] +let ``Adding an item to the cache should not evict a strongly kept item, even if it is the least recently used``() = + let cache = new LruCache(keepStrongly = 2, keepWeakly = 0, requiredToKeep = (fun v -> v = "one")) + cache.Set(1, "one") + cache.Set(2, "two") + cache.Set(3, "three") + let result = cache.TryGet(1) + Assert.Equal("one", result.Value) + +[] +let ``Adding an item to the cache should not evict a weakly kept item if its reference is still valid``() = + let cache = new LruCache(keepStrongly = 2, keepWeakly = 1) + let value = "one" + cache.Set(1, value) + cache.Set(2, "two") + GC.Collect(2, GCCollectionMode.Forced, true) + let result = cache.TryGet(1) + Assert.Equal(value, result.Value) + + +// Doing this directly in the test prevents GC for some reason +let private addObjToCache (cache: LruCache<_, int,_>) key = + let o = obj () + cache.Set(key, o) + +[] +let ``Adding an item to the cache should evict a weakly kept item if its reference is no longer valid``() = + let cache = new LruCache<_, int, _>(keepStrongly = 2, keepWeakly = 1) + addObjToCache cache 1 + addObjToCache cache 2 + addObjToCache cache 3 + GC.Collect(2, GCCollectionMode.Forced, true) + + let result = cache.TryGet(1) + Assert.True(result.IsNone) + + +[] +let ``When a new version is added other versions get weakened`` () = + let eventLog = ResizeArray() + + let cache = new LruCache<_, int, _>(keepStrongly = 2, keepWeakly = 2, event = (fun e v -> eventLog.Add(e, v))) + + cache.Set(1, 1, "one1") + cache.Set(1, 2, "one2") + cache.Set(1, 3, "one3") + cache.Set(1, 4, "one4") + + let expected = [ + CacheEvent.Weakened, ("[no label]", 1, 1) + CacheEvent.Weakened, ("[no label]", 1, 2) + CacheEvent.Weakened, ("[no label]", 1, 3) + CacheEvent.Evicted, ("[no label]", 1, 1) + ] + + Assert.Equal>(expected, eventLog |> Seq.toList) + +[] +let ``When a new version is added other versions don't get weakened when they're required to keep`` () = + let eventLog = ResizeArray() + + let cache = new LruCache<_, int, _>(keepStrongly = 2, keepWeakly = 2, requiredToKeep = ((=) "one1"), event = (fun e v -> eventLog.Add(e, v))) + + cache.Set(1, 1, "one1") + cache.Set(1, 2, "one2") + cache.Set(1, 3, "one3") + cache.Set(1, 4, "one4") + + let expected = [ + CacheEvent.Weakened, ("[no label]", 1, 2) + CacheEvent.Weakened, ("[no label]", 1, 3) + ] + + Assert.Equal>(expected, eventLog |> Seq.toList) + +[] +let ``Looking up a weakened item will strengthen it`` () = + let eventLog = ResizeArray() + + let cache = new LruCache<_, int, _>(keepStrongly = 2, keepWeakly = 2, event = (fun e v -> eventLog.Add(e, v))) + + cache.Set(1, 1, "one1") + cache.Set(1, 2, "one2") + cache.Set(1, 3, "one3") + cache.Set(1, 4, "one4") + + let result = cache.TryGet(1, 2) + Assert.Equal("one2", result.Value) + + let expected = [ + CacheEvent.Weakened, ("[no label]", 1, 1) + CacheEvent.Weakened, ("[no label]", 1, 2) + CacheEvent.Weakened, ("[no label]", 1, 3) + CacheEvent.Evicted, ("[no label]", 1, 1) + CacheEvent.Strengthened, ("[no label]", 1, 2) + ] + + Assert.Equal>(expected, eventLog |> Seq.toList) + + +[] +let ``New version doesn't push other keys out of strong list``() = + + let eventLog = ResizeArray() + + let cache = new LruCache<_, int, _>(keepStrongly = 2, keepWeakly = 2, event = (fun e v -> eventLog.Add(e, v))) + + cache.Set(1, 1, "one1") + cache.Set(1, 2, "one2") + cache.Set(1, 3, "one3") + cache.Set(1, 4, "one4") + cache.Set(2, 1, "two1") + cache.Set(2, 2, "two2") + + let expected = [ + CacheEvent.Weakened, ("[no label]", 1, 1) + CacheEvent.Weakened, ("[no label]", 1, 2) + CacheEvent.Weakened, ("[no label]", 1, 3) + CacheEvent.Evicted, ("[no label]", 1, 1) + CacheEvent.Weakened, ("[no label]", 2, 1) + CacheEvent.Evicted, ("[no label]", 1, 2) + ] + + Assert.Equal>(expected, eventLog |> Seq.toList) + +[] +let ``We can clear specific keys based on a predicate``() = + + let eventLog = ResizeArray() + + let cache = new LruCache<_, int, _>(keepStrongly = 2, keepWeakly = 2, event = (fun e v -> eventLog.Add(e, v))) + + cache.Set(1, 1, "one1") + cache.Set(1, 2, "one2") + cache.Set(1, 3, "one3") + cache.Set(1, 4, "one4") + cache.Set(2, 1, "two1") + cache.Set(2, 2, "two2") + + cache.Clear((=) 1) + + let result = cache.TryGet(1, 2) + Assert.True(result.IsNone) + + let expected = [ + CacheEvent.Weakened, ("[no label]", 1, 1) + CacheEvent.Weakened, ("[no label]", 1, 2) + CacheEvent.Weakened, ("[no label]", 1, 3) + CacheEvent.Evicted, ("[no label]", 1, 1) + CacheEvent.Weakened, ("[no label]", 2, 1) + CacheEvent.Evicted, ("[no label]", 1, 2) + CacheEvent.Cleared, ("[no label]", 1, 3) + CacheEvent.Cleared, ("[no label]", 1, 4) + ] + + Assert.Equal>(expected, eventLog |> Seq.toList) diff --git a/tests/FSharp.Compiler.ComponentTests/ErrorMessages/ExtendedDiagnosticDataTests.fs b/tests/FSharp.Compiler.ComponentTests/ErrorMessages/ExtendedDiagnosticDataTests.fs index fea4dfddbb7..eec3ce03dcd 100644 --- a/tests/FSharp.Compiler.ComponentTests/ErrorMessages/ExtendedDiagnosticDataTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/ErrorMessages/ExtendedDiagnosticDataTests.fs @@ -143,8 +143,10 @@ if true then 1 else "a" Assert.Equal("int", typeMismatch.ExpectedType.Format(displayContext)) Assert.Equal("string", typeMismatch.ActualType.Format(displayContext))) -[] -let ``ArgumentsInSigAndImplMismatchExtendedData 01`` () = +[] +[] +[] +let ``ArgumentsInSigAndImplMismatchExtendedData 01`` useTransparentCompiler = let encodeFsi = Fsi """ module Test @@ -157,7 +159,7 @@ let f (y: int) = () """ encodeFsi |> withAdditionalSourceFile encodeFs - |> typecheckProject true + |> typecheckProject true useTransparentCompiler |> checkDiagnostic (3218, "The argument names in the signature 'x' and implementation 'y' do not match. The argument name from the signature file will be used. This may cause problems when debugging or profiling.") (fun (argsMismatch: ArgumentsInSigAndImplMismatchExtendedData) -> @@ -166,8 +168,10 @@ let f (y: int) = () Assert.True(argsMismatch.SignatureRange.FileName.EndsWith("fsi")) Assert.True(argsMismatch.ImplementationRange.FileName.EndsWith("fs"))) -[] -let ``FieldNotContainedDiagnosticExtendedData 01`` () = +[] +[] +[] +let ``FieldNotContainedDiagnosticExtendedData 01`` useTransparentCompiler = let encodeFsi = Fsi """ namespace rec Foo @@ -182,7 +186,7 @@ type A = """ encodeFsi |> withAdditionalSourceFile encodeFs - |> typecheckProject true + |> typecheckProject true useTransparentCompiler |> checkDiagnostic (193, "The module contains the field\n myStatic: int \nbut its signature specifies\n myStatic: int \nthe accessibility specified in the signature is more than that specified in the implementation") (fun (fieldsData: FieldNotContainedDiagnosticExtendedData) -> diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 904fbbf731c..a9dd5382893 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -19,6 +19,7 @@ $(DefineConstants);DEBUG true + true @@ -31,75 +32,52 @@ FsUnit.fs - - - - - + + + + + - - + + - + - + - + - - + + - - - - - - + + + + + + - + - + - - + + - + @@ -291,6 +269,9 @@ + + + @@ -319,16 +300,17 @@ + + - - - - %(RelativeDir)TestSource\%(Filename)%(Extension) + %(RelativeDir)TestSource\%(Filename)%(Extension) + + %(RelativeDir)\BaseLine\%(Filename)%(Extension) diff --git a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/CommonWorkflows.fs b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/CommonWorkflows.fs index ce9846a0ee5..f4c5c41770d 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/CommonWorkflows.fs +++ b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/CommonWorkflows.fs @@ -100,13 +100,14 @@ let ``Changes in a referenced project`` () = checkFile "Last" expectSignatureChanged } -[] -let ``Language service works if the same file is listed twice`` () = +[] +// TODO: This will probably require some special care in TransparentCompiler... +let ``Language service works if the same file is listed twice`` () = let file = sourceFile "First" [] - let project = SyntheticProject.Create(file) + let project = SyntheticProject.Create(file, file) project.Workflow { - checkFile "First" expectOk - addFileAbove "First" file + // checkFile "First" expectOk + // addFileAbove "First" file checkFile "First" (expectSingleWarningAndNoErrors "Please verify that it is included only once in the project file.") } diff --git a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/FindReferences.fs b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/FindReferences.fs index c8cff9112c2..b12edf8cb6d 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/FindReferences.fs +++ b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/FindReferences.fs @@ -5,6 +5,8 @@ open FSharp.Compiler.CodeAnalysis open FSharp.Test.ProjectGeneration open FSharp.Test.ProjectGeneration.Helpers +#nowarn "57" + type Occurence = Definition | InType | Use let deriveOccurence (su:FSharpSymbolUse) = @@ -344,7 +346,7 @@ and mytype = MyType let symbolUse = getSymbolUse fileName source "MyType" options checker |> Async.RunSynchronously - checker.FindBackgroundReferencesInFile(fileName, options, symbolUse.Symbol, fastCheck = true) + checker.FindBackgroundReferencesInFile(fileName, options, symbolUse.Symbol) |> Async.RunSynchronously |> expectToFind [ fileName, 2, 5, 11 @@ -430,7 +432,7 @@ match 2 with let symbolUse = getSymbolUse fileName source "Even" options checker |> Async.RunSynchronously - checker.FindBackgroundReferencesInFile(fileName, options, symbolUse.Symbol, fastCheck = true) + checker.FindBackgroundReferencesInFile(fileName, options, symbolUse.Symbol) |> Async.RunSynchronously |> expectToFind [ fileName, 2, 6, 10 @@ -463,7 +465,7 @@ module Two = let symbolUse = getSymbolUse fileName source "Even" options checker |> Async.RunSynchronously - checker.FindBackgroundReferencesInFile(fileName, options, symbolUse.Symbol, fastCheck = true) + checker.FindBackgroundReferencesInFile(fileName, options, symbolUse.Symbol) |> Async.RunSynchronously |> expectToFind [ fileName, 4, 10, 14 @@ -619,7 +621,7 @@ let y = MyType.Two let symbolUse = getSymbolUse fileName source "MyType" options checker |> Async.RunSynchronously - checker.FindBackgroundReferencesInFile(fileName, options, symbolUse.Symbol, fastCheck = true) + checker.FindBackgroundReferencesInFile(fileName, options, symbolUse.Symbol) |> Async.RunSynchronously |> expectToFind [ fileName, 4, 5, 11 @@ -648,7 +650,7 @@ let y = MyType.Three let symbolUse = getSymbolUse fileName source "MyType" options checker |> Async.RunSynchronously - checker.FindBackgroundReferencesInFile(fileName, options, symbolUse.Symbol, fastCheck = true) + checker.FindBackgroundReferencesInFile(fileName, options, symbolUse.Symbol) |> Async.RunSynchronously |> expectToFind [ fileName, 4, 7, 13 diff --git a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/ProjectSnapshot.fs b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/ProjectSnapshot.fs new file mode 100644 index 00000000000..90e28cc63ac --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/ProjectSnapshot.fs @@ -0,0 +1,104 @@ +module FSharpChecker.ProjectSnapshot + +open Xunit +open System +open FSharp.Compiler.CodeAnalysis.ProjectSnapshot + + +// TODO: restore tests + +//[] +//let WithoutImplFilesThatHaveSignatures () = + +// let snapshot = FSharpProjectSnapshot.Create( +// projectFileName = "Dummy.fsproj", +// projectId = None, +// sourceFiles = [ +// { FileName = "A.fsi"; Version = "1"; GetSource = Unchecked.defaultof<_> } +// { FileName = "A.fs"; Version = "1"; GetSource = Unchecked.defaultof<_> } +// { FileName = "B.fs"; Version = "1"; GetSource = Unchecked.defaultof<_> } +// { FileName = "C.fsi"; Version = "1"; GetSource = Unchecked.defaultof<_> } +// { FileName = "C.fs"; Version = "1"; GetSource = Unchecked.defaultof<_> } +// ], +// referencesOnDisk = [], +// otherOptions = [], +// referencedProjects = [], +// isIncompleteTypeCheckEnvironment = true, +// useScriptResolutionRules = false, +// loadTime = DateTime(1234, 5, 6), +// unresolvedReferences = None, +// originalLoadReferences = [], +// stamp = None +// ) + +// let result = snapshot.WithoutImplFilesThatHaveSignatures + +// let expected = [| "A.fsi"; "B.fs"; "C.fsi" |] + +// Assert.Equal(expected, result.SourceFileNames |> List.toArray) + +// Assert.Equal(result.FullVersion, snapshot.SignatureVersion) + +//[] +//let WithoutImplFilesThatHaveSignaturesExceptLastOne () = + +// let snapshot = FSharpProjectSnapshot.Create( +// projectFileName = "Dummy.fsproj", +// projectId = None, +// sourceFiles = [ +// { FileName = "A.fsi"; Version = "1"; GetSource = Unchecked.defaultof<_> } +// { FileName = "A.fs"; Version = "1"; GetSource = Unchecked.defaultof<_> } +// { FileName = "B.fs"; Version = "1"; GetSource = Unchecked.defaultof<_> } +// { FileName = "C.fsi"; Version = "1"; GetSource = Unchecked.defaultof<_> } +// { FileName = "C.fs"; Version = "1"; GetSource = Unchecked.defaultof<_> } +// ], +// referencesOnDisk = [], +// otherOptions = [], +// referencedProjects = [], +// isIncompleteTypeCheckEnvironment = true, +// useScriptResolutionRules = false, +// loadTime = DateTime(1234, 5, 6), +// unresolvedReferences = None, +// originalLoadReferences = [], +// stamp = None +// ) + +// let result = snapshot.WithoutImplFilesThatHaveSignaturesExceptLastOne + +// let expected = [| "A.fsi"; "B.fs"; "C.fsi"; "C.fs" |] + +// Assert.Equal(expected, result.SourceFileNames |> List.toArray) + +// Assert.Equal(result.FullVersion, snapshot.LastFileVersion) + + +//[] +//let WithoutImplFilesThatHaveSignaturesExceptLastOne_2 () = + +// let snapshot = FSharpProjectSnapshot.Create( +// projectFileName = "Dummy.fsproj", +// projectId = None, +// sourceFiles = [ +// { FileName = "A.fs"; Version = "1"; GetSource = Unchecked.defaultof<_> } +// { FileName = "B.fs"; Version = "1"; GetSource = Unchecked.defaultof<_> } +// { FileName = "C.fs"; Version = "1"; GetSource = Unchecked.defaultof<_> } +// ], +// referencesOnDisk = [], +// otherOptions = [], +// referencedProjects = [], +// isIncompleteTypeCheckEnvironment = true, +// useScriptResolutionRules = false, +// loadTime = DateTime(1234, 5, 6), +// unresolvedReferences = None, +// originalLoadReferences = [], +// stamp = None +// ) + +// let result = snapshot.WithoutImplFilesThatHaveSignaturesExceptLastOne + +// let expected = [| "A.fs"; "B.fs"; "C.fs" |] + +// Assert.Equal(expected, result.SourceFileNames |> List.toArray) + +// Assert.Equal(result.FullVersion, snapshot.LastFileVersion) + diff --git a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs new file mode 100644 index 00000000000..a6b7f6fcc41 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs @@ -0,0 +1,803 @@ +module FSharpChecker.TransparentCompiler + +open System.Collections.Concurrent +open System.Diagnostics +open FSharp.Compiler.CodeAnalysis +open Internal.Utilities.Collections +open FSharp.Compiler.CodeAnalysis.TransparentCompiler +open Internal.Utilities.Library.Extras +open FSharp.Compiler.GraphChecking.GraphProcessing +open FSharp.Compiler.Diagnostics + +open Xunit + +open FSharp.Test.ProjectGeneration +open FSharp.Test.ProjectGeneration.Helpers +open System.IO +open Microsoft.CodeAnalysis +open System +open System.Threading.Tasks +open System.Threading +open TypeChecks + +open OpenTelemetry +open OpenTelemetry.Resources +open OpenTelemetry.Trace + + +#nowarn "57" + +[] +let ``Use Transparent Compiler`` () = + + let size = 20 + + let project = + { SyntheticProject.Create() with + SourceFiles = [ + sourceFile $"File%03d{0}" [] + for i in 1..size do + sourceFile $"File%03d{i}" [$"File%03d{i-1}"] + ] + } + + let first = "File001" + let middle = $"File%03d{size / 2}" + let last = $"File%03d{size}" + + ProjectWorkflowBuilder(project, useTransparentCompiler = true) { + updateFile first updatePublicSurface + checkFile first expectSignatureChanged + checkFile last expectSignatureChanged + updateFile middle updatePublicSurface + checkFile last expectSignatureChanged + addFileAbove middle (sourceFile "addedFile" [first]) + updateFile middle (addDependency "addedFile") + checkFile middle expectSignatureChanged + checkFile last expectSignatureChanged + } + +[] +let ``Parallel processing`` () = + + let project = SyntheticProject.Create( + sourceFile "A" [], + sourceFile "B" ["A"], + sourceFile "C" ["A"], + sourceFile "D" ["A"], + sourceFile "E" ["B"; "C"; "D"]) + + ProjectWorkflowBuilder(project, useTransparentCompiler = true) { + checkFile "E" expectOk + updateFile "A" updatePublicSurface + saveFile "A" + + checkFile "E" expectSignatureChanged + } + +[] +let ``Parallel processing with signatures`` () = + + let project = SyntheticProject.Create( + sourceFile "A" [] |> addSignatureFile, + sourceFile "B" ["A"] |> addSignatureFile, + sourceFile "C" ["A"] |> addSignatureFile, + sourceFile "D" ["A"] |> addSignatureFile, + sourceFile "E" ["B"; "C"; "D"] |> addSignatureFile) + + //let cacheEvents = ConcurrentBag<_>() + + ProjectWorkflowBuilder(project, useTransparentCompiler = true) { + //withChecker (fun checker -> checker.CacheEvent.Add cacheEvents.Add) + checkFile "E" expectOk + updateFile "A" updatePublicSurface + checkFile "E" expectNoChanges + regenerateSignature "A" + regenerateSignature "B" + regenerateSignature "C" + regenerateSignature "D" + regenerateSignature "E" + checkFile "E" expectSignatureChanged + } + +let makeTestProject () = + SyntheticProject.Create( + sourceFile "First" [], + sourceFile "Second" ["First"], + sourceFile "Third" ["First"], + { sourceFile "Last" ["Second"; "Third"] with EntryPoint = true }) + +let testWorkflow () = + ProjectWorkflowBuilder(makeTestProject(), useTransparentCompiler = true) + +[] +let ``Edit file, check it, then check dependent file`` () = + testWorkflow() { + updateFile "First" breakDependentFiles + checkFile "First" expectSignatureChanged + checkFile "Second" expectErrors + } + +[] +let ``Edit file, don't check it, check dependent file`` () = + testWorkflow() { + updateFile "First" breakDependentFiles + checkFile "Second" expectErrors + } + +[] +let ``Check transitive dependency`` () = + testWorkflow() { + updateFile "First" breakDependentFiles + checkFile "Last" expectSignatureChanged + } + +[] +let ``Change multiple files at once`` () = + testWorkflow() { + updateFile "First" (setPublicVersion 2) + updateFile "Second" (setPublicVersion 2) + updateFile "Third" (setPublicVersion 2) + checkFile "Last" (expectSignatureContains "val f: x: 'a -> (ModuleFirst.TFirstV_2<'a> * ModuleSecond.TSecondV_2<'a>) * (ModuleFirst.TFirstV_2<'a> * ModuleThird.TThirdV_2<'a>) * TLastV_1<'a>") + } + +[] +let ``Files depend on signature file if present`` () = + let project = makeTestProject() |> updateFile "First" addSignatureFile + + ProjectWorkflowBuilder(project, useTransparentCompiler = true) { + updateFile "First" breakDependentFiles + saveFile "First" + checkFile "Second" expectNoChanges + } + +[] +let ``Project with signatures`` () = + + let project = SyntheticProject.Create( + { sourceFile "First" [] with + Source = "let f (x: int) = x" + SignatureFile = AutoGenerated }, + { sourceFile "Second" ["First"] with + Source = "let a x = ModuleFirst.f x" + SignatureFile = AutoGenerated }) + + ProjectWorkflowBuilder(project, useTransparentCompiler = true) { + checkFile "Second" expectOk + } + +[] +let ``Signature update`` () = + + let project = SyntheticProject.Create( + { sourceFile "First" [] with + Source = "let f (x: int) = x" + SignatureFile = Custom "val f: x: int -> int" }, + { sourceFile "Second" ["First"] with + Source = "let a x = ModuleFirst.f x" }) + + ProjectWorkflowBuilder(project, useTransparentCompiler = true) { + checkFile "Second" expectOk + updateFile "First" (fun f -> { f with SignatureFile = Custom "val f: x: string -> string" }) + checkFile "Second" expectSignatureChanged + } + +[] +let ``Adding a file`` () = + testWorkflow() { + addFileAbove "Second" (sourceFile "New" []) + updateFile "Second" (addDependency "New") + checkFile "Last" (expectSignatureContains "val f: x: 'a -> (ModuleFirst.TFirstV_1<'a> * ModuleNew.TNewV_1<'a> * ModuleSecond.TSecondV_1<'a>) * (ModuleFirst.TFirstV_1<'a> * ModuleThird.TThirdV_1<'a>) * TLastV_1<'a>") + } + +[] +let ``Removing a file`` () = + testWorkflow() { + removeFile "Second" + checkFile "Last" expectErrors + } + +[] +let ``Changes in a referenced project`` () = + let library = SyntheticProject.Create("library", sourceFile "Library" []) + + let project = + { makeTestProject() with DependsOn = [library] } + |> updateFile "First" (addDependency "Library") + + ProjectWorkflowBuilder(project, useTransparentCompiler = true) { + + updateFile "First" updatePublicSurface + checkFile "Last" expectOk + + updateFile "Library" updatePublicSurface + saveFile "Library" + checkFile "Last" expectSignatureChanged + + } + +[] +let ``File is not checked twice`` () = + + let cacheEvents = ResizeArray() + + testWorkflow() { + withChecker (fun checker -> + async { + do! Async.Sleep 50 // wait for events from initial project check + checker.Caches.TcIntermediate.OnEvent cacheEvents.Add + }) + updateFile "First" updatePublicSurface + checkFile "Third" expectOk + } |> ignore + + let intermediateTypeChecks = + cacheEvents + |> Seq.groupBy (fun (_e, (_l, (f, _p), _)) -> f |> Path.GetFileName) + |> Seq.map (fun (k, g) -> k, g |> Seq.map fst |> Seq.toList) + |> Map + + Assert.Equal([Weakened; Started; Finished], intermediateTypeChecks["FileFirst.fs"]) + Assert.Equal([Weakened; Started; Finished], intermediateTypeChecks["FileThird.fs"]) + +[] +let ``If a file is checked as a dependency it's not re-checked later`` () = + let cacheEvents = ResizeArray() + + testWorkflow() { + withChecker (fun checker -> + async { + do! Async.Sleep 50 // wait for events from initial project check + checker.Caches.TcIntermediate.OnEvent cacheEvents.Add + }) + updateFile "First" updatePublicSurface + checkFile "Last" expectOk + checkFile "Third" expectOk + } |> ignore + + let intermediateTypeChecks = + cacheEvents + |> Seq.groupBy (fun (_e, (_l, (f, _p), _)) -> f |> Path.GetFileName) + |> Seq.map (fun (k, g) -> k, g |> Seq.map fst |> Seq.toList) + |> Map + + Assert.Equal([Weakened; Started; Finished], intermediateTypeChecks["FileThird.fs"]) + + +// [] TODO: differentiate complete and minimal checking requests +let ``We don't check files that are not depended on`` () = + let project = SyntheticProject.Create( + sourceFile "First" [], + sourceFile "Second" ["First"], + sourceFile "Third" ["First"], + sourceFile "Last" ["Third"]) + + let cacheEvents = ResizeArray() + + ProjectWorkflowBuilder(project, useTransparentCompiler = true) { + withChecker (fun checker -> + async { + do! Async.Sleep 50 // wait for events from initial project check + checker.Caches.TcIntermediate.OnEvent cacheEvents.Add + }) + updateFile "First" updatePublicSurface + checkFile "Last" expectOk + } |> ignore + + let intermediateTypeChecks = + cacheEvents + |> Seq.groupBy (fun (_e, (_l, (f, _p), _)) -> Path.GetFileName f) + |> Seq.map (fun (k, g) -> k, g |> Seq.map fst |> Seq.toList) + |> Map + + Assert.Equal([Started; Finished], intermediateTypeChecks["FileFirst.fs"]) + Assert.Equal([Started; Finished], intermediateTypeChecks["FileThird.fs"]) + Assert.False (intermediateTypeChecks.ContainsKey "FileSecond.fs") + +// [] TODO: differentiate complete and minimal checking requests +let ``Files that are not depended on don't invalidate cache`` () = + let project = SyntheticProject.Create( + sourceFile "First" [], + sourceFile "Second" ["First"], + sourceFile "Third" ["First"], + sourceFile "Last" ["Third"]) + + let cacheTcIntermediateEvents = ResizeArray() + let cacheGraphConstructionEvents = ResizeArray() + + ProjectWorkflowBuilder(project, useTransparentCompiler = true) { + updateFile "First" updatePublicSurface + checkFile "Last" expectOk + withChecker (fun checker -> + async { + do! Async.Sleep 50 // wait for events from initial project check + checker.Caches.TcIntermediate.OnEvent cacheTcIntermediateEvents.Add + checker.Caches.DependencyGraph.OnEvent cacheGraphConstructionEvents.Add + + }) + updateFile "Second" updatePublicSurface + checkFile "Last" expectOk + } |> ignore + + let intermediateTypeChecks = + cacheTcIntermediateEvents + |> Seq.groupBy (fun (_e, (l, _k, _)) -> l) + |> Seq.map (fun (k, g) -> k, g |> Seq.map fst |> Seq.toList) + |> Map + + let graphConstructions = + cacheGraphConstructionEvents + |> Seq.groupBy (fun (_e, (l, _k, _)) -> l) + |> Seq.map (fun (k, g) -> k, g |> Seq.map fst |> Seq.toList) + |> Map + + Assert.Equal([Started; Finished], graphConstructions["FileLast.fs"]) + + Assert.Equal([], intermediateTypeChecks |> Map.toList) + +// [] TODO: differentiate complete and minimal checking requests +let ``Files that are not depended on don't invalidate cache part 2`` () = + let project = SyntheticProject.Create( + sourceFile "A" [], + sourceFile "B" ["A"], + sourceFile "C" ["A"], + sourceFile "D" ["B"; "C"], + sourceFile "E" ["C"]) + + let cacheTcIntermediateEvents = ResizeArray() + let cacheGraphConstructionEvents = ResizeArray() + + ProjectWorkflowBuilder(project, useTransparentCompiler = true) { + updateFile "A" updatePublicSurface + checkFile "D" expectOk + withChecker (fun checker -> + async { + do! Async.Sleep 50 // wait for events from initial project check + checker.Caches.TcIntermediate.OnEvent cacheTcIntermediateEvents.Add + checker.Caches.DependencyGraph.OnEvent cacheGraphConstructionEvents.Add + }) + updateFile "B" updatePublicSurface + checkFile "E" expectOk + } |> ignore + + let intermediateTypeChecks = + cacheTcIntermediateEvents + |> Seq.groupBy (fun (_e, (l, _k, _)) -> l) + |> Seq.map (fun (k, g) -> k, g |> Seq.map fst |> Seq.toList) + |> Seq.toList + + let graphConstructions = + cacheGraphConstructionEvents + |> Seq.groupBy (fun (_e, (l, _k, _)) -> l) + |> Seq.map (fun (k, g) -> k, g |> Seq.map fst |> Seq.toList) + |> Seq.toList + + Assert.Equal(["FileE.fs", [Started; Finished]], graphConstructions) + Assert.Equal(["FileE.fs", [Started; Finished]], intermediateTypeChecks) + +[] +let ``Changing impl files doesn't invalidate cache when they have signatures`` () = + let project = SyntheticProject.Create( + { sourceFile "A" [] with SignatureFile = AutoGenerated }, + { sourceFile "B" ["A"] with SignatureFile = AutoGenerated }, + { sourceFile "C" ["B"] with SignatureFile = AutoGenerated }) + + let cacheEvents = ResizeArray() + + ProjectWorkflowBuilder(project, useTransparentCompiler = true) { + updateFile "A" updatePublicSurface + checkFile "C" expectOk + withChecker (fun checker -> + async { + do! Async.Sleep 50 // wait for events from initial project check + checker.Caches.TcIntermediate.OnEvent cacheEvents.Add + }) + updateFile "A" updateInternal + checkFile "C" expectOk + } |> ignore + + let intermediateTypeChecks = + cacheEvents + |> Seq.groupBy (fun (_e, (l, _k, _)) -> l) + |> Seq.map (fun (k, g) -> k, g |> Seq.map fst |> Seq.toList) + |> Seq.toList + + Assert.Equal([], intermediateTypeChecks) + +[] +let ``Changing impl file doesn't invalidate an in-memory referenced project`` () = + let library = SyntheticProject.Create("library", { sourceFile "A" [] with SignatureFile = AutoGenerated }) + + let project = { + SyntheticProject.Create("project", sourceFile "B" ["A"] ) + with DependsOn = [library] } + + let cacheEvents = ResizeArray() + + ProjectWorkflowBuilder(project, useTransparentCompiler = true) { + checkFile "B" expectOk + withChecker (fun checker -> + async { + do! Async.Sleep 50 // wait for events from initial project check + checker.Caches.TcIntermediate.OnEvent cacheEvents.Add + }) + updateFile "A" updateInternal + checkFile "B" expectOk + } |> ignore + + let intermediateTypeChecks = + cacheEvents + |> Seq.groupBy (fun (_e, (l, _k, _)) -> l) + |> Seq.map (fun (k, g) -> k, g |> Seq.map fst |> Seq.toList) + |> Seq.toList + + Assert.Equal([], intermediateTypeChecks) + + +[] +[] +[] +let ``Multi-project`` signatureFiles = + + let sigFile = if signatureFiles then AutoGenerated else No + + let library = SyntheticProject.Create("library", + { sourceFile "LibA" [] + with + Source = "let f (x: int) = x" + SignatureFile = sigFile }, + { sourceFile "LibB" ["LibA"] with SignatureFile = sigFile }, + { sourceFile "LibC" ["LibA"] with SignatureFile = sigFile }, + { sourceFile "LibD" ["LibB"; "LibC"] with SignatureFile = sigFile } + ) + + let project = + { SyntheticProject.Create("app", + sourceFile "A" ["LibB"], + sourceFile "B" ["A"; "LibB"], + sourceFile "C" ["A"; "LibC"], + sourceFile "D" ["A"; "LibD"] + ) + with DependsOn = [library] } + + ProjectWorkflowBuilder(project, useTransparentCompiler = true) { + updateFile "LibA" updatePublicSurface + checkFile "D" expectOk + } + + + +type ProjectAction = Get | Modify of (SyntheticProject -> SyntheticProject) +type ProjectModificaiton = Update of int | Add | Remove +type ProjectRequest = ProjectAction * AsyncReplyChannel + +type FuzzingEvent = StartedChecking | FinishedChecking of bool | AbortedChecking of string | ModifiedImplFile | ModifiedSigFile + +[] +type SignatureFiles = Yes = 1 | No = 2 | Some = 3 + +let fuzzingTest seed (project: SyntheticProject) = task { + let rng = System.Random seed + + let checkingThreads = 3 + let maxModificationDelayMs = 10 + let maxCheckingDelayMs = 20 + //let runTimeMs = 30000 + let signatureFileModificationProbability = 0.25 + let modificationLoopIterations = 10 + let checkingLoopIterations = 5 + + let minCheckingTimeoutMs = 0 + let maxCheckingTimeoutMs = 300 + + let builder = ProjectWorkflowBuilder(project, useTransparentCompiler = true, autoStart = false) + let checker = builder.Checker + + // Force creation and caching of options + do! SaveAndCheckProject project checker |> Async.Ignore + + let projectAgent = MailboxProcessor.Start(fun (inbox: MailboxProcessor) -> + let rec loop project = + async { + let! action, reply = inbox.Receive() + let! project = + match action with + | Modify f -> async { + let p = f project + do! saveProject p false checker + return p } + | Get -> async.Return project + reply.Reply project + return! loop project + } + loop project) + + let getProject () = + projectAgent.PostAndAsyncReply(pair Get) + + let modifyProject f = + projectAgent.PostAndAsyncReply(pair(Modify f)) |> Async.Ignore + + let modificationProbabilities = [ + Update 1, 80 + Update 2, 5 + Update 10, 5 + //Add, 2 + //Remove, 1 + ] + + let modificationPicker = [| + for op, prob in modificationProbabilities do + for _ in 1 .. prob do + op + |] + + let addComment s = $"{s}\n\n// {rng.NextDouble()}" + let modifyImplFile f = { f with ExtraSource = f.ExtraSource |> addComment } + let modifySigFile f = { f with SignatureFile = Custom (f.SignatureFile.CustomText |> addComment) } + + let getRandomItem (xs: 'x array) = xs[rng.Next(0, xs.Length)] + + let getRandomModification () = modificationPicker |> getRandomItem + + let getRandomFile (project: SyntheticProject) = project.GetAllFiles() |> List.toArray |> getRandomItem + + let log = new ThreadLocal<_>((fun () -> ResizeArray<_>()), true) + + let exceptions = ConcurrentBag() + + let modificationLoop _ = task { + for _ in 1 .. modificationLoopIterations do + do! Task.Delay (rng.Next maxModificationDelayMs) + let modify project = + match getRandomModification() with + | Update n -> + + use _ = Activity.start "Update" [||] + let files = Set [ for _ in 1..n -> getRandomFile project |> snd ] + (project, files) + ||> Seq.fold (fun p file -> + let fileId = file.Id + let project, file = project.FindInAllProjects fileId + let opName, f = + if file.HasSignatureFile && rng.NextDouble() < signatureFileModificationProbability + then ModifiedSigFile, modifySigFile + else ModifiedImplFile, modifyImplFile + log.Value.Add (DateTime.Now.Ticks, opName, $"{project.Name} / {fileId}") + p |> updateFileInAnyProject fileId f) + | Add + | Remove -> + // TODO: + project + do! modifyProject modify + } + + let checkingLoop n _ = task { + for _ in 1 .. checkingLoopIterations do + let! project = getProject() + let p, file = project |> getRandomFile + + let timeout = rng.Next(minCheckingTimeoutMs, maxCheckingTimeoutMs) + + log.Value.Add (DateTime.Now.Ticks, StartedChecking, $"Loop #{n} {file.Id} ({timeout} ms timeout)") + let ct = new CancellationTokenSource() + ct.CancelAfter(timeout) + let job = Async.StartAsTask(checker |> checkFile file.Id p, cancellationToken = ct.Token) + try + use _ = Activity.start "Check" [||] + + let! parseResult, checkResult = job + log.Value.Add (DateTime.Now.Ticks, FinishedChecking (match checkResult with FSharpCheckFileAnswer.Succeeded _ -> true | _ -> false), $"Loop #{n} {file.Id}") + expectOk (parseResult, checkResult) () + with ex -> + let message = + match ex with + | :? AggregateException as e -> + match e.InnerException with + | :? GraphProcessingException as e -> $"GPE: {e.InnerException.Message}" + | _ -> e.Message + | _ -> ex.Message + log.Value.Add (DateTime.Now.Ticks, AbortedChecking (message), $"Loop #{n} {file.Id} %A{ex}") + if ex.Message <> "A task was canceled." then exceptions.Add ex + + do! Task.Delay (rng.Next maxCheckingDelayMs) + } + + use _tracerProvider = + Sdk.CreateTracerProviderBuilder() + .AddSource("fsc") + .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(serviceName="F# Fuzzing", serviceVersion = "1")) + .AddJaegerExporter() + .Build() + + use _ = Activity.start $"Fuzzing {project.Name}" [ Activity.Tags.project, project.Name; "seed", seed.ToString() ] + + do! task { + let threads = + seq { + modificationLoop CancellationToken.None + // ignore modificationLoop + for n in 1..checkingThreads do + checkingLoop n CancellationToken.None + } + + try + let! _x = threads |> Seq.skip 1 |> Task.WhenAll + () + with + | e -> + let _log = log.Values |> Seq.collect id |> Seq.sortBy p13 |> Seq.toArray + failwith $"Seed: {seed}\nException: %A{e}" + } + let log = log.Values |> Seq.collect id |> Seq.sortBy p13 |> Seq.toArray + + let _stats = log |> Array.groupBy (p23) |> Array.map (fun (op, xs) -> op, xs.Length) |> Map + + let _errors = _stats |> Map.toSeq |> Seq.filter (fst >> function AbortedChecking ex when ex <> "A task was canceled." -> true | _ -> false) |> Seq.toArray + + let _exceptions = exceptions + + Assert.Equal>([||], _errors) + + //Assert.Equal>(Map.empty, _stats) + + builder.DeleteProjectDir() +} + + +[] +[] +[] +[] +let Fuzzing signatureFiles = + + let seed = 1106087513 + let rng = System.Random(int seed) + + let fileCount = 30 + let maxDepsPerFile = 3 + + let fileName i = sprintf $"F%03d{i}" + + //let extraCode = __SOURCE_DIRECTORY__ ++ ".." ++ ".." ++ ".." ++ "src" ++ "Compiler" ++ "Utilities" ++ "EditDistance.fs" |> File.ReadAllLines |> Seq.skip 5 |> String.concat "\n" + let extraCode = "" + + let files = + [| for i in 1 .. fileCount do + let name = fileName i + let deps = [ + for _ in 1 .. maxDepsPerFile do + if i > 1 then + fileName <| rng.Next(1, i) ] + let signature = + match signatureFiles with + | SignatureFiles.Yes -> AutoGenerated + | SignatureFiles.Some when rng.NextDouble() < 0.5 -> AutoGenerated + | _ -> No + + { sourceFile name deps + with + SignatureFile = signature + ExtraSource = extraCode } + |] + + let initialProject = SyntheticProject.Create(files) + + let builder = ProjectWorkflowBuilder(initialProject, useTransparentCompiler = true, autoStart = false) + let checker = builder.Checker + + let initialProject = initialProject |> absorbAutoGeneratedSignatures checker |> Async.RunSynchronously + + fuzzingTest seed initialProject + + +let reposDir = __SOURCE_DIRECTORY__ ++ ".." ++ ".." ++ ".." ++ ".." +let giraffeDir = reposDir ++ "Giraffe" ++ "src" ++ "Giraffe" |> Path.GetFullPath +let giraffeTestsDir = reposDir ++ "Giraffe" ++ "tests" ++ "Giraffe.Tests" |> Path.GetFullPath +let giraffeSignaturesDir = reposDir ++ "giraffe-signatures" ++ "src" ++ "Giraffe" |> Path.GetFullPath +let giraffeSignaturesTestsDir = reposDir ++ "giraffe-signatures" ++ "tests" ++ "Giraffe.Tests" |> Path.GetFullPath + + +type GiraffeTheoryAttribute() = + inherit Xunit.TheoryAttribute() + do + if not (Directory.Exists giraffeDir) then + do base.Skip <- $"Giraffe not found ({giraffeDir}). You can get it here: https://github.com/giraffe-fsharp/Giraffe" + if not (Directory.Exists giraffeSignaturesDir) then + do base.Skip <- $"Giraffe (with signatures) not found ({giraffeSignaturesDir}). You can get it here: https://github.com/nojaf/Giraffe/tree/signatures" + +[] +[] +[] +let GiraffeFuzzing signatureFiles = + let seed = System.Random().Next() + //let seed = 1044159179 + + let giraffeDir = if signatureFiles then giraffeSignaturesDir else giraffeDir + let giraffeTestsDir = if signatureFiles then giraffeSignaturesTestsDir else giraffeTestsDir + + let giraffeProject = SyntheticProject.CreateFromRealProject giraffeDir + let giraffeProject = { giraffeProject with OtherOptions = "--nowarn:FS3520"::giraffeProject.OtherOptions } + + let testsProject = SyntheticProject.CreateFromRealProject giraffeTestsDir + let testsProject = + { testsProject + with + OtherOptions = "--nowarn:FS3520"::testsProject.OtherOptions + DependsOn = [ giraffeProject ] + NugetReferences = giraffeProject.NugetReferences @ testsProject.NugetReferences + } + + fuzzingTest seed testsProject + + + +[] +[] +[] +let ``File moving test`` signatureFiles = + let giraffeDir = if signatureFiles then giraffeSignaturesDir else giraffeDir + let giraffeProject = SyntheticProject.CreateFromRealProject giraffeDir + let giraffeProject = { giraffeProject with OtherOptions = "--nowarn:FS3520"::giraffeProject.OtherOptions } + + giraffeProject.Workflow { + // clearCache -- for better tracing + checkFile "Json" expectOk + moveFile "Json" 1 Down + checkFile "Json" expectOk + } + + +[] +[] +let ``What happens if bootrstapInfoStatic needs to be recomputed`` _ = + + let giraffeProject = SyntheticProject.CreateFromRealProject giraffeSignaturesDir + let giraffeProject = { giraffeProject with OtherOptions = "--nowarn:FS3520"::giraffeProject.OtherOptions } + + giraffeProject.Workflow { + updateFile "Helpers" (fun f -> { f with SignatureFile = Custom (f.SignatureFile.CustomText + "\n") }) + checkFile "EndpointRouting" expectOk + withChecker (fun checker -> + async { + checker.Caches.BootstrapInfoStatic.Clear() + checker.Caches.BootstrapInfo.Clear() + checker.Caches.FrameworkImports.Clear() + ignore checker + return () + }) + updateFile "Core" (fun f -> { f with SignatureFile = Custom (f.SignatureFile.CustomText + "\n") }) + checkFile "EndpointRouting" expectOk + } + + +module ParsedInputHashing = + + let source = """ + +type T = { A: int; B: string } + +module Stuff = + + // Some comment + let f x = x + 75 +""" + + let getParseResult source = + let fileName, snapshot, checker = singleFileChecker source + checker.ParseFile(fileName, snapshot) |> Async.RunSynchronously + + //[] + let ``Hash stays the same when whitespace changes`` () = + + //let parseResult = getParseResult source + + //let hash = parseResult.ParseTree |> parsedInputHash |> BitConverter.ToString + + //let parseResult2 = getParseResult (source + "\n \n") + + //let hash2 = parseResult2.ParseTree |> parsedInputHash |> BitConverter.ToString + + //Assert.Equal(hash, hash2) + + () \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Signatures/MissingDiagnostic.fs b/tests/FSharp.Compiler.ComponentTests/Signatures/MissingDiagnostic.fs index 55181649b03..15b2ae61df0 100644 --- a/tests/FSharp.Compiler.ComponentTests/Signatures/MissingDiagnostic.fs +++ b/tests/FSharp.Compiler.ComponentTests/Signatures/MissingDiagnostic.fs @@ -27,28 +27,34 @@ let ``Compile gives errors`` () = but here has type 'char' ") -[] -let ``Type check project with signature file doesn't get the diagnostic`` () = +[] +[] +[] +let ``Type check project with signature file doesn't get the diagnostic`` useTransparentCompiler = Fsi signature |> withAdditionalSourceFile (FsSource implementation) - |> typecheckProject false + |> typecheckProject false useTransparentCompiler |> fun projectResults -> projectResults.Diagnostics |> ignore Assert.False (projectResults.Diagnostics |> Array.isEmpty) -[] -let ``Type check project without signature file does get the diagnostic`` () = +[] +[] +[] +let ``Type check project without signature file does get the diagnostic`` useTransparentCompiler = Fs implementation - |> typecheckProject false + |> typecheckProject false useTransparentCompiler |> fun projectResults -> projectResults.Diagnostics |> ignore Assert.False (projectResults.Diagnostics |> Array.isEmpty) -[] -let ``Enabling enablePartialTypeChecking = true doesn't change the problem`` () = +[] +[] +[] +let ``Enabling enablePartialTypeChecking = true doesn't change the problem`` useTransparentCompiler = Fsi signature |> withAdditionalSourceFile (FsSource implementation) - |> typecheckProject true + |> typecheckProject true useTransparentCompiler |> fun projectResults -> projectResults.Diagnostics |> ignore Assert.False (projectResults.Diagnostics |> Array.isEmpty) \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/TypedTreeGraph.fs b/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/TypedTreeGraph.fs index 9e25c623532..c4680870efc 100644 --- a/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/TypedTreeGraph.fs +++ b/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/TypedTreeGraph.fs @@ -140,7 +140,7 @@ let ``Create Graph from typed tree`` (projectArgumentsFilePath: string) = graphFromTypedTree |> Graph.map (fun n -> n,files.[n].File) - |> Graph.serialiseToMermaid $"{fileName}.typed-tree.deps.md" + |> Graph.writeMermaidToFile $"{fileName}.typed-tree.deps.md" let collectAllDeps (graph: Graph) = (Map.empty, [ 0 .. (sourceFiles.Length - 1) ]) @@ -161,7 +161,7 @@ let ``Create Graph from typed tree`` (projectArgumentsFilePath: string) = graphFromHeuristic |> Graph.map (fun n -> n, files.[n].File) - |> Graph.serialiseToMermaid $"{fileName}.heuristic-tree.deps.md" + |> Graph.writeMermaidToFile $"{fileName}.heuristic-tree.deps.md" let heuristicMap = collectAllDeps graphFromHeuristic diff --git a/tests/FSharp.Compiler.ComponentTests/TypeChecks/TyparNameTests.fs b/tests/FSharp.Compiler.ComponentTests/TypeChecks/TyparNameTests.fs index 118ea134ff3..339fa1e2629 100644 --- a/tests/FSharp.Compiler.ComponentTests/TypeChecks/TyparNameTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/TypeChecks/TyparNameTests.fs @@ -14,7 +14,7 @@ module TyparNameTests = (additionalFile: SourceCodeFileKind) : string array = let typeCheckResult = - cUnit |> withAdditionalSourceFile additionalFile |> typecheckProject false + cUnit |> withAdditionalSourceFile additionalFile |> typecheckProject false false assert (Array.isEmpty typeCheckResult.Diagnostics) diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl index 63b98179465..fdee6f56559 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl @@ -2038,7 +2038,7 @@ FSharp.Compiler.CodeAnalysis.FSharpCheckProjectResults: FSharp.Compiler.Symbols. FSharp.Compiler.CodeAnalysis.FSharpCheckProjectResults: System.String ToString() FSharp.Compiler.CodeAnalysis.FSharpCheckProjectResults: System.String[] DependencyFiles FSharp.Compiler.CodeAnalysis.FSharpCheckProjectResults: System.String[] get_DependencyFiles() -FSharp.Compiler.CodeAnalysis.FSharpChecker: FSharp.Compiler.CodeAnalysis.FSharpChecker Create(Microsoft.FSharp.Core.FSharpOption`1[System.Int32], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`2[System.String,System.DateTime],Microsoft.FSharp.Core.FSharpOption`1[System.Tuple`3[System.Object,System.IntPtr,System.Int32]]]], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.DocumentSource], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean]) +FSharp.Compiler.CodeAnalysis.FSharpChecker: FSharp.Compiler.CodeAnalysis.FSharpChecker Create(Microsoft.FSharp.Core.FSharpOption`1[System.Int32], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`2[System.String,System.DateTime],Microsoft.FSharp.Core.FSharpOption`1[System.Tuple`3[System.Object,System.IntPtr,System.Int32]]]], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.DocumentSource], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean]) FSharp.Compiler.CodeAnalysis.FSharpChecker: FSharp.Compiler.CodeAnalysis.FSharpChecker Instance FSharp.Compiler.CodeAnalysis.FSharpChecker: FSharp.Compiler.CodeAnalysis.FSharpChecker get_Instance() FSharp.Compiler.CodeAnalysis.FSharpChecker: FSharp.Compiler.CodeAnalysis.FSharpProjectOptions GetProjectOptionsFromCommandLineArgs(System.String, System.String[], Microsoft.FSharp.Core.FSharpOption`1[System.DateTime], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean]) @@ -2049,14 +2049,19 @@ FSharp.Compiler.CodeAnalysis.FSharpChecker: Int32 get_ActualCheckFileCount() FSharp.Compiler.CodeAnalysis.FSharpChecker: Int32 get_ActualParseFileCount() FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.FSharpCheckFileAnswer] CheckFileInProject(FSharp.Compiler.CodeAnalysis.FSharpParseFileResults, System.String, Int32, FSharp.Compiler.Text.ISourceText, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.FSharpCheckProjectResults] ParseAndCheckProject(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) +FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.FSharpCheckProjectResults] ParseAndCheckProject(FSharpProjectSnapshot, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.FSharpParseFileResults] GetBackgroundParseResultsForFileInProject(System.String, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.FSharpParseFileResults] ParseFile(System.String, FSharp.Compiler.Text.ISourceText, FSharp.Compiler.CodeAnalysis.FSharpParsingOptions, Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.String]) +FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.FSharpParseFileResults] ParseFile(System.String, FSharpProjectSnapshot, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.FSharpParseFileResults] ParseFileInProject(System.String, System.String, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.FSharpCheckFileAnswer]] CheckFileInProjectAllowingStaleCachedResults(FSharp.Compiler.CodeAnalysis.FSharpParseFileResults, System.String, Int32, System.String, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.EditorServices.SemanticClassificationView]] GetBackgroundSemanticClassificationForFile(System.String, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) +FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.EditorServices.SemanticClassificationView]] GetBackgroundSemanticClassificationForFile(System.String, FSharpProjectSnapshot, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] NotifyFileChanged(System.String, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] NotifyProjectCleaned(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Collections.Generic.IEnumerable`1[FSharp.Compiler.Text.Range]] FindBackgroundReferencesInFile(System.String, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, FSharp.Compiler.Symbols.FSharpSymbol, Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.String]) +FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Collections.Generic.IEnumerable`1[FSharp.Compiler.Text.Range]] FindBackgroundReferencesInFile(System.String, FSharpProjectSnapshot, FSharp.Compiler.Symbols.FSharpSymbol, Microsoft.FSharp.Core.FSharpOption`1[System.String]) +FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.CodeAnalysis.FSharpParseFileResults,FSharp.Compiler.CodeAnalysis.FSharpCheckFileAnswer]] ParseAndCheckFileInProject(System.String, FSharpProjectSnapshot, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.CodeAnalysis.FSharpParseFileResults,FSharp.Compiler.CodeAnalysis.FSharpCheckFileAnswer]] ParseAndCheckFileInProject(System.String, Int32, FSharp.Compiler.Text.ISourceText, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.CodeAnalysis.FSharpParseFileResults,FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults]] GetBackgroundCheckResultsForFileInProject(System.String, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.CodeAnalysis.FSharpProjectOptions,Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Diagnostics.FSharpDiagnostic]]] GetProjectOptionsFromScript(System.String, FSharp.Compiler.Text.ISourceText, Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.DateTime], Microsoft.FSharp.Core.FSharpOption`1[System.String[]], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.String], Microsoft.FSharp.Core.FSharpOption`1[System.Int64], Microsoft.FSharp.Core.FSharpOption`1[System.String]) @@ -2077,6 +2082,7 @@ FSharp.Compiler.CodeAnalysis.FSharpChecker: System.Tuple`2[FSharp.Compiler.CodeA FSharp.Compiler.CodeAnalysis.FSharpChecker: System.Tuple`2[FSharp.Compiler.CodeAnalysis.FSharpParsingOptions,Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Diagnostics.FSharpDiagnostic]] GetParsingOptionsFromProjectOptions(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions) FSharp.Compiler.CodeAnalysis.FSharpChecker: System.Tuple`2[FSharp.Compiler.Tokenization.FSharpTokenInfo[],FSharp.Compiler.Tokenization.FSharpTokenizerLexState] TokenizeLine(System.String, FSharp.Compiler.Tokenization.FSharpTokenizerLexState) FSharp.Compiler.CodeAnalysis.FSharpChecker: Void ClearCache(System.Collections.Generic.IEnumerable`1[FSharp.Compiler.CodeAnalysis.FSharpProjectOptions], Microsoft.FSharp.Core.FSharpOption`1[System.String]) +FSharp.Compiler.CodeAnalysis.FSharpChecker: Void ClearCache(System.Collections.Generic.IEnumerable`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier], Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Void ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients() FSharp.Compiler.CodeAnalysis.FSharpChecker: Void InvalidateAll() FSharp.Compiler.CodeAnalysis.FSharpChecker: Void InvalidateConfiguration(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) @@ -2294,6 +2300,76 @@ FSharp.Compiler.CodeAnalysis.LegacyResolvedFile: System.String get_baggage() FSharp.Compiler.CodeAnalysis.LegacyResolvedFile: System.String get_itemSpec() FSharp.Compiler.CodeAnalysis.LegacyResolvedFile: System.String itemSpec FSharp.Compiler.CodeAnalysis.LegacyResolvedFile: Void .ctor(System.String, Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`2[System.String,System.String],System.String], System.String) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: Boolean Equals(System.Object) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: Boolean IsSignatureFile +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: Boolean get_IsSignatureFile() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: FSharpFileSnapshot Create(System.String, System.String, Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Threading.Tasks.Task`1[FSharp.Compiler.Text.ISourceTextNew]]) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: FSharpFileSnapshot CreateFromFileSystem(System.String) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: Int32 GetHashCode() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: System.String FileName +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: System.String GetFileName() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: System.String Version +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: System.String get_FileName() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: System.String get_Version() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: System.Threading.Tasks.Task`1[FSharp.Compiler.Text.ISourceTextNew] GetSource() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: Void .ctor(System.String, System.String, Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Threading.Tasks.Task`1[FSharp.Compiler.Text.ISourceTextNew]]) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: Boolean Equals(FSharpProjectIdentifier) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: Boolean Equals(System.Object) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: Boolean Equals(System.Object, System.Collections.IEqualityComparer) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: FSharpProjectIdentifier NewFSharpProjectIdentifier(System.String, System.String) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: Int32 CompareTo(FSharpProjectIdentifier) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: Int32 CompareTo(System.Object) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: Int32 CompareTo(System.Object, System.Collections.IComparer) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: Int32 GetHashCode() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: Int32 GetHashCode(System.Collections.IEqualityComparer) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: Int32 Tag +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: Int32 get_Tag() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: System.String ToString() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: System.String get_outputFileName() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: System.String get_projectFileName() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: System.String outputFileName +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: System.String projectFileName +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: FSharpProjectIdentifier Identifier +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: FSharpProjectIdentifier get_Identifier() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: FSharpProjectSnapshot Create(System.String, Microsoft.FSharp.Core.FSharpOption`1[System.String], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk], Microsoft.FSharp.Collections.FSharpList`1[System.String], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot], Boolean, Boolean, System.DateTime, Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.FSharpUnresolvedReferencesSet], Microsoft.FSharp.Collections.FSharpList`1[System.Tuple`3[FSharp.Compiler.Text.Range,System.String,System.String]], Microsoft.FSharp.Core.FSharpOption`1[System.Int64]) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: FSharpProjectSnapshot Replace(Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot]) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot] FromOptions(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot] FromOptions(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpFunc`2[FSharp.Compiler.CodeAnalysis.FSharpProjectOptions,Microsoft.FSharp.Core.FSharpFunc`2[System.String,Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot]]], Microsoft.FSharp.Core.FSharpOption`1[System.Collections.Generic.Dictionary`2[FSharp.Compiler.CodeAnalysis.FSharpProjectOptions,FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot]]) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot] FromOptions(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, System.String, Int32, FSharp.Compiler.Text.ISourceText) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: System.String Label +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: System.String get_Label() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Boolean Equals(System.Object) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharpProjectSnapshot get_options() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharpProjectSnapshot options +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharpReferencedProjectSnapshot CreateFSharp(System.String, FSharpProjectSnapshot) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharpReferencedProjectSnapshot NewFSharpReference(System.String, FSharpProjectSnapshot) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Int32 GetHashCode() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Int32 Tag +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Int32 get_Tag() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: System.String OutputFile +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: System.String ToString() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: System.String get_OutputFile() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: System.String get_projectOutputFile() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: System.String projectOutputFile +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Boolean Equals(ReferenceOnDisk) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Boolean Equals(System.Object) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Boolean Equals(System.Object, System.Collections.IEqualityComparer) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Int32 CompareTo(ReferenceOnDisk) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Int32 CompareTo(System.Object) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Int32 CompareTo(System.Object, System.Collections.IComparer) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Int32 GetHashCode() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Int32 GetHashCode(System.Collections.IEqualityComparer) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: System.DateTime LastModified +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: System.DateTime get_LastModified() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: System.String Path +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: System.String ToString() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: System.String get_Path() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Void .ctor(System.String, System.DateTime) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot: FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot +FSharp.Compiler.CodeAnalysis.ProjectSnapshot: FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier +FSharp.Compiler.CodeAnalysis.ProjectSnapshot: FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot +FSharp.Compiler.CodeAnalysis.ProjectSnapshot: FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot +FSharp.Compiler.CodeAnalysis.ProjectSnapshot: FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk FSharp.Compiler.CompilerEnvironment: Boolean IsCheckerSupportedSubcategory(System.String) FSharp.Compiler.CompilerEnvironment: Boolean IsCompilable(System.String) FSharp.Compiler.CompilerEnvironment: Boolean IsScriptFile(System.String) @@ -10193,6 +10269,7 @@ FSharp.Compiler.Text.ISourceText: System.String GetSubTextFromRange(FSharp.Compi FSharp.Compiler.Text.ISourceText: System.String GetSubTextString(Int32, Int32) FSharp.Compiler.Text.ISourceText: System.Tuple`2[System.Int32,System.Int32] GetLastCharacterPosition() FSharp.Compiler.Text.ISourceText: Void CopyTo(Int32, Char[], Int32, Int32) +FSharp.Compiler.Text.ISourceTextNew: System.Collections.Immutable.ImmutableArray`1[System.Byte] GetChecksum() FSharp.Compiler.Text.Line: Int32 fromZ(Int32) FSharp.Compiler.Text.Line: Int32 toZ(Int32) FSharp.Compiler.Text.NavigableTaggedText: FSharp.Compiler.Text.Range Range @@ -10272,6 +10349,8 @@ FSharp.Compiler.Text.RangeModule: System.Tuple`2[System.String,System.Tuple`2[Sy FSharp.Compiler.Text.RangeModule: System.Tuple`2[System.Tuple`2[System.Int32,System.Int32],System.Tuple`2[System.Int32,System.Int32]] toZ(FSharp.Compiler.Text.Range) FSharp.Compiler.Text.RangeModule: Void outputRange(System.IO.TextWriter, FSharp.Compiler.Text.Range) FSharp.Compiler.Text.SourceText: FSharp.Compiler.Text.ISourceText ofString(System.String) +FSharp.Compiler.Text.SourceTextNew: FSharp.Compiler.Text.ISourceTextNew ofISourceText(FSharp.Compiler.Text.ISourceText) +FSharp.Compiler.Text.SourceTextNew: FSharp.Compiler.Text.ISourceTextNew ofString(System.String) FSharp.Compiler.Text.TaggedText: FSharp.Compiler.Text.TextTag Tag FSharp.Compiler.Text.TaggedText: FSharp.Compiler.Text.TextTag get_Tag() FSharp.Compiler.Text.TaggedText: System.String Text diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl index 63b98179465..fdee6f56559 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl @@ -2038,7 +2038,7 @@ FSharp.Compiler.CodeAnalysis.FSharpCheckProjectResults: FSharp.Compiler.Symbols. FSharp.Compiler.CodeAnalysis.FSharpCheckProjectResults: System.String ToString() FSharp.Compiler.CodeAnalysis.FSharpCheckProjectResults: System.String[] DependencyFiles FSharp.Compiler.CodeAnalysis.FSharpCheckProjectResults: System.String[] get_DependencyFiles() -FSharp.Compiler.CodeAnalysis.FSharpChecker: FSharp.Compiler.CodeAnalysis.FSharpChecker Create(Microsoft.FSharp.Core.FSharpOption`1[System.Int32], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`2[System.String,System.DateTime],Microsoft.FSharp.Core.FSharpOption`1[System.Tuple`3[System.Object,System.IntPtr,System.Int32]]]], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.DocumentSource], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean]) +FSharp.Compiler.CodeAnalysis.FSharpChecker: FSharp.Compiler.CodeAnalysis.FSharpChecker Create(Microsoft.FSharp.Core.FSharpOption`1[System.Int32], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`2[System.String,System.DateTime],Microsoft.FSharp.Core.FSharpOption`1[System.Tuple`3[System.Object,System.IntPtr,System.Int32]]]], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.DocumentSource], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean]) FSharp.Compiler.CodeAnalysis.FSharpChecker: FSharp.Compiler.CodeAnalysis.FSharpChecker Instance FSharp.Compiler.CodeAnalysis.FSharpChecker: FSharp.Compiler.CodeAnalysis.FSharpChecker get_Instance() FSharp.Compiler.CodeAnalysis.FSharpChecker: FSharp.Compiler.CodeAnalysis.FSharpProjectOptions GetProjectOptionsFromCommandLineArgs(System.String, System.String[], Microsoft.FSharp.Core.FSharpOption`1[System.DateTime], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean]) @@ -2049,14 +2049,19 @@ FSharp.Compiler.CodeAnalysis.FSharpChecker: Int32 get_ActualCheckFileCount() FSharp.Compiler.CodeAnalysis.FSharpChecker: Int32 get_ActualParseFileCount() FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.FSharpCheckFileAnswer] CheckFileInProject(FSharp.Compiler.CodeAnalysis.FSharpParseFileResults, System.String, Int32, FSharp.Compiler.Text.ISourceText, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.FSharpCheckProjectResults] ParseAndCheckProject(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) +FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.FSharpCheckProjectResults] ParseAndCheckProject(FSharpProjectSnapshot, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.FSharpParseFileResults] GetBackgroundParseResultsForFileInProject(System.String, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.FSharpParseFileResults] ParseFile(System.String, FSharp.Compiler.Text.ISourceText, FSharp.Compiler.CodeAnalysis.FSharpParsingOptions, Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.String]) +FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.FSharpParseFileResults] ParseFile(System.String, FSharpProjectSnapshot, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.FSharpParseFileResults] ParseFileInProject(System.String, System.String, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.FSharpCheckFileAnswer]] CheckFileInProjectAllowingStaleCachedResults(FSharp.Compiler.CodeAnalysis.FSharpParseFileResults, System.String, Int32, System.String, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.EditorServices.SemanticClassificationView]] GetBackgroundSemanticClassificationForFile(System.String, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) +FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.EditorServices.SemanticClassificationView]] GetBackgroundSemanticClassificationForFile(System.String, FSharpProjectSnapshot, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] NotifyFileChanged(System.String, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] NotifyProjectCleaned(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Collections.Generic.IEnumerable`1[FSharp.Compiler.Text.Range]] FindBackgroundReferencesInFile(System.String, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, FSharp.Compiler.Symbols.FSharpSymbol, Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.String]) +FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Collections.Generic.IEnumerable`1[FSharp.Compiler.Text.Range]] FindBackgroundReferencesInFile(System.String, FSharpProjectSnapshot, FSharp.Compiler.Symbols.FSharpSymbol, Microsoft.FSharp.Core.FSharpOption`1[System.String]) +FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.CodeAnalysis.FSharpParseFileResults,FSharp.Compiler.CodeAnalysis.FSharpCheckFileAnswer]] ParseAndCheckFileInProject(System.String, FSharpProjectSnapshot, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.CodeAnalysis.FSharpParseFileResults,FSharp.Compiler.CodeAnalysis.FSharpCheckFileAnswer]] ParseAndCheckFileInProject(System.String, Int32, FSharp.Compiler.Text.ISourceText, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.CodeAnalysis.FSharpParseFileResults,FSharp.Compiler.CodeAnalysis.FSharpCheckFileResults]] GetBackgroundCheckResultsForFileInProject(System.String, FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[System.Tuple`2[FSharp.Compiler.CodeAnalysis.FSharpProjectOptions,Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Diagnostics.FSharpDiagnostic]]] GetProjectOptionsFromScript(System.String, FSharp.Compiler.Text.ISourceText, Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.DateTime], Microsoft.FSharp.Core.FSharpOption`1[System.String[]], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[System.String], Microsoft.FSharp.Core.FSharpOption`1[System.Int64], Microsoft.FSharp.Core.FSharpOption`1[System.String]) @@ -2077,6 +2082,7 @@ FSharp.Compiler.CodeAnalysis.FSharpChecker: System.Tuple`2[FSharp.Compiler.CodeA FSharp.Compiler.CodeAnalysis.FSharpChecker: System.Tuple`2[FSharp.Compiler.CodeAnalysis.FSharpParsingOptions,Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Diagnostics.FSharpDiagnostic]] GetParsingOptionsFromProjectOptions(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions) FSharp.Compiler.CodeAnalysis.FSharpChecker: System.Tuple`2[FSharp.Compiler.Tokenization.FSharpTokenInfo[],FSharp.Compiler.Tokenization.FSharpTokenizerLexState] TokenizeLine(System.String, FSharp.Compiler.Tokenization.FSharpTokenizerLexState) FSharp.Compiler.CodeAnalysis.FSharpChecker: Void ClearCache(System.Collections.Generic.IEnumerable`1[FSharp.Compiler.CodeAnalysis.FSharpProjectOptions], Microsoft.FSharp.Core.FSharpOption`1[System.String]) +FSharp.Compiler.CodeAnalysis.FSharpChecker: Void ClearCache(System.Collections.Generic.IEnumerable`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier], Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Void ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients() FSharp.Compiler.CodeAnalysis.FSharpChecker: Void InvalidateAll() FSharp.Compiler.CodeAnalysis.FSharpChecker: Void InvalidateConfiguration(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) @@ -2294,6 +2300,76 @@ FSharp.Compiler.CodeAnalysis.LegacyResolvedFile: System.String get_baggage() FSharp.Compiler.CodeAnalysis.LegacyResolvedFile: System.String get_itemSpec() FSharp.Compiler.CodeAnalysis.LegacyResolvedFile: System.String itemSpec FSharp.Compiler.CodeAnalysis.LegacyResolvedFile: Void .ctor(System.String, Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`2[System.String,System.String],System.String], System.String) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: Boolean Equals(System.Object) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: Boolean IsSignatureFile +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: Boolean get_IsSignatureFile() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: FSharpFileSnapshot Create(System.String, System.String, Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Threading.Tasks.Task`1[FSharp.Compiler.Text.ISourceTextNew]]) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: FSharpFileSnapshot CreateFromFileSystem(System.String) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: Int32 GetHashCode() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: System.String FileName +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: System.String GetFileName() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: System.String Version +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: System.String get_FileName() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: System.String get_Version() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: System.Threading.Tasks.Task`1[FSharp.Compiler.Text.ISourceTextNew] GetSource() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot: Void .ctor(System.String, System.String, Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Threading.Tasks.Task`1[FSharp.Compiler.Text.ISourceTextNew]]) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: Boolean Equals(FSharpProjectIdentifier) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: Boolean Equals(System.Object) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: Boolean Equals(System.Object, System.Collections.IEqualityComparer) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: FSharpProjectIdentifier NewFSharpProjectIdentifier(System.String, System.String) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: Int32 CompareTo(FSharpProjectIdentifier) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: Int32 CompareTo(System.Object) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: Int32 CompareTo(System.Object, System.Collections.IComparer) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: Int32 GetHashCode() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: Int32 GetHashCode(System.Collections.IEqualityComparer) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: Int32 Tag +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: Int32 get_Tag() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: System.String ToString() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: System.String get_outputFileName() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: System.String get_projectFileName() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: System.String outputFileName +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier: System.String projectFileName +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: FSharpProjectIdentifier Identifier +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: FSharpProjectIdentifier get_Identifier() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: FSharpProjectSnapshot Create(System.String, Microsoft.FSharp.Core.FSharpOption`1[System.String], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk], Microsoft.FSharp.Collections.FSharpList`1[System.String], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot], Boolean, Boolean, System.DateTime, Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.CodeAnalysis.FSharpUnresolvedReferencesSet], Microsoft.FSharp.Collections.FSharpList`1[System.Tuple`3[FSharp.Compiler.Text.Range,System.String,System.String]], Microsoft.FSharp.Core.FSharpOption`1[System.Int64]) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: FSharpProjectSnapshot Replace(Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot]) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot] FromOptions(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot] FromOptions(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpFunc`2[FSharp.Compiler.CodeAnalysis.FSharpProjectOptions,Microsoft.FSharp.Core.FSharpFunc`2[System.String,Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot]]], Microsoft.FSharp.Core.FSharpOption`1[System.Collections.Generic.Dictionary`2[FSharp.Compiler.CodeAnalysis.FSharpProjectOptions,FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot]]) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: Microsoft.FSharp.Control.FSharpAsync`1[FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot] FromOptions(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, System.String, Int32, FSharp.Compiler.Text.ISourceText) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: System.String Label +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot: System.String get_Label() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Boolean Equals(System.Object) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharpProjectSnapshot get_options() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharpProjectSnapshot options +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharpReferencedProjectSnapshot CreateFSharp(System.String, FSharpProjectSnapshot) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: FSharpReferencedProjectSnapshot NewFSharpReference(System.String, FSharpProjectSnapshot) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Int32 GetHashCode() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Int32 Tag +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: Int32 get_Tag() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: System.String OutputFile +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: System.String ToString() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: System.String get_OutputFile() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: System.String get_projectOutputFile() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot: System.String projectOutputFile +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Boolean Equals(ReferenceOnDisk) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Boolean Equals(System.Object) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Boolean Equals(System.Object, System.Collections.IEqualityComparer) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Int32 CompareTo(ReferenceOnDisk) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Int32 CompareTo(System.Object) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Int32 CompareTo(System.Object, System.Collections.IComparer) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Int32 GetHashCode() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Int32 GetHashCode(System.Collections.IEqualityComparer) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: System.DateTime LastModified +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: System.DateTime get_LastModified() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: System.String Path +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: System.String ToString() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: System.String get_Path() +FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk: Void .ctor(System.String, System.DateTime) +FSharp.Compiler.CodeAnalysis.ProjectSnapshot: FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpFileSnapshot +FSharp.Compiler.CodeAnalysis.ProjectSnapshot: FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectIdentifier +FSharp.Compiler.CodeAnalysis.ProjectSnapshot: FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpProjectSnapshot +FSharp.Compiler.CodeAnalysis.ProjectSnapshot: FSharp.Compiler.CodeAnalysis.ProjectSnapshot+FSharpReferencedProjectSnapshot +FSharp.Compiler.CodeAnalysis.ProjectSnapshot: FSharp.Compiler.CodeAnalysis.ProjectSnapshot+ReferenceOnDisk FSharp.Compiler.CompilerEnvironment: Boolean IsCheckerSupportedSubcategory(System.String) FSharp.Compiler.CompilerEnvironment: Boolean IsCompilable(System.String) FSharp.Compiler.CompilerEnvironment: Boolean IsScriptFile(System.String) @@ -10193,6 +10269,7 @@ FSharp.Compiler.Text.ISourceText: System.String GetSubTextFromRange(FSharp.Compi FSharp.Compiler.Text.ISourceText: System.String GetSubTextString(Int32, Int32) FSharp.Compiler.Text.ISourceText: System.Tuple`2[System.Int32,System.Int32] GetLastCharacterPosition() FSharp.Compiler.Text.ISourceText: Void CopyTo(Int32, Char[], Int32, Int32) +FSharp.Compiler.Text.ISourceTextNew: System.Collections.Immutable.ImmutableArray`1[System.Byte] GetChecksum() FSharp.Compiler.Text.Line: Int32 fromZ(Int32) FSharp.Compiler.Text.Line: Int32 toZ(Int32) FSharp.Compiler.Text.NavigableTaggedText: FSharp.Compiler.Text.Range Range @@ -10272,6 +10349,8 @@ FSharp.Compiler.Text.RangeModule: System.Tuple`2[System.String,System.Tuple`2[Sy FSharp.Compiler.Text.RangeModule: System.Tuple`2[System.Tuple`2[System.Int32,System.Int32],System.Tuple`2[System.Int32,System.Int32]] toZ(FSharp.Compiler.Text.Range) FSharp.Compiler.Text.RangeModule: Void outputRange(System.IO.TextWriter, FSharp.Compiler.Text.Range) FSharp.Compiler.Text.SourceText: FSharp.Compiler.Text.ISourceText ofString(System.String) +FSharp.Compiler.Text.SourceTextNew: FSharp.Compiler.Text.ISourceTextNew ofISourceText(FSharp.Compiler.Text.ISourceText) +FSharp.Compiler.Text.SourceTextNew: FSharp.Compiler.Text.ISourceTextNew ofString(System.String) FSharp.Compiler.Text.TaggedText: FSharp.Compiler.Text.TextTag Tag FSharp.Compiler.Text.TaggedText: FSharp.Compiler.Text.TextTag get_Tag() FSharp.Compiler.Text.TaggedText: System.String Text diff --git a/tests/FSharp.Test.Utilities/Compiler.fs b/tests/FSharp.Test.Utilities/Compiler.fs index 52c9b033119..46462d0a473 100644 --- a/tests/FSharp.Test.Utilities/Compiler.fs +++ b/tests/FSharp.Test.Utilities/Compiler.fs @@ -27,6 +27,7 @@ open TestFramework open System.Runtime.CompilerServices open System.Runtime.InteropServices +open FSharp.Compiler.CodeAnalysis module rec Compiler = @@ -917,7 +918,7 @@ module rec Compiler = CompilerAssert.TypeCheck(options, fileName, source) | _ -> failwith "Typecheck only supports F#" - let typecheckProject enablePartialTypeChecking (cUnit: CompilationUnit) : FSharp.Compiler.CodeAnalysis.FSharpCheckProjectResults = + let typecheckProject enablePartialTypeChecking useTransparentCompiler (cUnit: CompilationUnit) : FSharp.Compiler.CodeAnalysis.FSharpCheckProjectResults = match cUnit with | FS fsSource -> let options = fsSource.Options |> Array.ofList @@ -935,7 +936,8 @@ module rec Compiler = |> async.Return let sourceFiles = Array.map fst sourceFiles - CompilerAssert.TypeCheckProject(options, sourceFiles, getSourceText, enablePartialTypeChecking) + + CompilerAssert.TypeCheckProject(options, sourceFiles, getSourceText, enablePartialTypeChecking, useTransparentCompiler) | _ -> failwith "Typecheck only supports F#" let run (result: CompilationResult) : CompilationResult = diff --git a/tests/FSharp.Test.Utilities/CompilerAssert.fs b/tests/FSharp.Test.Utilities/CompilerAssert.fs index b5e12d8cd5a..7951ba31d5d 100644 --- a/tests/FSharp.Test.Utilities/CompilerAssert.fs +++ b/tests/FSharp.Test.Utilities/CompilerAssert.fs @@ -11,6 +11,7 @@ open System.Reflection open FSharp.Compiler.Interactive.Shell open FSharp.Compiler.IO open FSharp.Compiler.CodeAnalysis +open FSharp.Compiler.CodeAnalysis.ProjectSnapshot open FSharp.Compiler.Diagnostics open FSharp.Compiler.Text #if NETCOREAPP @@ -258,7 +259,8 @@ and Compilation = module rec CompilerAssertHelpers = - let checker = FSharpChecker.Create(suggestNamesForErrors=true) + let useTransparentCompiler = FSharp.Compiler.CompilerConfig.FSharpExperimentalFeaturesEnabledAutomatically + let checker = FSharpChecker.Create(suggestNamesForErrors=true, useTransparentCompiler=useTransparentCompiler) // Unlike C# whose entrypoint is always string[] F# can make an entrypoint with 0 args, or with an array of string[] let mkDefaultArgs (entryPoint:MethodBase) : obj[] = [| @@ -886,14 +888,31 @@ Updated automatically, please check diffs in your pull request, changes must be static member TypeCheckSingleError (source: string) (expectedSeverity: FSharpDiagnosticSeverity) (expectedErrorNumber: int) (expectedErrorRange: int * int * int * int) (expectedErrorMsg: string) = CompilerAssert.TypeCheckWithErrors source [| expectedSeverity, expectedErrorNumber, expectedErrorRange, expectedErrorMsg |] - static member TypeCheckProject(options: string array, sourceFiles: string array, getSourceText, enablePartialTypeChecking) : FSharpCheckProjectResults = - let checker = FSharpChecker.Create(documentSource = DocumentSource.Custom getSourceText, enablePartialTypeChecking = enablePartialTypeChecking) + static member TypeCheckProject(options: string array, sourceFiles: string array, getSourceText, enablePartialTypeChecking, useTransparentCompiler) : FSharpCheckProjectResults = + let checker = FSharpChecker.Create(documentSource = DocumentSource.Custom getSourceText, enablePartialTypeChecking = enablePartialTypeChecking, useTransparentCompiler = useTransparentCompiler) let defaultOptions = defaultProjectOptions TargetFramework.Current let projectOptions = { defaultOptions with OtherOptions = Array.append options defaultOptions.OtherOptions; SourceFiles = sourceFiles } - checker.ParseAndCheckProject(projectOptions) + if useTransparentCompiler then + let getFileSnapshot _ fileName = + async.Return + (FSharpFileSnapshot( + FileName = fileName, + Version = "1", + GetSource = fun () -> task { + match! getSourceText fileName with + | Some source -> return SourceTextNew.ofISourceText source + | None -> return failwith $"couldn't get source for {fileName}" + } + )) + + let snapshot = FSharpProjectSnapshot.FromOptions(projectOptions, getFileSnapshot) |> Async.RunSynchronously + + checker.ParseAndCheckProject(snapshot) + else + checker.ParseAndCheckProject(projectOptions) |> Async.RunImmediate - + static member CompileExeWithOptions(options, (source: SourceCodeFileKind)) = compile true options source (fun (errors, _, _) -> if errors.Length > 0 then diff --git a/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj b/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj index 6867d180ae9..5cfcba98ca7 100644 --- a/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj +++ b/tests/FSharp.Test.Utilities/FSharp.Test.Utilities.fsproj @@ -73,6 +73,18 @@ + + + + + + + + + + + + @@ -88,6 +100,7 @@ + diff --git a/tests/FSharp.Test.Utilities/ProjectGeneration.fs b/tests/FSharp.Test.Utilities/ProjectGeneration.fs index 73f7fd1f0e4..54c00ebe544 100644 --- a/tests/FSharp.Test.Utilities/ProjectGeneration.fs +++ b/tests/FSharp.Test.Utilities/ProjectGeneration.fs @@ -25,6 +25,7 @@ open System.Threading.Tasks open System.Xml open FSharp.Compiler.CodeAnalysis +open FSharp.Compiler.CodeAnalysis.ProjectSnapshot open FSharp.Compiler.Diagnostics open FSharp.Compiler.Text @@ -41,7 +42,7 @@ let private projectRoot = "test-projects" let private defaultFunctionName = "f" type Reference = { - Name: string + Name: string Version: string option } module ReferenceHelpers = @@ -68,13 +69,13 @@ module ReferenceHelpers = } |> String.concat "\n" - let runtimeList = lazy ( + let runtimeList = lazy ( // You can see which versions of the .NET runtime are currently installed with the following command. let psi = ProcessStartInfo("dotnet", "--list-runtimes", RedirectStandardOutput = true, UseShellExecute = false) let proc = Process.Start(psi) - proc.WaitForExit() + proc.WaitForExit(1000) |> ignore let output = seq { @@ -92,7 +93,8 @@ module ReferenceHelpers = { Name = matches.Groups.[1].Value Version = version - Path = DirectoryInfo(Path.Combine(matches.Groups[3].Value, version)) })) + Path = DirectoryInfo(Path.Combine(matches.Groups[3].Value, version)) }) + |> Seq.toList) let getFrameworkReference (reference: Reference) = @@ -540,10 +542,45 @@ module ProjectOperations = filePath |> project.FindByPath |> renderSourceFile project - |> SourceText.ofString + |> SourceTextNew.ofString + + let internal getFileSnapshot (project: SyntheticProject) _options (path: string) = + async { + let project, filePath = + if path.EndsWith(".fsi") then + let implFilePath = path[..path.Length - 2] + let p, f = project.FindInAllProjectsByPath implFilePath + p, getSignatureFilePath p f + else + let p, f = project.FindInAllProjectsByPath path + p, getFilePath p f + + let source = getSourceText project path + use md5 = System.Security.Cryptography.MD5.Create() + let inputBytes = Encoding.UTF8.GetBytes(source.ToString()) + let hash = md5.ComputeHash(inputBytes) |> Array.map (fun b -> b.ToString("X2")) |> String.concat "" + + return FSharpFileSnapshot( + FileName = filePath, + Version = hash, + GetSource = fun () -> source |> Task.FromResult + ) + } + + let checkFileWithTransparentCompiler fileId (project: SyntheticProject) (checker: FSharpChecker) = + async { + let file = project.Find fileId + let absFileName = getFilePath project file + let options = project.GetProjectOptions checker + let! projectSnapshot = FSharpProjectSnapshot.FromOptions(options, getFileSnapshot project) + return! checker.ParseAndCheckFileInProject(absFileName, projectSnapshot) + } let checkFile fileId (project: SyntheticProject) (checker: FSharpChecker) = - checkFileWithIncrementalBuilder fileId project checker + (if checker.UsesTransparentCompiler then + checkFileWithTransparentCompiler + else + checkFileWithIncrementalBuilder) fileId project checker let getTypeCheckResult (parseResults: FSharpParseFileResults, checkResults: FSharpCheckFileAnswer) = Assert.True(not parseResults.ParseHadErrors) @@ -680,7 +717,7 @@ module ProjectOperations = module Helpers = - let getSymbolUse fileName (source: string) (symbolName: string) options (checker: FSharpChecker) = + let internal getSymbolUse fileName (source: string) (symbolName: string) snapshot (checker: FSharpChecker) = async { let lines = source.Split '\n' |> Seq.skip 1 // module definition let lineNumber, fullLine, colAtEndOfNames = @@ -694,8 +731,7 @@ module Helpers = |> Seq.tryPick id |> Option.defaultValue (-1, "", -1) - let! results = checker.ParseAndCheckFileInProject( - fileName, 0, SourceText.ofString source, options) + let! results = checker.ParseAndCheckFileInProject(fileName, snapshot) let typeCheckResults = getTypeCheckResult results @@ -706,18 +742,23 @@ module Helpers = failwith $"No symbol found in {fileName} at {lineNumber}:{colAtEndOfNames}\nFile contents:\n\n{source}\n") } - let singleFileChecker source = + let internal singleFileChecker source = let fileName = "test.fs" - let getSource _ = source |> SourceText.ofString |> Some |> async.Return + let getSource _ fileName = + FSharpFileSnapshot( + FileName = fileName, + Version = "1", + GetSource = fun () -> source |> SourceTextNew.ofString |> Task.FromResult ) + |> async.Return let checker = FSharpChecker.Create( keepAllBackgroundSymbolUses = false, enableBackgroundItemKeyStoreAndSemanticClassification = true, enablePartialTypeChecking = true, captureIdentifiersWhenParsing = true, - documentSource = DocumentSource.Custom getSource) + useTransparentCompiler = true) let options = let baseOptions, _ = @@ -739,7 +780,9 @@ module Helpers = OriginalLoadReferences = [] Stamp = None } - fileName, options, checker + let snapshot = FSharpProjectSnapshot.FromOptions(options, getSource) |> Async.RunSynchronously + + fileName, snapshot, checker open Helpers @@ -757,8 +800,9 @@ let SaveAndCheckProject project checker = do! saveProject project true checker let options = project.GetProjectOptions checker + let! snapshot = FSharpProjectSnapshot.FromOptions(options, getFileSnapshot project) - let! results = checker.ParseAndCheckProject(options) + let! results = checker.ParseAndCheckProject(snapshot) if not (Array.isEmpty results.Diagnostics || project.SkipInitialCheck) then failwith $"Project {project.Name} failed initial check: \n%A{results.Diagnostics}" @@ -778,6 +822,8 @@ let SaveAndCheckProject project checker = Cursor = None } } +type MoveFileDirection = Up | Down + type ProjectWorkflowBuilder ( initialProject: SyntheticProject, @@ -791,7 +837,7 @@ type ProjectWorkflowBuilder ?autoStart ) = - let useTransparentCompiler = defaultArg useTransparentCompiler false + let useTransparentCompiler = defaultArg useTransparentCompiler FSharp.Compiler.CompilerConfig.FSharpExperimentalFeaturesEnabledAutomatically let useGetSource = not useTransparentCompiler && defaultArg useGetSource false let useChangeNotifications = not useTransparentCompiler && defaultArg useChangeNotifications false let autoStart = defaultArg autoStart true @@ -800,7 +846,7 @@ type ProjectWorkflowBuilder let mutable activity = None let mutable tracerProvider = None - let getSource f = f |> getSourceText latestProject |> Some |> async.Return + let getSource f = f |> getSourceText latestProject :> ISourceText |> Some |> async.Return let checker = defaultArg @@ -811,7 +857,9 @@ type ProjectWorkflowBuilder enablePartialTypeChecking = true, captureIdentifiersWhenParsing = true, documentSource = (if useGetSource then DocumentSource.Custom getSource else DocumentSource.FileSystem), - useSyntaxTreeCache = defaultArg useSyntaxTreeCache false)) + useSyntaxTreeCache = defaultArg useSyntaxTreeCache false, + useTransparentCompiler = useTransparentCompiler + )) let mapProjectAsync f workflow = async { @@ -859,7 +907,9 @@ type ProjectWorkflowBuilder member this.DeleteProjectDir() = if Directory.Exists initialProject.ProjectDir then - Directory.Delete(initialProject.ProjectDir, true) + try + Directory.Delete(initialProject.ProjectDir, true) + with _ -> () member this.Execute(workflow: Async) = try @@ -1000,6 +1050,30 @@ type ProjectWorkflowBuilder return { ctx with Signatures = ctx.Signatures.Add(fileId, newSignature) } } + [] + member this.MoveFile(workflow: Async, fileId: string, count, direction: MoveFileDirection) = + + workflow + |> mapProject (fun project -> + let index = + project.SourceFiles + |> List.tryFindIndex (fun f -> f.Id = fileId) + |> Option.defaultWith (fun () -> failwith $"File {fileId} not found") + + let dir = if direction = Up then -1 else 1 + let newIndex = index + count * dir + + if newIndex < 0 || newIndex > project.SourceFiles.Length - 1 then + failwith $"Cannot move file {fileId} {count} times {direction} as it would be out of bounds" + + let file = project.SourceFiles.[index] + let newFiles = + project.SourceFiles + |> List.filter (fun f -> f.Id <> fileId) + |> List.insertAt newIndex file + + { project with SourceFiles = newFiles }) + /// Find a symbol using the provided range, mimicking placing a cursor on it in IDE scenarios [] member this.PlaceCursor(workflow: Async, fileId, line, colAtEndOfNames, fullLine, symbolNames) = @@ -1025,8 +1099,9 @@ type ProjectWorkflowBuilder let project, file = ctx.Project.FindInAllProjects fileId let fileName = project.ProjectDir ++ file.FileName let source = renderSourceFile project file - let options= project.GetProjectOptions checker - return! getSymbolUse fileName source symbolName options checker + let options = project.GetProjectOptions checker + let! snapshot = FSharpProjectSnapshot.FromOptions(options, getFileSnapshot ctx.Project) + return! getSymbolUse fileName source symbolName snapshot checker } /// Find a symbol by finding the first occurrence of the symbol name in the file @@ -1085,7 +1160,10 @@ type ProjectWorkflowBuilder [ for p, f in ctx.Project.GetAllFiles() do let options = p.GetProjectOptions checker for fileName in [getFilePath p f; if f.SignatureFile <> No then getSignatureFilePath p f] do - checker.FindBackgroundReferencesInFile(fileName, options, symbolUse.Symbol, fastCheck = true) ] + async { + let! snapshot = FSharpProjectSnapshot.FromOptions(options, getFileSnapshot ctx.Project) + return! checker.FindBackgroundReferencesInFile(fileName, snapshot, symbolUse.Symbol) + } ] |> Async.Parallel results |> Seq.collect id |> Seq.toList |> processResults @@ -1210,7 +1288,7 @@ type SyntheticProject with projectDir ++ node.Attributes["Include"].InnerText ] |> List.partition (fun path -> path.EndsWith ".fsi") let signatureFiles = set signatureFiles - + let parseReferences refType = [ for node in fsproj.DocumentElement.SelectNodes($"//{refType}") do { Name = node.Attributes["Include"].InnerText diff --git a/tests/FSharp.Test.Utilities/Utilities.fs b/tests/FSharp.Test.Utilities/Utilities.fs index a2a84a5b725..d6dc8d2e6b4 100644 --- a/tests/FSharp.Test.Utilities/Utilities.fs +++ b/tests/FSharp.Test.Utilities/Utilities.fs @@ -15,6 +15,10 @@ open Microsoft.CodeAnalysis.CSharp open TestFramework open NUnit.Framework open System.Collections.Generic +open FSharp.Compiler.CodeAnalysis +open Newtonsoft.Json +open Newtonsoft.Json.Linq + type TheoryForNETCOREAPPAttribute() = inherit Xunit.TheoryAttribute() @@ -381,3 +385,10 @@ An error occurred getting netcoreapp references: %A{e} | TargetFramework.NetStandard20 -> netStandard20Files.Value |> Seq.toArray | TargetFramework.NetCoreApp31 -> [||] //ToDo --- Perhaps NetCoreApp31Files | TargetFramework.Current -> currentReferences + + +module internal FSharpProjectSnapshotSerialization = + + let serializeSnapshotToJson (snapshot: FSharpProjectSnapshot) = + + JsonConvert.SerializeObject(snapshot, Formatting.Indented, new JsonSerializerSettings(ReferenceLoopHandling = ReferenceLoopHandling.Ignore)) \ No newline at end of file diff --git a/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/BackgroundCompilerBenchmarks.fs b/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/BackgroundCompilerBenchmarks.fs index a6ce4b36487..d7d643ab915 100644 --- a/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/BackgroundCompilerBenchmarks.fs +++ b/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/BackgroundCompilerBenchmarks.fs @@ -6,6 +6,7 @@ open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.Text open FSharp.Compiler.Diagnostics open FSharp.Test.ProjectGeneration +open BenchmarkDotNet.Engines [] @@ -217,3 +218,215 @@ type NoFileSystemCheckerBenchmark() = [] member this.Cleanup() = benchmark.DeleteProjectDir() + + + +type TestProjectType = + | DependencyChain = 1 + | DependentGroups = 2 + | ParallelGroups = 3 + + +[] +[] +[] +[] +type TransparentCompilerBenchmark() = + + let size = 30 + + let groups = 6 + let filesPerGroup = size / groups + let somethingToCompile = File.ReadAllText (__SOURCE_DIRECTORY__ ++ "SomethingToCompileSmaller.fs") + + let projects = Map [ + + TestProjectType.DependencyChain, + SyntheticProject.Create("SingleDependencyChain", [| + sourceFile $"File%03d{0}" [] + for i in 1..size do + { sourceFile $"File%03d{i}" [$"File%03d{i-1}"] with ExtraSource = somethingToCompile } + |]) + + TestProjectType.DependentGroups, + SyntheticProject.Create("GroupDependenciesProject", [| + for group in 1..groups do + for i in 1..filesPerGroup do + { sourceFile $"G{group}_F%03d{i}" [ + if group > 1 then $"G1_F%03d{1}" + if i > 1 then $"G{group}_F%03d{i - 1}" ] + with ExtraSource = somethingToCompile } + |]) + + TestProjectType.ParallelGroups, + SyntheticProject.Create("ParallelGroupsProject", [| + for group in 1..groups do + for i in 1..filesPerGroup do + { sourceFile $"G{group}_F%03d{i}" [ + if group > 1 then + for i in 1..filesPerGroup do + $"G{group-1}_F%03d{i}" ] + with ExtraSource = somethingToCompile } + |]) + ] + + let mutable benchmark : ProjectWorkflowBuilder = Unchecked.defaultof<_> + + member val UseGetSource = true with get,set + + member val UseChangeNotifications = true with get,set + + //[] + member val EmptyCache = false with get,set + + [] + member val UseTransparentCompiler = true with get,set + + [] + member val ProjectType = TestProjectType.ParallelGroups with get,set + + member this.Project = projects[this.ProjectType] + + [] + member this.Setup() = + benchmark <- + ProjectWorkflowBuilder( + this.Project, + useGetSource = this.UseGetSource, + useChangeNotifications = this.UseChangeNotifications, + useTransparentCompiler = this.UseTransparentCompiler, + runTimeout = 15_000).CreateBenchmarkBuilder() + + [] + member this.EditFirstFile_OnlyInternalChange() = + if this.EmptyCache then + benchmark.Checker.InvalidateAll() + benchmark.Checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients() + + [] + member this.ExampleWorkflow() = + + use _ = Activity.start "Benchmark" [ + "UseTransparentCompiler", this.UseTransparentCompiler.ToString() + ] + + let first = this.Project.SourceFiles[0].Id + let middle = this.Project.SourceFiles[size / 2].Id + let last = this.Project.SourceFiles |> List.last |> fun f -> f.Id + + benchmark { + updateFile first updatePublicSurface + checkFile first expectSignatureChanged + checkFile last expectSignatureChanged + updateFile middle updatePublicSurface + checkFile last expectOk + addFileAbove middle (sourceFile "addedFile" [first]) + updateFile middle (addDependency "addedFile") + checkFile middle expectSignatureChanged + checkFile last expectOk + } + + [] + member this.Cleanup() = + benchmark.DeleteProjectDir() + + +[] +[] +[] +[] +type TransparentCompilerGiraffeBenchmark() = + + let mutable benchmark : ProjectWorkflowBuilder = Unchecked.defaultof<_> + + let rng = System.Random() + + let addComment s = $"{s}\n// {rng.NextDouble().ToString()}" + let prependSlash s = $"/{s}\n// {rng.NextDouble()}" + + let modify (sourceFile: SyntheticSourceFile) = + { sourceFile with Source = addComment sourceFile.Source } + + let break' (sourceFile: SyntheticSourceFile) = + { sourceFile with Source = prependSlash sourceFile.Source } + + let fix (sourceFile: SyntheticSourceFile) = + { sourceFile with Source = sourceFile.Source.Substring 1 } + + [] + member val UseTransparentCompiler = true with get,set + + [] + member val SignatureFiles = true with get,set + + member this.Project = + let projectDir = if this.SignatureFiles then "Giraffe-signatures" else "Giraffe" + + let project = SyntheticProject.CreateFromRealProject (__SOURCE_DIRECTORY__ ++ ".." ++ ".." ++ ".." ++ ".." ++ ".." ++ projectDir ++ "src/Giraffe") + { project with OtherOptions = "--nowarn:FS3520"::project.OtherOptions } + + [] + member this.Setup() = + benchmark <- + ProjectWorkflowBuilder( + this.Project, + useGetSource = true, + useChangeNotifications = true, + useTransparentCompiler = this.UseTransparentCompiler, + runTimeout = 15_000).CreateBenchmarkBuilder() + + //[] + member this.ChangeFirstCheckLast() = + + use _ = Activity.start "Benchmark" [ + "UseTransparentCompiler", this.UseTransparentCompiler.ToString() + ] + + benchmark { + updateFile this.Project.SourceFiles.Head.Id modify + checkFile (this.Project.SourceFiles |> List.last).Id expectOk + } + + //[] + member this.ChangeSecondCheckLast() = + + use _ = Activity.start "Benchmark" [ + "UseTransparentCompiler", this.UseTransparentCompiler.ToString() + ] + + benchmark { + updateFile this.Project.SourceFiles[1].Id modify + checkFile (this.Project.SourceFiles |> List.last).Id expectOk + } + + [] + member this.SomeWorkflow() = + + use _ = Activity.start "Benchmark" [ + "UseTransparentCompiler", this.UseTransparentCompiler.ToString() + ] + + benchmark { + updateFile "Json" modify + checkFile "Json" expectOk + checkFile "ModelValidation" expectOk + updateFile "ModelValidation" modify + checkFile "ModelValidation" expectOk + updateFile "Xml" modify + checkFile "Xml" expectOk + updateFile "ModelValidation" modify + checkFile "ModelValidation" expectOk + + updateFile "Core" break' + checkFile "Core" expectErrors + checkFile "Routing" (if this.SignatureFiles then expectOk else expectErrors) + updateFile "Routing" modify + checkFile "Streaming" (if this.SignatureFiles then expectOk else expectErrors) + checkFile "EndpointRouting" (if this.SignatureFiles then expectOk else expectErrors) + + updateFile "Core" fix + checkFile "Core" expectOk + checkFile "Routing" expectOk + checkFile "Streaming" expectOk + checkFile "EndpointRouting" expectOk + } \ No newline at end of file diff --git a/tests/service/Common.fs b/tests/service/Common.fs index a8d4782de0d..8516948626a 100644 --- a/tests/service/Common.fs +++ b/tests/service/Common.fs @@ -31,7 +31,7 @@ type Async with task.Result // Create one global interactive checker instance -let checker = FSharpChecker.Create() +let checker = FSharpChecker.Create(useTransparentCompiler=FSharp.Compiler.CompilerConfig.FSharpExperimentalFeaturesEnabledAutomatically) type TempFile(ext, contents: string) = let tmpFile = Path.ChangeExtension(tryCreateTemporaryFileName (), ext) @@ -137,8 +137,8 @@ let mkTestFileAndOptions source additionalArgs = let fileSource1 = "module M" FileSystem.OpenFileForWriteShim(fileName).Write(fileSource1) - let args = Array.append (mkProjectCommandLineArgs (dllName, [fileName])) additionalArgs - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let args = Array.append (mkProjectCommandLineArgs (dllName, [])) additionalArgs + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = [| fileName |] } fileName, options let parseAndCheckFile fileName source options = @@ -158,7 +158,7 @@ let parseAndCheckScriptWithOptions (file:string, input, opts) = let fname = Path.Combine(path, Path.GetFileName(file)) let dllName = Path.ChangeExtension(fname, ".dll") let projName = Path.ChangeExtension(fname, ".fsproj") - let args = mkProjectCommandLineArgsForScript (dllName, [file]) + let args = mkProjectCommandLineArgsForScript (dllName, []) printfn "file = %A, args = %A" file args checker.GetProjectOptionsFromCommandLineArgs (projName, args) @@ -171,7 +171,7 @@ let parseAndCheckScriptWithOptions (file:string, input, opts) = //printfn "projectOptions = %A" projectOptions #endif - let projectOptions = { projectOptions with OtherOptions = Array.append opts projectOptions.OtherOptions } + let projectOptions = { projectOptions with OtherOptions = Array.append opts projectOptions.OtherOptions; SourceFiles = [|file|] } let parseResult, typedRes = checker.ParseAndCheckFileInProject(file, 0, SourceText.ofString input, projectOptions) |> Async.RunImmediate // if parseResult.Errors.Length > 0 then diff --git a/tests/service/ExprTests.fs b/tests/service/ExprTests.fs index 662a60edc80..113931e62ed 100644 --- a/tests/service/ExprTests.fs +++ b/tests/service/ExprTests.fs @@ -364,8 +364,8 @@ let createOptionsAux fileSources extraArgs = Utils.createTempDir() for fileSource: string, fileName in List.zip fileSources fileNames do FileSystem.OpenFileForWriteShim(fileName).Write(fileSource) - let args = [| yield! extraArgs; yield! mkProjectCommandLineArgs (dllName, fileNames) |] - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let args = [| yield! extraArgs; yield! mkProjectCommandLineArgs (dllName, []) |] + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames |> List.toArray } Utils.cleanupTempFiles (fileNames @ [dllName; projFileName]), options @@ -732,11 +732,13 @@ let ignoreTestIfStackOverflowExpected () = #endif /// This test is run in unison with its optimized counterpart below +[] +[] [] -let ``Test Unoptimized Declarations Project1`` () = +let ``Test Unoptimized Declarations Project1`` useTransparentCompiler = let cleanup, options = Project1.createOptionsWithArgs [ "--langversion:preview" ] use _holder = cleanup - let exprChecker = FSharpChecker.Create(keepAssemblyContents=true) + let exprChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=useTransparentCompiler) let wholeProjectResults = exprChecker.ParseAndCheckProject(options) |> Async.RunImmediate for e in wholeProjectResults.Diagnostics do @@ -871,11 +873,13 @@ let ``Test Unoptimized Declarations Project1`` () = () +[] +[] [] -let ``Test Optimized Declarations Project1`` () = +let ``Test Optimized Declarations Project1`` useTransparentCompiler = let cleanup, options = Project1.createOptionsWithArgs [ "--langversion:preview" ] use _holder = cleanup - let exprChecker = FSharpChecker.Create(keepAssemblyContents=true) + let exprChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=useTransparentCompiler) let wholeProjectResults = exprChecker.ParseAndCheckProject(options) |> Async.RunImmediate for e in wholeProjectResults.Diagnostics do @@ -1017,7 +1021,7 @@ let testOperators dnName fsName excludedTests expectedUnoptimized expectedOptimi let filePath = Utils.getTempFilePathChangeExt tempFileName ".fs" let dllPath =Utils.getTempFilePathChangeExt tempFileName ".dll" let projFilePath = Utils.getTempFilePathChangeExt tempFileName ".fsproj" - let exprChecker = FSharpChecker.Create(keepAssemblyContents=true) + let exprChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=true) begin use _cleanup = Utils.cleanupTempFiles [filePath; dllPath; projFilePath] @@ -1027,9 +1031,9 @@ let testOperators dnName fsName excludedTests expectedUnoptimized expectedOptimi let fileSource = excludedTests |> List.fold replace source FileSystem.OpenFileForWriteShim(filePath).Write(fileSource) - let args = mkProjectCommandLineArgsSilent (dllPath, [filePath]) + let args = mkProjectCommandLineArgsSilent (dllPath, []) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFilePath, args) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFilePath, args) with SourceFiles = [|filePath|] } let wholeProjectResults = exprChecker.ParseAndCheckProject(options) |> Async.RunImmediate let referencedAssemblies = wholeProjectResults.ProjectContext.GetReferencedAssemblies() @@ -3206,12 +3210,14 @@ let BigSequenceExpression(outFileOpt,docFileOpt,baseAddressOpt) = let createOptions() = createOptionsAux [fileSource1] [] +[] +[] [] -let ``Test expressions of declarations stress big expressions`` () = +let ``Test expressions of declarations stress big expressions`` useTransparentCompiler = ignoreTestIfStackOverflowExpected () let cleanup, options = ProjectStressBigExpressions.createOptions() use _holder = cleanup - let exprChecker = FSharpChecker.Create(keepAssemblyContents=true) + let exprChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=useTransparentCompiler) let wholeProjectResults = exprChecker.ParseAndCheckProject(options) |> Async.RunImmediate wholeProjectResults.Diagnostics.Length |> shouldEqual 0 @@ -3223,12 +3229,14 @@ let ``Test expressions of declarations stress big expressions`` () = printDeclarations None (List.ofSeq file1.Declarations) |> Seq.toList |> ignore +[] +[] [] -let ``Test expressions of optimized declarations stress big expressions`` () = +let ``Test expressions of optimized declarations stress big expressions`` useTransparentCompiler = ignoreTestIfStackOverflowExpected () let cleanup, options = ProjectStressBigExpressions.createOptions() use _holder = cleanup - let exprChecker = FSharpChecker.Create(keepAssemblyContents=true) + let exprChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=useTransparentCompiler) let wholeProjectResults = exprChecker.ParseAndCheckProject(options) |> Async.RunImmediate wholeProjectResults.Diagnostics.Length |> shouldEqual 0 @@ -3284,11 +3292,13 @@ let f8() = callXY (D()) (C()) let createOptions() = createOptionsAux [fileSource1] ["--langversion:7.0"] +[] +[] [] -let ``Test ProjectForWitnesses1`` () = +let ``Test ProjectForWitnesses1`` useTransparentCompiler = let cleanup, options = ProjectForWitnesses1.createOptions() use _holder = cleanup - let exprChecker = FSharpChecker.Create(keepAssemblyContents=true) + let exprChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=useTransparentCompiler) let wholeProjectResults = exprChecker.ParseAndCheckProject(options) |> Async.RunImmediate for e in wholeProjectResults.Diagnostics do @@ -3328,11 +3338,13 @@ let ``Test ProjectForWitnesses1`` () = |> shouldPairwiseEqual expected +[] +[] [] -let ``Test ProjectForWitnesses1 GetWitnessPassingInfo`` () = +let ``Test ProjectForWitnesses1 GetWitnessPassingInfo`` useTransparentCompiler = let cleanup, options = ProjectForWitnesses1.createOptions() use _holder = cleanup - let exprChecker = FSharpChecker.Create(keepAssemblyContents=true) + let exprChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=useTransparentCompiler) let wholeProjectResults = exprChecker.ParseAndCheckProject(options) |> Async.RunImmediate for e in wholeProjectResults.Diagnostics do @@ -3408,11 +3420,13 @@ type MyNumberWrapper = let createOptions() = createOptionsAux [fileSource1] ["--langversion:7.0"] +[] +[] [] -let ``Test ProjectForWitnesses2`` () = +let ``Test ProjectForWitnesses2`` useTransparentCompiler = let cleanup, options = ProjectForWitnesses2.createOptions() use _holder = cleanup - let exprChecker = FSharpChecker.Create(keepAssemblyContents=true) + let exprChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=useTransparentCompiler) let wholeProjectResults = exprChecker.ParseAndCheckProject(options) |> Async.RunImmediate for e in wholeProjectResults.Diagnostics do @@ -3465,11 +3479,13 @@ let s2 = sign p1 let createOptions() = createOptionsAux [fileSource1] ["--langversion:7.0"] +[] +[] [] -let ``Test ProjectForWitnesses3`` () = +let ``Test ProjectForWitnesses3`` useTransparentCompiler = let cleanup, options = createOptionsAux [ ProjectForWitnesses3.fileSource1 ] ["--langversion:7.0"] use _holder = cleanup - let exprChecker = FSharpChecker.Create(keepAssemblyContents=true) + let exprChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=useTransparentCompiler) let wholeProjectResults = exprChecker.ParseAndCheckProject(options) |> Async.RunImmediate for e in wholeProjectResults.Diagnostics do @@ -3496,11 +3512,13 @@ let ``Test ProjectForWitnesses3`` () = actual |> shouldPairwiseEqual expected +[] +[] [] -let ``Test ProjectForWitnesses3 GetWitnessPassingInfo`` () = +let ``Test ProjectForWitnesses3 GetWitnessPassingInfo`` useTransparentCompiler = let cleanup, options = ProjectForWitnesses3.createOptions() use _holder = cleanup - let exprChecker = FSharpChecker.Create(keepAssemblyContents=true) + let exprChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=useTransparentCompiler) let wholeProjectResults = exprChecker.ParseAndCheckProject(options) |> Async.RunImmediate for e in wholeProjectResults.Diagnostics do @@ -3559,11 +3577,13 @@ let isNullQuoted (ts : 't[]) = let createOptions() = createOptionsAux [fileSource1] ["--langversion:7.0"] +[] +[] [] -let ``Test ProjectForWitnesses4 GetWitnessPassingInfo`` () = +let ``Test ProjectForWitnesses4 GetWitnessPassingInfo`` useTransparentCompiler = let cleanup, options = ProjectForWitnesses4.createOptions() use _holder = cleanup - let exprChecker = FSharpChecker.Create(keepAssemblyContents=true) + let exprChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=useTransparentCompiler) let wholeProjectResults = exprChecker.ParseAndCheckProject(options) |> Async.RunImmediate for e in wholeProjectResults.Diagnostics do diff --git a/tests/service/ModuleReaderCancellationTests.fs b/tests/service/ModuleReaderCancellationTests.fs index 434ddca02db..a401c637fe6 100644 --- a/tests/service/ModuleReaderCancellationTests.fs +++ b/tests/service/ModuleReaderCancellationTests.fs @@ -135,7 +135,7 @@ let createPreTypeDefs typeData = |> Array.ofList |> Array.map (fun data -> PreTypeDef data :> ILPreTypeDef) -let referenceReaderProject getPreTypeDefs (cancelOnModuleAccess: bool) options = +let referenceReaderProject getPreTypeDefs (cancelOnModuleAccess: bool) (options: FSharpProjectOptions) = let reader = new ModuleReader("Reference", mkILTypeDefsComputed getPreTypeDefs, cancelOnModuleAccess) let project = FSharpReferencedProject.ILModuleReference( diff --git a/tests/service/MultiProjectAnalysisTests.fs b/tests/service/MultiProjectAnalysisTests.fs index eab86f98324..a306107eb36 100644 --- a/tests/service/MultiProjectAnalysisTests.fs +++ b/tests/service/MultiProjectAnalysisTests.fs @@ -23,6 +23,7 @@ open TestFramework let toIList (x: _ array) = x :> IList<_> let numProjectsForStressTest = 100 let internal checker = FSharpChecker.Create(projectCacheSize=numProjectsForStressTest + 10) +let internal transparentCompilerChecker = FSharpChecker.Create(projectCacheSize=numProjectsForStressTest + 10, useTransparentCompiler=true) /// Extract range info let internal tups (m:range) = (m.StartLine, m.StartColumn), (m.EndLine, m.EndColumn) @@ -67,9 +68,9 @@ type U = let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] + let fileNames = [|fileName1|] let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } @@ -95,9 +96,9 @@ let x = let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] + let fileNames = [|fileName1|] let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } // A project referencing two sub-projects @@ -120,11 +121,12 @@ let u = Case1 3 """ FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) - let fileNames = [fileName1] + let fileNames = [|fileName1|] let args = mkProjectCommandLineArgs (dllName, fileNames) let options = - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } { options with + SourceFiles = fileNames OtherOptions = Array.append options.OtherOptions [| ("-r:" + Project1A.dllName); ("-r:" + Project1B.dllName) |] ReferencedProjects = [| FSharpReferencedProject.FSharpReference(Project1A.dllName, Project1A.options); FSharpReferencedProject.FSharpReference(Project1B.dllName, Project1B.options); |] } @@ -144,7 +146,11 @@ let ``Test multi project 1 basic`` () = |> shouldEqual ["p"; "c"; "u"] [] -let ``Test multi project 1 all symbols`` () = +[] +[] +let ``Test multi project 1 all symbols`` useTransparentCompiler = + + let checker = if useTransparentCompiler then transparentCompilerChecker else checker let p1A = checker.ParseAndCheckProject(Project1A.options) |> Async.RunImmediate let p1B = checker.ParseAndCheckProject(Project1B.options) |> Async.RunImmediate @@ -182,7 +188,11 @@ let ``Test multi project 1 all symbols`` () = usesOfx1FromProject1AInMultiProject1 |> shouldEqual usesOfx1FromMultiProject1InMultiProject1 [] -let ``Test multi project 1 xmldoc`` () = +[] +[] +let ``Test multi project 1 xmldoc`` useTransparentCompiler = + + let checker = if useTransparentCompiler then transparentCompilerChecker else checker let p1A = checker.ParseAndCheckProject(Project1A.options) |> Async.RunImmediate let p1B = checker.ParseAndCheckProject(Project1B.options) |> Async.RunImmediate @@ -284,9 +294,9 @@ let p = C.Print() let baseName = tryCreateTemporaryFileName () let dllName = Path.ChangeExtension(baseName, ".dll") let projFileName = Path.ChangeExtension(baseName, ".fsproj") - let fileNames = [fileName1 ] + let fileNames = [|fileName1|] let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } yield { ModuleName = moduleName; FileName=fileName1; Options = options; DllName=dllName } ] let jointProject = @@ -305,10 +315,10 @@ let p = (""" + String.concat ",\r\n " [ for p in projects -> p.ModuleName + ".v" ] + ")" FileSystem.OpenFileForWriteShim(fileName).Write(fileSource) - let fileNames = [fileName] + let fileNames = [|fileName|] let args = mkProjectCommandLineArgs (dllName, fileNames) let options = - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } { options with OtherOptions = Array.append options.OtherOptions [| for p in projects -> ("-r:" + p.DllName) |] ReferencedProjects = [| for p in projects -> FSharpReferencedProject.FSharpReference(p.DllName, p.Options); |] } @@ -319,14 +329,16 @@ let p = (""" |> function Some x -> x | None -> if a = jointProject.FileName then "fileN" else "??" - let makeCheckerForStressTest ensureBigEnough = + let makeCheckerForStressTest ensureBigEnough useTransparentCompiler = let size = (if ensureBigEnough then numProjectsForStressTest + 10 else numProjectsForStressTest / 2 ) - FSharpChecker.Create(projectCacheSize=size) + FSharpChecker.Create(projectCacheSize=size, useTransparentCompiler=useTransparentCompiler) [] -let ``Test ManyProjectsStressTest basic`` () = +[] +[] +let ``Test ManyProjectsStressTest basic`` useTransparentCompiler = - let checker = ManyProjectsStressTest.makeCheckerForStressTest true + let checker = ManyProjectsStressTest.makeCheckerForStressTest true useTransparentCompiler let wholeProjectResults = checker.ParseAndCheckProject(ManyProjectsStressTest.jointProject.Options) |> Async.RunImmediate @@ -338,9 +350,11 @@ let ``Test ManyProjectsStressTest basic`` () = |> shouldEqual ["p"] [] -let ``Test ManyProjectsStressTest cache too small`` () = +[] +[] +let ``Test ManyProjectsStressTest cache too small`` useTransparentCompiler = - let checker = ManyProjectsStressTest.makeCheckerForStressTest false + let checker = ManyProjectsStressTest.makeCheckerForStressTest false useTransparentCompiler let wholeProjectResults = checker.ParseAndCheckProject(ManyProjectsStressTest.jointProject.Options) |> Async.RunImmediate @@ -352,9 +366,11 @@ let ``Test ManyProjectsStressTest cache too small`` () = |> shouldEqual ["p"] [] -let ``Test ManyProjectsStressTest all symbols`` () = +[] +[] +let ``Test ManyProjectsStressTest all symbols`` useTransparentCompiler = - let checker = ManyProjectsStressTest.makeCheckerForStressTest true + let checker = ManyProjectsStressTest.makeCheckerForStressTest true useTransparentCompiler for i in 1 .. 10 do printfn "stress test iteration %d (first may be slow, rest fast)" i let projectsResults = [ for p in ManyProjectsStressTest.projects -> p, checker.ParseAndCheckProject(p.Options) |> Async.RunImmediate ] @@ -397,11 +413,11 @@ let x = "F#" let cleanFileName a = if a = fileName1 then "Project1" else "??" - let fileNames = [fileName1] + let fileNames = [|fileName1|] let getOptions() = let args = mkProjectCommandLineArgs (dllName, fileNames) - checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } module internal MultiProjectDirty2 = @@ -422,17 +438,21 @@ let z = Project1.x let cleanFileName a = if a = fileName1 then "Project2" else "??" - let fileNames = [fileName1] + let fileNames = [|fileName1|] let getOptions() = let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } { options with OtherOptions = Array.append options.OtherOptions [| ("-r:" + MultiProjectDirty1.dllName) |] ReferencedProjects = [| FSharpReferencedProject.FSharpReference(MultiProjectDirty1.dllName, MultiProjectDirty1.getOptions()) |] } [] -let ``Test multi project symbols should pick up changes in dependent projects`` () = +[] +[] +let ``Test multi project symbols should pick up changes in dependent projects`` useTransparentCompiler = + + let checker = if useTransparentCompiler then transparentCompilerChecker else checker // register to count the file checks let count = ref 0 @@ -614,9 +634,9 @@ type C() = let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] + let fileNames = [|fileName1|] let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } //Project2A.fileSource1 // A project referencing Project2A @@ -633,10 +653,10 @@ let v = Project2A.C().InternalMember // access an internal symbol """ FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, [||]) let options = - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } { options with OtherOptions = Array.append options.OtherOptions [| ("-r:" + Project2A.dllName); |] ReferencedProjects = [| FSharpReferencedProject.FSharpReference(Project2A.dllName, Project2A.options); |] } @@ -657,17 +677,21 @@ let v = Project2A.C().InternalMember // access an internal symbol """ FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, [||]) let options = - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } { options with OtherOptions = Array.append options.OtherOptions [| ("-r:" + Project2A.dllName); |] ReferencedProjects = [| FSharpReferencedProject.FSharpReference(Project2A.dllName, Project2A.options); |] } let cleanFileName a = if a = fileName1 then "file1" else "??" [] -let ``Test multi project2 errors`` () = +[] +[] +let ``Test multi project2 errors`` useTransparentCompiler = + + let checker = if useTransparentCompiler then transparentCompilerChecker else checker let wholeProjectResults = checker.ParseAndCheckProject(Project2B.options) |> Async.RunImmediate for e in wholeProjectResults.Diagnostics do @@ -725,9 +749,9 @@ let (|DivisibleBy|_|) by n = let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] + let fileNames = [|fileName1|] let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } // A project referencing a sub-project @@ -750,17 +774,22 @@ let fizzBuzz = function """ FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, [||]) let options = - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } { options with + SourceFiles = fileNames OtherOptions = Array.append options.OtherOptions [| ("-r:" + Project3A.dllName) |] ReferencedProjects = [| FSharpReferencedProject.FSharpReference(Project3A.dllName, Project3A.options) |] } let cleanFileName a = if a = fileName1 then "file1" else "??" [] -let ``Test multi project 3 whole project errors`` () = +[] +[] +let ``Test multi project 3 whole project errors`` useTransparentCompiler = + + let checker = if useTransparentCompiler then transparentCompilerChecker else checker let wholeProjectResults = checker.ParseAndCheckProject(MultiProject3.options) |> Async.RunImmediate for e in wholeProjectResults.Diagnostics do @@ -769,7 +798,11 @@ let ``Test multi project 3 whole project errors`` () = wholeProjectResults.Diagnostics.Length |> shouldEqual 0 [] -let ``Test active patterns' XmlDocSig declared in referenced projects`` () = +[] +[] +let ``Test active patterns' XmlDocSig declared in referenced projects`` useTransparentCompiler = + + let checker = if useTransparentCompiler then transparentCompilerChecker else checker let wholeProjectResults = checker.ParseAndCheckProject(MultiProject3.options) |> Async.RunImmediate let backgroundParseResults1, backgroundTypedParse1 = diff --git a/tests/service/ProjectAnalysisTests.fs b/tests/service/ProjectAnalysisTests.fs index dab349fd0ab..93b746e7d24 100644 --- a/tests/service/ProjectAnalysisTests.fs +++ b/tests/service/ProjectAnalysisTests.fs @@ -90,9 +90,10 @@ let mmmm2 : M.CAbbrev = new M.CAbbrev() // note, these don't count as uses of C FileSystem.OpenFileForWriteShim(fileName2).Write(fileSource2Text) let fileNames = [fileName1; fileName2] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) - let parsingOptions, _ = checker.GetParsingOptionsFromCommandLineArgs(List.ofArray args) + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames |> List.toArray } + let parsingOptions', _ = checker.GetParsingOptionsFromCommandLineArgs(List.ofArray args) + let parsingOptions = { parsingOptions' with SourceFiles = fileNames |> List.toArray } let cleanFileName a = if a = fileName1 then "file1" else if a = fileName2 then "file2" else "??" [] @@ -675,9 +676,9 @@ let _ = GenericFunction(3, 4) """ FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } @@ -928,9 +929,9 @@ let getM (foo: IFoo) = foo.InterfaceMethod("d") """ FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } @@ -1297,9 +1298,9 @@ let inline twice(x : ^U, y : ^U) = x + y """ FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } @@ -1471,9 +1472,9 @@ let parseNumeric str = let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] @@ -1683,9 +1684,9 @@ let f () = let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] @@ -1739,9 +1740,9 @@ let x2 = C.M(arg1 = 3, arg2 = 4, ?arg3 = Some 5) let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] @@ -1800,9 +1801,9 @@ let x = let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] @@ -1880,9 +1881,9 @@ let inline check< ^T when ^T : (static member IsInfinity : ^T -> bool)> (num: ^T let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] @@ -1959,9 +1960,9 @@ C.M("http://goo", query = 1) let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] @@ -2039,9 +2040,9 @@ let fff (x:System.Collections.Generic.Dictionary.Enumerator) = () let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] @@ -2108,9 +2109,9 @@ let x2 = query { for i in 0 .. 100 do let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] @@ -2175,9 +2176,9 @@ let x3 = new System.DateTime() let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] @@ -2334,9 +2335,9 @@ let x2 = S(3) let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] @@ -2401,9 +2402,9 @@ let f x = let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] @@ -2488,9 +2489,9 @@ and G = Case1 | Case2 of int FileSystem.OpenFileForWriteShim(sigFileName1).Write(sigFileSource1Text) let cleanFileName a = if a = fileName1 then "file1" elif a = sigFileName1 then "sig1" else "??" - let fileNames = [sigFileName1; fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|sigFileName1; fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] @@ -2754,9 +2755,9 @@ let f3 (x: System.Exception) = x.HelpLink <- "" // check use of .NET setter prop FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] @@ -2841,9 +2842,9 @@ let _ = list<_>.Empty FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] @@ -2897,9 +2898,9 @@ let s = System.DayOfWeek.Monday FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] @@ -2972,9 +2973,9 @@ type A<'T>() = FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] @@ -3033,9 +3034,9 @@ let _ = { new IMyInterface with FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] @@ -3107,9 +3108,9 @@ let f5 (x: int[,,]) = () // test a multi-dimensional array FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } @@ -3254,9 +3255,9 @@ module Setter = FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] let ``Test Project23 whole project errors`` () = @@ -3425,9 +3426,9 @@ TypeWithProperties.StaticAutoPropGetSet <- 3 FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] let ``Test Project24 whole project errors`` () = @@ -3682,12 +3683,12 @@ let _ = XmlProvider<"13">.GetSample() FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] + let fileNames = [|fileName1|] let args = - [| yield! mkProjectCommandLineArgs (dllName, fileNames) + [| yield! mkProjectCommandLineArgs (dllName, []) yield @"-r:" + Path.Combine(__SOURCE_DIRECTORY__, Path.Combine("data", "FSharp.Data.dll")) yield @"-r:" + sysLib "System.Xml.Linq" |] - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] #if NETCOREAPP @@ -3822,9 +3823,9 @@ type Class() = let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] @@ -3912,9 +3913,9 @@ type CFooImpl() = """ FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] let ``Test project27 whole project errors`` () = @@ -3977,9 +3978,9 @@ type Use() = """ FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } #if !NO_TYPEPROVIDERS [] let ``Test project28 all symbols in signature`` () = @@ -4055,9 +4056,9 @@ let f (x: INotifyPropertyChanged) = failwith "" """ FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] @@ -4114,9 +4115,9 @@ type T() = """ FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } let ``Test project30 whole project errors`` () = @@ -4174,10 +4175,9 @@ let g = Console.ReadKey() """ FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } let ``Test project31 whole project errors`` () = let wholeProjectResults = checker.ParseAndCheckProject(Project31.options) |> Async.RunImmediate @@ -4317,9 +4317,9 @@ val func : int -> int FileSystem.OpenFileForWriteShim(sigFileName1).Write(sigFileSource1) let cleanFileName a = if a = fileName1 then "file1" elif a = sigFileName1 then "sig1" else "??" - let fileNames = [sigFileName1; fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|sigFileName1; fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] @@ -4385,9 +4385,9 @@ type System.Int32 with FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] let ``Test Project33 whole project errors`` () = @@ -4424,17 +4424,17 @@ module internal Project34 = FileSystem.OpenFileForWriteShim(sourceFileName).Write(fileSource) let cleanFileName a = if a = sourceFileName then "file1" else "??" - let fileNames = [sourceFileName] + let fileNames = [|sourceFileName|] let args = [| - yield! mkProjectCommandLineArgs (dllName, fileNames) + yield! mkProjectCommandLineArgs (dllName, []) // We use .NET-built version of System.Data.dll since the tests depend on implementation details // i.e. the private type System.Data.Listeners may not be available on Mono. yield @"-r:" + Path.Combine(__SOURCE_DIRECTORY__, Path.Combine("data", "System.Data.dll")) |] |> Array.filter(fun arg -> not((arg.Contains("System.Data")) && not (arg.Contains(@"service\data\System.Data.dll")))) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] let ``Test Project34 whole project errors`` () = @@ -4502,9 +4502,9 @@ type Test = FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] @@ -4574,13 +4574,13 @@ module internal Project35b = FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1Text) let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] + let fileNames = [|fileName1|] #if NETCOREAPP let projPath = Path.ChangeExtension(fileName1, ".fsproj") let dllPath = Path.ChangeExtension(fileName1, ".dll") let args = mkProjectCommandLineArgs(dllPath, fileNames) let args2 = Array.append args [| "-r:notexist.dll" |] - let options = checker.GetProjectOptionsFromCommandLineArgs (projPath, args2) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projPath, args2) with SourceFiles = fileNames } #else let options = checker.GetProjectOptionsFromScript(fileName1, fileSource1) |> Async.RunImmediate |> fst #endif @@ -4641,13 +4641,15 @@ let callToOverload = B(5).Overload(4) FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) let cleanFileName a = if a = fileName1 then "file1" else "??" - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) [] -let ``Test project36 FSharpMemberOrFunctionOrValue.IsBaseValue`` () = - let keepAssemblyContentsChecker = FSharpChecker.Create(keepAssemblyContents=true) - let options = keepAssemblyContentsChecker.GetProjectOptionsFromCommandLineArgs (Project36.projFileName, Project36.args) +[] +[] +let ``Test project36 FSharpMemberOrFunctionOrValue.IsBaseValue`` useTransparentCompiler = + let keepAssemblyContentsChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=useTransparentCompiler) + let options = { keepAssemblyContentsChecker.GetProjectOptionsFromCommandLineArgs (Project36.projFileName, Project36.args) with SourceFiles = Project36.fileNames } let wholeProjectResults = keepAssemblyContentsChecker.ParseAndCheckProject(options) |> Async.RunImmediate @@ -4660,9 +4662,11 @@ let ``Test project36 FSharpMemberOrFunctionOrValue.IsBaseValue`` () = |> fun baseSymbol -> shouldEqual true baseSymbol.IsBaseValue [] -let ``Test project36 FSharpMemberOrFunctionOrValue.IsConstructorThisValue & IsMemberThisValue`` () = - let keepAssemblyContentsChecker = FSharpChecker.Create(keepAssemblyContents=true) - let options = keepAssemblyContentsChecker.GetProjectOptionsFromCommandLineArgs (Project36.projFileName, Project36.args) +[] +[] +let ``Test project36 FSharpMemberOrFunctionOrValue.IsConstructorThisValue & IsMemberThisValue`` useTransparentCompiler = + let keepAssemblyContentsChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=useTransparentCompiler) + let options = { keepAssemblyContentsChecker.GetProjectOptionsFromCommandLineArgs (Project36.projFileName, Project36.args) with SourceFiles = Project36.fileNames } let wholeProjectResults = keepAssemblyContentsChecker.ParseAndCheckProject(options) |> Async.RunImmediate let declarations = let checkedFile = wholeProjectResults.AssemblyContents.ImplementationFiles[0] @@ -4697,9 +4701,11 @@ let ``Test project36 FSharpMemberOrFunctionOrValue.IsConstructorThisValue & IsMe |> shouldEqual true [] -let ``Test project36 FSharpMemberOrFunctionOrValue.LiteralValue`` () = - let keepAssemblyContentsChecker = FSharpChecker.Create(keepAssemblyContents=true) - let options = keepAssemblyContentsChecker.GetProjectOptionsFromCommandLineArgs (Project36.projFileName, Project36.args) +[] +[] +let ``Test project36 FSharpMemberOrFunctionOrValue.LiteralValue`` useTransparentCompiler = + let keepAssemblyContentsChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=useTransparentCompiler) + let options = { keepAssemblyContentsChecker.GetProjectOptionsFromCommandLineArgs (Project36.projFileName, Project36.args) with SourceFiles = Project36.fileNames } let wholeProjectResults = keepAssemblyContentsChecker.ParseAndCheckProject(options) |> Async.RunImmediate let project36Module = wholeProjectResults.AssemblySignature.Entities[0] let lit = project36Module.MembersFunctionsAndValues[0] @@ -4760,9 +4766,9 @@ namespace AttrTests do () """ FileSystem.OpenFileForWriteShim(fileName2).Write(fileSource2) - let fileNames = [fileName1; fileName2] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1; fileName2|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] let ``Test project37 typeof and arrays in attribute constructor arguments`` () = @@ -4902,9 +4908,9 @@ type A<'XX, 'YY>() = member this.Property = 1 """ FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] let ``Test project38 abstract slot information`` () = @@ -4988,9 +4994,9 @@ let uses () = C().CurriedMemberWithIncompleteSignature (failwith "x1") (failwith "x2") (failwith "x3", failwith "x4") """ FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } let cleanFileName a = if a = fileName1 then "file1" else "??" [] @@ -5063,9 +5069,9 @@ let g (x: C) = x.IsItAnA,x.IsItAnAMethod() """ FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } let cleanFileName a = if a = fileName1 then "file1" else "??" [] @@ -5134,9 +5140,9 @@ module M if true then Foo else Bar """ FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } let cleanFileName a = if a = fileName1 then "file1" else "??" [] @@ -5226,9 +5232,9 @@ open File1 let test2() = test() """ FileSystem.OpenFileForWriteShim(fileName2).Write(fileSource2) - let fileNames = [fileName1;fileName2] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1;fileName2|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] let ``Test project42 to ensure cached checked results are invalidated`` () = @@ -5261,21 +5267,21 @@ module internal ProjectBig = let fileSources2 = [ for i,f in fileSources -> SourceText.ofString f ] let fileNames = [ for _,f in fileNamesI -> f ] - let args = mkProjectCommandLineArgs (dllName, fileNames) + let args = mkProjectCommandLineArgs (dllName, []) let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) - let parsingOptions, _ = checker.GetParsingOptionsFromCommandLineArgs(List.ofArray args) - + let parsingOptions', _ = checker.GetParsingOptionsFromCommandLineArgs(List.ofArray args) + let parsingOptions = { parsingOptions' with SourceFiles = fileNames |> List.toArray } [] // Simplified repro for https://github.com/dotnet/fsharp/issues/2679 let ``add files with same name from different folders`` () = let fileNames = - [ __SOURCE_DIRECTORY__ + "/data/samename/folder1/a.fs" - __SOURCE_DIRECTORY__ + "/data/samename/folder2/a.fs" ] + [| __SOURCE_DIRECTORY__ + "/data/samename/folder1/a.fs" + __SOURCE_DIRECTORY__ + "/data/samename/folder2/a.fs" |] let projFileName = __SOURCE_DIRECTORY__ + "/data/samename/tempet.fsproj" let args = mkProjectCommandLineArgs ("test.dll", fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } let wholeProjectResults = checker.ParseAndCheckProject(options) |> Async.RunImmediate let errors = wholeProjectResults.Diagnostics @@ -5308,13 +5314,15 @@ let foo (a: Foo): bool = """ FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] -let ``Test typed AST for struct unions`` () = // See https://github.com/fsharp/FSharp.Compiler.Service/issues/756 - let keepAssemblyContentsChecker = FSharpChecker.Create(keepAssemblyContents=true) +[] +[] +let ``Test typed AST for struct unions`` useTransparentCompiler = // See https://github.com/fsharp/FSharp.Compiler.Service/issues/756 + let keepAssemblyContentsChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=useTransparentCompiler) let wholeProjectResults = keepAssemblyContentsChecker.ParseAndCheckProject(ProjectStructUnions.options) |> Async.RunImmediate let declarations = @@ -5350,9 +5358,9 @@ let x = (1 = 3.0) """ let fileSource1 = SourceText.ofString fileSource1Text FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1Text) - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [| fileName1 |] + let args = mkProjectCommandLineArgs (dllName, []) + let options = { checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } [] let ``Test diagnostics with line directives active`` () = @@ -5401,7 +5409,9 @@ let ``Test diagnostics with line directives ignored`` () = //------------------------------------------------------ [] -let ``ParseAndCheckFileResults contains ImplFile list if FSharpChecker is created with keepAssemblyContent flag set to true``() = +[] +[] +let ``ParseAndCheckFileResults contains ImplFile list if FSharpChecker is created with keepAssemblyContent flag set to true`` useTransparentCompiler = let fileName1 = Path.ChangeExtension(tryCreateTemporaryFileName (), ".fs") let base2 = tryCreateTemporaryFileName () @@ -5414,10 +5424,10 @@ type A(i:int) = let fileSource1 = SourceText.ofString fileSource1Text FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1Text) - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let keepAssemblyContentsChecker = FSharpChecker.Create(keepAssemblyContents=true) - let options = keepAssemblyContentsChecker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let keepAssemblyContentsChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=useTransparentCompiler) + let options = { keepAssemblyContentsChecker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } let fileCheckResults = keepAssemblyContentsChecker.ParseAndCheckFileInProject(fileName1, 0, fileSource1, options) |> Async.RunImmediate @@ -5459,7 +5469,9 @@ let ``#4030, Incremental builder creation warnings`` (args, errorSeverities) = //------------------------------------------------------ [] -let ``Unused opens in rec module smoke test 1``() = +[] +[] +let ``Unused opens in rec module smoke test 1`` useTransparentCompiler = let fileName1 = Path.ChangeExtension(tryCreateTemporaryFileName (), ".fs") let base2 = tryCreateTemporaryFileName () @@ -5505,10 +5517,10 @@ type UseTheThings(i:int) = let fileSource1 = SourceText.ofString fileSource1Text FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1Text) - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let keepAssemblyContentsChecker = FSharpChecker.Create(keepAssemblyContents=true) - let options = keepAssemblyContentsChecker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let keepAssemblyContentsChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=useTransparentCompiler) + let options = { keepAssemblyContentsChecker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } let fileCheckResults = keepAssemblyContentsChecker.ParseAndCheckFileInProject(fileName1, 0, fileSource1, options) |> Async.RunImmediate @@ -5532,7 +5544,9 @@ type UseTheThings(i:int) = unusedOpensData |> shouldEqual expected [] -let ``Unused opens in non rec module smoke test 1``() = +[] +[] +let ``Unused opens in non rec module smoke test 1`` useTransparentCompiler = let fileName1 = Path.ChangeExtension(tryCreateTemporaryFileName (), ".fs") let base2 = tryCreateTemporaryFileName () @@ -5578,10 +5592,10 @@ type UseTheThings(i:int) = let fileSource1 = SourceText.ofString fileSource1Text FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1Text) - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let keepAssemblyContentsChecker = FSharpChecker.Create(keepAssemblyContents=true) - let options = keepAssemblyContentsChecker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let keepAssemblyContentsChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=useTransparentCompiler) + let options = { keepAssemblyContentsChecker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } let fileCheckResults = keepAssemblyContentsChecker.ParseAndCheckFileInProject(fileName1, 0, fileSource1, options) |> Async.RunImmediate @@ -5605,7 +5619,9 @@ type UseTheThings(i:int) = unusedOpensData |> shouldEqual expected [] -let ``Unused opens smoke test auto open``() = +[] +[] +let ``Unused opens smoke test auto open`` useTransparentCompiler = let fileName1 = Path.ChangeExtension(tryCreateTemporaryFileName (), ".fs") let base2 = tryCreateTemporaryFileName () @@ -5659,10 +5675,10 @@ module M2 = let fileSource1 = SourceText.ofString fileSource1Text FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1Text) - let fileNames = [fileName1] - let args = mkProjectCommandLineArgs (dllName, fileNames) - let keepAssemblyContentsChecker = FSharpChecker.Create(keepAssemblyContents=true) - let options = keepAssemblyContentsChecker.GetProjectOptionsFromCommandLineArgs (projFileName, args) + let fileNames = [|fileName1|] + let args = mkProjectCommandLineArgs (dllName, []) + let keepAssemblyContentsChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=useTransparentCompiler) + let options = { keepAssemblyContentsChecker.GetProjectOptionsFromCommandLineArgs (projFileName, args) with SourceFiles = fileNames } let fileCheckResults = keepAssemblyContentsChecker.ParseAndCheckFileInProject(fileName1, 0, fileSource1, options) |> Async.RunImmediate diff --git a/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs b/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs index 1cd71d4228e..0326688f987 100644 --- a/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs +++ b/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs @@ -1107,6 +1107,16 @@ module CancellableTasks = return! Task.WhenAll (tasks) } + let inline sequential (tasks: CancellableTask<'a> seq) = + cancellableTask { + let! ct = getCancellationToken () + let results = ResizeArray() + for task in tasks do + let! result = start ct task + results.Add(result) + return results + } + let inline ignore ([] ctask: CancellableTask<_>) = toUnit ctask /// diff --git a/vsintegration/src/FSharp.Editor/Common/Extensions.fs b/vsintegration/src/FSharp.Editor/Common/Extensions.fs index eb2f61c4147..b0eb7305713 100644 --- a/vsintegration/src/FSharp.Editor/Common/Extensions.fs +++ b/vsintegration/src/FSharp.Editor/Common/Extensions.fs @@ -87,7 +87,7 @@ module private SourceText = let combineValues (values: seq<'T>) = (0, values) ||> Seq.fold (fun hash value -> combine (value.GetHashCode()) hash) - let weakTable = ConditionalWeakTable() + let weakTable = ConditionalWeakTable() let create (sourceText: SourceText) = let sourceText = @@ -111,7 +111,9 @@ module private SourceText = |> Hash.combine encodingHash |> Hash.combine contentsHash |> Hash.combine sourceText.Length - interface ISourceText with + + override _.ToString() = sourceText.ToString() + interface ISourceTextNew with member _.Item with get index = sourceText.[index] @@ -197,6 +199,8 @@ module private SourceText = let lastLine = this.GetLineString(range.EndLine - 1) sb.Append(lastLine.Substring(0, range.EndColumn)).ToString() + + member _.GetChecksum() = sourceText.GetChecksum() } sourceText diff --git a/vsintegration/src/FSharp.Editor/Common/Logging.fs b/vsintegration/src/FSharp.Editor/Common/Logging.fs index 0ba681dc589..a69779e9acc 100644 --- a/vsintegration/src/FSharp.Editor/Common/Logging.fs +++ b/vsintegration/src/FSharp.Editor/Common/Logging.fs @@ -93,7 +93,10 @@ module Logging = let inline debug msg = Printf.kprintf Debug.WriteLine msg let private logger = lazy Logger(Logger.GlobalServiceProvider) - let private log logType msg = logger.Value.Log(logType, msg) + + let private log logType msg = + logger.Value.Log(logType, msg) + System.Diagnostics.Trace.TraceInformation(msg) let logMsg msg = log LogType.Message msg let logInfo msg = log LogType.Info msg diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx index ef5f05b3d24..6678a2d98e3 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx @@ -227,6 +227,7 @@ Inline hints; Display inline type hints (preview); Display return type hints (preview); Display inline parameter name hints (preview); +Use Transparent Compiler (experimental); Live Buffers; Use live (unsaved) buffers for analysis diff --git a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs index 301428f69cb..b492cf1f35f 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs @@ -25,6 +25,7 @@ open Microsoft.CodeAnalysis.ExternalAccess.FSharp open Microsoft.CodeAnalysis.Host.Mef open Microsoft.VisualStudio.FSharp.Editor.Telemetry open CancellableTasks +open FSharp.Compiler.Text #nowarn "9" // NativePtr.toNativeInt #nowarn "57" // Experimental stuff @@ -147,6 +148,8 @@ type internal FSharpWorkspaceServiceFactory [] let enableBackgroundItemKeyStoreAndSemanticClassification = editorOptions.LanguageServicePerformance.EnableBackgroundItemKeyStoreAndSemanticClassification + let useTransparentCompiler = editorOptions.Advanced.UseTransparentCompiler + // Default is false here let solutionCrawler = editorOptions.Advanced.SolutionBackgroundAnalysis @@ -168,6 +171,7 @@ type internal FSharpWorkspaceServiceFactory [] nameof enableBackgroundItemKeyStoreAndSemanticClassification, enableBackgroundItemKeyStoreAndSemanticClassification "captureIdentifiersWhenParsing", enableFastFindReferences + nameof useTransparentCompiler, useTransparentCompiler nameof solutionCrawler, solutionCrawler |], TelemetryThrottlingStrategy.NoThrottling @@ -187,13 +191,19 @@ type internal FSharpWorkspaceServiceFactory [] captureIdentifiersWhenParsing = enableFastFindReferences, documentSource = (if enableLiveBuffers then - DocumentSource.Custom getSource + (DocumentSource.Custom(fun filename -> + async { + match! getSource filename with + | Some source -> return Some(source :> ISourceText) + | None -> return None + })) else DocumentSource.FileSystem), - useSyntaxTreeCache = useSyntaxTreeCache + useSyntaxTreeCache = useSyntaxTreeCache, + useTransparentCompiler = useTransparentCompiler ) - if enableLiveBuffers then + if enableLiveBuffers && not useTransparentCompiler then workspace.WorkspaceChanged.Add(fun args -> if args.DocumentId <> null then cancellableTask { @@ -481,10 +491,10 @@ type internal HackCpsCommandLineChanges else Path.GetFileNameWithoutExtension projectFileName - [] /// This handles commandline change notifications from the Dotnet Project-system /// Prior to VS 15.7 path contained path to project file, post 15.7 contains target binpath /// binpath is more accurate because a project file can have multiple in memory projects based on configuration + [] member _.HandleCommandLineChanges ( path: string, @@ -527,10 +537,10 @@ type internal HackCpsCommandLineChanges let sourcePaths = sources |> Seq.map (fun s -> getFullPath s.Path) |> Seq.toArray - /// Due to an issue in project system, when we close and reopen solution, it sends the CommandLineChanges twice for every project. - /// First time it sends a correct path, sources, references and options. - /// Second time it sends a correct path, empty sources, empty references and empty options, and we rewrite our cache, and fail to colourize the document later. - /// As a workaround, until we have a fix from PS or will move to Roslyn as a source of truth, we will not overwrite the cache in case of empty lists. + // Due to an issue in project system, when we close and reopen solution, it sends the CommandLineChanges twice for every project. + // First time it sends a correct path, sources, references and options. + // Second time it sends a correct path, empty sources, empty references and empty options, and we rewrite our cache, and fail to colourize the document later. + // As a workaround, until we have a fix from PS or will move to Roslyn as a source of truth, we will not overwrite the cache in case of empty lists. if not (sources.IsEmpty && references.IsEmpty && options.IsEmpty) then let workspaceService = diff --git a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs index 8a74c5ad795..261c4950ef9 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs @@ -3,6 +3,7 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System.Collections.Concurrent +open System.Collections.Generic open System.Collections.Immutable open System.Threading.Tasks @@ -80,9 +81,19 @@ module internal SymbolHelpers = // TODO: this needs to be a single event with a duration TelemetryReporter.ReportSingleEvent(TelemetryEvents.GetSymbolUsesInProjectsStarted, props) + let snapshotAccumulator = Dictionary() + + let! projects = + projects + |> Seq.map (fun project -> + project.GetFSharpProjectSnapshot(snapshotAccumulator) + |> CancellableTask.map (fun s -> project, s)) + |> CancellableTask.sequential + do! projects - |> Seq.map (fun project -> project.FindFSharpReferencesAsync(symbol, onFound, "getSymbolUsesInProjects")) + |> Seq.map (fun (project, snapshot) -> + project.FindFSharpReferencesAsync(symbol, snapshot, onFound, "getSymbolUsesInProjects")) |> CancellableTask.whenAll TelemetryReporter.ReportSingleEvent(TelemetryEvents.GetSymbolUsesInProjectsFinished, props) diff --git a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs index 891eb960ab3..c0682607360 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs @@ -9,12 +9,326 @@ open Microsoft.VisualStudio.FSharp.Editor open FSharp.Compiler open FSharp.Compiler.CodeAnalysis +open FSharp.Compiler.CodeAnalysis.ProjectSnapshot open FSharp.Compiler.Symbols -open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks +open FSharp.Compiler.BuildGraph + +open CancellableTasks + +open Internal.Utilities.Collections +open Newtonsoft.Json +open Newtonsoft.Json.Linq +open System.Text.Json.Nodes + +#nowarn "57" // Experimental stuff + +[] +module internal ProjectCache = + + /// This is a cache to maintain FSharpParsingOptions and FSharpProjectOptions per Roslyn Project. + /// The Roslyn Project is held weakly meaning when it is cleaned up by the GC, the FSharParsingOptions and FSharpProjectOptions will be cleaned up by the GC. + /// At some point, this will be the main caching mechanism for FCS projects instead of FCS itself. + let Projects = + ConditionalWeakTable() + +type Solution with + + /// Get the instance of IFSharpWorkspaceService. + member internal this.GetFSharpWorkspaceService() = + this.Workspace.Services.GetRequiredService() + +module internal FSharpProjectSnapshotSerialization = + + let serializeFileSnapshot (snapshot: FSharpFileSnapshot) = + let output = JObject() + output.Add("FileName", snapshot.FileName) + output.Add("Version", snapshot.Version) + output + + let serializeReferenceOnDisk (reference: ReferenceOnDisk) = + let output = JObject() + output.Add("Path", reference.Path) + output.Add("LastModified", reference.LastModified) + output + + let rec serializeReferencedProject (reference: FSharpReferencedProjectSnapshot) = + let output = JObject() + + match reference with + | FSharpReference(projectOutputFile, snapshot) -> + output.Add("projectOutputFile", projectOutputFile) + output.Add("snapshot", serializeSnapshot snapshot) + + output + + and serializeSnapshot (snapshot: FSharpProjectSnapshot) = + + let output = JObject() + let snapshot = snapshot.ProjectSnapshot + + output.Add("ProjectFileName", snapshot.ProjectFileName) + output.Add("ProjectId", (snapshot.ProjectId |> Option.defaultValue null |> JToken.FromObject)) + output.Add("SourceFiles", snapshot.SourceFiles |> Seq.map serializeFileSnapshot |> JArray) + output.Add("ReferencesOnDisk", snapshot.ReferencesOnDisk |> Seq.map serializeReferenceOnDisk |> JArray) + output.Add("OtherOptions", JArray(snapshot.OtherOptions)) + output.Add("ReferencedProjects", snapshot.ReferencedProjects |> Seq.map serializeReferencedProject |> JArray) + output.Add("IsIncompleteTypeCheckEnvironment", snapshot.IsIncompleteTypeCheckEnvironment) + output.Add("UseScriptResolutionRules", snapshot.UseScriptResolutionRules) + output.Add("LoadTime", snapshot.LoadTime) + // output.Add("UnresolvedReferences", snapshot.UnresolvedReferences) + output.Add( + "OriginalLoadReferences", + snapshot.OriginalLoadReferences + |> Seq.map (fun (r: Text.range, a, b) -> JArray(r.FileName, r.Start, r.End, a, b)) + |> JArray + ) + + output.Add("Stamp", (snapshot.Stamp |> (Option.defaultValue 0) |> JToken.FromObject)) + + output + + let dumpToJson (snapshot) = + + let jObject = serializeSnapshot snapshot + + let json = jObject.ToString(Formatting.Indented) + + json + +open FSharpProjectSnapshotSerialization +open System.Collections.Concurrent [] module private CheckerExtensions = + let snapshotCache = AsyncMemoize(1000, 500, "SnapshotCache") + + let latestSnapshots = ConcurrentDictionary<_, _>() + + let exist xs = xs |> Seq.isEmpty |> not + + let getFSharpOptionsForProject (this: Project) = + if not this.IsFSharp then + raise (OperationCanceledException("Project is not a FSharp project.")) + else + match ProjectCache.Projects.TryGetValue(this) with + | true, result -> CancellableTask.singleton result + | _ -> + cancellableTask { + + let! ct = CancellableTask.getCancellationToken () + + let service = this.Solution.GetFSharpWorkspaceService() + let projectOptionsManager = service.FSharpProjectOptionsManager + + match! projectOptionsManager.TryGetOptionsByProject(this, ct) with + | ValueNone -> return raise (OperationCanceledException("FSharp project options not found.")) + | ValueSome(parsingOptions, projectOptions) -> + let result = + (service.Checker, projectOptionsManager, parsingOptions, projectOptions) + + return ProjectCache.Projects.GetValue(this, ConditionalWeakTable<_, _>.CreateValueCallback(fun _ -> result)) + } + + let documentToSnapshot (document: Document) = + cancellableTask { + let! version = document.GetTextVersionAsync() + + let getSource () = + task { + let! sourceText = document.GetTextAsync() + return sourceText.ToFSharpSourceText() + } + + return FSharpFileSnapshot(FileName = document.FilePath, Version = version.ToString(), GetSource = getSource) + } + + let getReferencedProjectVersions (project: Project) = + project.GetAllProjectsThisProjectDependsOn() + |> Seq.map (fun r ct -> r.GetDependentSemanticVersionAsync(ct)) + |> CancellableTask.whenAll + |> CancellableTask.map (Seq.map (fun x -> x.ToString()) >> Set) + + let getOnDiskReferences (options: FSharpProjectOptions) = + options.OtherOptions + |> Seq.filter (fun x -> x.StartsWith("-r:")) + |> Seq.map (fun x -> + let path = x.Substring(3) + + { + Path = path + LastModified = System.IO.File.GetLastWriteTimeUtc path + }) + |> Seq.toList + + let createProjectSnapshot (snapshotAccumulatorOpt) (project: Project) (options: FSharpProjectOptions option) = + cancellableTask { + + let! options = + match options with + | Some options -> CancellableTask.singleton options + | None -> + cancellableTask { + let! _, _, _, options = getFSharpOptionsForProject project + return options + } + + let! projectVersion = project.GetDependentSemanticVersionAsync() + + let! referenceVersions = getReferencedProjectVersions project + + let updatedSnapshot = + match project.IsTransparentCompilerSnapshotReuseEnabled, latestSnapshots.TryGetValue project.Id with + | true, (true, (_, _, oldReferenceVersions, _, _)) when referenceVersions <> oldReferenceVersions -> + System.Diagnostics.Trace.TraceWarning "Reference versions changed" + None + + | true, (true, (_, _, _, _, oldSnapshot: FSharpProjectSnapshot)) when + oldSnapshot.ProjectSnapshot.ReferencesOnDisk <> (getOnDiskReferences options) + -> + System.Diagnostics.Trace.TraceWarning "References on disk changed" + None + + | true, (true, (_, oldProjectVersion, _, _, oldSnapshot: FSharpProjectSnapshot)) when projectVersion = oldProjectVersion -> + Some(CancellableTask.singleton oldSnapshot) + + | true, (true, (oldProject, _oldProjectVersion, _oldReferencesVersion, oldOptions, oldSnapshot: FSharpProjectSnapshot)) when + FSharpProjectOptions.AreSameForChecking(options, oldOptions) + -> + + let changes = project.GetChanges(oldProject) + + if + changes.GetAddedDocuments() |> exist + || changes.GetRemovedDocuments() |> exist + || changes.GetAddedMetadataReferences() |> exist + || changes.GetRemovedMetadataReferences() |> exist + || changes.GetAddedProjectReferences() |> exist + || changes.GetRemovedProjectReferences() |> exist + then + // if any of that happened, we create it from scratch + System.Diagnostics.Trace.TraceWarning "Project change not covered by options - suspicious" + None + + else + // we build it from the previous one + + let changedDocuments = changes.GetChangedDocuments() |> Seq.toList + + System.Diagnostics.Trace.TraceInformation + $"Incremental update of FSharpProjectSnapshot ({oldSnapshot.Label}) - {changedDocuments.Length} changed documents" + + if changedDocuments.Length = 0 then + // this is suspicious + let _breakpoint = "here" + () + + changedDocuments + |> Seq.map (project.GetDocument >> documentToSnapshot) + |> CancellableTask.whenAll + |> CancellableTask.map (Array.toList >> oldSnapshot.Replace) + |> Some + + | _ -> None + + let! newSnapshot = + + match updatedSnapshot with + | Some snapshot -> snapshot + | _ -> + cancellableTask { + + let solution = project.Solution + + let projects = + solution.Projects + |> Seq.map (fun p -> p.FilePath, p.Documents |> Seq.map (fun d -> d.FilePath, d) |> Map) + |> Map + + let getFileSnapshot (options: FSharpProjectOptions) path = + async { + let project = projects.TryFind options.ProjectFileName + + if project.IsNone then + System.Diagnostics.Trace.TraceError( + "Could not find project {0} in solution {1}", + options.ProjectFileName, + solution.FilePath + ) + + let documentOpt = project |> Option.bind (Map.tryFind path) + + let! version, getSource = + match documentOpt with + | Some document -> + async { + + let! version = document.GetTextVersionAsync() |> Async.AwaitTask + + let getSource () = + task { + let! sourceText = document.GetTextAsync() + return sourceText.ToFSharpSourceText() + } + + return version.ToString(), getSource + + } + | None -> + // This happens with files that are read from /obj + + // Fall back to file system + let version = System.IO.File.GetLastWriteTimeUtc(path) + + let getSource () = + task { return System.IO.File.ReadAllText(path) |> FSharp.Compiler.Text.SourceTextNew.ofString } + + async.Return(version.ToString(), getSource) + + return FSharpFileSnapshot(FileName = path, Version = version, GetSource = getSource) + } + + let! snapshot = + FSharpProjectSnapshot.FromOptions(options, getFileSnapshot, ?snapshotAccumulator = snapshotAccumulatorOpt) + + System.Diagnostics.Trace.TraceInformation $"Created new FSharpProjectSnapshot ({snapshot.Label})" + + return snapshot + } + + let latestSnapshotData = + project, projectVersion, referenceVersions, options, newSnapshot + + latestSnapshots.AddOrUpdate(project.Id, latestSnapshotData, (fun _ _ -> latestSnapshotData)) + |> ignore + + return newSnapshot + } + + let getOrCreateSnapshotForProject (project: Project) options snapshotAccumulatorOpt = + + let key = + { new ICacheKey<_, _> with + member _.GetKey() = project.Id + member _.GetVersion() = project + member _.GetLabel() = project.FilePath + } + + snapshotCache.Get( + key, + node { + let! ct = NodeCode.CancellationToken + + return! + createProjectSnapshot snapshotAccumulatorOpt project options ct + |> NodeCode.AwaitTask + } + ) + |> Async.AwaitNodeCode + + let getProjectSnapshotForDocument (document: Document, options: FSharpProjectOptions) = + getOrCreateSnapshotForProject document.Project (Some options) None + type FSharpChecker with /// Parse the source text from the Roslyn document. @@ -26,6 +340,32 @@ module private CheckerExtensions = return! checker.ParseFile(document.FilePath, sourceText.ToFSharpSourceText(), parsingOptions, userOpName = userOpName) } + member checker.ParseDocumentUsingTransparentCompiler(document: Document, options: FSharpProjectOptions, userOpName: string) = + cancellableTask { + let! projectSnapshot = getProjectSnapshotForDocument (document, options) + return! checker.ParseFile(document.FilePath, projectSnapshot, userOpName = userOpName) + } + + member checker.ParseAndCheckDocumentUsingTransparentCompiler + ( + document: Document, + options: FSharpProjectOptions, + userOpName: string + ) = + cancellableTask { + + checker.TransparentCompiler.SetCacheSizeFactor(document.Project.TransparentCompilerCacheFactor) + + let! projectSnapshot = getProjectSnapshotForDocument (document, options) + + let! (parseResults, checkFileAnswer) = checker.ParseAndCheckFileInProject(document.FilePath, projectSnapshot, userOpName) + + return + match checkFileAnswer with + | FSharpCheckFileAnswer.Aborted -> None + | FSharpCheckFileAnswer.Succeeded(checkFileResults) -> Some(parseResults, checkFileResults) + } + /// Parse and check the source text from the Roslyn document with possible stale results. member checker.ParseAndCheckDocumentWithPossibleStaleResults ( @@ -106,28 +446,18 @@ module private CheckerExtensions = ?allowStaleResults: bool ) = cancellableTask { - let allowStaleResults = - match allowStaleResults with - | Some b -> b - | _ -> document.Project.IsFSharpStaleCompletionResultsEnabled - - return! checker.ParseAndCheckDocumentWithPossibleStaleResults(document, options, allowStaleResults, userOpName = userOpName) - } -[] -module internal ProjectCache = - - /// This is a cache to maintain FSharpParsingOptions and FSharpProjectOptions per Roslyn Project. - /// The Roslyn Project is held weakly meaning when it is cleaned up by the GC, the FSharParsingOptions and FSharpProjectOptions will be cleaned up by the GC. - /// At some point, this will be the main caching mechanism for FCS projects instead of FCS itself. - let Projects = - ConditionalWeakTable() - -type Solution with + if document.Project.UseTransparentCompiler then + return! checker.ParseAndCheckDocumentUsingTransparentCompiler(document, options, userOpName) + else + let allowStaleResults = + match allowStaleResults with + | Some b -> b + | _ -> document.Project.IsFSharpStaleCompletionResultsEnabled - /// Get the instance of IFSharpWorkspaceService. - member internal this.GetFSharpWorkspaceService() = - this.Workspace.Services.GetRequiredService() + return! + checker.ParseAndCheckDocumentWithPossibleStaleResults(document, options, allowStaleResults, userOpName = userOpName) + } type Document with @@ -195,8 +525,12 @@ type Document with /// Parses the given F# document. member this.GetFSharpParseResultsAsync(userOpName) = cancellableTask { - let! checker, _, parsingOptions, _ = this.GetFSharpCompilationOptionsAsync(userOpName) - return! checker.ParseDocument(this, parsingOptions, userOpName) + let! checker, _, parsingOptions, options = this.GetFSharpCompilationOptionsAsync(userOpName) + + if this.Project.UseTransparentCompiler then + return! checker.ParseDocumentUsingTransparentCompiler(this, options, userOpName) + else + return! checker.ParseDocument(this, parsingOptions, userOpName) } /// Parses and checks the given F# document. @@ -213,7 +547,15 @@ type Document with member this.GetFSharpSemanticClassificationAsync(userOpName) = cancellableTask { let! checker, _, _, projectOptions = this.GetFSharpCompilationOptionsAsync(userOpName) - let! result = checker.GetBackgroundSemanticClassificationForFile(this.FilePath, projectOptions) + + let! result = + if this.Project.UseTransparentCompiler then + async { + let! projectSnapshot = getProjectSnapshotForDocument (this, projectOptions) + return! checker.GetBackgroundSemanticClassificationForFile(this.FilePath, projectSnapshot) + } + else + checker.GetBackgroundSemanticClassificationForFile(this.FilePath, projectOptions) return result @@ -221,18 +563,22 @@ type Document with } /// Find F# references in the given F# document. - member inline this.FindFSharpReferencesAsync(symbol, [] onFound, userOpName) = + member inline this.FindFSharpReferencesAsync(symbol, projectSnapshot: FSharpProjectSnapshot, [] onFound, userOpName) = cancellableTask { let! checker, _, _, projectOptions = this.GetFSharpCompilationOptionsAsync(userOpName) let! symbolUses = - checker.FindBackgroundReferencesInFile( - this.FilePath, - projectOptions, - symbol, - canInvalidateProject = false, - fastCheck = this.Project.IsFastFindReferencesEnabled - ) + + if this.Project.UseTransparentCompiler then + checker.FindBackgroundReferencesInFile(this.FilePath, projectSnapshot, symbol) + else + checker.FindBackgroundReferencesInFile( + this.FilePath, + projectOptions, + symbol, + canInvalidateProject = false, + fastCheck = this.Project.IsFastFindReferencesEnabled + ) do! symbolUses @@ -267,7 +613,7 @@ type Document with type Project with /// Find F# references in the given project. - member this.FindFSharpReferencesAsync(symbol: FSharpSymbol, onFound, userOpName) = + member this.FindFSharpReferencesAsync(symbol: FSharpSymbol, projectSnapshot, onFound, userOpName) = cancellableTask { let declarationLocation = @@ -307,32 +653,15 @@ type Project with if this.IsFastFindReferencesEnabled then do! documents - |> Seq.map (fun doc -> doc.FindFSharpReferencesAsync(symbol, (fun range -> onFound doc range), userOpName)) + |> Seq.map (fun doc -> + doc.FindFSharpReferencesAsync(symbol, projectSnapshot, (fun range -> onFound doc range), userOpName)) |> CancellableTask.whenAll else for doc in documents do - do! doc.FindFSharpReferencesAsync(symbol, (fun range -> onFound doc range), userOpName) + do! doc.FindFSharpReferencesAsync(symbol, projectSnapshot, (onFound doc), userOpName) } - member this.GetFSharpCompilationOptionsAsync() = - if not this.IsFSharp then - raise (OperationCanceledException("Project is not a FSharp project.")) - else - match ProjectCache.Projects.TryGetValue(this) with - | true, result -> CancellableTask.singleton result - | _ -> - cancellableTask { - - let! ct = CancellableTask.getCancellationToken () - - let service = this.Solution.GetFSharpWorkspaceService() - let projectOptionsManager = service.FSharpProjectOptionsManager + member this.GetFSharpCompilationOptionsAsync() = this |> getFSharpOptionsForProject - match! projectOptionsManager.TryGetOptionsByProject(this, ct) with - | ValueNone -> return raise (OperationCanceledException("FSharp project options not found.")) - | ValueSome(parsingOptions, projectOptions) -> - let result = - (service.Checker, projectOptionsManager, parsingOptions, projectOptions) - - return ProjectCache.Projects.GetValue(this, ConditionalWeakTable<_, _>.CreateValueCallback(fun _ -> result)) - } + member this.GetFSharpProjectSnapshot(?snapshotAccumulator) = + cancellableTask { return! getOrCreateSnapshotForProject this None snapshotAccumulator } diff --git a/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs b/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs index 250565319e7..e9b7f60252a 100644 --- a/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs +++ b/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs @@ -83,6 +83,7 @@ type CodeFixesOptions = type LanguageServicePerformanceOptions = { EnableInMemoryCrossProjectReferences: bool + TransparentCompilerCacheFactor: int AllowStaleCompletionResults: bool TimeUntilStaleCompletion: int EnableParallelReferenceResolution: bool @@ -97,6 +98,7 @@ type LanguageServicePerformanceOptions = static member Default = { EnableInMemoryCrossProjectReferences = true + TransparentCompilerCacheFactor = 100 AllowStaleCompletionResults = true TimeUntilStaleCompletion = 2000 // In ms, so this is 2 seconds EnableParallelReferenceResolution = false @@ -117,6 +119,8 @@ type AdvancedOptions = IsInlineParameterNameHintsEnabled: bool IsInlineReturnTypeHintsEnabled: bool IsUseLiveBuffersEnabled: bool + UseTransparentCompiler: bool + TransparentCompilerSnapshotReuse: bool SendAdditionalTelemetry: bool SolutionBackgroundAnalysis: bool } @@ -128,6 +132,8 @@ type AdvancedOptions = IsInlineTypeHintsEnabled = false IsInlineParameterNameHintsEnabled = false IsInlineReturnTypeHintsEnabled = false + UseTransparentCompiler = false + TransparentCompilerSnapshotReuse = false IsUseLiveBuffersEnabled = true SendAdditionalTelemetry = true SolutionBackgroundAnalysis = false @@ -265,3 +271,11 @@ module EditorOptionsExtensions = member this.IsFastFindReferencesEnabled = this.EditorOptions.LanguageServicePerformance.EnableFastFindReferencesAndRename + + member this.UseTransparentCompiler = this.EditorOptions.Advanced.UseTransparentCompiler + + member this.IsTransparentCompilerSnapshotReuseEnabled = + this.EditorOptions.Advanced.TransparentCompilerSnapshotReuse + + member this.TransparentCompilerCacheFactor = + this.EditorOptions.LanguageServicePerformance.TransparentCompilerCacheFactor diff --git a/vsintegration/src/FSharp.Editor/Telemetry/TelemetryReporter.fs b/vsintegration/src/FSharp.Editor/Telemetry/TelemetryReporter.fs index 2a520d72066..b841b7e8cd6 100644 --- a/vsintegration/src/FSharp.Editor/Telemetry/TelemetryReporter.fs +++ b/vsintegration/src/FSharp.Editor/Telemetry/TelemetryReporter.fs @@ -105,9 +105,11 @@ type TelemetryReporter private (name: string, props: (string * obj) array, stopw TelemetryService.DefaultSession.IsUserMicrosoftInternal else let workspace = componentModel.GetService() + let options = workspace.Services.GetService() TelemetryService.DefaultSession.IsUserMicrosoftInternal - || workspace.Services.GetService().Advanced.SendAdditionalTelemetry) + || options.Advanced.SendAdditionalTelemetry + || options.Advanced.UseTransparentCompiler) static member ReportFault(name, ?severity: FaultSeverity, ?e: exn) = if TelemetryReporter.SendAdditionalTelemetry.Value then diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf index 8cd60083c90..182fc895c61 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf @@ -24,7 +24,7 @@ Add return type annotation - Přidat anotaci návratového typu + Add return type annotation @@ -41,9 +41,10 @@ Inline hints; Display inline type hints (preview); Display return type hints (preview); Display inline parameter name hints (preview); +Use Transparent Compiler (experimental); Live Buffers; Use live (unsaved) buffers for analysis - Vodítka struktury bloků; + Vodítka struktury bloků; Zobrazit pokyny ke struktuře pro kód F#; Osnova; Zobrazit osnovu a sbalitelné uzly kódu F#; @@ -52,7 +53,7 @@ Zobrazit tipy pro vložený typ (náhled); Zobrazení tipů pro návratový typ (náhled); Zobrazit nápovědy k názvům vložených parametrů (náhled); Živé vyrovnávací paměti; -Použití živých (neuložených) vyrovnávacích pamětí pro analýzu +Použití živých (neuložených) vyrovnávacích pamětí pro kontrolu @@ -174,16 +175,16 @@ Enable parallel type checking with signature files; Enable parallel reference resolution; Enable fast find references & rename (restart required); Cache parsing results (experimental) - Možnosti výkonu pro ukládání do mezipaměti a projekt F#; + Možnosti výkonu projektu F# a ukládání do mezipaměti; Povolit odkazy mezi projekty v paměti; -Povolit_částečnou_kontrolu_typu; -Možnosti výkonu pro IntelliSense; +Enable_partial_type_checking; +Možnosti výkonu IntelliSense; Povolit zastaralá data pro funkce IntelliSense; -Doba, do kdy se budou používat zastaralé výsledky (v milisekundách); +Doba, než se použijí zastaralé výsledky (v milisekundách); Paralelizace (vyžaduje restartování); Povolit paralelní kontrolu typů pomocí souborů podpisu; Povolit paralelní referenční rozlišení; -Povolit odkazy rychlého hledání a přejmenování (vyžaduje se restartování); +Povolit odkazy rychlého hledání a přejmenování (experimentální) Výsledky analýzy mezipaměti (experimentální) @@ -202,20 +203,19 @@ Solid underline; Dot underline; Dash underline; Show remarks in Quick Info - Formátování; + Formátování; Upřednostňovaná šířka popisu ve znacích; -Umožňuje formátovat podpis na danou šířku přidáním konců řádků odpovídajících pravidlům syntaxe F#; +Formátování podpisu na danou šířku přidáním konců řádků podle pravidel syntaxe F#; Navigační odkazy; Zobrazit navigační odkazy jako; Plné podtržení; Tečkované podtržení; -Čárkované podtržení; -Zobrazit poznámky v Rychlých informacích +Přerušované podtržení; Remarks: - Poznámky: + Remarks: @@ -230,7 +230,7 @@ Zobrazit poznámky v Rychlých informacích Remove unnecessary parentheses - Odebrat nadbytečné závorky + Remove unnecessary parentheses @@ -255,7 +255,7 @@ Zobrazit poznámky v Rychlých informacích Returns: - Vrací: + Returns: diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf index bc05638c6f5..5a8c128c134 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf @@ -24,7 +24,7 @@ Add return type annotation - Rückgabetypanmerkung hinzufügen + Add return type annotation @@ -41,9 +41,10 @@ Inline hints; Display inline type hints (preview); Display return type hints (preview); Display inline parameter name hints (preview); +Use Transparent Compiler (experimental); Live Buffers; Use live (unsaved) buffers for analysis - Führungslinien für Blockstruktur; + Führungslinien für Blockstruktur; Strukturrichtlinien für F#-Code anzeigen; Gliederung; Gliederungs- und reduzierbare Knoten für F#-Code anzeigen; @@ -52,7 +53,7 @@ Hinweise zu Inlinetypen anzeigen (Vorschau); Hinweise zu Rückgabetypen anzeigen (Vorschau); Hinweise zu Inlineparameternamen anzeigen (Vorschau); Livepuffer; -Livepuffer (nicht gespeichert) zur Analyse verwenden +Livepuffer (nicht gespeichert) zur Überprüfung verwenden @@ -174,16 +175,16 @@ Enable parallel type checking with signature files; Enable parallel reference resolution; Enable fast find references & rename (restart required); Cache parsing results (experimental) - Optionen zur F#-Projekt- und Cacheleistung; -Proj_ektübergreifende Verweise im Arbeitsspeicher aktivieren; -Aktivieren der partiellen Typüberprüfung; -Optionen zur IntelliSense-Leistung; + F#-Projekt- und Cacheleistungsoptionen; +Projektübergreifende Verweise im Arbeitsspeicher aktivieren; +Enable_partial_type_checking; +IntelliSense-Leistungsoptionen; Veraltete Daten für IntelliSense-Features aktivieren; Zeit bis zur Verwendung veralteter Ergebnisse (in Millisekunden); Parallelisierung (Neustart erforderlich); Parallele Typüberprüfung mit Signaturdateien aktivieren; Parallele Verweisauflösung aktivieren; -Schnellsuche und Umbenennen von Verweisen aktivieren (Neustart erforderlich); +Schnellsuche und Umbenennen von Verweisen aktivieren (experimentell); Cacheanalyseergebnisse (experimentell) @@ -202,20 +203,19 @@ Solid underline; Dot underline; Dash underline; Show remarks in Quick Info - Formatierung; + Formatierung; Bevorzugte Beschreibungsbreite in Zeichen; -Formatieren Sie die Signatur in der angegebenen Breite, indem Sie Zeilenumbrüche hinzufügen, die F#-Syntaxregeln entsprechen; +Signatur in der angegebenen Breite formatieren, indem Zeilenumbrüche hinzugefügt werden, die den F#-Syntaxregeln entsprechen; Navigationslinks; Navigationslinks anzeigen als; -Durchgezogene Unterstreichung; -Gepunktete Unterstreichung; -Gestrichelte Unterstreichung; -Hinweise in QuickInfo anzeigen +Unterstreichung einfarbig; +Punkt unterstrichen; +Strich unterstrichen; Remarks: - Bemerkungen: + Remarks: @@ -230,7 +230,7 @@ Hinweise in QuickInfo anzeigen Remove unnecessary parentheses - Unnötige Klammern entfernen + Remove unnecessary parentheses @@ -255,7 +255,7 @@ Hinweise in QuickInfo anzeigen Returns: - Rückgabe: + Returns: diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf index 4b789c84545..e313f8ceefc 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf @@ -24,7 +24,7 @@ Add return type annotation - Agregar una anotación de tipo + Add return type annotation @@ -41,9 +41,10 @@ Inline hints; Display inline type hints (preview); Display return type hints (preview); Display inline parameter name hints (preview); +Use Transparent Compiler (experimental); Live Buffers; Use live (unsaved) buffers for analysis - Guías de estructura de bloques; + Guías de estructura de bloques; Mostrar guías de estructura para código F#; Esquema; Mostrar esquema y nodos colapsables para código F#; @@ -52,7 +53,7 @@ Mostrar sugerencias de tipo insertadas (vista previa); Mostrar sugerencias de tipo de valor devuelto (vista previa); Mostrar sugerencias de nombres de parámetro insertadas (vista previa) Búferes activos; -Usar búferes activos (no guardados) para el análisis +Usar búferes activos (no guardados) para la comprobación @@ -174,17 +175,19 @@ Enable parallel type checking with signature files; Enable parallel reference resolution; Enable fast find references & rename (restart required); Cache parsing results (experimental) - Opciones de rendimiento de almacenamiento en caché y proyectos de F#; -Habilitar referencias entre proyectos en memoria; + Opciones de rendimiento de proyectos y caché de F#; +Habilitar referencias cruzadas de proyecto en memoria; Enable_partial_type_checking; Opciones de rendimiento de IntelliSense; -Habilitar datos obsoletos para características de IntelliSense; +Habilitar datos obsoletos para funciones de IntelliSense; Tiempo hasta que se utilizan los resultados obsoletos (en milisegundos); -Paralelización (requiere reiniciar); -Habilitar la comprobación de tipos paralelos con archivos de firma; -Habilitar resolución de referencias paralelas; -Habilitar referencias de búsqueda rápida y cambio de nombre (es necesario reiniciar); -Resultados del análisis de la caché (experimental) +Paralelización (requiere reinicio); + +Habilitar la comprobación paralela de tipos con archivos de firmas; + +Habilitar la resolución de referencias en paralelo; +Habilitar búsqueda rápida de referencias y renombrado (experimental); +Caché de resultados de análisis (experimental) @@ -202,20 +205,19 @@ Solid underline; Dot underline; Dash underline; Show remarks in Quick Info - Formateando; -Anchura preferida de la descripción en caracteres; -Da formato a la firma al ancho dado agregando saltos de línea conforme a las reglas de sintaxis de F#; + Formato; +Ancho de descripción preferido en caracteres; +Dar formato a la firma con el ancho dado agregando saltos de línea que cumplan las reglas de sintaxis de F#; Vínculos de navegación; Mostrar vínculos de navegación como; Subrayado sólido; -Subrayado de puntos; -Subrayado guion; -Mostrar comentarios en Información rápida +Subrayado de punto; +Subrayado de guion; Remarks: - Comentarios: + Remarks: @@ -230,7 +232,7 @@ Mostrar comentarios en Información rápida Remove unnecessary parentheses - Quitar los paréntesis innecesarios + Remove unnecessary parentheses @@ -255,7 +257,7 @@ Mostrar comentarios en Información rápida Returns: - Devuelve: + Returns: diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf index 93f27375a08..36245222af1 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf @@ -24,7 +24,7 @@ Add return type annotation - Ajouter une annotation de type de retour + Add return type annotation @@ -41,18 +41,19 @@ Inline hints; Display inline type hints (preview); Display return type hints (preview); Display inline parameter name hints (preview); +Use Transparent Compiler (experimental); Live Buffers; Use live (unsaved) buffers for analysis - Guides de structure de bloc; + Guides de structure de bloc ; Afficher les directives de structure pour le code F# ; -Décrire; -Afficher les nœuds de plan et réductibles pour le code F#; -Conseils en ligne; -Afficher les conseils de type en ligne (aperçu); -Afficher les conseils sur le type de retour (aperçu); -Afficher les conseils sur le nom des paramètres en ligne (aperçu); -Tampons en direct; -Utilisez des tampons en direct (non enregistrés) pour analyser +Décrire ; +Afficher les nœuds de plan et réductibles pour le code F# ; +Conseils en ligne ; +Afficher les conseils de type en ligne (aperçu) ; +Afficher les conseils sur le type de retour (aperçu) ; +Afficher les conseils sur le nom des paramètres en ligne (aperçu) ; +Tampons en direct ; +Utilisez des tampons en direct (non enregistrés) pour vérifier @@ -174,17 +175,17 @@ Enable parallel type checking with signature files; Enable parallel reference resolution; Enable fast find references & rename (restart required); Cache parsing results (experimental) - Options relatives aux performances de la mise en cache et des projets F#; -Activer les références de projet croisé en mémoire; -Enable_partial_type_checking; -Options relatives aux performances d’IntelliSense; -Activer les données périmées pour les fonctionnalités IntelliSense; -Délai avant l’utilisation des résultats périmés (en millisecondes); -Parallélisation (Nécessite un redémarrage); -Activer la vérification de type parallèle avec les fichiers de signature; -Activer la résolution de référence parallèle; -Activer la recherche rapide de références et le renommage (redémarrage requis); -Résultats de l’analyse du cache (expérimental) + Options de performances du projet F # et de la mise en cache ; +Activer les références de projets croisés en mémoire ; +Enable_partial_type_checking ; +Options de performances IntelliSense ; +Activer les données obsolètes pour les fonctionnalités IntelliSense ; +Temps jusqu'à ce que les résultats obsolètes soient utilisés (en millisecondes) ; +Parallélisation (nécessite un redémarrage) ; +Activer la vérification de type parallèle avec les fichiers de signature ; +Activer la résolution de référence parallèle ; +Activer les références de recherche rapide et renommer (expérimental) ; +Résultats de l'analyse du cache (expérimental) @@ -202,20 +203,19 @@ Solid underline; Dot underline; Dash underline; Show remarks in Quick Info - Mise en forme; -Largeur de description préférée en caractères; -Formatez la signature à la largeur donnée en ajoutant des sauts de ligne conformes aux règles de syntaxe F#; -Liens de navigation; -Afficher les liens de navigation en tant que; -Soulignement avec un trait uni; -Soulignement avec des points; -Soulignement avec des tirets; -Afficher les notes dans Info express + Formatage; +Largeur de description préférée en caractères ; +Mettre en forme la signature à la largeur donnée en ajoutant des sauts de ligne conformes aux règles de syntaxe F# ; +Liens de navigation ; +Afficher les liens de navigation en tant que ; +Soulignement uni ; +Soulignement pointé ; +Soulignement en tirets ; Remarks: - Notes : + Remarks: @@ -230,7 +230,7 @@ Afficher les notes dans Info express Remove unnecessary parentheses - Supprimer les parenthèses inutiles + Remove unnecessary parentheses @@ -255,7 +255,7 @@ Afficher les notes dans Info express Returns: - Retourne : + Returns: diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf index c43e7044b9b..12f274dffbd 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf @@ -24,7 +24,7 @@ Add return type annotation - Aggiungere annotazione di tipo restituito + Add return type annotation @@ -41,9 +41,10 @@ Inline hints; Display inline type hints (preview); Display return type hints (preview); Display inline parameter name hints (preview); +Use Transparent Compiler (experimental); Live Buffers; Use live (unsaved) buffers for analysis - Guide per strutture a blocchi; + Guide per strutture a blocchi; Mostra le linee guida per la struttura per il codice F#; Struttura; Mostra i nodi struttura e comprimibili per il codice F#; @@ -52,7 +53,7 @@ Visualizza suggerimenti di tipo inline (anteprima); Visualizza suggerimenti di tipo restituito (anteprima); Visualizza suggerimenti per i nomi di parametro inline (anteprima); Buffer in tempo reale; -Usa buffer in tempo reale (non salvati) per l’analisi +Usa buffer in tempo reale (non salvati) per il controllo @@ -174,17 +175,17 @@ Enable parallel type checking with signature files; Enable parallel reference resolution; Enable fast find references & rename (restart required); Cache parsing results (experimental) - Opzioni per le prestazioni di memorizzazione nella cache e progetti F#; -_Abilita i riferimenti tra progetti in memoria; -Abilita il controllo parziale dei tipi; -Opzioni per le prestazioni IntelliSense; -Abilita dati non aggiornati per le funzionalità IntelliSense; -Intervallo di utilizzo dei risultati non aggiornati (in millisecondi); + Opzioni prestazioni progetto F# e memorizzazione nella cache; +Abilita riferimenti tra progetti in memoria; +Enable_partial_type_checking; +Opzioni prestazioni IntelliSense; +Abilita dati non aggiornati per le funzionalità di IntelliSense; +Tempo prima dell'utilizzo dei risultati non aggiornati (in millisecondi); Parallelizzazione (richiede il riavvio); -Abilitare il controllo dei tipi paralleli con i file di firma; -Abilitare risoluzione riferimenti paralleli; -Abilitare i riferimenti di ricerca rapida > ridenominazione (riavvio necessario); -Risultati dell'analisi della cache (sperimentale) +Abilita il controllo dei tipi paralleli con i file di firma; +Abilita risoluzione riferimenti paralleli; +Abilita la ricerca rapida dei riferimenti e la ridenominazione (sperimentale); +Memorizza nella cache i risultati dell'analisi (sperimentale) @@ -202,20 +203,19 @@ Solid underline; Dot underline; Dash underline; Show remarks in Quick Info - Formattazione; + Formattazione; Larghezza descrizione preferita in caratteri; -Consente di formattare la firma in base alla larghezza specificata aggiungendo interruzioni di riga conformi alle regole di sintassi F#; -Collegamenti di navigazione; -Mostra collegamenti di navigazione come; -Sottolineatura continua; -Sottolineatura punto; -Sottolineatura tratteggiata; -Mostra i commenti in Informazioni rapide +Formatta la firma in base alla larghezza specificata aggiungendo interruzioni di riga conformi alle regole di sintassi F#; +Collegamenti di spostamento; +Mostra collegamenti di spostamento come; +Sottolineatura a tinta unita; +Sottolineatura a punto; +Sottolineatura a trattini; Remarks: - Note: + Remarks: @@ -230,7 +230,7 @@ Mostra i commenti in Informazioni rapide Remove unnecessary parentheses - Rimuovi le parentesi non necessarie + Remove unnecessary parentheses @@ -255,7 +255,7 @@ Mostra i commenti in Informazioni rapide Returns: - Restituisce: + Returns: diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf index 9d9dfb23c2e..1a23fcf5d9e 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf @@ -24,7 +24,7 @@ Add return type annotation - 戻り値の型の注釈の追加 + Add return type annotation @@ -41,18 +41,19 @@ Inline hints; Display inline type hints (preview); Display return type hints (preview); Display inline parameter name hints (preview); +Use Transparent Compiler (experimental); Live Buffers; Use live (unsaved) buffers for analysis - ブロック構造のガイド; -F# コードの構造のガイドラインを表示; -アウトライン表示; -F# コードのアウトラインおよび折りたたみ可能なノードを表示する; + ブロック構造ガイド; +F# コードの構造ガイドラインを表示する; +アウトラインを表示する; +アウトラインと折りたたみ可能なノードを F# コードに表示する; インライン ヒント; -インライン型のヒントを表示する (プレビュー); -戻り値型のヒントを表示する (プレビュー); -インライン パラメーター名のヒントを表示 (プレビュー); +インライン型ヒントを表示する (プレビュー); +戻り値の型ヒントを表示する (プレビュー); +インライン パラメーター名のヒントを表示する (プレビュー); ライブ バッファー; -分析にライブ (未保存) バッファーを使用する +ライブ (保存されていない) バッファーを使用してチェックする @@ -174,16 +175,16 @@ Enable parallel type checking with signature files; Enable parallel reference resolution; Enable fast find references & rename (restart required); Cache parsing results (experimental) - F# プロジェクトとキャッシュのパフォーマンス オプション; -メモリ内のプロジェクト間参照を有効にする; + F# プロジェクトとキャッシュのパフォーマンス オプション; +メモリ内のプロジェクト間参照を有効にする。 Enable_partial_type_checking; -IntelliSense のパフォーマンス オプション; -IntelliSense 機能に対して古いデータを有効にする; +IntelliSense パフォーマンス オプション; +IntelliSense 機能の古いデータを有効にする。 古い結果が使用されるまでの時間 (ミリ秒); 並列化 (再起動が必要); -署名ファイルを使用して並列型チェックを有効にする; +署名ファイルを使用して並列型チェックを有効にする。 並列参照解決を有効にする; -高速検索参照と名前の変更を有効にする (再起動が必要); +高速検索参照の有効化と名前の変更 (試験段階); キャッシュ解析の結果 (試験段階) @@ -202,20 +203,19 @@ Solid underline; Dot underline; Dash underline; Show remarks in Quick Info - 書式設定; -優先する説明の文字幅; -F# 構文規則に準拠した改行を追加して、署名を指定された幅に書式設定する; + 書式 設定; +希望の説明の幅 (文字数); +F# 構文規則に準拠するよう、改行を追加して指定された幅に署名を書式設定します; ナビゲーション リンク; -次としてナビゲーション リンクを表示; -実線の下線; +ナビゲーション リンクを次のように表示します: +塗りつぶしの下線; ドットの下線; -破線の下線; -クイック ヒントに注釈を表示する +ダッシュ下線; Remarks: - 注釈: + Remarks: @@ -230,7 +230,7 @@ F# 構文規則に準拠した改行を追加して、署名を指定された Remove unnecessary parentheses - 不要なかっこの削除 + Remove unnecessary parentheses @@ -255,7 +255,7 @@ F# 構文規則に準拠した改行を追加して、署名を指定された Returns: - 戻り値: + Returns: diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf index f5d17cf4c7d..1f58583afd2 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf @@ -24,7 +24,7 @@ Add return type annotation - 반환 형식 주석 추가 + Add return type annotation @@ -41,18 +41,20 @@ Inline hints; Display inline type hints (preview); Display return type hints (preview); Display inline parameter name hints (preview); +Use Transparent Compiler (experimental); Live Buffers; Use live (unsaved) buffers for analysis - 블록 구조 가이드; + 블록 구조 안내선; + F# 코드에 대한 구조 지침 표시; 개요; F# 코드에 대한 개요 및 축소 가능한 노드 표시; 인라인 힌트; 인라인 형식 힌트 표시(미리 보기); -반환 형식 힌트 표시(미리 보기); +반환 유형 힌트 표시(미리 보기); 인라인 매개 변수 이름 힌트 표시(미리 보기); 라이브 버퍼; -분석에 라이브(저장되지 않은) 버퍼 사용 +확인에 라이브(저장되지 않은) 버퍼 사용 @@ -174,16 +176,16 @@ Enable parallel type checking with signature files; Enable parallel reference resolution; Enable fast find references & rename (restart required); Cache parsing results (experimental) - F# 프로젝트 및 캐싱 성능 옵션; -메모리 내 크로스 프로젝트 참조 사용; + F# 프로젝트 및 캐싱 성능 옵션; +메모리 내 프로젝트 간 참조 활성화; Enable_partial_type_checking; IntelliSense 성능 옵션; -IntelliSense 기능에 대해 부실 데이터 사용; +IntelliSense 기능에 부실 데이터 사용; 부실 결과가 사용될 때까지의 시간(밀리초); 병렬화(다시 시작 필요); -서명 파일로 병렬 유형 검사 사용; -병렬 참조 해상도 사용; -빠른 찾기 참조 및 이름 바꾸기 사용(다시 시작 필요); +서명 파일과 함께 병렬 형식 검사 사용; +병렬 참조 확인 사용; +빠른 찾기 참조 및 이름 바꾸기 사용(실험적); 캐시 구문 분석 결과(실험적) @@ -202,20 +204,19 @@ Solid underline; Dot underline; Dash underline; Show remarks in Quick Info - 서식; -기본 설정 설명 너비(문자); -F# 구문 규칙에 맞는 줄 바꿈을 추가하여 지정된 너비에 시그니처의 서식 지정; + 서식; +기본 설정 문자 설명 너비; +F# 구문 규칙에 맞는 줄바꿈을 추가하여 지정된 너비에 서명 서식 지정; 탐색 링크; -탐색 링크를 다음으로 표시; +탐색 링크 표시 형식; 실선 밑줄; -점 밑줄; -대시 밑줄; -요약 정보에 설명 표시 +점선 밑줄; +대시 밑줄; Remarks: - 설명: + Remarks: @@ -230,7 +231,7 @@ F# 구문 규칙에 맞는 줄 바꿈을 추가하여 지정된 너비에 시그 Remove unnecessary parentheses - 불필요한 괄호 제거 + Remove unnecessary parentheses @@ -255,7 +256,7 @@ F# 구문 규칙에 맞는 줄 바꿈을 추가하여 지정된 너비에 시그 Returns: - 반환 값: + Returns: diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf index aadc760089a..24392a0e18e 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf @@ -24,7 +24,7 @@ Add return type annotation - Dodaj adnotację zwracanego typu + Add return type annotation @@ -41,18 +41,19 @@ Inline hints; Display inline type hints (preview); Display return type hints (preview); Display inline parameter name hints (preview); +Use Transparent Compiler (experimental); Live Buffers; Use live (unsaved) buffers for analysis - Przewodniki po strukturze bloku; + Przewodniki po strukturze bloku; Pokaż przewodniki po strukturze dla kodu języka F#; Tworzenie konspektu; Pokaż konspekt i węzły z możliwością zwijania dla kodu języka F#; Wskazówki w tekście; -Wyświetl wskazówki dotyczące typu w tekście (wersja zapoznawcza); -Wyświetlaj wskazówki dotyczące zwracanego typu (wersja zapoznawcza); -Wyświetl wskazówki dotyczące nazw parametrów w tekście (wersja zapoznawcza); +Wyświetl wskazówki w tekście dla typów (wersja zapoznawcza); +Wyświetlaj wskazówki dotyczące typu zwrotu (wersja zapoznawcza); +Wyświetl wskazówki w tekście dotyczące nazw parametrów (wersja zapoznawcza); Bufory bieżące; -Do analizy używaj buforów bieżących (niezapisanych) +Do sprawdzania używaj buforów bieżących (niezapisanych) @@ -174,17 +175,17 @@ Enable parallel type checking with signature files; Enable parallel reference resolution; Enable fast find references & rename (restart required); Cache parsing results (experimental) - Opcje wydajności projektu i buforowania języka F#; + Opcje wydajności projektów i buforowania języka F#; Włącz odwołania między projektami w pamięci; -Włącz_częściową_kontrolę_typu; +Enable_partial_type_checking; Opcje wydajności funkcji IntelliSense; -Włącz nieaktualne dane na potrzeby funkcji IntelliSense; +Włącz nieaktualne dane dla funkcji IntelliSense; Czas do użycia nieaktualnych wyników (w milisekundach); Równoległość (wymaga ponownego uruchomienia); -Włącz równoległą kontrolę typu za pomocą plików sygnatury; -Włącz równoległe rozpoznawanie odwołań; -Włącz szybkie znajdowanie odwołań i zmień nazwę (wymagane ponowne uruchomienie); -Wyniki analizy pamięci podręcznej (eksperymentalne) +Włącz kontrolę typów równoległych za pomocą plików podpisu; +Włącz rozpoznawanie odwołań równoległych; +Włącz szybkie znajdowanie odwołań i zmianę nazwy (eksperymentalne); +Wyniki analizowania pamięci podręcznej (eksperymentalne) @@ -202,20 +203,19 @@ Solid underline; Dot underline; Dash underline; Show remarks in Quick Info - Formatowanie; + Formatowanie; Preferowana szerokość opisu w znakach; Sformatuj sygnaturę na daną szerokość, dodając podziały wierszy zgodne z regułami składni języka F#; -Linki nawigacyjne; -Pokaż linki nawigacyjne jako; -Podkreślenie ciągłe; -Podkreślenie z kropek; -Podkreślenie z kresek; -Pokaż uwagi w szybkich informacjach +Linki nawigacji; +Pokaż linki nawigacji jako; +Pełne podkreślenie; +Podkreślenie kropką; +Podkreślenie kreską; Remarks: - Uwagi: + Remarks: @@ -230,7 +230,7 @@ Pokaż uwagi w szybkich informacjach Remove unnecessary parentheses - Usuń niepotrzebne nawiasy + Remove unnecessary parentheses @@ -255,7 +255,7 @@ Pokaż uwagi w szybkich informacjach Returns: - Zwraca: + Returns: diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf index e52c4a34ea9..85a79352133 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf @@ -24,7 +24,7 @@ Add return type annotation - Adicionar anotação de tipo de retorno + Add return type annotation @@ -41,9 +41,10 @@ Inline hints; Display inline type hints (preview); Display return type hints (preview); Display inline parameter name hints (preview); +Use Transparent Compiler (experimental); Live Buffers; Use live (unsaved) buffers for analysis - Guias de Estrutura de Bloco; + Guias de Estrutura de Bloco; Mostrar diretrizes de estrutura para código F#; Estrutura de tópicos; Mostrar nós de estrutura de tópicos e recolhíveis para código F#; @@ -174,17 +175,17 @@ Enable parallel type checking with signature files; Enable parallel reference resolution; Enable fast find references & rename (restart required); Cache parsing results (experimental) - Projeto em F# e opções de desempenho em cache; -_Habilitar referências de projeto cruzado na memória; -Habilitar verificação parcial de tipo; -Opções de desempenho do IntelliSense; -Habilitar dados obsoletos para os recursos do IntelliSense; -Tempo até que os resultados obsoletos sejam utilizados (em milissegundos); -Paralelização (requer reinicialização); -Habilitar a verificação de tipo paralelo com arquivos de assinatura; -Habilitar a resolução de referência paralela; -Habilitar referências de localização rápida e renomear (reinicialização necessária); -Resultados da análise de cache (experimental) + Opções de desempenho de projeto e cache do F#; + Habilitar referências de projeto cruzado na memória; + Enable_partial_type_checking; + Opções de desempenho do IntelliSense; + Habilitar dados obsoletos para recursos do IntelliSense; + Tempo até que os resultados obsoletos sejam usados (em milissegundos); + Paralelização (requer reinicialização); + Habilitar a verificação de tipo paralelo com arquivos de assinatura; + Habilitar resolução de referência paralela; + Habilitar referências de localização rápida e renomear (experimental); + Resultados da análise de cache (experimental) @@ -202,20 +203,19 @@ Solid underline; Dot underline; Dash underline; Show remarks in Quick Info - Formatação; -Largura de descrição preferencial em caracteres; -Formate a assinatura para a largura fornecida adicionando quebras de linha em conformidade com as regras de sintaxe de F#; + Formatação; +Largura da descrição preferida em caracteres; +Formate a assinatura para a largura especificada adicionando quebras de linha em conformidade com as regras de sintaxe do F#; Links de navegação; -Exibir link de navegação como; +Mostrar links de navegação como; Sublinhado sólido; -Sublinhado pontilhado; -Sublinhado tracejado; -Mostrar os comentários nas Informações Rápidas +Ponto sublinhado; +Traço sublinhado; Remarks: - Comentários: + Remarks: @@ -230,7 +230,7 @@ Mostrar os comentários nas Informações Rápidas Remove unnecessary parentheses - Remover os parênteses desnecessários + Remove unnecessary parentheses @@ -255,7 +255,7 @@ Mostrar os comentários nas Informações Rápidas Returns: - Retorna: + Returns: diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf index 6b67d4dee3e..357494b37b2 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf @@ -24,7 +24,7 @@ Add return type annotation - Добавить аннотацию типа возвращаемого значения + Add return type annotation @@ -41,9 +41,10 @@ Inline hints; Display inline type hints (preview); Display return type hints (preview); Display inline parameter name hints (preview); +Use Transparent Compiler (experimental); Live Buffers; Use live (unsaved) buffers for analysis - Руководства по блочной структуре; + Руководства по блочной структуре; Показать рекомендации по структуре кода F#; Структурирование; Показать структурные и сворачиваемые узлы кода F#; @@ -52,7 +53,7 @@ Use live (unsaved) buffers for analysis Отображать подсказки типа возвращаемого значения (предварительная версия); Отображать подсказки имен встроенных параметров (предварительная версия); Динамические буферы; -Используйте для анализа живые (несохраненные) буферы. +Использовать динамические (несохраненные) буферы для проверки @@ -174,17 +175,17 @@ Enable parallel type checking with signature files; Enable parallel reference resolution; Enable fast find references & rename (restart required); Cache parsing results (experimental) - Параметры производительности проекта и кэширования F#; -Включить перекрестные ссылки проектов в памяти; -Enable_partial_type_checking; -Параметры производительности IntelliSense; -Включите устаревшие данные для функций IntelliSense; -Время до использования устаревших результатов (в миллисекундах); -Распараллеливание (требуется перезагрузка); -Включить параллельную проверку типов с файлами сигнатур; -Включить параллельное разрешение ссылок; -Включить быстрый поиск ссылок и переименование (требуется перезагрузка); -Результаты анализа кэша (экспериментальная функция) + Параметры производительности проекта F# и кэширования; +Включить перекрестные ссылки проекта в памяти; +Enable_partial_type_checking; +Параметры производительности IntelliSense; +Включить устаревшие данные для функций IntelliSense; +Время до использования устаревших результатов (в миллисекундах); +Параллелизация (требуется перезагрузка); +Включить параллельную проверку типов с файлами подписей; +Включить параллельное эталонное разрешение; +Включить быстрый поиск ссылок и переименование (экспериментально); +Результаты анализа кэша (экспериментально) @@ -202,20 +203,19 @@ Solid underline; Dot underline; Dash underline; Show remarks in Quick Info - Форматирование; -Предпочтительная ширина описания в символах; -Отформатируйте подпись до заданной ширины, добавив разрывы строк в соответствии с правилами синтаксиса F#; -Навигационные ссылки; -Показывать навигационные ссылки как; -Сплошное подчеркивание; -Точка подчеркивания; -Подчеркивание тире; -Показать заметки в кратких сведениях + Форматирование; +Предпочитаемая ширина описания в символах; +Форматирование подписи до заданной ширины путем добавления разрывов строк в соответствии с правилами синтаксиса F#; +Ссылки навигации; +Показать ссылки навигации как; +Сплошное подчеркивание; +Пунктирное подчеркивание; +Штриховое подчеркивание; Remarks: - Комментарии: + Remarks: @@ -230,7 +230,7 @@ Show remarks in Quick Info Remove unnecessary parentheses - Удалить ненужные круглые скобки + Remove unnecessary parentheses @@ -255,7 +255,7 @@ Show remarks in Quick Info Returns: - Возврат: + Returns: diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf index e943accb2c6..d720dc43d7d 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf @@ -24,7 +24,7 @@ Add return type annotation - Dönüş türü ek açıklaması ekle + Add return type annotation @@ -41,18 +41,19 @@ Inline hints; Display inline type hints (preview); Display return type hints (preview); Display inline parameter name hints (preview); +Use Transparent Compiler (experimental); Live Buffers; Use live (unsaved) buffers for analysis - Blok Yapısı Kılavuzları; + Blok Yapısı Kılavuzları; F# kodu için yapı yönergelerini göster; -Ana hat oluşturma; +Anahat oluşturma; F# kodu için ana hattı ve daraltılabilir düğümleri göster; Satır içi ipuçları; Satır içi tür ipuçlarını görüntüle (önizleme); Dönüş türü ipuçlarını görüntüle (önizleme); Satır içi parametre adı ipuçlarını görüntüle (önizleme); Canlı Arabellekler; -Analiz için canlı (kaydedilmemiş) arabellekleri kullan +Denetim için canlı (kaydedilmemiş) arabellekler kullan @@ -174,16 +175,16 @@ Enable parallel type checking with signature files; Enable parallel reference resolution; Enable fast find references & rename (restart required); Cache parsing results (experimental) - F# Proje ve Önbelleğe Alma Performansı Seçenekleri; + F# Proje ve Önbelleğe Alma Performans Seçenekleri; Bellek içi çapraz proje başvurularını etkinleştir; -Kısmi_tür_denetlemeyi_etkinleştir; +Enable_partial_type_checking; IntelliSense Performans Seçenekleri; IntelliSense özellikleri için eski verileri etkinleştir; -Eski sonuçların kullanılması için geçecek süre (milisaniye cinsinden); +Eski sonuçlar kullanılana kadar geçen süre (milisaniye olarak); Paralelleştirme (yeniden başlatma gerektirir); İmza dosyalarıyla paralel tür denetlemeyi etkinleştir; Paralel başvuru çözümlemeyi etkinleştir; -Başvuruları hızlı bulmayı ve yeniden adlandırmayı etkinleştir (yeniden başlatma gerektirir); +Başvuruları hızlı bulma ve yeniden adlandırmayı etkinleştir (deneysel) Ayrıştırma sonuçlarını önbelleğe al (deneysel) @@ -202,20 +203,19 @@ Solid underline; Dot underline; Dash underline; Show remarks in Quick Info - Biçimlendirme; + Biçimlendirme; Karakter olarak tercih edilen açıklama genişliği; -F# söz dizimi kurallarına uyan satır sonları ekleyerek imzayı belirtilen genişliğe biçimlendir; +F# söz dizimi kurallarına uyan satır sonları ekleyerek imzayı verilen genişliğe biçimlendir; Gezinti bağlantıları; -Gezinti bağlantılarını farklı göster; -Kesintisiz alt çizgi; +Gezinti bağlantılarını şöyle göster; +Düz alt çizgi; Nokta alt çizgi; -Tire alt çizgi; -Açıklamaları Hızlı Bilgide göster +Tire alt çizgisi; Remarks: - Açıklamalar: + Remarks: @@ -230,7 +230,7 @@ Açıklamaları Hızlı Bilgide göster Remove unnecessary parentheses - Gereksiz parantezleri kaldır + Remove unnecessary parentheses @@ -255,7 +255,7 @@ Açıklamaları Hızlı Bilgide göster Returns: - Şunu döndürür: + Returns: diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf index 5e7381c2da5..43eaadf6593 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf @@ -24,7 +24,7 @@ Add return type annotation - 添加返回类型批注 + Add return type annotation @@ -41,18 +41,19 @@ Inline hints; Display inline type hints (preview); Display return type hints (preview); Display inline parameter name hints (preview); +Use Transparent Compiler (experimental); Live Buffers; Use live (unsaved) buffers for analysis - 块结构指南; -显示 F# 代码的结构指南; -概述; -显示 F# 代码的大纲显示节点和可折叠节点; -内联提示; -显示内联类型提示(预览); -显示返回类型提示(预览); -显示内联参数名称提示(预览版); -实时缓冲区; -使用实时(未保存)缓冲区进行分析 + 块结构指南; +显示 F# 代码的结构指南; +大纲; +显示 F# 代码的大纲和可折叠节点; +内联提示; +显示内联类型提示(预览); +显示返回类型提示(预览); +显示内联参数名称提示(预览); +实时缓冲区; +使用实时(未保存)缓冲区进行检查 @@ -174,17 +175,17 @@ Enable parallel type checking with signature files; Enable parallel reference resolution; Enable fast find references & rename (restart required); Cache parsing results (experimental) - F# 项目和缓存性能选项; -启用内存中跨项目引用; -Enable_partial_type_checking; -IntelliSense 性能选项; -为 IntelliSense 功能启用过时数据; -使用过时结果前等待的时间(以毫秒计); -并行化(需要重启); -使用签名文件启用并行类型检查; -启用并行引用解析; -启用快速查找引用和重命名(需要重新启动); -缓存分析结果(实验性) + F# 项目和缓存性能选项; +启用内存中的跨项目引用; +启用部分类型检查; +IntelliSense 性能选项; +为智能感知功能启用旧数据; +使用陈旧结果的时间(以毫秒为单位); +并行处理(需要重新启动); +使用签名文件进行并行类型检查; +启用并行引用解析; +启用快速查找参考和重命名(实验性); +缓存解析结果(实验性)。 @@ -202,20 +203,19 @@ Solid underline; Dot underline; Dash underline; Show remarks in Quick Info - 格式设置; -以字符为单位的首选说明宽度; -通过添加符合 F# 语法规则的换行符,将签名设置为给定宽度的格式; + 格式设置; +首选描述宽度 (以字符为单位); +通过添加符合 F# 语法规则的换行符,将签名格式设置为指定宽度; 导航链接; -导航链接显示方式; -实线下划线; +将导航链接显示为; +实心下划线; 点下划线; -短线下划线; -在快速信息中显示备注 +短划线下划线; Remarks: - 注解: + Remarks: @@ -230,7 +230,7 @@ Show remarks in Quick Info Remove unnecessary parentheses - 移除不必要的括号 + Remove unnecessary parentheses @@ -255,7 +255,7 @@ Show remarks in Quick Info Returns: - 返回: + Returns: diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf index 7152105652b..96f071fb27e 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf @@ -24,7 +24,7 @@ Add return type annotation - 新增傳回類型註釋 + Add return type annotation @@ -41,18 +41,19 @@ Inline hints; Display inline type hints (preview); Display return type hints (preview); Display inline parameter name hints (preview); +Use Transparent Compiler (experimental); Live Buffers; Use live (unsaved) buffers for analysis - 區塊結構輔助線; -顯示 F# 程式碼的結構方針; + 區塊結構輔助線; +顯示 F# 程式碼的結構輔助線; 大綱; 顯示 F# 程式碼的大綱與可折疊的節點; 內嵌提示; 顯示內嵌類型提示 (預覽); -顯示傳回類型提示 (預覽); +顯示傳回型別提示 (預覽); 顯示內嵌參數名稱提示 (預覽); 即時緩衝區; -使用即時 (未儲存) 緩衝區進行分析 +使用即時 (未儲存) 緩衝區進行檢查 @@ -174,16 +175,16 @@ Enable parallel type checking with signature files; Enable parallel reference resolution; Enable fast find references & rename (restart required); Cache parsing results (experimental) - F# 專案與快取效能選項; -允許記憶體內跨專案參考; -啟用部分型別檢查; -IntelliSense 效能選項; -為 IntelliSense 功能啟用過時資料; -使用過時結果前等待的時間 (毫秒); -平行處理 (需要重新啟動); -啟用簽章檔案的平行類型檢查; -啟用平行參考解析; -啟用快速尋找參考和重新命名 (需要重新啟動); + F# 專案和快取效能選項; +啟用記憶體內跨專案參考; +Enable_partial_type_checking; +IntelliSense 效能選項; +啟用 IntelliSense 功能的過時資料; +使用過時結果之前的時間 (毫秒); +平行化 (需要重新開機); +啟用平行類型檢查與簽章檔案; +啟用平行參考解析; +啟用快速尋找參考和重新命名 (實驗性); 快取剖析結果 (實驗性) @@ -202,20 +203,19 @@ Solid underline; Dot underline; Dash underline; Show remarks in Quick Info - 格式化; -慣用説明寬度 (以字元為單位); -透過新增符合 F# 語法規則的分行符號,將簽章格式設定為指定寬度; -導覽連結; -顯示導覽連結為; -實線底線; -點線底線; -虛點底線; -在快速諮詢中顯示備註 + 格式化; +慣用描述寬度 (以字元為單位); +透過新增符合 F# 語法規則的分行符號,將簽章格式設定為指定寬度; +瀏覽連結; +將瀏覽連結顯示為; +實心底線; +點底線; +虛線底線; Remarks: - 備註: + Remarks: @@ -230,7 +230,7 @@ Show remarks in Quick Info Remove unnecessary parentheses - 移除不必要的括號 + Remove unnecessary parentheses @@ -255,7 +255,7 @@ Show remarks in Quick Info Returns: - 傳回: + Returns: diff --git a/vsintegration/src/FSharp.UIResources/AdvancedOptionsControl.xaml b/vsintegration/src/FSharp.UIResources/AdvancedOptionsControl.xaml index ecabe2a56fd..15397d068ec 100644 --- a/vsintegration/src/FSharp.UIResources/AdvancedOptionsControl.xaml +++ b/vsintegration/src/FSharp.UIResources/AdvancedOptionsControl.xaml @@ -38,6 +38,17 @@ + + + + diff --git a/vsintegration/src/FSharp.UIResources/LanguageServicePerformanceOptionControl.xaml b/vsintegration/src/FSharp.UIResources/LanguageServicePerformanceOptionControl.xaml index 96b8915b80e..a5c5f1c0b6f 100644 --- a/vsintegration/src/FSharp.UIResources/LanguageServicePerformanceOptionControl.xaml +++ b/vsintegration/src/FSharp.UIResources/LanguageServicePerformanceOptionControl.xaml @@ -23,6 +23,28 @@ IsChecked="{Binding EnableInMemoryCrossProjectReferences}" Content="{x:Static local:Strings.Enable_in_memory_cross_project_references}" ToolTip="{x:Static local:Strings.Tooltip_in_memory_cross_project_references}"/> + + + + + + @@ -46,7 +68,7 @@ Content="{x:Static local:Strings.Time_until_stale_completion}" Margin="15 0 0 0"/> diff --git a/vsintegration/src/FSharp.UIResources/Strings.Designer.cs b/vsintegration/src/FSharp.UIResources/Strings.Designer.cs index 57b599de866..b166f234ccd 100644 --- a/vsintegration/src/FSharp.UIResources/Strings.Designer.cs +++ b/vsintegration/src/FSharp.UIResources/Strings.Designer.cs @@ -1,10 +1,10 @@ //------------------------------------------------------------------------------ // -// 此代码由工具生成。 -// 运行时版本:4.0.30319.42000 +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 // -// 对此文件的更改可能会导致不正确的行为,并且如果 -// 重新生成代码,这些更改将会丢失。 +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. // //------------------------------------------------------------------------------ @@ -13,12 +13,12 @@ namespace Microsoft.VisualStudio.FSharp.UIResources { /// - /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// A strongly-typed resource class, for looking up localized strings, etc. /// - // 此类是由 StronglyTypedResourceBuilder - // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 - // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen - // (以 /str 作为命令选项),或重新生成 VS 项目。 + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] @@ -33,7 +33,7 @@ internal Strings() { } /// - /// 返回此类使用的缓存的 ResourceManager 实例。 + /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] public static global::System.Resources.ResourceManager ResourceManager { @@ -47,8 +47,8 @@ internal Strings() { } /// - /// 重写当前线程的 CurrentUICulture 属性,对 - /// 使用此强类型资源类的所有资源查找执行重写。 + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] public static global::System.Globalization.CultureInfo Culture { @@ -61,7 +61,7 @@ internal Strings() { } /// - /// 查找类似 Additional performance telemetry (experimental) 的本地化字符串。 + /// Looks up a localized string similar to Additional performance telemetry (experimental). /// public static string AdditionalTelemetry { get { @@ -70,7 +70,7 @@ public static string AdditionalTelemetry { } /// - /// 查找类似 Always place open statements at the top level 的本地化字符串。 + /// Looks up a localized string similar to Always place open statements at the top level. /// public static string Always_place_opens_at_top_level { get { @@ -79,7 +79,7 @@ public static string Always_place_opens_at_top_level { } /// - /// 查找类似 Keep analyzing the entire solution for diagnostics as a low priority background task (requires restart) 的本地化字符串。 + /// Looks up a localized string similar to Keep analyzing the entire solution for diagnostics as a low priority background task (requires restart). /// public static string Analyze_full_solution_on_background { get { @@ -88,7 +88,7 @@ public static string Analyze_full_solution_on_background { } /// - /// 查找类似 Background analysis 的本地化字符串。 + /// Looks up a localized string similar to Background analysis. /// public static string Background_analysis { get { @@ -97,7 +97,7 @@ public static string Background_analysis { } /// - /// 查找类似 Block Structure Guides 的本地化字符串。 + /// Looks up a localized string similar to Block Structure Guides. /// public static string Block_Structure { get { @@ -106,7 +106,7 @@ public static string Block_Structure { } /// - /// 查找类似 Code Fixes 的本地化字符串。 + /// Looks up a localized string similar to Code Fixes. /// public static string Code_Fixes { get { @@ -115,7 +115,7 @@ public static string Code_Fixes { } /// - /// 查找类似 Completion Lists 的本地化字符串。 + /// Looks up a localized string similar to Completion Lists. /// public static string Completion_Lists { get { @@ -124,7 +124,7 @@ public static string Completion_Lists { } /// - /// 查找类似 D_ash underline 的本地化字符串。 + /// Looks up a localized string similar to D_ash underline. /// public static string Dash_underline { get { @@ -133,7 +133,7 @@ public static string Dash_underline { } /// - /// 查找类似 Diagnostics 的本地化字符串。 + /// Looks up a localized string similar to Diagnostics. /// public static string Diagnostics { get { @@ -142,7 +142,7 @@ public static string Diagnostics { } /// - /// 查找类似 D_ot underline 的本地化字符串。 + /// Looks up a localized string similar to D_ot underline. /// public static string Dot_underline { get { @@ -151,7 +151,7 @@ public static string Dot_underline { } /// - /// 查找类似 Keep background symbol keys 的本地化字符串。 + /// Looks up a localized string similar to Keep background symbol keys. /// public static string Enable_Background_ItemKeyStore_And_Semantic_Classification { get { @@ -160,7 +160,7 @@ public static string Enable_Background_ItemKeyStore_And_Semantic_Classification } /// - /// 查找类似 Enable fast find references & rename (experimental) 的本地化字符串。 + /// Looks up a localized string similar to Enable fast find references & rename (experimental). /// public static string Enable_Fast_Find_References { get { @@ -169,7 +169,7 @@ public static string Enable_Fast_Find_References { } /// - /// 查找类似 _Enable in-memory cross project references 的本地化字符串。 + /// Looks up a localized string similar to _Enable in-memory cross project references. /// public static string Enable_in_memory_cross_project_references { get { @@ -178,7 +178,7 @@ public static string Enable_in_memory_cross_project_references { } /// - /// 查找类似 Use live (unsaved) buffers for analysis (restart required) 的本地化字符串。 + /// Looks up a localized string similar to Use live (unsaved) buffers for analysis (restart required). /// public static string Enable_Live_Buffers { get { @@ -187,7 +187,7 @@ public static string Enable_Live_Buffers { } /// - /// 查找类似 Enable parallel reference resolution 的本地化字符串。 + /// Looks up a localized string similar to Enable parallel reference resolution. /// public static string Enable_Parallel_Reference_Resolution { get { @@ -196,7 +196,7 @@ public static string Enable_Parallel_Reference_Resolution { } /// - /// 查找类似 Enable partial type checking 的本地化字符串。 + /// Looks up a localized string similar to Enable partial type checking. /// public static string Enable_partial_type_checking { get { @@ -205,7 +205,7 @@ public static string Enable_partial_type_checking { } /// - /// 查找类似 Enable stale data for IntelliSense features 的本地化字符串。 + /// Looks up a localized string similar to Enable stale data for IntelliSense features. /// public static string Enable_Stale_IntelliSense_Results { get { @@ -214,7 +214,7 @@ public static string Enable_Stale_IntelliSense_Results { } /// - /// 查找类似 Always add new line on enter 的本地化字符串。 + /// Looks up a localized string similar to Always add new line on enter. /// public static string Enter_key_always { get { @@ -223,7 +223,7 @@ public static string Enter_key_always { } /// - /// 查找类似 Never add new line on enter 的本地化字符串。 + /// Looks up a localized string similar to Never add new line on enter. /// public static string Enter_key_never { get { @@ -232,7 +232,7 @@ public static string Enter_key_never { } /// - /// 查找类似 Only add new line on enter after end of fully typed word 的本地化字符串。 + /// Looks up a localized string similar to Only add new line on enter after end of fully typed word. /// public static string Enter_key_only { get { @@ -241,7 +241,7 @@ public static string Enter_key_only { } /// - /// 查找类似 Enter key behavior 的本地化字符串。 + /// Looks up a localized string similar to Enter key behavior. /// public static string Enter_Key_Rule { get { @@ -250,7 +250,7 @@ public static string Enter_Key_Rule { } /// - /// 查找类似 Find References Performance Options 的本地化字符串。 + /// Looks up a localized string similar to Find References Performance Options. /// public static string Find_References_Performance { get { @@ -259,7 +259,7 @@ public static string Find_References_Performance { } /// - /// 查找类似 Re-format indentation on paste (Experimental) 的本地化字符串。 + /// Looks up a localized string similar to Re-format indentation on paste (Experimental). /// public static string Format_on_paste { get { @@ -268,7 +268,7 @@ public static string Format_on_paste { } /// - /// 查找类似 Formatting 的本地化字符串。 + /// Looks up a localized string similar to Formatting. /// public static string Formatting { get { @@ -277,7 +277,7 @@ public static string Formatting { } /// - /// 查找类似 Inline Hints 的本地化字符串。 + /// Looks up a localized string similar to Inline Hints. /// public static string Inline_Hints { get { @@ -286,7 +286,7 @@ public static string Inline_Hints { } /// - /// 查找类似 IntelliSense Performance Options 的本地化字符串。 + /// Looks up a localized string similar to IntelliSense Performance Options. /// public static string IntelliSense_Performance { get { @@ -295,7 +295,7 @@ public static string IntelliSense_Performance { } /// - /// 查找类似 Keep all background intermediate resolutions (increases memory usage) 的本地化字符串。 + /// Looks up a localized string similar to Keep all background intermediate resolutions (increases memory usage). /// public static string Keep_All_Background_Resolutions { get { @@ -304,7 +304,7 @@ public static string Keep_All_Background_Resolutions { } /// - /// 查找类似 Keep all background symbol uses (increases memory usage) 的本地化字符串。 + /// Looks up a localized string similar to Keep all background symbol uses (increases memory usage). /// public static string Keep_All_Background_Symbol_Uses { get { @@ -313,7 +313,7 @@ public static string Keep_All_Background_Symbol_Uses { } /// - /// 查找类似 Performance 的本地化字符串。 + /// Looks up a localized string similar to Performance. /// public static string Language_Service_Performance { get { @@ -322,7 +322,7 @@ public static string Language_Service_Performance { } /// - /// 查找类似 Language service settings (advanced) 的本地化字符串。 + /// Looks up a localized string similar to Language service settings (advanced). /// public static string Language_Service_Settings { get { @@ -331,7 +331,7 @@ public static string Language_Service_Settings { } /// - /// 查找类似 Live Buffers 的本地化字符串。 + /// Looks up a localized string similar to Live Buffers. /// public static string LiveBuffers { get { @@ -340,7 +340,7 @@ public static string LiveBuffers { } /// - /// 查找类似 Navigation links 的本地化字符串。 + /// Looks up a localized string similar to Navigation links. /// public static string Navigation_links { get { @@ -349,7 +349,7 @@ public static string Navigation_links { } /// - /// 查找类似 Outlining 的本地化字符串。 + /// Looks up a localized string similar to Outlining. /// public static string Outlining { get { @@ -358,7 +358,7 @@ public static string Outlining { } /// - /// 查找类似 Parallelization (requires restart) 的本地化字符串。 + /// Looks up a localized string similar to Parallelization (requires restart). /// public static string Parallelization { get { @@ -367,7 +367,7 @@ public static string Parallelization { } /// - /// 查找类似 Preferred description width in characters 的本地化字符串。 + /// Looks up a localized string similar to Preferred description width in characters. /// public static string Preferred_description_width_in_characters { get { @@ -376,7 +376,7 @@ public static string Preferred_description_width_in_characters { } /// - /// 查找类似 F# Project and Caching Performance Options 的本地化字符串。 + /// Looks up a localized string similar to F# Project and Caching Performance Options. /// public static string Project_Performance { get { @@ -385,7 +385,7 @@ public static string Project_Performance { } /// - /// 查找类似 Remove unnecessary parentheses (experimental, might affect typing performance) 的本地化字符串。 + /// Looks up a localized string similar to Remove unnecessary parentheses (experimental, might affect typing performance). /// public static string Remove_parens_code_fix { get { @@ -394,7 +394,7 @@ public static string Remove_parens_code_fix { } /// - /// 查找类似 Send additional performance telemetry 的本地化字符串。 + /// Looks up a localized string similar to Send additional performance telemetry. /// public static string Send_Additional_Telemetry { get { @@ -403,7 +403,7 @@ public static string Send_Additional_Telemetry { } /// - /// 查找类似 Show s_ymbols in unopened namespaces 的本地化字符串。 + /// Looks up a localized string similar to Show s_ymbols in unopened namespaces. /// public static string Show_all_symbols { get { @@ -412,7 +412,7 @@ public static string Show_all_symbols { } /// - /// 查找类似 Show completion list after a character is _deleted 的本地化字符串。 + /// Looks up a localized string similar to Show completion list after a character is _deleted. /// public static string Show_completion_list_after_a_character_is_deleted { get { @@ -421,7 +421,7 @@ public static string Show_completion_list_after_a_character_is_deleted { } /// - /// 查找类似 _Show completion list after a character is typed 的本地化字符串。 + /// Looks up a localized string similar to _Show completion list after a character is typed. /// public static string Show_completion_list_after_a_character_is_typed { get { @@ -430,7 +430,7 @@ public static string Show_completion_list_after_a_character_is_typed { } /// - /// 查找类似 Show structure guidelines for F# code 的本地化字符串。 + /// Looks up a localized string similar to Show structure guidelines for F# code. /// public static string Show_guides { get { @@ -439,7 +439,7 @@ public static string Show_guides { } /// - /// 查找类似 Display inline parameter name hints (preview) 的本地化字符串。 + /// Looks up a localized string similar to Display inline parameter name hints (preview). /// public static string Show_Inline_Parameter_Name_Hints { get { @@ -448,7 +448,7 @@ public static string Show_Inline_Parameter_Name_Hints { } /// - /// 查找类似 Display inline type hints (preview) 的本地化字符串。 + /// Looks up a localized string similar to Display inline type hints (preview). /// public static string Show_Inline_Type_Hints { get { @@ -457,7 +457,7 @@ public static string Show_Inline_Type_Hints { } /// - /// 查找类似 S_how navigation links as 的本地化字符串。 + /// Looks up a localized string similar to S_how navigation links as. /// public static string Show_navigation_links_as { get { @@ -466,7 +466,7 @@ public static string Show_navigation_links_as { } /// - /// 查找类似 Show outlining and collapsible nodes for F# code 的本地化字符串。 + /// Looks up a localized string similar to Show outlining and collapsible nodes for F# code. /// public static string Show_Outlining { get { @@ -475,7 +475,7 @@ public static string Show_Outlining { } /// - /// 查找类似 Show remarks in Quick Info 的本地化字符串。 + /// Looks up a localized string similar to Show remarks in Quick Info. /// public static string Show_remarks_in_Quick_Info { get { @@ -484,7 +484,7 @@ public static string Show_remarks_in_Quick_Info { } /// - /// 查找类似 Display return type hints (preview) 的本地化字符串。 + /// Looks up a localized string similar to Display return type hints (preview). /// public static string Show_Return_Type_Hints { get { @@ -493,7 +493,7 @@ public static string Show_Return_Type_Hints { } /// - /// 查找类似 Simplify names (remove unnecessary qualifiers) 的本地化字符串。 + /// Looks up a localized string similar to Simplify names (remove unnecessary qualifiers). /// public static string Simplify_name_code_fix { get { @@ -502,7 +502,7 @@ public static string Simplify_name_code_fix { } /// - /// 查找类似 _Solid underline 的本地化字符串。 + /// Looks up a localized string similar to _Solid underline. /// public static string Solid_underline { get { @@ -511,7 +511,7 @@ public static string Solid_underline { } /// - /// 查找类似 Suggest names for unresolved identifiers 的本地化字符串。 + /// Looks up a localized string similar to Suggest names for unresolved identifiers. /// public static string Suggest_names_for_errors_code_fix { get { @@ -520,7 +520,7 @@ public static string Suggest_names_for_errors_code_fix { } /// - /// 查找类似 Text hover 的本地化字符串。 + /// Looks up a localized string similar to Text hover. /// public static string Text_hover { get { @@ -529,7 +529,7 @@ public static string Text_hover { } /// - /// 查找类似 Time until stale results are used (in milliseconds) 的本地化字符串。 + /// Looks up a localized string similar to Time until stale results are used (in milliseconds). /// public static string Time_until_stale_completion { get { @@ -538,7 +538,7 @@ public static string Time_until_stale_completion { } /// - /// 查找类似 In-memory cross-project references store project-level data in memory to allow IDE features to work across projects. 的本地化字符串。 + /// Looks up a localized string similar to In-memory cross-project references store project-level data in memory to allow IDE features to work across projects.. /// public static string Tooltip_in_memory_cross_project_references { get { @@ -547,7 +547,7 @@ public static string Tooltip_in_memory_cross_project_references { } /// - /// 查找类似 Format signature to the given width by adding line breaks conforming with F# syntax rules. 的本地化字符串。 + /// Looks up a localized string similar to Format signature to the given width by adding line breaks conforming with F# syntax rules. . /// public static string Tooltip_preferred_description_width_in_characters { get { @@ -556,7 +556,70 @@ public static string Tooltip_preferred_description_width_in_characters { } /// - /// 查找类似 Analyze and suggest fixes for unused values 的本地化字符串。 + /// Looks up a localized string similar to Transparent Compiler Cache Factor. + /// + public static string Transparent_Compiler_Cache_Factor { + get { + return ResourceManager.GetString("Transparent_Compiler_Cache_Factor", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Higher number means more memory will be used for caching. Changing the value wipes cache.. + /// + public static string Transparent_Compiler_Cache_Factor_Tooltip { + get { + return ResourceManager.GetString("Transparent_Compiler_Cache_Factor_Tooltip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Create new project snapshots from existing ones. + /// + public static string Transparent_Compiler_Snapshot_Reuse { + get { + return ResourceManager.GetString("Transparent_Compiler_Snapshot_Reuse", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Transparent Compiler (experimental). + /// + public static string TransparentCompiler { + get { + return ResourceManager.GetString("TransparentCompiler", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results.. + /// + public static string TransparentCompiler_Discalimer1 { + get { + return ResourceManager.GetString("TransparentCompiler_Discalimer1", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use at your own risk!. + /// + public static string TransparentCompiler_Discalimer2 { + get { + return ResourceManager.GetString("TransparentCompiler_Discalimer2", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to By checking this you also opt-in for additional performance telemetry. + /// + public static string TransparentCompiler_Discalimer3 { + get { + return ResourceManager.GetString("TransparentCompiler_Discalimer3", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Analyze and suggest fixes for unused values. /// public static string Unused_declaration_code_fix { get { @@ -565,7 +628,7 @@ public static string Unused_declaration_code_fix { } /// - /// 查找类似 Remove unused open statements 的本地化字符串。 + /// Looks up a localized string similar to Remove unused open statements. /// public static string Unused_opens_code_fix { get { @@ -574,12 +637,21 @@ public static string Unused_opens_code_fix { } /// - /// 查找类似 Cache parsing results (experimental) 的本地化字符串。 + /// Looks up a localized string similar to Cache parsing results (experimental). /// public static string Use_syntax_tree_cache { get { return ResourceManager.GetString("Use_syntax_tree_cache", resourceCulture); } } + + /// + /// Looks up a localized string similar to Use Transparent Compiler (restart required). + /// + public static string Use_Transparent_Compiler { + get { + return ResourceManager.GetString("Use_Transparent_Compiler", resourceCulture); + } + } } } diff --git a/vsintegration/src/FSharp.UIResources/Strings.resx b/vsintegration/src/FSharp.UIResources/Strings.resx index 7feb60ef6ae..58821d8b8be 100644 --- a/vsintegration/src/FSharp.UIResources/Strings.resx +++ b/vsintegration/src/FSharp.UIResources/Strings.resx @@ -279,16 +279,40 @@ Display return type hints (preview) + + Transparent Compiler (experimental) + + + Use Transparent Compiler (restart required) + Keep analyzing the entire solution for diagnostics as a low priority background task (requires restart) Background analysis + + Transparent Compiler Cache Factor + + + Higher number means more memory will be used for caching. Changing the value wipes cache. + Remove unnecessary parentheses (experimental, might affect typing performance) Show remarks in Quick Info + + Create new project snapshots from existing ones + + + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + + + Use at your own risk! + + + By checking this you also opt-in for additional performance telemetry + \ No newline at end of file diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.cs.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.cs.xlf index 8b6d86d1061..c0072448173 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.cs.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.cs.xlf @@ -202,6 +202,41 @@ Umožňuje formátovat podpis na danou šířku přidáním konců řádků odpovídajících pravidlům syntaxe F#. + + Transparent Compiler (experimental) + Transparent Compiler (experimental) + + + + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + + + + Use at your own risk! + Use at your own risk! + + + + By checking this you also opt-in for additional performance telemetry + By checking this you also opt-in for additional performance telemetry + + + + Transparent Compiler Cache Factor + Transparent Compiler Cache Factor + + + + Higher number means more memory will be used for caching. Changing the value wipes cache. + Higher number means more memory will be used for caching. Changing the value wipes cache. + + + + Create new project snapshots from existing ones + Create new project snapshots from existing ones + + Remove unused open statements Odebrat nepoužívané otevřené výkazy @@ -287,6 +322,11 @@ Navrhovat názvy pro nerozpoznané identifikátory + + Use Transparent Compiler (restart required) + Use Transparent Compiler (restart required) + + Cache parsing results (experimental) Výsledky analýzy mezipaměti (experimentální) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.de.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.de.xlf index 786ef654691..8afb1d8e999 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.de.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.de.xlf @@ -202,6 +202,41 @@ Formatieren Sie die Signatur in der angegebenen Breite, indem Sie Zeilenumbrüche hinzufügen, die F#-Syntaxregeln entsprechen. + + Transparent Compiler (experimental) + Transparent Compiler (experimental) + + + + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + + + + Use at your own risk! + Use at your own risk! + + + + By checking this you also opt-in for additional performance telemetry + By checking this you also opt-in for additional performance telemetry + + + + Transparent Compiler Cache Factor + Transparent Compiler Cache Factor + + + + Higher number means more memory will be used for caching. Changing the value wipes cache. + Higher number means more memory will be used for caching. Changing the value wipes cache. + + + + Create new project snapshots from existing ones + Create new project snapshots from existing ones + + Remove unused open statements Nicht verwendete "open"-Anweisungen entfernen @@ -287,6 +322,11 @@ Namen für nicht aufgelöste Bezeichner vorschlagen + + Use Transparent Compiler (restart required) + Use Transparent Compiler (restart required) + + Cache parsing results (experimental) Cacheanalyseergebnisse (experimentell) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.es.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.es.xlf index 64c0eaadbe2..eb0289c938c 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.es.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.es.xlf @@ -202,6 +202,41 @@ Da formato a la firma al ancho dado agregando saltos de línea conforme a las reglas de sintaxis de F#. + + Transparent Compiler (experimental) + Transparent Compiler (experimental) + + + + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + + + + Use at your own risk! + Use at your own risk! + + + + By checking this you also opt-in for additional performance telemetry + By checking this you also opt-in for additional performance telemetry + + + + Transparent Compiler Cache Factor + Transparent Compiler Cache Factor + + + + Higher number means more memory will be used for caching. Changing the value wipes cache. + Higher number means more memory will be used for caching. Changing the value wipes cache. + + + + Create new project snapshots from existing ones + Create new project snapshots from existing ones + + Remove unused open statements Quitar instrucciones open no usadas @@ -287,6 +322,11 @@ Sugerir nombres para los identificadores no resueltos + + Use Transparent Compiler (restart required) + Use Transparent Compiler (restart required) + + Cache parsing results (experimental) Resultados del análisis de la caché (experimental) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.fr.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.fr.xlf index 985e5ccf7b2..09430b644cc 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.fr.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.fr.xlf @@ -202,6 +202,41 @@ Formatez la signature à la largeur donnée en ajoutant des sauts de ligne conformes aux règles de syntaxe F#. + + Transparent Compiler (experimental) + Transparent Compiler (experimental) + + + + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + + + + Use at your own risk! + Use at your own risk! + + + + By checking this you also opt-in for additional performance telemetry + By checking this you also opt-in for additional performance telemetry + + + + Transparent Compiler Cache Factor + Transparent Compiler Cache Factor + + + + Higher number means more memory will be used for caching. Changing the value wipes cache. + Higher number means more memory will be used for caching. Changing the value wipes cache. + + + + Create new project snapshots from existing ones + Create new project snapshots from existing ones + + Remove unused open statements Supprimer les instructions open inutilisées @@ -287,6 +322,11 @@ Suggérer des noms pour les identificateurs non résolus + + Use Transparent Compiler (restart required) + Use Transparent Compiler (restart required) + + Cache parsing results (experimental) Résultats de l'analyse du cache (expérimental) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.it.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.it.xlf index 3c03c240b4d..640894aac42 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.it.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.it.xlf @@ -202,6 +202,41 @@ Consente di formattare la firma in base alla larghezza specificata aggiungendo interruzioni di riga conformi alle regole di sintassi F#. + + Transparent Compiler (experimental) + Transparent Compiler (experimental) + + + + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + + + + Use at your own risk! + Use at your own risk! + + + + By checking this you also opt-in for additional performance telemetry + By checking this you also opt-in for additional performance telemetry + + + + Transparent Compiler Cache Factor + Transparent Compiler Cache Factor + + + + Higher number means more memory will be used for caching. Changing the value wipes cache. + Higher number means more memory will be used for caching. Changing the value wipes cache. + + + + Create new project snapshots from existing ones + Create new project snapshots from existing ones + + Remove unused open statements Rimuovi istruzioni OPEN inutilizzate @@ -287,6 +322,11 @@ Suggerisci nomi per gli identificatori non risolti + + Use Transparent Compiler (restart required) + Use Transparent Compiler (restart required) + + Cache parsing results (experimental) Risultati dell'analisi della cache (sperimentale) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.ja.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.ja.xlf index 5518aa00bee..7267c7b7061 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.ja.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.ja.xlf @@ -202,6 +202,41 @@ F# 構文規則に準拠した改行を追加して、署名を指定された幅に書式設定します。 + + Transparent Compiler (experimental) + Transparent Compiler (experimental) + + + + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + + + + Use at your own risk! + Use at your own risk! + + + + By checking this you also opt-in for additional performance telemetry + By checking this you also opt-in for additional performance telemetry + + + + Transparent Compiler Cache Factor + Transparent Compiler Cache Factor + + + + Higher number means more memory will be used for caching. Changing the value wipes cache. + Higher number means more memory will be used for caching. Changing the value wipes cache. + + + + Create new project snapshots from existing ones + Create new project snapshots from existing ones + + Remove unused open statements 未使用の Open ステートメントを削除する @@ -287,6 +322,11 @@ 未解決の識別子の名前を提案します + + Use Transparent Compiler (restart required) + Use Transparent Compiler (restart required) + + Cache parsing results (experimental) キャッシュ解析の結果 (試験段階) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.ko.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.ko.xlf index 0af709880db..cfb0da2f9b4 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.ko.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.ko.xlf @@ -202,6 +202,41 @@ F# 구문 규칙에 맞는 줄 바꿈을 추가하여 지정된 너비에 시그니처의 서식을 지정합니다. + + Transparent Compiler (experimental) + Transparent Compiler (experimental) + + + + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + + + + Use at your own risk! + Use at your own risk! + + + + By checking this you also opt-in for additional performance telemetry + By checking this you also opt-in for additional performance telemetry + + + + Transparent Compiler Cache Factor + Transparent Compiler Cache Factor + + + + Higher number means more memory will be used for caching. Changing the value wipes cache. + Higher number means more memory will be used for caching. Changing the value wipes cache. + + + + Create new project snapshots from existing ones + Create new project snapshots from existing ones + + Remove unused open statements 사용되지 않는 open 문 제거 @@ -287,6 +322,11 @@ 확인되지 않은 식별자의 이름 제안 + + Use Transparent Compiler (restart required) + Use Transparent Compiler (restart required) + + Cache parsing results (experimental) 캐시 구문 분석 결과(실험적) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.pl.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.pl.xlf index 9cf39c5e71a..4fb4ff03b40 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.pl.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.pl.xlf @@ -202,6 +202,41 @@ Sformatuj sygnaturę na daną szerokość, dodając podziały wierszy zgodne z regułami składni języka F#. + + Transparent Compiler (experimental) + Transparent Compiler (experimental) + + + + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + + + + Use at your own risk! + Use at your own risk! + + + + By checking this you also opt-in for additional performance telemetry + By checking this you also opt-in for additional performance telemetry + + + + Transparent Compiler Cache Factor + Transparent Compiler Cache Factor + + + + Higher number means more memory will be used for caching. Changing the value wipes cache. + Higher number means more memory will be used for caching. Changing the value wipes cache. + + + + Create new project snapshots from existing ones + Create new project snapshots from existing ones + + Remove unused open statements Usuń nieużywane otwarte instrukcje @@ -287,6 +322,11 @@ Sugeruj nazwy w przypadku nierozpoznanych identyfikatorów + + Use Transparent Compiler (restart required) + Use Transparent Compiler (restart required) + + Cache parsing results (experimental) Wyniki analizy pamięci podręcznej (eksperymentalne) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.pt-BR.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.pt-BR.xlf index 2b8b3afc95c..f2778bcbd8b 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.pt-BR.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.pt-BR.xlf @@ -202,6 +202,41 @@ Formate a assinatura para a largura fornecida adicionando quebras de linha em conformidade com as regras de sintaxe F#. + + Transparent Compiler (experimental) + Transparent Compiler (experimental) + + + + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + + + + Use at your own risk! + Use at your own risk! + + + + By checking this you also opt-in for additional performance telemetry + By checking this you also opt-in for additional performance telemetry + + + + Transparent Compiler Cache Factor + Transparent Compiler Cache Factor + + + + Higher number means more memory will be used for caching. Changing the value wipes cache. + Higher number means more memory will be used for caching. Changing the value wipes cache. + + + + Create new project snapshots from existing ones + Create new project snapshots from existing ones + + Remove unused open statements Remover instruções abertas não usadas @@ -287,6 +322,11 @@ Sugerir nomes para identificadores não resolvidos + + Use Transparent Compiler (restart required) + Use Transparent Compiler (restart required) + + Cache parsing results (experimental) Resultados da análise de cache (experimental) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.ru.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.ru.xlf index 21644c83141..9b99816b43c 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.ru.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.ru.xlf @@ -202,6 +202,41 @@ Форматирование подписи до заданной ширины путем добавления разрывов строк, соответствующих правилам синтаксиса F#. + + Transparent Compiler (experimental) + Transparent Compiler (experimental) + + + + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + + + + Use at your own risk! + Use at your own risk! + + + + By checking this you also opt-in for additional performance telemetry + By checking this you also opt-in for additional performance telemetry + + + + Transparent Compiler Cache Factor + Transparent Compiler Cache Factor + + + + Higher number means more memory will be used for caching. Changing the value wipes cache. + Higher number means more memory will be used for caching. Changing the value wipes cache. + + + + Create new project snapshots from existing ones + Create new project snapshots from existing ones + + Remove unused open statements Удалить неиспользуемые открытые операторы @@ -287,6 +322,11 @@ Предлагать имена для неразрешенных идентификаторов + + Use Transparent Compiler (restart required) + Use Transparent Compiler (restart required) + + Cache parsing results (experimental) Результаты анализа кэша (экспериментальная функция) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.tr.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.tr.xlf index dd8be3331e4..e6afdd2e2b1 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.tr.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.tr.xlf @@ -202,6 +202,41 @@ F# söz dizimi kurallarına uyan satır sonları ekleyerek imzayı belirtilen genişliğe biçimlendirin. + + Transparent Compiler (experimental) + Transparent Compiler (experimental) + + + + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + + + + Use at your own risk! + Use at your own risk! + + + + By checking this you also opt-in for additional performance telemetry + By checking this you also opt-in for additional performance telemetry + + + + Transparent Compiler Cache Factor + Transparent Compiler Cache Factor + + + + Higher number means more memory will be used for caching. Changing the value wipes cache. + Higher number means more memory will be used for caching. Changing the value wipes cache. + + + + Create new project snapshots from existing ones + Create new project snapshots from existing ones + + Remove unused open statements Kullanılmayan açık deyimleri kaldır @@ -287,6 +322,11 @@ Çözümlenmemiş tanımlayıcılar için ad öner + + Use Transparent Compiler (restart required) + Use Transparent Compiler (restart required) + + Cache parsing results (experimental) Ayrıştırma sonuçlarını önbelleğe al (deneysel) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hans.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hans.xlf index 57a18b93541..9bd919f4b51 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hans.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hans.xlf @@ -202,6 +202,41 @@ 通过添加符合 F# 语法规则的换行符,将签名设置为给定宽度的格式。 + + Transparent Compiler (experimental) + Transparent Compiler (experimental) + + + + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + + + + Use at your own risk! + Use at your own risk! + + + + By checking this you also opt-in for additional performance telemetry + By checking this you also opt-in for additional performance telemetry + + + + Transparent Compiler Cache Factor + Transparent Compiler Cache Factor + + + + Higher number means more memory will be used for caching. Changing the value wipes cache. + Higher number means more memory will be used for caching. Changing the value wipes cache. + + + + Create new project snapshots from existing ones + Create new project snapshots from existing ones + + Remove unused open statements 删除未使用的 open 语句 @@ -287,6 +322,11 @@ 为未解析标识符建议名称 + + Use Transparent Compiler (restart required) + Use Transparent Compiler (restart required) + + Cache parsing results (experimental) 缓存分析结果(实验性) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hant.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hant.xlf index 6fcf0c141a4..d537c05ffd2 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hant.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hant.xlf @@ -202,6 +202,41 @@ 透過新增符合 F# 語法規則的分行符號,將簽章格式設定為指定寬度。 + + Transparent Compiler (experimental) + Transparent Compiler (experimental) + + + + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + WARNING! Transparent Compiler does not yet support all features and can cause crashes or give incorrect results. + + + + Use at your own risk! + Use at your own risk! + + + + By checking this you also opt-in for additional performance telemetry + By checking this you also opt-in for additional performance telemetry + + + + Transparent Compiler Cache Factor + Transparent Compiler Cache Factor + + + + Higher number means more memory will be used for caching. Changing the value wipes cache. + Higher number means more memory will be used for caching. Changing the value wipes cache. + + + + Create new project snapshots from existing ones + Create new project snapshots from existing ones + + Remove unused open statements 移除未使用的 open 陳述式 @@ -287,6 +322,11 @@ 為未解析的識別碼建議名稱 + + Use Transparent Compiler (restart required) + Use Transparent Compiler (restart required) + + Cache parsing results (experimental) 快取剖析結果 (實驗性)