-
-
Notifications
You must be signed in to change notification settings - Fork 32.6k
gh-116021: Deprecate support for instantiating abstract AST nodes #137865
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
gh-116021: Deprecate support for instantiating abstract AST nodes #137865
Conversation
check(_ast.AST(), size('P')) | ||
with self.assertWarns(DeprecationWarning): | ||
check(_ast.AST(), size('P')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should probably be rewritten, but I'm not sure I understand the goal of this test well enough to do that. Suggestions welcome!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's to check that objects in Python/
have the correct types. I don't know why it's actually in test_sys
though. I think we should split this test and move each check to the module it belongs (ast.AST's size should be checked in test_ast).
For now, leave it here. A separate issue should be opened.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be easier to have a module function checking if a node is abstract? say _ast.is_abstract(node)
(in the C module, not the Python module). That way we don't need an additional method on AST objects (which could be shadowed).
check(_ast.AST(), size('P')) | ||
with self.assertWarns(DeprecationWarning): | ||
check(_ast.AST(), size('P')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's to check that objects in Python/
have the correct types. I don't know why it's actually in test_sys
though. I think we should split this test and move each check to the module it belongs (ast.AST's size should be checked in test_ast).
For now, leave it here. A separate issue should be opened.
if (PyErr_WarnFormat( | ||
PyExc_DeprecationWarning, 1, | ||
"Instantiating abstract AST node class %T is deprecated. " | ||
"This will become an error in Python 3.20", | ||
self | ||
) < 0) { | ||
return -1; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (PyErr_WarnFormat( | |
PyExc_DeprecationWarning, 1, | |
"Instantiating abstract AST node class %T is deprecated. " | |
"This will become an error in Python 3.20", | |
self | |
) < 0) { | |
return -1; | |
} | |
if (PyErr_WarnFormat( | |
PyExc_DeprecationWarning, 1, | |
"Instantiating abstract AST node class %T is deprecated. " | |
"This will become an error in Python 3.20", self) < 0) | |
{ | |
return -1; | |
} |
for PEP-8
@@ -1443,6 +1458,23 @@ def visitModule(self, mod): | |||
return result; | |||
} | |||
/* Helper for checking if a node class is abstract in the tests. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should use clinic here, but this would create a new file as we don't use it at all. Maybe we can do it in a follow-up PR though.
@@ -1454,6 +1486,7 @@ def visitModule(self, mod): | |||
PyDoc_STR("__replace__($self, /, **fields)\\n--\\n\\n" | |||
"Return a copy of the AST node with new values " | |||
"for the specified fields.")}, | |||
{"_is_abstract", _PyCFunction_CAST(ast_is_abstract), METH_CLASS | METH_NOARGS, NULL}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change the signature of the function from PyObject (*)(PyObject *, void *)
to PyObject (*)(PyObject *, PyObject *)
and remove the cast. The second argument is always NULL so you can name it dummy
.
@@ -109,15 +111,15 @@ def cleanup(): | |||
|
|||
msg = "type object 'ast.AST' has no attribute '_fields'" | |||
# Both examples used to crash: | |||
with self.assertRaisesRegex(AttributeError, msg): | |||
with self.assertRaisesRegex(AttributeError, msg), self.assertWarns(DeprecationWarning): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
with self.assertRaisesRegex(AttributeError, msg), self.assertWarns(DeprecationWarning): | |
with ( | |
self.assertRaisesRegex(AttributeError, msg), | |
self.assertWarns(DeprecationWarning), | |
): |
and ditto below, just to warp at 80 chars.
if typ._is_abstract(): | ||
# Use an arbitrary concrete subclass | ||
concrete = next(sub for sub in typ.__subclasses__() if not sub._is_abstract()) | ||
kwargs[name] = self._construct_ast_class(concrete) | ||
else: | ||
kwargs[name] = self._construct_ast_class(typ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe ensure that "concrete" exists? also you could do concrete = typ
and remove the else:
and only call self._construct_ast_class
once.
@@ -573,14 +580,20 @@ def test_nodeclasses(self): | |||
x = ast.BinOp(1, 2, 3, foobarbaz=42) | |||
self.assertEqual(x.foobarbaz, 42) | |||
|
|||
# Directly instantiating abstract node types is allowed (but deprecated) | |||
with self.assertWarns(DeprecationWarning): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless we need a variable, and unless we need to fit on 80 chars, I would suggest using self.assertWarns(DeprecationWarning, ast.stmt)
. In 3.20, we can just do self.assertRaises(..., ast.stmt)
. This reduces the overall file length without necessarily reducing readability IMO.
m = ast.Module([ast.Expr(ast.expr(**pos), **pos)], []) | ||
with self.assertWarns(DeprecationWarning): | ||
# Creating instances of ast.expr is deprecated | ||
m = ast.Module([ast.Expr(ast.expr(**pos), **pos)], []) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just wrap ast.expr()
as being the deprecated one? just to be sure we're not catching another deprecation warning
@@ -1140,6 +1153,9 @@ def do(cls): | |||
return | |||
if cls is ast.Index: | |||
return | |||
# Don't attempt to create instances of abstract AST nodes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can also update the docstring saying that we also skip abstract classes.
Demonstration:
This tracks which AST nodes are abstract using an internal set that gets built during node type initialization. I tried a few different approaches, and this seemed the simplest. A nice thing about this approach is that the check for abstract-ness depends on the exact node type, so this avoids most inheritance subtleties that could interfere with existing user-defined subclasses.
📚 Documentation preview 📚: https://cpython-previews--137865.org.readthedocs.build/