Skip to content

Commit e4e709f

Browse files
authored
Parse sourcetext endpoint (#16899)
* Update ParseFile in TransparentCompiler * Add unit tests * Decrease cache size
1 parent a5763f1 commit e4e709f

File tree

6 files changed

+125
-4
lines changed

6 files changed

+125
-4
lines changed

src/Compiler/Facilities/Hashing.fs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,11 @@ module internal Md5Hasher =
7878

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

81+
let addInt (i: int) (s: byte array) =
82+
i |> BitConverter.GetBytes |> addBytes <| s
83+
84+
let addIntegers (items: int seq) (s: byte array) = addSeq items addInt s
85+
86+
let addBooleans (items: bool seq) (s: byte array) = addSeq items addBool s
87+
8188
let toString (bytes: byte array) = bytes |> System.BitConverter.ToString

src/Compiler/Facilities/Hashing.fsi

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,8 @@ module internal Md5Hasher =
4343

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

46+
val addIntegers: items: int seq -> s: byte array -> byte array
47+
48+
val addBooleans: items: bool seq -> s: byte array -> byte array
49+
4650
val toString: bytes: byte array -> string

src/Compiler/Service/TransparentCompiler.fs

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,9 @@ type internal CompilerCaches(sizeFactor: int) =
267267

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

270+
member val ParseFileWithoutProject =
271+
AsyncMemoize<string, string, FSharpParseFileResults>(keepStrongly = 5 * sf, keepWeakly = 2 * sf, name = "ParseFileWithoutProject")
272+
270273
member val ParseAndCheckFileInProject = AsyncMemoize(sf, 2 * sf, name = "ParseAndCheckFileInProject")
271274

272275
member val ParseAndCheckAllFilesInProject = AsyncMemoizeDisabled(sf, 2 * sf, name = "ParseAndCheckFullProject")
@@ -1999,6 +2002,70 @@ type internal TransparentCompiler
19992002
return parseResult
20002003
}
20012004

2005+
member _.ParseFileWithoutProject
2006+
(
2007+
fileName: string,
2008+
sourceText: ISourceText,
2009+
options: FSharpParsingOptions,
2010+
cache: bool,
2011+
flatErrors: bool,
2012+
userOpName: string
2013+
) : Async<FSharpParseFileResults> =
2014+
let parseFileAsync =
2015+
async {
2016+
let! ct = Async.CancellationToken
2017+
2018+
let diagnostics, parsedInput, anyErrors =
2019+
ParseAndCheckFile.parseFile (sourceText, fileName, options, userOpName, false, flatErrors, false, ct)
2020+
2021+
return FSharpParseFileResults(diagnostics, parsedInput, anyErrors, Array.empty)
2022+
}
2023+
2024+
if not cache then
2025+
parseFileAsync
2026+
else
2027+
let cacheKey =
2028+
let sourceText = SourceTextNew.ofISourceText sourceText
2029+
2030+
{ new ICacheKey<_, _> with
2031+
member _.GetLabel() = shortPath fileName
2032+
2033+
member _.GetKey() = fileName
2034+
2035+
member _.GetVersion() =
2036+
Md5Hasher.empty
2037+
|> Md5Hasher.addStrings
2038+
[
2039+
yield fileName
2040+
yield! options.ConditionalDefines
2041+
yield! options.SourceFiles
2042+
yield options.LangVersionText
2043+
]
2044+
|> Md5Hasher.addBytes (sourceText.GetChecksum().ToArray())
2045+
|> Md5Hasher.addIntegers
2046+
[
2047+
yield options.DiagnosticOptions.WarnLevel
2048+
yield! options.DiagnosticOptions.WarnOff
2049+
yield! options.DiagnosticOptions.WarnOn
2050+
yield! options.DiagnosticOptions.WarnAsError
2051+
yield! options.DiagnosticOptions.WarnAsWarn
2052+
]
2053+
|> Md5Hasher.addBooleans
2054+
[
2055+
yield options.ApplyLineDirectives
2056+
yield options.DiagnosticOptions.GlobalWarnAsError
2057+
yield options.IsInteractive
2058+
yield! (Option.toList options.IndentationAwareSyntax)
2059+
yield! (Option.toList options.StrictIndentation)
2060+
yield options.CompilingFSharpCore
2061+
yield options.IsExe
2062+
]
2063+
|> Md5Hasher.toString
2064+
}
2065+
2066+
caches.ParseFileWithoutProject.Get(cacheKey, NodeCode.AwaitAsync parseFileAsync)
2067+
|> Async.AwaitNodeCode
2068+
20022069
member _.ParseAndCheckFileInProject(fileName: string, projectSnapshot: ProjectSnapshot, userOpName: string) =
20032070
ignore userOpName
20042071
ComputeParseAndCheckFileInProject fileName projectSnapshot
@@ -2450,8 +2517,8 @@ type internal TransparentCompiler
24502517
cache: bool,
24512518
flatErrors: bool,
24522519
userOpName: string
2453-
) =
2454-
backgroundCompiler.ParseFile(fileName, sourceText, options, cache, flatErrors, userOpName)
2520+
) : Async<FSharpParseFileResults> =
2521+
this.ParseFileWithoutProject(fileName, sourceText, options, cache, flatErrors, userOpName)
24552522

