Closed HoraceGonzalez closed 1 month ago
The underlying issue here seems to be that fsi-specific options are being passed to the FSharp Checker in the project options, and fsc fails with the following error:
Unrecognized option: '--readline-'. Use '--help' to learn about recognized command line options.
I was able to workaround the problem locally in AdaptiveServerState.fs
by filtering out --readline-
right before the file it checked:
I'm happy to implement a more robust fix, but wasn't sure what the right way to fix this would be. The idea I had was to remove any fsi-specific options from the OtherOptions
. Might be a bit brittle if new options are added in future releases, though.
Thoughts?
I'm seeing the same issue with Ionide-vim with the same option (fsiExtraParameters
). This is particularly annoying, because --readline-
is necessary when doing interactive development with scripts to work around this issue in dotnet fsi
.
It ends up showing a repeated diagnostic error at the top of the .fsx file:
Thanks for the pokes on this folks - is it known which options are FSI-specific? If that set is stable (ideally it would be some kind of property that the FCS APIs expose?) then I agree that it makes the most sense to have FSAC strip out such arguments before they become an error.
Separately, I think we should be able to 'correct' any diagnostics with negative ranges just to point at the start of the file (meaning position (0, 0)
) so that the LSP client isn't getting invalid data.
Would either of you be open to submitting one or both of these changes?
I'm currently looking through the code and trying to figure out where a property that is named as if it is scoped to only the interpreter (FSIExtraParameters
) ends up getting combined with options for things that are not, in fact, an interactive interpreter. That seems to be the core of the issue.
It appears that this is happening in four methods on the class FSharpCompilerServiceChecker
. Relevant portions excerpted below from src/FsAutoComplete.Core/CompilerServiceInterface.fs.
You'll see that each of these appends the fsi arguments into an array which ends up as part of FSharpChecker
's otherOptions
field. Each of the four methods below does basically the same thing with respect to how they treat these parameters intended only for the FSI interpreter.
I find myself wondering if we should simply remove the references to these FSI parameters from each of the four members. At least in Ionide-vim, there should be no impact, because the plugin invokes dotnet fsi
itself and concatenates on the extra parameters locally in the vim process. There is no interaction I am aware of from Ionide-vim through the fsautocomplete process to the dotnet fsi
process.
I have already burned more time than I should have in digging into this. Some questions:
dotnet fsi
process?FSIExtraParameters
configuration option?Array.append
that is happening in each of the methods below?++ninja edit++: Some grepping around the code base makes it appear to me that all appearances of the string 'fsi' (case insensitive) refer to .fsi files and that none of them have to do with executing an interpreter process.
type FSharpCompilerServiceChecker(hasAnalyzers, typecheckCacheSize, parallelReferenceResolution, useTransparentCompiler)
=
let checker =
FSharpChecker.Create(
projectCacheSize = 200,
keepAssemblyContents = hasAnalyzers,
keepAllBackgroundResolutions = true,
suggestNamesForErrors = true,
keepAllBackgroundSymbolUses = true,
enableBackgroundItemKeyStoreAndSemanticClassification = true,
enablePartialTypeChecking = not hasAnalyzers,
parallelReferenceResolution = parallelReferenceResolution,
captureIdentifiersWhenParsing = true,
useSyntaxTreeCache = true,
useTransparentCompiler = useTransparentCompiler
)
...
member private __.GetNetFxScriptSnapshot(file: string<LocalPath>, source) =
async {
optsLogger.info (
Log.setMessage "Getting NetFX options for script file {file}"
>> Log.addContextDestructured "file" file
)
let allFlags = Array.append [| "--targetprofile:mscorlib" |] fsiAdditionalArguments
let! (opts, errors) =
checker.GetProjectSnapshotFromScript(
UMX.untag file,
source,
assumeDotNetFramework = true,
useFsiAuxLib = true,
otherFlags = allFlags,
userOpName = "getNetFrameworkScriptOptions"
)
...
member private __.GetNetCoreScriptSnapshot(file: string<LocalPath>, source) =
async {
optsLogger.info (
Log.setMessage "Getting NetCore options for script file {file}"
>> Log.addContextDestructured "file" file
)
let allFlags =
Array.append [| "--targetprofile:netstandard" |] fsiAdditionalArguments
let! (snapshot, errors) =
checker.GetProjectSnapshotFromScript(
UMX.untag file,
source,
assumeDotNetFramework = false,
useSdkRefs = true,
useFsiAuxLib = true,
otherFlags = allFlags,
userOpName = "getNetCoreScriptOptions"
)
...
member private __.GetNetFxScriptOptions(file: string<LocalPath>, source) =
async {
optsLogger.info (
Log.setMessage "Getting NetFX options for script file {file}"
>> Log.addContextDestructured "file" file
)
let allFlags = Array.append [| "--targetprofile:mscorlib" |] fsiAdditionalArguments
let! (opts, errors) =
checker.GetProjectOptionsFromScript(
UMX.untag file,
source,
assumeDotNetFramework = true,
useFsiAuxLib = true,
otherFlags = allFlags,
userOpName = "getNetFrameworkScriptOptions"
)
...
member private __.GetNetCoreScriptOptions(file: string<LocalPath>, source) =
async {
optsLogger.info (
Log.setMessage "Getting NetCore options for script file {file}"
>> Log.addContextDestructured "file" file
)
let allFlags =
Array.append [| "--targetprofile:netstandard" |] fsiAdditionalArguments
let! (opts, errors) =
checker.GetProjectOptionsFromScript(
UMX.untag file,
source,
assumeDotNetFramework = false,
useSdkRefs = true,
useFsiAuxLib = true,
otherFlags = allFlags,
userOpName = "getNetCoreScriptOptions"
)
...
Right now editors spawn the FSI instances, yes, but part of the intent of this parameter was to look towards a time when FSAC used the hosted-FSI APIs instead of driving and FSI instances over stdin/stdout communication. This would eliminate a source of mixed-results that exists today, namely that the FSI spawned from your SDK is not using the same analysis as the FSharpChecker analyzing your script inside FSAC.
Some more digging and thinking. The option is a field on FSharpConfig
and FSharpConfigDto
. It is only ever set in two methods, both of which read from a Dto. One for instantiation and one for modification. It seems that this would be the place to strip out the dotnet fsi
command line options that cannot be used with the compiler.
A couple options present themselves to me here:
FromDto
and AddDto
to strip out dotnet fsi
-only options.dotnet fsi
and the compiler. Delegate dotnet fsi
-only options to the editor, where the editor configuration can take an additional, separate configuration (that is never sent to FSAC) for how to invoke dotnet fsi
.Question: do the hosted FSI APIs take similar parameters as the command line dotnet fsi
?
Here's a quick command to get just the options for dotnet fsi
. I'm not sure how to get the same for the compiler. (command and sample output at end). This list is not immediately usable for direct string comparisons, as many options, as shown, would expand into multiple valid options. For example, the line below --realine[+|-]
could expand to each of --readline-
and readline+
. Others would properly be a regex or some other pattern match, rather than a literal equality test.
$ dotnet fsi --help | awk '/^-/ {print $1}'
--use:<file>
--load:<file>
--reference:<file>
--compilertool:<file>
--usesdkrefs[+|-]
--
--debug[+|-]
--debug:{full|pdbonly|portable|embedded}
--optimize[+|-]
--tailcalls[+|-]
--deterministic[+|-]
--pathmap:<path=sourcePath;...>
--crossoptimize[+|-]
--reflectionfree
--warnaserror[+|-]
--warnaserror[+|-]:<warn;...>
--warn:<n>
--nowarn:<warn;...>
--warnon:<warn;...>
--consolecolors[+|-]
--langversion:?
--langversion:{version|latest|preview}
--checked[+|-]
--define:<string>
--mlcompatibility
--strict-indentation[+|-]
--nologo
--version
--help
--codepage:<n>
--utf8output
--preferreduilang:<string>
--fullpaths
--lib:<dir;...>
--simpleresolution
--targetprofile:<string>
--clearResultsCache
--exec
--gui[+|-]
--quiet
--readline[+|-]
--quotations-debug[+|-]
--shadowcopyreferences[+|-]
--multiemit[+|-]
The FSI hosting APIs do take the command line arguments directly, see the API reference and tutorial for some examples there, so we would need those in that case.
So I think that gets us to a design decision for implementing a fix here. I lean toward 1.2
FSIExtraParameters
into two fields: FSIExtraInteractiveParameters
and FSIExtraSharedParameters
. The configuration is such that FSIExtraInteractiveParameters
is only used for interactive things and FSIExtraSharedParameters
is used for both interactive, and when passing on parameters to compiler/checker. Today, FSIExtraInteractiveParameters
is useless. Therefore:
FSIExtraInteractiveParameters
today. Let the editors deal with this on their ownI think I can take on any of these, but would prefer input from @baronfel or another maintainer on the choice and any specifics of implementation that matter.
Thanks for laying out some plans and thoughts! 1.2 is perfectly agreeable by me, and I'd love to review and merge a PR implementing it if you feel so inclined.
Linking for reference here, and will include in documentation as part of PR:
The page for interactive options helpfully lists whether an option is shared by the compiler as well. I expect this will be a useful doc reference for instructing users how to configure things.
@baronfel , PR is raised. I am happy to take any feedback you may have.
I've tried to capture and consolidate the discussion here, and also make clear the way that this is intended to address current use cases and grow cleanly into the future where FSAC learns to manage the interactive interpreter.
look towards a time when FSAC used the hosted-FSI APIs instead of driving and FSI instances over stdin/stdout
Oh, interesting.
@baronfel , I just wanted to say thanks for your thoughtful and prompt feedback on this. This was definitely a great first contribution experience for me (:
Keep it up!
Glad to hear it! It's very motivating from my end when someone reports an issue with great detail and is gung-ho about making a quality contribution as well.
Version
ionide.vscode v7.16.1, fsac 0.68.0
Dotnet Info
.NET SDK: Version: 7.0.400 Commit: 73bf45718d
Runtime Environment: OS Name: Mac OS X OS Version: 12.3 OS Platform: Darwin RID: osx.12-arm64 Base Path: /Users/horacegonzalez/.dotnet/sdk/7.0.400/
Host: Version: 8.0.0 Architecture: arm64 Commit: 5535e31a71
.NET SDKs installed: 6.0.108 [/Users/horacegonzalez/.dotnet/sdk] 6.0.300 [/Users/horacegonzalez/.dotnet/sdk] 6.0.303 [/Users/horacegonzalez/.dotnet/sdk] 6.0.400 [/Users/horacegonzalez/.dotnet/sdk] 6.0.408 [/Users/horacegonzalez/.dotnet/sdk] 7.0.201 [/Users/horacegonzalez/.dotnet/sdk] 7.0.400 [/Users/horacegonzalez/.dotnet/sdk] 8.0.100-preview.3.23178.7 [/Users/horacegonzalez/.dotnet/sdk] 8.0.100-rc.2.23502.2 [/Users/horacegonzalez/.dotnet/sdk] 8.0.100 [/Users/horacegonzalez/.dotnet/sdk]
.NET runtimes installed: Microsoft.AspNetCore.App 6.0.5 [/Users/horacegonzalez/.dotnet/shared/Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 6.0.8 [/Users/horacegonzalez/.dotnet/shared/Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 6.0.16 [/Users/horacegonzalez/.dotnet/shared/Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 7.0.3 [/Users/horacegonzalez/.dotnet/shared/Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 7.0.10 [/Users/horacegonzalez/.dotnet/shared/Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 8.0.0-preview.3.23177.8 [/Users/horacegonzalez/.dotnet/shared/Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 8.0.0-rc.2.23480.2 [/Users/horacegonzalez/.dotnet/shared/Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 8.0.0 [/Users/horacegonzalez/.dotnet/shared/Microsoft.AspNetCore.App] Microsoft.NETCore.App 6.0.5 [/Users/horacegonzalez/.dotnet/shared/Microsoft.NETCore.App] Microsoft.NETCore.App 6.0.8 [/Users/horacegonzalez/.dotnet/shared/Microsoft.NETCore.App] Microsoft.NETCore.App 6.0.16 [/Users/horacegonzalez/.dotnet/shared/Microsoft.NETCore.App] Microsoft.NETCore.App 7.0.3 [/Users/horacegonzalez/.dotnet/shared/Microsoft.NETCore.App] Microsoft.NETCore.App 7.0.10 [/Users/horacegonzalez/.dotnet/shared/Microsoft.NETCore.App] Microsoft.NETCore.App 8.0.0-preview.3.23174.8 [/Users/horacegonzalez/.dotnet/shared/Microsoft.NETCore.App] Microsoft.NETCore.App 8.0.0-rc.2.23479.6 [/Users/horacegonzalez/.dotnet/shared/Microsoft.NETCore.App] Microsoft.NETCore.App 8.0.0 [/Users/horacegonzalez/.dotnet/shared/Microsoft.NETCore.App]
Other architectures found: x64 [/usr/local/share/dotnet/x64] registered at [/etc/dotnet/install_location_x64]
Environment variables: DOTNET_ROOT [/Users/horacegonzalez/.dotnet]
global.json file: /Users/horacegonzalez/code/FsAutoComplete/global.json
Learn more: https://aka.ms/dotnet/info
Download .NET: https://aka.ms/dotnet/download
Steps to reproduce
temp
(anywhere is fine)temp/test.fsx
file with the following text:temp/.vscode/settings.json
file with the following text:test.fsx
Details
Expected Behavior
Actual Behavior
Logs
Here's the notification from FSAC that's causing the issue:
Notice the
range.start.line: -1
andrange.end.line: -1
. This causes the LSP client to throw an error because the range is invalid: https://github.com/microsoft/vscode-languageserver-node/blob/02806427ce7251ec8fa2ff068febd9a9e59dbd2f/client/src/common/protocolConverter.ts#L400This results in the second
The type 'string' is not compatible with any of the types ...
error not being processed.Checklist