diff --git a/src/fsharp/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj b/src/fsharp/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj index dd96b7da59e..de6827d530a 100644 --- a/src/fsharp/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj +++ b/src/fsharp/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj @@ -826,6 +826,12 @@ Service/SemanticClassificationKey.fs + + Service/FSharpDocument.fsi + + + Service/FSharpDocument.fs + Service/IncrementalBuild.fsi diff --git a/src/fsharp/ParseAndCheckInputs.fs b/src/fsharp/ParseAndCheckInputs.fs index ffe1bfe4a97..c0dc950da73 100644 --- a/src/fsharp/ParseAndCheckInputs.fs +++ b/src/fsharp/ParseAndCheckInputs.fs @@ -402,6 +402,22 @@ let checkInputFile (tcConfig: TcConfig) filename = else error(Error(FSComp.SR.buildInvalidSourceFileExtension(SanitizeFileName filename tcConfig.implicitIncludeDir), rangeStartup)) +let parseInputStreamAux (tcConfig: TcConfig, lexResourceManager, conditionalCompilationDefines, filename, isLastCompiland, errorLogger, retryLocked, stream: Stream) = + use reader = stream.GetReader(tcConfig.inputCodePage, retryLocked) + + // Set up the LexBuffer for the file + let lexbuf = UnicodeLexing.StreamReaderAsLexbuf(not tcConfig.compilingFslib, tcConfig.langVersion.SupportsFeature, reader) + + // Parse the file drawing tokens from the lexbuf + ParseOneInputLexbuf(tcConfig, lexResourceManager, conditionalCompilationDefines, lexbuf, filename, isLastCompiland, errorLogger) + +let parseInputSourceTextAux (tcConfig: TcConfig, lexResourceManager, conditionalCompilationDefines, filename, isLastCompiland, errorLogger, sourceText: ISourceText) = + // Set up the LexBuffer for the file + let lexbuf = UnicodeLexing.SourceTextAsLexbuf(not tcConfig.compilingFslib, tcConfig.langVersion.SupportsFeature, sourceText) + + // Parse the file drawing tokens from the lexbuf + ParseOneInputLexbuf(tcConfig, lexResourceManager, conditionalCompilationDefines, lexbuf, filename, isLastCompiland, errorLogger) + let parseInputFileAux (tcConfig: TcConfig, lexResourceManager, conditionalCompilationDefines, filename, isLastCompiland, errorLogger, retryLocked) = // Get a stream reader for the file use fileStream = FileSystem.OpenFileForReadShim(filename) @@ -422,6 +438,22 @@ let ParseOneInputFile (tcConfig: TcConfig, lexResourceManager, conditionalCompil errorRecovery e rangeStartup EmptyParsedInput(filename, isLastCompiland) +/// Parse an input from stream +let ParseOneInputStream (tcConfig: TcConfig, lexResourceManager, conditionalCompilationDefines, filename, isLastCompiland, errorLogger, retryLocked, stream: Stream) = + try + parseInputStreamAux(tcConfig, lexResourceManager, conditionalCompilationDefines, filename, isLastCompiland, errorLogger, retryLocked, stream) + with e -> + errorRecovery e rangeStartup + EmptyParsedInput(filename, isLastCompiland) + +/// Parse an input from source text +let ParseOneInputSourceText (tcConfig: TcConfig, lexResourceManager, conditionalCompilationDefines, filename, isLastCompiland, errorLogger, sourceText: ISourceText) = + try + parseInputSourceTextAux(tcConfig, lexResourceManager, conditionalCompilationDefines, filename, isLastCompiland, errorLogger, sourceText) + with e -> + errorRecovery e rangeStartup + EmptyParsedInput(filename, isLastCompiland) + /// Parse multiple input files from disk let ParseInputFiles (tcConfig: TcConfig, lexResourceManager, conditionalCompilationDefines, sourceFiles, errorLogger: ErrorLogger, exiter: Exiter, createErrorLogger: (Exiter -> CapturingErrorLogger), retryLocked) = try diff --git a/src/fsharp/ParseAndCheckInputs.fsi b/src/fsharp/ParseAndCheckInputs.fsi index 207d41c878b..ba995a5a988 100644 --- a/src/fsharp/ParseAndCheckInputs.fsi +++ b/src/fsharp/ParseAndCheckInputs.fsi @@ -3,6 +3,7 @@ /// Contains logic to coordinate the parsing and checking of one or a group of files module internal FSharp.Compiler.ParseAndCheckInputs +open System.IO open Internal.Utilities.Library open FSharp.Compiler.CheckExpressions open FSharp.Compiler.CheckDeclarations @@ -49,6 +50,12 @@ val ApplyNoWarnsToTcConfig: TcConfig * ParsedInput * string -> TcConfig /// Parse one input file val ParseOneInputFile: TcConfig * Lexhelp.LexResourceManager * conditionalCompilationDefines: string list * string * isLastCompiland: (bool * bool) * ErrorLogger * retryLocked: bool -> ParsedInput +/// Parse one input stream +val ParseOneInputStream: TcConfig * Lexhelp.LexResourceManager * conditionalCompilationDefines: string list * string * isLastCompiland: (bool * bool) * ErrorLogger * retryLocked: bool * stream: Stream -> ParsedInput + +/// Parse one input source text +val ParseOneInputSourceText: TcConfig * Lexhelp.LexResourceManager * conditionalCompilationDefines: string list * string * isLastCompiland: (bool * bool) * ErrorLogger * sourceText: ISourceText -> ParsedInput + /// Parse multiple input files from disk val ParseInputFiles: TcConfig * Lexhelp.LexResourceManager * conditionalCompilationDefines: string list * string list * ErrorLogger * Exiter * createErrorLogger: (Exiter -> CapturingErrorLogger) * retryLocked: bool -> (ParsedInput * string) list diff --git a/src/fsharp/absil/ilread.fs b/src/fsharp/absil/ilread.fs index b89cccd82c3..37af98f0014 100644 --- a/src/fsharp/absil/ilread.fs +++ b/src/fsharp/absil/ilread.fs @@ -3913,7 +3913,6 @@ let createByteFileChunk opts fileName chunk = let getBinaryFile fileName useMemoryMappedFile = let stream = FileSystem.OpenFileForReadShim(fileName, useMemoryMappedFile = useMemoryMappedFile) - let byteMem = stream.AsByteMemory() let safeHolder = { new obj() with @@ -3927,7 +3926,7 @@ let getBinaryFile fileName useMemoryMappedFile = stats.memoryMapFileOpenedCount <- stats.memoryMapFileOpenedCount + 1 - safeHolder, RawMemoryFile(fileName, safeHolder, byteMem) :> BinaryFile + safeHolder, RawMemoryFile(fileName, safeHolder, stream.AsByteMemory()) :> BinaryFile let OpenILModuleReaderFromBytes fileName assemblyContents options = let pefile = ByteFile(fileName, assemblyContents) :> BinaryFile diff --git a/src/fsharp/service/FSharpCheckerResults.fsi b/src/fsharp/service/FSharpCheckerResults.fsi index 1858e4b24db..377166d3717 100644 --- a/src/fsharp/service/FSharpCheckerResults.fsi +++ b/src/fsharp/service/FSharpCheckerResults.fsi @@ -50,7 +50,7 @@ type public FSharpProjectOptions = /// This is the unique identifier for the project, it is case sensitive. If it's None, will key off of ProjectFileName in our caching. ProjectId: string option - /// The files in the project + /// The source files in the project SourceFiles: string[] /// Additional command line argument options for the project. These can include additional files and references. diff --git a/src/fsharp/service/FSharpDocument.fs b/src/fsharp/service/FSharpDocument.fs new file mode 100644 index 00000000000..330546324b0 --- /dev/null +++ b/src/fsharp/service/FSharpDocument.fs @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace FSharp.Compiler.CodeAnalysis + +open System +open System.Collections.Generic +open System.Collections.Immutable +open System.IO +open System.IO.MemoryMappedFiles +open System.Xml +open System.Runtime.InteropServices +open System.Threading +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.CheckExpressions +open FSharp.Compiler.CheckDeclarations +open FSharp.Compiler.CompilerConfig +open FSharp.Compiler.CompilerDiagnostics +open FSharp.Compiler.CompilerGlobalState +open FSharp.Compiler.CompilerImports +open FSharp.Compiler.CompilerOptions +open FSharp.Compiler.CreateILModule +open FSharp.Compiler.DependencyManager +open FSharp.Compiler.Diagnostics +open FSharp.Compiler.EditorServices +open FSharp.Compiler.ErrorLogger +open FSharp.Compiler.IO +open FSharp.Compiler.CodeAnalysis +open FSharp.Compiler.NameResolution +open FSharp.Compiler.ParseAndCheckInputs +open FSharp.Compiler.ScriptClosure +open FSharp.Compiler.Syntax +open FSharp.Compiler.TcGlobals +open FSharp.Compiler.Text +open FSharp.Compiler.Text.Range +open FSharp.Compiler.Xml +open FSharp.Compiler.TypedTree +open FSharp.Compiler.TypedTreeOps + +[] +type DocumentText = + | OnDisk + | Stream of Stream + | SourceText of ISourceText + + interface IDisposable with + + member this.Dispose() = + match this with + | Stream stream -> stream.Dispose() + | _ -> () + +[] +type FSharpDocument internal () = + + abstract FilePath : string + + abstract TimeStamp : DateTime + + abstract IsOpen : bool + + abstract GetText : unit -> DocumentText + +type private FSharpDocumentMemoryMappedFile(filePath: string, timeStamp: DateTime, isOpen, openStream: unit -> Stream) = + inherit FSharpDocument() + + override _.FilePath = filePath + + override _.TimeStamp = timeStamp + + override _.IsOpen = isOpen + + override _.GetText() = + openStream () |> DocumentText.Stream + +type private FSharpDocumentByteArray(filePath: string, timeStamp: DateTime, isOpen, bytes: byte[]) = + inherit FSharpDocument() + + override _.FilePath = filePath + + override _.TimeStamp = timeStamp + + override _.IsOpen = isOpen + + override _.GetText() = + DocumentText.Stream(new MemoryStream(bytes, 0, bytes.Length, false) :> Stream) + +type private FSharpDocumentFromFile(filePath: string) = + inherit FSharpDocument() + + override _.FilePath = filePath + + override _.TimeStamp = FileSystem.GetLastWriteTimeShim(filePath) + + override _.IsOpen = false + + override _.GetText() = DocumentText.OnDisk + +type private FSharpDocumentCustom(filePath: string, isOpen, getTimeStamp, getSourceText) = + inherit FSharpDocument() + + override _.FilePath = filePath + + override _.TimeStamp = getTimeStamp() + + override _.IsOpen = isOpen + + override _.GetText() = + DocumentText.SourceText(getSourceText()) + +type FSharpDocument with + + static member Create(filePath, isOpen, getTimeStamp, getSourceText) = + FSharpDocumentCustom(filePath, isOpen, getTimeStamp, getSourceText) :> FSharpDocument + + static member CreateFromFile(filePath: string) = + FSharpDocumentFromFile(filePath) :> FSharpDocument + + static member CreateCopyFromFile(filePath: string, isOpen) = + let timeStamp = FileSystem.GetLastWriteTimeShim(filePath) + + // We want to use mmaped documents only when + // not running on mono, since its MemoryMappedFile implementation throws when "mapName" is not provided (is null), (see: https://github.com/mono/mono/issues/10245) + if runningOnMono then + let bytes = FileSystem.OpenFileForReadShim(filePath, useMemoryMappedFile = false).ReadAllBytes() + FSharpDocumentByteArray(filePath, timeStamp, isOpen, bytes) :> FSharpDocument + else + let openStream = fun () -> + FileSystem.OpenFileForReadShim(filePath, useMemoryMappedFile = true, shouldShadowCopy = true) + FSharpDocumentMemoryMappedFile(filePath, timeStamp, isOpen, openStream) :> FSharpDocument + diff --git a/src/fsharp/service/FSharpDocument.fsi b/src/fsharp/service/FSharpDocument.fsi new file mode 100644 index 00000000000..42dfb672556 --- /dev/null +++ b/src/fsharp/service/FSharpDocument.fsi @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace FSharp.Compiler.CodeAnalysis + +open System +open System.IO +open FSharp.Compiler.Text + +[] +type internal DocumentText = + | OnDisk + | Stream of Stream + | SourceText of ISourceText + + interface IDisposable + +[] +type FSharpDocument = + + /// The file path of the document. + abstract FilePath : string + + /// The timestamp of the document. + abstract TimeStamp : DateTime + + /// Is the document open in an editor? + /// This is used to allow the background build to provide rich information on the document. + abstract IsOpen : bool + + abstract internal GetText : unit -> DocumentText + + static member internal CreateFromFile : filePath: string -> FSharpDocument + + static member CreateCopyFromFile : filePath: string * isOpen: bool -> FSharpDocument + + static member Create : filePath: string * isOpen: bool * getTimeStamp: (unit -> DateTime) * getSourceText: (unit -> ISourceText) -> FSharpDocument \ No newline at end of file diff --git a/src/fsharp/service/IncrementalBuild.fs b/src/fsharp/service/IncrementalBuild.fs index ddcfefd2a5a..d99c695f06f 100644 --- a/src/fsharp/service/IncrementalBuild.fs +++ b/src/fsharp/service/IncrementalBuild.fs @@ -98,9 +98,10 @@ module IncrementalBuildSyntaxTree = /// Information needed to lazily parse a file to get a ParsedInput. Internally uses a weak cache. [] - type SyntaxTree (tcConfig: TcConfig, fileParsed: Event, lexResourceManager, sourceRange: range, filename: string, isLastCompiland) = + type SyntaxTree (tcConfig: TcConfig, fileParsed: Event, lexResourceManager, sourceRange: range, doc: FSharpDocument, isLastCompiland) = let mutable weakCache: WeakReference<_> option = None + let filename = doc.FilePath let parse(sigNameOpt: QualifiedNameOfFile option) = let errorLogger = CompilationErrorLogger("Parse", tcConfig.errorSeverityOptions) @@ -125,7 +126,14 @@ module IncrementalBuildSyntaxTree = ) ) else - ParseOneInputFile(tcConfig, lexResourceManager, [], filename, isLastCompiland, errorLogger, (*retryLocked*)true) + use text = doc.GetText() + match text with + | DocumentText.Stream(stream) -> + ParseOneInputStream(tcConfig, lexResourceManager, [], filename, isLastCompiland, errorLogger, (*retryLocked*)false, stream) + | DocumentText.SourceText(sourceText) -> + ParseOneInputSourceText(tcConfig, lexResourceManager, [], filename, isLastCompiland, errorLogger, sourceText) + | DocumentText.OnDisk -> + ParseOneInputFile(tcConfig, lexResourceManager, [], filename, isLastCompiland, errorLogger, (*retryLocked*)true) fileParsed.Trigger filename @@ -149,10 +157,12 @@ module IncrementalBuildSyntaxTree = | _ -> parse sigNameOpt member _.Invalidate() = - SyntaxTree(tcConfig, fileParsed, lexResourceManager, sourceRange, filename, isLastCompiland) + SyntaxTree(tcConfig, fileParsed, lexResourceManager, sourceRange, doc, isLastCompiland) member _.FileName = filename + member _.Document = doc + /// Accumulated results of type checking. The minimum amount of state in order to continue type-checking following files. [] type TcInfo = @@ -255,6 +265,10 @@ type BoundModel private (tcConfig: TcConfig, prevTcInfo: TcInfo, syntaxTreeOpt: SyntaxTree option, tcInfoStateOpt: TcInfoState option) as this = + let isDocOpen = + match syntaxTreeOpt with + | Some syntaxTree -> syntaxTree.Document.IsOpen + | _ -> false let tcInfoNode = match tcInfoStateOpt with @@ -269,7 +283,7 @@ type BoundModel private (tcConfig: TcConfig, let partialGraphNode = GraphNode(node { - if enablePartialTypeChecking then + if enablePartialTypeChecking && not isDocOpen then // Optimization so we have less of a chance to duplicate work. if fullGraphNode.IsComputing then let! tcInfo, _ = fullGraphNode.GetOrComputeValue() @@ -310,14 +324,10 @@ type BoundModel private (tcConfig: TcConfig, | _ -> None - /// If partial type-checking is enabled, - /// this will create a new bound-model that will only have the partial state if the + /// This will create a new bound-model that will only have the partial state if the /// the current bound-model has the full state. member this.ClearTcInfoExtras() = - let hasSig = this.BackingSignature.IsSome - - // If partial checking is enabled and we have a backing sig file, then use the partial state. The partial state contains the sig state. - if tcInfoNode.HasFull && enablePartialTypeChecking && hasSig then + if tcInfoNode.HasFull then // Always invalidate the syntax tree cache. let newSyntaxTreeOpt = syntaxTreeOpt @@ -539,8 +549,8 @@ type BoundModel private (tcConfig: TcConfig, { /// Only keep the typed interface files when doing a "full" build for fsc.exe, otherwise just throw them away latestImplFile = if keepAssemblyContents then implFile else None - tcResolutions = (if keepAllBackgroundResolutions then sink.GetResolutions() else TcResolutions.Empty) - tcSymbolUses = (if keepAllBackgroundSymbolUses then sink.GetSymbolUses() else TcSymbolUses.Empty) + tcResolutions = (if keepAllBackgroundResolutions || syntaxTree.Document.IsOpen then sink.GetResolutions() else TcResolutions.Empty) + tcSymbolUses = (if keepAllBackgroundSymbolUses || syntaxTree.Document.IsOpen then sink.GetSymbolUses() else TcSymbolUses.Empty) tcOpenDeclarations = sink.GetOpenDeclarations() itemKeyStore = itemKeyStore semanticClassificationKeyStore = semanticClassification @@ -705,52 +715,48 @@ type IncrementalBuilderState = { // stampedFileNames represent the real stamps of the files. // logicalStampedFileNames represent the stamps of the files that are used to calculate the project's logical timestamp. + documents: ImmutableArray<(range * FSharpDocument * (bool * bool))> stampedFileNames: ImmutableArray logicalStampedFileNames: ImmutableArray stampedReferencedAssemblies: ImmutableArray - initialBoundModel: GraphNode boundModels: ImmutableArray> finalizedBoundModel: GraphNode<((ILAssemblyRef * IRawFSharpAssemblyData option * TypedImplFile list option * BoundModel) * DateTime)> } -/// Manages an incremental build graph for the build of a single F# project -type IncrementalBuilder( - initialBoundModel: BoundModel, - tcGlobals, - nonFrameworkAssemblyInputs, - tcConfig: TcConfig, - outfile, - assemblyName, - lexResourceManager, - sourceFiles, - enablePartialTypeChecking, - beforeFileChecked: Event, - fileChecked: Event, +type IncrementalBuilderMainState = + { + mutable isImportsInvalidated: bool + initialBoundModel: BoundModel + initialBoundModelNode: GraphNode + tcGlobals: TcGlobals + nonFrameworkAssemblyInputs: (Choice * (TimeStampCache -> DateTime)) list + tcConfig: TcConfig + outfile: string + assemblyName: string + lexResourceManager: Lexhelp.LexResourceManager + sourceFiles: ImmutableArray<(range * string * (bool * bool))> + enablePartialTypeChecking: bool + allDependencies: string [] + beforeFileChecked: Event + fileChecked: Event + fileParsed: Event + projectChecked: Event #if !NO_EXTENSIONTYPING - importsInvalidatedByTypeProvider: Event, + importsInvalidatedByTypeProvider: Event #endif - allDependencies) = - - let fileParsed = new Event() - let projectChecked = new Event() - - let defaultTimeStamp = DateTime.UtcNow - - let mutable isImportsInvalidated = false + } -#if !NO_EXTENSIONTYPING - do importsInvalidatedByTypeProvider.Publish.Add(fun () -> isImportsInvalidated <- true) -#endif +/// Manages an incremental build graph for the build of a single F# project +type IncrementalBuilder(mainState: IncrementalBuilderMainState, state: IncrementalBuilderState) = - //---------------------------------------------------- - // START OF BUILD TASK FUNCTIONS + static let defaultTimeStamp = DateTime.UtcNow /// Get the timestamp of the given file name. - let StampFileNameTask (cache: TimeStampCache) (_m: range, filename: string, _isLastCompiland) = - cache.GetFileTimeStamp filename + static let StampFileNameTask (_m: range, doc: FSharpDocument, _isLastCompiland) = + doc.TimeStamp /// Timestamps of referenced assemblies are taken from the file's timestamp. - let StampReferencedAssemblyTask (cache: TimeStampCache) (_ref, timeStamper) = + static let StampReferencedAssemblyTask (cache: TimeStampCache) (_ref, timeStamper) = timeStamper cache // Link all the assemblies together and produce the input typecheck accumulator @@ -848,7 +854,7 @@ type IncrementalBuilder( None) } /// Type check all files eagerly. - let TypeCheckTask partialCheck (prevBoundModel: BoundModel) syntaxTree: NodeCode = + static let TypeCheckTask partialCheck (prevBoundModel: BoundModel) syntaxTree: NodeCode = node { let! tcInfo = prevBoundModel.GetOrComputeTcInfo() let boundModel = prevBoundModel.Next(syntaxTree, tcInfo) @@ -866,15 +872,15 @@ type IncrementalBuilder( } /// Finish up the typechecking to produce outputs for the rest of the compilation process - let FinalizeTypeCheckTask (boundModels: ImmutableArray) = + static let FinalizeTypeCheckTask mainState (boundModels: ImmutableArray) = node { - let errorLogger = CompilationErrorLogger("FinalizeTypeCheckTask", tcConfig.errorSeverityOptions) + let errorLogger = CompilationErrorLogger("FinalizeTypeCheckTask", mainState.tcConfig.errorSeverityOptions) use _ = new CompilationGlobalsScope(errorLogger, BuildPhase.TypeCheck) let! results = boundModels |> Seq.map (fun boundModel -> node { - if enablePartialTypeChecking then + if mainState.enablePartialTypeChecking then let! tcInfo = boundModel.GetOrComputeTcInfo() return tcInfo, None else @@ -911,22 +917,22 @@ type IncrementalBuilder( let ilAssemRef = let publicKey = try - let signingInfo = ValidateKeySigningAttributes (tcConfig, tcGlobals, topAttrs) + let signingInfo = ValidateKeySigningAttributes (mainState.tcConfig, mainState.tcGlobals, topAttrs) match GetStrongNameSigner signingInfo with | None -> None | Some s -> Some (PublicKey.KeyAsToken(s.PublicKey)) with e -> errorRecoveryNoRange e None - let locale = TryFindFSharpStringAttribute tcGlobals (tcGlobals.FindSysAttrib "System.Reflection.AssemblyCultureAttribute") topAttrs.assemblyAttrs + let locale = TryFindFSharpStringAttribute mainState.tcGlobals (mainState.tcGlobals.FindSysAttrib "System.Reflection.AssemblyCultureAttribute") topAttrs.assemblyAttrs let assemVerFromAttrib = - TryFindFSharpStringAttribute tcGlobals (tcGlobals.FindSysAttrib "System.Reflection.AssemblyVersionAttribute") topAttrs.assemblyAttrs + TryFindFSharpStringAttribute mainState.tcGlobals (mainState.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) + | None -> mainState.tcConfig.version.GetVersionInfo(mainState.tcConfig.implicitIncludeDir) | Some v -> v - ILAssemblyRef.Create(assemblyName, None, publicKey, false, Some ver, locale) + ILAssemblyRef.Create(mainState.assemblyName, None, publicKey, false, Some ver, locale) let tcAssemblyDataOpt = try @@ -940,46 +946,36 @@ type IncrementalBuilder( if tcState.CreatesGeneratedProvidedTypes || hasTypeProviderAssemblyAttrib then None else - Some (RawFSharpAssemblyDataBackedByLanguageService (tcConfig, tcGlobals, generatedCcu, outfile, topAttrs, assemblyName, ilAssemRef) :> IRawFSharpAssemblyData) + Some (RawFSharpAssemblyDataBackedByLanguageService (mainState.tcConfig, mainState.tcGlobals, generatedCcu, mainState.outfile, topAttrs, mainState.assemblyName, ilAssemRef) :> IRawFSharpAssemblyData) with e -> errorRecoveryNoRange e None ilAssemRef, tcAssemblyDataOpt, Some tcAssemblyExpr with e -> errorRecoveryNoRange e - mkSimpleAssemblyRef assemblyName, None, None + mkSimpleAssemblyRef mainState.assemblyName, None, None let diagnostics = errorLogger.GetDiagnostics() :: finalInfo.tcErrorsRev let! finalBoundModelWithErrors = finalBoundModel.Finish(diagnostics, Some topAttrs) return ilAssemRef, tcAssemblyDataOpt, tcAssemblyExprOpt, finalBoundModelWithErrors } - // END OF BUILD TASK FUNCTIONS - // --------------------------------------------------------------------------------------------- + static let GetSyntaxTree (mainState: IncrementalBuilderMainState) (sourceRange: range, doc: FSharpDocument, isLastCompiland) = + SyntaxTree(mainState.tcConfig, mainState.fileParsed, mainState.lexResourceManager, sourceRange, doc, isLastCompiland) - // --------------------------------------------------------------------------------------------- - // START OF BUILD DESCRIPTION - - let GetSyntaxTree (sourceRange: range, filename: string, isLastCompiland) = - SyntaxTree(tcConfig, fileParsed, lexResourceManager, sourceRange, filename, isLastCompiland) - - // Inputs - let fileNames = sourceFiles |> Array.ofList // TODO: This should be an immutable array. - let referencedAssemblies = nonFrameworkAssemblyInputs |> Array.ofList // TODO: This should be an immutable array. - - let createBoundModelGraphNode initialBoundModel (boundModels: ImmutableArray>.Builder) i = - let fileInfo = fileNames.[i] + static let createBoundModelGraphNode mainState (documents: ImmutableArray<_>) (boundModels: ImmutableArray>.Builder) i = + let fileInfo = documents.[i] let prevBoundModelGraphNode = match i with - | 0 (* first file *) -> initialBoundModel + | 0 (* first file *) -> mainState.initialBoundModelNode | _ -> boundModels.[i - 1] - let syntaxTree = GetSyntaxTree fileInfo + let syntaxTree = GetSyntaxTree mainState fileInfo GraphNode(node { let! prevBoundModel = prevBoundModelGraphNode.GetOrComputeValue() - return! TypeCheckTask enablePartialTypeChecking prevBoundModel syntaxTree + return! TypeCheckTask (mainState.enablePartialTypeChecking && not syntaxTree.Document.IsOpen) prevBoundModel syntaxTree }) - let rec createFinalizeBoundModelGraphNode (boundModels: ImmutableArray>.Builder) = + static let rec createFinalizeBoundModelGraphNode mainState (boundModels: ImmutableArray>.Builder) = GraphNode(node { // Compute last bound model then get all the evaluated models. let! _ = boundModels.[boundModels.Count - 1].GetOrComputeValue() @@ -988,63 +984,94 @@ type IncrementalBuilder( |> Seq.map (fun x -> x.TryPeekValue().Value) |> ImmutableArray.CreateRange - let! result = FinalizeTypeCheckTask boundModels + let! result = FinalizeTypeCheckTask mainState boundModels let result = (result, DateTime.UtcNow) return result }) - and computeStampedFileName (state: IncrementalBuilderState) (cache: TimeStampCache) slot fileInfo = + and computeStampedFileName mainState (state: IncrementalBuilderState) slot fileInfo = let currentStamp = state.stampedFileNames.[slot] - let stamp = StampFileNameTask cache fileInfo + let stamp = StampFileNameTask fileInfo if currentStamp <> stamp then + let boundModels = state.boundModels.ToBuilder() + let newDocuments = state.documents.RemoveAt(slot).Insert(slot, fileInfo) match state.boundModels.[slot].TryPeekValue() with // This prevents an implementation file that has a backing signature file from invalidating the rest of the build. - | ValueSome(boundModel) when enablePartialTypeChecking && boundModel.BackingSignature.IsSome -> - let newBoundModel = boundModel.ClearTcInfoExtras() + | ValueSome(boundModel) when mainState.enablePartialTypeChecking && boundModel.BackingSignature.IsSome -> + let newBoundModelNode = + match fileInfo with + | _, doc, _ when doc.IsOpen -> + createBoundModelGraphNode mainState newDocuments boundModels slot + | _ -> + let newBoundModel = boundModel.ClearTcInfoExtras() + GraphNode(node { return newBoundModel }) { state with - boundModels = state.boundModels.RemoveAt(slot).Insert(slot, GraphNode(node { return newBoundModel })) - stampedFileNames = state.stampedFileNames.SetItem(slot, StampFileNameTask cache fileInfo) + documents = newDocuments + boundModels = state.boundModels.RemoveAt(slot).Insert(slot, newBoundModelNode) + stampedFileNames = state.stampedFileNames.SetItem(slot, stamp) } | _ -> let stampedFileNames = state.stampedFileNames.ToBuilder() let logicalStampedFileNames = state.logicalStampedFileNames.ToBuilder() - let boundModels = state.boundModels.ToBuilder() // Invalidate the file and all files below it. for j = 0 to stampedFileNames.Count - slot - 1 do - let stamp = StampFileNameTask cache fileNames.[slot + j] + let stamp = StampFileNameTask newDocuments.[slot + j] stampedFileNames.[slot + j] <- stamp logicalStampedFileNames.[slot + j] <- stamp - boundModels.[slot + j] <- createBoundModelGraphNode state.initialBoundModel boundModels (slot + j) + boundModels.[slot + j] <- createBoundModelGraphNode mainState newDocuments boundModels (slot + j) { state with // Something changed, the finalized view of the project must be invalidated. - finalizedBoundModel = createFinalizeBoundModelGraphNode boundModels + finalizedBoundModel = createFinalizeBoundModelGraphNode mainState boundModels + documents = newDocuments stampedFileNames = stampedFileNames.ToImmutable() logicalStampedFileNames = logicalStampedFileNames.ToImmutable() boundModels = boundModels.ToImmutable() } else - state + let _, doc, _ = fileInfo + let _, currentDoc, _ = state.documents.[slot] + if currentDoc.IsOpen && not doc.IsOpen then + let state = + { state with + documents = state.documents.RemoveAt(slot).Insert(slot, fileInfo) + } + let currentBoundModel = state.boundModels.[slot] + match currentBoundModel.TryPeekValue() with + | ValueSome currentBoundModel -> + let boundModel = currentBoundModel.ClearTcInfoExtras() + { state with + boundModels = state.boundModels.RemoveAt(slot).Insert(slot, GraphNode(node { return boundModel })) + } + | _ -> + state + elif not currentDoc.IsOpen && doc.IsOpen then + { state with + documents = state.documents.RemoveAt(slot).Insert(slot, fileInfo) + } + else + state - and computeStampedFileNames state (cache: TimeStampCache) = + and computeStampedFileNames mainState state = let mutable i = 0 - (state, fileNames) - ||> Array.fold (fun state fileInfo -> - let newState = computeStampedFileName state cache i fileInfo + (state, state.documents) + ||> Seq.fold (fun state fileInfo -> + let newState = computeStampedFileName mainState state i fileInfo i <- i + 1 newState ) - and computeStampedReferencedAssemblies state canTriggerInvalidation (cache: TimeStampCache) = + and computeStampedReferencedAssemblies (mainState: IncrementalBuilderMainState) state canTriggerInvalidation = + let cache = TimeStampCache(defaultTimeStamp) let stampedReferencedAssemblies = state.stampedReferencedAssemblies.ToBuilder() let mutable referencesUpdated = false - referencedAssemblies - |> Array.iteri (fun i asmInfo -> + mainState.nonFrameworkAssemblyInputs + |> List.iteri (fun i asmInfo -> let currentStamp = state.stampedReferencedAssemblies.[i] let stamp = StampReferencedAssemblyTask cache asmInfo @@ -1056,15 +1083,15 @@ type IncrementalBuilder( if referencesUpdated then // Build is invalidated. The build must be rebuilt with the newly updated references. - if not isImportsInvalidated && canTriggerInvalidation then - isImportsInvalidated <- true + if not mainState.isImportsInvalidated && canTriggerInvalidation then + mainState.isImportsInvalidated <- true { state with stampedReferencedAssemblies = stampedReferencedAssemblies.ToImmutable() } else state - let tryGetSlot (state: IncrementalBuilderState) slot = + static let tryGetSlot (state: IncrementalBuilderState) slot = match state.boundModels.[slot].TryPeekValue() with | ValueSome boundModel -> (boundModel, state.stampedFileNames.[slot]) @@ -1072,114 +1099,85 @@ type IncrementalBuilder( | _ -> None - let tryGetBeforeSlot (state: IncrementalBuilderState) slot = + static let tryGetBeforeSlot mainState (state: IncrementalBuilderState) slot = match slot with | 0 (* first file *) -> - (initialBoundModel, DateTime.MinValue) + (mainState.initialBoundModel, DateTime.MinValue) |> Some | _ -> tryGetSlot state (slot - 1) - let evalUpToTargetSlot (state: IncrementalBuilderState) targetSlot = + static let evalUpToTargetSlot mainState (state: IncrementalBuilderState) targetSlot = node { if targetSlot < 0 then - return Some(initialBoundModel, DateTime.MinValue) + return Some(mainState.initialBoundModel, DateTime.MinValue) else let! boundModel = state.boundModels.[targetSlot].GetOrComputeValue() return Some(boundModel, state.stampedFileNames.[targetSlot]) } - let MaxTimeStampInDependencies stamps = + static let MaxTimeStampInDependencies stamps = if Seq.isEmpty stamps then DateTime.MinValue else stamps |> Seq.max - // END OF BUILD DESCRIPTION - // --------------------------------------------------------------------------------------------- - - (* - The data below represents a dependency graph. - - ReferencedAssembliesStamps => FileStamps => BoundModels => FinalizedBoundModel - *) - - let gate = obj () - let mutable currentState = - let cache = TimeStampCache(defaultTimeStamp) - let initialBoundModel = GraphNode(node { return initialBoundModel }) - let boundModels = ImmutableArray.CreateBuilder(fileNames.Length) - - for slot = 0 to fileNames.Length - 1 do - boundModels.Add(createBoundModelGraphNode initialBoundModel boundModels slot) - - let state = - { - stampedFileNames = Array.init fileNames.Length (fun _ -> DateTime.MinValue) |> ImmutableArray.CreateRange - logicalStampedFileNames = Array.init fileNames.Length (fun _ -> DateTime.MinValue) |> ImmutableArray.CreateRange - stampedReferencedAssemblies = Array.init referencedAssemblies.Length (fun _ -> DateTime.MinValue) |> ImmutableArray.CreateRange - initialBoundModel = initialBoundModel - boundModels = boundModels.ToImmutable() - finalizedBoundModel = createFinalizeBoundModelGraphNode boundModels - } - let state = computeStampedReferencedAssemblies state false cache - let state = computeStampedFileNames state cache - state - - let computeProjectTimeStamp (state: IncrementalBuilderState) = + static let computeProjectTimeStamp (state: IncrementalBuilderState) = let t1 = MaxTimeStampInDependencies state.stampedReferencedAssemblies let t2 = MaxTimeStampInDependencies state.logicalStampedFileNames max t1 t2 - let setCurrentState state cache (ct: CancellationToken) = + let gate = obj () + let mutable currentState = state + + let setCurrentState state (ct: CancellationToken) = lock gate (fun () -> ct.ThrowIfCancellationRequested() - currentState <- computeStampedFileNames state cache + currentState <- computeStampedFileNames mainState state ) - let checkFileTimeStamps (cache: TimeStampCache) = + let checkFileTimeStamps () = node { let! ct = NodeCode.CancellationToken - setCurrentState currentState cache ct + setCurrentState currentState ct } do IncrementalBuilderEventTesting.MRU.Add(IncrementalBuilderEventTesting.IBECreated) - member _.TcConfig = tcConfig + member _.TcConfig = mainState.tcConfig - member _.FileParsed = fileParsed.Publish + member _.FileParsed = mainState.fileParsed.Publish - member _.BeforeFileChecked = beforeFileChecked.Publish + member _.BeforeFileChecked = mainState.beforeFileChecked.Publish - member _.FileChecked = fileChecked.Publish + member _.FileChecked = mainState.fileChecked.Publish - member _.ProjectChecked = projectChecked.Publish + member _.ProjectChecked = mainState.projectChecked.Publish #if !NO_EXTENSIONTYPING - member _.ImportsInvalidatedByTypeProvider = importsInvalidatedByTypeProvider.Publish + member _.ImportsInvalidatedByTypeProvider = mainState.importsInvalidatedByTypeProvider.Publish #endif member _.IsReferencesInvalidated = // fast path - if isImportsInvalidated then true + if mainState.isImportsInvalidated then true else - computeStampedReferencedAssemblies currentState true (TimeStampCache(defaultTimeStamp)) |> ignore - isImportsInvalidated + computeStampedReferencedAssemblies mainState currentState true |> ignore + mainState.isImportsInvalidated - member _.AllDependenciesDeprecated = allDependencies + member _.AllDependenciesDeprecated = mainState.allDependencies member _.PopulatePartialCheckingResults () = node { - let cache = TimeStampCache defaultTimeStamp // One per step - do! checkFileTimeStamps cache + do! checkFileTimeStamps () let! _ = currentState.finalizedBoundModel.GetOrComputeValue() - projectChecked.Trigger() + mainState.projectChecked.Trigger() } member builder.GetCheckResultsBeforeFileInProjectEvenIfStale filename: PartialCheckResults option = let slotOfFile = builder.GetSlotOfFileName filename - let result = tryGetBeforeSlot currentState slotOfFile + let result = tryGetBeforeSlot mainState currentState slotOfFile match result with | Some (boundModel, timestamp) -> Some (PartialCheckResults (boundModel, timestamp)) @@ -1194,11 +1192,10 @@ type IncrementalBuilder( | _ -> None member builder.TryGetCheckResultsBeforeFileInProject (filename) = - let cache = TimeStampCache defaultTimeStamp - let tmpState = computeStampedFileNames currentState cache + let tmpState = computeStampedFileNames mainState currentState let slotOfFile = builder.GetSlotOfFileName filename - match tryGetBeforeSlot tmpState slotOfFile with + match tryGetBeforeSlot mainState tmpState slotOfFile with | Some(boundModel, timestamp) -> PartialCheckResults(boundModel, timestamp) |> Some | _ -> None @@ -1207,9 +1204,8 @@ type IncrementalBuilder( member _.GetCheckResultsBeforeSlotInProject (slotOfFile) = node { - let cache = TimeStampCache defaultTimeStamp - do! checkFileTimeStamps cache - let! result = evalUpToTargetSlot currentState (slotOfFile - 1) + do! checkFileTimeStamps() + let! result = evalUpToTargetSlot mainState currentState (slotOfFile - 1) match result with | Some (boundModel, timestamp) -> return PartialCheckResults(boundModel, timestamp) | None -> return! failwith "Expected results to be ready. (GetCheckResultsBeforeSlotInProject)." @@ -1217,9 +1213,8 @@ type IncrementalBuilder( member _.GetFullCheckResultsBeforeSlotInProject (slotOfFile) = node { - let cache = TimeStampCache defaultTimeStamp - do! checkFileTimeStamps cache - let! result = evalUpToTargetSlot currentState (slotOfFile - 1) + do! checkFileTimeStamps() + let! result = evalUpToTargetSlot mainState currentState (slotOfFile - 1) match result with | Some (boundModel, timestamp) -> let! _ = boundModel.GetOrComputeTcInfoExtras() @@ -1251,8 +1246,7 @@ type IncrementalBuilder( member _.GetCheckResultsAndImplementationsForProject() = node { - let cache = TimeStampCache(defaultTimeStamp) - do! checkFileTimeStamps cache + do! checkFileTimeStamps() let! result = currentState.finalizedBoundModel.GetOrComputeValue() match result with | ((ilAssemRef, tcAssemblyDataOpt, tcAssemblyExprOpt, boundModel), timestamp) -> @@ -1267,18 +1261,17 @@ type IncrementalBuilder( return result } - member _.GetLogicalTimeStampForProject(cache) = - let tmpState = computeStampedFileNames currentState cache + member _.GetLogicalTimeStampForProject(_cache: TimeStampCache) = + let tmpState = computeStampedFileNames mainState currentState computeProjectTimeStamp tmpState member _.TryGetSlotOfFileName(filename: string) = - // Get the slot of the given file and force it to build. - let CompareFileNames (_, f2, _) = + let CompareFileNames (_, f2: string, _) = let result = String.Compare(filename, f2, StringComparison.CurrentCultureIgnoreCase)=0 || String.Compare(FileSystem.GetFullPathShim filename, FileSystem.GetFullPathShim f2, StringComparison.CurrentCultureIgnoreCase)=0 result - match fileNames |> Array.tryFindIndex CompareFileNames with + match mainState.sourceFiles |> Seq.tryFindIndex CompareFileNames with | Some slot -> Some slot | None -> None @@ -1287,22 +1280,38 @@ type IncrementalBuilder( | Some slot -> slot | None -> failwith (sprintf "The file '%s' was not part of the project. Did you call InvalidateConfiguration when the list of files in the project changed?" filename) - member _.GetSlotsCount () = fileNames.Length + member _.GetSlotsCount () = mainState.sourceFiles.Length member this.ContainsFile(filename: string) = (this.TryGetSlotOfFileName filename).IsSome member builder.GetParseResultsForFile (filename) = + let state = currentState let slotOfFile = builder.GetSlotOfFileName filename - let fileInfo = fileNames.[slotOfFile] + let fileInfo = state.documents.[slotOfFile] // re-parse on demand instead of retaining - let syntaxTree = GetSyntaxTree fileInfo + let syntaxTree = GetSyntaxTree mainState fileInfo syntaxTree.Parse None - member _.SourceFiles = sourceFiles |> List.map (fun (_, f, _) -> f) + member _.SourceFiles = mainState.sourceFiles |> Seq.map (fun (_, f, _) -> f) |> List.ofSeq + + member this.UpdateDocuments(docs: FSharpDocument seq) = + let state = currentState + + let newState = + // Sort the documents by slot in order to update in ascending order. + let docsWithSlotSorted = + docs + |> Seq.map (fun doc -> doc, this.GetSlotOfFileName(doc.FilePath)) + |> Seq.sortBy snd + (state, docsWithSlotSorted) + ||> Seq.fold (fun state (doc, slot) -> + let m, _, isLastCompiland = mainState.sourceFiles.[slot] + computeStampedFileName mainState state slot (m, doc, isLastCompiland) + ) + + setCurrentState newState CancellationToken.None - /// CreateIncrementalBuilder (for background type checking). Note that fsc.fs also - /// creates an incremental builder used by the command line compiler. static member TryCreateIncrementalBuilderForProjectOptions (legacyReferenceResolver, defaultFSharpBinariesDir, frameworkTcImportsCache: FrameworkImportsCache, @@ -1524,22 +1533,32 @@ type IncrementalBuilder( ) let builder = - new IncrementalBuilder( - initialBoundModel, - tcGlobals, - nonFrameworkAssemblyInputs, - tcConfig, - outfile, - assemblyName, - resourceManager, - sourceFiles, - enablePartialTypeChecking, - beforeFileChecked, - fileChecked, + let mainState = + { + isImportsInvalidated = false + initialBoundModel = initialBoundModel + initialBoundModelNode = GraphNode(node { return initialBoundModel }) + tcGlobals = tcGlobals + nonFrameworkAssemblyInputs = nonFrameworkAssemblyInputs + tcConfig = tcConfig + outfile = outfile + assemblyName = assemblyName + lexResourceManager = resourceManager + sourceFiles = ImmutableArray.CreateRange(sourceFiles) + enablePartialTypeChecking = enablePartialTypeChecking + allDependencies = allDependencies + beforeFileChecked = beforeFileChecked + fileChecked = fileChecked + fileParsed = Event<_>() + projectChecked = Event<_>() +#if !NO_EXTENSIONTYPING + importsInvalidatedByTypeProvider = importsInvalidatedByTypeProvider +#endif + } #if !NO_EXTENSIONTYPING - importsInvalidatedByTypeProvider, + importsInvalidatedByTypeProvider.Publish.Add(fun () -> mainState.isImportsInvalidated <- true) #endif - allDependencies) + new IncrementalBuilder(mainState) return Some builder with e -> errorRecoveryNoRange e @@ -1559,3 +1578,31 @@ type IncrementalBuilder( return builderOpt, diagnostics } + + + new(mainState) = + let sourceFiles = mainState.sourceFiles + + let boundModels = ImmutableArray.CreateBuilder(sourceFiles.Length) + let documents = + sourceFiles + |> Seq.map (fun (m, f, isLastCompiland) -> (m, FSharpDocument.CreateFromFile(f), isLastCompiland)) + |> ImmutableArray.CreateRange + + for slot = 0 to sourceFiles.Length - 1 do + boundModels.Add(createBoundModelGraphNode mainState documents boundModels slot) + + let referencedAssemblies = mainState.nonFrameworkAssemblyInputs |> Array.ofList + + let state = + { + documents = documents + stampedFileNames = Array.init sourceFiles.Length (fun _ -> DateTime.MinValue) |> ImmutableArray.CreateRange + logicalStampedFileNames = Array.init sourceFiles.Length (fun _ -> DateTime.MinValue) |> ImmutableArray.CreateRange + stampedReferencedAssemblies = Array.init referencedAssemblies.Length (fun _ -> DateTime.MinValue) |> ImmutableArray.CreateRange + boundModels = boundModels.ToImmutable() + finalizedBoundModel = createFinalizeBoundModelGraphNode mainState boundModels + } + let state = computeStampedReferencedAssemblies mainState state false + let state = computeStampedFileNames mainState state + IncrementalBuilder(mainState, state) diff --git a/src/fsharp/service/IncrementalBuild.fsi b/src/fsharp/service/IncrementalBuild.fsi index c8c5c26da6a..c3852309beb 100755 --- a/src/fsharp/service/IncrementalBuild.fsi +++ b/src/fsharp/service/IncrementalBuild.fsi @@ -237,6 +237,8 @@ type internal IncrementalBuilder = /// This may be a marginally long-running operation (parses are relatively quick, only one file needs to be parsed) member GetParseResultsForFile: filename:string -> ParsedInput * range * string * (PhasedDiagnostic * FSharpDiagnosticSeverity)[] + member UpdateDocuments: docs: FSharpDocument seq -> unit + /// Create the incremental builder static member TryCreateIncrementalBuilderForProjectOptions: LegacyReferenceResolver * diff --git a/src/fsharp/service/SemanticClassificationKey.fs b/src/fsharp/service/SemanticClassificationKey.fs index 960d3fade56..42962053074 100644 --- a/src/fsharp/service/SemanticClassificationKey.fs +++ b/src/fsharp/service/SemanticClassificationKey.fs @@ -56,8 +56,9 @@ type SemanticClassificationKeyStoreBuilder() = let b = BlobBuilder() member _.WriteAll (semanticClassification: SemanticClassificationItem[]) = - use ptr = fixed semanticClassification - b.WriteBytes(NativePtr.ofNativeInt (NativePtr.toNativeInt ptr), semanticClassification.Length * sizeof) + if semanticClassification.Length > 0 then + use ptr = fixed semanticClassification + b.WriteBytes(NativePtr.ofNativeInt (NativePtr.toNativeInt ptr), semanticClassification.Length * sizeof) member _.TryBuildAndReset() = if b.Count > 0 then diff --git a/src/fsharp/service/service.fs b/src/fsharp/service/service.fs index 1ed29a76651..7d767c2dbd4 100644 --- a/src/fsharp/service/service.fs +++ b/src/fsharp/service/service.fs @@ -303,7 +303,7 @@ type BackgroundCompiler( let! builderOpt, diagnostics = IncrementalBuilder.TryCreateIncrementalBuilderForProjectOptions - (legacyReferenceResolver, FSharpCheckerResultsSettings.defaultFSharpBinariesDir, frameworkTcImportsCache, loadClosure, Array.toList options.SourceFiles, + (legacyReferenceResolver, FSharpCheckerResultsSettings.defaultFSharpBinariesDir, frameworkTcImportsCache, loadClosure, List.ofArray options.SourceFiles, Array.toList options.OtherOptions, projectReferences, options.ProjectDirectory, options.UseScriptResolutionRules, keepAssemblyContents, keepAllBackgroundResolutions, tryGetMetadataSnapshot, suggestNamesForErrors, keepAllBackgroundSymbolUses, @@ -919,6 +919,16 @@ type BackgroundCompiler( let _ = createBuilderNode (options, userOpName, CancellationToken.None) () + member bc.UpdateDocuments(options: FSharpProjectOptions, docs) = + let builderNode = getOrCreateBuilder(options, "UpdateDocuments") + node { + match! builderNode with + | Some builder, _ -> + builder.UpdateDocuments(docs) + | _ -> + () + } + member bc.ClearCache(options: seq, _userOpName) = lock gate (fun () -> options @@ -1217,6 +1227,10 @@ type FSharpChecker(legacyReferenceResolver, let userOpName = defaultArg userOpName "Unknown" backgroundCompiler.InvalidateConfiguration(options, userOpName) + member _.UpdateBackgroundDocuments(options: FSharpProjectOptions, docs) = + backgroundCompiler.UpdateDocuments(options, docs) + |> Async.AwaitNodeCode + /// Clear the internal cache of the given projects. member _.ClearCache(options: FSharpProjectOptions seq, ?userOpName: string) = let userOpName = defaultArg userOpName "Unknown" diff --git a/src/fsharp/service/service.fsi b/src/fsharp/service/service.fsi index 97b6f9f7983..8134e7715e7 100644 --- a/src/fsharp/service/service.fsi +++ b/src/fsharp/service/service.fsi @@ -362,6 +362,13 @@ type public FSharpChecker = /// An optional string used for tracing compiler operations associated with this request. member InvalidateConfiguration: options: FSharpProjectOptions * ?userOpName: string -> unit + /// + /// This function is called when the documents of a particular project need to be updated in a batch for the background build. + /// + /// The options for the project or script, used to determine active --define conditionals and other options relevant to parsing. + /// FSharp documents + member UpdateBackgroundDocuments: options: FSharpProjectOptions * docs: FSharpDocument seq -> Async + /// Clear the internal cache of the given projects. /// The given project options. /// An optional string used for tracing compiler operations associated with this request. diff --git a/src/fsharp/utils/FileSystem.fs b/src/fsharp/utils/FileSystem.fs index 9346e6ab1a8..5deb1176e25 100644 --- a/src/fsharp/utils/FileSystem.fs +++ b/src/fsharp/utils/FileSystem.fs @@ -640,7 +640,7 @@ module public StreamExtensions = member s.Write (data: 'a) : unit = use sw = s.GetWriter() sw.Write(data) - + member s.GetReader(codePage: int option, ?retryLocked: bool) = let retryLocked = defaultArg retryLocked false let retryDelayMilliseconds = 50 diff --git a/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs b/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs index 18b6fd7e05d..63af099ba31 100644 --- a/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs +++ b/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs @@ -1992,6 +1992,7 @@ FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync 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.Unit] NotifyProjectCleaned(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) +FSharp.Compiler.CodeAnalysis.FSharpChecker: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] UpdateBackgroundDocuments(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, System.Collections.Generic.IEnumerable`1[FSharp.Compiler.CodeAnalysis.FSharpDocument]) 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.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]) @@ -2022,6 +2023,16 @@ FSharp.Compiler.CodeAnalysis.FSharpChecker: Void ClearLanguageServiceRootCachesA FSharp.Compiler.CodeAnalysis.FSharpChecker: Void InvalidateAll() FSharp.Compiler.CodeAnalysis.FSharpChecker: Void InvalidateConfiguration(FSharp.Compiler.CodeAnalysis.FSharpProjectOptions, Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.CodeAnalysis.FSharpChecker: Void set_MaxMemory(Int32) +FSharp.Compiler.CodeAnalysis.FSharpDocument +FSharp.Compiler.CodeAnalysis.FSharpDocument: Boolean IsOpen +FSharp.Compiler.CodeAnalysis.FSharpDocument: Boolean get_IsOpen() +FSharp.Compiler.CodeAnalysis.FSharpDocument: FSharp.Compiler.CodeAnalysis.DocumentText GetText() +FSharp.Compiler.CodeAnalysis.FSharpDocument: FSharp.Compiler.CodeAnalysis.FSharpDocument Create(System.String, Boolean, Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.DateTime], Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,FSharp.Compiler.Text.ISourceText]) +FSharp.Compiler.CodeAnalysis.FSharpDocument: FSharp.Compiler.CodeAnalysis.FSharpDocument CreateCopyFromFile(System.String, Boolean) +FSharp.Compiler.CodeAnalysis.FSharpDocument: System.DateTime TimeStamp +FSharp.Compiler.CodeAnalysis.FSharpDocument: System.DateTime get_TimeStamp() +FSharp.Compiler.CodeAnalysis.FSharpDocument: System.String FilePath +FSharp.Compiler.CodeAnalysis.FSharpDocument: System.String get_FilePath() FSharp.Compiler.CodeAnalysis.FSharpParseFileResults FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean IsBindingALambdaAtPosition(FSharp.Compiler.Text.Position) FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean IsPosContainedInApplication(FSharp.Compiler.Text.Position) @@ -5154,6 +5165,7 @@ FSharp.Compiler.Syntax.DebugPointAtBinding: Int32 Tag FSharp.Compiler.Syntax.DebugPointAtBinding: Int32 get_Tag() FSharp.Compiler.Syntax.DebugPointAtBinding: System.String ToString() FSharp.Compiler.Syntax.DebugPointAtFinally +FSharp.Compiler.Syntax.DebugPointAtFinally+Tags: Int32 Body FSharp.Compiler.Syntax.DebugPointAtFinally+Tags: Int32 No FSharp.Compiler.Syntax.DebugPointAtFinally+Tags: Int32 Yes FSharp.Compiler.Syntax.DebugPointAtFinally+Yes: FSharp.Compiler.Text.Range get_range() @@ -5161,12 +5173,16 @@ FSharp.Compiler.Syntax.DebugPointAtFinally+Yes: FSharp.Compiler.Text.Range range FSharp.Compiler.Syntax.DebugPointAtFinally: Boolean Equals(FSharp.Compiler.Syntax.DebugPointAtFinally) FSharp.Compiler.Syntax.DebugPointAtFinally: Boolean Equals(System.Object) FSharp.Compiler.Syntax.DebugPointAtFinally: Boolean Equals(System.Object, System.Collections.IEqualityComparer) +FSharp.Compiler.Syntax.DebugPointAtFinally: Boolean IsBody FSharp.Compiler.Syntax.DebugPointAtFinally: Boolean IsNo FSharp.Compiler.Syntax.DebugPointAtFinally: Boolean IsYes +FSharp.Compiler.Syntax.DebugPointAtFinally: Boolean get_IsBody() FSharp.Compiler.Syntax.DebugPointAtFinally: Boolean get_IsNo() FSharp.Compiler.Syntax.DebugPointAtFinally: Boolean get_IsYes() +FSharp.Compiler.Syntax.DebugPointAtFinally: FSharp.Compiler.Syntax.DebugPointAtFinally Body FSharp.Compiler.Syntax.DebugPointAtFinally: FSharp.Compiler.Syntax.DebugPointAtFinally NewYes(FSharp.Compiler.Text.Range) FSharp.Compiler.Syntax.DebugPointAtFinally: FSharp.Compiler.Syntax.DebugPointAtFinally No +FSharp.Compiler.Syntax.DebugPointAtFinally: FSharp.Compiler.Syntax.DebugPointAtFinally get_Body() FSharp.Compiler.Syntax.DebugPointAtFinally: FSharp.Compiler.Syntax.DebugPointAtFinally get_No() FSharp.Compiler.Syntax.DebugPointAtFinally: FSharp.Compiler.Syntax.DebugPointAtFinally+Tags FSharp.Compiler.Syntax.DebugPointAtFinally: FSharp.Compiler.Syntax.DebugPointAtFinally+Yes @@ -5175,11 +5191,6 @@ FSharp.Compiler.Syntax.DebugPointAtFinally: Int32 GetHashCode(System.Collections FSharp.Compiler.Syntax.DebugPointAtFinally: Int32 Tag FSharp.Compiler.Syntax.DebugPointAtFinally: Int32 get_Tag() FSharp.Compiler.Syntax.DebugPointAtFinally: System.String ToString() -FSharp.Compiler.Syntax.DebugPointAtFinally+Tags: Int32 Body -FSharp.Compiler.Syntax.DebugPointAtFinally: Boolean IsBody -FSharp.Compiler.Syntax.DebugPointAtFinally: Boolean get_IsBody() -FSharp.Compiler.Syntax.DebugPointAtFinally: FSharp.Compiler.Syntax.DebugPointAtFinally Body -FSharp.Compiler.Syntax.DebugPointAtFinally: FSharp.Compiler.Syntax.DebugPointAtFinally get_Body() FSharp.Compiler.Syntax.DebugPointAtFor FSharp.Compiler.Syntax.DebugPointAtFor+Tags: Int32 No FSharp.Compiler.Syntax.DebugPointAtFor+Tags: Int32 Yes diff --git a/tests/fsharp/tools/fsharp41/net45/providerDesigner.dll b/tests/fsharp/tools/fsharp41/net45/providerDesigner.dll index cb79536e157..3d36c659c05 100644 Binary files a/tests/fsharp/tools/fsharp41/net45/providerDesigner.dll and b/tests/fsharp/tools/fsharp41/net45/providerDesigner.dll differ diff --git a/tests/fsharp/tools/fsharp41/net461/providerDesigner.dll b/tests/fsharp/tools/fsharp41/net461/providerDesigner.dll index 1b581c260dc..a1196e9ddea 100644 Binary files a/tests/fsharp/tools/fsharp41/net461/providerDesigner.dll and b/tests/fsharp/tools/fsharp41/net461/providerDesigner.dll differ diff --git a/tests/fsharp/tools/fsharp41/netstandard2.0/providerDesigner.dll b/tests/fsharp/tools/fsharp41/netstandard2.0/providerDesigner.dll index 1b6a4ded584..d304e2a81c6 100644 Binary files a/tests/fsharp/tools/fsharp41/netstandard2.0/providerDesigner.dll and b/tests/fsharp/tools/fsharp41/netstandard2.0/providerDesigner.dll differ diff --git a/tests/fsharp/typeProviders/fsharp41/net45/providerDesigner.dll b/tests/fsharp/typeProviders/fsharp41/net45/providerDesigner.dll index c033697fd00..76adae056cb 100644 Binary files a/tests/fsharp/typeProviders/fsharp41/net45/providerDesigner.dll and b/tests/fsharp/typeProviders/fsharp41/net45/providerDesigner.dll differ diff --git a/tests/fsharp/typeProviders/fsharp41/net461/providerDesigner.dll b/tests/fsharp/typeProviders/fsharp41/net461/providerDesigner.dll index a26e114cc74..665d51e85e3 100644 Binary files a/tests/fsharp/typeProviders/fsharp41/net461/providerDesigner.dll and b/tests/fsharp/typeProviders/fsharp41/net461/providerDesigner.dll differ diff --git a/tests/fsharp/typeProviders/fsharp41/netstandard2.0/providerDesigner.dll b/tests/fsharp/typeProviders/fsharp41/netstandard2.0/providerDesigner.dll index 4f0c61b8cea..244f2329815 100644 Binary files a/tests/fsharp/typeProviders/fsharp41/netstandard2.0/providerDesigner.dll and b/tests/fsharp/typeProviders/fsharp41/netstandard2.0/providerDesigner.dll differ diff --git a/tests/service/EditorTests.fs b/tests/service/EditorTests.fs index ab3a1f8e492..bc30ae2dfd7 100644 --- a/tests/service/EditorTests.fs +++ b/tests/service/EditorTests.fs @@ -1206,7 +1206,7 @@ let _ = RegexTypedStatic.IsMatch<"ABC" >( (*$*) ) // TEST: no assert on Ctrl-sp """ FileSystem.OpenFileForWriteShim(fileName1).Write(fileSource1) - let fileLines1 = FileSystem.OpenFileForReadShim(fileName1).AsStream().ReadLines() + let fileLines1 = FileSystem.OpenFileForReadShim(fileName1).ReadLines() let fileNames = [fileName1] let args = Array.append (mkProjectCommandLineArgs (dllName, fileNames)) [| "-r:" + PathRelativeToTestAssembly(@"DummyProviderForLanguageServiceTesting.dll") |] diff --git a/tests/service/FileSystemTests.fs b/tests/service/FileSystemTests.fs index a6dd94fcc45..115a6409494 100644 --- a/tests/service/FileSystemTests.fs +++ b/tests/service/FileSystemTests.fs @@ -40,8 +40,7 @@ type internal MyFileSystem() = let shouldShadowCopy = defaultArg shouldShadowCopy false let useMemoryMappedFile = defaultArg useMemoryMappedFile false match MyFileSystem.FilesCache.TryGetValue filePath with - | true, text -> - new MemoryStream(Encoding.UTF8.GetBytes(text)) :> Stream + | true, text -> new MemoryStream(Encoding.UTF8.GetBytes(text)) :> Stream | _ -> base.OpenFileForReadShim(filePath, useMemoryMappedFile, shouldShadowCopy) override _.FileExistsShim(fileName) = MyFileSystem.FilesCache.ContainsKey(fileName) || base.FileExistsShim(fileName) diff --git a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs index a3844ae749e..b6683c4e24a 100644 --- a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs +++ b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs @@ -167,7 +167,7 @@ type internal FSharpClassificationService let! sourceText = document.GetTextAsync(cancellationToken) // If we are trying to get semantic classification for a document that is not open, get the results from the background and cache it. - // We do this for find all references when it is populating results. + // We do this for find all references when it is populating results. // We cache it temporarily so we do not have to continously call into the checker and perform a background operation. if not (document.Project.Solution.Workspace.IsDocumentOpen document.Id) then match! semanticClassificationCache.TryGetValueAsync document |> liftAsync with @@ -179,10 +179,10 @@ type internal FSharpClassificationService do! semanticClassificationCache.SetAsync(document, classificationDataLookup) |> liftAsync addSemanticClassificationByLookup sourceText textSpan classificationDataLookup result else - let! _, _, checkResults = checkerProvider.Checker.ParseAndCheckDocument(document, projectOptions, allowStaleResults = false, userOpName=userOpName) - let targetRange = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, textSpan, sourceText) - let classificationData = checkResults.GetSemanticClassification (Some targetRange) - addSemanticClassification sourceText textSpan classificationData result + let! _ = checkerProvider.Checker.CheckDocumentInProject(document, projectOptions) |> liftAsync + let! classificationData = checkerProvider.Checker.GetBackgroundSemanticClassificationForFile(document.FilePath, projectOptions, userOpName=userOpName) |> liftAsync + let classificationDataLookup = toSemanticClassificationLookup classificationData + addSemanticClassificationByLookup sourceText textSpan classificationDataLookup result } |> Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask cancellationToken diff --git a/vsintegration/src/FSharp.Editor/CodeFix/AddOpenCodeFixProvider.fs b/vsintegration/src/FSharp.Editor/CodeFix/AddOpenCodeFixProvider.fs index 3890eba616f..d341ea30e80 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/AddOpenCodeFixProvider.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/AddOpenCodeFixProvider.fs @@ -90,7 +90,7 @@ type internal FSharpAddOpenCodeFixProvider let document = context.Document let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName) let! sourceText = context.Document.GetTextAsync(context.CancellationToken) - let! _, parsedInput, checkResults = checker.ParseAndCheckDocument(document, projectOptions, userOpName = userOpName) + let! parseResults, checkResults = checker.CheckDocumentInProject(document, projectOptions) |> liftAsync let line = sourceText.Lines.GetLineFromPosition(context.Span.End) let linePos = sourceText.Lines.GetLinePosition(context.Span.End) let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions @@ -110,7 +110,7 @@ type internal FSharpAddOpenCodeFixProvider let endPos = Position.fromZ endLinePos.Line endLinePos.Character Range.mkRange context.Document.FilePath startPos endPos - let isAttribute = ParsedInput.GetEntityKind(unresolvedIdentRange.Start, parsedInput) = Some EntityKind.Attribute + let isAttribute = ParsedInput.GetEntityKind(unresolvedIdentRange.Start, parseResults.ParseTree) = Some EntityKind.Attribute let entities = assemblyContentProvider.GetAllEntitiesInProjectAndReferencedAssemblies checkResults @@ -126,7 +126,7 @@ type internal FSharpAddOpenCodeFixProvider s.CleanedIdents |> Array.replace (s.CleanedIdents.Length - 1) (lastIdent.Substring(0, lastIdent.Length - 9)) ]) - let longIdent = ParsedInput.GetLongIdentAt parsedInput unresolvedIdentRange.End + let longIdent = ParsedInput.GetLongIdentAt parseResults.ParseTree unresolvedIdentRange.End let! maybeUnresolvedIdents = longIdent @@ -141,7 +141,7 @@ type internal FSharpAddOpenCodeFixProvider if document.FSharpOptions.CodeFixes.AlwaysPlaceOpensAtTopLevel then OpenStatementInsertionPoint.TopLevel else OpenStatementInsertionPoint.Nearest - let createEntity = ParsedInput.TryFindInsertionContext unresolvedIdentRange.StartLine parsedInput maybeUnresolvedIdents insertionPoint + let createEntity = ParsedInput.TryFindInsertionContext unresolvedIdentRange.StartLine parseResults.ParseTree maybeUnresolvedIdents insertionPoint return entities |> Seq.map createEntity |> Seq.concat |> Seq.toList |> addSuggestionsAsCodeFixes context } |> Async.Ignore diff --git a/vsintegration/src/FSharp.Editor/CodeFix/AddTypeAnnotationToObjectOfIndeterminateType.fs b/vsintegration/src/FSharp.Editor/CodeFix/AddTypeAnnotationToObjectOfIndeterminateType.fs index cbf1b645648..9dbf8d7f16d 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/AddTypeAnnotationToObjectOfIndeterminateType.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/AddTypeAnnotationToObjectOfIndeterminateType.fs @@ -47,7 +47,7 @@ type internal FSharpAddTypeAnnotationToObjectOfIndeterminateTypeFixProvider let textLine = sourceText.Lines.GetLineFromPosition position let textLinePos = sourceText.Lines.GetLinePosition position let fcsTextLineNumber = Line.fromZ textLinePos.Line - let! _, _, checkFileResults = checker.ParseAndCheckDocument (document, projectOptions, userOpName=userOpName) + let! _, checkFileResults = checker.CheckDocumentInProject(document, projectOptions) |> liftAsync let! lexerSymbol = Tokenizer.getSymbolAtPosition (document.Id, sourceText, position, document.FilePath, defines, SymbolLookupKind.Greedy, false, false) let decl = checkFileResults.GetDeclarationLocation (fcsTextLineNumber, lexerSymbol.Ident.idRange.EndColumn, textLine.ToString(), lexerSymbol.FullIsland, false) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ChangeRefCellDerefToNotExpression.fs b/vsintegration/src/FSharp.Editor/CodeFix/ChangeRefCellDerefToNotExpression.fs index b595cc21e55..5a320d6f5d4 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ChangeRefCellDerefToNotExpression.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ChangeRefCellDerefToNotExpression.fs @@ -27,7 +27,7 @@ type internal FSharpChangeRefCellDerefToNotExpressionCodeFixProvider let document = context.Document let! parsingOptions, _ = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName) let! sourceText = context.Document.GetTextAsync(context.CancellationToken) - let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions, userOpName=userOpName) + let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions) |> liftAsync let errorRange = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText) let! derefRange = parseResults.TryRangeOfRefCellDereferenceContainingPos errorRange.Start diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ConvertCSharpLambdaToFSharpLambda.fs b/vsintegration/src/FSharp.Editor/CodeFix/ConvertCSharpLambdaToFSharpLambda.fs index 169588e1c84..a6627e71df5 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ConvertCSharpLambdaToFSharpLambda.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ConvertCSharpLambdaToFSharpLambda.fs @@ -24,7 +24,7 @@ type internal FSharpConvertCSharpLambdaToFSharpLambdaCodeFixProvider override _.RegisterCodeFixesAsync context = asyncMaybe { let! parsingOptions, _ = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(context.Document, context.CancellationToken, userOpName) - let! parseResults = checkerProvider.Checker.ParseDocument(context.Document, parsingOptions, userOpName) + let! parseResults = checkerProvider.Checker.ParseDocument(context.Document, parsingOptions) |> liftAsync let! sourceText = context.Document.GetTextAsync(context.CancellationToken) let errorRange = RoslynHelpers.TextSpanToFSharpRange(context.Document.FilePath, context.Span, sourceText) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ConvertToAnonymousRecord.fs b/vsintegration/src/FSharp.Editor/CodeFix/ConvertToAnonymousRecord.fs index 3710101ed4e..62f9c741421 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ConvertToAnonymousRecord.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ConvertToAnonymousRecord.fs @@ -29,7 +29,7 @@ type internal FSharpConvertToAnonymousRecordCodeFixProvider asyncMaybe { let document = context.Document let! parsingOptions, _ = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName) - let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions, userOpName=userOpName) + let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions) |> liftAsync let! sourceText = context.Document.GetTextAsync(context.CancellationToken) let errorRange = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ImplementInterfaceCodeFixProvider.fs b/vsintegration/src/FSharp.Editor/CodeFix/ImplementInterfaceCodeFixProvider.fs index c21a1123380..580bfefe909 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ImplementInterfaceCodeFixProvider.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ImplementInterfaceCodeFixProvider.fs @@ -145,7 +145,7 @@ type internal FSharpImplementInterfaceCodeFixProvider let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(context.Document, context.CancellationToken, userOpName) let cancellationToken = context.CancellationToken let! sourceText = context.Document.GetTextAsync(cancellationToken) - let! _, parsedInput, checkFileResults = checker.ParseAndCheckDocument(context.Document, projectOptions, userOpName = userOpName) + let! parseResults, checkFileResults = checker.CheckDocumentInProject(context.Document, projectOptions) |> liftAsync let textLine = sourceText.Lines.GetLineFromPosition context.Span.Start let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions // Notice that context.Span doesn't return reliable ranges to find tokens at exact positions. @@ -172,7 +172,7 @@ type internal FSharpImplementInterfaceCodeFixProvider | '}' -> None | _ -> Some context.Span.End - let! interfaceState = queryInterfaceState appendBracketAt interfacePos tokens parsedInput + let! interfaceState = queryInterfaceState appendBracketAt interfacePos tokens parseResults.ParseTree let! symbol = Tokenizer.getSymbolAtPosition(context.Document.Id, sourceText, fixupPosition, context.Document.FilePath, defines, SymbolLookupKind.Greedy, false, false) let fcsTextLineNumber = textLine.LineNumber + 1 let lineContents = textLine.ToString() diff --git a/vsintegration/src/FSharp.Editor/CodeFix/MakeDeclarationMutable.fs b/vsintegration/src/FSharp.Editor/CodeFix/MakeDeclarationMutable.fs index 9aab9475320..27dce9cb0c2 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/MakeDeclarationMutable.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/MakeDeclarationMutable.fs @@ -46,7 +46,7 @@ type internal FSharpMakeDeclarationMutableFixProvider let textLine = sourceText.Lines.GetLineFromPosition position let textLinePos = sourceText.Lines.GetLinePosition position let fcsTextLineNumber = Line.fromZ textLinePos.Line - let! parseFileResults, _, checkFileResults = checker.ParseAndCheckDocument (document, projectOptions, userOpName=userOpName) + let! parseFileResults, checkFileResults = checker.CheckDocumentInProject(document, projectOptions) |> liftAsync let! lexerSymbol = Tokenizer.getSymbolAtPosition (document.Id, sourceText, position, document.FilePath, defines, SymbolLookupKind.Greedy, false, false) let decl = checkFileResults.GetDeclarationLocation (fcsTextLineNumber, lexerSymbol.Ident.idRange.EndColumn, textLine.ToString(), lexerSymbol.FullIsland, false) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/MakeOuterBindingRecursive.fs b/vsintegration/src/FSharp.Editor/CodeFix/MakeOuterBindingRecursive.fs index 67b586688e5..8c54567942b 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/MakeOuterBindingRecursive.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/MakeOuterBindingRecursive.fs @@ -25,7 +25,7 @@ type internal FSharpMakeOuterBindingRecursiveCodeFixProvider override _.RegisterCodeFixesAsync context = asyncMaybe { let! parsingOptions, _ = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(context.Document, context.CancellationToken, userOpName) - let! parseResults = checkerProvider.Checker.ParseDocument(context.Document, parsingOptions, userOpName) + let! parseResults = checkerProvider.Checker.ParseDocument(context.Document, parsingOptions) |> liftAsync let! sourceText = context.Document.GetTextAsync(context.CancellationToken) let diagnosticRange = RoslynHelpers.TextSpanToFSharpRange(context.Document.FilePath, context.Span, sourceText) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RemoveReturnOrYield.fs b/vsintegration/src/FSharp.Editor/CodeFix/RemoveReturnOrYield.fs index 59e6ff102c1..95b5377b504 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RemoveReturnOrYield.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RemoveReturnOrYield.fs @@ -24,7 +24,7 @@ type internal FSharpRemoveReturnOrYieldCodeFixProvider override _.RegisterCodeFixesAsync context = asyncMaybe { let! parsingOptions, _ = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(context.Document, context.CancellationToken, userOpName) - let! parseResults = checkerProvider.Checker.ParseDocument(context.Document, parsingOptions, userOpName=userOpName) + let! parseResults = checkerProvider.Checker.ParseDocument(context.Document, parsingOptions) |> liftAsync let! sourceText = context.Document.GetTextAsync(context.CancellationToken) let errorRange = RoslynHelpers.TextSpanToFSharpRange(context.Document.FilePath, context.Span, sourceText) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs index 35e489e8675..7ac03d8f4a8 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RemoveUnusedBinding.fs @@ -32,7 +32,7 @@ type internal FSharpRemoveUnusedBindingCodeFixProvider let! sourceText = document.GetTextAsync(context.CancellationToken) let! parsingOptions, _ = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName) - let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions, userOpName=userOpName) + let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions) |> liftAsync let diagnostics = context.Diagnostics diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs b/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs index 96ecbcd4351..93ee2c3e57e 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RenameUnusedValue.fs @@ -42,7 +42,7 @@ type internal FSharpRenameUnusedValueCodeFixProvider // where backtickes are replaced with parens. if not (PrettyNaming.IsOperatorOrBacktickedName ident) && not (ident.StartsWith "``") then let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName) - let! _, _, checkResults = checker.ParseAndCheckDocument(document, projectOptions, userOpName=userOpName) + let! _, checkResults = checker.CheckDocumentInProject(document, projectOptions) |> liftAsync let m = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText) let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions let! lexerSymbol = Tokenizer.getSymbolAtPosition (document.Id, sourceText, context.Span.Start, document.FilePath, defines, SymbolLookupKind.Greedy, false, false) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs b/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs index fdbd65bd228..0c9787e70e1 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs @@ -35,7 +35,7 @@ type internal FSharpReplaceWithSuggestionCodeFixProvider let document = context.Document let! _, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName) - let! parseFileResults, _, checkFileResults = checker.ParseAndCheckDocument(document, projectOptions, userOpName=userOpName) + let! parseFileResults, checkFileResults = checker.CheckDocumentInProject(document, projectOptions) |> liftAsync // This is all needed to get a declaration list let! sourceText = document.GetTextAsync(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/UseMutationWhenValueIsMutable.fs b/vsintegration/src/FSharp.Editor/CodeFix/UseMutationWhenValueIsMutable.fs index 44a1fa5682d..100383057b1 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/UseMutationWhenValueIsMutable.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/UseMutationWhenValueIsMutable.fs @@ -56,7 +56,7 @@ type internal FSharpUseMutationWhenValueIsMutableFixProvider let textLine = sourceText.Lines.GetLineFromPosition adjustedPosition let textLinePos = sourceText.Lines.GetLinePosition adjustedPosition let fcsTextLineNumber = Line.fromZ textLinePos.Line - let! _, _, checkFileResults = checker.ParseAndCheckDocument (document, projectOptions, userOpName=userOpName) + let! _, checkFileResults = checker.CheckDocumentInProject(document, projectOptions) |> liftAsync let! lexerSymbol = Tokenizer.getSymbolAtPosition (document.Id, sourceText, adjustedPosition, document.FilePath, defines, SymbolLookupKind.Greedy, false, false) let! symbolUse = checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, lexerSymbol.Ident.idRange.EndColumn, textLine.ToString(), lexerSymbol.FullIsland) diff --git a/vsintegration/src/FSharp.Editor/CodeLens/FSharpCodeLensService.fs b/vsintegration/src/FSharp.Editor/CodeLens/FSharpCodeLensService.fs index a9b1fc84584..867d93c48d6 100644 --- a/vsintegration/src/FSharp.Editor/CodeLens/FSharpCodeLensService.fs +++ b/vsintegration/src/FSharp.Editor/CodeLens/FSharpCodeLensService.fs @@ -158,7 +158,7 @@ type internal FSharpCodeLensService #endif let! document = workspace.CurrentSolution.GetDocument(documentId.Value) |> Option.ofObj let! _, options = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, bufferChangedCts.Token, userOpName) - let! _, parsedInput, checkFileResults = checker.ParseAndCheckDocument(document, options, "LineLens", allowStaleResults=true) + let! parseResults, checkFileResults = checker.CheckDocumentInProject(document, options) |> liftAsync #if DEBUG logInfof "Getting uses of all symbols!" #endif @@ -236,7 +236,7 @@ type internal FSharpCodeLensService oldResults.Remove funcID |> ignore else let declarationLine, range = - match visit func.DeclarationLocation.Start parsedInput with + match visit func.DeclarationLocation.Start parseResults.ParseTree with | Some range -> range.StartLine - 1, range | _ -> func.DeclarationLocation.StartLine - 1, func.DeclarationLocation // Track the old element for removal @@ -267,7 +267,7 @@ type internal FSharpCodeLensService for unattachedSymbol in unattachedSymbols do let symbolUse, func, funcID, fullTypeSignature = unattachedSymbol let declarationLine, range = - match visit func.DeclarationLocation.Start parsedInput with + match visit func.DeclarationLocation.Start parseResults.ParseTree with | Some range -> range.StartLine - 1, range | _ -> func.DeclarationLocation.StartLine - 1, func.DeclarationLocation diff --git a/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs b/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs index 1f77bba3428..e13b840644c 100644 --- a/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs +++ b/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs @@ -25,9 +25,9 @@ type internal FSharpHelpContextService ) = static let userOpName = "ImplementInterfaceCodeFix" - static member GetHelpTerm(checker: FSharpChecker, document: Document, options, span: TextSpan, tokens: List, perfOptions) : Async = + static member GetHelpTerm(checker: FSharpChecker, document: Document, options, span: TextSpan, tokens: List, _perfOptions) : Async = asyncMaybe { - let! _, _, check = checker.ParseAndCheckDocument(document, options, perfOptions, userOpName) + let! _, check = checker.CheckDocumentInProject(document, options) |> liftAsync let! sourceText = document.GetTextAsync() |> liftTaskAsync let textLines = sourceText.Lines let lineInfo = textLines.GetLineFromPosition(span.Start) diff --git a/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs b/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs index 4dd22f0ffa1..70a402290d6 100644 --- a/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs +++ b/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs @@ -70,7 +70,7 @@ type internal XmlDocCommandFilter let! parsingOptions, _options = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, CancellationToken.None, userOpName) let! cancellationToken = Async.CancellationToken |> liftAsync let! sourceText = document.GetTextAsync(cancellationToken) - let! parseResults = checker.ParseDocument(document, parsingOptions, userOpName) + let! parseResults = checker.ParseDocument(document, parsingOptions) |> liftAsync let xmlDocables = XmlDocParser.GetXmlDocables (sourceText.ToFSharpSourceText(), parseResults.ParseTree) let xmlDocablesBelowThisLine = // +1 because looking below current line for e.g. a 'member' diff --git a/vsintegration/src/FSharp.Editor/Common/Extensions.fs b/vsintegration/src/FSharp.Editor/Common/Extensions.fs index 825a010f387..40ac2c767d3 100644 --- a/vsintegration/src/FSharp.Editor/Common/Extensions.fs +++ b/vsintegration/src/FSharp.Editor/Common/Extensions.fs @@ -14,6 +14,7 @@ open Microsoft.CodeAnalysis.Host open FSharp.Compiler.EditorServices open FSharp.Compiler.Syntax open FSharp.Compiler.Text +open FSharp.Compiler.CodeAnalysis open Microsoft.VisualStudio.FSharp.Editor @@ -289,3 +290,27 @@ module Exception = | _ -> root |> flattenInner |> String.concat " ---> " + +type Document with + + member this.ToFSharpDocument() = + let dt = DateTime.UtcNow + let getTimeStamp = fun () -> dt + + let mutable weakFSharpText = Unchecked.defaultof<_> + let getSourceText = fun () -> + match weakFSharpText with + | null -> + let fsharpText = this.GetTextAsync().Result.ToFSharpSourceText() + weakFSharpText <- WeakReference<_>(fsharpText) + fsharpText + | _ -> + match weakFSharpText.TryGetTarget() with + | true, fsharpText -> fsharpText + | _ -> + let fsharpText = this.GetTextAsync().Result.ToFSharpSourceText() + weakFSharpText <- WeakReference<_>(fsharpText) + fsharpText + + let isOpen = this.Project.Solution.Workspace.IsDocumentOpen(this.Id) + FSharpDocument.Create(this.FilePath, isOpen, getTimeStamp, getSourceText) diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs index f99d696c62e..fe73a425179 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs @@ -108,10 +108,10 @@ type internal FSharpCompletionProvider static member ProvideCompletionsAsyncAux(checker: FSharpChecker, document: Document, caretPosition: int, options: FSharpProjectOptions, - getAllSymbols: FSharpCheckFileResults -> AssemblySymbol list, languageServicePerformanceOptions: LanguageServicePerformanceOptions, intellisenseOptions: IntelliSenseOptions) = + getAllSymbols: FSharpCheckFileResults -> AssemblySymbol list, _languageServicePerformanceOptions: LanguageServicePerformanceOptions, intellisenseOptions: IntelliSenseOptions) = asyncMaybe { - let! parseResults, _, checkFileResults = checker.ParseAndCheckDocument(document, options, languageServicePerformanceOptions, userOpName = userOpName) + let! parseResults, checkFileResults = checker.CheckDocumentInProject(document, options) |> liftAsync let! sourceText = document.GetTextAsync() |> liftTaskAsync let textLines = sourceText.Lines let caretLinePos = textLines.GetLinePosition(caretPosition) @@ -292,7 +292,7 @@ type internal FSharpCompletionProvider let textWithItemCommitted = sourceText.WithChanges(TextChange(item.Span, nameInCode)) let line = sourceText.Lines.GetLineFromPosition(item.Span.Start) let! parsingOptions, _options = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName) - let! parseResults = checker.ParseDocument(document, parsingOptions, userOpName) + let! parseResults = checker.ParseDocument(document, parsingOptions) |> liftAsync let fullNameIdents = fullName |> Option.map (fun x -> x.Split '.') |> Option.defaultValue [||] let insertionPoint = diff --git a/vsintegration/src/FSharp.Editor/Completion/SignatureHelp.fs b/vsintegration/src/FSharp.Editor/Completion/SignatureHelp.fs index 96ff3b2d7a2..a4fb179ca18 100644 --- a/vsintegration/src/FSharp.Editor/Completion/SignatureHelp.fs +++ b/vsintegration/src/FSharp.Editor/Completion/SignatureHelp.fs @@ -508,15 +508,14 @@ type internal FSharpSignatureHelpProvider possibleCurrentSignatureHelpSessionKind: CurrentSignatureHelpSessionKind option ) = asyncMaybe { + let! parseResults, checkFileResults = checker.CheckDocumentInProject(document, options) |> liftAsync + let! sourceText = document.GetTextAsync() |> liftTaskAsync let textLines = sourceText.Lines - let perfOptions = document.FSharpOptions.LanguageServicePerformance let caretLinePos = textLines.GetLinePosition(caretPosition) let caretLineColumn = caretLinePos.Character - let! parseResults, _, checkFileResults = checker.ParseAndCheckDocument(document, options, perfOptions, userOpName = userOpName) - let adjustedColumnInSource = let rec loop ch pos = if Char.IsWhiteSpace(ch) then diff --git a/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs b/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs index 77f3081d879..f3b26332c5c 100644 --- a/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs +++ b/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs @@ -40,10 +40,8 @@ type internal FSharpBreakpointResolutionService else let textLineColumn = textLinePos.Character let fcsTextLineNumber = Line.fromZ textLinePos.Line // Roslyn line numbers are zero-based, FSharp.Compiler.Service line numbers are 1-based - let! parseResults = checker.ParseDocument(document, parsingOptions, userOpName) - match parseResults with - | Some parseResults -> return parseResults.ValidateBreakpointLocation(mkPos fcsTextLineNumber textLineColumn) - | _ -> return None + let! parseResults = checker.ParseDocument(document, parsingOptions) + return parseResults.ValidateBreakpointLocation(mkPos fcsTextLineNumber textLineColumn) } interface IFSharpBreakpointResolutionService with diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs index dd26fbf1026..5e4666f4e89 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs @@ -58,31 +58,23 @@ type internal FSharpDocumentDiagnosticAnalyzer static member GetDiagnostics(checker: FSharpChecker, document: Document, parsingOptions: FSharpParsingOptions, options: FSharpProjectOptions, diagnosticType: DiagnosticsType) = async { - let! ct = Async.CancellationToken - - let! parseResults = checker.ParseDocument(document, parsingOptions, userOpName) - match parseResults with - | None -> return ImmutableArray.Empty - | Some parseResults -> - - let! sourceText = document.GetTextAsync(ct) |> Async.AwaitTask - let filePath = document.FilePath - let! errors = async { match diagnosticType with | DiagnosticsType.Semantic -> - let! checkResultsAnswer = checker.CheckDocument(document, parseResults, options, userOpName) - match checkResultsAnswer with - | FSharpCheckFileAnswer.Aborted -> return [||] - | FSharpCheckFileAnswer.Succeeded results -> - // In order to eleminate duplicates, we should not return parse errors here because they are returned by `AnalyzeSyntaxAsync` method. - let allErrors = HashSet(results.Diagnostics, errorInfoEqualityComparer) - allErrors.ExceptWith(parseResults.Diagnostics) - return Seq.toArray allErrors + let! parseResults, checkResults = checker.CheckDocumentInProject(document, options) + // In order to eleminate duplicates, we should not return parse errors here because they are returned by `AnalyzeSyntaxAsync` method. + let allErrors = HashSet(checkResults.Diagnostics, errorInfoEqualityComparer) + allErrors.ExceptWith(parseResults.Diagnostics) + return Seq.toArray allErrors | DiagnosticsType.Syntax -> + let! parseResults = checker.ParseDocument(document, parsingOptions) return parseResults.Diagnostics } + + let! ct = Async.CancellationToken + let! sourceText = document.GetTextAsync(ct) |> Async.AwaitTask + let filePath = document.FilePath let results = HashSet(errors, errorInfoEqualityComparer) diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/SimplifyNameDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/SimplifyNameDiagnosticAnalyzer.fs index 64007b19de8..c335caf30d4 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/SimplifyNameDiagnosticAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/SimplifyNameDiagnosticAnalyzer.fs @@ -51,7 +51,7 @@ type internal SimplifyNameDiagnosticAnalyzer | _ -> let! sourceText = document.GetTextAsync() let checker = checkerProvider.Checker - let! _, _, checkResults = checker.ParseAndCheckDocument(document, projectOptions, userOpName=userOpName) + let! _, checkResults = checker.CheckDocumentInProject(document, projectOptions) |> liftAsync let! result = SimplifyNames.getSimplifiableNames(checkResults, fun lineNumber -> sourceText.Lines.[Line.toZ lineNumber].ToString()) |> liftAsync let mutable diag = ResizeArray() for r in result do diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/UnusedDeclarationsAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/UnusedDeclarationsAnalyzer.fs index 094796c9e36..9448b62b4f0 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/UnusedDeclarationsAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/UnusedDeclarationsAnalyzer.fs @@ -35,7 +35,7 @@ type internal UnusedDeclarationsAnalyzer | (_parsingOptions, projectOptions) -> let! sourceText = document.GetTextAsync() let checker = checkerProvider.Checker - let! _, _, checkResults = checker.ParseAndCheckDocument(document, projectOptions, userOpName = userOpName) + let! _, checkResults = checker.CheckDocumentInProject(document, projectOptions) |> liftAsync let! unusedRanges = UnusedDeclarations.getUnusedDeclarations( checkResults, (isScriptFile document.FilePath)) |> liftAsync return unusedRanges diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/UnusedOpensDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/UnusedOpensDiagnosticAnalyzer.fs index c63341ecd0a..d22d1627541 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/UnusedOpensDiagnosticAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/UnusedOpensDiagnosticAnalyzer.fs @@ -29,8 +29,9 @@ type internal UnusedOpensDiagnosticAnalyzer static member GetUnusedOpenRanges(document: Document, options, checker: FSharpChecker) : Async> = asyncMaybe { do! Option.guard document.FSharpOptions.CodeFixes.UnusedOpens - let! sourceText = document.GetTextAsync() - let! _, _, checkResults = checker.ParseAndCheckDocument(document, options, userOpName = userOpName) + let! _, checkResults = checker.CheckDocumentInProject(document, options) |> liftAsync + let! ct = Async.CancellationToken |> liftAsync + let! sourceText = document.GetTextAsync(ct) |> Async.AwaitTask |> liftAsync let! unusedOpens = UnusedOpens.getUnusedOpens(checkResults, fun lineNumber -> sourceText.Lines.[Line.toZ lineNumber].ToString()) |> liftAsync return unusedOpens } diff --git a/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs b/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs index c2309227658..83618a67728 100644 --- a/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs +++ b/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs @@ -52,7 +52,7 @@ type internal FSharpDocumentHighlightsService [] (checkerP |> Seq.toArray static member GetDocumentHighlights(checker: FSharpChecker, document: Document, position: int, - defines: string list, options: FSharpProjectOptions, languageServicePerformanceOptions: LanguageServicePerformanceOptions) : Async = + defines: string list, options: FSharpProjectOptions, _languageServicePerformanceOptions: LanguageServicePerformanceOptions) : Async = asyncMaybe { let! sourceText = document.GetTextAsync() |> liftTaskAsync let filePath = document.FilePath @@ -60,7 +60,7 @@ type internal FSharpDocumentHighlightsService [] (checkerP let textLinePos = sourceText.Lines.GetLinePosition(position) let fcsTextLineNumber = Line.fromZ textLinePos.Line let! symbol = Tokenizer.getSymbolAtPosition(document.Id, sourceText, position, filePath, defines, SymbolLookupKind.Greedy, false, false) - let! _, _, checkFileResults = checker.ParseAndCheckDocument(document, options, languageServicePerformanceOptions, userOpName = userOpName) + let! _, checkFileResults = checker.CheckDocumentInProject(document, options) |> liftAsync let! symbolUse = checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, symbol.Ident.idRange.EndColumn, textLine.ToString(), symbol.FullIsland) let symbolUses = checkFileResults.GetUsesOfSymbolInFile(symbolUse.Symbol) return diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index 39f449db8bc..29d12baa94c 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -50,7 +50,6 @@ - diff --git a/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs b/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs index d08d365f0c6..a277b0b9681 100644 --- a/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs +++ b/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs @@ -156,7 +156,7 @@ type internal InlineRenameService let textLinePos = sourceText.Lines.GetLinePosition(position) let fcsTextLineNumber = Line.fromZ textLinePos.Line let! symbol = Tokenizer.getSymbolAtPosition(document.Id, sourceText, position, document.FilePath, defines, SymbolLookupKind.Greedy, false, false) - let! _, _, checkFileResults = checker.ParseAndCheckDocument(document, options, userOpName = userOpName) + let! _, checkFileResults = checker.CheckDocumentInProject(document, options) |> liftAsync let! symbolUse = checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, symbol.Ident.idRange.EndColumn, textLine.Text.ToString(), symbol.FullIsland) let! declLoc = symbolUse.GetDeclarationLocation(document) diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpAnalysisSaveFileCommandHandler.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpAnalysisSaveFileCommandHandler.fs deleted file mode 100644 index 05664377fd1..00000000000 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpAnalysisSaveFileCommandHandler.fs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -namespace Microsoft.VisualStudio.FSharp.Editor - -open System -open System.ComponentModel.Composition -open Microsoft.CodeAnalysis -open Microsoft.CodeAnalysis.Text -open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics -open Microsoft.VisualStudio.FSharp.Editor -open Microsoft.VisualStudio.FSharp.Editor.Logging -open Microsoft.VisualStudio.Text.Editor.Commanding.Commands -open Microsoft.VisualStudio.Commanding -open Microsoft.VisualStudio.Utilities - -// This causes re-analysis to happen when a F# document is saved. -// We do this because FCS relies on the file system and existing open documents -// need to be re-analyzed so the changes are propogated. -// We only re-analyze F# documents that are dependent on the document that was just saved. -// We ignore F# script documents here. -// REVIEW: This could be removed when Roslyn workspaces becomes the source of truth for FCS instead of the file system. -[] -[)>] -[] -[] -type internal FSharpAnalysisSaveFileCommandHandler - [] - (analyzerService: IFSharpDiagnosticAnalyzerService) = - - interface IChainedCommandHandler with - - member _.DisplayName = Constants.FSharpAnalysisSaveFileHandler - - member _.ExecuteCommand(args: SaveCommandArgs, nextCommandHandler: Action, _) = - let textContainer = args.SubjectBuffer.AsTextContainer() - match textContainer with - | null -> () - | _ -> - let mutable workspace = Unchecked.defaultof<_> - if Workspace.TryGetWorkspace(textContainer, &workspace) then - let solution = workspace.CurrentSolution - let documentId = workspace.GetDocumentIdInCurrentContext(textContainer) - match box documentId with - | null -> () - | _ -> - let document = solution.GetDocument(documentId) - async { - try - if document.Project.Language = LanguageNames.FSharp then - let openDocIds = workspace.GetOpenDocumentIds() - - let docIdsToReanalyze = - if document.IsFSharpScript then - openDocIds - |> Seq.filter (fun x -> - x <> document.Id && - ( - let doc = solution.GetDocument(x) - match doc with - | null -> false - | _ -> doc.IsFSharpScript - ) - ) - |> Array.ofSeq - else - let depProjIds = document.Project.GetDependentProjectIds().Add(document.Project.Id) - openDocIds - |> Seq.filter (fun x -> - depProjIds.Contains(x.ProjectId) && x <> document.Id && - ( - let doc = solution.GetDocument(x) - match box doc with - | null -> false - | _ -> doc.Project.Language = LanguageNames.FSharp - ) - ) - |> Array.ofSeq - - if docIdsToReanalyze.Length > 0 then - analyzerService.Reanalyze(workspace, documentIds=docIdsToReanalyze) - with - | ex -> logException ex - } - |> Async.Start // fire and forget - - nextCommandHandler.Invoke() - - member _.GetCommandState(_, nextCommandHandler: Func) = - nextCommandHandler.Invoke() \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerExtensions.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerExtensions.fs index 44e2b4b9684..94276aa9d5d 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerExtensions.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerExtensions.fs @@ -1,98 +1,18 @@ [] module internal Microsoft.VisualStudio.FSharp.Editor.FSharpCheckerExtensions -open System - open Microsoft.CodeAnalysis -open Microsoft.CodeAnalysis.Text - open FSharp.Compiler.CodeAnalysis type FSharpChecker with - member checker.ParseDocument(document: Document, parsingOptions: FSharpParsingOptions, userOpName: string) = - asyncMaybe { - let! ct = Async.CancellationToken |> liftAsync - - let! sourceText = document.GetTextAsync(ct) |> liftTaskAsync - - let! fileParseResults = checker.ParseFile(document.FilePath, sourceText.ToFSharpSourceText(), parsingOptions, userOpName=userOpName) |> liftAsync - return fileParseResults - } - - member checker.CheckDocument(document: Document, parseResults: FSharpParseFileResults, options: FSharpProjectOptions, userOpName: string) = + member checker.ParseDocument(document: Document, options: FSharpParsingOptions) = async { let! ct = Async.CancellationToken - let! sourceText = document.GetTextAsync(ct) |> Async.AwaitTask - let! textVersion = document.GetTextVersionAsync(ct) |> Async.AwaitTask - - let filePath = document.FilePath - let textVersionHash = textVersion.GetHashCode() - - return! checker.CheckFileInProject(parseResults, filePath, textVersionHash, sourceText.ToFSharpSourceText(), options,userOpName=userOpName) - } - - member checker.ParseAndCheckDocument(document: Document, options: FSharpProjectOptions, languageServicePerformanceOptions: LanguageServicePerformanceOptions, userOpName: string) = - async { - let! ct = Async.CancellationToken - - let! sourceText = document.GetTextAsync(ct) |> Async.AwaitTask - let! textVersion = document.GetTextVersionAsync(ct) |> Async.AwaitTask - - let filePath = document.FilePath - let textVersionHash = textVersion.GetHashCode() - - let parseAndCheckFile = - async { - let! (parseResults, checkFileAnswer) = checker.ParseAndCheckFileInProject(filePath, textVersionHash, sourceText.ToFSharpSourceText(), options, userOpName=userOpName) - return - match checkFileAnswer with - | FSharpCheckFileAnswer.Aborted -> - None - | FSharpCheckFileAnswer.Succeeded(checkFileResults) -> - Some (parseResults, checkFileResults) - } - - let tryGetFreshResultsWithTimeout() = - async { - let! worker = Async.StartChild(async { try return! parseAndCheckFile with | _ -> return None }, millisecondsTimeout=languageServicePerformanceOptions.TimeUntilStaleCompletion) - try - return! worker - with :? TimeoutException -> - return None // worker is cancelled at this point, we cannot return it and wait its completion anymore - } - - let bindParsedInput(results: (FSharpParseFileResults * FSharpCheckFileResults) option) = - match results with - | Some(parseResults, checkResults) -> - Some (parseResults, parseResults.ParseTree, checkResults) - | None -> None - - if languageServicePerformanceOptions.AllowStaleCompletionResults then - let! freshResults = tryGetFreshResultsWithTimeout() - - let! results = - match freshResults with - | Some x -> async.Return (Some x) - | None -> - async { - match checker.TryGetRecentCheckResultsForFile(filePath, options, userOpName=userOpName) with - | Some (parseResults, checkFileResults, _) -> - return Some (parseResults, checkFileResults) - | None -> - return! parseAndCheckFile - } - return bindParsedInput results - else - let! results = parseAndCheckFile - return bindParsedInput results + return! checker.ParseFile(document.FilePath, sourceText.ToFSharpSourceText(), options, cache = true) } - member checker.ParseAndCheckDocument(document: Document, options: FSharpProjectOptions, userOpName: string, ?allowStaleResults: bool) = + member checker.CheckDocumentInProject(document: Document, options: FSharpProjectOptions) = async { - let perfOpts = - match allowStaleResults with - | Some b -> { document.FSharpOptions.LanguageServicePerformance with AllowStaleCompletionResults = b } - | _ -> document.FSharpOptions.LanguageServicePerformance - return! checker.ParseAndCheckDocument(document, options, perfOpts, userOpName=userOpName) + return! checker.GetBackgroundCheckResultsForFileInProject(document.FilePath, options) } diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs index aa5d35b0815..3c8d48a7f3e 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs @@ -75,12 +75,9 @@ module private FSharpProjectOptionsHelpers = let p2 = newProject.Solution.GetProject(p2.ProjectId) doesProjectIdDiffer || ( - if p1.Language = LanguageNames.FSharp then - p1.Version <> p2.Version - else - let v1 = p1.GetDependentVersionAsync(ct).Result - let v2 = p2.GetDependentVersionAsync(ct).Result - v1 <> v2 + let v1 = p1.GetDependentVersionAsync(ct).Result + let v2 = p2.GetDependentVersionAsync(ct).Result + v1 <> v2 ) ) @@ -326,6 +323,21 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor checkerProvider.Checker.InvalidateConfiguration(projectOptions, userOpName = "tryComputeOptions") + let docsToUpdate = + project.DocumentIds + |> Seq.filter (fun x -> workspace.IsDocumentOpen(x)) + |> Array.ofSeq + + if docsToUpdate.Length > 0 then + let fsharpDocs = + docsToUpdate + |> Array.map (fun docId -> + let doc = project.GetDocument(docId) + doc.ToFSharpDocument() + ) + + do! checkerProvider.Checker.UpdateBackgroundDocuments(projectOptions, fsharpDocs) + let parsingOptions, _ = checkerProvider.Checker.GetParsingOptionsFromProjectOptions(projectOptions) cache.[projectId] <- (project, parsingOptions, projectOptions) @@ -337,6 +349,20 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor cache.TryRemove(projectId) |> ignore return! tryComputeOptions project ct else + let projectChanges = project.GetChanges(oldProject) + let changedDocs = projectChanges.GetChangedDocuments() |> Array.ofSeq + + if changedDocs.Length > 0 then + let fsharpDocs = + changedDocs + |> Array.map (fun docId -> + let doc = project.GetDocument(docId) + doc.ToFSharpDocument()) + + do! checkerProvider.Checker.UpdateBackgroundDocuments(projectOptions, fsharpDocs) + + cache.[projectId] <- (project, parsingOptions, projectOptions) + return Some(parsingOptions, projectOptions) } diff --git a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs index 3ed760c5bae..e49dcea1874 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs @@ -28,8 +28,7 @@ module internal SymbolHelpers = let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName) let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions let! symbol = Tokenizer.getSymbolAtPosition(document.Id, sourceText, position, document.FilePath, defines, SymbolLookupKind.Greedy, false, false) - let settings = document.FSharpOptions - let! _, _, checkFileResults = checker.ParseAndCheckDocument(document, projectOptions, settings.LanguageServicePerformance, userOpName = userOpName) + let! _, checkFileResults = checker.CheckDocumentInProject(document, projectOptions) |> liftAsync let! symbolUse = checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, symbol.Ident.idRange.EndColumn, textLine.ToString(), symbol.FullIsland) let! ct = Async.CancellationToken |> liftAsync let symbolUses = checkFileResults.GetUsesOfSymbolInFile(symbolUse.Symbol, cancellationToken=ct) @@ -123,7 +122,7 @@ module internal SymbolHelpers = let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName) let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions let! symbol = Tokenizer.getSymbolAtPosition(document.Id, sourceText, symbolSpan.Start, document.FilePath, defines, SymbolLookupKind.Greedy, false, false) - let! _, _, checkFileResults = checker.ParseAndCheckDocument(document, projectOptions, userOpName = userOpName) + let! _, checkFileResults = checker.CheckDocumentInProject(document, projectOptions) |> liftAsync let textLine = sourceText.Lines.GetLineFromPosition(symbolSpan.Start) let textLinePos = sourceText.Lines.GetLinePosition(symbolSpan.Start) let fcsTextLineNumber = Line.fromZ textLinePos.Line diff --git a/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs b/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs index 53a0e68bd25..2548dde0d35 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs @@ -53,7 +53,7 @@ type internal FSharpFindUsagesService let! sourceText = document.GetTextAsync(context.CancellationToken) |> Async.AwaitTask |> liftAsync let checker = checkerProvider.Checker let! parsingOptions, _, projectOptions = projectInfoManager.TryGetOptionsForDocumentOrProject(document, context.CancellationToken, userOpName) - let! _, _, checkFileResults = checker.ParseAndCheckDocument(document, projectOptions, userOpName = userOpName) + let! _, checkFileResults = checker.CheckDocumentInProject(document, projectOptions) |> liftAsync let textLine = sourceText.Lines.GetLineFromPosition(position).ToString() let lineNumber = sourceText.Lines.GetLinePosition(position).Line + 1 let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs index 240207ad780..d6faf95e8cf 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs @@ -186,7 +186,7 @@ type internal GoToDefinition(checkerProvider: FSharpCheckerProvider, projectInfo let fcsTextLineNumber = Line.fromZ textLinePos.Line let lineText = (sourceText.Lines.GetLineFromPosition position).ToString() - let! _, _, checkFileResults = checker.ParseAndCheckDocument (originDocument, projectOptions, userOpName=userOpName) + let! _, checkFileResults = checker.CheckDocumentInProject(originDocument, projectOptions) |> liftAsync let idRange = lexerSymbol.Ident.idRange let! fsSymbolUse = checkFileResults.GetSymbolUseAtLocation (fcsTextLineNumber, idRange.EndColumn, lineText, lexerSymbol.FullIsland) let symbol = fsSymbolUse.Symbol @@ -199,7 +199,7 @@ type internal GoToDefinition(checkerProvider: FSharpCheckerProvider, projectInfo let! implDoc = originDocument.Project.Solution.TryGetDocumentFromPath fsfilePath let! implSourceText = implDoc.GetTextAsync () let! _parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(implDoc, CancellationToken.None, userOpName) - let! _, _, checkFileResults = checker.ParseAndCheckDocument (implDoc, projectOptions, userOpName=userOpName) + let! _, checkFileResults = checker.CheckDocumentInProject(implDoc, projectOptions) |> liftAsync let symbolUses = checkFileResults.GetUsesOfSymbolInFile symbol let! implSymbol = symbolUses |> Array.tryHead let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (implSourceText, implSymbol.Range) @@ -218,7 +218,7 @@ type internal GoToDefinition(checkerProvider: FSharpCheckerProvider, projectInfo match targetSymbolUse.Symbol.DeclarationLocation with | Some decl when decl.FileName = filePath -> return decl | _ -> - let! _, _, checkFileResults = checker.ParseAndCheckDocument(document, options, userOpName) + let! _, checkFileResults = checker.CheckDocumentInProject(document, options) |> liftAsync let symbolUses = checkFileResults.GetUsesOfSymbolInFile targetSymbolUse.Symbol let! implSymbol = symbolUses |> Array.tryHead return implSymbol.Range @@ -237,7 +237,7 @@ type internal GoToDefinition(checkerProvider: FSharpCheckerProvider, projectInfo let preferSignature = isSignatureFile originDocument.FilePath - let! _, _, checkFileResults = checker.ParseAndCheckDocument (originDocument, projectOptions, userOpName=userOpName) + let! _, checkFileResults = checker.CheckDocumentInProject(originDocument, projectOptions) |> liftAsync let! lexerSymbol = Tokenizer.getSymbolAtPosition (originDocument.Id, sourceText, position,originDocument.FilePath, defines, SymbolLookupKind.Greedy, false, false) let idRange = lexerSymbol.Ident.idRange @@ -426,7 +426,7 @@ type internal GoToDefinition(checkerProvider: FSharpCheckerProvider, projectInfo let goToAsync = asyncMaybe { let! _, _, projectOptions = projectInfoManager.TryGetOptionsForDocumentOrProject (tmpShownDoc, cancellationToken, userOpName) - let! _, _, checkResults = checker.ParseAndCheckDocument(tmpShownDoc, projectOptions, userOpName) + let! _, checkResults = checker.CheckDocumentInProject(tmpShownDoc, projectOptions) |> liftAsync let! r = let rec areTypesEqual (ty1: FSharpType) (ty2: FSharpType) = let ty1 = ty1.StripAbbreviations() diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs index 821dab6624c..a9f79c843a2 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs @@ -181,34 +181,31 @@ type internal FSharpNavigateToSearchService let GetNavigableItems(document: Document, parsingOptions: FSharpParsingOptions, kinds: IImmutableSet) = async { + let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions) let! cancellationToken = Async.CancellationToken - let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions, userOpName) - match parseResults with - | None -> return [||] - | Some parseResults -> - let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask - let navItems parsedInput = - NavigateTo.GetNavigableItems parsedInput - |> Array.filter (fun i -> kinds.Contains(navigateToItemKindToRoslynKind i.Kind)) - - let items = parseResults.ParseTree |> navItems - let navigableItems = - [| - for item in items do - match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, item.Range) with - | None -> () - | Some sourceSpan -> - let glyph = navigateToItemKindToGlyph item.Kind - let kind = navigateToItemKindToRoslynKind item.Kind - let additionalInfo = containerToString item.Container document - let _name = - if isSignatureFile document.FilePath then - item.Name + " (signature)" - else - item.Name - yield NavigableItem(document, sourceSpan, glyph, item.Name, kind, additionalInfo) - |] - return navigableItems + let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask + let navItems parsedInput = + NavigateTo.GetNavigableItems parsedInput + |> Array.filter (fun i -> kinds.Contains(navigateToItemKindToRoslynKind i.Kind)) + + let items = parseResults.ParseTree |> navItems + let navigableItems = + [| + for item in items do + match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, item.Range) with + | None -> () + | Some sourceSpan -> + let glyph = navigateToItemKindToGlyph item.Kind + let kind = navigateToItemKindToRoslynKind item.Kind + let additionalInfo = containerToString item.Container document + let _name = + if isSignatureFile document.FilePath then + item.Name + " (signature)" + else + item.Name + yield NavigableItem(document, sourceSpan, glyph, item.Name, kind, additionalInfo) + |] + return navigableItems } let getCachedIndexedNavigableItems(document: Document, parsingOptions: FSharpParsingOptions, kinds: IImmutableSet) = diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigationBarItemService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigationBarItemService.fs index c83fb363a2b..6e946ac29a3 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigationBarItemService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigationBarItemService.fs @@ -28,9 +28,9 @@ type internal FSharpNavigationBarItemService member _.GetItemsAsync(document, cancellationToken) : Task> = asyncMaybe { let! parsingOptions, _options = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName) - let! sourceText = document.GetTextAsync(cancellationToken) - let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions, userOpName=userOpName) + let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions) |> liftAsync let navItems = Navigation.getNavigation parseResults.ParseTree + let! sourceText = document.GetTextAsync(cancellationToken) let rangeToTextSpan range = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range) return navItems.Declarations diff --git a/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs b/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs index 0d383f76136..7e3d882d216 100644 --- a/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs +++ b/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs @@ -60,7 +60,7 @@ module internal FSharpQuickInfo = let! extParsingOptions, extProjectOptions = projectInfoManager.TryGetOptionsByProject(extDocument.Project, cancellationToken) let extDefines = CompilerEnvironment.GetCompilationDefinesForEditing extParsingOptions let! extLexerSymbol = Tokenizer.getSymbolAtPosition(extDocId, extSourceText, extSpan.Start, declRange.FileName, extDefines, SymbolLookupKind.Greedy, true, true) - let! _, _, extCheckFileResults = checker.ParseAndCheckDocument(extDocument, extProjectOptions, allowStaleResults=true, userOpName = userOpName) + let! _, extCheckFileResults = checker.CheckDocumentInProject(extDocument, extProjectOptions) |> liftAsync let extQuickInfoText = extCheckFileResults.GetToolTip @@ -97,7 +97,7 @@ module internal FSharpQuickInfo = let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions let! lexerSymbol = Tokenizer.getSymbolAtPosition(document.Id, sourceText, position, document.FilePath, defines, SymbolLookupKind.Greedy, true, true) let idRange = lexerSymbol.Ident.idRange - let! _, _, checkFileResults = checker.ParseAndCheckDocument(document, projectOptions, allowStaleResults = true, userOpName = userOpName) + let! _, checkFileResults = checker.CheckDocumentInProject(document, projectOptions) |> liftAsync let textLinePos = sourceText.Lines.GetLinePosition position let fcsTextLineNumber = Line.fromZ textLinePos.Line let lineText = (sourceText.Lines.GetLineFromPosition position).ToString() @@ -177,9 +177,9 @@ type internal FSharpAsyncQuickInfoSource ) = // test helper - static member ProvideQuickInfo(checker:FSharpChecker, document: Document, position:int, parsingOptions:FSharpParsingOptions, options:FSharpProjectOptions, languageServicePerformanceOptions: LanguageServicePerformanceOptions) = + static member ProvideQuickInfo(checker:FSharpChecker, document: Document, position:int, parsingOptions:FSharpParsingOptions, options:FSharpProjectOptions, _languageServicePerformanceOptions: LanguageServicePerformanceOptions) = asyncMaybe { - let! _, _, checkFileResults = checker.ParseAndCheckDocument(document, options, languageServicePerformanceOptions, userOpName=FSharpQuickInfo.userOpName) + let! _, checkFileResults = checker.CheckDocumentInProject(document, options) |> liftAsync let! sourceText = document.GetTextAsync() let filePath = document.FilePath let textLine = sourceText.Lines.GetLineFromPosition position diff --git a/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs b/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs index f2c73274dd4..89c659af6aa 100644 --- a/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs +++ b/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs @@ -38,7 +38,7 @@ type internal FSharpAddExplicitTypeToParameterRefactoring let textLine = sourceText.Lines.GetLineFromPosition position let textLinePos = sourceText.Lines.GetLinePosition position let fcsTextLineNumber = Line.fromZ textLinePos.Line - let! parseFileResults, _, checkFileResults = checker.ParseAndCheckDocument (document, projectOptions, userOpName=userOpName) + let! parseFileResults, checkFileResults = checker.CheckDocumentInProject(document, projectOptions) |> liftAsync let! lexerSymbol = Tokenizer.getSymbolAtPosition (document.Id, sourceText, position, document.FilePath, defines, SymbolLookupKind.Greedy, false, false) let! symbolUse = checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, lexerSymbol.Ident.idRange.EndColumn, textLine.ToString(), lexerSymbol.FullIsland) diff --git a/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs b/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs index 2b51b4c3444..c6cdec2c792 100644 --- a/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs +++ b/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs @@ -32,7 +32,7 @@ type internal FSharpChangeDerefToValueRefactoring let document = context.Document let! parsingOptions, _ = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName) let! sourceText = context.Document.GetTextAsync(context.CancellationToken) - let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions, userOpName=userOpName) + let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions) |> liftAsync let selectionRange = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText) let! derefRange = parseResults.TryRangeOfRefCellDereferenceContainingPos selectionRange.Start diff --git a/vsintegration/src/FSharp.Editor/Refactor/ChangeTypeofWithNameToNameofExpression.fs b/vsintegration/src/FSharp.Editor/Refactor/ChangeTypeofWithNameToNameofExpression.fs index e1ef1dcf8af..3731c35065c 100644 --- a/vsintegration/src/FSharp.Editor/Refactor/ChangeTypeofWithNameToNameofExpression.fs +++ b/vsintegration/src/FSharp.Editor/Refactor/ChangeTypeofWithNameToNameofExpression.fs @@ -32,7 +32,7 @@ type internal FSharpChangeTypeofWithNameToNameofExpressionRefactoring let document = context.Document let! parsingOptions, _ = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName) let! sourceText = context.Document.GetTextAsync(context.CancellationToken) - let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions, userOpName=userOpName) + let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions) |> liftAsync let selectionRange = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText) let! namedTypeOfResults = parseResults.TryRangeOfTypeofWithNameAndTypeExpr(selectionRange.Start) diff --git a/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs b/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs index 2a4a7a58da6..eee1fe06598 100644 --- a/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs +++ b/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs @@ -151,7 +151,7 @@ type internal FSharpBlockStructureService [] (checkerProvi asyncMaybe { let! parsingOptions, _options = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName) let! sourceText = document.GetTextAsync(cancellationToken) - let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions, userOpName) + let! parseResults = checkerProvider.Checker.ParseDocument(document, parsingOptions) |> liftAsync return createBlockSpans document.FSharpOptions.Advanced.IsBlockStructureEnabled sourceText parseResults.ParseTree |> Seq.toImmutableArray } |> Async.map (Option.defaultValue ImmutableArray<_>.Empty) diff --git a/vsintegration/src/FSharp.LanguageService/FSharpSource.fs b/vsintegration/src/FSharp.LanguageService/FSharpSource.fs index b311983e6b4..7a60fcaedda 100644 --- a/vsintegration/src/FSharp.LanguageService/FSharpSource.fs +++ b/vsintegration/src/FSharp.LanguageService/FSharpSource.fs @@ -362,7 +362,7 @@ type internal FSharpSource_DEPRECATED(service:LanguageService_DEPRECATED, textLi let co, _ = { ProjectFileName = fileName + ".dummy.fsproj" ProjectId = None - SourceFiles = [| fileName |] + SourceFiles = [|fileName|] OtherOptions = flags ReferencedProjects = [| |] IsIncompleteTypeCheckEnvironment = true diff --git a/vsintegration/tests/UnitTests/GoToDefinitionServiceTests.fs b/vsintegration/tests/UnitTests/GoToDefinitionServiceTests.fs index f77a3e6e9cf..49d48964c48 100644 --- a/vsintegration/tests/UnitTests/GoToDefinitionServiceTests.fs +++ b/vsintegration/tests/UnitTests/GoToDefinitionServiceTests.fs @@ -53,7 +53,7 @@ module GoToDefinitionServiceTests = let textLinePos = sourceText.Lines.GetLinePosition position let fcsTextLineNumber = Line.fromZ textLinePos.Line let! lexerSymbol = Tokenizer.getSymbolAtPosition(document.Id, sourceText, position, document.FilePath, defines, SymbolLookupKind.Greedy, false, false) - let! _, _, checkFileResults = checker.ParseAndCheckDocument (document, options, LanguageServicePerformanceOptions.Default, userOpName=userOpName) |> Async.RunSynchronously + let _, checkFileResults = checker.CheckDocumentInProject(document, options) |> Async.RunSynchronously let declarations = checkFileResults.GetDeclarationLocation (fcsTextLineNumber, lexerSymbol.Ident.idRange.EndColumn, textLine.ToString(), lexerSymbol.FullIsland, false) diff --git a/vsintegration/tests/UnitTests/SemanticColorizationServiceTests.fs b/vsintegration/tests/UnitTests/SemanticColorizationServiceTests.fs index 3846e2b6308..1785aa5ffa0 100644 --- a/vsintegration/tests/UnitTests/SemanticColorizationServiceTests.fs +++ b/vsintegration/tests/UnitTests/SemanticColorizationServiceTests.fs @@ -29,13 +29,13 @@ type SemanticClassificationServiceTests() = } let checker = FSharpChecker.Create() - let perfOptions = { LanguageServicePerformanceOptions.Default with AllowStaleCompletionResults = false } + let _perfOptions = { LanguageServicePerformanceOptions.Default with AllowStaleCompletionResults = false } let getRanges (source: string) : SemanticClassificationItem list = let projectOptions = { projectOptions with ProjectId = Some(Guid.NewGuid().ToString()) } asyncMaybe { let document, _ = RoslynTestHelpers.CreateDocument(filePath, source) - let! _, _, checkFileResults = checker.ParseAndCheckDocument(document, projectOptions, perfOptions, "") + let! _, checkFileResults = checker.CheckDocumentInProject(document, projectOptions) |> liftAsync return checkFileResults.GetSemanticClassification(None) } |> Async.RunSynchronously diff --git a/vsintegration/tests/UnitTests/SignatureHelpProviderTests.fs b/vsintegration/tests/UnitTests/SignatureHelpProviderTests.fs index df7ff193736..961f20d711f 100644 --- a/vsintegration/tests/UnitTests/SignatureHelpProviderTests.fs +++ b/vsintegration/tests/UnitTests/SignatureHelpProviderTests.fs @@ -53,14 +53,12 @@ let GetSignatureHelp (project:FSharpProject) (fileName:string) (caretPosition:in let textLines = sourceText.Lines let caretLinePos = textLines.GetLinePosition(caretPosition) let caretLineColumn = caretLinePos.Character - let perfOptions = LanguageServicePerformanceOptions.Default + let _perfOptions = LanguageServicePerformanceOptions.Default let document = RoslynTestHelpers.CreateDocument(fileName, sourceText) - let parseResults, _, checkFileResults = - let x = - checker.ParseAndCheckDocument(document, project.Options, perfOptions, "TestSignatureHelpProvider") - |> Async.RunSynchronously - x.Value + let parseResults, checkFileResults = + checker.CheckDocumentInProject(document, project.Options) + |> Async.RunSynchronously let paramInfoLocations = parseResults.FindParameterLocations(Position.fromZ caretLinePos.Line caretLineColumn).Value let triggered = @@ -103,17 +101,12 @@ let assertSignatureHelpForMethodCalls (fileContents: string) (marker: string) (e let textLines = sourceText.Lines let caretLinePos = textLines.GetLinePosition(caretPosition) let caretLineColumn = caretLinePos.Character - let perfOptions = LanguageServicePerformanceOptions.Default + let _perfOptions = LanguageServicePerformanceOptions.Default let document = RoslynTestHelpers.CreateDocument(filePath, sourceText) - let parseResults, _, checkFileResults = - let x = - checker.ParseAndCheckDocument(document, projectOptions, perfOptions, "TestSignatureHelpProvider") - |> Async.RunSynchronously - - if x.IsNone then - Assert.Fail("Could not parse and check document.") - x.Value + let parseResults, checkFileResults = + checker.CheckDocumentInProject(document, projectOptions) + |> Async.RunSynchronously let actual = let paramInfoLocations = parseResults.FindParameterLocations(Position.fromZ caretLinePos.Line caretLineColumn) @@ -142,16 +135,11 @@ let assertSignatureHelpForMethodCalls (fileContents: string) (marker: string) (e let assertSignatureHelpForFunctionApplication (fileContents: string) (marker: string) expectedArgumentCount expectedArgumentIndex = let caretPosition = fileContents.LastIndexOf(marker) + marker.Length let document, sourceText = RoslynTestHelpers.CreateDocument(filePath, fileContents) - let perfOptions = LanguageServicePerformanceOptions.Default + let _perfOptions = LanguageServicePerformanceOptions.Default - let parseResults, _, checkFileResults = - let x = - checker.ParseAndCheckDocument(document, projectOptions, perfOptions, "TestSignatureHelpProvider") - |> Async.RunSynchronously - - if x.IsNone then - Assert.Fail("Could not parse and check document.") - x.Value + let parseResults, checkFileResults = + checker.CheckDocumentInProject(document, projectOptions) + |> Async.RunSynchronously let adjustedColumnInSource = let rec loop ch pos = @@ -429,16 +417,11 @@ M.f let caretPosition = fileContents.IndexOf(marker) + marker.Length let document, sourceText = RoslynTestHelpers.CreateDocument(filePath, fileContents) - let perfOptions = LanguageServicePerformanceOptions.Default + let _perfOptions = LanguageServicePerformanceOptions.Default - let parseResults, _, checkFileResults = - let x = - checker.ParseAndCheckDocument(document, projectOptions, perfOptions, "TestSignatureHelpProvider") - |> Async.RunSynchronously - - if x.IsNone then - Assert.Fail("Could not parse and check document.") - x.Value + let parseResults, checkFileResults = + checker.CheckDocumentInProject(document, projectOptions) + |> Async.RunSynchronously let adjustedColumnInSource = let rec loop ch pos =