24562523
member this.TryGetRecentCheckResultsForFile
24572524
(

src/Compiler/Service/TransparentCompiler.fsi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ type internal CompilerCaches =
125125
member ParseFile:
126126
AsyncMemoize<((string * string) * string), (string * string * bool), ProjectSnapshot.FSharpParsedFile>
127127

128+
member ParseFileWithoutProject: AsyncMemoize<string, string, FSharpParseFileResults>
129+
128130
member ProjectExtras: AsyncMemoizeDisabled<obj, obj, obj>
129131

130132
member SemanticClassification: AsyncMemoize<(string * (string * string)), string, SemanticClassificationView option>

src/Compiler/Service/service.fsi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ type public FSharpChecker =
9797
/// <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>
9898
/// <param name="sourceText">The source to be parsed.</param>
9999
/// <param name="options">Parsing options for the project or script.</param>
100-
/// <param name="cache">Store the parse in a size-limited cache assocaited with the FSharpChecker. Default: true</param>
100+
/// <param name="cache">Store the parse in a size-limited cache associated with the FSharpChecker. Default: true</param>
101101
/// <param name="userOpName">An optional string used for tracing compiler operations associated with this request.</param>
102102
member ParseFile:
103103
fileName: string * sourceText: ISourceText * options: FSharpParsingOptions * ?cache: bool * ?userOpName: string ->

tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1125,4 +1125,45 @@ let ``The script load closure should always be evaluated`` useTransparentCompile
11251125
| FSharpCheckFileAnswer.Succeeded checkFileResults -> Assert.Equal(0, checkFileResults.Diagnostics.Length)
11261126
finally
11271127
FileSystemAutoOpens.FileSystem <- currentFileSystem
1128-
}
1128+
}
1129+
1130+
[<Fact>]
1131+
let ``Parsing without cache and without project snapshot`` () =
1132+
async {
1133+
let checker = FSharpChecker.Create(useTransparentCompiler = true)
1134+
let fileName = "Temp.fs"
1135+
let sourceText = "let a = 0" |> SourceText.ofString
1136+
let parsingOptions = { FSharpParsingOptions.Default with SourceFiles = [| fileName |]; IsExe = true }
1137+
let! parseResult = checker.ParseFile(fileName, sourceText, parsingOptions, cache = false)
1138+
Assert.False(parseResult.ParseHadErrors)
1139+
Assert.True(Array.isEmpty parseResult.Diagnostics)
1140+
Assert.Equal(0, checker.Caches.ParseFile.Count)
1141+
Assert.Equal(0, checker.Caches.ParseFileWithoutProject.Count)
1142+
}
1143+
1144+
// In this scenario, the user is typing something in file B.fs.
1145+
// The idea is that the IDE will introduce an additional (fake) identifier in order to have a potential complete syntax tree.
1146+
// The user never wrote this code so we need to ensure nothing is added to checker.Caches.ParseFile
1147+
[<Fact>]
1148+
let ``Parsing with cache and without project snapshot`` () =
1149+
async {
1150+
let checker = FSharpChecker.Create(useTransparentCompiler = true)
1151+
let fileName = "B.fs"
1152+
let parsingOptions = { FSharpParsingOptions.Default with SourceFiles = [| "A.fs"; fileName; "C.fs" |] }
1153+
let sourceText =
1154+
SourceText.ofString """
1155+
module B
1156+
1157+
let b : int = ExtraIdentUserNeverWroteRulezzz
1158+
"""
1159+
let! parseResult = checker.ParseFile(fileName, sourceText, parsingOptions, cache = true)
1160+
Assert.False(parseResult.ParseHadErrors)
1161+
Assert.True(Array.isEmpty parseResult.Diagnostics)
1162+
1163+
let! parseAgainResult = checker.ParseFile(fileName, sourceText, parsingOptions, cache = true)
1164+
Assert.False(parseAgainResult.ParseHadErrors)
1165+
Assert.True(Array.isEmpty parseAgainResult.Diagnostics)
1166+
1167+
Assert.Equal(0, checker.Caches.ParseFile.Count)
1168+
Assert.Equal(1, checker.Caches.ParseFileWithoutProject.Count)
1169+
}

0 commit comments

Comments
 (0)