From 4d7a6c51d355b559372059ece1be7de07b23051f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 12:07:49 +0000 Subject: [PATCH 01/13] Initial plan From 665f272b7a20f8dbd671b78e78892cf1d643a8a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 12:58:44 +0000 Subject: [PATCH 02/13] Fix typecheck-only to catch errors in scripts with #load directives Co-authored-by: abonie <20281641+abonie@users.noreply.github.com> --- src/Compiler/Interactive/fsi.fs | 11 +++++---- .../Scripting/TypeCheckOnlyTests.fs | 24 ++++++++++++++++++- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index fc1a55ca445..1ce9a024a9c 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -2198,7 +2198,8 @@ type internal FsiDynamicCompiler isIncrementalFragment: bool, isInteractiveItExpr: bool, prefixPath: LongIdent, - m + m, + isLoadedFile: bool ) = let optEnv = istate.optEnv let tcState = istate.tcState @@ -2223,8 +2224,8 @@ type internal FsiDynamicCompiler inputs )) - // typeCheckOnly either reports all errors found so far or exits with 0 - it stops processing the script - if tcConfig.typeCheckOnly then + // typeCheckOnly stops processing after type-checking, but not for files loaded via #load + if tcConfig.typeCheckOnly && not isLoadedFile then diagnosticsLogger.AbortOnError(fsiConsoleOutput) raise StopProcessing @@ -2446,7 +2447,7 @@ type internal FsiDynamicCompiler let isIncrementalFragment = false let istate, _, _ = - ProcessInputs(ctok, diagnosticsLogger, istate, inputs, true, isIncrementalFragment, false, prefix, m) + ProcessInputs(ctok, diagnosticsLogger, istate, inputs, true, isIncrementalFragment, false, prefix, m, true) istate @@ -2499,7 +2500,7 @@ type internal FsiDynamicCompiler let isIncrementalFragment = true let istate, tcEnvAtEndOfLastInput, declaredImpls = - ProcessInputs(ctok, diagnosticsLogger, istate, [ input ], showTypes, isIncrementalFragment, isInteractiveItExpr, prefix, m) + ProcessInputs(ctok, diagnosticsLogger, istate, [ input ], showTypes, isIncrementalFragment, isInteractiveItExpr, prefix, m, false) let tcState = istate.tcState diff --git a/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs b/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs index 703a6326293..3f2b65a09eb 100644 --- a/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs @@ -1,5 +1,6 @@ module FSharp.Compiler.ComponentTests.Scripting.TypeCheckOnlyTests +open System.IO open Xunit open FSharp.Test open FSharp.Test.Compiler @@ -44,4 +45,25 @@ let x = 21+21 |> withOptions ["--nologo"] |> runFsi |> shouldSucceed - |> verifyOutputContains [|"val x: int = 42"|] \ No newline at end of file + |> verifyOutputContains [|"val x: int = 42"|] + +[] +let ``typecheck-only flag catches type errors in scripts with #load``() = + let tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()) + Directory.CreateDirectory(tempDir) |> ignore + + try + let domainPath = Path.Combine(tempDir, "Domain.fsx") + let mainPath = Path.Combine(tempDir, "A.fsx") + + File.WriteAllText(domainPath, "type T = { Field: string }\nprintfn \"D\"") + File.WriteAllText(mainPath, sprintf "#load \"%s\"\nopen Domain\nlet y = { Field = 1 }\nprintfn \"A\"" domainPath) + + FsxFromPath mainPath + |> withOptions ["--typecheck-only"] + |> runFsi + |> shouldFail + |> withStdErrContains "This expression was expected to have type" + finally + if Directory.Exists(tempDir) then + Directory.Delete(tempDir, true) \ No newline at end of file From 07fbf54a465dbe4965eb98f0a3e57ce39b9adfe8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 15:13:20 +0000 Subject: [PATCH 03/13] Fix test for Windows paths and add test for errors in loaded files Co-authored-by: abonie <20281641+abonie@users.noreply.github.com> --- src/Compiler/Interactive/fsi.fs | 13 ++++++- .../Scripting/TypeCheckOnlyTests.fs | 38 +++++++++++++++++-- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index 1ce9a024a9c..3305775a935 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -2500,7 +2500,18 @@ type internal FsiDynamicCompiler let isIncrementalFragment = true let istate, tcEnvAtEndOfLastInput, declaredImpls = - ProcessInputs(ctok, diagnosticsLogger, istate, [ input ], showTypes, isIncrementalFragment, isInteractiveItExpr, prefix, m, false) + ProcessInputs( + ctok, + diagnosticsLogger, + istate, + [ input ], + showTypes, + isIncrementalFragment, + isInteractiveItExpr, + prefix, + m, + false + ) let tcState = istate.tcState diff --git a/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs b/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs index 3f2b65a09eb..f41cec58b54 100644 --- a/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs @@ -1,5 +1,6 @@ module FSharp.Compiler.ComponentTests.Scripting.TypeCheckOnlyTests +open System open System.IO open Xunit open FSharp.Test @@ -51,13 +52,15 @@ let x = 21+21 let ``typecheck-only flag catches type errors in scripts with #load``() = let tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()) Directory.CreateDirectory(tempDir) |> ignore + let originalDir = Environment.CurrentDirectory try + Environment.CurrentDirectory <- tempDir let domainPath = Path.Combine(tempDir, "Domain.fsx") let mainPath = Path.Combine(tempDir, "A.fsx") File.WriteAllText(domainPath, "type T = { Field: string }\nprintfn \"D\"") - File.WriteAllText(mainPath, sprintf "#load \"%s\"\nopen Domain\nlet y = { Field = 1 }\nprintfn \"A\"" domainPath) + File.WriteAllText(mainPath, "#load \"Domain.fsx\"\nopen Domain\nlet y = { Field = 1 }\nprintfn \"A\"") FsxFromPath mainPath |> withOptions ["--typecheck-only"] @@ -65,5 +68,34 @@ let ``typecheck-only flag catches type errors in scripts with #load``() = |> shouldFail |> withStdErrContains "This expression was expected to have type" finally - if Directory.Exists(tempDir) then - Directory.Delete(tempDir, true) \ No newline at end of file + try + Environment.CurrentDirectory <- originalDir + if Directory.Exists(tempDir) then + Directory.Delete(tempDir, true) + with _ -> () + +[] +let ``typecheck-only flag catches type errors in loaded file``() = + let tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()) + Directory.CreateDirectory(tempDir) |> ignore + let originalDir = Environment.CurrentDirectory + + try + Environment.CurrentDirectory <- tempDir + let domainPath = Path.Combine(tempDir, "Domain.fsx") + let mainPath = Path.Combine(tempDir, "A.fsx") + + File.WriteAllText(domainPath, "type T = { Field: string }\nlet x: int = \"error\"\nprintfn \"D\"") + File.WriteAllText(mainPath, "#load \"Domain.fsx\"\nopen Domain\nlet y = { Field = \"ok\" }\nprintfn \"A\"") + + FsxFromPath mainPath + |> withOptions ["--typecheck-only"] + |> runFsi + |> shouldFail + |> withStdErrContains "This expression was expected to have type" + finally + try + Environment.CurrentDirectory <- originalDir + if Directory.Exists(tempDir) then + Directory.Delete(tempDir, true) + with _ -> () \ No newline at end of file From ac2f3f077b7eb374f62277f481e489ca67659791 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 20:33:46 +0000 Subject: [PATCH 04/13] Fix typecheck-only to properly handle loaded files without code generation Co-authored-by: abonie <20281641+abonie@users.noreply.github.com> --- src/Compiler/Interactive/fsi.fs | 81 ++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index 3305775a935..a7f66d1f69b 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -2224,46 +2224,53 @@ type internal FsiDynamicCompiler inputs )) - // typeCheckOnly stops processing after type-checking, but not for files loaded via #load - if tcConfig.typeCheckOnly && not isLoadedFile then - diagnosticsLogger.AbortOnError(fsiConsoleOutput) - raise StopProcessing - - let codegenResults, optEnv, fragName = - ProcessTypedImpl( - diagnosticsLogger, - optEnv, - tcState, - tcConfig, - isInteractiveItExpr, - topCustomAttrs, - prefixPath, - isIncrementalFragment, - declaredImpls, - ilxGenerator - ) + // typeCheckOnly stops processing after type-checking + // For loaded files, we skip code generation but continue to process the main script + // For main script, we report errors and stop processing + if tcConfig.typeCheckOnly then + if not isLoadedFile then + diagnosticsLogger.AbortOnError(fsiConsoleOutput) + raise StopProcessing + else + // For loaded files, update state with type-checking results but skip code generation + let newIState = { istate with tcState = tcState } + newIState, tcEnvAtEndOfLastInput, [] + else + let codegenResults, optEnv, fragName = + ProcessTypedImpl( + diagnosticsLogger, + optEnv, + tcState, + tcConfig, + isInteractiveItExpr, + topCustomAttrs, + prefixPath, + isIncrementalFragment, + declaredImpls, + ilxGenerator + ) - let newState, declaredImpls = - ProcessCodegenResults( - ctok, - diagnosticsLogger, - istate, - optEnv, - tcState, - tcConfig, - prefixPath, - showTypes, - isIncrementalFragment, - fragName, - declaredImpls, - ilxGenerator, - codegenResults, - m - ) + let newState, declaredImpls = + ProcessCodegenResults( + ctok, + diagnosticsLogger, + istate, + optEnv, + tcState, + tcConfig, + prefixPath, + showTypes, + isIncrementalFragment, + fragName, + declaredImpls, + ilxGenerator, + codegenResults, + m + ) - CheckEntryPoint istate.tcGlobals declaredImpls + CheckEntryPoint istate.tcGlobals declaredImpls - (newState, tcEnvAtEndOfLastInput, declaredImpls) + (newState, tcEnvAtEndOfLastInput, declaredImpls) let tryGetGeneratedValue istate cenv v = match istate.ilxGenerator.LookupGeneratedValue(valuePrinter.GetEvaluationContext(istate.emEnv), v) with From acff810bb44e39c36b39fd885cc60ee5e00e0d8f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Nov 2025 11:03:53 +0000 Subject: [PATCH 05/13] Fix typecheck-only to properly report errors in loaded files Co-authored-by: abonie <20281641+abonie@users.noreply.github.com> --- src/Compiler/Interactive/fsi.fs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index a7f66d1f69b..fbbc2d42e90 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -2224,17 +2224,19 @@ type internal FsiDynamicCompiler inputs )) - // typeCheckOnly stops processing after type-checking - // For loaded files, we skip code generation but continue to process the main script - // For main script, we report errors and stop processing + // typeCheckOnly: check for errors after type-checking + // Always abort on errors (for both loaded files and main script) + diagnosticsLogger.AbortOnError(fsiConsoleOutput) + + // typeCheckOnly: skip code generation and execution if tcConfig.typeCheckOnly then + // Update state with type-checking results but skip code generation + let newIState = { istate with tcState = tcState } + // Only raise StopProcessing for main script, not for loaded files if not isLoadedFile then - diagnosticsLogger.AbortOnError(fsiConsoleOutput) raise StopProcessing - else - // For loaded files, update state with type-checking results but skip code generation - let newIState = { istate with tcState = tcState } - newIState, tcEnvAtEndOfLastInput, [] + + newIState, tcEnvAtEndOfLastInput, [] else let codegenResults, optEnv, fragName = ProcessTypedImpl( From 797b1acc32a6d1d5369c99e0b0d235c783cc936c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 6 Nov 2025 17:02:30 +0000 Subject: [PATCH 06/13] Fix AbortOnError to only be called when typecheck-only is enabled Co-authored-by: abonie <20281641+abonie@users.noreply.github.com> --- src/Compiler/Interactive/fsi.fs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index fbbc2d42e90..474bc5a8edf 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -2224,12 +2224,10 @@ type internal FsiDynamicCompiler inputs )) - // typeCheckOnly: check for errors after type-checking - // Always abort on errors (for both loaded files and main script) - diagnosticsLogger.AbortOnError(fsiConsoleOutput) - - // typeCheckOnly: skip code generation and execution + // typeCheckOnly: check for errors and skip code generation if tcConfig.typeCheckOnly then + // Always abort on errors (for both loaded files and main script) + diagnosticsLogger.AbortOnError(fsiConsoleOutput) // Update state with type-checking results but skip code generation let newIState = { istate with tcState = tcState } // Only raise StopProcessing for main script, not for loaded files From cd269d871484b8efabbf0f5f2e03994f8b44e6e0 Mon Sep 17 00:00:00 2001 From: Adam Boniecki <20281641+abonie@users.noreply.github.com> Date: Tue, 11 Nov 2025 22:35:26 +0100 Subject: [PATCH 07/13] Use absolute path in tests --- .../Scripting/TypeCheckOnlyTests.fs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs b/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs index f41cec58b54..8ca43866285 100644 --- a/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs @@ -52,15 +52,15 @@ let x = 21+21 let ``typecheck-only flag catches type errors in scripts with #load``() = let tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()) Directory.CreateDirectory(tempDir) |> ignore - let originalDir = Environment.CurrentDirectory try - Environment.CurrentDirectory <- tempDir let domainPath = Path.Combine(tempDir, "Domain.fsx") let mainPath = Path.Combine(tempDir, "A.fsx") - File.WriteAllText(domainPath, "type T = { Field: string }\nprintfn \"D\"") - File.WriteAllText(mainPath, "#load \"Domain.fsx\"\nopen Domain\nlet y = { Field = 1 }\nprintfn \"A\"") + File.WriteAllText(domainPath, "type T = {\n Field: string\n}\n\nprintfn \"printfn Domain.fsx\"") + // Use absolute path in #load directive since runFsi uses EvalInteraction which doesn't preserve script location context + let mainContent = sprintf "#load \"%s\"\n\nopen Domain\n\nlet y = {\n Field = 1\n}\n\nprintfn \"printfn A.fsx\"" (domainPath.Replace("\\", "\\\\")) + File.WriteAllText(mainPath, mainContent) FsxFromPath mainPath |> withOptions ["--typecheck-only"] @@ -69,7 +69,6 @@ let ``typecheck-only flag catches type errors in scripts with #load``() = |> withStdErrContains "This expression was expected to have type" finally try - Environment.CurrentDirectory <- originalDir if Directory.Exists(tempDir) then Directory.Delete(tempDir, true) with _ -> () @@ -78,15 +77,15 @@ let ``typecheck-only flag catches type errors in scripts with #load``() = let ``typecheck-only flag catches type errors in loaded file``() = let tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()) Directory.CreateDirectory(tempDir) |> ignore - let originalDir = Environment.CurrentDirectory try - Environment.CurrentDirectory <- tempDir let domainPath = Path.Combine(tempDir, "Domain.fsx") let mainPath = Path.Combine(tempDir, "A.fsx") File.WriteAllText(domainPath, "type T = { Field: string }\nlet x: int = \"error\"\nprintfn \"D\"") - File.WriteAllText(mainPath, "#load \"Domain.fsx\"\nopen Domain\nlet y = { Field = \"ok\" }\nprintfn \"A\"") + // Use absolute path in #load directive since runFsi uses EvalInteraction which doesn't preserve script location context + let mainContent = sprintf "#load \"%s\"\nopen Domain\nlet y = { Field = \"ok\" }\nprintfn \"A\"" (domainPath.Replace("\\", "\\\\")) + File.WriteAllText(mainPath, mainContent) FsxFromPath mainPath |> withOptions ["--typecheck-only"] @@ -95,7 +94,6 @@ let ``typecheck-only flag catches type errors in loaded file``() = |> withStdErrContains "This expression was expected to have type" finally try - Environment.CurrentDirectory <- originalDir if Directory.Exists(tempDir) then Directory.Delete(tempDir, true) with _ -> () \ No newline at end of file From e95cd60f56d98ec8a10b12d5d7603e63978f2cc4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 11 Nov 2025 22:48:20 +0000 Subject: [PATCH 08/13] Add test to verify typecheck-only prevents code execution with #load Co-authored-by: abonie <20281641+abonie@users.noreply.github.com> --- .../Scripting/TypeCheckOnlyTests.fs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs b/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs index 8ca43866285..071718838eb 100644 --- a/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs @@ -92,6 +92,32 @@ let ``typecheck-only flag catches type errors in loaded file``() = |> runFsi |> shouldFail |> withStdErrContains "This expression was expected to have type" + finally + try + if Directory.Exists(tempDir) then + Directory.Delete(tempDir, true) + with _ -> () + +[] +let ``typecheck-only flag prevents execution with #load``() = + let tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()) + Directory.CreateDirectory(tempDir) |> ignore + + try + let domainPath = Path.Combine(tempDir, "Domain.fsx") + let mainPath = Path.Combine(tempDir, "A.fsx") + + File.WriteAllText(domainPath, "type T = { Field: string }\nprintfn \"Domain.fsx output\"") + // Use absolute path in #load directive since runFsi uses EvalInteraction which doesn't preserve script location context + let mainContent = sprintf "#load \"%s\"\nopen Domain\nlet y = { Field = \"test\" }\nprintfn \"A.fsx output\"" (domainPath.Replace("\\", "\\\\")) + File.WriteAllText(mainPath, mainContent) + + FsxFromPath mainPath + |> withOptions ["--typecheck-only"] + |> runFsi + |> shouldSucceed + |> verifyNotInOutput "Domain.fsx output" + |> verifyNotInOutput "A.fsx output" finally try if Directory.Exists(tempDir) then From da15d99f86bf41eaf28a5723f9921e6cd85ff616 Mon Sep 17 00:00:00 2001 From: Adam Boniecki <20281641+abonie@users.noreply.github.com> Date: Wed, 12 Nov 2025 18:00:22 +0100 Subject: [PATCH 09/13] Remove unnecessary param for typecheckonly --- src/Compiler/Interactive/fsi.fs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index 474bc5a8edf..a6417923e18 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -2198,8 +2198,7 @@ type internal FsiDynamicCompiler isIncrementalFragment: bool, isInteractiveItExpr: bool, prefixPath: LongIdent, - m, - isLoadedFile: bool + m ) = let optEnv = istate.optEnv let tcState = istate.tcState @@ -2230,9 +2229,6 @@ type internal FsiDynamicCompiler diagnosticsLogger.AbortOnError(fsiConsoleOutput) // Update state with type-checking results but skip code generation let newIState = { istate with tcState = tcState } - // Only raise StopProcessing for main script, not for loaded files - if not isLoadedFile then - raise StopProcessing newIState, tcEnvAtEndOfLastInput, [] else @@ -2454,7 +2450,7 @@ type internal FsiDynamicCompiler let isIncrementalFragment = false let istate, _, _ = - ProcessInputs(ctok, diagnosticsLogger, istate, inputs, true, isIncrementalFragment, false, prefix, m, true) + ProcessInputs(ctok, diagnosticsLogger, istate, inputs, true, isIncrementalFragment, false, prefix, m) istate @@ -2516,8 +2512,7 @@ type internal FsiDynamicCompiler isIncrementalFragment, isInteractiveItExpr, prefix, - m, - false + m ) let tcState = istate.tcState From c129964f2f5f3c601dc89a3c305111f624bb0935 Mon Sep 17 00:00:00 2001 From: Adam Boniecki <20281641+abonie@users.noreply.github.com> Date: Wed, 12 Nov 2025 18:07:59 +0100 Subject: [PATCH 10/13] Add helper function in script tests --- .../Scripting/TypeCheckOnlyTests.fs | 75 ++++++++----------- 1 file changed, 31 insertions(+), 44 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs b/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs index 071718838eb..b6717ecd5ba 100644 --- a/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs @@ -6,6 +6,23 @@ open Xunit open FSharp.Test open FSharp.Test.Compiler +let private withTempDirectory (test: string -> unit) = + let tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()) + Directory.CreateDirectory(tempDir) |> ignore + + try + test tempDir + finally + try + if Directory.Exists(tempDir) then + Directory.Delete(tempDir, true) + with _ -> () + +let private writeScript (dir: string) (filename: string) (content: string) = + let path = Path.Combine(dir, filename) + File.WriteAllText(path, content) + path + [] let ``typecheck-only flag works for valid script``() = Fsx """ @@ -50,76 +67,46 @@ let x = 21+21 [] let ``typecheck-only flag catches type errors in scripts with #load``() = - let tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()) - Directory.CreateDirectory(tempDir) |> ignore - - try - let domainPath = Path.Combine(tempDir, "Domain.fsx") - let mainPath = Path.Combine(tempDir, "A.fsx") + withTempDirectory (fun tempDir -> + let domainPath = writeScript tempDir "Domain.fsx" "type T = {\n Field: string\n}\n\nprintfn \"printfn Domain.fsx\"" - File.WriteAllText(domainPath, "type T = {\n Field: string\n}\n\nprintfn \"printfn Domain.fsx\"") - // Use absolute path in #load directive since runFsi uses EvalInteraction which doesn't preserve script location context let mainContent = sprintf "#load \"%s\"\n\nopen Domain\n\nlet y = {\n Field = 1\n}\n\nprintfn \"printfn A.fsx\"" (domainPath.Replace("\\", "\\\\")) - File.WriteAllText(mainPath, mainContent) + let mainPath = writeScript tempDir "A.fsx" mainContent FsxFromPath mainPath |> withOptions ["--typecheck-only"] |> runFsi |> shouldFail |> withStdErrContains "This expression was expected to have type" - finally - try - if Directory.Exists(tempDir) then - Directory.Delete(tempDir, true) - with _ -> () + |> ignore) [] let ``typecheck-only flag catches type errors in loaded file``() = - let tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()) - Directory.CreateDirectory(tempDir) |> ignore - - try - let domainPath = Path.Combine(tempDir, "Domain.fsx") - let mainPath = Path.Combine(tempDir, "A.fsx") + withTempDirectory (fun tempDir -> + let domainPath = writeScript tempDir "Domain.fsx" "type T = { Field: string }\nlet x: int = \"error\"\nprintfn \"D\"" - File.WriteAllText(domainPath, "type T = { Field: string }\nlet x: int = \"error\"\nprintfn \"D\"") - // Use absolute path in #load directive since runFsi uses EvalInteraction which doesn't preserve script location context let mainContent = sprintf "#load \"%s\"\nopen Domain\nlet y = { Field = \"ok\" }\nprintfn \"A\"" (domainPath.Replace("\\", "\\\\")) - File.WriteAllText(mainPath, mainContent) + let mainPath = writeScript tempDir "A.fsx" mainContent FsxFromPath mainPath |> withOptions ["--typecheck-only"] |> runFsi |> shouldFail |> withStdErrContains "This expression was expected to have type" - finally - try - if Directory.Exists(tempDir) then - Directory.Delete(tempDir, true) - with _ -> () + |> ignore) [] let ``typecheck-only flag prevents execution with #load``() = - let tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()) - Directory.CreateDirectory(tempDir) |> ignore - - try - let domainPath = Path.Combine(tempDir, "Domain.fsx") - let mainPath = Path.Combine(tempDir, "A.fsx") - - File.WriteAllText(domainPath, "type T = { Field: string }\nprintfn \"Domain.fsx output\"") - // Use absolute path in #load directive since runFsi uses EvalInteraction which doesn't preserve script location context + withTempDirectory (fun tempDir -> + let domainPath = writeScript tempDir "Domain.fsx" "type T = { Field: string }\nprintfn \"Domain.fsx output\"" + let mainContent = sprintf "#load \"%s\"\nopen Domain\nlet y = { Field = \"test\" }\nprintfn \"A.fsx output\"" (domainPath.Replace("\\", "\\\\")) - File.WriteAllText(mainPath, mainContent) - + let mainPath = writeScript tempDir "A.fsx" mainContent + FsxFromPath mainPath |> withOptions ["--typecheck-only"] |> runFsi |> shouldSucceed |> verifyNotInOutput "Domain.fsx output" |> verifyNotInOutput "A.fsx output" - finally - try - if Directory.Exists(tempDir) then - Directory.Delete(tempDir, true) - with _ -> () \ No newline at end of file + |> ignore) From b73bbebd0b0cdc550ee67beed807873d5adc7866 Mon Sep 17 00:00:00 2001 From: Adam Boniecki <20281641+abonie@users.noreply.github.com> Date: Wed, 12 Nov 2025 18:31:45 +0100 Subject: [PATCH 11/13] Add release note --- docs/release-notes/.FSharp.Compiler.Service/10.0.100.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md index c75892f80d6..cbfc37b614f 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md @@ -38,6 +38,7 @@ * Ensure that line directives are applied to source identifiers (issue [#18908](https://github.com/dotnet/fsharp/issues/18908), PR [#18918](https://github.com/dotnet/fsharp/pull/18918)) * Fix expected and actual types in ErrorFromAddingTypeEquation message and extended diagnostic data. ([PR #18915](https://github.com/dotnet/fsharp/pull/18915)) * Editor: Fix Record fields completion in update record with partial field name. ([PR #18946](https://github.com/dotnet/fsharp/pull/18946)) +* Fix `--typecheck-only` for scripts stopping after processing `#load`-ed script ([PR #19048](https://github.com/dotnet/fsharp/pull/19048)) ### Changed * Use `errorR` instead of `error` in `CheckDeclarations.fs` when possible. ([PR #18645](https://github.com/dotnet/fsharp/pull/18645)) From 4453d1b543c3bfb6f4aa7e374a9694dee71b7578 Mon Sep 17 00:00:00 2001 From: GH Actions Date: Wed, 12 Nov 2025 19:16:49 +0000 Subject: [PATCH 12/13] Apply patch from /run fantomas --- src/Compiler/Interactive/fsi.fs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index 48dc02107e2..a52b5a70d42 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -2503,17 +2503,7 @@ type internal FsiDynamicCompiler let isIncrementalFragment = true let istate, tcEnvAtEndOfLastInput, declaredImpls = - ProcessInputs( - ctok, - diagnosticsLogger, - istate, - [ input ], - showTypes, - isIncrementalFragment, - isInteractiveItExpr, - prefix, - m - ) + ProcessInputs(ctok, diagnosticsLogger, istate, [ input ], showTypes, isIncrementalFragment, isInteractiveItExpr, prefix, m) let tcState = istate.tcState From 4c6bfb2042a60cae520e7e41db164b5448847820 Mon Sep 17 00:00:00 2001 From: Adam Boniecki <20281641+abonie@users.noreply.github.com> Date: Wed, 12 Nov 2025 19:21:08 +0000 Subject: [PATCH 13/13] Move release note --- docs/release-notes/.FSharp.Compiler.Service/10.0.100.md | 1 - docs/release-notes/.FSharp.Compiler.Service/11.0.0.md | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md index cbfc37b614f..c75892f80d6 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md @@ -38,7 +38,6 @@ * Ensure that line directives are applied to source identifiers (issue [#18908](https://github.com/dotnet/fsharp/issues/18908), PR [#18918](https://github.com/dotnet/fsharp/pull/18918)) * Fix expected and actual types in ErrorFromAddingTypeEquation message and extended diagnostic data. ([PR #18915](https://github.com/dotnet/fsharp/pull/18915)) * Editor: Fix Record fields completion in update record with partial field name. ([PR #18946](https://github.com/dotnet/fsharp/pull/18946)) -* Fix `--typecheck-only` for scripts stopping after processing `#load`-ed script ([PR #19048](https://github.com/dotnet/fsharp/pull/19048)) ### Changed * Use `errorR` instead of `error` in `CheckDeclarations.fs` when possible. ([PR #18645](https://github.com/dotnet/fsharp/pull/18645)) diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.0.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.0.md index 290e8e855d7..7cc15f46c9e 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.0.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.0.md @@ -13,6 +13,7 @@ * Fix race in graph checking of type extensions. ([PR #19062](https://github.com/dotnet/fsharp/pull/19062)) * Type relations cache: handle unsolved type variables ([Issue #19037](https://github.com/dotnet/fsharp/issues/19037)) ([PR #19040](https://github.com/dotnet/fsharp/pull/19040)) * Fix insertion context for modules with multiline attributes. ([Issue #18671](https://github.com/dotnet/fsharp/issues/18671)) +* Fix `--typecheck-only` for scripts stopping after processing `#load`-ed script ([PR #19048](https://github.com/dotnet/fsharp/pull/19048)) ### Added