diff --git a/src/fsharp/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj b/src/fsharp/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj index d972271f2b0..2cee1ba5a9c 100644 --- a/src/fsharp/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj +++ b/src/fsharp/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj @@ -843,6 +843,12 @@ Service/SemanticClassificationKey.fs + + Service/FSharpSource.fsi + + + Service/FSharpSource.fs + Service/IncrementalBuild.fsi diff --git a/src/fsharp/ParseAndCheckInputs.fs b/src/fsharp/ParseAndCheckInputs.fs index d6b690a8ad7..4003fb5fc79 100644 --- a/src/fsharp/ParseAndCheckInputs.fs +++ b/src/fsharp/ParseAndCheckInputs.fs @@ -420,6 +420,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, 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, 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) @@ -431,6 +447,22 @@ let parseInputFileAux (tcConfig: TcConfig, lexResourceManager, conditionalCompil // Parse the file drawing tokens from the lexbuf ParseOneInputLexbuf(tcConfig, lexResourceManager, conditionalCompilationDefines, lexbuf, filename, isLastCompiland, errorLogger) +/// 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 an input from disk let ParseOneInputFile (tcConfig: TcConfig, lexResourceManager, conditionalCompilationDefines, filename, isLastCompiland, errorLogger, retryLocked) = try diff --git a/src/fsharp/ParseAndCheckInputs.fsi b/src/fsharp/ParseAndCheckInputs.fsi index d1074191899..d0906edbf27 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 @@ -46,6 +47,12 @@ val ApplyMetaCommandsFromInputToTcConfig: TcConfig * ParsedInput * string * Depe /// Process the #nowarn in an input and integrate them into the TcConfig val ApplyNoWarnsToTcConfig: TcConfig * ParsedInput * string -> TcConfig +/// 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 one input file val ParseOneInputFile: TcConfig * Lexhelp.LexResourceManager * conditionalCompilationDefines: string list * string * isLastCompiland: (bool * bool) * ErrorLogger * retryLocked: bool -> ParsedInput diff --git a/src/fsharp/service/FSharpSource.fs b/src/fsharp/service/FSharpSource.fs new file mode 100644 index 00000000000..1f1d12373a9 --- /dev/null +++ b/src/fsharp/service/FSharpSource.fs @@ -0,0 +1,92 @@ +// 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 Internal.Utilities.Library +open FSharp.Compiler.IO +open FSharp.Compiler.Text + +[] +type TextContainer = + | OnDisk + | Stream of Stream + | SourceText of ISourceText + + interface IDisposable with + + member this.Dispose() = + match this with + | Stream stream -> stream.Dispose() + | _ -> () + +[] +type FSharpSource internal () = + + abstract FilePath : string + + abstract TimeStamp : DateTime + + abstract GetTextContainer: unit -> TextContainer + +type private FSharpSourceMemoryMappedFile(filePath: string, timeStamp: DateTime, openStream: unit -> Stream) = + inherit FSharpSource() + + override _.FilePath = filePath + + override _.TimeStamp = timeStamp + + override _.GetTextContainer() = + openStream () |> TextContainer.Stream + +type private FSharpSourceByteArray(filePath: string, timeStamp: DateTime, bytes: byte[]) = + inherit FSharpSource() + + override _.FilePath = filePath + + override _.TimeStamp = timeStamp + + override _.GetTextContainer() = + TextContainer.Stream(new MemoryStream(bytes, 0, bytes.Length, false) :> Stream) + +type private FSharpSourceFromFile(filePath: string) = + inherit FSharpSource() + + override _.FilePath = filePath + + override _.TimeStamp = FileSystem.GetLastWriteTimeShim(filePath) + + override _.GetTextContainer() = + TextContainer.OnDisk + +type private FSharpSourceCustom(filePath: string, getTimeStamp, getSourceText) = + inherit FSharpSource() + + override _.FilePath = filePath + + override _.TimeStamp = getTimeStamp() + + override _.GetTextContainer() = + TextContainer.SourceText(getSourceText()) + +type FSharpSource with + + static member Create(filePath, getTimeStamp, getSourceText) = + FSharpSourceCustom(filePath, getTimeStamp, getSourceText) :> FSharpSource + + static member CreateFromFile(filePath: string) = + FSharpSourceFromFile(filePath) :> FSharpSource + + static member CreateCopyFromFile(filePath: string) = + 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() + FSharpSourceByteArray(filePath, timeStamp, bytes) :> FSharpSource + else + let openStream = fun () -> + FileSystem.OpenFileForReadShim(filePath, useMemoryMappedFile = true, shouldShadowCopy = true) + FSharpSourceMemoryMappedFile(filePath, timeStamp, openStream) :> FSharpSource \ No newline at end of file diff --git a/src/fsharp/service/FSharpSource.fsi b/src/fsharp/service/FSharpSource.fsi new file mode 100644 index 00000000000..c8738342046 --- /dev/null +++ b/src/fsharp/service/FSharpSource.fsi @@ -0,0 +1,38 @@ +// 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 TextContainer = + | OnDisk + | Stream of Stream + | SourceText of ISourceText + + interface IDisposable + +/// The storage container for a F# source item that could either be on-disk or in-memory. +/// TODO: Make this public. +[] +type internal FSharpSource = + + /// The file path of the source. + abstract FilePath : string + + /// The timestamp of the source. + abstract TimeStamp : DateTime + + /// Gets the internal text container. Text may be on-disk, in a stream, or a source text. + abstract internal GetTextContainer : unit -> TextContainer + + /// Creates a FSharpSource from disk. Only used internally. + static member internal CreateFromFile : filePath: string -> FSharpSource + + /// Creates a FSharpSource from the specified file path by shadow-copying the file. + static member CreateCopyFromFile : filePath: string -> FSharpSource + + /// Creates a FSharpSource. + static member Create : filePath: string * getTimeStamp: (unit -> DateTime) * getSourceText: (unit -> ISourceText) -> FSharpSource \ No newline at end of file diff --git a/src/fsharp/service/IncrementalBuild.fs b/src/fsharp/service/IncrementalBuild.fs index ebb94f18398..8a5c531534d 100644 --- a/src/fsharp/service/IncrementalBuild.fs +++ b/src/fsharp/service/IncrementalBuild.fs @@ -96,8 +96,9 @@ 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, source: FSharpSource, isLastCompiland) = + let filename = source.FilePath let mutable weakCache: WeakReference<_> option = None let parse(sigNameOpt: QualifiedNameOfFile option) = @@ -123,7 +124,14 @@ module IncrementalBuildSyntaxTree = ) ) else - ParseOneInputFile(tcConfig, lexResourceManager, [], filename, isLastCompiland, errorLogger, (*retryLocked*)true) + use text = source.GetTextContainer() + match text with + | TextContainer.Stream(stream) -> + ParseOneInputStream(tcConfig, lexResourceManager, [], filename, isLastCompiland, errorLogger, (*retryLocked*)false, stream) + | TextContainer.SourceText(sourceText) -> + ParseOneInputSourceText(tcConfig, lexResourceManager, [], filename, isLastCompiland, errorLogger, sourceText) + | TextContainer.OnDisk -> + ParseOneInputFile(tcConfig, lexResourceManager, [], filename, isLastCompiland, errorLogger, (*retryLocked*)true) fileParsed.Trigger filename @@ -147,7 +155,7 @@ module IncrementalBuildSyntaxTree = | _ -> parse sigNameOpt member _.Invalidate() = - SyntaxTree(tcConfig, fileParsed, lexResourceManager, sourceRange, filename, isLastCompiland) + SyntaxTree(tcConfig, fileParsed, lexResourceManager, sourceRange, source, isLastCompiland) member _.FileName = filename @@ -703,8 +711,8 @@ type RawFSharpAssemblyDataBackedByLanguageService (tcConfig, tcGlobals, generate module IncrementalBuilderHelpers = /// Get the timestamp of the given file name. - let StampFileNameTask (cache: TimeStampCache) (_m: range, filename: string, _isLastCompiland) = - cache.GetFileTimeStamp filename + let StampFileNameTask (cache: TimeStampCache) (_m: range, source: FSharpSource, _isLastCompiland) = + cache.GetFileTimeStamp source.FilePath /// Timestamps of referenced assemblies are taken from the file's timestamp. let StampReferencedAssemblyTask (cache: TimeStampCache) (_ref, timeStamper) = @@ -911,8 +919,8 @@ module IncrementalBuilderHelpers = return ilAssemRef, tcAssemblyDataOpt, tcAssemblyExprOpt, finalBoundModelWithErrors } - let GetSyntaxTree tcConfig fileParsed lexResourceManager (sourceRange: range, filename: string, isLastCompiland) = - SyntaxTree(tcConfig, fileParsed, lexResourceManager, sourceRange, filename, isLastCompiland) + let GetSyntaxTree tcConfig fileParsed lexResourceManager (sourceRange: range, source, isLastCompiland) = + SyntaxTree(tcConfig, fileParsed, lexResourceManager, sourceRange, source, isLastCompiland) [] type IncrementalBuilderInitialState = @@ -924,7 +932,7 @@ type IncrementalBuilderInitialState = outfile: string assemblyName: string lexResourceManager: Lexhelp.LexResourceManager - fileNames: block + fileNames: block enablePartialTypeChecking: bool beforeFileChecked: Event fileChecked: Event @@ -1332,10 +1340,10 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc member _.TryGetSlotOfFileName(filename: string) = // Get the slot of the given file and force it to build. - let CompareFileNames (_, f2, _) = + let CompareFileNames (_, f2: FSharpSource, _) = let result = - String.Compare(filename, f2, StringComparison.CurrentCultureIgnoreCase)=0 - || String.Compare(FileSystem.GetFullPathShim filename, FileSystem.GetFullPathShim f2, StringComparison.CurrentCultureIgnoreCase)=0 + String.Compare(filename, f2.FilePath, StringComparison.CurrentCultureIgnoreCase)=0 + || String.Compare(FileSystem.GetFullPathShim filename, FileSystem.GetFullPathShim f2.FilePath, StringComparison.CurrentCultureIgnoreCase)=0 result match fileNames |> Block.tryFindIndex CompareFileNames with | Some slot -> Some slot @@ -1358,7 +1366,7 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc let syntaxTree = GetSyntaxTree initialState.tcConfig initialState.fileParsed initialState.lexResourceManager fileInfo syntaxTree.Parse None - member _.SourceFiles = fileNames |> Seq.map (fun (_, f, _) -> f) |> List.ofSeq + member _.SourceFiles = fileNames |> Seq.map (fun (_, f, _) -> f.FilePath) |> List.ofSeq /// CreateIncrementalBuilder (for background type checking). Note that fsc.fs also /// creates an incremental builder used by the command line compiler. @@ -1584,6 +1592,12 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc importsInvalidatedByTypeProvider ) + let sourceFiles = + sourceFiles + |> List.map (fun (m, filename, isLastCompiland) -> + (m, FSharpSource.CreateFromFile(filename), isLastCompiland) + ) + let initialState = IncrementalBuilderInitialState.Create( initialBoundModel,