Skip to content

Commit eacda90

Browse files
authored
Fix typecheck-only issues with #load in scripts (#19048)
1 parent 558f256 commit eacda90

File tree

3 files changed

+105
-35
lines changed

3 files changed

+105
-35
lines changed

docs/release-notes/.FSharp.Compiler.Service/11.0.0.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* Fix race in graph checking of type extensions. ([PR #19062](https://github.com/dotnet/fsharp/pull/19062))
1414
* 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))
1515
* Fix insertion context for modules with multiline attributes. ([Issue #18671](https://github.com/dotnet/fsharp/issues/18671))
16+
* Fix `--typecheck-only` for scripts stopping after processing `#load`-ed script ([PR #19048](https://github.com/dotnet/fsharp/pull/19048))
1617

1718
### Added
1819

src/Compiler/Interactive/fsi.fs

Lines changed: 38 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2223,46 +2223,50 @@ type internal FsiDynamicCompiler
22232223
inputs
22242224
))
22252225

2226-
// typeCheckOnly either reports all errors found so far or exits with 0 - it stops processing the script
2226+
// typeCheckOnly: check for errors and skip code generation
22272227
if tcConfig.typeCheckOnly then
2228+
// Always abort on errors (for both loaded files and main script)
22282229
diagnosticsLogger.AbortOnError(fsiConsoleOutput)
2229-
raise StopProcessing
2230+
// Update state with type-checking results but skip code generation
2231+
let newIState = { istate with tcState = tcState }
22302232

2231-
let codegenResults, optEnv, fragName =
2232-
ProcessTypedImpl(
2233-
diagnosticsLogger,
2234-
optEnv,
2235-
tcState,
2236-
tcConfig,
2237-
isInteractiveItExpr,
2238-
topCustomAttrs,
2239-
prefixPath,
2240-
isIncrementalFragment,
2241-
declaredImpls,
2242-
ilxGenerator
2243-
)
2233+
newIState, tcEnvAtEndOfLastInput, []
2234+
else
2235+
let codegenResults, optEnv, fragName =
2236+
ProcessTypedImpl(
2237+
diagnosticsLogger,
2238+
optEnv,
2239+
tcState,
2240+
tcConfig,
2241+
isInteractiveItExpr,
2242+
topCustomAttrs,
2243+
prefixPath,
2244+
isIncrementalFragment,
2245+
declaredImpls,
2246+
ilxGenerator
2247+
)
22442248

2245-
let newState, declaredImpls =
2246-
ProcessCodegenResults(
2247-
ctok,
2248-
diagnosticsLogger,
2249-
istate,
2250-
optEnv,
2251-
tcState,
2252-
tcConfig,
2253-
prefixPath,
2254-
showTypes,
2255-
isIncrementalFragment,
2256-
fragName,
2257-
declaredImpls,
2258-
ilxGenerator,
2259-
codegenResults,
2260-
m
2261-
)
2249+
let newState, declaredImpls =
2250+
ProcessCodegenResults(
2251+
ctok,
2252+
diagnosticsLogger,
2253+
istate,
2254+
optEnv,
2255+
tcState,
2256+
tcConfig,
2257+
prefixPath,
2258+
showTypes,
2259+
isIncrementalFragment,
2260+
fragName,
2261+
declaredImpls,
2262+
ilxGenerator,
2263+
codegenResults,
2264+
m
2265+
)
22622266

2263-
CheckEntryPoint istate.tcGlobals declaredImpls
2267+
CheckEntryPoint istate.tcGlobals declaredImpls
22642268

2265-
(newState, tcEnvAtEndOfLastInput, declaredImpls)
2269+
(newState, tcEnvAtEndOfLastInput, declaredImpls)
22662270

22672271
let tryGetGeneratedValue istate cenv v =
22682272
match istate.ilxGenerator.LookupGeneratedValue(valuePrinter.GetEvaluationContext(istate.emEnv), v) with

tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,28 @@
11
module FSharp.Compiler.ComponentTests.Scripting.TypeCheckOnlyTests
22

3+
open System
4+
open System.IO
35
open Xunit
46
open FSharp.Test
57
open FSharp.Test.Compiler
68

9+
let private withTempDirectory (test: string -> unit) =
10+
let tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())
11+
Directory.CreateDirectory(tempDir) |> ignore
12+
13+
try
14+
test tempDir
15+
finally
16+
try
17+
if Directory.Exists(tempDir) then
18+
Directory.Delete(tempDir, true)
19+
with _ -> ()
20+
21+
let private writeScript (dir: string) (filename: string) (content: string) =
22+
let path = Path.Combine(dir, filename)
23+
File.WriteAllText(path, content)
24+
path
25+
726
[<Fact>]
827
let ``typecheck-only flag works for valid script``() =
928
Fsx """
@@ -44,4 +63,50 @@ let x = 21+21
4463
|> withOptions ["--nologo"]
4564
|> runFsi
4665
|> shouldSucceed
47-
|> verifyOutputContains [|"val x: int = 42"|]
66+
|> verifyOutputContains [|"val x: int = 42"|]
67+
68+
[<Fact>]
69+
let ``typecheck-only flag catches type errors in scripts with #load``() =
70+
withTempDirectory (fun tempDir ->
71+
let domainPath = writeScript tempDir "Domain.fsx" "type T = {\n Field: string\n}\n\nprintfn \"printfn Domain.fsx\""
72+
73+
let mainContent = sprintf "#load \"%s\"\n\nopen Domain\n\nlet y = {\n Field = 1\n}\n\nprintfn \"printfn A.fsx\"" (domainPath.Replace("\\", "\\\\"))
74+
let mainPath = writeScript tempDir "A.fsx" mainContent
75+
76+
FsxFromPath mainPath
77+
|> withOptions ["--typecheck-only"]
78+
|> runFsi
79+
|> shouldFail
80+
|> withStdErrContains "This expression was expected to have type"
81+
|> ignore)
82+
83+
[<Fact>]
84+
let ``typecheck-only flag catches type errors in loaded file``() =
85+
withTempDirectory (fun tempDir ->
86+
let domainPath = writeScript tempDir "Domain.fsx" "type T = { Field: string }\nlet x: int = \"error\"\nprintfn \"D\""
87+
88+
let mainContent = sprintf "#load \"%s\"\nopen Domain\nlet y = { Field = \"ok\" }\nprintfn \"A\"" (domainPath.Replace("\\", "\\\\"))
89+
let mainPath = writeScript tempDir "A.fsx" mainContent
90+
91+
FsxFromPath mainPath
92+
|> withOptions ["--typecheck-only"]
93+
|> runFsi
94+
|> shouldFail
95+
|> withStdErrContains "This expression was expected to have type"
96+
|> ignore)
97+
98+
[<Fact>]
99+
let ``typecheck-only flag prevents execution with #load``() =
100+
withTempDirectory (fun tempDir ->
101+
let domainPath = writeScript tempDir "Domain.fsx" "type T = { Field: string }\nprintfn \"Domain.fsx output\""
102+
103+
let mainContent = sprintf "#load \"%s\"\nopen Domain\nlet y = { Field = \"test\" }\nprintfn \"A.fsx output\"" (domainPath.Replace("\\", "\\\\"))
104+
let mainPath = writeScript tempDir "A.fsx" mainContent
105+
106+
FsxFromPath mainPath
107+
|> withOptions ["--typecheck-only"]
108+
|> runFsi
109+
|> shouldSucceed
110+
|> verifyNotInOutput "Domain.fsx output"
111+
|> verifyNotInOutput "A.fsx output"
112+
|> ignore)

0 commit comments

Comments
 (0)