Skip to content

Commit 32e3b0a

Browse files
committed
Add GetProjectSnapshotFromScript to FSharpChecker (dotnet#16735)
* Initial attempt for GetProjectSnapshotFromScript * Add GetProjectSnapshotFromScript to service. * Address feedback from code review. * Assert both return the same results. * Remove old comment * Construct cache key in ComputeScriptClosure * Include stamp * Don't take projectSnapshot as input for ComputeScriptClosure * Add other flags * Add checksum directly. * Remove duplicate checksum * Use ISourceTextNew for Source * Mark API as experimental
1 parent fdf077b commit 32e3b0a

File tree

9 files changed

+300
-11
lines changed

9 files changed

+300
-11
lines changed

src/Compiler/Service/BackgroundCompiler.fs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,20 @@ type internal IBackgroundCompiler =
123123
userOpName: string ->
124124
Async<FSharpProjectOptions * FSharp.Compiler.Diagnostics.FSharpDiagnostic list>
125125

126+
abstract GetProjectSnapshotFromScript:
127+
fileName: string *
128+
sourceText: ISourceTextNew *
129+
previewEnabled: bool option *
130+
loadedTimeStamp: System.DateTime option *
131+
otherFlags: string array option *
132+
useFsiAuxLib: bool option *
133+
useSdkRefs: bool option *
134+
sdkDirOverride: string option *
135+
assumeDotNetFramework: bool option *
136+
optionsStamp: int64 option *
137+
userOpName: string ->
138+
Async<FSharpProjectSnapshot * FSharpDiagnostic list>
139+
126140
abstract member GetSemanticClassificationForFile:
127141
fileName: string * options: FSharpProjectOptions * userOpName: string ->
128142
NodeCode<FSharp.Compiler.EditorServices.SemanticClassificationView option>
@@ -1597,6 +1611,40 @@ type internal BackgroundCompiler
15971611
userOpName
15981612
)
15991613

