From 6ba4d2c672151596086a71e132ffee73b8ae91af Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Oct 2025 09:16:14 +0000 Subject: [PATCH 1/3] Initial plan From fd8508f58f0eff6eb6c8f224341467e83357ce07 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Oct 2025 09:50:56 +0000 Subject: [PATCH 2/3] Add FSharpPlus regression tests for F# 9 issues Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com> --- .../FSharp.Compiler.ComponentTests.fsproj | 1 + .../Language/FSharpPlusRegressionTests.fs | 130 ++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/FSharpPlusRegressionTests.fs diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 29847415be2..c3641d31f65 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -234,6 +234,7 @@ + diff --git a/tests/FSharp.Compiler.ComponentTests/Language/FSharpPlusRegressionTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/FSharpPlusRegressionTests.fs new file mode 100644 index 00000000000..5aa448516d4 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/FSharpPlusRegressionTests.fs @@ -0,0 +1,130 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Language + +open System +open Xunit +open FSharp.Test.ScriptHelpers +open FSharp.Compiler.Diagnostics + +module FSharpPlusRegressionTests = + + /// + /// Regression test for FSharpPlus issue #613 - monad.plus usage scenario. + /// This test reproduces a consumer-side failure where using monad.plus in F# 9 + /// causes compilation issues. The code should compile successfully. + /// Issue: https://github.com/fsprojects/FSharpPlus/issues/613 + /// + [] + [] + [] + [] + let ``monad.plus usage should compile successfully`` (langVersion: string, [] additionalArgs: string[]) = + let allArgs = Array.concat [[| "--langversion:" + langVersion |]; additionalArgs] + use script = new FSharpScript(additionalArgs = allArgs, quiet = true) + + let code = """ +// Simulated monad.plus pattern from FSharpPlus +// This pattern uses statically resolved type parameters (SRTP) for ad-hoc polymorphism +type MonadPlusClass = + static member inline MPlus (x: option<'a>, y: option<'a>) = + match x with + | Some _ -> x + | None -> y + + static member inline MPlus (x: list<'a>, y: list<'a>) = x @ y + +// Generic mplus function using SRTP to dispatch to appropriate implementation +let inline mplus (x: ^M) (y: ^M) : ^M = + ((^MonadPlusClass or ^M) : (static member MPlus : ^M * ^M -> ^M) (x, y)) + +// Direct usage with concrete types +let testOption() = + let result : int option = mplus (Some 1) (Some 2) + printfn "Option result = %A" result + +let testList() = + let result : int list = mplus [1; 2] [3; 4] + printfn "List result = %A" result + +testOption() +testList() +""" + + let evalResult, diagnostics = script.Eval(code) + + // The code should compile successfully + match evalResult with + | Ok _ -> + // Filter out informational diagnostics + let errors = diagnostics |> Array.filter (fun d -> + d.Severity = FSharpDiagnosticSeverity.Error) + Assert.Empty(errors) + | Error ex -> + Assert.True(false, sprintf "Evaluation failed with exception: %s\nDiagnostics: %A" ex.Message diagnostics) + + /// + /// Regression test for FSharpPlus issue #613 - custom ResultTBuilder scenario. + /// This test reproduces a consumer-side failure where defining a custom ResultTBuilder + /// in F# 9 causes compilation issues. The code should compile successfully. + /// Issue: https://github.com/fsprojects/FSharpPlus/issues/613 + /// + [] + [] + [] + [] + let ``custom ResultTBuilder should compile successfully`` (langVersion: string, [] additionalArgs: string[]) = + let allArgs = Array.concat [[| "--langversion:" + langVersion |]; additionalArgs] + use script = new FSharpScript(additionalArgs = allArgs, quiet = true) + + let code = """ +// Custom ResultTBuilder pattern from FSharpPlus +type ResultTBuilder() = + member inline _.Return(x: 'T) : Result<'T, 'Error> = Ok x + + member inline _.ReturnFrom(m: Result<'T, 'Error>) : Result<'T, 'Error> = m + + member inline _.Bind(m: Result<'T, 'Error>, f: 'T -> Result<'U, 'Error>) : Result<'U, 'Error> = + match m with + | Ok x -> f x + | Error e -> Error e + + member inline _.Zero() : Result = Ok () + + member inline _.Combine(m1: Result, m2: Result<'T, 'Error>) : Result<'T, 'Error> = + match m1 with + | Ok () -> m2 + | Error e -> Error e + + member inline _.Delay(f: unit -> Result<'T, 'Error>) : unit -> Result<'T, 'Error> = f + + member inline _.Run(f: unit -> Result<'T, 'Error>) : Result<'T, 'Error> = f() + +let resultT = ResultTBuilder() + +// Usage example +let compute x y = + resultT { + let! a = Ok x + let! b = Ok y + return a + b + } + +// Apply the function to avoid value restriction +let testResult : Result = compute 5 10 + +// Verify result +printfn "testResult = %A" testResult +""" + + let evalResult, diagnostics = script.Eval(code) + + // The code should compile successfully + match evalResult with + | Ok _ -> + // Filter out informational diagnostics + let errors = diagnostics |> Array.filter (fun d -> + d.Severity = FSharpDiagnosticSeverity.Error) + Assert.Empty(errors) + | Error ex -> + Assert.True(false, sprintf "Evaluation failed with exception: %s\nDiagnostics: %A" ex.Message diagnostics) From 0fafa17c432a945bed5ba619335430e999e4908a Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Fri, 5 Dec 2025 10:38:11 +0100 Subject: [PATCH 3/3] address fsharpplus consumer regression --- src/Compiler/Checking/CheckDeclarations.fs | 2 +- .../Conformance/Inference/SRTP.fs | 33 ++++++++ .../Conformance/Inference/SRTP_NuGet.fs | 25 ++++++ .../FSharp.Compiler.ComponentTests.fsproj | 2 + .../FSharpPlusRegressionTests.fs | 78 +++++++++++++++++++ 5 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 tests/FSharp.Compiler.ComponentTests/Conformance/Inference/SRTP.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Conformance/Inference/SRTP_NuGet.fs create mode 100644 tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharpPlusRegressionTests.fs diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs index e230cd48280..28c7cb46ce2 100644 --- a/src/Compiler/Checking/CheckDeclarations.fs +++ b/src/Compiler/Checking/CheckDeclarations.fs @@ -5650,7 +5650,7 @@ let rec IterTyconsOfModuleOrNamespaceType f (mty: ModuleOrNamespaceType) = // Defaults get applied in priority order. Defaults listed last get priority 0 (lowest), 2nd last priority 1 etc. let ApplyDefaults (cenv: cenv) g denvAtEnd m moduleContents extraAttribs = try - let unsolved = FindUnsolved.UnsolvedTyparsOfModuleDef g cenv.amap denvAtEnd moduleContents extraAttribs + let unsolved = FindUnsolved.UnsolvedTyparsOfModuleDef g cenv.amap denvAtEnd moduleContents extraAttribs |> List.rev CanonicalizePartialInferenceProblem cenv.css denvAtEnd m unsolved diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Inference/SRTP.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Inference/SRTP.fs new file mode 100644 index 00000000000..8276c80a9ef --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Inference/SRTP.fs @@ -0,0 +1,33 @@ +namespace FSharp.Compiler.ComponentTests.Conformance.Inference + +open FSharp.Test +open Xunit + +module SRTP = + + [] + let ``SRTP resolution with curryN and Tuple`` () = + let source = """ +module SRTP_Repro + +open System + +type Curry = + static member inline Invoke f = + let inline call_2 (a: ^a, b: ^b) = ((^a or ^b) : (static member Curry: _*_ -> _) b, a) + call_2 (Unchecked.defaultof, Unchecked.defaultof<'t>) (f: 't -> 'r) : 'args + + static member Curry (_: Tuple<'t1> , _: Curry) = fun f t1 -> f (Tuple<_> t1) + static member Curry (_: Tuple<'t1, 't2> , _: Curry) = fun f t1 t2 -> f (Tuple<_,_>(t1, t2)) + +let inline curryN (f: (^``T1 * ^T2 * ... * ^Tn``) -> 'Result) : 'T1 -> '``T2 -> ... -> 'Tn -> 'Result`` = fun t -> Curry.Invoke f t + +let f1 (x: Tuple<_>) = [x.Item1] +let f2 (x: Tuple<_,_>) = [x.Item1; x.Item2] + +let test () = + let _x1 = curryN f1 100 + let _x2 = curryN f2 10 20 + () +""" + CompilerAssert.TypeCheckWithErrors(source) diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Inference/SRTP_NuGet.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Inference/SRTP_NuGet.fs new file mode 100644 index 00000000000..7a34e5a8945 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Inference/SRTP_NuGet.fs @@ -0,0 +1,25 @@ +namespace FSharp.Compiler.ComponentTests.Conformance.Inference + +open FSharp.Test +open Xunit + +module SRTP_NuGet = + + [] + let ``SRTP resolution with curryN and Tuple from FSharpPlus NuGet`` () = + CompilerAssert.RunScriptWithOptions + [| "--langversion:preview"; "--source"; "https://api.nuget.org/v3/index.json" |] + """ +#r "nuget: FSharpPlus, 1.6.1" +open FSharpPlus +open System + +let f1 (x: Tuple<_>) = [x.Item1] + +let test () = + let _x1 = curryN f1 100 + () + +test() +""" + [] diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index c22d5f95004..adb463ec232 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -128,6 +128,8 @@ + + diff --git a/tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharpPlusRegressionTests.fs b/tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharpPlusRegressionTests.fs new file mode 100644 index 00000000000..dc52eb6a019 --- /dev/null +++ b/tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharpPlusRegressionTests.fs @@ -0,0 +1,78 @@ +namespace FSharp.Compiler.Scripting.UnitTests + +open System +open FSharp.Compiler.Interactive.Shell +open FSharp.Test.ScriptHelpers +open Xunit + +type FSharpPlusRegressionTests() = + + [] + member _.``FSharpPlus Regression Test 1``() = + use script = new FSharpScript() + let code = """ +#r "nuget: FSharpPlus, 1.8.0" + +open FSharpPlus + +let y: seq<_> = monad.plus { + for x in seq [1..3] do + for y in seq [10; 20] do + return (x, y) +} +""" + let result, errors = script.Eval(code) + if errors.Length > 0 then + let msg = errors |> Array.map (fun e -> e.Message) |> String.concat "\n" + Assert.Fail($"Script failed with errors:\n{msg}") + + match result with + | Ok(_) -> () + | Error(ex) -> Assert.Fail($"Script failed with exception: {ex}") + + [] + member _.``FSharpPlus Regression Test 2``() = + use script = new FSharpScript() + let code = """ +#r "nuget: FSharpPlus, 1.8.0" + +open FSharpPlus +open FSharpPlus.Data + +type AsyncResult<'T, 'E> = ResultT>> + +type ResultTBuilder<'``monad>``>() = + inherit Builder>``>>() + + member inline _.For (x: ResultT<'``Monad>``>, f: 'T -> ResultT<'``Monad>``>) = x >>= f : ResultT<'``Monad>``> + + [] + member inline _.Lift (x: ResultT<'``Monad>``>, m: '``Monad<'U>``, f: 'T -> 'U -> 'V) = + x >>= fun a -> + lift m |> ResultT.bind (fun b -> + result (f a b) : ResultT<'``Monad>``>) + +let resultT<'``Monad>``> = new ResultTBuilder<'``Monad>``>() + +let sampleWorkflow2 = + monad { + let! x = Some 1 + let! y = Some 2 + return x + y + } + +let test2 () = + resultT { + let! x = ResultT.hoist (Ok 1) + lift y in sampleWorkflow2 + return x + y + } +""" + let result, errors = script.Eval(code) + if errors.Length > 0 then + let msg = errors |> Array.map (fun e -> e.Message) |> String.concat "\n" + Assert.Fail($"Script failed with errors:\n{msg}") + + match result with + | Ok(_) -> () + | Error(ex) -> Assert.Fail($"Script failed with exception: {ex}")