Skip to content

marimo convert: variable renamed inside nested function bodies but not their parameters, producing wrong results #10736

Description

@damyantilev

Describe the bug

When marimo convert privatizes a cell-level variable x to _x (because it is defined in multiple cells and would otherwise collide), it also renames every occurrence of x inside that cell — including references within nested def/lambda bodies whose own parameter is named x. The parameter is left as x, so the body now reads the cell-level _x closure instead of the argument.

This is silent when the enclosing cell defines _x: the function ignores its argument and reads the closure, so it runs without error and produces a wrong result (no crash, and marimo check reports nothing). When the enclosing cell does not define _x, it instead becomes a NameError / undefined-name at runtime.

Root problem: a function/lambda parameter introduces a new scope that shadows the cell-level name, so occurrences of that name inside the function body should not be rewritten by the cell-level rename.

Expected: the nested function's parameter and its uses stay consistent — either the parameter is renamed too, or (correct) the body is left alone because the parameter shadows the cell-level variable.

Actual: body uses are renamed to _x, parameter stays x → function silently ignores its argument.

Reproduction

bug.ipynb — two code cells (n appears in both cells, so convert privatizes it):

Cell 1:

def double(n):
    return n * 2

n = 5
print('double(3) =', double(3))   # correct: 6

Cell 2:

n = 999
print('n =', n)

Run:

marimo convert bug.ipynb -o bug.py

Produced bug.py (note def double(n) but body return _n * 2):

import marimo

__generated_with = "0.24.0"
app = marimo.App()


@app.cell
def _():
    def double(n):
        return _n * 2
    _n = 5
    print('double(3) =', double(3))
    return


@app.cell
def _():
    _n = 999
    print('n =', _n)
    return


if __name__ == "__main__":
    app.run()

double(3) now returns 10 (_n * 2 = 5 * 2) instead of 6. The function silently ignores its argument. This also affects lambda parameters, comprehension/for targets, and with ... as targets that share a name with a privatized cell-level variable.

Will you submit a PR?

  • Yes

Environment

{
  "marimo": "0.24.0",
  "editable": false,
  "OS": "Linux",
  "OS Version": "6.17.0-1032-oem",
  "Processor": "x86_64",
  "Python Version": "3.12.13",
  "Locale": "en_US",
  "Binaries": {
    "Browser": "--",
    "Node": "v18.19.1",
    "uv": "0.10.8"
  },
  "Dependencies": {
    "click": "8.4.2",
    "docutils": "0.23",
    "itsdangerous": "2.2.0",
    "jedi": "0.19.2",
    "markdown": "3.10.3",
    "narwhals": "2.24.0",
    "packaging": "26.2",
    "psutil": "7.2.2",
    "pygments": "2.20.0",
    "pymdown-extensions": "11.0.2",
    "pyyaml": "6.0.3",
    "starlette": "1.2.1",
    "tomlkit": "0.15.1",
    "typing-extensions": "4.16.0",
    "uvicorn": "0.52.0",
    "websockets": "16.1.1"
  },
  "Optional Dependencies": {
    "loro": "1.13.2",
    "openai": "2.50.0",
    "pandas": "3.0.5",
    "pytest": "9.1.1",
    "ruff": "0.16.0"
  },
  "Experimental Flags": {}
}

Code to reproduce

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions