Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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
Expand Down
12 changes: 11 additions & 1 deletion ReSharper.FSharp/src/FSharp.Common/src/Settings/FSharpOptions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ module FSharpExperimentalFeatures =
let [<Literal>] formatter = "Enable F# code formatter"
let [<Literal>] fsiInteractiveEditor = "Enable analysis of F# Interactive editor (experimental)"
let [<Literal>] outOfProcessTypeProviders = "Host type providers out-of-process (solution reload required)"
let [<Literal>] hostTypeProvidersFromTempFolder = "Host type providers from Temp folder"


[<SettingsKey(typeof<FSharpOptions>, "F# experimental features")>]
Expand All @@ -88,7 +89,10 @@ type FSharpExperimentalFeatures =
mutable FsiInteractiveEditor: bool

[<SettingsEntry(true, FSharpExperimentalFeatures.outOfProcessTypeProviders)>]
mutable OutOfProcessTypeProviders: bool }
mutable OutOfProcessTypeProviders: bool

[<SettingsEntry(false, FSharpExperimentalFeatures.hostTypeProvidersFromTempFolder)>]
mutable HostTypeProvidersFromTempFolder: bool }


[<AllowNullLiteral>]
Expand Down Expand Up @@ -123,6 +127,7 @@ type FSharpExperimentalFeaturesProvider(lifetime, solution, settings, settingsSc
member val RedundantParensAnalysis = base.GetValueProperty<bool>("RedundantParensAnalysis")
member val Formatter = base.GetValueProperty<bool>("Formatter")
member val OutOfProcessTypeProviders = base.GetValueProperty<bool>("OutOfProcessTypeProviders")
member val HostTypeProvidersFromTempFolder = base.GetValueProperty<bool>("HostTypeProvidersFromTempFolder")


[<SolutionInstanceComponent>]
Expand Down Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

[<SolutionComponent>]
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 [<VolatileField>] mutable connection: TypeProvidersConnection = null
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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]))
[]
Expand Down Expand Up @@ -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()
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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() =
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to use outputAssemblyPath since multiple projects can have the same resolutionFolder

typeProvidersPerAssembly.Keys
|> Seq.map snd
|> Seq.distinct
|> Seq.map FileSystemPath.Parse
|> HashSet

member x.Dump() =
let typeProviders =
proxyTypeProvidersPerId
Expand All @@ -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<FileSystemPath>
abstract member Dump: unit -> string

type TypeProvidersManager(connection: TypeProvidersConnection) =
Expand All @@ -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
Expand All @@ -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 ->
Expand All @@ -117,3 +128,6 @@ type TypeProvidersManager(connection: TypeProvidersConnection) =

member this.Dump() =
$"{typeProviders.Dump()}\n\n{tpContext.Dump()}"

member this.GetAssemblies() =
typeProviders.GetProvidersLocations()
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> OurAdditionalProbingPaths = new List<string>();

static RiderPluginAssemblyResolver()
static TypeProvidersAssemblyResolver()
{
var paths = Environment.GetEnvironmentVariable(AdditionalProbingPathsEnvVar);
if (string.IsNullOrWhiteSpace(paths)) return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public IProvidedRdModelsCreator<CustomAttributeData, RdCustomAttributeData>
public TypeProvidersContext(ILogger logger)
{
Logger = logger;
TypeProvidersLoader = new TypeProvidersLoader();
TypeProvidersLoader = new TypeProvidersLoader(logger);

TypeProvidersCache = new TypeProvidersCache();
ProvidedTypesCache = new ProvidedTypesCache(ProvidedTypesComparer.Instance);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,50 @@
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;
using Range = FSharp.Compiler.Text.Range;

namespace JetBrains.ReSharper.Plugins.FSharp.TypeProviders.Host
{
public interface ITypeProvidersLoader
public interface ITypeProvidersLoader : IDisposable
{
IEnumerable<ITypeProvider> InstantiateTypeProvidersOfAssembly(
InstantiateTypeProvidersOfAssemblyParameters parameters);
}

public class TypeProvidersLoader : ITypeProvidersLoader
{
private readonly ILogger myLogger;
private readonly Dictionary<string, string> myShadowCopyMapping = new Dictionary<string, string>();

public TypeProvidersLoader(ILogger logger) => myLogger = logger;

private readonly FSharpFunc<TypeProviderError, Unit> myLogError =
FSharpFunc<TypeProviderError, Unit>.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<ITypeProvider> InstantiateTypeProvidersOfAssembly(
InstantiateTypeProvidersOfAssemblyParameters parameters)
{
Expand All @@ -32,11 +53,24 @@ public IEnumerable<ITypeProvider> 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);
}
}
}
5 changes: 5 additions & 0 deletions ReSharper.FSharp/test/src/FSharp.Tests.Host/FSharpTestHost.fs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ type FSharpTestHost(solution: ISolution, sourceCache: FSharpSourceCache, itemsCo
CultureInfo.CurrentUICulture <- newCulture
currentCulture.Name

let killTypeProvidersProcess _ =
solution.GetComponent<IProxyExtensionTypingProvider>().TerminateConnection()
JetBrains.Core.Unit.Instance

do
let fsTestHost = solution.RdFSharpModel().FsharpTestHost

Expand All @@ -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)
11 changes: 9 additions & 2 deletions rider-fsharp/protocol/src/kotlin/model/RdFSharpModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -60,6 +66,7 @@ object RdFSharpModel : Ext(SolutionModel.Solution) {

field("fSharpInteractiveHost", RdFSharpInteractiveHost)
field("fsharpTestHost", RdFSharpTestHost)
field("fSharpTypeProvidersHost", RdFSharpTypeProvidersHost)
property("fcsBusyDelayMs", int)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down Expand Up @@ -144,6 +143,7 @@ object RdFSharpTypeProvidersModel : Root() {
field("SystemRuntimeAssemblyVersion", string)
field("CompilerToolsPath", array(string))
field("FakeTcImports", RdFakeTcImports)
field("ShadowCopyDesignTimeAssembly", bool)
}

private val RdFakeDllInfo = structdef {
Expand Down
Loading