From 7d0c0a30963ea3f9a39b21e811e70e0c59d34955 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Mon, 31 Aug 2026 19:31:20 +0000 Subject: [PATCH] The Terminal's compiler service is checked rather than warned about NU1608 has said on every Terminal publish since 2.3.0 that FSharp.Compiler.Service 43.9.300 wants FSharp.Core 9.0.300 and got 10.1, sixteen times per run, and nobody had checked which of the two readings was right. #1100 asked for an hour of running the Terminal against something non-trivial. **It is benign.** A kernel built on the shipped pair compiles generic functions and inference, records, discriminated unions, classes with members, units of measure, async, sequence expressions, list comprehensions, active patterns, tuple destructuring, recursion, `sprintf`, string interpolation, `inline` generic constraints and an object expression over an interface: sixteen of sixteen. It also still refuses a type error and a syntax error, each with the position the Terminal shows to whoever typed it -- a compiler that accepted everything would pass the first sixteen and be useless. So the warning is suppressed, and the claim it was making is carried by `CompilerServiceWorksTest` instead. That is the point of the change rather than a side effect: a warning nobody reads is not a check, and this one had been passing through CI unread for two releases. If FSharp.Core or FSharp.Compiler.Service moves and the pair really does come apart, a test fails where before a sixteen-line warning would have scrolled past again. The constraint cannot be met, which is why suppressing is the option rather than pinning: the 10.1 is the FSharp.Core the F# SDK references implicitly for every F# project, and satisfying FSharp.Compiler.Service would mean pinning two majors back, against the SDK, across the graph. Measured: 16 NU1608 on `dotnet pack` before, 0 after, and 0 NuGet warnings of any kind on the publish path. The tests use a bare kernel deliberately. The Terminal opens `AggressiveOperators` over the top, which rebinds the comparison operators to build expressions, and that is the tool's whole point -- but it means `if a < b then` does not typecheck in the Terminal, which is a separate question and is filed as one rather than smuggled in here. Part of #1100. --- .../AngouriMath.Terminal.Lib.fsproj | 20 +++++ .../AngouriMath.Terminal.fsproj | 7 ++ .../CompilerServiceWorksTest.fs | 84 +++++++++++++++++++ .../TerminalUnitTests.fsproj | 1 + 4 files changed, 112 insertions(+) create mode 100644 Sources/Tests/TerminalUnitTests/CompilerServiceWorksTest.fs diff --git a/Sources/Terminal/AngouriMath.Terminal.Lib/AngouriMath.Terminal.Lib.fsproj b/Sources/Terminal/AngouriMath.Terminal.Lib/AngouriMath.Terminal.Lib.fsproj index 6faadc314..7a35cb087 100644 --- a/Sources/Terminal/AngouriMath.Terminal.Lib/AngouriMath.Terminal.Lib.fsproj +++ b/Sources/Terminal/AngouriMath.Terminal.Lib/AngouriMath.Terminal.Lib.fsproj @@ -22,6 +22,26 @@ https://github.com/asc-community/AngouriMath/issues/542 --> false + + + $(NoWarn);NU1608 diff --git a/Sources/Terminal/AngouriMath.Terminal/AngouriMath.Terminal.fsproj b/Sources/Terminal/AngouriMath.Terminal/AngouriMath.Terminal.fsproj index 28e902cd5..96abe3fd5 100644 --- a/Sources/Terminal/AngouriMath.Terminal/AngouriMath.Terminal.fsproj +++ b/Sources/Terminal/AngouriMath.Terminal/AngouriMath.Terminal.fsproj @@ -14,6 +14,13 @@ F#-based command line interface for symbolic algebra library AngouriMath $(PackageTags), cli, terminal + + + $(NoWarn);NU1608 diff --git a/Sources/Tests/TerminalUnitTests/CompilerServiceWorksTest.fs b/Sources/Tests/TerminalUnitTests/CompilerServiceWorksTest.fs new file mode 100644 index 000000000..2af0c233b --- /dev/null +++ b/Sources/Tests/TerminalUnitTests/CompilerServiceWorksTest.fs @@ -0,0 +1,84 @@ +/// The Terminal bundles FSharp.Compiler.Service beside an FSharp.Core two majors above the one +/// it declares it needs, and NU1608 has said so on every publish since 2.3.0. This is what +/// settles whether that matters: the pair is asked to compile F#, and told to. +/// https://github.com/asc-community/AngouriMath/issues/1100 +module AngouriMath.Terminal.CompilerServiceWorksTest + +open Xunit +open AngouriMath.Terminal.Lib.FSharpInteractive +open AngouriMath.Terminal.Lib.PreRunCode +open AngouriMath.Terminal.Lib.Consts + +/// A kernel with nothing loaded into it, so what is under test is the compiler rather than +/// AngouriMath's own bindings. The Terminal opens AggressiveOperators over the top, which +/// deliberately rebinds the comparison operators to build expressions — that is the tool's whole +/// point and it is not what this asks about. +let private bareKernel () = + match createKernel () with + | Result.Error _ -> + Assert.True(false, "the kernel did not load at all") + raise (System.Exception()) + | Result.Ok kernel -> kernel + +let private evaluates (code: string) (expected: string) = + match execute (bareKernel ()) code with + | ExecutionResult.PlainTextSuccess actual -> Assert.Equal(expected, actual) + | other -> + Assert.True(false, $"expected {expected}, and the kernel answered {other}") + +/// Each of these is a language feature the compiler service has to implement rather than a +/// value the parser can fold, so a mismatched FSharp.Core would show here rather than in +/// arithmetic. Sixteen of sixteen pass on the shipped pair. +[] +// Inference and generics. +[ n * 2) 5", "20")>] +[] +// Type definitions of every shape. +[] +[ n | B -> 0", "7")>] +[] +[] type m\nlet d = 5.0\nfloat d", "5")>] +// Computation expressions and the library that backs them. +[ Async.RunSynchronously", "2")>] +[ Seq.filter (fun x -> x % 7 = 0) |> Seq.length", "14")>] +[ i * i ] |> List.sum", "385")>] +// Pattern matching, including one the compiler generates code for. +[ \"e\" | Odd -> \"o\"", "e")>] +[ List.map (fun (a,b) -> a + b)", "[3; 7]")>] +[ 0 then Some x else None\nOption.defaultValue 0 (f 5)", "5")>] +// Recursion, formatting, interpolation, and an object expression over an interface. +[] +[] +[] +[ with member _.CompareTo o = 0 }\nc.CompareTo 1", "0")>] +let ``The compiler service compiles F#`` (code: string) (expected: string) = + evaluates code expected + +/// And it still refuses what it should, with a position — a compiler that accepted everything +/// would pass the theory above and be useless. +[] +[] +[] +let ``The compiler service reports errors`` (code: string) (expected: string) = + match execute (bareKernel ()) code with + | ExecutionResult.Error message -> + Assert.Contains(expected, message) + // A position, not just a complaint: the Terminal shows these to whoever typed them. + Assert.Contains("input.fsx (1,", message) + | other -> + Assert.True(false, $"expected an error and the kernel answered {other}") + +/// The pairing the warning is about, doing the work the Terminal exists for. +[] +[] +[] +[] +let ``AngouriMath works through the compiler service`` (code: string) (expected: string) = + let kernel = bareKernel () + match enableAngouriMath kernel with + | ExecutionResult.Error reason -> Assert.True(false, $"AngouriMath did not load: {reason}") + | _ -> () + match execute kernel code with + | ExecutionResult.LatexSuccess (_, actual) -> Assert.Equal(expected, actual) + | ExecutionResult.PlainTextSuccess actual -> Assert.Equal(expected, actual) + | other -> Assert.True(false, $"expected {expected}, and the kernel answered {other}") diff --git a/Sources/Tests/TerminalUnitTests/TerminalUnitTests.fsproj b/Sources/Tests/TerminalUnitTests/TerminalUnitTests.fsproj index 96627fbd0..1a3855286 100644 --- a/Sources/Tests/TerminalUnitTests/TerminalUnitTests.fsproj +++ b/Sources/Tests/TerminalUnitTests/TerminalUnitTests.fsproj @@ -9,6 +9,7 @@ +