Skip to content
Open
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/10.0.200.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
35 changes: 34 additions & 1 deletion src/Compiler/Interactive/fsi.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1068,8 +1068,41 @@ 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 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 isPreferredUiLangArg arg ->
// Extract culture and set it
let colonIndex = arg.IndexOf(':')

if colonIndex >= 0 && colonIndex < arg.Length - 1 then
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
with
| :? CultureNotFoundException
| :? ArgumentException ->
// 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 *)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@
<Compile Include="Interop\Literals.fs" />
<Compile Include="Scripting\Interactive.fs" />
<Compile Include="Scripting\TypeCheckOnlyTests.fs" />
<Compile Include="Scripting\PreferredUiLangTests.fs" />
<Compile Include="TypeChecks\TypeRelations.fs" />
<Compile Include="TypeChecks\SeqTypeCheckTests.fs" />
<Compile Include="TypeChecks\CheckDeclarationsTests.fs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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`` =

[<Fact>]
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<string>).Count = 0, sprintf "Expected no errors, got: %A" errors)
finally
if File.Exists(tmpFile) then File.Delete(tmpFile)

[<Fact>]
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<string>).Count = 0, sprintf "Expected no errors, got: %A" errors)
finally
if File.Exists(tmpFile) then File.Delete(tmpFile)

[<Fact>]
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<string>).Count = 0, sprintf "Expected no errors, got: %A" errors)
finally
if File.Exists(tmpFile) then File.Delete(tmpFile)

[<Fact>]
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<string>).Count = 0, sprintf "Expected no errors, got: %A" errors)
finally
if File.Exists(tmpFile) then File.Delete(tmpFile)

[<Fact>]
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<string>).Count = 0, sprintf "Expected no errors, got: %A" errors)
finally
if File.Exists(tmpFile) then File.Delete(tmpFile)

[<Fact>]
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<string>).Count = 0, sprintf "Expected no errors, got: %A" errors)
finally
if File.Exists(tmpFile) then File.Delete(tmpFile)
Loading