Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
72 changes: 38 additions & 34 deletions src/Compiler/Interactive/fsi.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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

[<Fact>]
let ``typecheck-only flag works for valid script``() =
Fsx """
Expand Down Expand Up @@ -44,4 +63,50 @@ let x = 21+21
|> withOptions ["--nologo"]
|> runFsi
|> shouldSucceed
|> verifyOutputContains [|"val x: int = 42"|]
|> verifyOutputContains [|"val x: int = 42"|]

[<Fact>]
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)

[<Fact>]
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)

[<Fact>]
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)
Loading