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 diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index f077682178e..a52b5a70d42 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -2223,46 +2223,50 @@ type internal FsiDynamicCompiler inputs )) - // typeCheckOnly either reports all errors found so far or exits with 0 - it stops processing the script + // 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) - raise StopProcessing + // Update state with type-checking results but skip code generation + let newIState = { istate with tcState = tcState } - let codegenResults, optEnv, fragName = - ProcessTypedImpl( - diagnosticsLogger, - optEnv, - tcState, - tcConfig, - isInteractiveItExpr, - topCustomAttrs, - prefixPath, - isIncrementalFragment, - declaredImpls, - ilxGenerator - ) + 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 diff --git a/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs b/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs index 703a6326293..b6717ecd5ba 100644 --- a/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs @@ -1,9 +1,28 @@ module FSharp.Compiler.ComponentTests.Scripting.TypeCheckOnlyTests +open System +open System.IO 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 """ @@ -44,4 +63,50 @@ 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``() = + withTempDirectory (fun tempDir -> + let domainPath = writeScript tempDir "Domain.fsx" "type T = {\n Field: string\n}\n\nprintfn \"printfn Domain.fsx\"" + + let mainContent = sprintf "#load \"%s\"\n\nopen Domain\n\nlet y = {\n Field = 1\n}\n\nprintfn \"printfn A.fsx\"" (domainPath.Replace("\\", "\\\\")) + let mainPath = writeScript tempDir "A.fsx" mainContent + + FsxFromPath mainPath + |> withOptions ["--typecheck-only"] + |> runFsi + |> shouldFail + |> withStdErrContains "This expression was expected to have type" + |> ignore) + +[] +let ``typecheck-only flag catches type errors in loaded file``() = + withTempDirectory (fun tempDir -> + let domainPath = writeScript tempDir "Domain.fsx" "type T = { Field: string }\nlet x: int = \"error\"\nprintfn \"D\"" + + let mainContent = sprintf "#load \"%s\"\nopen Domain\nlet y = { Field = \"ok\" }\nprintfn \"A\"" (domainPath.Replace("\\", "\\\\")) + let mainPath = writeScript tempDir "A.fsx" mainContent + + FsxFromPath mainPath + |> withOptions ["--typecheck-only"] + |> runFsi + |> shouldFail + |> withStdErrContains "This expression was expected to have type" + |> ignore) + +[] +let ``typecheck-only flag prevents execution with #load``() = + 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("\\", "\\\\")) + let mainPath = writeScript tempDir "A.fsx" mainContent + + FsxFromPath mainPath + |> withOptions ["--typecheck-only"] + |> runFsi + |> shouldSucceed + |> verifyNotInOutput "Domain.fsx output" + |> verifyNotInOutput "A.fsx output" + |> ignore)