Skip to content
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
7 changes: 7 additions & 0 deletions src/Compiler/Facilities/Hashing.fs
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,11 @@ module internal Md5Hasher =

let addDateTimes (dts: System.DateTime seq) (s: byte array) = s |> addSeq dts addDateTime

let addInt (i: int) (s: byte array) =
i |> BitConverter.GetBytes |> addBytes <| s

let addIntegers (items: int seq) (s: byte array) = addSeq items addInt s

let addBooleans (items: bool seq) (s: byte array) = addSeq items addBool s

let toString (bytes: byte array) = bytes |> System.BitConverter.ToString
4 changes: 4 additions & 0 deletions src/Compiler/Facilities/Hashing.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,8 @@ module internal Md5Hasher =

val addDateTimes: dts: System.DateTime seq -> s: byte array -> byte array

val addIntegers: items: int seq -> s: byte array -> byte array

val addBooleans: items: bool seq -> s: byte array -> byte array

val toString: bytes: byte array -> string
71 changes: 69 additions & 2 deletions src/Compiler/Service/TransparentCompiler.fs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,9 @@ type internal CompilerCaches(sizeFactor: int) =

member val ParseFile = AsyncMemoize(keepStrongly = 50 * sf, keepWeakly = 20 * sf, name = "ParseFile")

member val ParseFileWithoutProject =
AsyncMemoize<string, string, FSharpParseFileResults>(keepStrongly = 5 * sf, keepWeakly = 2 * sf, name = "ParseFileWithoutProject")

member val ParseAndCheckFileInProject = AsyncMemoize(sf, 2 * sf, name = "ParseAndCheckFileInProject")

member val ParseAndCheckAllFilesInProject = AsyncMemoizeDisabled(sf, 2 * sf, name = "ParseAndCheckFullProject")
Expand Down Expand Up @@ -1999,6 +2002,70 @@ type internal TransparentCompiler
return parseResult
}

member _.ParseFileWithoutProject
(
fileName: string,
sourceText: ISourceText,
options: FSharpParsingOptions,
cache: bool,
flatErrors: bool,
userOpName: string
) : Async<FSharpParseFileResults> =
let parseFileAsync =
async {
let! ct = Async.CancellationToken

let diagnostics, parsedInput, anyErrors =
ParseAndCheckFile.parseFile (sourceText, fileName, options, userOpName, false, flatErrors, false, ct)

return FSharpParseFileResults(diagnostics, parsedInput, anyErrors, Array.empty)
}

if not cache then
parseFileAsync
else
let cacheKey =
let sourceText = SourceTextNew.ofISourceText sourceText

{ new ICacheKey<_, _> with
member _.GetLabel() = shortPath fileName

member _.GetKey() = fileName

member _.GetVersion() =
Md5Hasher.empty
|> Md5Hasher.addStrings
[
yield fileName
yield! options.ConditionalDefines
yield! options.SourceFiles
yield options.LangVersionText
]
|> Md5Hasher.addBytes (sourceText.GetChecksum().ToArray())
|> Md5Hasher.addIntegers
[
yield options.DiagnosticOptions.WarnLevel
yield! options.DiagnosticOptions.WarnOff
yield! options.DiagnosticOptions.WarnOn
yield! options.DiagnosticOptions.WarnAsError
yield! options.DiagnosticOptions.WarnAsWarn
]
|> Md5Hasher.addBooleans
[
yield options.ApplyLineDirectives
yield options.DiagnosticOptions.GlobalWarnAsError
yield options.IsInteractive
yield! (Option.toList options.IndentationAwareSyntax)
yield! (Option.toList options.StrictIndentation)
yield options.CompilingFSharpCore
yield options.IsExe
]
|> Md5Hasher.toString
}

caches.ParseFileWithoutProject.Get(cacheKey, NodeCode.AwaitAsync parseFileAsync)
|> Async.AwaitNodeCode

