From 315a42375011a9465ee04f422026c6de5470ae22 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Dec 2025 17:18:33 +0000 Subject: [PATCH 1/8] Initial plan From 82501b8ff986abcd5b04f7055729d50f7a408822 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Dec 2025 17:49:00 +0000 Subject: [PATCH 2/8] Implement preferreduilang switch filtering in FSI script args Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com> --- src/Compiler/Interactive/fsi.fs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index ca96324426..744d067c0f 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -1068,8 +1068,29 @@ type internal FsiCommandLineOptions(fsi: FsiEvaluationSessionHostConfig, argv: s (fun args -> let scriptFile = args[0] let scriptArgs = List.tail args + + // Filter out and process preferreduilang from script args + let rec filterScriptArgs (args: string list) = + match args with + | [] -> [] + | (arg: string) :: rest when + arg.StartsWith("--preferreduilang:", StringComparison.OrdinalIgnoreCase) || + arg.StartsWith("/preferreduilang:", StringComparison.OrdinalIgnoreCase) -> + // Extract culture and set it + let culture = arg.Substring(arg.IndexOf(':') + 1) + try + tcConfigB.preferredUiLang <- Some culture + Thread.CurrentThread.CurrentUICulture <- CultureInfo(culture) + with _ -> + // Ignore invalid culture, just don't set it + () + filterScriptArgs rest + | arg :: rest -> arg :: filterScriptArgs rest + + let filteredScriptArgs = filterScriptArgs scriptArgs + inputFilesAcc <- inputFilesAcc @ [ (scriptFile, true) ] (* record script.fsx for evaluation *) - List.iter recordExplicitArg scriptArgs (* record rest of line as explicit arguments *) + List.iter recordExplicitArg filteredScriptArgs (* record rest of line as explicit arguments *) tcConfigB.noFeedback <- true (* "quiet", no banners responses etc *) interact <- false (* --exec, exit after eval *) [] (* no arguments passed on, all consumed here *) From b1d162381b7a1c3cd63cc8dd2b1d6492724072d1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Dec 2025 18:00:59 +0000 Subject: [PATCH 3/8] Add regression tests for preferreduilang switch in FSI Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com> --- .../FSharp.Compiler.ComponentTests.fsproj | 1 + .../Scripting/PreferredUiLangTests.fs | 147 ++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 tests/FSharp.Compiler.ComponentTests/Scripting/PreferredUiLangTests.fs diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 8f5ace7aad..5dac9eb8fa 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -277,6 +277,7 @@ + diff --git a/tests/FSharp.Compiler.ComponentTests/Scripting/PreferredUiLangTests.fs b/tests/FSharp.Compiler.ComponentTests/Scripting/PreferredUiLangTests.fs new file mode 100644 index 0000000000..63fbfb3df7 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Scripting/PreferredUiLangTests.fs @@ -0,0 +1,147 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Scripting + +open Xunit +open System +open System.IO +open FSharp.Test +open FSharp.Test.CompilerAssertHelpers +open FSharp.Compiler.Interactive.Shell +open FSharp.Compiler.Diagnostics + +module ``PreferredUiLang tests`` = + + [] + let ``preferreduilang switch before script is consumed from CommandLineArgs``() = + let scriptContent = """ +printfn "Args: %A" fsi.CommandLineArgs +if fsi.CommandLineArgs.Length <> 2 then exit 1 +if fsi.CommandLineArgs.[0] <> __SOURCE_FILE__ then exit 1 +if fsi.CommandLineArgs.[1] <> "arg1" then exit 1 +exit 0 +""" + let tmpFile = Path.GetTempFileName() + ".fsx" + try + File.WriteAllText(tmpFile, scriptContent) + let errors, _, _ = + CompilerAssert.RunScriptWithOptionsAndReturnResult + [| "--preferreduilang:es-ES"; tmpFile; "arg1" |] + "" + + // Should succeed (exit 0) + Assert.True((errors: ResizeArray).Count = 0, sprintf "Expected no errors, got: %A" errors) + finally + if File.Exists(tmpFile) then File.Delete(tmpFile) + + [] + let ``preferreduilang switch after script is consumed from CommandLineArgs``() = + let scriptContent = """ +printfn "Args: %A" fsi.CommandLineArgs +if fsi.CommandLineArgs.Length <> 2 then exit 1 +if fsi.CommandLineArgs.[0] <> __SOURCE_FILE__ then exit 1 +if fsi.CommandLineArgs.[1] <> "arg1" then exit 1 +exit 0 +""" + let tmpFile = Path.GetTempFileName() + ".fsx" + try + File.WriteAllText(tmpFile, scriptContent) + let errors, _, _ = + CompilerAssert.RunScriptWithOptionsAndReturnResult + [| tmpFile; "--preferreduilang:es-ES"; "arg1" |] + "" + + // Should succeed (exit 0) + Assert.True((errors: ResizeArray).Count = 0, sprintf "Expected no errors, got: %A" errors) + finally + if File.Exists(tmpFile) then File.Delete(tmpFile) + + [] + let ``preferreduilang with slash form is consumed from CommandLineArgs``() = + let scriptContent = """ +printfn "Args: %A" fsi.CommandLineArgs +if fsi.CommandLineArgs.Length <> 2 then exit 1 +if fsi.CommandLineArgs.[0] <> __SOURCE_FILE__ then exit 1 +if fsi.CommandLineArgs.[1] <> "arg1" then exit 1 +exit 0 +""" + let tmpFile = Path.GetTempFileName() + ".fsx" + try + File.WriteAllText(tmpFile, scriptContent) + let errors, _, _ = + CompilerAssert.RunScriptWithOptionsAndReturnResult + [| tmpFile; "/preferreduilang:de-DE"; "arg1" |] + "" + + // Should succeed (exit 0) + Assert.True((errors: ResizeArray).Count = 0, sprintf "Expected no errors, got: %A" errors) + finally + if File.Exists(tmpFile) then File.Delete(tmpFile) + + [] + let ``preferreduilang sets CurrentUICulture correctly``() = + let scriptContent = """ +let culture = System.Threading.Thread.CurrentThread.CurrentUICulture.Name +printfn "Culture: %s" culture +if not (culture.StartsWith("fr-FR")) then + printfn "Expected culture starting with fr-FR, got: %s" culture + exit 1 +exit 0 +""" + let tmpFile = Path.GetTempFileName() + ".fsx" + try + File.WriteAllText(tmpFile, scriptContent) + let errors, _, _ = + CompilerAssert.RunScriptWithOptionsAndReturnResult + [| "--preferreduilang:fr-FR"; tmpFile |] + "" + + // Should succeed (exit 0) + Assert.True((errors: ResizeArray).Count = 0, sprintf "Expected no errors, got: %A" errors) + finally + if File.Exists(tmpFile) then File.Delete(tmpFile) + + [] + let ``preferreduilang after script also sets culture``() = + let scriptContent = """ +let culture = System.Threading.Thread.CurrentThread.CurrentUICulture.Name +printfn "Culture: %s" culture +if not (culture.StartsWith("ja-JP")) then + printfn "Expected culture starting with ja-JP, got: %s" culture + exit 1 +exit 0 +""" + let tmpFile = Path.GetTempFileName() + ".fsx" + try + File.WriteAllText(tmpFile, scriptContent) + let errors, _, _ = + CompilerAssert.RunScriptWithOptionsAndReturnResult + [| tmpFile; "--preferreduilang:ja-JP" |] + "" + + // Should succeed (exit 0) + Assert.True((errors: ResizeArray).Count = 0, sprintf "Expected no errors, got: %A" errors) + finally + if File.Exists(tmpFile) then File.Delete(tmpFile) + + [] + let ``invalid culture in preferreduilang is ignored gracefully``() = + let scriptContent = """ +printfn "Args: %A" fsi.CommandLineArgs +if fsi.CommandLineArgs.Length <> 2 then exit 1 +if fsi.CommandLineArgs.[0] <> __SOURCE_FILE__ then exit 1 +if fsi.CommandLineArgs.[1] <> "arg1" then exit 1 +exit 0 +""" + let tmpFile = Path.GetTempFileName() + ".fsx" + try + File.WriteAllText(tmpFile, scriptContent) + let errors, _, _ = + CompilerAssert.RunScriptWithOptionsAndReturnResult + [| tmpFile; "--preferreduilang:invalid-culture-xyz"; "arg1" |] + "" + + // Should succeed - invalid culture is ignored, but switch is still consumed + Assert.True((errors: ResizeArray).Count = 0, sprintf "Expected no errors, got: %A" errors) + finally + if File.Exists(tmpFile) then File.Delete(tmpFile) From 47d5e895c3afa7e095de2975b3d993de4a2f5e59 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Dec 2025 18:07:20 +0000 Subject: [PATCH 4/8] Format code with fantomas Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com> --- src/Compiler/Interactive/fsi.fs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index 744d067c0f..b193e7bca4 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -1068,27 +1068,30 @@ type internal FsiCommandLineOptions(fsi: FsiEvaluationSessionHostConfig, argv: s (fun args -> let scriptFile = args[0] let scriptArgs = List.tail args - + // Filter out and process preferreduilang from script args let rec filterScriptArgs (args: string list) = match args with | [] -> [] - | (arg: string) :: rest when - arg.StartsWith("--preferreduilang:", StringComparison.OrdinalIgnoreCase) || - arg.StartsWith("/preferreduilang:", StringComparison.OrdinalIgnoreCase) -> + | (arg: string) :: rest when + arg.StartsWith("--preferreduilang:", StringComparison.OrdinalIgnoreCase) + || arg.StartsWith("/preferreduilang:", StringComparison.OrdinalIgnoreCase) + -> // Extract culture and set it let culture = arg.Substring(arg.IndexOf(':') + 1) + try tcConfigB.preferredUiLang <- Some culture Thread.CurrentThread.CurrentUICulture <- CultureInfo(culture) with _ -> // Ignore invalid culture, just don't set it () + filterScriptArgs rest | arg :: rest -> arg :: filterScriptArgs rest - + let filteredScriptArgs = filterScriptArgs scriptArgs - + inputFilesAcc <- inputFilesAcc @ [ (scriptFile, true) ] (* record script.fsx for evaluation *) List.iter recordExplicitArg filteredScriptArgs (* record rest of line as explicit arguments *) tcConfigB.noFeedback <- true (* "quiet", no banners responses etc *) From f40ffbb3f5e28f6ae2d1cd862b91faf20a69c6a7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Dec 2025 18:13:57 +0000 Subject: [PATCH 5/8] Fix preferreduilang switch leaking into fsi.CommandLineArgs Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com> --- src/Compiler/Interactive/fsi.fs | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index b193e7bca4..9e8551293a 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -1070,22 +1070,28 @@ type internal FsiCommandLineOptions(fsi: FsiEvaluationSessionHostConfig, argv: s let scriptArgs = List.tail args // Filter out and process preferreduilang from script args + let isPreferredUiLangArg (arg: string) = + arg.StartsWith("--preferreduilang:", StringComparison.OrdinalIgnoreCase) + || arg.StartsWith("/preferreduilang:", StringComparison.OrdinalIgnoreCase) + let rec filterScriptArgs (args: string list) = match args with | [] -> [] - | (arg: string) :: rest when - arg.StartsWith("--preferreduilang:", StringComparison.OrdinalIgnoreCase) - || arg.StartsWith("/preferreduilang:", StringComparison.OrdinalIgnoreCase) - -> + | (arg: string) :: rest when isPreferredUiLangArg arg -> // Extract culture and set it - let culture = arg.Substring(arg.IndexOf(':') + 1) - - try - tcConfigB.preferredUiLang <- Some culture - Thread.CurrentThread.CurrentUICulture <- CultureInfo(culture) - with _ -> - // Ignore invalid culture, just don't set it - () + let colonIndex = arg.IndexOf(':') + + if colonIndex >= 0 && colonIndex < arg.Length - 1 then + let culture = arg.Substring(colonIndex + 1) + + try + tcConfigB.preferredUiLang <- Some culture + Thread.CurrentThread.CurrentUICulture <- CultureInfo(culture) + with + | :? CultureNotFoundException + | :? ArgumentException -> + // Ignore invalid culture, just don't set it + () filterScriptArgs rest | arg :: rest -> arg :: filterScriptArgs rest From 328b7b35a7f38932971dd8987fdeb7489a129afa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Dec 2025 08:36:52 +0000 Subject: [PATCH 6/8] Fix invalid culture exception by validating before setting tcConfigB Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com> --- src/Compiler/Interactive/fsi.fs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index 9e8551293a..78f9e572d1 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -1085,8 +1085,11 @@ type internal FsiCommandLineOptions(fsi: FsiEvaluationSessionHostConfig, argv: s let culture = arg.Substring(colonIndex + 1) try + // Validate culture first by creating CultureInfo + let cultureInfo = CultureInfo(culture) + // Only set if valid tcConfigB.preferredUiLang <- Some culture - Thread.CurrentThread.CurrentUICulture <- CultureInfo(culture) + Thread.CurrentThread.CurrentUICulture <- cultureInfo with | :? CultureNotFoundException | :? ArgumentException -> From 6e922805afdcb4b747bb3507add7d5de270a844a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:08:31 +0000 Subject: [PATCH 7/8] Add release notes for preferreduilang fix Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com> --- docs/release-notes/.FSharp.Compiler.Service/10.0.200.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md b/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md index b647952d09..ccb62137c0 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md +++ b/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md @@ -1,6 +1,7 @@ ### Fixed * Type relations cache: optimize key generation ([Issue #19116](https://github.com/dotnet/fsharp/issues/18767)) ([PR #19120](https://github.com/dotnet/fsharp/pull/19120)) +* Fix `--preferreduilang` switch leaking into `fsi.CommandLineArgs` when positioned after script file ([PR #19171](https://github.com/dotnet/fsharp/pull/19171)) ### Added From 8c3ed3fe4ec250520b734981223e5253ebdd9098 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Thu, 18 Dec 2025 15:05:59 +0100 Subject: [PATCH 8/8] Update docs/release-notes/.FSharp.Compiler.Service/10.0.200.md --- docs/release-notes/.FSharp.Compiler.Service/10.0.200.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md b/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md index ccb62137c0..a5b9fe2ed7 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md +++ b/docs/release-notes/.FSharp.Compiler.Service/10.0.200.md @@ -1,7 +1,7 @@ ### Fixed * Type relations cache: optimize key generation ([Issue #19116](https://github.com/dotnet/fsharp/issues/18767)) ([PR #19120](https://github.com/dotnet/fsharp/pull/19120)) -* Fix `--preferreduilang` switch leaking into `fsi.CommandLineArgs` when positioned after script file ([PR #19171](https://github.com/dotnet/fsharp/pull/19171)) +* Fix `--preferreduilang` switch leaking into `fsi.CommandLineArgs` when positioned after script file ([PR #19151](https://github.com/dotnet/fsharp/pull/19151)) ### Added