Skip to content
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

Add GetProjectSnapshotFromScript to FSharpChecker #16735

Merged
merged 15 commits into from
Feb 23, 2024
Merged
48 changes: 48 additions & 0 deletions src/Compiler/Service/BackgroundCompiler.fs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,20 @@ type internal IBackgroundCompiler =
userOpName: string ->
Async<FSharpProjectOptions * FSharp.Compiler.Diagnostics.FSharpDiagnostic list>

abstract GetProjectSnapshotFromScript:
fileName: string *
sourceText: ISourceTextNew *
previewEnabled: bool option *
loadedTimeStamp: System.DateTime option *
otherFlags: string array option *
useFsiAuxLib: bool option *
useSdkRefs: bool option *
sdkDirOverride: string option *
assumeDotNetFramework: bool option *
optionsStamp: int64 option *
userOpName: string ->
Async<FSharpProjectSnapshot * FSharpDiagnostic list>

abstract member GetSemanticClassificationForFile:
fileName: string * options: FSharpProjectOptions * userOpName: string ->
NodeCode<FSharp.Compiler.EditorServices.SemanticClassificationView option>
Expand Down Expand Up @@ -1595,6 +1609,40 @@ type internal BackgroundCompiler
userOpName
)

member _.GetProjectSnapshotFromScript
(
fileName: string,
sourceText: ISourceTextNew,
previewEnabled: bool option,
loadedTimeStamp: DateTime option,
otherFlags: string array option,
useFsiAuxLib: bool option,
useSdkRefs: bool option,
sdkDirOverride: string option,
assumeDotNetFramework: bool option,
optionsStamp: int64 option,
userOpName: string
) : Async<FSharpProjectSnapshot * FSharpDiagnostic list> =
async {
let! options, diagnostics =
self.GetProjectOptionsFromScript(
fileName,
sourceText,
previewEnabled,
loadedTimeStamp,
otherFlags,
useFsiAuxLib,
useSdkRefs,
sdkDirOverride,
assumeDotNetFramework,
optionsStamp,
userOpName
)

let! snapshot = FSharpProjectSnapshot.FromOptions(options, DocumentSource.FileSystem)
return snapshot, diagnostics
}