member _.ParseAndCheckFileInProject(fileName: string, projectSnapshot: ProjectSnapshot, userOpName: string) =
ignore userOpName
ComputeParseAndCheckFileInProject fileName projectSnapshot
Expand Down Expand Up @@ -2450,8 +2517,8 @@ type internal TransparentCompiler
cache: bool,
flatErrors: bool,
userOpName: string
) =
backgroundCompiler.ParseFile(fileName, sourceText, options, cache, flatErrors, userOpName)
) : Async<FSharpParseFileResults> =
this.ParseFileWithoutProject(fileName, sourceText, options, cache, flatErrors, userOpName)

member this.TryGetRecentCheckResultsForFile
(
Expand Down
2 changes: 2 additions & 0 deletions src/Compiler/Service/TransparentCompiler.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ type internal CompilerCaches =
member ParseFile:
AsyncMemoize<((string * string) * string), (string * string * bool), ProjectSnapshot.FSharpParsedFile>

member ParseFileWithoutProject: AsyncMemoize<string, string, FSharpParseFileResults>

member ProjectExtras: AsyncMemoizeDisabled<obj, obj, obj>

member SemanticClassification: AsyncMemoize<(string * (string * string)), string, SemanticClassificationView option>
Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/Service/service.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ type public FSharpChecker =
/// <param name="fileName">The path for the file. The file name is used as a module name for implicit top level modules (e.g. in scripts).</param>
/// <param name="sourceText">The source to be parsed.</param>
/// <param name="options">Parsing options for the project or script.</param>
/// <param name="cache">Store the parse in a size-limited cache assocaited with the FSharpChecker. Default: true</param>
/// <param name="cache">Store the parse in a size-limited cache associated with the FSharpChecker. Default: true</param>
/// <param name="userOpName">An optional string used for tracing compiler operations associated with this request.</param>
member ParseFile:
fileName: string * sourceText: ISourceText * options: FSharpParsingOptions * ?cache: bool * ?userOpName: string ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1125,4 +1125,45 @@ let ``The script load closure should always be evaluated`` useTransparentCompile
| FSharpCheckFileAnswer.Succeeded checkFileResults -> Assert.Equal(0, checkFileResults.Diagnostics.Length)
finally
FileSystemAutoOpens.FileSystem <- currentFileSystem
}
}

[<Fact>]
let ``Parsing without cache and without project snapshot`` () =
async {
let checker = FSharpChecker.Create(useTransparentCompiler = true)
let fileName = "Temp.fs"
let sourceText = "let a = 0" |> SourceText.ofString
let parsingOptions = { FSharpParsingOptions.Default with SourceFiles = [| fileName |]; IsExe = true }
let! parseResult = checker.ParseFile(fileName, sourceText, parsingOptions, cache = false)
Assert.False(parseResult.ParseHadErrors)
Assert.True(Array.isEmpty parseResult.Diagnostics)
Assert.Equal(0, checker.Caches.ParseFile.Count)
Assert.Equal(0, checker.Caches.ParseFileWithoutProject.Count)
}

// In this scenario, the user is typing something in file B.fs.
// The idea is that the IDE will introduce an additional (fake) identifier in order to have a potential complete syntax tree.
// The user never wrote this code so we need to ensure nothing is added to checker.Caches.ParseFile
[<Fact>]
let ``Parsing with cache and without project snapshot`` () =
async {
let checker = FSharpChecker.Create(useTransparentCompiler = true)
let fileName = "B.fs"
let parsingOptions = { FSharpParsingOptions.Default with SourceFiles = [| "A.fs"; fileName; "C.fs" |] }
let sourceText =
SourceText.ofString """
module B

let b : int = ExtraIdentUserNeverWroteRulezzz
"""
let! parseResult = checker.ParseFile(fileName, sourceText, parsingOptions, cache = true)
Assert.False(parseResult.ParseHadErrors)
Assert.True(Array.isEmpty parseResult.Diagnostics)

let! parseAgainResult = checker.ParseFile(fileName, sourceText, parsingOptions, cache = true)
Assert.False(parseAgainResult.ParseHadErrors)
Assert.True(Array.isEmpty parseAgainResult.Diagnostics)

Assert.Equal(0, checker.Caches.ParseFile.Count)
Assert.Equal(1, checker.Caches.ParseFileWithoutProject.Count)
}