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

Package Management for FSI #5850

Merged
merged 13 commits into from
Sep 12, 2019

Conversation

KevinRansom
Copy link
Member

This is heavily based on the work @forki and @dsyme and others did for this PR: #2483
Thanks guys, that is a genius mechanism.

Notes:

  1. It's a WIP --- there are undoubtedly a ton of bugs, we need to work through the scenarios. I expect there are places where Don and I will have philosophical differences, we usually do :-)

  2. It works on the desktop with fsi.exe and on the coreclr, and in VS. We rely on the dotnet sdk for resolution, essentially we generate a .fsproj and restore it for resolution.

  3. We surface dotnet/msbuild diagnostic messages, using a verbosity filter.

  4. The PM integration point calls the package resolution mechanism with a list of all of the resolution strings specified in the session, which then computes the assemblies to reference.

  5. If resolution fails the last group of references added are removed from the list of resolution strings, after all it made the graph non resolvable. The developer can retype a corrected command.

  6. It is pluggable, the approach and interfaces are very close to the original PR, so I expect Steffen, can rework his paket resolver pretty quick.

  7. ToDo: Currently it doesn't allow a pluggable resolver to be referenced using #r"nuget:" that is my next thing to get working.

  8. This adds a --compilertools: command line option for specifying the path to the root of a nuget package containing a compiler tool, such as a package manager. The package manager is then resolved similarly to type providers, with fsc and fsi computing a run time version and then compatible framework ids to probe for a dll to load.

  9. ToDo: compile single script file doesn't seem to work, since I expected it to … I am slightly confused.

  10. ToDo unify TP and DependencyManager framework detection … probably publicly expose it from FCS.

  11. The Fsharp.Data package works on coreclr and desktop, so this script does what you might expect:

  12. There are some basic tests but many more to do.

How to see it work:
What I do:

With the branch on a machine, build the F# VisualFSharp tools by:

build vs

Run vs by:
devenv /RootSuffix RoslynDev

Create a new script file and start typing:

For example:
Note that openweathermap which is very cool now requires an APIKey, the number of access is quiet limited, so you may get an error back from the message, so, best to grab an api key of your own, then other folk looking at it won't use up your experiment time.

#r "nuget:include=fsharp.data, version=3.0.0"

open FSharp.Data

// Api Key = 93a2637fe6dad8426d128d5289325ca9
open FSharp.Data

type Weather = JsonProvider<"http://api.openweathermap.org/data/2.5/weather?id=2172797&APPID=93a2637fe6dad8426d128d5289325ca9">
let apiUrl = "http://api.openweathermap.org/data/2.5/weather?id=2172797"
let sf = Weather.Load(apiUrl + "&APPID=93a2637fe6dad8426d128d5289325ca9")
let country = sf.Sys.Country
let speed = sf.Wind.Speed
let temp = sf.Main.Temp
printfn "Temp for Australia is {%M}" temp

Note the intellisense and syntax colouring:

image

Select everything and send to interactive:


Microsoft (R) F# Interactive version 10.2.3 for F# 4.5
Copyright (c) Microsoft Corporation. All Rights Reserved.

For help type #help;;

> 
"C:\Program Files (x86)\Microsoft Visual Studio\Preview\Enterprise\MSBuild\Microsoft\VisualStudio\v15.0\FSharp\Microsoft.FSharp.NetSdk.targets" cannot be imported again. It was already imported at "c:\kevinransom\visualfsharp\Tools\dotnet20\sdk\2.1.403\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.FSharpTargetsShim.targets (63,3)". This is most likely a build authoring error. This subsequent import will be ignored. [C:\Users\kevinr\AppData\Local\Temp\uy2ymaj3.jz2\Project.fsproj]
visiting C:\Users\kevinr\AppData\Local\Temp\uy2ymaj3.jz2\Project.fsproj.fsx
yielding source C:\Users\kevinr\AppData\Local\Temp\uy2ymaj3.jz2\Project.fsproj.fsx
[Loading C:\Users\kevinr\AppData\Local\Temp\uy2ymaj3.jz2\Project.fsproj.fsx]
namespace FSI_0002.Project

Temp for Australia is {298.15}
type Weather = FSharp.Data.JsonProvider<...>
val apiUrl : string =
  "http://api.openweathermap.org/data/2.5/weather?id=2172797"
val sf : FSharp.Data.JsonProvider<...>.Root =
  {
  "coord": {
    "lon": 145.77,
    "lat": -16.92
  },
  "weather": [
    {
      "id": 802,
      "main": "Clouds",
      "description": "scattered clouds",
      "icon": "03n"
    }
  ],
  "base": "stations",
  "main": {
    "temp": 298.15,
    "pressure": 1015,
    "humidity": 78,
    "temp_min": 298.15,
    "temp_max": 298.15
  },
  "visibility": 10000,
  "wind": {
    "speed": 3.1,
    "deg": 140
  },
  "clouds": {
    "all": 40
  },
  "dt": 1541012400,
  "sys": {
  ...
val country : string = "AU"
val speed : decimal = 3.1M
val temp : decimal = 298.15M
val it : unit = ()

> 

@KevinRansom
Copy link
Member Author

Since many of you are bound to ask : how to test the coreclr version

This is one way that I use:

  1. in the visualfsharp repo:
    build coreclr
  2. Sync the dotnet cli open source rebuild from here: https://github.com/dotnet/cli
  3. checkout the branch release/2.1.5xx
  4. in the directory: src\tool_fsharp add the file named Nuget.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>  
    <add key="artifacts" value="{path to your visualfsharp repo --- in my case: C:\kevinransom\visualfsharp}\artifacts" />
  </packageSources>
</configuration>
  1. Edit the file : build\DependencyVersions.props
Change this:
    <MicrosoftFSharpCompilerPackageVersion>10.2.3-rtm-181017-0</MicrosoftFSharpCompilerPackageVersion>

To a value that matches the most recent build in your artifacts directory
E.g.
    <MicrosoftFSharpCompilerPackageVersion>10.2.3-rtm-181031-00</MicrosoftFSharpCompilerPackageVersion>

Then type build:

building/whiring and test running will then ensue. If build fails and the failure is not to do with the fsc_tools project, then don't worry, a testable dotnet cli has been created in:

bin\2\win-x64\dotnet


c:\kevinransom\visualfsharp>C:\kevinransom\cli\bin\2\win-x64\dotnet\dotnet.exe C:\kevinransom\cli\bin\2\win-x64\dotnet\sdk\2.2.200-preview-009554\FSharp\fsi.exe

Microsoft (R) F# Interactive version 10.2.3 for F# 4.5
Copyright (c) Microsoft Corporation. All Rights Reserved.

For help type #help;;

> #quit;;

c:\kevinransom\visualfsharp>C:\kevinransom\cli\bin\2\win-x64\dotnet\dotnet.exe C:\kevinransom\cli\bin\2\win-x64\dotnet\sdk\2.2.200-preview-009554\FSharp\fsi.exe

Microsoft (R) F# Interactive version 10.2.3 for F# 4.5
Copyright (c) Microsoft Corporation. All Rights Reserved.

For help type #help;;

> #r "nuget:include=fsharp.data, version=3.0.0"

open FSharp.Data

// Api Key = 93a2637fe6dad8426d128d5289325ca9

open FSharp.Data

type Weather = JsonProvider<"http://api.openweathermap.org/data/2.5/weather?id=2172797&APPID=93a2637fe6dad8426d128d5289325ca9">

let apiUrl = "http://api.openweathermap.org/data/2.5/weather?id=2172797"

let sf = Weather.Load(apiUrl + "&APPID=93a2637fe6dad8426d128d5289325ca9")
let country = sf.Sys.Country
let speed = sf.Wind.Speed
let temp = sf.Main.Temp

printfn "Temp for Australia is {%M}" temp

;;
visiting C:\Users\kevinr\AppData\Local\Temp\t2duao4m.sby\Project.fsproj.fsx
yielding source C:\Users\kevinr\AppData\Local\Temp\t2duao4m.sby\Project.fsproj.fsx
[Loading C:\Users\kevinr\AppData\Local\Temp\t2duao4m.sby\Project.fsproj.fsx]
namespace FSI_0002.Project

Temp for Australia is {298.15}
type Weather = FSharp.Data.JsonProvider<...>
val apiUrl : string =
  "http://api.openweathermap.org/data/2.5/weather?id=2172797"
val sf : FSharp.Data.JsonProvider<...>.Root =
  {
  "coord": {
    "lon": 145.77,
    "lat": -16.92
  },
  "weather": [
    {
      "id": 802,
      "main": "Clouds",
      "description": "scattered clouds",
      "icon": "03n"
    }
  ],
  "base": "stations",
  "main": {
    "temp": 298.15,
    "pressure": 1015,
    "humidity": 78,
    "temp_min": 298.15,
    "temp_max": 298.15
  },
  "visibility": 10000,
  "wind": {
    "speed": 3.6,
    "deg": 140
  },
  "clouds": {
    "all": 40
  },
  "dt": 1541014200,
  "sys": {
  ...
val country : string = "AU"
val speed : decimal = 3.6M
val temp : decimal = 298.15M
val it : unit = ()

> #quit;;

c


src/buildfromsource/FSharp.Compiler.Private/FSComp.resx Outdated Show resolved Hide resolved
@cartermp
Copy link
Contributor

Link #2407

@dsyme
Copy link
Contributor

dsyme commented Oct 31, 2018

I scanned the basic shape of the PR and couldn't find major philosophical differences :) It looks great!

@forki
Copy link
Contributor

forki commented Nov 1, 2018 via email

@dsyme
Copy link
Contributor

dsyme commented Nov 1, 2018

My biggest question is about bootstrapping? How can we make sure F# users have paket access ootb when they are installing dotnet core. This whole thing is only interesting if we can get this to work.

Maybe it can happen, do you have an idea how you might want this to work?

It does feel like Paket may need some bootstrap script, user-global paket install or something.

@forki
Copy link
Contributor

forki commented Nov 1, 2018

There are multiple ways to do it

  1. assume people already have global paket installed
  2. put a bootstrapper into the box
  3. allow to curl paket (or any other #r plugin) with a one line #r directive and resolve in multiple phases

@cartermp
Copy link
Contributor

cartermp commented Nov 1, 2018

I don't think we can put a bootstrapper in the box unless we authored and owned a paket bootstrapper that was subject to all the same requirements that any MS-authored component has.

But in general, since this is pluggable, it stands to reason that any other installed tool could also supply a plugin. That could be paket or fake. But @KevinRansom knows more about how it actually plugs in, and this also needs to be documented. But we're a ways off from this being ship-ready anyways.

@forki
Copy link
Contributor

forki commented Nov 1, 2018 via email

@KevinRansom
Copy link
Member Author

@forki, we will do whatever we must to make a paket packagemanager usable and work well.

Either:
A nuget package containing a paket manager will be reference-able and usable immediately in the same script.

Or we will ship an in-box bootstrapping package manager that will go fetch the paket package manager ideally on first use of #r "paket:blah ...."
or with a #r "compilertool:paket.packagemanagerId, version=1.0.0"
or something

We are committed to make it work and work well. TBH: if paket doesn't work well, Don would have my guts for garters.

Kevin

@cartermp
Copy link
Contributor

cartermp commented Nov 1, 2018

Usability note from today:

You must turn off shadow copying for assemblies in Tools > Options > F# Tools > F# Interactive for this to execute in F# interactive.

That makes things difficult, since unless we can resolve the design to not require that be off, we'll either have a "doesn't work by default" experience or a "break some people" experience.

@cartermp
Copy link
Contributor

cartermp commented Nov 1, 2018

FYI this currently breaks all editor tooling except for a standalone F# script file.

@KevinRansom
Copy link
Member Author

Cab you explain the all editor tooling? Only fsi.exe turns on shadow copy and turning that off doesn't break anything as far as I know. Except of course for FSI now locking referenced assemblies.

@cartermp
Copy link
Contributor

cartermp commented Nov 1, 2018

File > New Project > any F# project

@KevinRansom
Copy link
Member Author

Is this what you are seeing?

if so .fs are not intended to support #r directives.

If the project contains a .fsx file, as the bottom file, then it is expected to work

image

@cartermp
Copy link
Contributor

cartermp commented Nov 1, 2018

No, in the build of master against 15.8.8 with this VSIX installed, no files have language service lightup. What build are you testing against?

@KevinRansom
Copy link
Member Author

Dogfood (15.9 preview 5). Although I can't imagine why 15.8.8 would be a problem. I'll take a look.

@KevinRansom
Copy link
Member Author

Works on my box, I will take a look at your machine tomorrow:

image

validatePackageName v "mscorlib" // Even though it's not a real package.
validatePackageName v "FSharp.Core"
validatePackageName v "System.ValueTuple"
validatePackageName v "NETStandard.Library"
Copy link
Contributor

Choose a reason for hiding this comment

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

We may want to consider Microsoft.NETCore.App and/or whatever gives us access to Span. I suppose that's a bit tricky, but Iactually just saw some requests about how to use Span with FSI, and the only way to do that here would be to reference the relevant bits.

raise (ArgumentException(sprintf "PackageManager can not reference the System Package '%s'" packageName)) // @@@@@@@@@@@@@@@@@@@@@@@ Globalize me please

let mutable loggerVerbosity = ""
let references = [
Copy link
Contributor

@enricosada enricosada Nov 2, 2018

Choose a reason for hiding this comment

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

@KevinRansom parsing the console log to read the dependencies can be improved.

In fsac/ionide/fable etc, to get the fsc args (so similar to this) we invoke an external msbuild (like here), and to do so we use https://github.com/enricosada/dotnet-proj-info/

That lib/tool invoke an external msbuild, but instead of parsing the log use a different strategy.

To the fsproj is added a target file, with a target who run in design mode, and save the items to file (like a csv).

the target is like https://github.com/enricosada/dotnet-proj-info/blob/master/src/dotnet-proj-info/Inspect.fs#L170-L196 so you can directly use that if you want

The trick is good because there is no parsing of log and work with any fsproj/csproj, and you can read everything you want inside the msbuild target (at any msbuild step, with a single invocation) and in the target write the output file as you wish

Some features: get installed .net frameworks, get fsc args from proj or fsx, get props/items, etc. Will use same design time run, like vs, so will not invoke fsc either.
Works the same for.net core fsproj but also for verbose fsproj (with another trick, replace completly projectcracker), but here is not needed.

If you want, can be used as library (like fsac/fable does), so we can reuse the code too, the library (https://www.nuget.org/packages/Dotnet.ProjInfo/) doesnt have any deps, only fsharp.core

Example use as lib: https://github.com/fable-compiler/Fable/blob/master/src/dotnet/Fable.Compiler/CLI/ProjectCoreCracker.fs#L66-L75
fsac usage is more complicated because does navigate the p2p hierachy too, and gather additional info, but here is not needed

happy to chat about it, ihmo can be a nice way to share and improve a common library and strategy.
Really happy to move it somewhere else (like in fsharp/dotnet org) and give you ownership too (plus doing things like strong name/etc if needed).

@enricosada
Copy link
Contributor

i'll check how to bootstrap paket.

@KevinRansom
Copy link
Member Author

@enricosada the mono build shouldn't be under reshaped msbuild. And so this code path is the controlling one which uses msbuild inproc:
https://github.com/Microsoft/visualfsharp/pull/5850/files/0b62b3d6e9d6087d3cfc243d1afe825c5c49e852#diff-c3a4578a1255d318a537ddaee38842e5R256

@cartermp
Copy link
Contributor

cartermp commented Nov 2, 2018

Just confirming that there was something environmental going on my end. This works just fine with 15.8.8.

Some usability notes:

  • Needing to specify the version explicitly, though due to a NuGet limitation, needs to get resolved. nuget: FSharp.Data should always give the latest stable version, for example. It should be similar to dotnet add package.

  • The editor for F# scripts is noticeably slow when using this. Likely a lot of redundant work going on.

  • Need to find a way not to rely on Shadow Copy being off. This is on by default for users, and turning it off would regress current workflows for people. This could end up being a showstopper.

  • Logging done in FSI eventually needs to be cleaned up, but it makes sense it see it now given that we're very early on.

@forki
Copy link
Contributor

forki commented Nov 2, 2018 via email

@KevinRansom
Copy link
Member Author

KevinRansom commented Nov 2, 2018 via email

@matthid
Copy link
Contributor

matthid commented Nov 2, 2018

Technically, this PR is a breaking change. For this to be compatible with fake all we need would be a public api in fcs. Ideally, no extra dll is required in that scenario...

@KevinRansom
Copy link
Member Author

@matthid, what's broken exactly?

@KevinRansom KevinRansom changed the base branch from release/fsharp48 to release/fsharp5 August 9, 2019 17:17
@goswinr
Copy link
Contributor

goswinr commented Oct 25, 2019

I am trying to us this already today. (for my own scripting editor)
I build the fsharp5 branch and tried this in fsiExe.exe:
How can I register a package manager key?

> #r "nuget:include=fsharp.data, version=3.0.0";;

  #r "nuget:include=fsharp.data, version=3.0.0";;
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stdin(1,1): **error FS3216: Package manager key 'nuget' was not registered in** [D:\Git\fsharp\fsharp5\fcs\net461; D:\Git\fsharp\fsharp5\fcs\net461\], []. Currently registered:


  #r "nuget:include=fsharp.data, version=3.0.0";;
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stdin(1,1): error FS3217: Processing of a script fragment has stopped because an exception has been raised

@realvictorprm
Copy link
Contributor

See my tweet here with code example. Syntax should be #r: "nuget: PackageName".

https://twitter.com/realvictorprm/status/1173639408693723136?s=19

@goswinr
Copy link
Contributor

goswinr commented Oct 25, 2019

@realvictorprm thanks! Your code example works. How can I use the FSharp.Compiler.Service.dll ?

I used the one from fsharp\artifacts\bin\fcs\net461 with

    let allArgs = [|"ignored" ; "--langversion:preview" ; "--noninteractive"|] 
    let fsiConfig = FsiEvaluationSession.GetDefaultConfiguration() 
    let fsiSession = FsiEvaluationSession.Create(fsiConfig, allArgs, inStream, textwriter, textwriter)

I still get this error.
error FS3216: Package manager key 'nuget' was not registered
image

@KevinRansom
Copy link
Member Author

@Goswin, fcs need to deploy:
FSharp.DependencyManager.dll alongside the FSharp.Compiler.Private.dll

So use the assemblies built into FSharp.Compiler.Private.Scripting
fsharp\artifacts\bin\FSharp.Compiler.Private.Scripting

I will up0date the fcs build to do the necessary at some point.

@KevinRansom
Copy link
Member Author

@Goswin, on coreclr, you should also use the latest version of FSharp.Data. Earlier versions don't work correctly due to a bug in the typeprovider sdk.

@goswinr
Copy link
Contributor

goswinr commented Oct 25, 2019

@KevinRansom Thank you for you help!
After running the build command I only found fsharp\artifacts\bin\FSharp.Compiler.Private.Scripting\Debug\netstandard2.0
(I assume it is Ok to build net472 apps with those)
there are

FSharp.Compiler.Private.dll
FSharp.Compiler.Private.Scripting.dll
FSharp.Core.dll
FSharp.DependencyManager.dll

Shall i refrence those instead of the FSharp.Compiler.Service.dll ?
Do I also need to use this FSharp.Core.dll instead of the current nuget?

yield! resolveDependencyManagerSources filename
#if DEBUG
for (_,subFile) in sources do
printfn "visiting %s - has subsource of %s " filename subFile
Copy link
Contributor

Choose a reason for hiding this comment

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

This and the other conditional enclosed printfn below fails the "load-test" test when run locally (in debug mode by default), could it be removed from master?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the answer to this is to adjust testing all-up, since these kinds of diagnostics are very important for environments like Jupyter (where this is enabled) to see what went wrong

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe a #if JUPYTER_NOTEBOOK_DEBUG is a good approach then? Not a big issue but wanted to report it may be an overlooked remnant.

The diagnostics could also be incorporated in the dependency manager itself (assuming jupyter will have its own) rather than CompileOps.fs.

Copy link
Contributor

Choose a reason for hiding this comment

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

@KevinRansom @dsyme was pointing at the printf here ^^^

Copy link
Contributor

@smoothdeveloper smoothdeveloper left a comment

Choose a reason for hiding this comment

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

I'm giving this a shot reviving and adjusting the paket implementation (https://github.com/smoothdeveloper/Paket/tree/fsi-paket-dep-manager), this is working 🙂.

@brettfo / @KevinRansom is there a way to pull the version of master branch compiled fsi.exe out of continuous integration with a stable URI so I can work on integrating the tests in the CI pipeline in paket repository with all the tests we initially had in the experimental branch?

None)
|> Seq.filter (fun a -> ReflectionHelper.assemblyHasAttribute a dependencyManagerAttributeName)

let registeredDependencyManagers = ref None
Copy link
Contributor

Choose a reason for hiding this comment

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

This reference is never written to, the assemblies and types seems to currently be scanned on each batch of dependency manager lines evaluated.

yield! resolveDependencyManagerSources filename
#if DEBUG
for (_,subFile) in sources do
printfn "visiting %s - has subsource of %s " filename subFile
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe a #if JUPYTER_NOTEBOOK_DEBUG is a good approach then? Not a big issue but wanted to report it may be an overlooked remnant.

The diagnostics could also be incorporated in the dependency manager itself (assuming jupyter will have its own) rather than CompileOps.fs.

let keyProperty = keyProperty.GetValue >> string

static member InstanceMaker (theType: System.Type, outputDir: string option) =
match ReflectionHelper.getAttributeNamed theType dependencyManagerAttributeName,
Copy link
Contributor

Choose a reason for hiding this comment

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

In the original code, those were nested checks; it looks much better that way, but I believe the first check (attribute on the type) should be handled separately, there are potentially lots of types that will be coming in the assemblies, better to reject them on not having the attribute, then do the remaining checks.

namespace lib"

let generateProjectBody = @"
<Project Sdk='Microsoft.NET.Sdk'>
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like msbuild black belt stuff 😄

with _ -> Seq.empty)
|> Seq.choose (fun path ->
try
Some(AbstractIL.Internal.Library.Shim.FileSystem.AssemblyLoadFrom path)
Copy link
Contributor

Choose a reason for hiding this comment

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

The assemblies seem to be locked, would it be possible/better to shadow copy those?

type internal IDependencyManagerProvider =
abstract Name : string
abstract Key: string
abstract ResolveDependencies : scriptDir: string * mainScriptName: string * scriptName: string * packageManagerTextLines: string seq * tfm: string -> bool * string list * string list
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider replacing the string list with arrays or enumerables. this makes it possible to implement the provider without dependency on FSharp.Core.

This was a concern when I made the initial implementation of the provider interface so the same provider could be loaded from scriptcs (@glennblock), ideally the same interface could just work for C# interactive as well (given they implement similar hooks at some point).

The signature should be documented, it is not obvious that return type is:

  • success: bool
  • load scripts: string []
  • include dirs: string []

yield AppDomain.CurrentDomain.BaseDirectory
])

let enumerateDependencyManagerAssemblies compilerTools m =
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this going to conform to the RFC https://github.com/fsharp/fslang-design/blob/master/tooling/FST-1027-fsi-references.md#handler-resolution or does the RFC need to be adjusted?

@KevinRansom
Copy link
Member Author

@smoothdeveloper, Excellent, now I can see it, thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet