Skip to content

Commit

Permalink
More WIP, try and use snapshot for script stuff.
Browse files Browse the repository at this point in the history
  • Loading branch information
nojaf committed Feb 22, 2024
1 parent dcdf888 commit 927a802
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -202,25 +202,27 @@ type FcsCheckerService(lifetime: Lifetime, logger: ILogger, onSolutionCloseNotif
| _ -> { new IDisposable with member this.Dispose() = () }

member x.TryGetStaleCheckResults([<NotNull>] file: IPsiSourceFile, opName) =
match x.FcsProjectProvider.GetProjectOptions(file) with
match x.FcsProjectProvider.GetProjectSnapshot(file) with
| None -> None
| Some options ->
| Some snapshot ->

let path = file.GetLocation().FullPath
logger.Trace("TryGetStaleCheckResults: start {0}, {1}", path, opName)

match x.Checker.TryGetRecentCheckResultsForFile(path, options) with
| Some (_, checkResults, _) ->
logger.Trace("TryGetStaleCheckResults: finish {0}, {1}", path, opName)
Some checkResults

| _ ->
logger.Trace("TryGetStaleCheckResults: fail {0}, {1}", path, opName)
None
// TODO: TryGetRecentCheckResultsForFile for Snapshot
Unchecked.defaultof<FSharpProjectSnapshot option>
// match x.Checker.TryGetRecentCheckResultsForFile(path, snapshot) with
// | Some (_, checkResults, _) ->
// logger.Trace("TryGetStaleCheckResults: finish {0}, {1}", path, opName)
// Some checkResults
//
// | _ ->
// logger.Trace("TryGetStaleCheckResults: fail {0}, {1}", path, opName)
// None

member x.GetCachedScriptOptions(path) =
member x.GetCachedScriptSnapshot(path) =
if checker.IsValueCreated then
checker.Value.GetCachedScriptOptions(path)
checker.Value.GetCachedScriptSnapshot(path)
else None

member x.InvalidateFcsProject(projectSnapshot: FSharpProjectSnapshot, invalidationType: FcsProjectInvalidationType) =
Expand Down Expand Up @@ -275,8 +277,8 @@ type IFcsProjectProvider =
abstract GetFcsProject: psiModule: IPsiModule -> FcsProject option
abstract GetPsiModule: outputPath: VirtualFileSystemPath -> IPsiModule option

abstract GetProjectOptions: sourceFile: IPsiSourceFile -> FSharpProjectOptions option
abstract GetProjectOptions: psiModule: IPsiModule -> FSharpProjectOptions option
abstract GetProjectSnapshot: sourceFile: IPsiSourceFile -> FSharpProjectSnapshot option
abstract GetProjectSnapshot: psiModule: IPsiModule -> FSharpProjectSnapshot option

abstract GetFileIndex: IPsiSourceFile -> int
abstract GetParsingOptions: sourceFile: IPsiSourceFile -> FSharpParsingOptions
Expand Down Expand Up @@ -304,7 +306,7 @@ type IScriptFcsProjectProvider =
abstract GetScriptSnapshot: IPsiSourceFile -> FSharpProjectSnapshot option
// TODO: unused?
// abstract GetScriptOptions: VirtualFileSystemPath * string -> FSharpProjectOptions option
abstract OptionsUpdated: Signal<VirtualFileSystemPath * FSharpProjectOptions>
abstract SnapshotUpdated: Signal<VirtualFileSystemPath * FSharpProjectSnapshot>
abstract SyncUpdate: bool


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,8 +319,6 @@ type FcsProjectProvider(lifetime: Lifetime, solution: ISolution, changeManager:
| None -> None
| Some projectSnapshot ->

// let snapshot = FSharpProjectSnapshot.FromOptions projectOptions

let parsingOptions =
{ FSharpParsingOptions.Default with
SourceFiles = [| sourceFile.GetLocation().FullPath |]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
namespace JetBrains.ReSharper.Plugins.FSharp.Checker

#nowarn "57"

open System
open System.Collections.Generic
open System.Threading.Tasks
open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.Text
open JetBrains.DataFlow
Expand All @@ -24,13 +27,13 @@ type ScriptFcsProjectProvider(lifetime: Lifetime, logger: ILogger, checkerServic

let scriptFcsProjects = Dictionary<VirtualFileSystemPath, FcsProject option>()

let mutable defaultOptions: FSharpProjectOptions option option = None
let mutable defaultSnapshot: FSharpProjectSnapshot option option = None

let currentRequests = HashSet<VirtualFileSystemPath>()
let dirtyPaths = HashSet<VirtualFileSystemPath>()

let optionsUpdated =
new Signal<VirtualFileSystemPath * FSharpProjectOptions>("ScriptFcsProjectProvider.optionsUpdated")
let snapshotUpdated =
new Signal<VirtualFileSystemPath * FSharpProjectSnapshot>("ScriptFcsProjectProvider.optionsUpdated")

let isHeadless =
let var = Environment.GetEnvironmentVariable("JET_HEADLESS_MODE") |> Option.ofObj |> Option.defaultValue "false"
Expand Down Expand Up @@ -63,60 +66,84 @@ type ScriptFcsProjectProvider(lifetime: Lifetime, logger: ILogger, checkerServic
IPropertyEx.FlowInto(languageVersion, lifetime, flags, getOtherFlags)
flags

let getOptionsImpl (path: VirtualFileSystemPath) source =
let getSnapshotImpl (path: VirtualFileSystemPath) source : FSharpProjectSnapshot option =
let path = path.FullPath
let source = SourceText.ofString source
let targetNetFramework = not PlatformUtil.IsRunningOnCore && scriptSettings.TargetNetFramework.Value

let toolset = toolset.GetDotNetCoreToolset()
let getScriptOptionsAsync =
let getScriptSnapshotAsync: Async<FSharpProjectSnapshot * FSharp.Compiler.Diagnostics.FSharpDiagnostic list> =
if isNotNull toolset && isNotNull toolset.Sdk then
let sdkRootFolder = toolset.Cli.NotNull("cli").SdkRootFolder.NotNull("sdkRootFolder")
let sdkFolderPath = sdkRootFolder / toolset.Sdk.NotNull("sdk").FolderName.NotNull("sdkFolderName")
checkerService.Checker.GetProjectOptionsFromScript(path, source,
otherFlags = otherFlags.Value.Value,
assumeDotNetFramework = targetNetFramework,
sdkDirOverride = sdkFolderPath.FullPath)
// TODO: GetProjectOptionsFromScript does not exists for Snapshot
Unchecked.defaultof<Async<FSharpProjectSnapshot * FSharp.Compiler.Diagnostics.FSharpDiagnostic list>>
// checkerService.Checker.GetProjectOptionsFromScript(path, source,
// otherFlags = otherFlags.Value.Value,
// assumeDotNetFramework = targetNetFramework,
// sdkDirOverride = sdkFolderPath.FullPath)
else
checkerService.Checker.GetProjectOptionsFromScript(path, source,
otherFlags = otherFlags.Value.Value,
assumeDotNetFramework = targetNetFramework)
Unchecked.defaultof<Async<FSharpProjectSnapshot * FSharp.Compiler.Diagnostics.FSharpDiagnostic list>>
// checkerService.Checker.GetProjectOptionsFromScript(path, source,
// otherFlags = otherFlags.Value.Value,
// assumeDotNetFramework = targetNetFramework)

try
let options, errors = getScriptOptionsAsync.RunAsTask()
let snapshot, errors = getScriptSnapshotAsync.RunAsTask()
if not errors.IsEmpty then
logErrors logger (sprintf "Script options for %s" path) errors
Some options
Some snapshot
with
| OperationCanceled -> reraise()
| exn ->
logger.Warn("Error while getting script options for {0}: {1}", path, exn.Message)
logger.LogExceptionSilently(exn)
None

let getDefaultOptions (path: VirtualFileSystemPath) =
let withPath (options: FSharpProjectOptions option) =
match options with
| Some options -> Some { options with SourceFiles = [| path.FullPath |] }
| _ -> None

match defaultOptions with
let getDefaultSnapshot (path: VirtualFileSystemPath) (source: string): ProjectSnapshot.FSharpProjectSnapshot option =
let withPath (snapshot: FSharpProjectSnapshot option) =
snapshot
|> Option.map (fun snapshot ->
let name = path.Name
let version = string path.Info.ModificationTimeUtc.Ticks
let getSource () = SourceTextNew.ofString source |> Task.FromResult
let sourceFiles =
ProjectSnapshot.FSharpFileSnapshot.Create(name, version, getSource)
|> List.singleton

FSharpProjectSnapshot.Create(
snapshot.ProjectFileName,
snapshot.ProjectId,
sourceFiles,
snapshot.ReferencesOnDisk,
snapshot.OtherOptions,
snapshot.ReferencedProjects,
snapshot.IsIncompleteTypeCheckEnvironment,
snapshot.UseScriptResolutionRules,
snapshot.LoadTime,
snapshot.UnresolvedReferences,
snapshot.OriginalLoadReferences,
snapshot.Stamp
)
)

match defaultSnapshot with
| Some options -> withPath options
| _ ->

lock defaultOptionsLock (fun _ ->
match defaultOptions with
match defaultSnapshot with
| Some options -> withPath options
| _ ->

let newOptions = getOptionsImpl path ""
defaultOptions <- Some newOptions
let newOptions = getSnapshotImpl path ""
defaultSnapshot <- Some newOptions
newOptions
)

let createFcsProject (path: VirtualFileSystemPath) options =
options
|> Option.map (fun options ->
let createFcsProject (path: VirtualFileSystemPath) snapshot =
snapshot
|> Option.map (fun snapshot ->
let parsingOptions =
{ FSharpParsingOptions.Default with
SourceFiles = [| path.FullPath |]
Expand All @@ -125,7 +152,7 @@ type ScriptFcsProjectProvider(lifetime: Lifetime, logger: ILogger, checkerServic
IsExe = true }

{ OutputPath = path
ProjectOptions = options
ProjectSnapshot = snapshot
ParsingOptions = parsingOptions
FileIndices = dict [path, 0]
ImplementationFilesWithSignatures = EmptySet.Instance
Expand All @@ -143,11 +170,11 @@ type ScriptFcsProjectProvider(lifetime: Lifetime, logger: ILogger, checkerServic
currentRequests.Add(path) |> ignore
dirtyPaths.Remove(path) |> ignore
let oldOptions = tryGetValue path scriptFcsProjects |> Option.bind id
let newOptions = getOptionsImpl path source
let newSnapshot = getSnapshotImpl path source

scriptFcsProjects[path] <-
newOptions
|> Option.map (fun options ->
newSnapshot
|> Option.map (fun snapshot ->
let parsingOptions =
{ FSharpParsingOptions.Default with
SourceFiles = [| path.FullPath |]
Expand All @@ -158,27 +185,27 @@ type ScriptFcsProjectProvider(lifetime: Lifetime, logger: ILogger, checkerServic
let indices = Dictionary()

{ OutputPath = path
ProjectOptions = options
ProjectSnapshot = snapshot
ParsingOptions = parsingOptions
FileIndices = indices
ImplementationFilesWithSignatures = EmptySet.Instance
ReferencedModules = EmptySet.Instance }
)

match oldOptions, newOptions with
| Some oldOptions, Some newOptions ->
let areEqualForChecking (options1: FSharpProjectOptions) (options2: FSharpProjectOptions) =
let arrayEq a1 a2 =
Array.length a1 = Array.length a2 && Array.forall2 (=) a1 a2
match oldOptions, newSnapshot with
| Some oldOptions, Some newSnapshot ->
let areEqualForChecking (options1: FSharpProjectSnapshot) (options2: FSharpProjectSnapshot) =
let listEq l1 l2 =
List.length l1 = List.length l2 && List.forall2 (=) l1 l2

arrayEq options1.OtherOptions options2.OtherOptions &&
arrayEq options1.SourceFiles options2.SourceFiles
listEq options1.OtherOptions options2.OtherOptions &&
listEq options1.SourceFiles options2.SourceFiles

if not (areEqualForChecking oldOptions.ProjectOptions newOptions) then
optionsUpdated.Fire((path, newOptions))
if not (areEqualForChecking oldOptions.ProjectSnapshot newSnapshot) then
snapshotUpdated.Fire((path, newSnapshot))

| _, Some newOptions ->
optionsUpdated.Fire((path, newOptions))
snapshotUpdated.Fire((path, newOptions))

| _ -> ()

Expand All @@ -203,7 +230,7 @@ type ScriptFcsProjectProvider(lifetime: Lifetime, logger: ILogger, checkerServic
if isHeadless && allowRetry then
getFcsProject path source false
else
getDefaultOptions path |> createFcsProject path
getDefaultSnapshot path source |> createFcsProject path

let getOptions path source : FSharpProjectSnapshot option =
getFcsProject path source true |> Option.map (fun fcsProject -> fcsProject.ProjectSnapshot)
Expand All @@ -223,5 +250,5 @@ type ScriptFcsProjectProvider(lifetime: Lifetime, logger: ILogger, checkerServic
let source = sourceFile.Document.GetText()
getFcsProject path source true

member this.OptionsUpdated = optionsUpdated
member this.SnapshotUpdated = snapshotUpdated
member this.SyncUpdate = isHeadless
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
namespace rec JetBrains.ReSharper.Plugins.FSharp.ProjectModel.Scripts

#nowarn "57"

open System
open System.Collections.Generic
open System.IO
Expand Down Expand Up @@ -64,16 +66,16 @@ type FSharpScriptPsiModulesProvider(lifetime: Lifetime, solution: ISolution, cha
let platformInfo = platformInfos |> Seq.maxBy (fun info -> info.TargetFrameworkId.Version)
platformInfo.TargetFrameworkId

let getScriptReferences (scriptPath: VirtualFileSystemPath) scriptOptions =
let getScriptReferences (scriptPath: VirtualFileSystemPath) (scriptSnapshot: FSharpProjectSnapshot) =
let assembliesPaths = HashSet<VirtualFileSystemPath>()
for o in scriptOptions.OtherOptions do
for o in scriptSnapshot.OtherOptions do
if o.StartsWith("-r:", StringComparison.Ordinal) then
let path = VirtualFileSystemPath.TryParse(o.Substring(3), InteractionContext.SolutionContext)
if not path.IsEmpty then assembliesPaths.Add(path) |> ignore

let filesPaths = HashSet<VirtualFileSystemPath>()
for file in scriptOptions.SourceFiles do
let path = VirtualFileSystemPath.TryParse(file, InteractionContext.SolutionContext)
for file in scriptSnapshot.SourceFiles do
let path = VirtualFileSystemPath.TryParse(file.FileName, InteractionContext.SolutionContext)
if not path.IsEmpty && not (path.Equals(scriptPath)) then
filesPaths.Add(path) |> ignore

Expand Down Expand Up @@ -148,7 +150,7 @@ type FSharpScriptPsiModulesProvider(lifetime: Lifetime, solution: ISolution, cha
locks.QueueReadLock(lifetime, "AssemblyGC after removing F# script reference", fun _ ->
solution.GetComponent<AssemblyGC>().ForceGC())

and queueUpdateReferences (path: VirtualFileSystemPath) (newOptions: FSharpProjectOptions) =
and queueUpdateReferences (path: VirtualFileSystemPath) (newSnapshot: FSharpProjectSnapshot) =
locks.QueueReadLock(lifetime, "Request new F# script references", fun _ ->
let oldReferences =
let mutable oldReferences = Unchecked.defaultof<ScriptReferences>
Expand All @@ -161,7 +163,7 @@ type FSharpScriptPsiModulesProvider(lifetime: Lifetime, solution: ISolution, cha

ira.FuncRun <-
fun _ ->
let newReferences = getScriptReferences path newOptions
let newReferences = getScriptReferences path newSnapshot
Interruption.Current.CheckAndThrow()

let getDiff oldPaths newPaths =
Expand All @@ -181,7 +183,7 @@ type FSharpScriptPsiModulesProvider(lifetime: Lifetime, solution: ISolution, cha

ira.FuncCancelled <-
// Reschedule again
fun _ -> queueUpdateReferences path newOptions
fun _ -> queueUpdateReferences path newSnapshot

ira.DoStart() |> ignore
)
Expand All @@ -190,7 +192,7 @@ type FSharpScriptPsiModulesProvider(lifetime: Lifetime, solution: ISolution, cha
changeManager.RegisterChangeProvider(lifetime, this)

if not scriptOptionsProvider.SyncUpdate then
scriptOptionsProvider.OptionsUpdated.Advise(lifetime, fun (path, options) ->
scriptOptionsProvider.SnapshotUpdated.Advise(lifetime, fun (path, options) ->
queueUpdateReferences path options
)

Expand Down Expand Up @@ -230,7 +232,7 @@ type FSharpScriptPsiModulesProvider(lifetime: Lifetime, solution: ISolution, cha
if scriptOptionsProvider.SyncUpdate then
scriptOptionsProvider.GetFcsProject(psiModule.SourceFile)
|> Option.iter (fun fcsProject ->
let references = getScriptReferences path fcsProject.ProjectOptions
let references = getScriptReferences path fcsProject.ProjectSnapshot
updateReferences path references references.Assemblies [] changeBuilder
)

Expand Down Expand Up @@ -261,8 +263,8 @@ type FSharpScriptPsiModulesProvider(lifetime: Lifetime, solution: ISolution, cha
scriptsFromProjectFiles.GetValuesSafe(path)
|> Seq.tryFind (fun psiModule -> psiModule.Path = moduleToRemove.Path)
|> Option.iter (fun psiModule ->
match checkerService.GetCachedScriptOptions(path.FullPath) with
| Some options -> checkerService.InvalidateFcsProject(options, FcsProjectInvalidationType.Remove)
match checkerService.GetCachedScriptSnapshot(path.FullPath) with
| Some snapshot -> checkerService.InvalidateFcsProject(snapshot, FcsProjectInvalidationType.Remove)
| None -> ()

scriptsFromProjectFiles.RemoveValue(path, psiModule) |> ignore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ type FSharpTestHost(solution: ISolution, sourceCache: FSharpSourceCache, itemsCo
psiModule.SourceFiles
|> Seq.find (fun sourceFile -> sourceFile.LanguageType.Is<FSharpProjectFileType>())

projectProvider.GetProjectOptions(sourceFile)
projectProvider.GetProjectSnapshot(sourceFile)
|> Option.map (fun options ->
options.OtherOptions
|> Array.choose (fun o -> if o.StartsWith("-r:") then Some (o.Substring("-r:".Length)) else None)
Expand All @@ -67,7 +67,7 @@ type FSharpTestHost(solution: ISolution, sourceCache: FSharpSourceCache, itemsCo

let project = projectModelViewHost.GetItemById<IProject>(projectModelId)
let psiModule = psiModules.GetPsiModules(project) |> Seq.exactlyOne
projectProvider.GetProjectOptions(psiModule).Value
projectProvider.GetProjectSnapshot(psiModule).Value

let dumpFcsProjectStamp (projectModelId: int) =
let projectOptions = getProjectOptions projectModelId
Expand Down

0 comments on commit 927a802

Please sign in to comment.