member _.GetSemanticClassificationForFile
(
fileName: string,
Expand Down
14 changes: 14 additions & 0 deletions src/Compiler/Service/BackgroundCompiler.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,20 @@ type internal IBackgroundCompiler =
userOpName: string ->
Async<FSharpProjectOptions * FSharpDiagnostic list>

abstract GetProjectSnapshotFromScript:
fileName: string *
sourceText: ISourceTextNew *
previewEnabled: bool option *
loadedTimeStamp: System.DateTime option *
otherFlags: string array option *
useFsiAuxLib: bool option *
useSdkRefs: bool option *
sdkDirOverride: string option *
assumeDotNetFramework: bool option *
optionsStamp: int64 option *
userOpName: string ->
Async<FSharpProjectSnapshot * FSharpDiagnostic list>

abstract GetSemanticClassificationForFile:
fileName: string * snapshot: FSharpProjectSnapshot * userOpName: string ->
NodeCode<FSharp.Compiler.EditorServices.SemanticClassificationView option>
Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/Service/FSharpProjectSnapshot.fs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ type FSharpFileSnapshot(FileName: string, Version: string, GetSource: unit -> Ta

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

let version = lazy (SourceHash.ToBuilder().ToArray())
let stringVersion = lazy (version.Value |> BitConverter.ToString)
Expand Down
167 changes: 158 additions & 9 deletions src/Compiler/Service/TransparentCompiler.fs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace FSharp.Compiler.CodeAnalysis.TransparentCompiler

open System
open System.Linq
open System.Collections.Generic
open System.Runtime.CompilerServices
open System.Diagnostics
Expand Down Expand Up @@ -377,26 +378,51 @@ type internal TransparentCompiler

let ComputeScriptClosure
(fileName: string)
(source: ISourceText)
(source: ISourceTextNew)
(defaultFSharpBinariesDir: string)
(useSimpleResolution: bool)
(useFsiAuxLib: bool option)
(useSdkRefs: bool option)
(sdkDirOverride: string option)
(assumeDotNetFramework: bool option)
(projectSnapshot: ProjectSnapshot)
(projectIdentifier: ProjectIdentifier)
(otherOptions: string list)
(stamp: int64 option)
=
let useFsiAuxLib = defaultArg useFsiAuxLib true
let useSdkRefs = defaultArg useSdkRefs true
let assumeDotNetFramework = defaultArg assumeDotNetFramework false

let key =
{ new ICacheKey<string * ProjectIdentifier, string> with
member _.GetKey() = fileName, projectIdentifier
member _.GetLabel() = $"ScriptClosure for %s{fileName}"

member _.GetVersion() =
Md5Hasher.empty
|> Md5Hasher.addStrings
[|
yield! otherOptions
match stamp with
| None -> ()
| Some stamp -> string stamp
|]
|> Md5Hasher.addBytes (source.GetChecksum().ToArray())
|> Md5Hasher.addBool useFsiAuxLib
|> Md5Hasher.addBool useFsiAuxLib
|> Md5Hasher.addBool useSdkRefs
|> Md5Hasher.addBool assumeDotNetFramework
|> Md5Hasher.toString
}

caches.ScriptClosure.Get(
projectSnapshot.FileKey fileName,
key,
node {
let useFsiAuxLib = defaultArg useFsiAuxLib true
let useSdkRefs = defaultArg useSdkRefs true
let reduceMemoryUsage = ReduceMemoryFlag.Yes
let assumeDotNetFramework = defaultArg assumeDotNetFramework false

let applyCompilerOptions tcConfig =
let fsiCompilerOptions = GetCoreFsiCompilerOptions tcConfig
ParseCompilerOptions(ignore, fsiCompilerOptions, projectSnapshot.OtherOptions)
ParseCompilerOptions(ignore, fsiCompilerOptions, otherOptions)

let closure =
LoadClosure.ComputeClosureOfScriptText(
Expand Down Expand Up @@ -665,7 +691,9 @@ type internal TransparentCompiler
None
None
None
projectSnapshot
projectSnapshot.Identifier
projectSnapshot.OtherOptions
projectSnapshot.Stamp

return (Some closure)
}
Expand Down Expand Up @@ -1521,7 +1549,9 @@ type internal TransparentCompiler
(Some tcConfig.useSdkRefs)
tcConfig.sdkDirOverride
(Some tcConfig.assumeDotNetFramework)
projectSnapshot
projectSnapshot.Identifier
projectSnapshot.OtherOptions
projectSnapshot.Stamp

let typedResults =
FSharpCheckFileResults.Make(
Expand Down Expand Up @@ -2111,6 +2141,125 @@ type internal TransparentCompiler
userOpName
)

member this.GetProjectSnapshotFromScript
(
fileName: string,
sourceText: ISourceTextNew,
previewEnabled: bool option,
loadedTimeStamp: DateTime option,
otherFlags: string array option,
useFsiAuxLib: bool option,
useSdkRefs: bool option,
sdkDirOverride: string option,
assumeDotNetFramework: bool option,
optionsStamp: int64 option,
userOpName: string
) : Async<FSharpProjectSnapshot * FSharpDiagnostic list> =
use _ =
Activity.start
"BackgroundCompiler.GetProjectOptionsFromScript"
[| Activity.Tags.fileName, fileName; Activity.Tags.userOpName, userOpName |]

async {
// Use the same default as the background compiler.
let useFsiAuxLib = defaultArg useFsiAuxLib true
let useSdkRefs = defaultArg useSdkRefs true
let previewEnabled = defaultArg previewEnabled false

// Do we assume .NET Framework references for scripts?
let assumeDotNetFramework = defaultArg assumeDotNetFramework true

let extraFlags =
if previewEnabled then
[| "--langversion:preview" |]
else
[||]

let otherFlags = defaultArg otherFlags extraFlags
use diagnostics = new DiagnosticsScope(otherFlags |> Array.contains "--flaterrors")

let useSimpleResolution =
otherFlags |> Array.exists (fun x -> x = "--simpleresolution")

let loadedTimeStamp = defaultArg loadedTimeStamp DateTime.MaxValue // Not 'now', we don't want to force reloading
let projectFileName = fileName + ".fsproj"

let currentSourceFile =
FSharpFileSnapshot.Create(fileName, sourceText.GetHashCode().ToString(), (fun () -> Task.FromResult sourceText))

let! loadClosure =
ComputeScriptClosure
fileName
sourceText
FSharpCheckerResultsSettings.defaultFSharpBinariesDir
useSimpleResolution
(Some useFsiAuxLib)
(Some useSdkRefs)
sdkDirOverride
(Some assumeDotNetFramework)
(projectFileName, fileName)
(List.ofArray otherFlags)
optionsStamp
|> Async.AwaitNodeCode

let otherFlags =
[
yield "--noframework"
yield "--warn:3"
yield! otherFlags
for code, _ in loadClosure.NoWarns do
yield "--nowarn:" + code
]

let sourceFiles =
loadClosure.SourceFiles
|> List.map (fun (sf, _) ->
if sf = fileName then
currentSourceFile
else
FSharpFileSnapshot.CreateFromFileSystem sf)

let references =
loadClosure.References
|> List.map (fun (r, _) ->
let lastModified = FileSystem.GetLastWriteTimeShim r

{
Path = r
LastModified = lastModified
})

let snapshot =
FSharpProjectSnapshot.Create(
fileName + ".fsproj",
None,
sourceFiles,
references,
otherFlags,
List.empty,
false,
true,
loadedTimeStamp,
Some(FSharpUnresolvedReferencesSet(loadClosure.UnresolvedReferences)),
loadClosure.OriginalLoadReferences,
optionsStamp
)

let diags =
loadClosure.LoadClosureRootFileDiagnostics
|> List.map (fun (exn, isError) ->
FSharpDiagnostic.CreateFromException(
exn,
isError,
range.Zero,
false,
otherFlags |> List.contains "--flaterrors",
None
))

return snapshot, (diags @ diagnostics.Diagnostics)
}

member this.GetSemanticClassificationForFile(fileName: string, snapshot: FSharpProjectSnapshot, userOpName: string) =
node {
ignore userOpName
Expand Down
31 changes: 31 additions & 0 deletions src/Compiler/Service/service.fs
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,37 @@ type FSharpChecker
userOpName
)

/// For a given script file, get the ProjectSnapshot implied by the #load closure
member _.GetProjectSnapshotFromScript
(
fileName,
source,
?previewEnabled,
?loadedTimeStamp,
?otherFlags,
?useFsiAuxLib,
?useSdkRefs,
?assumeDotNetFramework,
?sdkDirOverride,
?optionsStamp: int64,
?userOpName: string
) =
let userOpName = defaultArg userOpName "Unknown"

backgroundCompiler.GetProjectSnapshotFromScript(
fileName,
source,
previewEnabled,
loadedTimeStamp,
otherFlags,
useFsiAuxLib,
useSdkRefs,
sdkDirOverride,
assumeDotNetFramework,
optionsStamp,
userOpName
)

member _.GetProjectOptionsFromCommandLineArgs(projectFileName, argv, ?loadedTimeStamp, ?isInteractive, ?isEditing) =
let isEditing = defaultArg isEditing false
let isInteractive = defaultArg isInteractive false
Expand Down
28 changes: 28 additions & 0 deletions src/Compiler/Service/service.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,34 @@ type public FSharpChecker =
?userOpName: string ->
Async<FSharpProjectOptions * FSharpDiagnostic list>

/// <param name="fileName">Used to differentiate between scripts, to consider each script a separate project. Also used in formatted error messages.</param>
/// <param name="source">The source for the file.</param>
/// <param name="previewEnabled">Is the preview compiler enabled.</param>
/// <param name="loadedTimeStamp">Indicates when the script was loaded into the editing environment,
/// so that an 'unload' and 'reload' action will cause the script to be considered as a new project,
/// so that references are re-resolved.</param>
/// <param name="otherFlags">Other flags for compilation.</param>
/// <param name="useFsiAuxLib">Add a default reference to the FSharp.Compiler.Interactive.Settings library.</param>
/// <param name="useSdkRefs">Use the implicit references from the .NET SDK.</param>
/// <param name="assumeDotNetFramework">Set up compilation and analysis for .NET Framework scripts.</param>
/// <param name="sdkDirOverride">Override the .NET SDK used for default references.</param>
/// <param name="optionsStamp">An optional unique stamp for the options.</param>
/// <param name="userOpName">An optional string used for tracing compiler operations associated with this request.</param>
[<Experimental("This FCS API is experimental and subject to change.")>]
member GetProjectSnapshotFromScript:
fileName: string *
source: ISourceTextNew *
?previewEnabled: bool *
?loadedTimeStamp: DateTime *
?otherFlags: string[] *
?useFsiAuxLib: bool *
?useSdkRefs: bool *
?assumeDotNetFramework: bool *
?sdkDirOverride: string *
?optionsStamp: int64 *
?userOpName: string ->
Async<FSharpProjectSnapshot * FSharpDiagnostic list>

/// <summary>Get the FSharpProjectOptions implied by a set of command line arguments.</summary>
///
/// <param name="projectFileName">Used to differentiate between projects and for the base directory of the project.</param>
Expand Down
Loading
Loading