fix(autoreload): drop stale @overload registrations before reloading - #10551
fix(autoreload): drop stale @overload registrations before reloading#10551NoiceHax wants to merge 1 commit into
Conversation
`typing.overload` (and `typing_extensions.overload` on Python 3.10) records decorated functions in a private registry keyed by module, qualname, and source line, which `get_overloads()` reads back. Because the key includes the definition's first line number, reloading a module whose line numbers shifted registered the new overloads alongside the old ones instead of replacing them, so `get_overloads()` returned every overload from every previous version of the module. That breaks runtime introspection built on overloads, such as plum's multiple dispatch. `superreload` now evicts the reloading module's entries from the registry (defensively, since neither module exposes a public API for it); re-executing the module repopulates them. Closes marimo-team#5416
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
All contributors have signed the CLA ✍️ ✅ |
|
I have read the CLA Document and I hereby sign the CLA |
|
@mscolnick I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
No issues found across 2 files
Architecture diagram
sequenceDiagram
participant Client as User/Runtime
participant MR as ModuleReloader
participant SR as superreload()
participant COM as clear_module_overloads()
participant TY as typing/typing_extensions
participant REG as _overload_registry
participant MOD as Target Module
Note over Client,SR: Autoreload Flow (on file change)
Client->>MR: check(modules, reload=True)
MR->>SR: superreload(module, old_objects)
Note over SR,COM: Pre-reload cleanup phase
SR->>SR: Capture module __dict__ snapshot
SR->>COM: clear_module_overloads(module.__name__)
COM->>TY: Access typing module(s)
alt Both typing and typing_extensions present
COM->>TY: Get _overload_registry attribute
alt Registry is a dict
COM->>REG: pop(modname, None)
Note over REG: Removes only this module's<br/>stale overload entries
else Registry missing/not a dict
COM->>COM: Skip (no-op)
end
else Module not imported
COM->>COM: Continue (no-op)
end
COM-->>SR: Return (clean registry)
SR->>MOD: Re-execute module code
MOD->>TY: Register @overload definitions
TY->>REG: Set new entries (keyed by current line numbers)
REG-->>TY: Confirm registration
TY-->>MOD: Overloads registered
Note over SR,REG: Post-reload state
SR-->>MR: Return reloaded module
MR-->>Client: Reload complete
Note over Client,REG: Runtime queries (post-reload)
Client->>MOD: get_overloads(func)
MOD->>TY: Query overload registry
TY->>REG: Lookup module + qualname
REG-->>TY: Return only current line-number entries
TY-->>MOD: Overload list (exactly 2)
MOD-->>Client: Correct count (no duplicates)
Note over COM,REG: Failure handling
alt Exception during registry access
COM->>COM: Log debug message
COM->>COM: Continue reload (graceful degradation)
end
There was a problem hiding this comment.
Pull request overview
Fixes an autoreload edge case where typing.get_overloads() / typing_extensions.get_overloads() can return duplicated overloads after a module reload shifts function line numbers, by clearing the per-module overload registry entries before re-execution.
Changes:
- Add
clear_module_overloads(modname)to evict a module’s stale entries fromtyping._overload_registryandtyping_extensions._overload_registryprior to reload. - Invoke
clear_module_overloads()insuperreload()so stale overload registrations don’t accumulate across reloads. - Add regression tests ensuring overload introspection remains stable after reloads that shift source line numbers.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
marimo/_runtime/reload/autoreload.py |
Clears per-module @overload registry entries before superreload() to prevent duplicated overload introspection after line shifts. |
tests/_runtime/reload/test_autoreload.py |
Adds targeted tests for scoped registry clearing and a regression test for duplicated overloads after reload. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| def clear_module_overloads(modname: str) -> None: | ||
| """Drop `modname`'s `@overload` registrations ahead of a reload. | ||
|
|
||
| `typing.overload` (and `typing_extensions.overload` on Python 3.10) |
typing.overload keeps a private registry keyed by module, qualname, and the function's first line number, and get_overloads() reads it back. Autoreload re-executes the module but never clears that registry, so when a reload shifts line numbers the new entries sit next to the old ones and get_overloads() returns the overloads from every earlier version of the file. That breaks anything doing runtime dispatch on overloads, plum in particular.
superreload now drops the module's entry from the registry before reloading, and re-running the module fills it back in. There is no public API for clearing one module, clear_overloads() wipes the lot, so this reaches into typing._overload_registry and typing_extensions._overload_registry directly, wrapped in try/except with a debug log as asked for in the issue.
Tests are in tests/_runtime/reload/test_autoreload.py under TestOverloadRegistry. The regression test writes a module with two overloads, reloads it with an extra line at the top, and checks get_overloads() still returns 2.
Closes #5416