From 6c25d66d276cf67cc967e82d539b799013e168a2 Mon Sep 17 00:00:00 2001 From: majocha <1760221+majocha@users.noreply.github.com> Date: Sat, 1 Aug 2026 13:12:50 +0200 Subject: [PATCH 01/21] Add regression repro for parallel cross-assembly inline overloads Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- ...on_ParallelCrossAssemblyInlineOverloads.fs | 42 +++++++++++++++++++ .../FSharp.Compiler.ComponentTests.fsproj | 1 + 2 files changed, 43 insertions(+) create mode 100644 tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_ParallelCrossAssemblyInlineOverloads.fs diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_ParallelCrossAssemblyInlineOverloads.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_ParallelCrossAssemblyInlineOverloads.fs new file mode 100644 index 00000000000..92c09310a3f --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_ParallelCrossAssemblyInlineOverloads.fs @@ -0,0 +1,42 @@ +namespace EmittedIL.Inlining + +open Xunit +open FSharp.Test +open FSharp.Test.Compiler + +module Regression_ParallelCrossAssemblyInlineOverloads = + + [] + let ``Cross-assembly overloaded inline static members compile and run`` () = + let library = + FSharp """ +module Library + +type InlineOps = + static member inline Source (x: int) : int = x + 1 + static member inline Source (x: string) : string = x + "!" +""" + |> withOutputType CompileOutput.Library + |> withName "Library" + |> withOptimize + + let consumer = + FSharp """ +module Consumer + +open Library + +[] +let main _ = + let x = InlineOps.Source 41 + let y = InlineOps.Source "hello" + if x = 42 && y = "hello!" then 0 else 1 +""" + |> withOutputType CompileOutput.Exe + |> withReferences [ library ] + |> withOptimize + + consumer + |> compileAndRun + |> shouldSucceed + |> ignore diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index e50201ba8f9..4a853801879 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -276,6 +276,7 @@ + From 54b5fbe056ef673b31ce5a447c658f2006fa9183 Mon Sep 17 00:00:00 2001 From: majocha <1760221+majocha@users.noreply.github.com> Date: Sat, 1 Aug 2026 15:55:12 +0200 Subject: [PATCH 02/21] Add regression test for parallel cross-assembly inline overloads Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- ...on_ParallelCrossAssemblyInlineOverloads.fs | 153 ++++++++++++++---- 1 file changed, 126 insertions(+), 27 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_ParallelCrossAssemblyInlineOverloads.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_ParallelCrossAssemblyInlineOverloads.fs index 92c09310a3f..22310a3838f 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_ParallelCrossAssemblyInlineOverloads.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_ParallelCrossAssemblyInlineOverloads.fs @@ -7,36 +7,135 @@ open FSharp.Test.Compiler module Regression_ParallelCrossAssemblyInlineOverloads = [] - let ``Cross-assembly overloaded inline static members compile and run`` () = - let library = - FSharp """ -module Library - -type InlineOps = - static member inline Source (x: int) : int = x + 1 - static member inline Source (x: string) : string = x + "!" + let ``Cross-assembly overloaded inline Source members compile and run`` () = + let library = + ( + FSharp """ +module LibraryImpl + +open System.Threading.Tasks +open Microsoft.FSharp.Control + +module Result = + let ofChoice choice = + match choice with + | Choice1Of2 value -> Ok value + | Choice2Of2 error -> Error error + +module Async = + let singleton value = async.Return value + +type Validation<'ok, 'error> = Result<'ok, 'error> +type TaskValidation<'ok, 'error> = Task> + +type TaskValidationBuilderBase() = + member inline _.Return(value: 'ok) : TaskValidation<'ok, 'error> = + task { return Ok value } + + member inline _.ReturnFrom(taskValidation: TaskValidation<'ok, 'error>) : TaskValidation<'ok, 'error> = + taskValidation + + member inline this.Bind + (source: Validation<'okInput, 'error>, binder: 'okInput -> TaskValidation<'okOutput, 'error>) + : TaskValidation<'okOutput, 'error> = + task { + let! result = this.Source source + match result with + | Ok value -> return! binder value + | Error error -> return Error error + } + + member inline this.Bind + (source: Choice<'okInput, 'error>, binder: 'okInput -> TaskValidation<'okOutput, 'error>) + : TaskValidation<'okOutput, 'error> = + task { + let! result = this.Source source + match result with + | Ok value -> return! binder value + | Error error -> return Error error + } + + member inline this.Bind + (source: Async<'okInput>, binder: 'okInput -> TaskValidation<'okOutput, 'error>) + : TaskValidation<'okOutput, 'error> = + task { + let! result = this.Source source + match result with + | Ok value -> return! binder value + | Error error -> return Error error + } + + member inline _.Delay(generator: unit -> TaskValidation<'ok, 'error>) : TaskValidation<'ok, 'error> = + generator () + + member inline this.Source(result: Validation<'ok, 'error>) : TaskValidation<'ok, 'error> = + task { return result } + + member inline this.Source(choice: Choice<'ok, 'error>) : TaskValidation<'ok, 'error> = + task { + return + choice + |> Result.ofChoice + } + + member inline this.Source(asyncComputation: Async<'ok>) : TaskValidation<'ok, 'error> = + task { + let! value = asyncComputation + return Ok value + } + +type TaskValidationBuilder() = + inherit TaskValidationBuilderBase() + +let taskValidation = TaskValidationBuilder() """ - |> withOutputType CompileOutput.Library - |> withName "Library" - |> withOptimize + |> withAdditionalSourceFile (SourceCodeFileKind.Create("Library.Support.fs", """ +module LibraryImplSupport - let consumer = - FSharp """ -module Consumer +let taskValidation = LibraryImpl.taskValidation +""")) + |> withOutputType CompileOutput.Library + |> withName "Library" + |> withOptimize + |> withOptions ["--parallelcompilation+"; "--nowarn:75"] + |> ignoreWarnings + ) -open Library + let consumerSource = + "module ConsumerImpl\n\nopen LibraryImpl\nopen LibraryImplSupport\n\nlet run () =\n taskValidation {\n let! asyncValue = Async.singleton 42\n let! resultValue = Ok 42\n let! choiceValue = Choice1Of2 42\n return asyncValue + resultValue + choiceValue\n }\n" + + let consumerAdditionalSources = + Array.init 12 (fun i -> + let source = + "module Consumer" + + string i + + "\n\nopen LibraryImpl\nopen LibraryImplSupport\n\nlet run () =\n taskValidation {\n let! asyncValue = Async.singleton 42\n let! resultValue = Ok 42\n let! choiceValue = Choice1Of2 42\n return asyncValue + resultValue + choiceValue\n }\n" + + SourceCodeFileKind.Create(sprintf "Consumer.%d.fs" i, source)) + |> Array.toList + + let consumer = + ( + FSharp consumerSource + |> withAdditionalSourceFiles consumerAdditionalSources + |> withAdditionalSourceFile (SourceCodeFileKind.Create("Consumer.Support.fs", """ +module ConsumerSupport [] let main _ = - let x = InlineOps.Source 41 - let y = InlineOps.Source "hello" - if x = 42 && y = "hello!" then 0 else 1 -""" - |> withOutputType CompileOutput.Exe - |> withReferences [ library ] - |> withOptimize - - consumer - |> compileAndRun - |> shouldSucceed - |> ignore + match ConsumerImpl.run () |> Async.RunSynchronously with + | Ok value -> if value = 126 then 0 else 1 + | Error _ -> 1 +""")) + |> withOutputType CompileOutput.Exe + |> withReferences [ library ] + |> withOptimize + |> withOptions ["--parallelcompilation+"; "--nowarn:75"] + |> ignoreWarnings + ) + + for _i = 1 to 30 do + consumer + |> compile + |> shouldSucceed + |> ignore From 3f5f34c2f8310dea52e7b53a702c14a57f2980b8 Mon Sep 17 00:00:00 2001 From: majocha <1760221+majocha@users.noreply.github.com> Date: Sat, 1 Aug 2026 17:22:18 +0200 Subject: [PATCH 03/21] tighten the regression fixture --- ...ssion_ParallelCrossAssemblyInlineOverloads.fs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_ParallelCrossAssemblyInlineOverloads.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_ParallelCrossAssemblyInlineOverloads.fs index 22310a3838f..7967309b348 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_ParallelCrossAssemblyInlineOverloads.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_ParallelCrossAssemblyInlineOverloads.fs @@ -35,6 +35,16 @@ type TaskValidationBuilderBase() = member inline _.ReturnFrom(taskValidation: TaskValidation<'ok, 'error>) : TaskValidation<'ok, 'error> = taskValidation + member inline _.Bind + (source: TaskValidation<'okInput, 'error>, binder: 'okInput -> TaskValidation<'okOutput, 'error>) + : TaskValidation<'okOutput, 'error> = + task { + let! result = source + match result with + | Ok value -> return! binder value + | Error error -> return Error error + } + member inline this.Bind (source: Validation<'okInput, 'error>, binder: 'okInput -> TaskValidation<'okOutput, 'error>) : TaskValidation<'okOutput, 'error> = @@ -102,14 +112,14 @@ let taskValidation = LibraryImpl.taskValidation ) let consumerSource = - "module ConsumerImpl\n\nopen LibraryImpl\nopen LibraryImplSupport\n\nlet run () =\n taskValidation {\n let! asyncValue = Async.singleton 42\n let! resultValue = Ok 42\n let! choiceValue = Choice1Of2 42\n return asyncValue + resultValue + choiceValue\n }\n" + "module ConsumerImpl\n\nopen LibraryImpl\nopen LibraryImplSupport\n\nlet run () =\n taskValidation.Bind(\n Async.singleton 42,\n fun asyncValue ->\n taskValidation.Bind(\n Ok 42,\n fun resultValue ->\n taskValidation.Bind(\n Choice1Of2 42,\n fun choiceValue ->\n taskValidation.Return(asyncValue + resultValue + choiceValue))))\n" let consumerAdditionalSources = Array.init 12 (fun i -> let source = "module Consumer" + string i - + "\n\nopen LibraryImpl\nopen LibraryImplSupport\n\nlet run () =\n taskValidation {\n let! asyncValue = Async.singleton 42\n let! resultValue = Ok 42\n let! choiceValue = Choice1Of2 42\n return asyncValue + resultValue + choiceValue\n }\n" + + "\n\nopen LibraryImpl\nopen LibraryImplSupport\n\nlet run () =\n taskValidation.Bind(\n Async.singleton 42,\n fun asyncValue ->\n taskValidation.Bind(\n Ok 42,\n fun resultValue ->\n taskValidation.Bind(\n Choice1Of2 42,\n fun choiceValue ->\n taskValidation.Return(asyncValue + resultValue + choiceValue))))\n" SourceCodeFileKind.Create(sprintf "Consumer.%d.fs" i, source)) |> Array.toList @@ -123,7 +133,7 @@ module ConsumerSupport [] let main _ = - match ConsumerImpl.run () |> Async.RunSynchronously with + match (ConsumerImpl.run ()).GetAwaiter().GetResult() with | Ok value -> if value = 126 then 0 else 1 | Error _ -> 1 """)) From fd22df98f46fd1a9c8167db6b2c8ae904cb07e96 Mon Sep 17 00:00:00 2001 From: majocha <1760221+majocha@users.noreply.github.com> Date: Sat, 1 Aug 2026 18:05:02 +0200 Subject: [PATCH 04/21] add sequential repro --- ...on_ParallelCrossAssemblyInlineOverloads.fs | 97 +++++++++++-------- 1 file changed, 58 insertions(+), 39 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_ParallelCrossAssemblyInlineOverloads.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_ParallelCrossAssemblyInlineOverloads.fs index 7967309b348..1ca63330fa9 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_ParallelCrossAssemblyInlineOverloads.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_ParallelCrossAssemblyInlineOverloads.fs @@ -6,11 +6,8 @@ open FSharp.Test.Compiler module Regression_ParallelCrossAssemblyInlineOverloads = - [] - let ``Cross-assembly overloaded inline Source members compile and run`` () = - let library = - ( - FSharp """ + let private librarySource = + FSharp """ module LibraryImpl open System.Threading.Tasks @@ -99,36 +96,45 @@ type TaskValidationBuilder() = let taskValidation = TaskValidationBuilder() """ - |> withAdditionalSourceFile (SourceCodeFileKind.Create("Library.Support.fs", """ + + let private mkLibrary options = + librarySource + |> withAdditionalSourceFile ( + SourceCodeFileKind.Create( + "Library.Support.fs", + """ module LibraryImplSupport let taskValidation = LibraryImpl.taskValidation -""")) - |> withOutputType CompileOutput.Library - |> withName "Library" - |> withOptimize - |> withOptions ["--parallelcompilation+"; "--nowarn:75"] - |> ignoreWarnings +""" ) - - let consumerSource = - "module ConsumerImpl\n\nopen LibraryImpl\nopen LibraryImplSupport\n\nlet run () =\n taskValidation.Bind(\n Async.singleton 42,\n fun asyncValue ->\n taskValidation.Bind(\n Ok 42,\n fun resultValue ->\n taskValidation.Bind(\n Choice1Of2 42,\n fun choiceValue ->\n taskValidation.Return(asyncValue + resultValue + choiceValue))))\n" - - let consumerAdditionalSources = - Array.init 12 (fun i -> - let source = - "module Consumer" - + string i - + "\n\nopen LibraryImpl\nopen LibraryImplSupport\n\nlet run () =\n taskValidation.Bind(\n Async.singleton 42,\n fun asyncValue ->\n taskValidation.Bind(\n Ok 42,\n fun resultValue ->\n taskValidation.Bind(\n Choice1Of2 42,\n fun choiceValue ->\n taskValidation.Return(asyncValue + resultValue + choiceValue))))\n" - - SourceCodeFileKind.Create(sprintf "Consumer.%d.fs" i, source)) - |> Array.toList - - let consumer = - ( - FSharp consumerSource - |> withAdditionalSourceFiles consumerAdditionalSources - |> withAdditionalSourceFile (SourceCodeFileKind.Create("Consumer.Support.fs", """ + ) + |> withOutputType CompileOutput.Library + |> withName "Library" + |> withOptimize + |> withOptions options + |> ignoreWarnings + + let private consumerSource = + "module ConsumerImpl\n\nopen LibraryImpl\nopen LibraryImplSupport\n\nlet run () =\n taskValidation.Bind(\n Async.singleton 42,\n fun asyncValue ->\n taskValidation.Bind(\n Ok 42,\n fun resultValue ->\n taskValidation.Bind(\n Choice1Of2 42,\n fun choiceValue ->\n taskValidation.Return(asyncValue + resultValue + choiceValue))))\n" + + let private consumerAdditionalSources = + Array.init 12 (fun i -> + let source = + "module Consumer" + + string i + + "\n\nopen LibraryImpl\nopen LibraryImplSupport\n\nlet run () =\n taskValidation.Bind(\n Async.singleton 42,\n fun asyncValue ->\n taskValidation.Bind(\n Ok 42,\n fun resultValue ->\n taskValidation.Bind(\n Choice1Of2 42,\n fun choiceValue ->\n taskValidation.Return(asyncValue + resultValue + choiceValue))))\n" + + SourceCodeFileKind.Create(sprintf "Consumer.%d.fs" i, source)) + |> Array.toList + + let private mkConsumer options library = + FSharp consumerSource + |> withAdditionalSourceFiles consumerAdditionalSources + |> withAdditionalSourceFile ( + SourceCodeFileKind.Create( + "Consumer.Support.fs", + """ module ConsumerSupport [] @@ -136,16 +142,29 @@ let main _ = match (ConsumerImpl.run ()).GetAwaiter().GetResult() with | Ok value -> if value = 126 then 0 else 1 | Error _ -> 1 -""")) - |> withOutputType CompileOutput.Exe - |> withReferences [ library ] - |> withOptimize - |> withOptions ["--parallelcompilation+"; "--nowarn:75"] - |> ignoreWarnings +""" ) - - for _i = 1 to 30 do + ) + |> withOutputType CompileOutput.Exe + |> withReferences [ library ] + |> withOptimize + |> withOptions options + |> ignoreWarnings + + let private assertCompiles repeatCount options = + let library = mkLibrary options + let consumer = mkConsumer options library + + for _i = 1 to repeatCount do consumer |> compile |> shouldSucceed |> ignore + + [] + let ``Cross-assembly overloaded inline Source members compile and run`` () = + assertCompiles 30 [ "--parallelcompilation+"; "--nowarn:75" ] + + [] + let ``Cross-assembly overloaded inline Source members compile and run under sequential compilation`` () = + assertCompiles 1 [ "--parallelcompilation-"; "--nowarn:75" ] From f7e7e9eedd6c1efdd765aadb2b98f80077eceae8 Mon Sep 17 00:00:00 2001 From: majocha <1760221+majocha@users.noreply.github.com> Date: Sat, 1 Aug 2026 22:12:27 +0200 Subject: [PATCH 05/21] Generalize recursive inline member ordering fix Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/Compiler/Optimize/Optimizer.fs | 114 ++++++++- ...on_ParallelCrossAssemblyInlineOverloads.fs | 170 ------------- ...ssion_RecursiveInlineMemberDependencies.fs | 226 ++++++++++++++++++ .../FSharp.Compiler.ComponentTests.fsproj | 2 +- 4 files changed, 337 insertions(+), 175 deletions(-) delete mode 100644 tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_ParallelCrossAssemblyInlineOverloads.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs diff --git a/src/Compiler/Optimize/Optimizer.fs b/src/Compiler/Optimize/Optimizer.fs index 3d88004e673..56b8224e67c 100644 --- a/src/Compiler/Optimize/Optimizer.fs +++ b/src/Compiler/Optimize/Optimizer.fs @@ -4448,7 +4448,20 @@ and OptimizeBinding cenv isRec env (TBind(vref, expr, spBind)) = raise (ReportedError (Some exn)) and OptimizeBindings cenv isRec env xs = - List.mapFold (OptimizeBinding cenv isRec) env xs + if isRec then + let xsArray = xs |> List.toArray + let order = GetBindingOptimizationOrder cenv xs + + let results, env = + (env, order) + ||> List.mapFold (fun env idx -> + let result, env = OptimizeBinding cenv isRec env xsArray[idx] + (idx, result), env) + + let resultsByIndex = results |> Map.ofList + [ for idx in 0 .. xsArray.Length - 1 -> resultsByIndex[idx] ], env + else + List.mapFold (OptimizeBinding cenv isRec) env xs and OptimizeModuleExprWithSig cenv env mty def = let g = cenv.g @@ -4538,11 +4551,84 @@ and OptimizeModuleExprWithSig cenv env mty def = and mkValBind (bind: Binding) info = (mkLocalValRef bind.Var, info) +and GetBindingOptimizationOrder cenv (binds: Binding list) = + let bindsArray = binds |> List.toArray + + let bindIndexByStamp = + binds + |> List.mapi (fun idx bind -> bind.Var.Stamp, idx) + |> Map.ofList + + let bindingArity idx = + bindsArray[idx].Var.ValReprInfo + |> Option.map (fun repr -> repr.TotalArgCount) + |> Option.defaultValue 0 + + let rec addBindingDependencies depIdxs expr = + let addVals depIdxs vals = + vals + |> Seq.choose (fun (v: Val) -> bindIndexByStamp |> Map.tryFind v.Stamp) + |> Seq.fold (fun depIdxs depIdx -> Set.add depIdx depIdxs) depIdxs + + let fvs = freeInExpr CollectLocalsNoCaching expr + + let depIdxs = + let depIdxs = addVals depIdxs (fvs.FreeLocals |> Zset.elements) + addVals depIdxs (fvs.FreeTyvars.FreeTraitSolutions |> Zset.elements) + + let folder = + { ExprFolder0 with + exprIntercept = + (fun _exprF noInterceptF depIdxs expr -> + let depIdxs = + match expr with + | Expr.Op(TOp.TraitCall traitInfo, _, args, m) -> + match ConstraintSolver.CodegenWitnessExprForTraitConstraint cenv.TcVal cenv.g cenv.amap m traitInfo args with + | OkResult (_, Some witnessExpr) -> addBindingDependencies depIdxs witnessExpr + | _ -> depIdxs + | _ -> depIdxs + + noInterceptF depIdxs expr) } + + FoldExpr folder depIdxs expr + + let dependencyIndexes = + binds + |> List.map (fun (TBind(_, expr, _)) -> + addBindingDependencies Set.empty expr |> Set.toArray) + |> List.toArray + + let ordered = ResizeArray() + let visiting = HashSet() + let visited = HashSet() + + let rec visit idx = + if not (visited.Contains idx) then + if not (visiting.Contains idx) then + visiting.Add idx |> ignore + + for depIdx in dependencyIndexes[idx] do + if depIdx <> idx then + visit depIdx + + visiting.Remove idx |> ignore + visited.Add idx |> ignore + ordered.Add idx + + let rootOrder = + [ 0 .. binds.Length - 1 ] + |> List.sortBy (fun idx -> bindingArity idx, -idx) + + for idx in rootOrder do + visit idx + + ordered |> Seq.toList + and OptimizeModuleContents cenv (env, bindInfosColl) input = match input with | TMDefRec(isRec, opens, tycons, mbinds, m) -> let env = if isRec then BindInternalValsToUnknown cenv (allValsOfModDef input) env else env - let mbindInfos, (env, bindInfosColl) = OptimizeModuleBindings cenv (env, bindInfosColl) mbinds + let mbindInfos, (env, bindInfosColl) = OptimizeModuleBindings cenv isRec (env, bindInfosColl) mbinds let mbinds, minfos = List.unzip mbindInfos let binds = minfos |> List.choose (function Choice1Of2 (x, _) -> Some x | _ -> None) let binfos = minfos |> List.choose (function Choice1Of2 (_, x) -> Some x | _ -> None) @@ -4572,8 +4658,28 @@ and OptimizeModuleContents cenv (env, bindInfosColl) input = let (defs, info), (env, bindInfosColl) = OptimizeModuleDefs cenv (env, bindInfosColl) defs (TMDefs defs, info), (env, bindInfosColl) -and OptimizeModuleBindings cenv (env, bindInfosColl) xs = - List.mapFold (OptimizeModuleBinding cenv) (env, bindInfosColl) xs +and OptimizeModuleBindings cenv isRec (env, bindInfosColl) xs = + let bindingGroup = + xs + |> List.map (function + | ModuleOrNamespaceBinding.Binding bind -> Some bind + | _ -> None) + + if isRec && (bindingGroup |> List.forall Option.isSome) then + let xsArray = xs |> List.toArray + let binds = bindingGroup |> List.choose id + let order = GetBindingOptimizationOrder cenv binds + + let results, (env, bindInfosColl) = + ((env, bindInfosColl), order) + ||> List.mapFold (fun state idx -> + let result, state = OptimizeModuleBinding cenv state xsArray[idx] + (idx, result), state) + + let resultsByIndex = results |> Map.ofList + [ for idx in 0 .. xsArray.Length - 1 -> resultsByIndex[idx] ], (env, bindInfosColl) + else + List.mapFold (OptimizeModuleBinding cenv) (env, bindInfosColl) xs and OptimizeModuleBinding cenv (env, bindInfosColl) x = match x with diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_ParallelCrossAssemblyInlineOverloads.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_ParallelCrossAssemblyInlineOverloads.fs deleted file mode 100644 index 1ca63330fa9..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_ParallelCrossAssemblyInlineOverloads.fs +++ /dev/null @@ -1,170 +0,0 @@ -namespace EmittedIL.Inlining - -open Xunit -open FSharp.Test -open FSharp.Test.Compiler - -module Regression_ParallelCrossAssemblyInlineOverloads = - - let private librarySource = - FSharp """ -module LibraryImpl - -open System.Threading.Tasks -open Microsoft.FSharp.Control - -module Result = - let ofChoice choice = - match choice with - | Choice1Of2 value -> Ok value - | Choice2Of2 error -> Error error - -module Async = - let singleton value = async.Return value - -type Validation<'ok, 'error> = Result<'ok, 'error> -type TaskValidation<'ok, 'error> = Task> - -type TaskValidationBuilderBase() = - member inline _.Return(value: 'ok) : TaskValidation<'ok, 'error> = - task { return Ok value } - - member inline _.ReturnFrom(taskValidation: TaskValidation<'ok, 'error>) : TaskValidation<'ok, 'error> = - taskValidation - - member inline _.Bind - (source: TaskValidation<'okInput, 'error>, binder: 'okInput -> TaskValidation<'okOutput, 'error>) - : TaskValidation<'okOutput, 'error> = - task { - let! result = source - match result with - | Ok value -> return! binder value - | Error error -> return Error error - } - - member inline this.Bind - (source: Validation<'okInput, 'error>, binder: 'okInput -> TaskValidation<'okOutput, 'error>) - : TaskValidation<'okOutput, 'error> = - task { - let! result = this.Source source - match result with - | Ok value -> return! binder value - | Error error -> return Error error - } - - member inline this.Bind - (source: Choice<'okInput, 'error>, binder: 'okInput -> TaskValidation<'okOutput, 'error>) - : TaskValidation<'okOutput, 'error> = - task { - let! result = this.Source source - match result with - | Ok value -> return! binder value - | Error error -> return Error error - } - - member inline this.Bind - (source: Async<'okInput>, binder: 'okInput -> TaskValidation<'okOutput, 'error>) - : TaskValidation<'okOutput, 'error> = - task { - let! result = this.Source source - match result with - | Ok value -> return! binder value - | Error error -> return Error error - } - - member inline _.Delay(generator: unit -> TaskValidation<'ok, 'error>) : TaskValidation<'ok, 'error> = - generator () - - member inline this.Source(result: Validation<'ok, 'error>) : TaskValidation<'ok, 'error> = - task { return result } - - member inline this.Source(choice: Choice<'ok, 'error>) : TaskValidation<'ok, 'error> = - task { - return - choice - |> Result.ofChoice - } - - member inline this.Source(asyncComputation: Async<'ok>) : TaskValidation<'ok, 'error> = - task { - let! value = asyncComputation - return Ok value - } - -type TaskValidationBuilder() = - inherit TaskValidationBuilderBase() - -let taskValidation = TaskValidationBuilder() -""" - - let private mkLibrary options = - librarySource - |> withAdditionalSourceFile ( - SourceCodeFileKind.Create( - "Library.Support.fs", - """ -module LibraryImplSupport - -let taskValidation = LibraryImpl.taskValidation -""" - ) - ) - |> withOutputType CompileOutput.Library - |> withName "Library" - |> withOptimize - |> withOptions options - |> ignoreWarnings - - let private consumerSource = - "module ConsumerImpl\n\nopen LibraryImpl\nopen LibraryImplSupport\n\nlet run () =\n taskValidation.Bind(\n Async.singleton 42,\n fun asyncValue ->\n taskValidation.Bind(\n Ok 42,\n fun resultValue ->\n taskValidation.Bind(\n Choice1Of2 42,\n fun choiceValue ->\n taskValidation.Return(asyncValue + resultValue + choiceValue))))\n" - - let private consumerAdditionalSources = - Array.init 12 (fun i -> - let source = - "module Consumer" - + string i - + "\n\nopen LibraryImpl\nopen LibraryImplSupport\n\nlet run () =\n taskValidation.Bind(\n Async.singleton 42,\n fun asyncValue ->\n taskValidation.Bind(\n Ok 42,\n fun resultValue ->\n taskValidation.Bind(\n Choice1Of2 42,\n fun choiceValue ->\n taskValidation.Return(asyncValue + resultValue + choiceValue))))\n" - - SourceCodeFileKind.Create(sprintf "Consumer.%d.fs" i, source)) - |> Array.toList - - let private mkConsumer options library = - FSharp consumerSource - |> withAdditionalSourceFiles consumerAdditionalSources - |> withAdditionalSourceFile ( - SourceCodeFileKind.Create( - "Consumer.Support.fs", - """ -module ConsumerSupport - -[] -let main _ = - match (ConsumerImpl.run ()).GetAwaiter().GetResult() with - | Ok value -> if value = 126 then 0 else 1 - | Error _ -> 1 -""" - ) - ) - |> withOutputType CompileOutput.Exe - |> withReferences [ library ] - |> withOptimize - |> withOptions options - |> ignoreWarnings - - let private assertCompiles repeatCount options = - let library = mkLibrary options - let consumer = mkConsumer options library - - for _i = 1 to repeatCount do - consumer - |> compile - |> shouldSucceed - |> ignore - - [] - let ``Cross-assembly overloaded inline Source members compile and run`` () = - assertCompiles 30 [ "--parallelcompilation+"; "--nowarn:75" ] - - [] - let ``Cross-assembly overloaded inline Source members compile and run under sequential compilation`` () = - assertCompiles 1 [ "--parallelcompilation-"; "--nowarn:75" ] diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs new file mode 100644 index 00000000000..67338f6d5bf --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs @@ -0,0 +1,226 @@ +namespace EmittedIL.Inlining + +open Xunit +open FSharp.Test +open FSharp.Test.Compiler + +module Regression_RecursiveInlineMemberDependencies = + + let private parallelOptions = [ "--parallelcompilation+"; "--nowarn:75" ] + let private sequentialOptions = [ "--parallelcompilation-"; "--nowarn:75" ] + + let private recursiveInlineMemberDependencySource = + FSharp """ +module LibraryImpl + +open System.Threading.Tasks +open Microsoft.FSharp.Control + +module Result = + let ofChoice choice = + match choice with + | Choice1Of2 value -> Ok value + | Choice2Of2 error -> Error error + +module Async = + let singleton value = async.Return value + +type Validation<'ok, 'error> = Result<'ok, 'error> +type TaskValidation<'ok, 'error> = Task> + +type TaskValidationBuilderBase() = + member inline _.Return(value: 'ok) : TaskValidation<'ok, 'error> = + task { return Ok value } + + member inline _.ReturnFrom(taskValidation: TaskValidation<'ok, 'error>) : TaskValidation<'ok, 'error> = + taskValidation + + member inline this.Bind + (source: Validation<'okInput, 'error>, binder: 'okInput -> TaskValidation<'okOutput, 'error>) + : TaskValidation<'okOutput, 'error> = + task { + let! result = this.Source source + match result with + | Ok value -> return! binder value + | Error error -> return Error error + } + + member inline this.Bind + (source: Choice<'okInput, 'error>, binder: 'okInput -> TaskValidation<'okOutput, 'error>) + : TaskValidation<'okOutput, 'error> = + task { + let! result = this.Source source + match result with + | Ok value -> return! binder value + | Error error -> return Error error + } + + member inline this.Bind + (source: Async<'okInput>, binder: 'okInput -> TaskValidation<'okOutput, 'error>) + : TaskValidation<'okOutput, 'error> = + task { + let! result = this.Source source + match result with + | Ok value -> return! binder value + | Error error -> return Error error + } + + member inline _.Delay(generator: unit -> TaskValidation<'ok, 'error>) : TaskValidation<'ok, 'error> = + generator () + + member inline this.Source(result: Validation<'ok, 'error>) : TaskValidation<'ok, 'error> = + task { return result } + + member inline this.Source(choice: Choice<'ok, 'error>) : TaskValidation<'ok, 'error> = + task { + return + choice + |> Result.ofChoice + } + + member inline this.Source(asyncComputation: Async<'ok>) : TaskValidation<'ok, 'error> = + task { + let! value = asyncComputation + return Ok value + } + +type TaskValidationBuilder() = + inherit TaskValidationBuilderBase() + +let taskValidation = TaskValidationBuilder() +""" + + let private issue1565Example1 = + FSharp """ +module Issue1565Example1 + +let inline checkBounds f (g: 'b -> ^c) (tp: ^a) = + let convertFrom = (^a: (static member name: string) ()) + let convertTo = (^c: (static member name : string) ()) + let value = (^a: (member Value: 'b) tp) + + if f value then + g value + else + failwithf "Cannot convert from %s to %s." convertFrom convertTo + +[] +type ConverterA = + val Value: sbyte + new(v) = { Value = v } + + static member inline name with get () = "converter-a" + + static member inline convert(x: ConverterA): ConverterB = + checkBounds ((>=) 0y) (byte >> ConverterB) x + +and [] ConverterB = + val Value: byte + new(v) = { Value = v } + + static member inline name with get () = "converter-b" +""" + + let private issue1565Example2 = + FSharp """ +module Issue1565Example2 + +[] +type MyType = + | Integer = 0b0001 + | Float = 0b0010 + +module Test = + [] + type SomeType = + | Int of int64 + | Float of float + + override x.Equals other = + match other with + | :? SomeType as y -> + match SomeType.getType x &&& SomeType.getType y with + | MyType.Integer -> int64 x = int64 y + | MyType.Float -> float x = float y + | _ -> false + | _ -> false + + override x.GetHashCode() = + match x with + | Int i -> hash i + | Float f -> hash f + + static member inline op_Explicit(n: SomeType): float = + match n with + | Int i -> float i + | Float f -> f + + static member inline op_Explicit(n: SomeType): int64 = + match n with + | Int i -> i + | Float f -> int64 f + + static member inline getType x = + match x with + | Int _ -> MyType.Integer + | Float _ -> MyType.Float +""" + + let private issue1565Example3 = + FSharp """ +module Test + +type SomeType = + | Int of int64 + | Float of float + + static member MyEquals(x, other: SomeType) = + float x = float other + + static member inline op_Explicit(n: SomeType): float = + match n with + | Int i -> float i + | Float f -> f + + static member inline op_Explicit(n: SomeType): int64 = + match n with + | Int i -> i + | Float f -> int64 f +""" + + let private mkLibrary source options = + source + |> withOutputType CompileOutput.Library + |> withName "Library" + |> withOptimize + |> withOptions options + |> ignoreWarnings + + let private assertCompiles repeatCount source options = + let library = mkLibrary source options + + for _i = 1 to repeatCount do + library + |> compile + |> shouldSucceed + |> ignore + + [] + let ``Recursive inline member dependencies compile under parallel compilation`` () = + assertCompiles 30 recursiveInlineMemberDependencySource parallelOptions + + [] + let ``Recursive inline member dependencies compile under sequential compilation`` () = + assertCompiles 1 recursiveInlineMemberDependencySource sequentialOptions + + [] + let ``Issue 1565 example 1 compiles under sequential compilation`` () = + assertCompiles 1 issue1565Example1 sequentialOptions + + [] + let ``Issue 1565 example 2 compiles under sequential compilation`` () = + assertCompiles 1 issue1565Example2 sequentialOptions + + [] + let ``Issue 1565 example 3 compiles under sequential compilation`` () = + assertCompiles 1 issue1565Example3 sequentialOptions diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 4a853801879..0a456b0b5a8 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -276,7 +276,7 @@ - + From a94286293d7014894239afd48046ef1a449ce978 Mon Sep 17 00:00:00 2001 From: majocha <1760221+majocha@users.noreply.github.com> Date: Sat, 1 Aug 2026 23:59:39 +0200 Subject: [PATCH 06/21] Fix quoted args in fsc subprocess tests Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- tests/FSharp.Test.Utilities/Compiler.fs | 72 ++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/tests/FSharp.Test.Utilities/Compiler.fs b/tests/FSharp.Test.Utilities/Compiler.fs index 723e94345a5..a6e2b20be25 100644 --- a/tests/FSharp.Test.Utilities/Compiler.fs +++ b/tests/FSharp.Test.Utilities/Compiler.fs @@ -13,6 +13,7 @@ open Microsoft.CodeAnalysis.CSharp open Xunit open System open System.Collections.Immutable +open System.Diagnostics open System.IO open System.Text open System.Text.RegularExpressions @@ -2382,17 +2383,76 @@ $ code --diff {outFile} {expectedFile} /// Result type for CLI subprocess execution (runFsiProcess / runFscProcess). type ProcessResult = { ExitCode: int; StdOut: string; StdErr: string } + let private quoteProcessArg (arg: string) = + if String.IsNullOrEmpty(arg) then + "\"\"" + elif arg.IndexOfAny([| ' '; '\t'; '"' |]) = -1 then + arg + else + let sb = StringBuilder() + sb.Append('"') |> ignore + + let mutable backslashes = 0 + + for ch in arg do + match ch with + | '\\' -> + backslashes <- backslashes + 1 + | '"' -> + sb.Append('\\', backslashes * 2 + 1) |> ignore + sb.Append('"') |> ignore + backslashes <- 0 + | _ -> + if backslashes > 0 then + sb.Append('\\', backslashes) |> ignore + backslashes <- 0 + + sb.Append(ch) |> ignore + + if backslashes > 0 then + sb.Append('\\', backslashes * 2) |> ignore + + sb.Append('"') |> ignore + sb.ToString() + /// Run an F# tool (FSI or FSC) as a subprocess. Shared helper for runFsiProcess / runFscProcess. let private runToolProcess (toolPath: string) (args: string list) : ProcessResult = + let psi = ProcessStartInfo() + #if NETCOREAPP - let exe = TestFramework.initialConfig.DotNetExe - let arguments = toolPath + " " + (args |> String.concat " ") + psi.FileName <- TestFramework.initialConfig.DotNetExe + psi.ArgumentList.Add(toolPath) + for arg in args do + psi.ArgumentList.Add(arg) #else - let exe = toolPath - let arguments = args |> String.concat " " + psi.FileName <- toolPath + psi.Arguments <- args |> List.map quoteProcessArg |> String.concat " " #endif - let exitCode, stdout, stderr = Commands.executeProcess exe arguments (Directory.GetCurrentDirectory()) - { ExitCode = exitCode; StdOut = stdout; StdErr = stderr } + + psi.WorkingDirectory <- Directory.GetCurrentDirectory() + psi.RedirectStandardOutput <- true + psi.RedirectStandardError <- true + psi.CreateNoWindow <- true + + psi.EnvironmentVariables["DOTNET_ROLL_FORWARD"] <- "LatestMajor" + psi.EnvironmentVariables["DOTNET_ROLL_FORWARD_TO_PRERELEASE"] <- "1" + psi.EnvironmentVariables.Remove("MSBuildSDKsPath") + psi.UseShellExecute <- false + + use p = new Process() + p.StartInfo <- psi + + if not (p.Start()) then + failwith "new process did not start" + + let readOutput = backgroundTask { return! p.StandardOutput.ReadToEndAsync() } + let readErrors = backgroundTask { return! p.StandardError.ReadToEndAsync() } + + p.WaitForExit() + + { ExitCode = p.ExitCode + StdOut = readOutput.Result + StdErr = readErrors.Result } /// Run FSI as a subprocess with the given arguments. For CLI-level tests only (--help, exit codes, etc.). let runFsiProcess (args: string list) : ProcessResult = From d4dc94b7cd61ea9c4790dbb410f3b89c304b8e89 Mon Sep 17 00:00:00 2001 From: majocha <1760221+majocha@users.noreply.github.com> Date: Sun, 2 Aug 2026 00:17:07 +0200 Subject: [PATCH 07/21] Update emitted IL baselines Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- ...n_TLR_MutualInnerRec_CapturedEnv.fs.il.bsl | 18 +++++---------- ...ssion_TLR_MutualInnerRec_Generic.fs.il.bsl | 15 +++++-------- ...RealInternalSignatureOff.OptimizeOn.il.bsl | 22 +++++++------------ ....RealInternalSignatureOn.OptimizeOn.il.bsl | 22 +++++++------------ 4 files changed, 28 insertions(+), 49 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_TLR_MutualInnerRec_CapturedEnv.fs.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_TLR_MutualInnerRec_CapturedEnv.fs.il.bsl index 0345a39f809..20959173c6a 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_TLR_MutualInnerRec_CapturedEnv.fs.il.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_TLR_MutualInnerRec_CapturedEnv.fs.il.bsl @@ -38,21 +38,15 @@ { .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 ) - .maxstack 5 - .locals init (int32 V_0, - int32 V_1) + .maxstack 8 IL_0000: ldarg.0 - IL_0001: stloc.0 - IL_0002: ldarg.1 - IL_0003: stloc.1 - IL_0004: ldarg.0 - IL_0005: ldarg.1 - IL_0006: ldc.i4.s 100 - IL_0008: tail. - IL_000a: call int32 assembly::a@4(int32, + IL_0001: ldarg.1 + IL_0002: ldc.i4.s 100 + IL_0004: tail. + IL_0006: call int32 assembly::a@4(int32, int32, int32) - IL_000f: ret + IL_000b: ret } .method public static int32 main(string[] _argv) cil managed diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_TLR_MutualInnerRec_Generic.fs.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_TLR_MutualInnerRec_Generic.fs.il.bsl index 278bd1737cc..2b8798ae5b1 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_TLR_MutualInnerRec_Generic.fs.il.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_TLR_MutualInnerRec_Generic.fs.il.bsl @@ -38,18 +38,15 @@ { .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 ) - .maxstack 5 - .locals init (!!T V_0) + .maxstack 8 IL_0000: ldarg.1 - IL_0001: stloc.0 - IL_0002: ldarg.1 - IL_0003: ldc.i4 0x3e8 - IL_0008: ldarg.0 - IL_0009: tail. - IL_000b: call !!0 assembly::a@4(!!0, + IL_0001: ldc.i4 0x3e8 + IL_0006: ldarg.0 + IL_0007: tail. + IL_0009: call !!0 assembly::a@4(!!0, int32, !!0) - IL_0010: ret + IL_000e: ret } .method public static int32 main(string[] _argv) cil managed diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/Verify13043.fs.RealInternalSignatureOff.OptimizeOn.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/Verify13043.fs.RealInternalSignatureOff.OptimizeOn.il.bsl index 6ec0bc58182..c3014389d2c 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/Verify13043.fs.RealInternalSignatureOff.OptimizeOn.il.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/Verify13043.fs.RealInternalSignatureOff.OptimizeOn.il.bsl @@ -132,30 +132,24 @@ { .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 ) - .maxstack 4 - .locals init (class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2 V_0) + .maxstack 8 IL_0000: ldarg.0 - IL_0001: stloc.0 - IL_0002: ldarg.0 - IL_0003: ldarg.1 - IL_0004: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 assembly::f@7(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2, + IL_0001: ldarg.1 + IL_0002: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 assembly::f@7(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2, class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1) - IL_0009: ret + IL_0007: ret } .method public static class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 dropWhileWithFunction(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2 condition, class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 list) cil managed { .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 ) - .maxstack 4 - .locals init (class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2 V_0) + .maxstack 8 IL_0000: ldarg.0 - IL_0001: stloc.0 - IL_0002: ldarg.0 - IL_0003: ldarg.1 - IL_0004: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 assembly::'f@26-1'(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2, + IL_0001: ldarg.1 + IL_0002: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 assembly::'f@26-1'(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2, class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1) - IL_0009: ret + IL_0007: ret } .method public specialname static class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 get_matchResult() cil managed diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/Verify13043.fs.RealInternalSignatureOn.OptimizeOn.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/Verify13043.fs.RealInternalSignatureOn.OptimizeOn.il.bsl index 6ec0bc58182..c3014389d2c 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/Verify13043.fs.RealInternalSignatureOn.OptimizeOn.il.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/TestFunctions/Verify13043.fs.RealInternalSignatureOn.OptimizeOn.il.bsl @@ -132,30 +132,24 @@ { .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 ) - .maxstack 4 - .locals init (class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2 V_0) + .maxstack 8 IL_0000: ldarg.0 - IL_0001: stloc.0 - IL_0002: ldarg.0 - IL_0003: ldarg.1 - IL_0004: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 assembly::f@7(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2, + IL_0001: ldarg.1 + IL_0002: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 assembly::f@7(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2, class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1) - IL_0009: ret + IL_0007: ret } .method public static class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 dropWhileWithFunction(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2 condition, class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 list) cil managed { .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 ) - .maxstack 4 - .locals init (class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2 V_0) + .maxstack 8 IL_0000: ldarg.0 - IL_0001: stloc.0 - IL_0002: ldarg.0 - IL_0003: ldarg.1 - IL_0004: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 assembly::'f@26-1'(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2, + IL_0001: ldarg.1 + IL_0002: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 assembly::'f@26-1'(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2, class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1) - IL_0009: ret + IL_0007: ret } .method public specialname static class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 get_matchResult() cil managed From 78b00113ce1321c6ddc18d42808cf22327a3f4bd Mon Sep 17 00:00:00 2001 From: majocha <1760221+majocha@users.noreply.github.com> Date: Sun, 2 Aug 2026 09:28:05 +0200 Subject: [PATCH 08/21] add comments --- src/Compiler/Optimize/Optimizer.fs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Compiler/Optimize/Optimizer.fs b/src/Compiler/Optimize/Optimizer.fs index 56b8224e67c..f3df1548ba4 100644 --- a/src/Compiler/Optimize/Optimizer.fs +++ b/src/Compiler/Optimize/Optimizer.fs @@ -4552,6 +4552,10 @@ and mkValBind (bind: Binding) info = (mkLocalValRef bind.Var, info) and GetBindingOptimizationOrder cenv (binds: Binding list) = + // Recursive binding groups are published to the optimizer incrementally as each binding is + // processed. If a caller is optimized before a later sibling it depends on, inline lookup can + // observe an incomplete optimization environment. Compute a dependency-first schedule for the + // recursive group, then restore source order after optimization. let bindsArray = binds |> List.toArray let bindIndexByStamp = @@ -4582,6 +4586,8 @@ and GetBindingOptimizationOrder cenv (binds: Binding list) = (fun _exprF noInterceptF depIdxs expr -> let depIdxs = match expr with + // Member-constraint calls can hide the real sibling dependency behind + // a witness expression, so fold over the resolved witness as well. | Expr.Op(TOp.TraitCall traitInfo, _, args, m) -> match ConstraintSolver.CodegenWitnessExprForTraitConstraint cenv.TcVal cenv.g cenv.amap m traitInfo args with | OkResult (_, Some witnessExpr) -> addBindingDependencies depIdxs witnessExpr @@ -4617,6 +4623,8 @@ and GetBindingOptimizationOrder cenv (binds: Binding list) = let rootOrder = [ 0 .. binds.Length - 1 ] + // Prefer leaf-like bindings when there is no explicit dependency edge. This makes + // simple inline members such as getters and conversions available before their callers. |> List.sortBy (fun idx -> bindingArity idx, -idx) for idx in rootOrder do @@ -4677,6 +4685,7 @@ and OptimizeModuleBindings cenv isRec (env, bindInfosColl) xs = (idx, result), state) let resultsByIndex = results |> Map.ofList + // Keep the emitted binding list in source order; only the optimization schedule changes. [ for idx in 0 .. xsArray.Length - 1 -> resultsByIndex[idx] ], (env, bindInfosColl) else List.mapFold (OptimizeModuleBinding cenv) (env, bindInfosColl) xs From 0d8eceb3fbdb08e3ce08a87cfe59a93f903632c0 Mon Sep 17 00:00:00 2001 From: majocha <1760221+majocha@users.noreply.github.com> Date: Sun, 2 Aug 2026 09:41:49 +0200 Subject: [PATCH 09/21] Revert "Fix quoted args in fsc subprocess tests" This reverts commit a94286293d7014894239afd48046ef1a449ce978. --- tests/FSharp.Test.Utilities/Compiler.fs | 72 +++---------------------- 1 file changed, 6 insertions(+), 66 deletions(-) diff --git a/tests/FSharp.Test.Utilities/Compiler.fs b/tests/FSharp.Test.Utilities/Compiler.fs index a6e2b20be25..723e94345a5 100644 --- a/tests/FSharp.Test.Utilities/Compiler.fs +++ b/tests/FSharp.Test.Utilities/Compiler.fs @@ -13,7 +13,6 @@ open Microsoft.CodeAnalysis.CSharp open Xunit open System open System.Collections.Immutable -open System.Diagnostics open System.IO open System.Text open System.Text.RegularExpressions @@ -2383,76 +2382,17 @@ $ code --diff {outFile} {expectedFile} /// Result type for CLI subprocess execution (runFsiProcess / runFscProcess). type ProcessResult = { ExitCode: int; StdOut: string; StdErr: string } - let private quoteProcessArg (arg: string) = - if String.IsNullOrEmpty(arg) then - "\"\"" - elif arg.IndexOfAny([| ' '; '\t'; '"' |]) = -1 then - arg - else - let sb = StringBuilder() - sb.Append('"') |> ignore - - let mutable backslashes = 0 - - for ch in arg do - match ch with - | '\\' -> - backslashes <- backslashes + 1 - | '"' -> - sb.Append('\\', backslashes * 2 + 1) |> ignore - sb.Append('"') |> ignore - backslashes <- 0 - | _ -> - if backslashes > 0 then - sb.Append('\\', backslashes) |> ignore - backslashes <- 0 - - sb.Append(ch) |> ignore - - if backslashes > 0 then - sb.Append('\\', backslashes * 2) |> ignore - - sb.Append('"') |> ignore - sb.ToString() - /// Run an F# tool (FSI or FSC) as a subprocess. Shared helper for runFsiProcess / runFscProcess. let private runToolProcess (toolPath: string) (args: string list) : ProcessResult = - let psi = ProcessStartInfo() - #if NETCOREAPP - psi.FileName <- TestFramework.initialConfig.DotNetExe - psi.ArgumentList.Add(toolPath) - for arg in args do - psi.ArgumentList.Add(arg) + let exe = TestFramework.initialConfig.DotNetExe + let arguments = toolPath + " " + (args |> String.concat " ") #else - psi.FileName <- toolPath - psi.Arguments <- args |> List.map quoteProcessArg |> String.concat " " + let exe = toolPath + let arguments = args |> String.concat " " #endif - - psi.WorkingDirectory <- Directory.GetCurrentDirectory() - psi.RedirectStandardOutput <- true - psi.RedirectStandardError <- true - psi.CreateNoWindow <- true - - psi.EnvironmentVariables["DOTNET_ROLL_FORWARD"] <- "LatestMajor" - psi.EnvironmentVariables["DOTNET_ROLL_FORWARD_TO_PRERELEASE"] <- "1" - psi.EnvironmentVariables.Remove("MSBuildSDKsPath") - psi.UseShellExecute <- false - - use p = new Process() - p.StartInfo <- psi - - if not (p.Start()) then - failwith "new process did not start" - - let readOutput = backgroundTask { return! p.StandardOutput.ReadToEndAsync() } - let readErrors = backgroundTask { return! p.StandardError.ReadToEndAsync() } - - p.WaitForExit() - - { ExitCode = p.ExitCode - StdOut = readOutput.Result - StdErr = readErrors.Result } + let exitCode, stdout, stderr = Commands.executeProcess exe arguments (Directory.GetCurrentDirectory()) + { ExitCode = exitCode; StdOut = stdout; StdErr = stderr } /// Run FSI as a subprocess with the given arguments. For CLI-level tests only (--help, exit codes, etc.). let runFsiProcess (args: string list) : ProcessResult = From 66fee1d369a2dfee2b9aeb6ec37cb7862892bde5 Mon Sep 17 00:00:00 2001 From: majocha <1760221+majocha@users.noreply.github.com> Date: Sun, 2 Aug 2026 10:20:13 +0200 Subject: [PATCH 10/21] Simplify recursive inline member regression tests Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- ...ssion_RecursiveInlineMemberDependencies.fs | 40 +++++++------------ 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs index 67338f6d5bf..412871d37eb 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs @@ -6,9 +6,6 @@ open FSharp.Test.Compiler module Regression_RecursiveInlineMemberDependencies = - let private parallelOptions = [ "--parallelcompilation+"; "--nowarn:75" ] - let private sequentialOptions = [ "--parallelcompilation-"; "--nowarn:75" ] - let private recursiveInlineMemberDependencySource = FSharp """ module LibraryImpl @@ -188,39 +185,32 @@ type SomeType = | Float f -> int64 f """ - let private mkLibrary source options = + let private mkLibrary source = source |> withOutputType CompileOutput.Library |> withName "Library" |> withOptimize - |> withOptions options + |> withOptions [ "--nowarn:75" ] |> ignoreWarnings - let private assertCompiles repeatCount source options = - let library = mkLibrary source options - - for _i = 1 to repeatCount do - library - |> compile - |> shouldSucceed - |> ignore - - [] - let ``Recursive inline member dependencies compile under parallel compilation`` () = - assertCompiles 30 recursiveInlineMemberDependencySource parallelOptions + let private assertCompiles source = + mkLibrary source + |> compile + |> shouldSucceed + |> ignore [] - let ``Recursive inline member dependencies compile under sequential compilation`` () = - assertCompiles 1 recursiveInlineMemberDependencySource sequentialOptions + let ``Recursive inline member dependencies compile`` () = + assertCompiles recursiveInlineMemberDependencySource [] - let ``Issue 1565 example 1 compiles under sequential compilation`` () = - assertCompiles 1 issue1565Example1 sequentialOptions + let ``Issue 1565 example 1 compiles`` () = + assertCompiles issue1565Example1 [] - let ``Issue 1565 example 2 compiles under sequential compilation`` () = - assertCompiles 1 issue1565Example2 sequentialOptions + let ``Issue 1565 example 2 compiles`` () = + assertCompiles issue1565Example2 [] - let ``Issue 1565 example 3 compiles under sequential compilation`` () = - assertCompiles 1 issue1565Example3 sequentialOptions + let ``Issue 1565 example 3 compiles`` () = + assertCompiles issue1565Example3 From 1a09b700c3b7e5661ddaa2638dce0778fec48bdc Mon Sep 17 00:00:00 2001 From: majocha <1760221+majocha@users.noreply.github.com> Date: Sun, 2 Aug 2026 11:24:20 +0200 Subject: [PATCH 11/21] simplify tests --- ...ssion_RecursiveInlineMemberDependencies.fs | 275 +++++++----------- 1 file changed, 98 insertions(+), 177 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs index 412871d37eb..c82b4e94dac 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs @@ -6,211 +6,132 @@ open FSharp.Test.Compiler module Regression_RecursiveInlineMemberDependencies = - let private recursiveInlineMemberDependencySource = - FSharp """ -module LibraryImpl - -open System.Threading.Tasks -open Microsoft.FSharp.Control - -module Result = - let ofChoice choice = - match choice with - | Choice1Of2 value -> Ok value - | Choice2Of2 error -> Error error - -module Async = - let singleton value = async.Return value - -type Validation<'ok, 'error> = Result<'ok, 'error> -type TaskValidation<'ok, 'error> = Task> - -type TaskValidationBuilderBase() = - member inline _.Return(value: 'ok) : TaskValidation<'ok, 'error> = - task { return Ok value } - - member inline _.ReturnFrom(taskValidation: TaskValidation<'ok, 'error>) : TaskValidation<'ok, 'error> = - taskValidation - - member inline this.Bind - (source: Validation<'okInput, 'error>, binder: 'okInput -> TaskValidation<'okOutput, 'error>) - : TaskValidation<'okOutput, 'error> = - task { - let! result = this.Source source - match result with - | Ok value -> return! binder value - | Error error -> return Error error - } - - member inline this.Bind - (source: Choice<'okInput, 'error>, binder: 'okInput -> TaskValidation<'okOutput, 'error>) - : TaskValidation<'okOutput, 'error> = - task { - let! result = this.Source source - match result with - | Ok value -> return! binder value - | Error error -> return Error error - } - - member inline this.Bind - (source: Async<'okInput>, binder: 'okInput -> TaskValidation<'okOutput, 'error>) - : TaskValidation<'okOutput, 'error> = - task { - let! result = this.Source source - match result with - | Ok value -> return! binder value - | Error error -> return Error error - } - - member inline _.Delay(generator: unit -> TaskValidation<'ok, 'error>) : TaskValidation<'ok, 'error> = - generator () - - member inline this.Source(result: Validation<'ok, 'error>) : TaskValidation<'ok, 'error> = - task { return result } - - member inline this.Source(choice: Choice<'ok, 'error>) : TaskValidation<'ok, 'error> = - task { - return - choice - |> Result.ofChoice - } - - member inline this.Source(asyncComputation: Async<'ok>) : TaskValidation<'ok, 'error> = - task { - let! value = asyncComputation - return Ok value - } - -type TaskValidationBuilder() = - inherit TaskValidationBuilderBase() - -let taskValidation = TaskValidationBuilder() + let private assertCompiles source = + source + |> withOptimize + |> compile + |> shouldSucceed + |> ignore + + [] + let ``Inline members that depend on sibling member access compile`` () = + FSharp """ +module MemberAccessDependencyRepro + +type ValidationBuilder() = + member inline _.Return(value: int) : int = value + + member inline this.Bind(value: int, binder: int -> int) : int = + let result = this.Source value + binder result + + member inline this.Source(value: int) : int = value + +let inline run (builder: ValidationBuilder) = + builder.Bind(1, fun x -> x + 1) """ + |> assertCompiles - let private issue1565Example1 = - FSharp """ + [] + let ``Issue 1565 example 1 compiles`` () = + FSharp """ module Issue1565Example1 let inline checkBounds f (g: 'b -> ^c) (tp: ^a) = - let convertFrom = (^a: (static member name: string) ()) - let convertTo = (^c: (static member name : string) ()) - let value = (^a: (member Value: 'b) tp) + let convertFrom = (^a: (static member name: string) ()) + let convertTo = (^c: (static member name : string) ()) + let value = (^a: (member Value: 'b) tp) - if f value then - g value - else - failwithf "Cannot convert from %s to %s." convertFrom convertTo + if f value then + g value + else + failwithf "Cannot convert from %s to %s." convertFrom convertTo [] type ConverterA = - val Value: sbyte - new(v) = { Value = v } + val Value: sbyte + new(v) = { Value = v } - static member inline name with get () = "converter-a" + static member inline name with get () = "converter-a" - static member inline convert(x: ConverterA): ConverterB = - checkBounds ((>=) 0y) (byte >> ConverterB) x + static member inline convert(x: ConverterA): ConverterB = + checkBounds ((>=) 0y) (byte >> ConverterB) x and [] ConverterB = - val Value: byte - new(v) = { Value = v } + val Value: byte + new(v) = { Value = v } - static member inline name with get () = "converter-b" + static member inline name with get () = "converter-b" """ + |> assertCompiles - let private issue1565Example2 = - FSharp """ + [] + let ``Issue 1565 example 2 compiles`` () = + FSharp """ module Issue1565Example2 [] type MyType = - | Integer = 0b0001 - | Float = 0b0010 + | Integer = 0b0001 + | Float = 0b0010 module Test = - [] - type SomeType = - | Int of int64 - | Float of float - - override x.Equals other = - match other with - | :? SomeType as y -> - match SomeType.getType x &&& SomeType.getType y with - | MyType.Integer -> int64 x = int64 y - | MyType.Float -> float x = float y - | _ -> false - | _ -> false - - override x.GetHashCode() = - match x with - | Int i -> hash i - | Float f -> hash f - - static member inline op_Explicit(n: SomeType): float = - match n with - | Int i -> float i - | Float f -> f - - static member inline op_Explicit(n: SomeType): int64 = - match n with - | Int i -> i - | Float f -> int64 f - - static member inline getType x = - match x with - | Int _ -> MyType.Integer - | Float _ -> MyType.Float + [] + type SomeType = + | Int of int64 + | Float of float + + override x.Equals other = + match other with + | :? SomeType as y -> + match SomeType.getType x &&& SomeType.getType y with + | MyType.Integer -> int64 x = int64 y + | MyType.Float -> float x = float y + | _ -> false + | _ -> false + + override x.GetHashCode() = + match x with + | Int i -> hash i + | Float f -> hash f + + static member inline op_Explicit(n: SomeType): float = + match n with + | Int i -> float i + | Float f -> f + + static member inline op_Explicit(n: SomeType): int64 = + match n with + | Int i -> i + | Float f -> int64 f + + static member inline getType x = + match x with + | Int _ -> MyType.Integer + | Float _ -> MyType.Float """ + |> assertCompiles - let private issue1565Example3 = - FSharp """ + [] + let ``Issue 1565 example 3 compiles`` () = + FSharp """ module Test type SomeType = - | Int of int64 - | Float of float + | Int of int64 + | Float of float - static member MyEquals(x, other: SomeType) = - float x = float other + static member MyEquals(x, other: SomeType) = + float x = float other - static member inline op_Explicit(n: SomeType): float = - match n with - | Int i -> float i - | Float f -> f + static member inline op_Explicit(n: SomeType): float = + match n with + | Int i -> float i + | Float f -> f - static member inline op_Explicit(n: SomeType): int64 = - match n with - | Int i -> i - | Float f -> int64 f + static member inline op_Explicit(n: SomeType): int64 = + match n with + | Int i -> i + | Float f -> int64 f """ - - let private mkLibrary source = - source - |> withOutputType CompileOutput.Library - |> withName "Library" - |> withOptimize - |> withOptions [ "--nowarn:75" ] - |> ignoreWarnings - - let private assertCompiles source = - mkLibrary source - |> compile - |> shouldSucceed - |> ignore - - [] - let ``Recursive inline member dependencies compile`` () = - assertCompiles recursiveInlineMemberDependencySource - - [] - let ``Issue 1565 example 1 compiles`` () = - assertCompiles issue1565Example1 - - [] - let ``Issue 1565 example 2 compiles`` () = - assertCompiles issue1565Example2 - - [] - let ``Issue 1565 example 3 compiles`` () = - assertCompiles issue1565Example3 + |> assertCompiles From 79ce76a5b151f7e1648b2046231882e0017873b3 Mon Sep 17 00:00:00 2001 From: majocha <1760221+majocha@users.noreply.github.com> Date: Sun, 2 Aug 2026 13:32:06 +0200 Subject: [PATCH 12/21] Add recursive inline member regression coverage Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- ...ssion_RecursiveInlineMemberDependencies.fs | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs index c82b4e94dac..a72994b66ff 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs @@ -32,6 +32,64 @@ let inline run (builder: ValidationBuilder) = """ |> assertCompiles + [] + let ``Cross-assembly inline overload consumers compile`` () = + let library = + FSharpWithFileName "Library.fs" """ +module LibraryImpl + +type ValidationBuilder() = + member inline this.Bind(value: int, binder: int -> int) : int = + binder (this.Source value) + + member inline this.Bind(value: string, binder: string -> int) : int = + binder (this.Source value) + + member inline this.Bind(value: bool, binder: bool -> int) : int = + binder (this.Source value) + + member inline this.Source(value: int) : int = value + member inline this.Source(value: string) : string = value + member inline this.Source(value: bool) : bool = value +""" + |> withOutputType CompileOutput.Library + |> withName "Library" + |> withOptimize + |> withOptions [ "--nowarn:75" ] + |> ignoreWarnings + + FSharpWithFileName "Consumer.fs" """ +module Consumer + +open LibraryImpl + +let run (builder: ValidationBuilder) = + builder.Bind(1, fun x -> x + 1) +""" + |> withReferences [ library ] + |> withOptimize + |> withAdditionalSourceFiles [ + FsSourceWithFileName "Consumer2.fs" """ +module Consumer2 + +open LibraryImpl + +let run (builder: ValidationBuilder) = + builder.Bind("hello", fun x -> x.Length) +"""; + FsSourceWithFileName "Consumer3.fs" """ +module Consumer3 + +open LibraryImpl + +let run (builder: ValidationBuilder) = + builder.Bind(true, fun x -> if x then 1 else 0) +""" + ] + |> compile + |> shouldSucceed + |> ignore + [] let ``Issue 1565 example 1 compiles`` () = FSharp """ From d1b02eb6855fe7736153540ad4a0727494eef370 Mon Sep 17 00:00:00 2001 From: majocha <1760221+majocha@users.noreply.github.com> Date: Sun, 2 Aug 2026 16:28:41 +0200 Subject: [PATCH 13/21] Handle trait-witness inline regression Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/Compiler/Optimize/Optimizer.fs | 25 +++++++++++---- ...ssion_RecursiveInlineMemberDependencies.fs | 31 +++++++++++++++++++ 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/Compiler/Optimize/Optimizer.fs b/src/Compiler/Optimize/Optimizer.fs index f3df1548ba4..88a309bfff0 100644 --- a/src/Compiler/Optimize/Optimizer.fs +++ b/src/Compiler/Optimize/Optimizer.fs @@ -632,7 +632,7 @@ let GetInfoForLocalValue cenv env (v: Val) m = match env.localExternalVals.TryFind v.Stamp with | Some vval -> vval | None -> - if v.ShouldInline then + if cenv.optimizing && v.ShouldInline then errorR(Error(FSComp.SR.optValueMarkedInlineButWasNotBoundInTheOptEnv(fullDisplayTextOfValRef (mkLocalValRef v)), m)) UnknownValInfo @@ -3148,11 +3148,11 @@ and TryOptimizeVal cenv env (vOpt: ValRef option, shouldInline, inlineIfLambda, | TupleValue _ | UnionCaseValue _ | RecdValue _ when shouldInline -> failwith "tuple, union and record values cannot be marked 'inline'" - | UnknownValue when shouldInline && cenv.settings.alwaysInline -> + | UnknownValue when shouldInline && cenv.settings.alwaysInline && cenv.optimizing -> warning(Error(FSComp.SR.optValueMarkedInlineHasUnexpectedValue(), m)) None - | _ when shouldInline && cenv.settings.alwaysInline -> + | _ when shouldInline && cenv.settings.alwaysInline && cenv.optimizing -> warning(Error(FSComp.SR.optValueMarkedInlineCouldNotBeInlined(), m)) None @@ -3198,7 +3198,7 @@ and OptimizeVal cenv env expr (v: ValRef, m) = e, AddValEqualityInfo g m v einfo | None -> - if cenv.settings.alwaysInline then + if cenv.optimizing && cenv.settings.alwaysInline then if v.ShouldInline then match valInfoForVal.ValExprInfo with | UnknownValue -> error(Error(FSComp.SR.optFailedToInlineValue(v.DisplayName), m)) @@ -4563,6 +4563,11 @@ and GetBindingOptimizationOrder cenv (binds: Binding list) = |> List.mapi (fun idx bind -> bind.Var.Stamp, idx) |> Map.ofList + let addDependency depIdxs stamp = + match bindIndexByStamp |> Map.tryFind stamp with + | Some depIdx -> Set.add depIdx depIdxs + | None -> depIdxs + let bindingArity idx = bindsArray[idx].Var.ValReprInfo |> Option.map (fun repr -> repr.TotalArgCount) @@ -4571,8 +4576,13 @@ and GetBindingOptimizationOrder cenv (binds: Binding list) = let rec addBindingDependencies depIdxs expr = let addVals depIdxs vals = vals - |> Seq.choose (fun (v: Val) -> bindIndexByStamp |> Map.tryFind v.Stamp) - |> Seq.fold (fun depIdxs depIdx -> Set.add depIdx depIdxs) depIdxs + |> Seq.fold (fun depIdxs (v: Val) -> addDependency depIdxs v.Stamp) depIdxs + + let rec addTraitSolutionDependencies depIdxs (traitInfo: TraitConstraintInfo) = + match traitInfo.Solution with + | Some(FSMethSln(_, vref, _, _)) -> addDependency depIdxs vref.Deref.Stamp + | Some(ClosedExprSln witnessExpr) -> addBindingDependencies depIdxs witnessExpr + | _ -> depIdxs let fvs = freeInExpr CollectLocalsNoCaching expr @@ -4586,9 +4596,12 @@ and GetBindingOptimizationOrder cenv (binds: Binding list) = (fun _exprF noInterceptF depIdxs expr -> let depIdxs = match expr with + | Expr.Val(vref, _, _) -> addDependency depIdxs vref.Deref.Stamp // Member-constraint calls can hide the real sibling dependency behind // a witness expression, so fold over the resolved witness as well. | Expr.Op(TOp.TraitCall traitInfo, _, args, m) -> + let depIdxs = addTraitSolutionDependencies depIdxs traitInfo + match ConstraintSolver.CodegenWitnessExprForTraitConstraint cenv.TcVal cenv.g cenv.amap m traitInfo args with | OkResult (_, Some witnessExpr) -> addBindingDependencies depIdxs witnessExpr | _ -> depIdxs diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs index a72994b66ff..2015eaf0200 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs @@ -90,6 +90,37 @@ let run (builder: ValidationBuilder) = |> shouldSucceed |> ignore + [] + let ``Trait-witness inline overload consumers compile`` () = + FSharp """ +module TraitWitnessOverloadRepro + +open System.Runtime.InteropServices + +type Default1 = class end + +type Intersperse = + inherit Default1 + + static member inline Intersperse (x: '``Collection<'T>``, e: 'T, []_impl: Default1) = + x + + static member Intersperse (x: list<'T>, e: 'T, []_impl: Intersperse) = + x + + static member inline Invoke (sep: 'T) (source: '``Collection<'T>``) = + let inline call_2 (a: ^a, b: ^b, s) = + ((^a or ^b): (static member Intersperse: _ * _ * _ -> _) (b, s, a)) + + let inline call (a: 'a, b: 'b, s) = + call_2 (a, b, s) + + call (Unchecked.defaultof, source, sep) : '``Collection<'T>`` + +let _ = Intersperse.Invoke 0 [1] +""" + |> assertCompiles + [] let ``Issue 1565 example 1 compiles`` () = FSharp """ From 492a85007d483b0b72f3e53d7114a0da0cc9a6e7 Mon Sep 17 00:00:00 2001 From: majocha <1760221+majocha@users.noreply.github.com> Date: Sun, 2 Aug 2026 18:21:00 +0200 Subject: [PATCH 14/21] Fix optimizer ordering regressions Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../.FSharp.Compiler.Service/11.0.100.md | 1 + src/Compiler/Optimize/Optimizer.fs | 31 ++++--------------- tests/AheadOfTime/Trimming/check.ps1 | 4 +-- 3 files changed, 9 insertions(+), 27 deletions(-) diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index 9e5b990b2ce..096ab778b43 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -110,6 +110,7 @@ * Reference assembly MVIDs are now deterministic across compiler invocations. Previously, `--refout` / `true` produced a different MVID every build because the implied signature hash used .NET's randomized `String.GetHashCode()`. ([Issue #19751](https://github.com/dotnet/fsharp/issues/19751), [PR #19801](https://github.com/dotnet/fsharp/pull/19801)) * Parser: recover on unfinished if and binary expressions ([PR #19724](https://github.com/dotnet/fsharp/pull/19724)) +* Fix recursive inline-member optimization dependencies so inline consumers in recursive groups are resolved reliably without changing static initialization order. ([PR #20111](https://github.com/dotnet/fsharp/pull/20111)) * Fix `SynExpr.shouldBeParenthesizedInContext` to report parentheses as required around `SynExpr.Sequential` expressions used as record or anonymous-record field values, so the IDE "remove unnecessary parentheses" analyzer no longer breaks code like `{| A = ((); B = 3) |}`. ([Issue #17826](https://github.com/dotnet/fsharp/issues/17826), [PR #19850](https://github.com/dotnet/fsharp/pull/19850)) * Fix semantic classification of `IDisposable` and other interface types in type-occurrence positions being incorrectly classified as `DisposableType` instead of `Interface`. ([Issue #16268](https://github.com/dotnet/fsharp/issues/16268), [PR #19809](https://github.com/dotnet/fsharp/pull/19809)) * Fix missing semantic classification on second and later type qualifiers in nested copy-and-update expressions like `{ p with Person.Info.X = 1; Person.Info.Y = 2 }`. ([Issue #17428](https://github.com/dotnet/fsharp/issues/17428), [PR #19878](https://github.com/dotnet/fsharp/pull/19878)) diff --git a/src/Compiler/Optimize/Optimizer.fs b/src/Compiler/Optimize/Optimizer.fs index 88a309bfff0..2a32105c5a4 100644 --- a/src/Compiler/Optimize/Optimizer.fs +++ b/src/Compiler/Optimize/Optimizer.fs @@ -4448,20 +4448,7 @@ and OptimizeBinding cenv isRec env (TBind(vref, expr, spBind)) = raise (ReportedError (Some exn)) and OptimizeBindings cenv isRec env xs = - if isRec then - let xsArray = xs |> List.toArray - let order = GetBindingOptimizationOrder cenv xs - - let results, env = - (env, order) - ||> List.mapFold (fun env idx -> - let result, env = OptimizeBinding cenv isRec env xsArray[idx] - (idx, result), env) - - let resultsByIndex = results |> Map.ofList - [ for idx in 0 .. xsArray.Length - 1 -> resultsByIndex[idx] ], env - else - List.mapFold (OptimizeBinding cenv isRec) env xs + List.mapFold (OptimizeBinding cenv isRec) env xs and OptimizeModuleExprWithSig cenv env mty def = let g = cenv.g @@ -4556,8 +4543,6 @@ and GetBindingOptimizationOrder cenv (binds: Binding list) = // processed. If a caller is optimized before a later sibling it depends on, inline lookup can // observe an incomplete optimization environment. Compute a dependency-first schedule for the // recursive group, then restore source order after optimization. - let bindsArray = binds |> List.toArray - let bindIndexByStamp = binds |> List.mapi (fun idx bind -> bind.Var.Stamp, idx) @@ -4568,11 +4553,6 @@ and GetBindingOptimizationOrder cenv (binds: Binding list) = | Some depIdx -> Set.add depIdx depIdxs | None -> depIdxs - let bindingArity idx = - bindsArray[idx].Var.ValReprInfo - |> Option.map (fun repr -> repr.TotalArgCount) - |> Option.defaultValue 0 - let rec addBindingDependencies depIdxs expr = let addVals depIdxs vals = vals @@ -4636,9 +4616,6 @@ and GetBindingOptimizationOrder cenv (binds: Binding list) = let rootOrder = [ 0 .. binds.Length - 1 ] - // Prefer leaf-like bindings when there is no explicit dependency edge. This makes - // simple inline members such as getters and conversions available before their callers. - |> List.sortBy (fun idx -> bindingArity idx, -idx) for idx in rootOrder do visit idx @@ -4686,7 +4663,11 @@ and OptimizeModuleBindings cenv isRec (env, bindInfosColl) xs = | ModuleOrNamespaceBinding.Binding bind -> Some bind | _ -> None) - if isRec && (bindingGroup |> List.forall Option.isSome) then + if + isRec + && (bindingGroup |> List.forall Option.isSome) + && (bindingGroup |> List.exists (Option.exists (fun bind -> bind.Var.ShouldInline))) + then let xsArray = xs |> List.toArray let binds = bindingGroup |> List.choose id let order = GetBindingOptimizationOrder cenv binds diff --git a/tests/AheadOfTime/Trimming/check.ps1 b/tests/AheadOfTime/Trimming/check.ps1 index 406eefc616e..49a3af46ca7 100644 --- a/tests/AheadOfTime/Trimming/check.ps1 +++ b/tests/AheadOfTime/Trimming/check.ps1 @@ -63,12 +63,12 @@ function CheckTrim($root, $tfm, $outputfile, $expected_len, $callerLineNumber) { $allErrors = @() # Check net9.0 trimmed assemblies. -$allErrors += CheckTrim -root "SelfContained_Trimming_Test" -tfm "net9.0" -outputfile "FSharp.Core.dll" -expected_len 311296 -callerLineNumber 66 +$allErrors += CheckTrim -root "SelfContained_Trimming_Test" -tfm "net9.0" -outputfile "FSharp.Core.dll" -expected_len 310272 -callerLineNumber 66 # Check net9.0 trimmed assemblies with static linked FSharpCore. # Statically links FSharp.Compiler.Service; the size is stable now that its codegen is # deterministic (#19928/#19929). Update if compiler/trimming output intentionally changes. -$allErrors += CheckTrim -root "StaticLinkedFSharpCore_Trimming_Test" -tfm "net9.0" -outputfile "StaticLinkedFSharpCore_Trimming_Test.dll" -expected_len 9174016 -callerLineNumber 71 +$allErrors += CheckTrim -root "StaticLinkedFSharpCore_Trimming_Test" -tfm "net9.0" -outputfile "StaticLinkedFSharpCore_Trimming_Test.dll" -expected_len 9173504 -callerLineNumber 71 # Check net9.0 trimmed assemblies with F# metadata resources removed $allErrors += CheckTrim -root "FSharpMetadataResource_Trimming_Test" -tfm "net9.0" -outputfile "FSharpMetadataResource_Trimming_Test.dll" -expected_len 7613440 -callerLineNumber 74 From 7a1f5dabb7f5042acaf127602e29f96d285fee95 Mon Sep 17 00:00:00 2001 From: majocha <1760221+majocha@users.noreply.github.com> Date: Sun, 2 Aug 2026 19:53:58 +0200 Subject: [PATCH 15/21] Fix inline member optimization ordering Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/Compiler/Optimize/Optimizer.fs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Compiler/Optimize/Optimizer.fs b/src/Compiler/Optimize/Optimizer.fs index 2a32105c5a4..9cd0abc0150 100644 --- a/src/Compiler/Optimize/Optimizer.fs +++ b/src/Compiler/Optimize/Optimizer.fs @@ -4538,11 +4538,13 @@ and OptimizeModuleExprWithSig cenv env mty def = and mkValBind (bind: Binding) info = (mkLocalValRef bind.Var, info) -and GetBindingOptimizationOrder cenv (binds: Binding list) = +and GetBindingOptimizationOrder cenv preferLowArity (binds: Binding list) = // Recursive binding groups are published to the optimizer incrementally as each binding is // processed. If a caller is optimized before a later sibling it depends on, inline lookup can // observe an incomplete optimization environment. Compute a dependency-first schedule for the // recursive group, then restore source order after optimization. + let bindsArray = binds |> List.toArray + let bindIndexByStamp = binds |> List.mapi (fun idx bind -> bind.Var.Stamp, idx) @@ -4616,6 +4618,16 @@ and GetBindingOptimizationOrder cenv (binds: Binding list) = let rootOrder = [ 0 .. binds.Length - 1 ] + |> (if preferLowArity then + List.sortBy (fun idx -> + let arity = + bindsArray[idx].Var.ValReprInfo + |> Option.map (fun repr -> repr.TotalArgCount) + |> Option.defaultValue 0 + + arity, -idx) + else + id) for idx in rootOrder do visit idx @@ -4670,7 +4682,8 @@ and OptimizeModuleBindings cenv isRec (env, bindInfosColl) xs = then let xsArray = xs |> List.toArray let binds = bindingGroup |> List.choose id - let order = GetBindingOptimizationOrder cenv binds + let preferLowArity = binds |> List.forall (fun bind -> bind.Var.IsMember) + let order = GetBindingOptimizationOrder cenv preferLowArity binds let results, (env, bindInfosColl) = ((env, bindInfosColl), order) From 7eed09a24636d9262ba26c6a0022ce632a402df3 Mon Sep 17 00:00:00 2001 From: majocha <1760221+majocha@users.noreply.github.com> Date: Sun, 2 Aug 2026 22:21:20 +0200 Subject: [PATCH 16/21] Restore recursive optimizer dependency ordering Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/Compiler/Optimize/Optimizer.fs | 28 ++++++++++++++++++++++------ tests/AheadOfTime/Trimming/check.ps1 | 4 ++-- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/Compiler/Optimize/Optimizer.fs b/src/Compiler/Optimize/Optimizer.fs index 9cd0abc0150..46a2c8b2853 100644 --- a/src/Compiler/Optimize/Optimizer.fs +++ b/src/Compiler/Optimize/Optimizer.fs @@ -4448,7 +4448,20 @@ and OptimizeBinding cenv isRec env (TBind(vref, expr, spBind)) = raise (ReportedError (Some exn)) and OptimizeBindings cenv isRec env xs = - List.mapFold (OptimizeBinding cenv isRec) env xs + if isRec then + let xsArray = xs |> List.toArray + let order = GetBindingOptimizationOrder cenv false true xs + + let results, env = + (env, order) + ||> List.mapFold (fun env idx -> + let result, env = OptimizeBinding cenv isRec env xsArray[idx] + (idx, result), env) + + let resultsByIndex = results |> Map.ofList + [ for idx in 0 .. xsArray.Length - 1 -> resultsByIndex[idx] ], env + else + List.mapFold (OptimizeBinding cenv isRec) env xs and OptimizeModuleExprWithSig cenv env mty def = let g = cenv.g @@ -4538,7 +4551,7 @@ and OptimizeModuleExprWithSig cenv env mty def = and mkValBind (bind: Binding) info = (mkLocalValRef bind.Var, info) -and GetBindingOptimizationOrder cenv preferLowArity (binds: Binding list) = +and GetBindingOptimizationOrder cenv inlineDependenciesOnly preferLowArity (binds: Binding list) = // Recursive binding groups are published to the optimizer incrementally as each binding is // processed. If a caller is optimized before a later sibling it depends on, inline lookup can // observe an incomplete optimization environment. Compute a dependency-first schedule for the @@ -4552,8 +4565,10 @@ and GetBindingOptimizationOrder cenv preferLowArity (binds: Binding list) = let addDependency depIdxs stamp = match bindIndexByStamp |> Map.tryFind stamp with - | Some depIdx -> Set.add depIdx depIdxs + | Some depIdx when not inlineDependenciesOnly || bindsArray[depIdx].Var.ShouldInline -> + Set.add depIdx depIdxs | None -> depIdxs + | Some _ -> depIdxs let rec addBindingDependencies depIdxs expr = let addVals depIdxs vals = @@ -4675,15 +4690,16 @@ and OptimizeModuleBindings cenv isRec (env, bindInfosColl) xs = | ModuleOrNamespaceBinding.Binding bind -> Some bind | _ -> None) + let binds = bindingGroup |> List.choose id + if isRec && (bindingGroup |> List.forall Option.isSome) - && (bindingGroup |> List.exists (Option.exists (fun bind -> bind.Var.ShouldInline))) + && (binds |> List.exists (fun bind -> bind.Var.ShouldInline)) then let xsArray = xs |> List.toArray - let binds = bindingGroup |> List.choose id let preferLowArity = binds |> List.forall (fun bind -> bind.Var.IsMember) - let order = GetBindingOptimizationOrder cenv preferLowArity binds + let order = GetBindingOptimizationOrder cenv true preferLowArity binds let results, (env, bindInfosColl) = ((env, bindInfosColl), order) diff --git a/tests/AheadOfTime/Trimming/check.ps1 b/tests/AheadOfTime/Trimming/check.ps1 index 49a3af46ca7..406eefc616e 100644 --- a/tests/AheadOfTime/Trimming/check.ps1 +++ b/tests/AheadOfTime/Trimming/check.ps1 @@ -63,12 +63,12 @@ function CheckTrim($root, $tfm, $outputfile, $expected_len, $callerLineNumber) { $allErrors = @() # Check net9.0 trimmed assemblies. -$allErrors += CheckTrim -root "SelfContained_Trimming_Test" -tfm "net9.0" -outputfile "FSharp.Core.dll" -expected_len 310272 -callerLineNumber 66 +$allErrors += CheckTrim -root "SelfContained_Trimming_Test" -tfm "net9.0" -outputfile "FSharp.Core.dll" -expected_len 311296 -callerLineNumber 66 # Check net9.0 trimmed assemblies with static linked FSharpCore. # Statically links FSharp.Compiler.Service; the size is stable now that its codegen is # deterministic (#19928/#19929). Update if compiler/trimming output intentionally changes. -$allErrors += CheckTrim -root "StaticLinkedFSharpCore_Trimming_Test" -tfm "net9.0" -outputfile "StaticLinkedFSharpCore_Trimming_Test.dll" -expected_len 9173504 -callerLineNumber 71 +$allErrors += CheckTrim -root "StaticLinkedFSharpCore_Trimming_Test" -tfm "net9.0" -outputfile "StaticLinkedFSharpCore_Trimming_Test.dll" -expected_len 9174016 -callerLineNumber 71 # Check net9.0 trimmed assemblies with F# metadata resources removed $allErrors += CheckTrim -root "FSharpMetadataResource_Trimming_Test" -tfm "net9.0" -outputfile "FSharpMetadataResource_Trimming_Test.dll" -expected_len 7613440 -callerLineNumber 74 From 5d57c2ea4a31f1bf400c5a5aa19cd50642dc775a Mon Sep 17 00:00:00 2001 From: majocha <1760221+majocha@users.noreply.github.com> Date: Sun, 2 Aug 2026 23:35:08 +0200 Subject: [PATCH 17/21] Update AOT trimming size baselines Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- tests/AheadOfTime/Trimming/check.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/AheadOfTime/Trimming/check.ps1 b/tests/AheadOfTime/Trimming/check.ps1 index 406eefc616e..2eb01f448b5 100644 --- a/tests/AheadOfTime/Trimming/check.ps1 +++ b/tests/AheadOfTime/Trimming/check.ps1 @@ -63,12 +63,12 @@ function CheckTrim($root, $tfm, $outputfile, $expected_len, $callerLineNumber) { $allErrors = @() # Check net9.0 trimmed assemblies. -$allErrors += CheckTrim -root "SelfContained_Trimming_Test" -tfm "net9.0" -outputfile "FSharp.Core.dll" -expected_len 311296 -callerLineNumber 66 +$allErrors += CheckTrim -root "SelfContained_Trimming_Test" -tfm "net9.0" -outputfile "FSharp.Core.dll" -expected_len 310272 -callerLineNumber 66 # Check net9.0 trimmed assemblies with static linked FSharpCore. # Statically links FSharp.Compiler.Service; the size is stable now that its codegen is # deterministic (#19928/#19929). Update if compiler/trimming output intentionally changes. -$allErrors += CheckTrim -root "StaticLinkedFSharpCore_Trimming_Test" -tfm "net9.0" -outputfile "StaticLinkedFSharpCore_Trimming_Test.dll" -expected_len 9174016 -callerLineNumber 71 +$allErrors += CheckTrim -root "StaticLinkedFSharpCore_Trimming_Test" -tfm "net9.0" -outputfile "StaticLinkedFSharpCore_Trimming_Test.dll" -expected_len 9172992 -callerLineNumber 71 # Check net9.0 trimmed assemblies with F# metadata resources removed $allErrors += CheckTrim -root "FSharpMetadataResource_Trimming_Test" -tfm "net9.0" -outputfile "FSharpMetadataResource_Trimming_Test.dll" -expected_len 7613440 -callerLineNumber 74 From 23213a06096d8e888117b6899215134fd56ef8d2 Mon Sep 17 00:00:00 2001 From: majocha <1760221+majocha@users.noreply.github.com> Date: Mon, 3 Aug 2026 09:50:48 +0200 Subject: [PATCH 18/21] Clarify recursive inline release note Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/release-notes/.FSharp.Compiler.Service/11.0.100.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index 096ab778b43..649d61cc31b 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -110,7 +110,7 @@ * Reference assembly MVIDs are now deterministic across compiler invocations. Previously, `--refout` / `true` produced a different MVID every build because the implied signature hash used .NET's randomized `String.GetHashCode()`. ([Issue #19751](https://github.com/dotnet/fsharp/issues/19751), [PR #19801](https://github.com/dotnet/fsharp/pull/19801)) * Parser: recover on unfinished if and binary expressions ([PR #19724](https://github.com/dotnet/fsharp/pull/19724)) -* Fix recursive inline-member optimization dependencies so inline consumers in recursive groups are resolved reliably without changing static initialization order. ([PR #20111](https://github.com/dotnet/fsharp/pull/20111)) +* Fix recursive inline-member optimization dependencies so inline consumers in recursive groups are resolved reliably without changing static initialization order. ([Issue #20085](https://github.com/dotnet/fsharp/issues/20085), [PR #20111](https://github.com/dotnet/fsharp/pull/20111)) * Fix `SynExpr.shouldBeParenthesizedInContext` to report parentheses as required around `SynExpr.Sequential` expressions used as record or anonymous-record field values, so the IDE "remove unnecessary parentheses" analyzer no longer breaks code like `{| A = ((); B = 3) |}`. ([Issue #17826](https://github.com/dotnet/fsharp/issues/17826), [PR #19850](https://github.com/dotnet/fsharp/pull/19850)) * Fix semantic classification of `IDisposable` and other interface types in type-occurrence positions being incorrectly classified as `DisposableType` instead of `Interface`. ([Issue #16268](https://github.com/dotnet/fsharp/issues/16268), [PR #19809](https://github.com/dotnet/fsharp/pull/19809)) * Fix missing semantic classification on second and later type qualifiers in nested copy-and-update expressions like `{ p with Person.Info.X = 1; Person.Info.Y = 2 }`. ([Issue #17428](https://github.com/dotnet/fsharp/issues/17428), [PR #19878](https://github.com/dotnet/fsharp/pull/19878)) From 7bfd44a769bfdd81bb700603eb1cb06151ace000 Mon Sep 17 00:00:00 2001 From: majocha <1760221+majocha@users.noreply.github.com> Date: Tue, 4 Aug 2026 16:28:10 +0200 Subject: [PATCH 19/21] cross assembly repro --- ...ssion_RecursiveInlineMemberDependencies.fs | 233 +++++++++++++----- 1 file changed, 175 insertions(+), 58 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs index 2015eaf0200..b85017ea43a 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs @@ -4,6 +4,181 @@ open Xunit open FSharp.Test open FSharp.Test.Compiler +module Regression_ParallelCrossAssemblyInlineOverloads = + + [] + let ``Cross-assembly overloaded inline Source members compile and run`` () = + let library = + ( + FSharp """ +module LibraryImpl + +open System.Threading.Tasks +open Microsoft.FSharp.Control + +module Result = + let ofChoice choice = + match choice with + | Choice1Of2 value -> Ok value + | Choice2Of2 error -> Error error + +module Async = + let singleton value = async.Return value + +type Validation<'ok, 'error> = Result<'ok, 'error> +type TaskValidation<'ok, 'error> = Task> + +type TaskValidationBuilderBase() = + member inline _.Return(value: 'ok) : TaskValidation<'ok, 'error> = + task { return Ok value } + + member inline _.ReturnFrom(taskValidation: TaskValidation<'ok, 'error>) : TaskValidation<'ok, 'error> = + taskValidation + + member inline _.Bind + (source: TaskValidation<'okInput, 'error>, binder: 'okInput -> TaskValidation<'okOutput, 'error>) + : TaskValidation<'okOutput, 'error> = + task { + let! result = source + match result with + | Ok value -> return! binder value + | Error error -> return Error error + } + + member inline this.Bind + (source: Validation<'okInput, 'error>, binder: 'okInput -> TaskValidation<'okOutput, 'error>) + : TaskValidation<'okOutput, 'error> = + task { + let! result = this.Source source + match result with + | Ok value -> return! binder value + | Error error -> return Error error + } + + member inline this.Bind + (source: Choice<'okInput, 'error>, binder: 'okInput -> TaskValidation<'okOutput, 'error>) + : TaskValidation<'okOutput, 'error> = + task { + let! result = this.Source source + match result with + | Ok value -> return! binder value + | Error error -> return Error error + } + + member inline this.Bind + (source: Async<'okInput>, binder: 'okInput -> TaskValidation<'okOutput, 'error>) + : TaskValidation<'okOutput, 'error> = + task { + let! result = this.Source source + match result with + | Ok value -> return! binder value + | Error error -> return Error error + } + + member inline _.Delay(generator: unit -> TaskValidation<'ok, 'error>) : TaskValidation<'ok, 'error> = + generator () + + member inline this.Source(result: Validation<'ok, 'error>) : TaskValidation<'ok, 'error> = + task { return result } + + member inline this.Source(choice: Choice<'ok, 'error>) : TaskValidation<'ok, 'error> = + task { + return + choice + |> Result.ofChoice + } + + member inline this.Source(asyncComputation: Async<'ok>) : TaskValidation<'ok, 'error> = + task { + let! value = asyncComputation + return Ok value + } + +type TaskValidationBuilder() = + inherit TaskValidationBuilderBase() + +let taskValidation = TaskValidationBuilder() +""" + |> withAdditionalSourceFile (SourceCodeFileKind.Create("Library.Support.fs", """ +module LibraryImplSupport + +let taskValidation = LibraryImpl.taskValidation +""")) + |> withOutputType CompileOutput.Library + |> withName "Library" + |> withOptimize + |> withOptions ["--parallelcompilation+"; "--nowarn:75"] + |> ignoreWarnings + ) + + let consumerSource = + """module ConsumerImpl + +open LibraryImpl +open LibraryImplSupport + +let run () = + taskValidation.Bind( + Async.singleton 42, + fun asyncValue -> + taskValidation.Bind( + Ok 42, + fun resultValue -> + taskValidation.Bind( + Choice1Of2 42, + fun choiceValue -> + taskValidation.Return(asyncValue + resultValue + choiceValue)))) +""" + + let consumerAdditionalSources = + Array.init 12 (fun i -> + let source = $"""module Consumer{i} + +open LibraryImpl +open LibraryImplSupport + +let run () = + taskValidation.Bind( + Async.singleton 42, + fun asyncValue -> + taskValidation.Bind( + Ok 42, + fun resultValue -> + taskValidation.Bind( + Choice1Of2 42, + fun choiceValue -> + taskValidation.Return(asyncValue + resultValue + choiceValue)))) +""" + + SourceCodeFileKind.Create(sprintf "Consumer.%d.fs" i, source)) + |> Array.toList + + let consumer = + ( + FSharp consumerSource + |> withAdditionalSourceFiles consumerAdditionalSources + |> withAdditionalSourceFile (SourceCodeFileKind.Create("Consumer.Support.fs", """ +module ConsumerSupport + +[] +let main _ = + match (ConsumerImpl.run ()).GetAwaiter().GetResult() with + | Ok value -> if value = 126 then 0 else 1 + | Error _ -> 1 +""")) + |> withOutputType CompileOutput.Exe + |> withReferences [ library ] + |> withOptimize + |> withOptions ["--parallelcompilation+"; "--nowarn:75"] + |> ignoreWarnings + ) + + for _i = 1 to 30 do + consumer + |> compile + |> shouldSucceed + |> ignore + module Regression_RecursiveInlineMemberDependencies = let private assertCompiles source = @@ -32,64 +207,6 @@ let inline run (builder: ValidationBuilder) = """ |> assertCompiles - [] - let ``Cross-assembly inline overload consumers compile`` () = - let library = - FSharpWithFileName "Library.fs" """ -module LibraryImpl - -type ValidationBuilder() = - member inline this.Bind(value: int, binder: int -> int) : int = - binder (this.Source value) - - member inline this.Bind(value: string, binder: string -> int) : int = - binder (this.Source value) - - member inline this.Bind(value: bool, binder: bool -> int) : int = - binder (this.Source value) - - member inline this.Source(value: int) : int = value - member inline this.Source(value: string) : string = value - member inline this.Source(value: bool) : bool = value -""" - |> withOutputType CompileOutput.Library - |> withName "Library" - |> withOptimize - |> withOptions [ "--nowarn:75" ] - |> ignoreWarnings - - FSharpWithFileName "Consumer.fs" """ -module Consumer - -open LibraryImpl - -let run (builder: ValidationBuilder) = - builder.Bind(1, fun x -> x + 1) -""" - |> withReferences [ library ] - |> withOptimize - |> withAdditionalSourceFiles [ - FsSourceWithFileName "Consumer2.fs" """ -module Consumer2 - -open LibraryImpl - -let run (builder: ValidationBuilder) = - builder.Bind("hello", fun x -> x.Length) -"""; - FsSourceWithFileName "Consumer3.fs" """ -module Consumer3 - -open LibraryImpl - -let run (builder: ValidationBuilder) = - builder.Bind(true, fun x -> if x then 1 else 0) -""" - ] - |> compile - |> shouldSucceed - |> ignore - [] let ``Trait-witness inline overload consumers compile`` () = FSharp """ From 7cddf24bd1a91d97873e540655181975d493f655 Mon Sep 17 00:00:00 2001 From: majocha <1760221+majocha@users.noreply.github.com> Date: Tue, 4 Aug 2026 17:40:32 +0200 Subject: [PATCH 20/21] Revert "cross assembly repro" This reverts commit 7bfd44a769bfdd81bb700603eb1cb06151ace000. --- ...ssion_RecursiveInlineMemberDependencies.fs | 233 +++++------------- 1 file changed, 58 insertions(+), 175 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs index b85017ea43a..2015eaf0200 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs @@ -4,181 +4,6 @@ open Xunit open FSharp.Test open FSharp.Test.Compiler -module Regression_ParallelCrossAssemblyInlineOverloads = - - [] - let ``Cross-assembly overloaded inline Source members compile and run`` () = - let library = - ( - FSharp """ -module LibraryImpl - -open System.Threading.Tasks -open Microsoft.FSharp.Control - -module Result = - let ofChoice choice = - match choice with - | Choice1Of2 value -> Ok value - | Choice2Of2 error -> Error error - -module Async = - let singleton value = async.Return value - -type Validation<'ok, 'error> = Result<'ok, 'error> -type TaskValidation<'ok, 'error> = Task> - -type TaskValidationBuilderBase() = - member inline _.Return(value: 'ok) : TaskValidation<'ok, 'error> = - task { return Ok value } - - member inline _.ReturnFrom(taskValidation: TaskValidation<'ok, 'error>) : TaskValidation<'ok, 'error> = - taskValidation - - member inline _.Bind - (source: TaskValidation<'okInput, 'error>, binder: 'okInput -> TaskValidation<'okOutput, 'error>) - : TaskValidation<'okOutput, 'error> = - task { - let! result = source - match result with - | Ok value -> return! binder value - | Error error -> return Error error - } - - member inline this.Bind - (source: Validation<'okInput, 'error>, binder: 'okInput -> TaskValidation<'okOutput, 'error>) - : TaskValidation<'okOutput, 'error> = - task { - let! result = this.Source source - match result with - | Ok value -> return! binder value - | Error error -> return Error error - } - - member inline this.Bind - (source: Choice<'okInput, 'error>, binder: 'okInput -> TaskValidation<'okOutput, 'error>) - : TaskValidation<'okOutput, 'error> = - task { - let! result = this.Source source - match result with - | Ok value -> return! binder value - | Error error -> return Error error - } - - member inline this.Bind - (source: Async<'okInput>, binder: 'okInput -> TaskValidation<'okOutput, 'error>) - : TaskValidation<'okOutput, 'error> = - task { - let! result = this.Source source - match result with - | Ok value -> return! binder value - | Error error -> return Error error - } - - member inline _.Delay(generator: unit -> TaskValidation<'ok, 'error>) : TaskValidation<'ok, 'error> = - generator () - - member inline this.Source(result: Validation<'ok, 'error>) : TaskValidation<'ok, 'error> = - task { return result } - - member inline this.Source(choice: Choice<'ok, 'error>) : TaskValidation<'ok, 'error> = - task { - return - choice - |> Result.ofChoice - } - - member inline this.Source(asyncComputation: Async<'ok>) : TaskValidation<'ok, 'error> = - task { - let! value = asyncComputation - return Ok value - } - -type TaskValidationBuilder() = - inherit TaskValidationBuilderBase() - -let taskValidation = TaskValidationBuilder() -""" - |> withAdditionalSourceFile (SourceCodeFileKind.Create("Library.Support.fs", """ -module LibraryImplSupport - -let taskValidation = LibraryImpl.taskValidation -""")) - |> withOutputType CompileOutput.Library - |> withName "Library" - |> withOptimize - |> withOptions ["--parallelcompilation+"; "--nowarn:75"] - |> ignoreWarnings - ) - - let consumerSource = - """module ConsumerImpl - -open LibraryImpl -open LibraryImplSupport - -let run () = - taskValidation.Bind( - Async.singleton 42, - fun asyncValue -> - taskValidation.Bind( - Ok 42, - fun resultValue -> - taskValidation.Bind( - Choice1Of2 42, - fun choiceValue -> - taskValidation.Return(asyncValue + resultValue + choiceValue)))) -""" - - let consumerAdditionalSources = - Array.init 12 (fun i -> - let source = $"""module Consumer{i} - -open LibraryImpl -open LibraryImplSupport - -let run () = - taskValidation.Bind( - Async.singleton 42, - fun asyncValue -> - taskValidation.Bind( - Ok 42, - fun resultValue -> - taskValidation.Bind( - Choice1Of2 42, - fun choiceValue -> - taskValidation.Return(asyncValue + resultValue + choiceValue)))) -""" - - SourceCodeFileKind.Create(sprintf "Consumer.%d.fs" i, source)) - |> Array.toList - - let consumer = - ( - FSharp consumerSource - |> withAdditionalSourceFiles consumerAdditionalSources - |> withAdditionalSourceFile (SourceCodeFileKind.Create("Consumer.Support.fs", """ -module ConsumerSupport - -[] -let main _ = - match (ConsumerImpl.run ()).GetAwaiter().GetResult() with - | Ok value -> if value = 126 then 0 else 1 - | Error _ -> 1 -""")) - |> withOutputType CompileOutput.Exe - |> withReferences [ library ] - |> withOptimize - |> withOptions ["--parallelcompilation+"; "--nowarn:75"] - |> ignoreWarnings - ) - - for _i = 1 to 30 do - consumer - |> compile - |> shouldSucceed - |> ignore - module Regression_RecursiveInlineMemberDependencies = let private assertCompiles source = @@ -207,6 +32,64 @@ let inline run (builder: ValidationBuilder) = """ |> assertCompiles + [] + let ``Cross-assembly inline overload consumers compile`` () = + let library = + FSharpWithFileName "Library.fs" """ +module LibraryImpl + +type ValidationBuilder() = + member inline this.Bind(value: int, binder: int -> int) : int = + binder (this.Source value) + + member inline this.Bind(value: string, binder: string -> int) : int = + binder (this.Source value) + + member inline this.Bind(value: bool, binder: bool -> int) : int = + binder (this.Source value) + + member inline this.Source(value: int) : int = value + member inline this.Source(value: string) : string = value + member inline this.Source(value: bool) : bool = value +""" + |> withOutputType CompileOutput.Library + |> withName "Library" + |> withOptimize + |> withOptions [ "--nowarn:75" ] + |> ignoreWarnings + + FSharpWithFileName "Consumer.fs" """ +module Consumer + +open LibraryImpl + +let run (builder: ValidationBuilder) = + builder.Bind(1, fun x -> x + 1) +""" + |> withReferences [ library ] + |> withOptimize + |> withAdditionalSourceFiles [ + FsSourceWithFileName "Consumer2.fs" """ +module Consumer2 + +open LibraryImpl + +let run (builder: ValidationBuilder) = + builder.Bind("hello", fun x -> x.Length) +"""; + FsSourceWithFileName "Consumer3.fs" """ +module Consumer3 + +open LibraryImpl + +let run (builder: ValidationBuilder) = + builder.Bind(true, fun x -> if x then 1 else 0) +""" + ] + |> compile + |> shouldSucceed + |> ignore + [] let ``Trait-witness inline overload consumers compile`` () = FSharp """ From 65a188e2e4447ae8ca0b257763090176ba7694fd Mon Sep 17 00:00:00 2001 From: majocha <1760221+majocha@users.noreply.github.com> Date: Tue, 4 Aug 2026 23:15:44 +0200 Subject: [PATCH 21/21] Remove unrelated cross-assembly regression Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- ...ssion_RecursiveInlineMemberDependencies.fs | 58 ------------------- 1 file changed, 58 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs index 2015eaf0200..247b46545df 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Inlining/Regression_RecursiveInlineMemberDependencies.fs @@ -32,64 +32,6 @@ let inline run (builder: ValidationBuilder) = """ |> assertCompiles - [] - let ``Cross-assembly inline overload consumers compile`` () = - let library = - FSharpWithFileName "Library.fs" """ -module LibraryImpl - -type ValidationBuilder() = - member inline this.Bind(value: int, binder: int -> int) : int = - binder (this.Source value) - - member inline this.Bind(value: string, binder: string -> int) : int = - binder (this.Source value) - - member inline this.Bind(value: bool, binder: bool -> int) : int = - binder (this.Source value) - - member inline this.Source(value: int) : int = value - member inline this.Source(value: string) : string = value - member inline this.Source(value: bool) : bool = value -""" - |> withOutputType CompileOutput.Library - |> withName "Library" - |> withOptimize - |> withOptions [ "--nowarn:75" ] - |> ignoreWarnings - - FSharpWithFileName "Consumer.fs" """ -module Consumer - -open LibraryImpl - -let run (builder: ValidationBuilder) = - builder.Bind(1, fun x -> x + 1) -""" - |> withReferences [ library ] - |> withOptimize - |> withAdditionalSourceFiles [ - FsSourceWithFileName "Consumer2.fs" """ -module Consumer2 - -open LibraryImpl - -let run (builder: ValidationBuilder) = - builder.Bind("hello", fun x -> x.Length) -"""; - FsSourceWithFileName "Consumer3.fs" """ -module Consumer3 - -open LibraryImpl - -let run (builder: ValidationBuilder) = - builder.Bind(true, fun x -> if x then 1 else 0) -""" - ] - |> compile - |> shouldSucceed - |> ignore - [] let ``Trait-witness inline overload consumers compile`` () = FSharp """