Skip to content

Added FSharpSource type #12305

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,12 @@
<Compile Include="..\service\SemanticClassificationKey.fs">
<Link>Service/SemanticClassificationKey.fs</Link>
</Compile>
<Compile Include="..\service\FSharpSource.fsi">
<Link>Service/FSharpSource.fsi</Link>
</Compile>
<Compile Include="..\service\FSharpSource.fs">
<Link>Service/FSharpSource.fs</Link>
</Compile>
<Compile Include="..\service\IncrementalBuild.fsi">
<Link>Service/IncrementalBuild.fsi</Link>
</Compile>
Expand Down
32 changes: 32 additions & 0 deletions src/fsharp/ParseAndCheckInputs.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
7 changes: 7 additions & 0 deletions src/fsharp/ParseAndCheckInputs.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
92 changes: 92 additions & 0 deletions src/fsharp/service/FSharpSource.fs
Original file line number Diff line number Diff line change
@@ -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

[<RequireQualifiedAccess>]
type TextContainer =
| OnDisk
| Stream of Stream
| SourceText of ISourceText

interface IDisposable with

member this.Dispose() =
match this with
| Stream stream -> stream.Dispose()
| _ -> ()

[<AbstractClass>]
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
38 changes: 38 additions & 0 deletions src/fsharp/service/FSharpSource.fsi
Original file line number Diff line number Diff line change
@@ -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

[<RequireQualifiedAccess>]
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.
[<AbstractClass>]
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
38 changes: 26 additions & 12 deletions src/fsharp/service/IncrementalBuild.fs
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,9 @@ module IncrementalBuildSyntaxTree =

/// Information needed to lazily parse a file to get a ParsedInput. Internally uses a weak cache.
[<Sealed>]
type SyntaxTree (tcConfig: TcConfig, fileParsed: Event<string>, lexResourceManager, sourceRange: range, filename: string, isLastCompiland) =
type SyntaxTree (tcConfig: TcConfig, fileParsed: Event<string>, lexResourceManager, sourceRange: range, source: FSharpSource, isLastCompiland) =

let filename = source.FilePath
let mutable weakCache: WeakReference<_> option = None

let parse(sigNameOpt: QualifiedNameOfFile option) =
Expand All @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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) =
Expand Down Expand Up @@ -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)

[<NoComparison;NoEquality>]
type IncrementalBuilderInitialState =
Expand All @@ -924,7 +932,7 @@ type IncrementalBuilderInitialState =
outfile: string
assemblyName: string
lexResourceManager: Lexhelp.LexResourceManager
fileNames: block<range * string * (bool * bool)>
fileNames: block<range * FSharpSource * (bool * bool)>
enablePartialTypeChecking: bool
beforeFileChecked: Event<string>
fileChecked: Event<string>
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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,
Expand Down