diff --git a/src/Fable.Cli/CHANGELOG.md b/src/Fable.Cli/CHANGELOG.md index 24a14d18ec..a50706b5e1 100644 --- a/src/Fable.Cli/CHANGELOG.md +++ b/src/Fable.Cli/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +* [Python] Fix function references passed as arguments inside tail-call optimised functions gaining unnecessary default parameters for outer TCO variables they don't reference (fix #3877) * [TS] Fix abstract instance methods not emitted in TypeScript when class uses `[]` (fixes #3506) (by @MangelMaxime) * [JS/TS] Fix `Unchecked.defaultof<'T>` for struct types with fields returning `undefined` instead of zero-initialized values (by @MangelMaxime) * [JS/TS] Fixed quotation for union string cases (by @MangelMaxime) diff --git a/src/Fable.Compiler/CHANGELOG.md b/src/Fable.Compiler/CHANGELOG.md index 85c6e927bb..fd2d970c6c 100644 --- a/src/Fable.Compiler/CHANGELOG.md +++ b/src/Fable.Compiler/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +* [Python] Fix function references passed as arguments inside tail-call optimised functions gaining unnecessary default parameters for outer TCO variables they don't reference (fix #3877) * [TS] Fix abstract instance methods not emitted in TypeScript when class uses `[]` (fixes #3506) (by @MangelMaxime) * [JS/TS] Fix `Unchecked.defaultof<'T>` for struct types with fields returning `undefined` instead of zero-initialized values (by @MangelMaxime) * [JS/TS] Fixed quotation for union string cases (by @MangelMaxime) diff --git a/src/Fable.Transforms/Python/Fable2Python.Transforms.fs b/src/Fable.Transforms/Python/Fable2Python.Transforms.fs index a4faafa166..2ec963ca0c 100644 --- a/src/Fable.Transforms/Python/Fable2Python.Transforms.fs +++ b/src/Fable.Transforms/Python/Fable2Python.Transforms.fs @@ -3005,6 +3005,10 @@ let transformFunction match name with | "tupled_arg_m" -> None // Remove these arguments (not sure why) + // Only capture TCO variables actually referenced in the function body. + // This avoids unnecessary default parameters on nested lambdas that don't + // use the outer TCO variables. See #3877. + | _ when not (isIdentUsed name body) -> None | _ -> let annotation = // Cleanup type annotations to avoid non-repeated generics diff --git a/tests/Python/TestTailCall.fs b/tests/Python/TestTailCall.fs index 29894849f2..0311682d15 100644 --- a/tests/Python/TestTailCall.fs +++ b/tests/Python/TestTailCall.fs @@ -292,3 +292,17 @@ let ``test Expressiones captured in a closure don't change after tail-call optim main init view update accValue |> equal 10 + +// See https://github.com/fable-compiler/Fable/issues/3877 +[] +let ``test Passing a function reference in a TCO context does not capture unneeded outer args`` () = + let applyFn (f: unit -> int) = f() + let alwaysFortyTwo () = 42 + + // `alwaysFortyTwo` doesn't reference `_unused`, so it should not capture it + // even though `loop` is tail-call optimised with `_unused` in scope. + let rec loop (_unused: string) acc n = + if n <= 0 then acc + else loop _unused (acc + applyFn alwaysFortyTwo) (n - 1) + + loop "ignored" 0 5 |> equal 210