1614+
member _.GetProjectSnapshotFromScript
1615+
(
1616+
fileName: string,
1617+
sourceText: ISourceTextNew,
1618+
previewEnabled: bool option,
1619+
loadedTimeStamp: DateTime option,
1620+
otherFlags: string array option,
1621+
useFsiAuxLib: bool option,
1622+
useSdkRefs: bool option,
1623+
sdkDirOverride: string option,
1624+
assumeDotNetFramework: bool option,
1625+
optionsStamp: int64 option,
1626+
userOpName: string
1627+
) : Async<FSharpProjectSnapshot * FSharpDiagnostic list> =
1628+
async {
1629+
let! options, diagnostics =
1630+
self.GetProjectOptionsFromScript(
1631+
fileName,
1632+
sourceText,
1633+
previewEnabled,
1634+
loadedTimeStamp,
1635+
otherFlags,
1636+
useFsiAuxLib,
1637+
useSdkRefs,
1638+
sdkDirOverride,
1639+
assumeDotNetFramework,
1640+
optionsStamp,
1641+
userOpName
1642+
)
1643+
1644+
let! snapshot = FSharpProjectSnapshot.FromOptions(options, DocumentSource.FileSystem)
1645+
return snapshot, diagnostics
1646+
}
1647+
16001648
member _.GetSemanticClassificationForFile
16011649
(
16021650
fileName: string,

src/Compiler/Service/BackgroundCompiler.fsi

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,20 @@ type internal IBackgroundCompiler =
102102
userOpName: string ->
103103
Async<FSharpProjectOptions * FSharpDiagnostic list>
104104

105+
abstract GetProjectSnapshotFromScript:
106+
fileName: string *
107+
sourceText: ISourceTextNew *
108+
previewEnabled: bool option *
109+
loadedTimeStamp: System.DateTime option *
110+
otherFlags: string array option *
111+
useFsiAuxLib: bool option *
112+
useSdkRefs: bool option *
113+
sdkDirOverride: string option *
114+
assumeDotNetFramework: bool option *
115+
optionsStamp: int64 option *
116+
userOpName: string ->
117+
Async<FSharpProjectSnapshot * FSharpDiagnostic list>
118+
105119
abstract GetSemanticClassificationForFile:
106120
fileName: string * snapshot: FSharpProjectSnapshot * userOpName: string ->
107121
NodeCode<FSharp.Compiler.EditorServices.SemanticClassificationView option>

src/Compiler/Service/FSharpProjectSnapshot.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ type FSharpFileSnapshot(FileName: string, Version: string, GetSource: unit -> Ta
104104

105105
/// A source file snapshot with loaded source text.
106106
type internal FSharpFileSnapshotWithSource
107-
(FileName: string, SourceHash: ImmutableArray<byte>, Source: ISourceText, IsLastCompiland: bool, IsExe: bool) =
107+
(FileName: string, SourceHash: ImmutableArray<byte>, Source: ISourceTextNew, IsLastCompiland: bool, IsExe: bool) =
108108

109109
let version = lazy (SourceHash.ToBuilder().ToArray())
110110
let stringVersion = lazy (version.Value |> BitConverter.ToString)

src/Compiler/Service/TransparentCompiler.fs

Lines changed: 158 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace FSharp.Compiler.CodeAnalysis.TransparentCompiler
22

33
open System
4+
open System.Linq
45
open System.Collections.Generic
56
open System.Runtime.CompilerServices
67
open System.Diagnostics
@@ -372,26 +373,51 @@ type internal TransparentCompiler
372373

373374
let ComputeScriptClosure
374375
(fileName: string)
375-
(source: ISourceText)
376+
(source: ISourceTextNew)
376377
(defaultFSharpBinariesDir: string)
377378
(useSimpleResolution: bool)
378379
(useFsiAuxLib: bool option)
379380
(useSdkRefs: bool option)
380381
(sdkDirOverride: string option)
381382
(assumeDotNetFramework: bool option)
382-
(projectSnapshot: ProjectSnapshot)
383+
(projectIdentifier: ProjectIdentifier)
384+
(otherOptions: string list)
385+
(stamp: int64 option)
383386
=
387+
let useFsiAuxLib = defaultArg useFsiAuxLib true
388+
let useSdkRefs = defaultArg useSdkRefs true
389+
let assumeDotNetFramework = defaultArg assumeDotNetFramework false
390+
391+
let key =
392+
{ new ICacheKey<string * ProjectIdentifier, string> with
393+
member _.GetKey() = fileName, projectIdentifier
394+
member _.GetLabel() = $"ScriptClosure for %s{fileName}"
395+
396+
member _.GetVersion() =
397+
Md5Hasher.empty
398+
|> Md5Hasher.addStrings
399+
[|
400+
yield! otherOptions
401+
match stamp with
402+
| None -> ()
403+
| Some stamp -> string stamp
404+
|]
405+
|> Md5Hasher.addBytes (source.GetChecksum().ToArray())
406+
|> Md5Hasher.addBool useFsiAuxLib
407+
|> Md5Hasher.addBool useFsiAuxLib
408+
|> Md5Hasher.addBool useSdkRefs
409+
|> Md5Hasher.addBool assumeDotNetFramework
410+
|> Md5Hasher.toString
411+
}
412+
384413
caches.ScriptClosure.Get(
385-
projectSnapshot.FileKey fileName,
414+
key,
386415
node {
387-
let useFsiAuxLib = defaultArg useFsiAuxLib true
388-
let useSdkRefs = defaultArg useSdkRefs true
389416
let reduceMemoryUsage = ReduceMemoryFlag.Yes
390-
let assumeDotNetFramework = defaultArg assumeDotNetFramework false
391417

392418
let applyCompilerOptions tcConfig =
393419
let fsiCompilerOptions = GetCoreFsiCompilerOptions tcConfig
394-
ParseCompilerOptions(ignore, fsiCompilerOptions, projectSnapshot.OtherOptions)
420+
ParseCompilerOptions(ignore, fsiCompilerOptions, otherOptions)
395421

396422
let closure =
397423
LoadClosure.ComputeClosureOfScriptText(
@@ -660,7 +686,9 @@ type internal TransparentCompiler
660686
None
661687
None
662688
None
663-
projectSnapshot
689+
projectSnapshot.Identifier
690+
projectSnapshot.OtherOptions
691+
projectSnapshot.Stamp
664692

665693
return (Some closure)
666694
}
@@ -1518,7 +1546,9 @@ type internal TransparentCompiler
15181546
(Some tcConfig.useSdkRefs)
15191547
tcConfig.sdkDirOverride
15201548
(Some tcConfig.assumeDotNetFramework)
1521-
projectSnapshot
1549+
projectSnapshot.Identifier
1550+
projectSnapshot.OtherOptions
1551+
projectSnapshot.Stamp
15221552

15231553
let typedResults =
15241554
FSharpCheckFileResults.Make(
@@ -2097,6 +2127,125 @@ type internal TransparentCompiler
20972127
userOpName
20982128
)
20992129

2130+
member this.GetProjectSnapshotFromScript
2131+
(
2132+
fileName: string,
2133+
sourceText: ISourceTextNew,
2134+
previewEnabled: bool option,
2135+
loadedTimeStamp: DateTime option,
2136+
otherFlags: string array option,
2137+
useFsiAuxLib: bool option,
2138+
useSdkRefs: bool option,
2139+
sdkDirOverride: string option,
2140+
assumeDotNetFramework: bool option,
2141+
optionsStamp: int64 option,
2142+
userOpName: string
2143+
) : Async<FSharpProjectSnapshot * FSharpDiagnostic list> =
2144+
use _ =
2145+
Activity.start
2146+
"BackgroundCompiler.GetProjectOptionsFromScript"
2147+
[| Activity.Tags.fileName, fileName; Activity.Tags.userOpName, userOpName |]
2148+
2149+
async {
2150+
// Use the same default as the background compiler.
2151+
let useFsiAuxLib = defaultArg useFsiAuxLib true
2152+
let useSdkRefs = defaultArg useSdkRefs true
2153+
let previewEnabled = defaultArg previewEnabled false
2154+
2155+
// Do we assume .NET Framework references for scripts?
2156+
let assumeDotNetFramework = defaultArg assumeDotNetFramework true
2157+
2158+
let extraFlags =
2159+
if previewEnabled then
2160+
[| "--langversion:preview" |]
2161+
else
2162+
[||]
2163+
2164+
let otherFlags = defaultArg otherFlags extraFlags
2165+
use diagnostics = new DiagnosticsScope(otherFlags |> Array.contains "--flaterrors")
2166+
2167+
let useSimpleResolution =
2168+
otherFlags |> Array.exists (fun x -> x = "--simpleresolution")
2169+
2170+
let loadedTimeStamp = defaultArg loadedTimeStamp DateTime.MaxValue // Not 'now', we don't want to force reloading
2171+
let projectFileName = fileName + ".fsproj"
2172+
2173+
let currentSourceFile =
2174+
FSharpFileSnapshot.Create(fileName, sourceText.GetHashCode().ToString(), (fun () -> Task.FromResult sourceText))
2175+
2176+
let! loadClosure =
2177+
ComputeScriptClosure
2178+
fileName
2179+
sourceText
2180+
FSharpCheckerResultsSettings.defaultFSharpBinariesDir
2181+
useSimpleResolution
2182+
(Some useFsiAuxLib)
2183+
(Some useSdkRefs)
2184+
sdkDirOverride
2185+
(Some assumeDotNetFramework)
2186+
(projectFileName, fileName)
2187+
(List.ofArray otherFlags)
2188+
optionsStamp
2189+
|> Async.AwaitNodeCode
2190+
2191+
let otherFlags =
2192+
[
2193+
yield "--noframework"
2194+
yield "--warn:3"
2195+
yield! otherFlags
2196+
for code, _ in loadClosure.NoWarns do
2197+
yield "--nowarn:" + code
2198+
]
2199+
2200+
let sourceFiles =
2201+
loadClosure.SourceFiles
2202+
|> List.map (fun (sf, _) ->
2203+
if sf = fileName then
2204+
currentSourceFile
2205+
else
2206+
FSharpFileSnapshot.CreateFromFileSystem sf)
2207+
2208+
let references =
2209+
loadClosure.References
2210+
|> List.map (fun (r, _) ->
2211+
let lastModified = FileSystem.GetLastWriteTimeShim r
2212+
2213+
{
2214+
Path = r
2215+
LastModified = lastModified
2216+
})
2217+
2218+
let snapshot =
2219+
FSharpProjectSnapshot.Create(
2220+
fileName + ".fsproj",
2221+
None,
2222+
sourceFiles,
2223+
references,
2224+
otherFlags,
2225+
List.empty,
2226+
false,
2227+
true,
2228+
loadedTimeStamp,
2229+
Some(FSharpUnresolvedReferencesSet(loadClosure.UnresolvedReferences)),
2230+
loadClosure.OriginalLoadReferences,
2231+
optionsStamp
2232+
)
2233+
2234+
let diags =
2235+
loadClosure.LoadClosureRootFileDiagnostics
2236+
|> List.map (fun (exn, isError) ->
2237+
FSharpDiagnostic.CreateFromException(
2238+
exn,
2239+
isError,
2240+
range.Zero,
2241+
false,
2242+
otherFlags |> List.contains "--flaterrors",
2243+
None
2244+
))
2245+
2246+
return snapshot, (diags @ diagnostics.Diagnostics)
2247+
}
2248+
21002249
member this.GetSemanticClassificationForFile(fileName: string, snapshot: FSharpProjectSnapshot, userOpName: string) =
21012250
node {
21022251
ignore userOpName

src/Compiler/Service/service.fs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,37 @@ type FSharpChecker
565565
userOpName
566566
)
567567

568+
/// For a given script file, get the ProjectSnapshot implied by the #load closure
569+
member _.GetProjectSnapshotFromScript
570+
(
571+
fileName,
572+
source,
573+
?previewEnabled,
574+
?loadedTimeStamp,
575+
?otherFlags,
576+
?useFsiAuxLib,
577+
?useSdkRefs,
578+
?assumeDotNetFramework,
579+
?sdkDirOverride,
580+
?optionsStamp: int64,
581+
?userOpName: string
582+
) =
583+
let userOpName = defaultArg userOpName "Unknown"
584+
585+
backgroundCompiler.GetProjectSnapshotFromScript(
586+
fileName,
587+
source,
588+
previewEnabled,
589+
loadedTimeStamp,
590+
otherFlags,
591+
useFsiAuxLib,
592+
useSdkRefs,
593+
sdkDirOverride,
594+
assumeDotNetFramework,
595+
optionsStamp,
596+
userOpName
597+
)
598+
568599
member _.GetProjectOptionsFromCommandLineArgs(projectFileName, argv, ?loadedTimeStamp, ?isInteractive, ?isEditing) =
569600
let isEditing = defaultArg isEditing false
570601
let isInteractive = defaultArg isInteractive false

src/Compiler/Service/service.fsi

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,34 @@ type public FSharpChecker =
258258

259259
member GetCachedScriptSnapshot: path: string -> FSharpProjectSnapshot option
260260

261+
/// <param name="fileName">Used to differentiate between scripts, to consider each script a separate project. Also used in formatted error messages.</param>
262+
/// <param name="source">The source for the file.</param>
263+
/// <param name="previewEnabled">Is the preview compiler enabled.</param>
264+
/// <param name="loadedTimeStamp">Indicates when the script was loaded into the editing environment,
265+
/// so that an 'unload' and 'reload' action will cause the script to be considered as a new project,
266+
/// so that references are re-resolved.</param>
267+
/// <param name="otherFlags">Other flags for compilation.</param>
268+
/// <param name="useFsiAuxLib">Add a default reference to the FSharp.Compiler.Interactive.Settings library.</param>
269+
/// <param name="useSdkRefs">Use the implicit references from the .NET SDK.</param>
270+
/// <param name="assumeDotNetFramework">Set up compilation and analysis for .NET Framework scripts.</param>
271+
/// <param name="sdkDirOverride">Override the .NET SDK used for default references.</param>
272+
/// <param name="optionsStamp">An optional unique stamp for the options.</param>
273+
/// <param name="userOpName">An optional string used for tracing compiler operations associated with this request.</param>
274+
[<Experimental("This FCS API is experimental and subject to change.")>]
275+
member GetProjectSnapshotFromScript:
276+
fileName: string *
277+
source: ISourceTextNew *
278+
?previewEnabled: bool *
279+
?loadedTimeStamp: DateTime *
280+
?otherFlags: string[] *
281+
?useFsiAuxLib: bool *
282+
?useSdkRefs: bool *
283+
?assumeDotNetFramework: bool *
284+
?sdkDirOverride: string *
285+
?optionsStamp: int64 *
286+
?userOpName: string ->
287+
Async<FSharpProjectSnapshot * FSharpDiagnostic list>
288+
261289
/// <summary>Get the FSharpProjectOptions implied by a set of command line arguments.</summary>
262290
///
263291
/// <param name="projectFileName">Used to differentiate between projects and for the base directory of the project.</param>

0 commit comments

Comments
 (0)