From 403448737f1ae7c072ccc3917c74baed45a2f3a2 Mon Sep 17 00:00:00 2001 From: nojaf Date: Tue, 19 Mar 2024 15:02:23 +0100 Subject: [PATCH 1/3] Update ParseFile in TransparentCompiler --- src/Compiler/Facilities/Hashing.fs | 7 ++ src/Compiler/Facilities/Hashing.fsi | 4 ++ src/Compiler/Service/TransparentCompiler.fs | 71 +++++++++++++++++++- src/Compiler/Service/TransparentCompiler.fsi | 2 + src/Compiler/Service/service.fsi | 2 +- 5 files changed, 83 insertions(+), 3 deletions(-) diff --git a/src/Compiler/Facilities/Hashing.fs b/src/Compiler/Facilities/Hashing.fs index 2dfbb38b7ee..dd58495c59e 100644 --- a/src/Compiler/Facilities/Hashing.fs +++ b/src/Compiler/Facilities/Hashing.fs @@ -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 diff --git a/src/Compiler/Facilities/Hashing.fsi b/src/Compiler/Facilities/Hashing.fsi index 121afb29eb2..c154fd66078 100644 --- a/src/Compiler/Facilities/Hashing.fsi +++ b/src/Compiler/Facilities/Hashing.fsi @@ -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 diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs index e206971eaad..a6285a04909 100644 --- a/src/Compiler/Service/TransparentCompiler.fs +++ b/src/Compiler/Service/TransparentCompiler.fs @@ -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(keepStrongly = 10 * sf, keepWeakly = 5 * sf, name = "ParseFileWithoutProject") + member val ParseAndCheckFileInProject = AsyncMemoize(sf, 2 * sf, name = "ParseAndCheckFileInProject") member val ParseAndCheckAllFilesInProject = AsyncMemoizeDisabled(sf, 2 * sf, name = "ParseAndCheckFullProject") @@ -1999,6 +2002,70 @@ type internal TransparentCompiler return parseResult } + member _.ParseFileWithoutProject + ( + fileName: string, + sourceText: ISourceText, + options: FSharpParsingOptions, + cache: bool, + flatErrors: bool, + userOpName: string + ) : Async = + 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 @@ -2450,8 +2517,8 @@ type internal TransparentCompiler cache: bool, flatErrors: bool, userOpName: string - ) = - backgroundCompiler.ParseFile(fileName, sourceText, options, cache, flatErrors, userOpName) + ) : Async = + this.ParseFileWithoutProject(fileName, sourceText, options, cache, flatErrors, userOpName) member this.TryGetRecentCheckResultsForFile ( diff --git a/src/Compiler/Service/TransparentCompiler.fsi b/src/Compiler/Service/TransparentCompiler.fsi index 8e581872d84..906ba4d698a 100644 --- a/src/Compiler/Service/TransparentCompiler.fsi +++ b/src/Compiler/Service/TransparentCompiler.fsi @@ -125,6 +125,8 @@ type internal CompilerCaches = member ParseFile: AsyncMemoize<((string * string) * string), (string * string * bool), ProjectSnapshot.FSharpParsedFile> + member ParseFileWithoutProject: AsyncMemoize + member ProjectExtras: AsyncMemoizeDisabled member SemanticClassification: AsyncMemoize<(string * (string * string)), string, SemanticClassificationView option> diff --git a/src/Compiler/Service/service.fsi b/src/Compiler/Service/service.fsi index a15c208c26c..5e78154d77e 100644 --- a/src/Compiler/Service/service.fsi +++ b/src/Compiler/Service/service.fsi @@ -97,7 +97,7 @@ type public FSharpChecker = /// The path for the file. The file name is used as a module name for implicit top level modules (e.g. in scripts). /// The source to be parsed. /// Parsing options for the project or script. - /// Store the parse in a size-limited cache assocaited with the FSharpChecker. Default: true + /// Store the parse in a size-limited cache associated with the FSharpChecker. Default: true /// An optional string used for tracing compiler operations associated with this request. member ParseFile: fileName: string * sourceText: ISourceText * options: FSharpParsingOptions * ?cache: bool * ?userOpName: string -> From e58ab169e0da68172c0553604dc8f688c093ad72 Mon Sep 17 00:00:00 2001 From: nojaf Date: Tue, 19 Mar 2024 15:19:02 +0100 Subject: [PATCH 2/3] Add unit tests --- .../FSharpChecker/TransparentCompiler.fs | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs index c7e7777fd98..cbc0c8a34e8 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs +++ b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs @@ -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 - } \ No newline at end of file + } + +[] +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 +[] +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) + } From 83afeabcd04d5de4c80c0b84e249460d8d11b9fa Mon Sep 17 00:00:00 2001 From: nojaf Date: Tue, 19 Mar 2024 15:57:54 +0100 Subject: [PATCH 3/3] Decrease cache size --- src/Compiler/Service/TransparentCompiler.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs index a6285a04909..679c0d46d3f 100644 --- a/src/Compiler/Service/TransparentCompiler.fs +++ b/src/Compiler/Service/TransparentCompiler.fs @@ -268,7 +268,7 @@ type internal CompilerCaches(sizeFactor: int) = member val ParseFile = AsyncMemoize(keepStrongly = 50 * sf, keepWeakly = 20 * sf, name = "ParseFile") member val ParseFileWithoutProject = - AsyncMemoize(keepStrongly = 10 * sf, keepWeakly = 5 * sf, name = "ParseFileWithoutProject") + AsyncMemoize(keepStrongly = 5 * sf, keepWeakly = 2 * sf, name = "ParseFileWithoutProject") member val ParseAndCheckFileInProject = AsyncMemoize(sf, 2 * sf, name = "ParseAndCheckFileInProject")