diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index 965957dbc0c..785b6df131a 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -75,6 +75,7 @@ ([PR #19724](https://github.com/dotnet/fsharp/pull/19724)) * Emit debug points at a stack-empty position ([PR #19877](https://github.com/dotnet/fsharp/pull/19877)) * Fix spurious XmlDoc warnings (unknown parameter / no documentation for parameter) under `--warnon:3390` when a get/set property documents the full parameter set across both accessors. ([Issue #13684](https://github.com/dotnet/fsharp/issues/13684), [PR #19884](https://github.com/dotnet/fsharp/pull/19884)) +* Stop F# Interactive from mutating script arguments that follow `--`. Abbreviated flags like `-d`, `-r`, `-I` after the `--` separator are no longer colon-joined with their next token in `fsi.CommandLineArgs`. ([Issue #10819](https://github.com/dotnet/fsharp/issues/10819), [PR #19926](https://github.com/dotnet/fsharp/pull/19926)) ### Added diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index 30801263752..cdc6cb0c56d 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -1225,7 +1225,27 @@ type internal FsiCommandLineOptions(fsi: FsiEvaluationSessionHostConfig, argv: s @ fsiUsageSuffix tcConfigB let abbrevArgs = GetAbbrevFlagSet tcConfigB false - ParseCompilerOptions(collect, fsiCompilerOptions, List.tail (PostProcessCompilerArgs abbrevArgs argv)) + // PostProcessCompilerArgs rewrites abbreviated flags like `-d 5` to `-d:5`. + // We must NOT apply that rewrite to user-script arguments that follow `--`, + // otherwise fsi.CommandLineArgs leaks the rewrite to scripts (see #10819). + // Split argv at the first `--`, post-process only the compiler-args prefix, + // and pass the suffix (including the `--` separator itself) through unmodified. + // Keeping `--` in the suffix preserves both downstream handlers: + // * with a script preceding `--`, the OptionGeneral IsScript handler captures + // the suffix as script args (matching pre-fix behaviour for fsi.CommandLineArgs); + // * with no script (e.g. `dotnet fsi -- -d 5`), the `--` token reaches + // ParseCompilerOptions and fires its OptionRest recordExplicitArg handler, + // so `-d` and `5` are captured as explicit args instead of being parsed + // as compiler options. + let processedArgs = + match Array.tryFindIndex (fun (a: string) -> a = "--") argv with + | Some idx -> + let prefix = argv[0 .. idx - 1] + let suffix = argv[idx..] + PostProcessCompilerArgs abbrevArgs prefix @ List.ofArray suffix + | None -> PostProcessCompilerArgs abbrevArgs argv + + ParseCompilerOptions(collect, fsiCompilerOptions, List.tail processedArgs) with e -> stopProcessingRecovery e range0 failwithf "Error creating evaluation session: %A" e diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsi/FsiCommandLineArgsTests.fs b/tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsi/FsiCommandLineArgsTests.fs new file mode 100644 index 00000000000..10754e1684c --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsi/FsiCommandLineArgsTests.fs @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace CompilerOptions.Fsi + +open System +open System.IO +open Xunit +open FSharp.Test +open FSharp.Test.Compiler + +/// Regression tests for https://github.com/dotnet/fsharp/issues/10819 +/// fsi.CommandLineArgs must preserve every token after `--` byte-for-byte; +/// in particular abbreviated flags like -d / -r / -I that follow `--` must +/// NOT be colon-joined with their next argument. +module FsiCommandLineArgsTests = + + let private writeProbeScript () : string = + // Prints one ARG= line per element of fsi.CommandLineArgs so the + // test can parse exact contents from stdout. A leading empty `printfn` + // ensures the first ARG= line is never prefixed by an FSI `> ` prompt + // (which only matters when the probe runs interactively via --use, + // not when it is invoked as the script-file argument directly). + let body = """ +printfn "" +for a in fsi.CommandLineArgs do + printfn "ARG=%s" a +""" + let path = + Path.Combine( + Path.GetTempPath(), + sprintf "fsi_cmdline_%s.fsx" (Guid.NewGuid().ToString("N"))) + File.WriteAllText(path, body) + path + + let private parseArgsFromStdOut (stdout: string) : string list = + stdout.Split([| '\r'; '\n' |], StringSplitOptions.RemoveEmptyEntries) + |> Array.choose (fun line -> + if line.StartsWith("ARG=") then Some (line.Substring(4)) else None) + |> Array.toList + + /// Run fsi