diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 5091aec9f0d..edd2c68d426 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -22,10 +22,6 @@ jobs: env: CI: false run: dotnet build ./FSharp.Compiler.Service.sln --verbosity quiet - - name: Restore the language server solution - env: - CI: false - run: dotnet build ./VSFSharpExtension.sln --verbosity quiet - name: Restore dotnet tools env: CI: false diff --git a/src/Compiler/Driver/CompilerOptions.fs b/src/Compiler/Driver/CompilerOptions.fs index cdeda6b6566..4dd36dec6bd 100644 --- a/src/Compiler/Driver/CompilerOptions.fs +++ b/src/Compiler/Driver/CompilerOptions.fs @@ -1308,6 +1308,13 @@ let advancedFlagsFsi tcConfigB = None, Some(FSComp.SR.optsClearResultsCache ()) ) + CompilerOption( + "typecheck-only", + tagNone, + OptionUnit(fun () -> tcConfigB.typeCheckOnly <- true), + None, + Some(FSComp.SR.optsTypecheckOnly ()) + ) ] let advancedFlagsFsc tcConfigB = diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index 78dde5bd148..b29c31d6671 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -903,6 +903,7 @@ optsVersion,"Display compiler version banner and exit" optsResponseFile,"Read response file for more options" optsCodepage,"Specify the codepage used to read source files" optsClearResultsCache,"Clear the package manager results cache" +optsTypecheckOnly,"Perform type checking only, do not execute code" optsUtf8output,"Output messages in UTF-8 encoding" optsFullpaths,"Output messages with fully qualified paths" optsLib,"Specify a directory for the include path which is used to resolve source files and assemblies (Short form: -I)" diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index 662ba9752e7..cbe606b648c 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -2217,6 +2217,10 @@ type internal FsiDynamicCompiler inputs )) + // Add this check after CheckClosedInputSet + if tcConfig.typeCheckOnly then + raise StopProcessing + let codegenResults, optEnv, fragName = ProcessTypedImpl( diagnosticsLogger, diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 952624f9fcd..601fc70be62 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -271,6 +271,7 @@ + diff --git a/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs b/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs new file mode 100644 index 00000000000..e960237f6f7 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Scripting/TypeCheckOnlyTests.fs @@ -0,0 +1,48 @@ +module FSharp.Compiler.ComponentTests.Scripting.TypeCheckOnlyTests + +open Xunit +open FSharp.Test +open FSharp.Test.Compiler + +[] +let ``typecheck-only flag works for valid script``() = + Fsx """ +let x = 42 +printfn "This should not execute" +""" + |> withOptions ["--typecheck-only"] + |> compile + |> shouldSucceed + +[] +let ``typecheck-only flag catches type errors``() = + Fsx """ +let x: int = "string" // Type error +""" + |> withOptions ["--typecheck-only"] + |> compile + |> shouldFail + |> withDiagnostics [ + (Error 1, Line 2, Col 14, Line 2, Col 22, "This expression was expected to have type\n 'int' \nbut here has type\n 'string'") + ] + +[] +let ``typecheck-only flag prevents execution side effects``() = + Fsx """ +printfn "MyCrazyString" +let x = 42 +""" + |> withOptions ["--typecheck-only"] + |> runFsi + |> shouldSucceed + |> VerifyNotInOutput "MyCrazyString" + +[] +let ``script executes without typecheck-only flag``() = + Fsx """ +printfn "MyCrazyString" +let x = 42 +""" + |> runFsi + |> shouldSucceed + |> verifyOutput "MyCrazyString" \ No newline at end of file diff --git a/tests/FSharp.Test.Utilities/Compiler.fs b/tests/FSharp.Test.Utilities/Compiler.fs index e3c9269a5bd..ecf0bd54642 100644 --- a/tests/FSharp.Test.Utilities/Compiler.fs +++ b/tests/FSharp.Test.Utilities/Compiler.fs @@ -1421,6 +1421,18 @@ Actual: failwith $"""Output does not match expected:{Environment.NewLine}{item}{Environment.NewLine}Actual:{Environment.NewLine}{actual}{Environment.NewLine}""" cResult + let verifyNotInOutput (expected: string) (cResult: CompilationResult) : CompilationResult = + match getOutput cResult with + | None -> cResult + | Some actual -> + if actual.Contains(expected) then + failwith $"""Output should not contain:{Environment.NewLine}{expected}{Environment.NewLine}Actual:{Environment.NewLine}{actual}{Environment.NewLine}""" + else + cResult + + let VerifyNotInOutput (expected: string) (cResult: CompilationResult) : CompilationResult = + verifyNotInOutput expected cResult + type ImportScope = { Kind: ImportDefinitionKind; Name: string } type PdbVerificationOption = @@ -1906,6 +1918,13 @@ Actual: let withStdOutContains (substring: string) (result: CompilationResult) : CompilationResult = checkOutputInOrder "STDOUT" [substring] (fun o -> o.StdOut) result + let withStdOutNotContains (substring: string) (result: CompilationResult) : CompilationResult = + let checkOutput = fun o -> o.StdOut + let output = checkOutput result.Output + if output.Contains(substring) then + failwith $"""STDOUT should not contain:{Environment.NewLine}{substring}{Environment.NewLine}Actual:{Environment.NewLine}{output}{Environment.NewLine}""" + result + let withStdOutContainsAllInOrder (substrings: string list) (result: CompilationResult) : CompilationResult = checkOutputInOrder "STDOUT" substrings (fun o -> o.StdOut) result