Repository navigation
deduperreload gives invalid tracebacks in reloaded functions #15359
Copy link
Copy link
Open
Description
Activity
I'm not sure why
DeduperReloader._patch_namespace_inner()unparses the AST node to text here instead of directly wrapping it with anast.ClassDef:
ipython/IPython/extensions/deduperreload/deduperreload.py
Lines 448 to 453 in dd41c52
# exec new source code using old function's (obj) globals environment. func_code = textwrap.dedent(ast.unparse(new_ast_def)) if is_method := (len(prefixes) > 0): func_code = "class __autoreload_class__:\n" + textwrap.indent( func_code, " " ) Something like this should preserve all the line number information from the source file:
@@ -446,11 +446,8 @@ def _patch_namespace_inner( if isinstance(to_patch_to, (staticmethod, classmethod)): to_patch_to = to_patch_to.__func__ # exec new source code using old function's (obj) globals environment. - func_code = textwrap.dedent(ast.unparse(new_ast_def)) if is_method := (len(prefixes) > 0): - func_code = "class __autoreload_class__:\n" + textwrap.indent( - func_code, " " - ) + new_ast_def = ast.ClassDef(name="__autoreload_class__", body=[new_ast_def]) global_env = ns.__dict__ if not isinstance(global_env, dict): global_env = dict(global_env) @@ -460,7 +457,7 @@ def _patch_namespace_inner( and to_patch_to.__code__.co_filename or "<string>" ) - func_asts = [ast.parse(func_code)] + func_asts = [new_ast_def] if len(cast(ast.FunctionDef, func_asts[0].body[0]).decorator_list) > 0: without_decorator_list = pickle.loads(pickle.dumps(func_asts[0])) cast(
I've been testing out #15369 locally, and found a deeper issue: the traceback of a non-reloaded function can point to the wrong line if the function definition was moved around in the file due to added or removed lines before it.
It looks like it might be possible to update the function line numbers in-place with
func.__code__ = func.__code__.replace(co_firstlineno=<new line number>).
Metadata
Metadata
Assignees
Labels
No labels
When a function is reloaded with deduperreload, the line number information from the source file is lost. This breaks tracebacks, causing them to point to the start of the file instead of the correct source line. The reported line number matches the offset from the start of the function (plus one if the function is inside a class), but any comments and blank lines are ignored.
Tested with Python 3.13.14, IPython 9.15.0.