diff --git a/ReSharper.FSharp/src/FSharp.Common/src/Checker/FcsCheckerService.fs b/ReSharper.FSharp/src/FSharp.Common/src/Checker/FcsCheckerService.fs index 81638f5950..cc2c491729 100644 --- a/ReSharper.FSharp/src/FSharp.Common/src/Checker/FcsCheckerService.fs +++ b/ReSharper.FSharp/src/FSharp.Common/src/Checker/FcsCheckerService.fs @@ -23,6 +23,7 @@ open JetBrains.ReSharper.Psi.CSharp open JetBrains.ReSharper.Psi.Modules open JetBrains.ReSharper.Psi.Tree open JetBrains.ReSharper.Psi.VB +open JetBrains.ReSharper.Resources.Shell open JetBrains.Util module FcsCheckerService = @@ -146,6 +147,7 @@ type FcsCheckerService(lifetime: Lifetime, logger: ILogger, onSolutionCloseNotif member x.InvalidateFcsProjects(solution: ISolution, isApplicable: IProject -> bool) = if checker.IsValueCreated then + use lock = ReadLockCookie.Create() solution.GetAllProjects() |> Seq.filter isApplicable |> Seq.iter x.InvalidateFcsProject diff --git a/ReSharper.FSharp/src/FSharp.Common/src/Settings/FSharpOptions.fs b/ReSharper.FSharp/src/FSharp.Common/src/Settings/FSharpOptions.fs index a918356069..e46962c94e 100644 --- a/ReSharper.FSharp/src/FSharp.Common/src/Settings/FSharpOptions.fs +++ b/ReSharper.FSharp/src/FSharp.Common/src/Settings/FSharpOptions.fs @@ -71,6 +71,7 @@ module FSharpExperimentalFeatures = let [] formatter = "Enable F# code formatter" let [] fsiInteractiveEditor = "Enable analysis of F# Interactive editor (experimental)" let [] outOfProcessTypeProviders = "Host type providers out-of-process (solution reload required)" + let [] hostTypeProvidersFromTempFolder = "Host type providers from Temp folder" [, "F# experimental features")>] @@ -88,7 +89,10 @@ type FSharpExperimentalFeatures = mutable FsiInteractiveEditor: bool [] - mutable OutOfProcessTypeProviders: bool } + mutable OutOfProcessTypeProviders: bool + + [] + mutable HostTypeProvidersFromTempFolder: bool } [] @@ -123,6 +127,7 @@ type FSharpExperimentalFeaturesProvider(lifetime, solution, settings, settingsSc member val RedundantParensAnalysis = base.GetValueProperty("RedundantParensAnalysis") member val Formatter = base.GetValueProperty("Formatter") member val OutOfProcessTypeProviders = base.GetValueProperty("OutOfProcessTypeProviders") + member val HostTypeProvidersFromTempFolder = base.GetValueProperty("HostTypeProvidersFromTempFolder") [] @@ -175,6 +180,11 @@ type FSharpOptionsPage(lifetime: Lifetime, optionsPageContext, settings, this.AddHeader("FSharp.Compiler.Service options") this.AddBoolOption((fun key -> key.SkipImplementationAnalysis), RichText(skipImplementationAnalysis), null) |> ignore this.AddBoolOption((fun key -> key.OutOfProcessTypeProviders), RichText(FSharpExperimentalFeatures.outOfProcessTypeProviders), null) |> ignore + do + use indent = this.Indent() + [ this.AddBoolOption((fun key -> key.HostTypeProvidersFromTempFolder), RichText(FSharpExperimentalFeatures.hostTypeProvidersFromTempFolder), null) ] + |> Seq.iter (fun checkbox -> + this.AddBinding(checkbox, BindingStyle.IsEnabledProperty, (fun key -> key.OutOfProcessTypeProviders), id)) if configurations.IsInternalMode() then this.AddHeader("Experimental features options") diff --git a/ReSharper.FSharp/src/FSharp.Common/src/Shim/TypeProviders/ExtensionTypingProviderShim.fs b/ReSharper.FSharp/src/FSharp.Common/src/Shim/TypeProviders/ExtensionTypingProviderShim.fs index 8ebfba58f0..bf980e2cb8 100644 --- a/ReSharper.FSharp/src/FSharp.Common/src/Shim/TypeProviders/ExtensionTypingProviderShim.fs +++ b/ReSharper.FSharp/src/FSharp.Common/src/Shim/TypeProviders/ExtensionTypingProviderShim.fs @@ -8,24 +8,33 @@ open FSharp.Core.CompilerServices open JetBrains.Core open JetBrains.Lifetimes open JetBrains.ProjectModel +open JetBrains.ProjectModel.Tasks +open JetBrains.Rd.Tasks +open JetBrains.ReSharper.Feature.Services.Daemon +open JetBrains.ReSharper.Plugins.FSharp +open JetBrains.ReSharper.Plugins.FSharp.Checker open JetBrains.ReSharper.Plugins.FSharp.Settings open JetBrains.ReSharper.Plugins.FSharp.TypeProviders.Protocol open JetBrains.ReSharper.Plugins.FSharp.TypeProviders.Protocol.Exceptions open JetBrains.ReSharper.Plugins.FSharp.TypeProviders.Protocol.Models +open JetBrains.ReSharper.Psi.Files type IProxyExtensionTypingProvider = inherit IExtensionTypingProvider abstract RuntimeVersion: unit -> string abstract DumpTypeProvidersProcess: unit -> string + abstract TerminateConnection: unit -> unit [] type ExtensionTypingProviderShim(solution: ISolution, toolset: ISolutionToolset, experimentalFeatures: FSharpExperimentalFeaturesProvider, + checkerService: FcsCheckerService, daemon: IDaemon, psiFiles: IPsiFiles, typeProvidersLoadersFactory: TypeProvidersExternalProcessFactory) as this = let lifetime = solution.GetLifetime() let defaultShim = ExtensionTypingProvider let outOfProcess = experimentalFeatures.OutOfProcessTypeProviders + let hostFromTemp = experimentalFeatures.HostTypeProvidersFromTempFolder let createProcessLockObj = obj() let [] mutable connection: TypeProvidersConnection = null @@ -36,7 +45,7 @@ type ExtensionTypingProviderShim(solution: ISolution, toolset: ISolutionToolset, isNotNull connection && connection.IsActive let terminateConnection () = - if isConnectionAlive() then typeProvidersHostLifetime.Terminate() + if isConnectionAlive () then typeProvidersHostLifetime.Terminate() let connect () = if isConnectionAlive () then () else @@ -49,14 +58,29 @@ type ExtensionTypingProviderShim(solution: ISolution, toolset: ISolutionToolset, typeProvidersManager <- TypeProvidersManager(newConnection) :?> _ connection <- newConnection) + let restart _ = + let paths = typeProvidersManager.GetAssemblies() + terminateConnection () + checkerService.InvalidateFcsProjects(solution, fun x -> paths.Contains(x.Location)) + psiFiles.IncrementModificationTimestamp(null) + daemon.Invalidate() + Unit.Instance + do - lifetime.Bracket((fun () -> ExtensionTypingProvider <- this), - fun () -> ExtensionTypingProvider <- defaultShim) + lifetime.Bracket((fun () -> ExtensionTypingProvider <- this), fun () -> ExtensionTypingProvider <- defaultShim) toolset.Changed.Advise(lifetime, fun _ -> terminateConnection ()) outOfProcess.Change.Advise(lifetime, fun enabled -> if enabled.HasNew && not enabled.New then terminateConnection ()) + hostFromTemp.Change.Advise(lifetime, fun enabled -> + if enabled.HasNew && not enabled.New then terminateConnection ()) + + let rdTypeProvidersHost = solution.RdFSharpModel().FSharpTypeProvidersHost + + rdTypeProvidersHost.RestartTypeProviders.Set(restart) + rdTypeProvidersHost.IsLaunched.Set(fun _ -> outOfProcess.Value && isNotNull connection) + interface IProxyExtensionTypingProvider with member this.InstantiateTypeProvidersOfAssembly(runTimeAssemblyFileName: string, designTimeAssemblyNameString: string, resolutionEnvironment: ResolutionEnvironment, @@ -72,7 +96,7 @@ type ExtensionTypingProviderShim(solution: ISolution, toolset: ISolutionToolset, try typeProvidersManager.GetOrCreate(runTimeAssemblyFileName, designTimeAssemblyNameString, resolutionEnvironment, isInvalidationSupported, isInteractive, systemRuntimeContainsType, - systemRuntimeAssemblyVersion, compilerToolsPath) + systemRuntimeAssemblyVersion, compilerToolsPath, hostFromTemp.Value) with :? TypeProvidersInstantiationException as e -> logError (TypeProviderError(e.FcsNumber, "", m, [e.Message])) [] @@ -114,5 +138,4 @@ type ExtensionTypingProviderShim(solution: ISolution, toolset: ISolutionToolset, $"{inProcessDump}\n\n{outOfProcessDump}" - interface IDisposable with - member this.Dispose() = terminateConnection () + member this.TerminateConnection() = terminateConnection() diff --git a/ReSharper.FSharp/src/FSharp.Common/src/Shim/TypeProviders/TypeProvidersManager.fs b/ReSharper.FSharp/src/FSharp.Common/src/Shim/TypeProviders/TypeProvidersManager.fs index e1d5730193..e3095917c2 100644 --- a/ReSharper.FSharp/src/FSharp.Common/src/Shim/TypeProviders/TypeProvidersManager.fs +++ b/ReSharper.FSharp/src/FSharp.Common/src/Shim/TypeProviders/TypeProvidersManager.fs @@ -2,6 +2,7 @@ open System open System.Collections.Concurrent +open System.Collections.Generic open System.Threading open FSharp.Compiler.ExtensionTyping open FSharp.Core.CompilerServices @@ -52,6 +53,13 @@ type internal TypeProvidersCache() = if not hasValue then failwith $"Cannot get type provider {id} from TypeProvidersCache" else proxyTypeProvidersPerId.[id] + member x.GetProvidersLocations() = + typeProvidersPerAssembly.Keys + |> Seq.map snd + |> Seq.distinct + |> Seq.map FileSystemPath.Parse + |> HashSet + member x.Dump() = let typeProviders = proxyTypeProvidersPerId @@ -72,8 +80,10 @@ type IProxyTypeProvidersManager = isInteractive: bool * systemRuntimeContainsType: (string -> bool) * systemRuntimeAssemblyVersion: Version * - compilerToolsPath: string list -> ITypeProvider list + compilerToolsPath: string list * + shadowCopyDesignTimeAssembly: bool -> ITypeProvider list + abstract member GetAssemblies: unit -> HashSet abstract member Dump: unit -> string type TypeProvidersManager(connection: TypeProvidersConnection) = @@ -89,8 +99,8 @@ type TypeProvidersManager(connection: TypeProvidersConnection) = member x.GetOrCreate(runTimeAssemblyFileName: string, designTimeAssemblyNameString: string, resolutionEnvironment: ResolutionEnvironment, isInvalidationSupported: bool, isInteractive: bool, systemRuntimeContainsType: string -> bool, systemRuntimeAssemblyVersion: Version, - compilerToolsPath: string list) = - let envKey = $"{designTimeAssemblyNameString}+{resolutionEnvironment.resolutionFolder}" + compilerToolsPath: string list, shadowCopyDesignTimeAssembly) = + let envKey = (designTimeAssemblyNameString, resolutionEnvironment.resolutionFolder) let result = let fakeTcImports = getFakeTcImports systemRuntimeContainsType @@ -100,7 +110,8 @@ type TypeProvidersManager(connection: TypeProvidersConnection) = InstantiateTypeProvidersOfAssemblyParameters(runTimeAssemblyFileName, designTimeAssemblyNameString, resolutionEnvironment.toRdResolutionEnvironment(), isInvalidationSupported, isInteractive, systemRuntimeAssemblyVersion.ToString(), - compilerToolsPath |> Array.ofList, fakeTcImports), RpcTimeouts.Maximal)) + compilerToolsPath |> Array.ofList, fakeTcImports, shadowCopyDesignTimeAssembly), + RpcTimeouts.Maximal)) let typeProviderProxies = [ for tp in result.TypeProviders -> @@ -117,3 +128,6 @@ type TypeProvidersManager(connection: TypeProvidersConnection) = member this.Dump() = $"{typeProviders.Dump()}\n\n{tpContext.Dump()}" + + member this.GetAssemblies() = + typeProviders.GetProvidersLocations() diff --git a/ReSharper.FSharp/src/FSharp.TypeProviders.Host/src/Hosts/TypeProvidersHost.cs b/ReSharper.FSharp/src/FSharp.TypeProviders.Host/src/Hosts/TypeProvidersHost.cs index 67eff8fb9c..1a040c790e 100644 --- a/ReSharper.FSharp/src/FSharp.TypeProviders.Host/src/Hosts/TypeProvidersHost.cs +++ b/ReSharper.FSharp/src/FSharp.TypeProviders.Host/src/Hosts/TypeProvidersHost.cs @@ -111,8 +111,9 @@ private InstantiationResult InstantiateTypeProvidersOfAssembly(InstantiateTypePr return new InstantiationResult(rdTypeProviders.ToArray(), cachedIds.ToArray()); } - private static Unit Die(Unit _) + private Unit Die(Unit _) { + myTypeProvidersContext.TypeProvidersLoader.Dispose(); Environment.Exit(0); return Unit.Instance; } diff --git a/ReSharper.FSharp/src/FSharp.TypeProviders.Host/src/Program.cs b/ReSharper.FSharp/src/FSharp.TypeProviders.Host/src/Program.cs index 49ca791f90..c4a692c6c1 100644 --- a/ReSharper.FSharp/src/FSharp.TypeProviders.Host/src/Program.cs +++ b/ReSharper.FSharp/src/FSharp.TypeProviders.Host/src/Program.cs @@ -6,7 +6,7 @@ internal static class Program { public static void Main(string[] args) { - AppDomain.CurrentDomain.AssemblyResolve += RiderPluginAssemblyResolver.Resolve; + AppDomain.CurrentDomain.AssemblyResolve += TypeProvidersAssemblyResolver.Resolve; MainInternal(args); } diff --git a/ReSharper.FSharp/src/FSharp.TypeProviders.Host/src/RiderPluginAssemblyResolver.cs b/ReSharper.FSharp/src/FSharp.TypeProviders.Host/src/TypeProvidersAssemblyResolver.cs similarity index 93% rename from ReSharper.FSharp/src/FSharp.TypeProviders.Host/src/RiderPluginAssemblyResolver.cs rename to ReSharper.FSharp/src/FSharp.TypeProviders.Host/src/TypeProvidersAssemblyResolver.cs index 85e1566deb..8d472ab360 100644 --- a/ReSharper.FSharp/src/FSharp.TypeProviders.Host/src/RiderPluginAssemblyResolver.cs +++ b/ReSharper.FSharp/src/FSharp.TypeProviders.Host/src/TypeProvidersAssemblyResolver.cs @@ -5,12 +5,12 @@ namespace JetBrains.ReSharper.Plugins.FSharp.TypeProviders.Host { - public static class RiderPluginAssemblyResolver + public static class TypeProvidersAssemblyResolver { private const string AdditionalProbingPathsEnvVar = "RIDER_PLUGIN_ADDITIONAL_PROBING_PATHS"; private static readonly List OurAdditionalProbingPaths = new List(); - static RiderPluginAssemblyResolver() + static TypeProvidersAssemblyResolver() { var paths = Environment.GetEnvironmentVariable(AdditionalProbingPathsEnvVar); if (string.IsNullOrWhiteSpace(paths)) return; diff --git a/ReSharper.FSharp/src/FSharp.TypeProviders.Host/src/TypeProvidersContext.cs b/ReSharper.FSharp/src/FSharp.TypeProviders.Host/src/TypeProvidersContext.cs index bf4e423210..1abe061741 100644 --- a/ReSharper.FSharp/src/FSharp.TypeProviders.Host/src/TypeProvidersContext.cs +++ b/ReSharper.FSharp/src/FSharp.TypeProviders.Host/src/TypeProvidersContext.cs @@ -55,7 +55,7 @@ public IProvidedRdModelsCreator public TypeProvidersContext(ILogger logger) { Logger = logger; - TypeProvidersLoader = new TypeProvidersLoader(); + TypeProvidersLoader = new TypeProvidersLoader(logger); TypeProvidersCache = new TypeProvidersCache(); ProvidedTypesCache = new ProvidedTypesCache(ProvidedTypesComparer.Instance); diff --git a/ReSharper.FSharp/src/FSharp.TypeProviders.Host/src/TypeProvidersLoader.cs b/ReSharper.FSharp/src/FSharp.TypeProviders.Host/src/TypeProvidersLoader.cs index 54b9d5660f..5d2f0cfdae 100644 --- a/ReSharper.FSharp/src/FSharp.TypeProviders.Host/src/TypeProvidersLoader.cs +++ b/ReSharper.FSharp/src/FSharp.TypeProviders.Host/src/TypeProvidersLoader.cs @@ -1,10 +1,13 @@ using System; using System.Collections.Generic; +using System.IO; using FSharp.Compiler; +using JetBrains.Diagnostics; using JetBrains.ReSharper.Plugins.FSharp.Shim.TypeProviders; using JetBrains.ReSharper.Plugins.FSharp.TypeProviders.Protocol.Exceptions; using JetBrains.ReSharper.Plugins.FSharp.Util; using JetBrains.Rider.FSharp.TypeProviders.Protocol.Server; +using JetBrains.Util; using Microsoft.FSharp.Collections; using Microsoft.FSharp.Core; using Microsoft.FSharp.Core.CompilerServices; @@ -12,7 +15,7 @@ namespace JetBrains.ReSharper.Plugins.FSharp.TypeProviders.Host { - public interface ITypeProvidersLoader + public interface ITypeProvidersLoader : IDisposable { IEnumerable InstantiateTypeProvidersOfAssembly( InstantiateTypeProvidersOfAssemblyParameters parameters); @@ -20,10 +23,28 @@ IEnumerable InstantiateTypeProvidersOfAssembly( public class TypeProvidersLoader : ITypeProvidersLoader { + private readonly ILogger myLogger; + private readonly Dictionary myShadowCopyMapping = new Dictionary(); + + public TypeProvidersLoader(ILogger logger) => myLogger = logger; + private readonly FSharpFunc myLogError = FSharpFunc.FromConverter(e => throw new TypeProvidersInstantiationException(e.ContextualErrorMessage, e.Number)); + private string GetAssemblyShadowCopy(string designTimeAssembly) + { + if (!myShadowCopyMapping.TryGetValue(designTimeAssembly, out var shadowCopy)) + { + shadowCopy = Path.ChangeExtension(Path.GetTempFileName(), "dll"); + File.Copy(designTimeAssembly, shadowCopy); + myLogger.Log(LoggingLevel.INFO, $"Shadow copying assembly {designTimeAssembly} to {shadowCopy}"); + myShadowCopyMapping.Add(designTimeAssembly, shadowCopy); + } + + return shadowCopy; + } + public IEnumerable InstantiateTypeProvidersOfAssembly( InstantiateTypeProvidersOfAssemblyParameters parameters) { @@ -32,11 +53,24 @@ public IEnumerable InstantiateTypeProvidersOfAssembly( var systemRuntimeAssemblyVersion = Version.Parse(parameters.SystemRuntimeAssemblyVersion); var compilerToolsPath = ListModule.OfSeq(parameters.CompilerToolsPath); + // We assume that to check the type provider, its developer will reference the type provider design-time assembly in the project. + // In this case, RunTimeAssemblyFileName will be the same as DesignTimeAssemblyNameString. + var designTimeAssembly = parameters.ShadowCopyDesignTimeAssembly + ? GetAssemblyShadowCopy(parameters.RunTimeAssemblyFileName) + : parameters.DesignTimeAssemblyNameString; + var typeProviders = ExtensionTyping.Shim.ExtensionTypingProvider.InstantiateTypeProvidersOfAssembly( - parameters.RunTimeAssemblyFileName, parameters.DesignTimeAssemblyNameString, + parameters.RunTimeAssemblyFileName, designTimeAssembly, resolutionEnvironment, parameters.IsInvalidationSupported, parameters.IsInteractive, systemRuntimeContainsType, systemRuntimeAssemblyVersion, compilerToolsPath, myLogError, Range.Zero); return typeProviders; } + + public void Dispose() + { + foreach (var shadowCopy in myShadowCopyMapping.Values) + if (File.Exists(shadowCopy)) + File.Delete(shadowCopy); + } } } diff --git a/ReSharper.FSharp/test/src/FSharp.Tests.Host/FSharpTestHost.fs b/ReSharper.FSharp/test/src/FSharp.Tests.Host/FSharpTestHost.fs index 203c1c609f..c91dbccd9e 100644 --- a/ReSharper.FSharp/test/src/FSharp.Tests.Host/FSharpTestHost.fs +++ b/ReSharper.FSharp/test/src/FSharp.Tests.Host/FSharpTestHost.fs @@ -59,6 +59,10 @@ type FSharpTestHost(solution: ISolution, sourceCache: FSharpSourceCache, itemsCo CultureInfo.CurrentUICulture <- newCulture currentCulture.Name + let killTypeProvidersProcess _ = + solution.GetComponent().TerminateConnection() + JetBrains.Core.Unit.Instance + do let fsTestHost = solution.RdFSharpModel().FsharpTestHost @@ -69,3 +73,4 @@ type FSharpTestHost(solution: ISolution, sourceCache: FSharpSourceCache, itemsCo fsTestHost.TypeProvidersRuntimeVersion.Set(typeProvidersRuntimeVersion) fsTestHost.DumpTypeProvidersProcess.Set(dumpTypeProvidersProcess) fsTestHost.GetCultureInfoAndSetNew.Set(getCultureInfoAndSetNew) + fsTestHost.KillTypeProvidersProcess.Set(killTypeProvidersProcess) diff --git a/rider-fsharp/protocol/src/kotlin/model/RdFSharpModel.kt b/rider-fsharp/protocol/src/kotlin/model/RdFSharpModel.kt index 65e7d9a814..609c7802a9 100644 --- a/rider-fsharp/protocol/src/kotlin/model/RdFSharpModel.kt +++ b/rider-fsharp/protocol/src/kotlin/model/RdFSharpModel.kt @@ -49,8 +49,14 @@ object RdFSharpModel : Ext(SolutionModel.Solution) { }.nullable) call("dumpSingleProjectMapping", void, string) call("dumpSingleProjectLocalReferences", void, immutableList(string)) - call("TypeProvidersRuntimeVersion", void, string.nullable) - call("DumpTypeProvidersProcess", void, string) + call("typeProvidersRuntimeVersion", void, string.nullable) + call("dumpTypeProvidersProcess", void, string) + call("killTypeProvidersProcess", void, void) + } + + private val RdFSharpTypeProvidersHost = aggregatedef("RdFSharpTypeProvidersHost") { + call("restartTypeProviders", void, void) + call("isLaunched", void, bool) } init { @@ -60,6 +66,7 @@ object RdFSharpModel : Ext(SolutionModel.Solution) { field("fSharpInteractiveHost", RdFSharpInteractiveHost) field("fsharpTestHost", RdFSharpTestHost) + field("fSharpTypeProvidersHost", RdFSharpTypeProvidersHost) property("fcsBusyDelayMs", int) } } diff --git a/rider-fsharp/protocol/src/kotlin/model/RdFSharpTypeProvidersModel.kt b/rider-fsharp/protocol/src/kotlin/model/RdFSharpTypeProvidersModel.kt index 604cbcc24e..cfb4eca8e0 100644 --- a/rider-fsharp/protocol/src/kotlin/model/RdFSharpTypeProvidersModel.kt +++ b/rider-fsharp/protocol/src/kotlin/model/RdFSharpTypeProvidersModel.kt @@ -77,7 +77,6 @@ object RdFSharpTypeProvidersModel : Root() { private val RdTypeProviderProcessModel = aggregatedef("RdTypeProviderProcessModel") { signal("Invalidate", int) - call("InvalidateExternalTP", int, void) call("GetProvidedNamespaces", int, array(RdProvidedNamespace)) call("Dispose", int, void) call("GetCustomAttributes", structdef("GetCustomAttributesArgs") { @@ -144,6 +143,7 @@ object RdFSharpTypeProvidersModel : Root() { field("SystemRuntimeAssemblyVersion", string) field("CompilerToolsPath", array(string)) field("FakeTcImports", RdFakeTcImports) + field("ShadowCopyDesignTimeAssembly", bool) } private val RdFakeDllInfo = structdef { diff --git a/rider-fsharp/src/main/java/com/jetbrains/rider/plugins/fsharp/services/typeProviders/TypeProvidersActions.kt b/rider-fsharp/src/main/java/com/jetbrains/rider/plugins/fsharp/services/typeProviders/TypeProvidersActions.kt new file mode 100644 index 0000000000..55912c6b77 --- /dev/null +++ b/rider-fsharp/src/main/java/com/jetbrains/rider/plugins/fsharp/services/typeProviders/TypeProvidersActions.kt @@ -0,0 +1,23 @@ +package com.jetbrains.rider.plugins.fsharp.services.typeProviders + +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.jetbrains.rd.platform.util.lifetime +import com.jetbrains.rider.plugins.fsharp.rdFSharpModel +import com.jetbrains.rider.projectView.solution + +class RestartTypeProvidersAction : AnAction("Restart Type Providers") { + override fun update(e: AnActionEvent) { + val project = e.project + if (project != null) { + e.presentation.isEnabledAndVisible = project.solution.rdFSharpModel.fSharpTypeProvidersHost.isLaunched.sync(Unit) + } + } + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project + if (project != null) { + project.solution.rdFSharpModel.fSharpTypeProvidersHost.restartTypeProviders.start(project.lifetime, Unit) + } + } +} diff --git a/rider-fsharp/src/main/resources/META-INF/plugin.xml b/rider-fsharp/src/main/resources/META-INF/plugin.xml index 55aef7996d..2202741ca6 100644 --- a/rider-fsharp/src/main/resources/META-INF/plugin.xml +++ b/rider-fsharp/src/main/resources/META-INF/plugin.xml @@ -105,6 +105,7 @@ + diff --git a/rider-fsharp/src/test/kotlin/Extensions.kt b/rider-fsharp/src/test/kotlin/Extensions.kt index 0490ede5f8..9fd57b0c46 100644 --- a/rider-fsharp/src/test/kotlin/Extensions.kt +++ b/rider-fsharp/src/test/kotlin/Extensions.kt @@ -17,17 +17,19 @@ fun com.intellij.openapi.editor.Editor.dumpTypeProviders(stream: PrintStream) { } } -fun withSetting(project: Project, setting: String, function: () -> Unit) { - TestHost.getInstance(project.protocolHost).setSetting(setting, "true") +fun withSettings(project: Project, settings: List, function: () -> Unit) { + settings.forEach { TestHost.getInstance(project.protocolHost).setSetting(it, "true") } try { function() } finally { - TestHost.getInstance(project.protocolHost).setSetting(setting, "false") + settings.forEach { TestHost.getInstance(project.protocolHost).setSetting(it, "false") } } } -fun BaseTestWithSolution.withTypeProviders(function: () -> Unit) { - withSetting(project, "FSharp/FSharpOptions/FSharpExperimentalFeatures/OutOfProcessTypeProviders/@EntryValue") { +fun BaseTestWithSolution.withTypeProviders(shadowCopyMode: Boolean = false, function: () -> Unit) { + val settings = mutableListOf("FSharp/FSharpOptions/FSharpExperimentalFeatures/OutOfProcessTypeProviders/@EntryValue") + if (shadowCopyMode) settings.add("FSharp/FSharpOptions/FSharpExperimentalFeatures/HostTypeProvidersFromTempFolder/@EntryValue") + withSettings(project, settings) { try { function() } finally { @@ -40,7 +42,7 @@ fun BaseTestWithSolution.withTypeProviders(function: () -> Unit) { } fun withEditorConfig(project: Project, function: () -> Unit) { - withSetting(project, "CodeStyle/EditorConfig/EnableEditorConfigSupport", function) + withSettings(project, listOf("CodeStyle/EditorConfig/EnableEditorConfigSupport"), function) } fun withCultureInfo(project: Project, culture: String, function: () -> Unit) { diff --git a/rider-fsharp/src/test/kotlin/typeProviders/TypeProvidersActionsTest.kt b/rider-fsharp/src/test/kotlin/typeProviders/TypeProvidersActionsTest.kt new file mode 100644 index 0000000000..fe1f8bf8a2 --- /dev/null +++ b/rider-fsharp/src/test/kotlin/typeProviders/TypeProvidersActionsTest.kt @@ -0,0 +1,98 @@ +package typeProviders + +import com.intellij.execution.configurations.GeneralCommandLine +import com.jetbrains.rd.platform.util.lifetime +import com.jetbrains.rdclient.testFramework.waitForDaemon +import com.jetbrains.rdclient.util.idea.waitAndPump +import com.jetbrains.rider.NetCoreRuntime +import com.jetbrains.rider.daemon.util.hasErrors +import com.jetbrains.rider.plugins.fsharp.rdFSharpModel +import com.jetbrains.rider.projectView.solution +import com.jetbrains.rider.projectView.solutionDirectoryPath +import com.jetbrains.rider.test.annotations.TestEnvironment +import com.jetbrains.rider.test.asserts.* +import com.jetbrains.rider.test.base.BaseTestWithSolution +import com.jetbrains.rider.test.enums.CoreVersion +import com.jetbrains.rider.test.enums.ToolsetVersion +import com.jetbrains.rider.test.scriptingApi.* +import org.testng.annotations.Test +import withTypeProviders +import java.time.Duration + +@Test +@TestEnvironment(toolset = ToolsetVersion.TOOLSET_16, coreVersion = CoreVersion.DOT_NET_CORE_3_1) +class TypeProvidersActionsTest : BaseTestWithSolution() { + override fun getSolutionDirectoryName() = "TypeProviderLibrary" + override val restoreNuGetPackages = true + private val sourceFile = "TypeProviderLibrary/Caches.fs" + private val rdFcsHost get() = project.solution.rdFSharpModel.fsharpTestHost + private val restartTypeProvidersAction = "Rider.Plugins.FSharp.RestartTypeProviders" + + private fun waitForTypeProviders() { + waitAndPump(project.lifetime, { rdFcsHost.typeProvidersRuntimeVersion.sync(Unit) != null }, Duration.ofSeconds(60000)) + } + + @Test + fun restartTypeProviders() { + withTypeProviders { + withOpenedEditor(project, sourceFile) { + waitForDaemon() + rdFcsHost.typeProvidersRuntimeVersion.sync(Unit).shouldNotBeNull() + markupAdapter.hasErrors.shouldBeFalse() + + rdFcsHost.killTypeProvidersProcess.sync(Unit) + rdFcsHost.typeProvidersRuntimeVersion.sync(Unit).shouldBeNull() + + callAction(restartTypeProvidersAction) + waitForTypeProviders() + waitForDaemon() + markupAdapter.hasErrors.shouldBeFalse() + } + } + } + + @Test + @TestEnvironment(solution = "LemonadeProvider") + fun rebuildTypeProvider() { + val lemonadeProviderProject = "${project.solutionDirectoryPath}/LemonadeProvider.DesignTime/LemonadeProvider.DesignTime.fsproj" + val sourceFileToCheck = "LemonadeProviderConsumer/Library.fs" + + withTypeProviders(true) { + + GeneralCommandLine() + .withWorkDirectory(project.solutionDirectoryPath.toString()) + .withExePath(NetCoreRuntime.cliPath.value) + .withParameters("tool", "restore") + .createProcess() + .waitFor() + .shouldBe(0) + + buildSelectedProjectsWithReSharperBuild(listOf(lemonadeProviderProject), ignoreReferencesResolve = true) + + withOpenedEditor(project, sourceFileToCheck) { + waitForDaemon() + rdFcsHost.typeProvidersRuntimeVersion.sync(Unit).shouldNotBeNull() + markupAdapter.hasErrors.shouldBeFalse() + } + + withOpenedEditor(project, "LemonadeProvider.DesignTime/LemonadeProvider.DesignTime.fs") { + //change "Drink" -> "Drink1" + typeFromOffset("1", 496) + } + + buildSelectedProjectsWithReSharperBuild(project, listOf(lemonadeProviderProject)) + + withOpenedEditor(project, sourceFileToCheck) { + callAction(restartTypeProvidersAction) + waitForTypeProviders() + waitForDaemon() + markupAdapter.hasErrors.shouldBeTrue() + + //change "Drink" -> "Drink1" + typeFromOffset("1", 86) + waitForDaemon() + markupAdapter.hasErrors.shouldBeFalse() + } + } + } +} diff --git a/rider-fsharp/testData/solutions/LemonadeProvider/.config/dotnet-tools.json b/rider-fsharp/testData/solutions/LemonadeProvider/.config/dotnet-tools.json new file mode 100644 index 0000000000..697889be03 --- /dev/null +++ b/rider-fsharp/testData/solutions/LemonadeProvider/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "paket": { + "version": "5.241.5", + "commands": [ + "paket" + ] + } + } +} \ No newline at end of file diff --git a/rider-fsharp/testData/solutions/LemonadeProvider/.paket/Paket.Restore.targets b/rider-fsharp/testData/solutions/LemonadeProvider/.paket/Paket.Restore.targets new file mode 100644 index 0000000000..dfc4aaec7a --- /dev/null +++ b/rider-fsharp/testData/solutions/LemonadeProvider/.paket/Paket.Restore.targets @@ -0,0 +1,488 @@ + + + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + $(MSBuildVersion) + 15.0.0 + false + true + + true + $(MSBuildThisFileDirectory) + $(MSBuildThisFileDirectory)..\ + $(PaketRootPath)paket-files\paket.restore.cached + $(PaketRootPath)paket.lock + classic + proj + assembly + native + /Library/Frameworks/Mono.framework/Commands/mono + mono + + + $(PaketRootPath)paket.bootstrapper.exe + $(PaketToolsPath)paket.bootstrapper.exe + $([System.IO.Path]::GetDirectoryName("$(PaketBootStrapperExePath)"))\ + + "$(PaketBootStrapperExePath)" + $(MonoPath) --runtime=v4.0.30319 "$(PaketBootStrapperExePath)" + + + True + + + False + + $(BaseIntermediateOutputPath.TrimEnd('\').TrimEnd('\/')) + + + + + + + + + $(PaketRootPath)paket + $(PaketToolsPath)paket + + + + + + $(PaketRootPath)paket.exe + $(PaketToolsPath)paket.exe + + + + + + <_DotnetToolsJson Condition="Exists('$(PaketRootPath)/.config/dotnet-tools.json')">$([System.IO.File]::ReadAllText("$(PaketRootPath)/.config/dotnet-tools.json")) + <_ConfigContainsPaket Condition=" '$(_DotnetToolsJson)' != ''">$(_DotnetToolsJson.Contains('"paket"')) + <_ConfigContainsPaket Condition=" '$(_ConfigContainsPaket)' == ''">false + + + + + + + + + + + <_PaketCommand>dotnet paket + + + + + + $(PaketToolsPath)paket + $(PaketBootStrapperExeDir)paket + + + paket + + + + + <_PaketExeExtension>$([System.IO.Path]::GetExtension("$(PaketExePath)")) + <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(_PaketExeExtension)' == '.dll' ">dotnet "$(PaketExePath)" + <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(OS)' != 'Windows_NT' AND '$(_PaketExeExtension)' == '.exe' ">$(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)" + <_PaketCommand Condition=" '$(_PaketCommand)' == '' ">"$(PaketExePath)" + + + + + + + + + + + + + + + + + + + + + true + $(NoWarn);NU1603;NU1604;NU1605;NU1608 + false + true + + + + + + + + + $([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)')) + + + + + + + $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[0].Replace(`"`, ``).Replace(` `, ``)) + $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[1].Replace(`"`, ``).Replace(` `, ``)) + + + + + %(PaketRestoreCachedKeyValue.Value) + %(PaketRestoreCachedKeyValue.Value) + + + + + true + false + true + + + + + true + + + + + + + + + + + + + + + + + + + $(PaketIntermediateOutputPath)\$(MSBuildProjectFile).paket.references.cached + + $(MSBuildProjectFullPath).paket.references + + $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references + + $(MSBuildProjectDirectory)\paket.references + + false + true + true + references-file-or-cache-not-found + + + + + $([System.IO.File]::ReadAllText('$(PaketReferencesCachedFilePath)')) + $([System.IO.File]::ReadAllText('$(PaketOriginalReferencesFilePath)')) + references-file + false + + + + + false + + + + + true + target-framework '$(TargetFramework)' or '$(TargetFrameworks)' files @(PaketResolvedFilePaths) + + + + + + + + + + + false + true + + + + + + + + + + + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',').Length) + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0]) + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1]) + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[4]) + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5]) + + + %(PaketReferencesFileLinesInfo.PackageVersion) + All + runtime + runtime + true + true + + + + + $(PaketIntermediateOutputPath)/$(MSBuildProjectFile).paket.clitools + + + + + + + + + $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[0]) + $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[1]) + + + %(PaketCliToolFileLinesInfo.PackageVersion) + + + + + + + + + + false + + + + + + <_NuspecFilesNewLocation Include="$(PaketIntermediateOutputPath)\$(Configuration)\*.nuspec"/> + + + + + + $(MSBuildProjectDirectory)/$(MSBuildProjectFile) + true + false + true + false + true + false + true + false + true + $(PaketIntermediateOutputPath)\$(Configuration) + $(PaketIntermediateOutputPath) + + + + <_NuspecFiles Include="$(AdjustedNuspecOutputPath)\*.$(PackageVersion.Split(`+`)[0]).nuspec"/> + + + + + + + + + + + + + + + + + + + + + diff --git a/rider-fsharp/testData/solutions/LemonadeProvider/LemonadeProvider.DesignTime/LemonadeProvider.DesignTime.fs b/rider-fsharp/testData/solutions/LemonadeProvider/LemonadeProvider.DesignTime/LemonadeProvider.DesignTime.fs new file mode 100644 index 0000000000..03e9552667 --- /dev/null +++ b/rider-fsharp/testData/solutions/LemonadeProvider/LemonadeProvider.DesignTime/LemonadeProvider.DesignTime.fs @@ -0,0 +1,19 @@ +module LemonadeProviderImplementation + +open System.Reflection +open FSharp.Core.CompilerServices +open ProviderImplementation.ProvidedTypes + +[] +type LemonadeProvider (config : TypeProviderConfig) as this = + inherit TypeProviderForNamespaces (config, addDefaultProbingLocation=true) + + let ns = "ProvidedNamespace" + + do + let myType = ProvidedTypeDefinition(Assembly.GetExecutingAssembly(), ns, "Lemonade", Some typeof) + myType.AddMember(ProvidedMethod("Drink", [], typeof, isStatic=true)) + this.AddNamespace(ns, [myType]) + +[] +do () diff --git a/rider-fsharp/testData/solutions/LemonadeProvider/LemonadeProvider.DesignTime/LemonadeProvider.DesignTime.fsproj b/rider-fsharp/testData/solutions/LemonadeProvider/LemonadeProvider.DesignTime/LemonadeProvider.DesignTime.fsproj new file mode 100644 index 0000000000..93b4c14e44 --- /dev/null +++ b/rider-fsharp/testData/solutions/LemonadeProvider/LemonadeProvider.DesignTime/LemonadeProvider.DesignTime.fsproj @@ -0,0 +1,24 @@ + + + + + Library + net461 + true + PackageReference + true + + + + True + paket-files/ProvidedTypes.fsi + + + True + paket-files/ProvidedTypes.fs + + + + + + diff --git a/rider-fsharp/testData/solutions/LemonadeProvider/LemonadeProvider.DesignTime/paket.references b/rider-fsharp/testData/solutions/LemonadeProvider/LemonadeProvider.DesignTime/paket.references new file mode 100644 index 0000000000..9d160db053 --- /dev/null +++ b/rider-fsharp/testData/solutions/LemonadeProvider/LemonadeProvider.DesignTime/paket.references @@ -0,0 +1,4 @@ +File:ProvidedTypes.fsi +File:ProvidedTypes.fs + +FSharp.Core diff --git a/rider-fsharp/testData/solutions/LemonadeProvider/LemonadeProvider.sln b/rider-fsharp/testData/solutions/LemonadeProvider/LemonadeProvider.sln new file mode 100644 index 0000000000..95819f80a6 --- /dev/null +++ b/rider-fsharp/testData/solutions/LemonadeProvider/LemonadeProvider.sln @@ -0,0 +1,34 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".paket", ".paket", "{63297B98-5CED-492C-A5B7-A5B4F73CF142}" + ProjectSection(SolutionItems) = preProject + paket.dependencies = paket.dependencies + EndProjectSection +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "LemonadeProvider.DesignTime", "LemonadeProvider.DesignTime\LemonadeProvider.DesignTime.fsproj", "{CF49A75C-998C-423B-BF2D-995FC40B1DEA}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "LemonadeProviderConsumer", "LemonadeProviderConsumer\LemonadeProviderConsumer.fsproj", "{E47ECEEC-F12C-4CE8-9B9B-CE8429B0A5FE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CF49A75C-998C-423B-BF2D-995FC40B1DEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF49A75C-998C-423B-BF2D-995FC40B1DEA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF49A75C-998C-423B-BF2D-995FC40B1DEA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF49A75C-998C-423B-BF2D-995FC40B1DEA}.Release|Any CPU.Build.0 = Release|Any CPU + {E47ECEEC-F12C-4CE8-9B9B-CE8429B0A5FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E47ECEEC-F12C-4CE8-9B9B-CE8429B0A5FE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E47ECEEC-F12C-4CE8-9B9B-CE8429B0A5FE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E47ECEEC-F12C-4CE8-9B9B-CE8429B0A5FE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + EndGlobalSection +EndGlobal diff --git a/rider-fsharp/testData/solutions/LemonadeProvider/LemonadeProviderConsumer/LemonadeProviderConsumer.fsproj b/rider-fsharp/testData/solutions/LemonadeProvider/LemonadeProviderConsumer/LemonadeProviderConsumer.fsproj new file mode 100644 index 0000000000..fee8bf101b --- /dev/null +++ b/rider-fsharp/testData/solutions/LemonadeProvider/LemonadeProviderConsumer/LemonadeProviderConsumer.fsproj @@ -0,0 +1,17 @@ + + + + net461 + + + + + + + + + ..\LemonadeProvider.DesignTime\bin\Debug\net461\LemonadeProvider.DesignTime.dll + + + + diff --git a/rider-fsharp/testData/solutions/LemonadeProvider/LemonadeProviderConsumer/Library.fs b/rider-fsharp/testData/solutions/LemonadeProvider/LemonadeProviderConsumer/Library.fs new file mode 100644 index 0000000000..b83289079d --- /dev/null +++ b/rider-fsharp/testData/solutions/LemonadeProvider/LemonadeProviderConsumer/Library.fs @@ -0,0 +1,3 @@ +module LemonadeProviderConsumer.AfterRebuild + +let t = ProvidedNamespace.Lemonade.Drink() diff --git a/rider-fsharp/testData/solutions/LemonadeProvider/global.json b/rider-fsharp/testData/solutions/LemonadeProvider/global.json new file mode 100644 index 0000000000..6a40f933f6 --- /dev/null +++ b/rider-fsharp/testData/solutions/LemonadeProvider/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "3.1.*" + } +} \ No newline at end of file diff --git a/rider-fsharp/testData/solutions/LemonadeProvider/netfx.props b/rider-fsharp/testData/solutions/LemonadeProvider/netfx.props new file mode 100644 index 0000000000..4ec34f8634 --- /dev/null +++ b/rider-fsharp/testData/solutions/LemonadeProvider/netfx.props @@ -0,0 +1,35 @@ + + + + + + + true + + + /Library/Frameworks/Mono.framework/Versions/Current/lib/mono + /usr/lib/mono + /usr/local/lib/mono + + + $(BaseFrameworkPathOverrideForMono)/4.5-api + $(BaseFrameworkPathOverrideForMono)/4.5.1-api + $(BaseFrameworkPathOverrideForMono)/4.5.2-api + $(BaseFrameworkPathOverrideForMono)/4.6-api + $(BaseFrameworkPathOverrideForMono)/4.6.1-api + $(BaseFrameworkPathOverrideForMono)/4.6.2-api + $(BaseFrameworkPathOverrideForMono)/4.7-api + $(BaseFrameworkPathOverrideForMono)/4.7.1-api + true + + + $(FrameworkPathOverride)/Facades;$(AssemblySearchPaths) + + + + + + + + + \ No newline at end of file diff --git a/rider-fsharp/testData/solutions/LemonadeProvider/paket.dependencies b/rider-fsharp/testData/solutions/LemonadeProvider/paket.dependencies new file mode 100644 index 0000000000..b270c56579 --- /dev/null +++ b/rider-fsharp/testData/solutions/LemonadeProvider/paket.dependencies @@ -0,0 +1,9 @@ +storage: packages +source https://nuget.org/api/v2 + +frameworks: net461 + +nuget FSharp.Core 4.3.4 + +github fsprojects/FSharp.TypeProviders.SDK:f4aca36af04aa84b16ec04df6f6bf55ac2f17a73 src/ProvidedTypes.fsi +github fsprojects/FSharp.TypeProviders.SDK:f4aca36af04aa84b16ec04df6f6bf55ac2f17a73 src/ProvidedTypes.fs diff --git a/rider-fsharp/testData/solutions/LemonadeProvider/paket.lock b/rider-fsharp/testData/solutions/LemonadeProvider/paket.lock new file mode 100644 index 0000000000..eef1613ee9 --- /dev/null +++ b/rider-fsharp/testData/solutions/LemonadeProvider/paket.lock @@ -0,0 +1,9 @@ +STORAGE: PACKAGES +RESTRICTION: == net461 +NUGET + remote: https://www.nuget.org/api/v2 + FSharp.Core (4.3.4) +GITHUB + remote: fsprojects/FSharp.TypeProviders.SDK + src/ProvidedTypes.fs (f4aca36af04aa84b16ec04df6f6bf55ac2f17a73) + src/ProvidedTypes.fsi (f4aca36af04aa84b16ec04df6f6bf55ac2f17a73) \ No newline at end of file