Skip to content

Conversation

@JelleZijlstra
Copy link
Member

@JelleZijlstra JelleZijlstra commented Jun 17, 2023

Demonstration:

>>> ast.FunctionDef.__annotations__
{'name': <class 'str'>, 'args': <class 'ast.arguments'>, 'body': list[ast.stmt], 'decorator_list': list[ast.expr], 'returns': ast.expr | None, 'type_comment': str | None, 'type_params': list[ast.type_param]}
>>> ast.FunctionDef()
<stdin>:1: DeprecationWarning: FunctionDef.__init__ missing 1 required positional argument: 'name'. This will become an error in Python 3.15.
<stdin>:1: DeprecationWarning: FunctionDef.__init__ missing 1 required positional argument: 'args'. This will become an error in Python 3.15.
<ast.FunctionDef object at 0x101959460>
>>> node = ast.FunctionDef(name="foo", args=ast.arguments())
>>> node.decorator_list
[]
>>> ast.FunctionDef(whatever="you want", name="x", args=ast.arguments())
<stdin>:1: DeprecationWarning: FunctionDef.__init__ got an unexpected keyword argument 'whatever'. Support for arbitrary keyword arguments is deprecated and will be removed in Python 3.15.
<ast.FunctionDef object at 0x1019581f0>

Demonstration:

>>> ast.FunctionDef.__annotations__
{'name': <class 'str'>, 'args': <class 'ast.arguments'>, 'body': list[ast.stmt], 'decorator_list': list[ast.expr], 'returns': ast.expr | None, 'type_comment': str | None, 'type_params': list[ast.type_param]}
>>> ast.FunctionDef()
<stdin>:1: DeprecationWarning: FunctionDef.__init__ missing 1 required positional argument: 'name'. This will become an error in Python 3.15.
<stdin>:1: DeprecationWarning: FunctionDef.__init__ missing 1 required positional argument: 'args'. This will become an error in Python 3.15.
<ast.FunctionDef object at 0x101959460>
>>> node = ast.FunctionDef(name="foo", args=ast.arguments())
>>> node.decorator_list
[]
>>> ast.FunctionDef(whatever="you want", name="x", args=ast.arguments())
<stdin>:1: DeprecationWarning: FunctionDef.__init__ got an unexpected keyword argument 'whatever'. Support for arbitrary keyword arguments is deprecated and will be removed in Python 3.15.
<ast.FunctionDef object at 0x1019581f0>

Known problems:
- Subclasses of AST nodes don't work properly, because we don't look up __annotations__ on the
  right class.
- Unpickling throws DeprecationWarnings, probably because of how we construct the unpickled
  object.

Need to think more about how to handle those cases.
@JelleZijlstra JelleZijlstra marked this pull request as ready for review October 24, 2023 19:55
@JelleZijlstra JelleZijlstra added the 🔨 test-with-refleak-buildbots Test PR w/ refleak buildbots; report in status section label Feb 18, 2024
@bedevere-bot
Copy link

🤖 New build scheduled with the buildbot fleet by @JelleZijlstra for commit a2442b6 🤖

If you want to schedule another build, you need to add the 🔨 test-with-refleak-buildbots label again.

@bedevere-bot bedevere-bot removed the 🔨 test-with-refleak-buildbots Test PR w/ refleak buildbots; report in status section label Feb 18, 2024
@JelleZijlstra JelleZijlstra requested a review from carljm February 19, 2024 05:08
Comment on lines +1053 to +1056
// Serialize the fields as positional args if possible, because if we
// serialize them as a dict, during unpickling they are set only *after*
// the object is constructed, which will now trigger a DeprecationWarning
// if the AST type has required fields.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do AST types even need their own custom reduce function? Shouldn't object.__reduce__ (which IIRC uses __new__ to safely create an empty instance, then updates its __dict__) work fine for AST objects as well?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The __new__ call will trigger DeprecationWarnings. If we don't have our own reducer, unpickling will essentially do node = ast.FunctionDef(); node.__dict__.update(...), and the first line raises warnings because we don't set the required name field.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would the __new__ call raise deprecation warnings? Aren't the deprecation warnings in __init__, not __new__? That's why object.reduce() uses __new__, so that it can bypass whatever is happening in custom __init__ methods (otherwise lots of custom classes would fail to unpickle by default). So object.reduce() is not equivalent to node = ast.FunctionDef(); node.__dict__.update(...) -- it's more like node = ast.FunctionDef.__new__(ast.FunctionDef); node.__dict__.update(...), which is a critical difference. So it seems to me like using the default reducer should work fine here, and not require all this extra work. But I could definitely be missing something; there's probably some reason these nodes had a custom reduce() in the first place.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean. I tried commenting out the __reduce__ definition, and lots of tests fail with:

ERROR: test_pickling (test.test_ast.AST_Tests.test_pickling) (ast=<ast.Module object at 0x102323da0>, protocol=1)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/jelle/py/cpython/Lib/test/test_ast.py", line 977, in test_pickling
    ast2 = pickle.loads(pickle.dumps(ast, protocol))
                        ~~~~~~~~~~~~^^^^^^^^^^^^^^^
  File "/Users/jelle/py/cpython/Lib/copyreg.py", line 71, in _reduce_ex
    state = base(self)
            ~~~~^^^^^^
TypeError: AST constructor takes at most 0 positional arguments

I don't entirely understand what _reduce_ex is doing there, but it picks ast.AST as the base, which fails.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. Well, I might have just nerd-sniped myself into digging into this more later to understand what's happening, but I don't think it should block the PR; your added code to use positional args works fine.

Copy link
Member

@carljm carljm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are satisfied with the Discourse thread outcome, the code here LGTM! Thanks for this improvement.

@bedevere-bot

This comment was marked as duplicate.

@JelleZijlstra
Copy link
Member Author

Reproduces locally with ./python.exe -m test -u cpu test_peg_generator -v. Now looking into ways to let the extension build by the PEG generator access this type, or find some workaround.

@bedevere-bot

This comment was marked as duplicate.

@bedevere-bot

This comment was marked as duplicate.

@bedevere-bot

This comment was marked as duplicate.

@bedevere-bot

This comment was marked as duplicate.

@bedevere-bot

This comment was marked as duplicate.

@bedevere-bot

This comment was marked as duplicate.

@bedevere-bot

This comment was marked as duplicate.

@bedevere-bot

This comment was marked as duplicate.

@bedevere-bot

This comment was marked as duplicate.

@bedevere-bot

This comment was marked as duplicate.

@bedevere-bot

This comment was marked as duplicate.

@bedevere-bot

This comment was marked as duplicate.

@bedevere-bot

This comment was marked as duplicate.

Comment on lines +1081 to +1092
if (!value) {
if (PyErr_Occurred()) {
goto cleanup;
}
break;
}
if (PyList_Append(positional_args, value) < 0) {
goto cleanup;
}
if (PyDict_DelItem(remaining_dict, name) < 0) {
goto cleanup;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are leaks of name here. They will be fixed in #116438.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants