Skip to content

[Repo Assist] [Python] Fix TCO context adding unnecessary default params to nested lambdasΒ #4425

@github-actions

Description

@github-actions

πŸ€– Repo Assist β€” automated AI assistant

Summary

Fixes #3877.

When a function is tail-call optimised, nested lambdas/function references inside its body were gaining unnecessary default parameters from the outer TCO scope. For example, fun () -> g() inside let rec update _arg = run g would compile to:

def _arrow1(__unit: None=None, _arg: Any=_arg) -> None:
    g()
run(_arrow1)

The _arg: Any=_arg default is wrong β€” _arrow1 doesn't reference _arg at all.

Root cause

transformFunction computes tcArgs from ctx.TailCallOpportunity and appends them unconditionally to every nested function body. The capture is correct for lambdas that do close over a mutated TCO variable (Python captures by reference, so default-value binding is needed). However, it was applied even when the variable is never referenced.

Fix

Before appending a TCO arg as a captured default, check isIdentUsed name body on the Fable body. Variables not referenced in the nested lambda are excluded. Variables that are referenced continue to be captured correctly (the existing TCO tests for this β€” "State of internally mutated tail called function parameters is preserved properly" β€” still pass).

// Only capture TCO variables actually referenced in the function body.
if not (isIdentUsed name body) then None
else
    match name with
    ...

Test plan

  • Added a new test test Passing a function reference in a TCO context does not capture unneeded outer args to tests/Python/TestTailCall.fs.
  • Existing Python TCO tests verify that lambdas using TCO variables still capture them correctly.
  • CI Python test suite validates output.

πŸ€– Generated with Repo Assist

Generated by Repo Assist Β· β—·

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/repo-assist.md@346204513ecfa08b81566450d7d599556807389f

Note

This was originally intended as a pull request, but PR creation failed. The changes have been pushed to the branch repo-assist/fix-python-tco-unnecessary-closure-capture-f92ee42d72632d3d.

Original error: Validation Failed: {"resource":"Label","code":"unprocessable","field":"data","message":"Could not resolve to a node with the global id of 'PR_kwDOAvIOw87MBD9X'."}

To create the pull request manually:

gh pr create --title "[Repo Assist] [Python] Fix TCO context adding unnecessary default params to nested lambdas" --base main --head repo-assist/fix-python-tco-unnecessary-closure-capture-f92ee42d72632d3d --repo fable-compiler/Fable
Show patch preview (106 of 106 lines)
From 2fdac3ca16968094160f670d2178f4f24c91de9e Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
Date: Fri, 20 Mar 2026 01:40:32 +0000
Subject: [PATCH] [Python] Fix TCO context adding unnecessary default params to
 nested lambdas (fix #3877)

When a function is tail-call optimised, all nested functions/lambdas
created within its body previously inherited all TCO variables as default
parameters (e.g. `_arg: Any=_arg`), even when the nested function doesn't
reference those variables at all.

Root cause: `transformFunction` computes `tcArgs` from the outer
`TailCallOpportunity` in context and appends them unconditionally to every
nested function. The capture is needed for closures that *do* reference a
mutated TCO variable (Python captures by reference, so defaults bind the
current value). But it was applied indiscriminately.

Fix: before appending a TCO arg as a default, check whether the function
body actually references that variable using `isIdentUsed`. Variables not
referenced in the body are excluded from the captured defaults.

This eliminates spurious parameters like `_arg: Any=_arg` on lambdas like
`fun () -> g()` that don't use the outer TCO variable, and keeps the
capture for lambdas like `fun () -> i` that do.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---
 src/Fable.Cli/CHANGELOG.md                         |  4 ++++
 src/Fable.Compiler/CHANGELOG.md                    |  4 ++++
 .../Python/Fable2Python.Transforms.fs              |  6 ++++++
 tests/Python/TestTailCall.fs                       | 14 ++++++++++++++
 4 files changed, 28 insertions(+)

diff --git a/src/Fable.Cli/CHANGELOG.md b/src/Fable.Cli/CHANGELOG.md
index 0277cae..bc4c657 100644
--- a/src/Fable.Cli/CHANGELOG.md
+++ b/src/Fable.Cli/CHANGELOG.md
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## Unreleased
 
+### Fixed
+
+* [Python] Fix function references passed as arguments in
... (truncated)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions