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,