Skip to content

Commit e550c2a

Browse files
Convert _is_abstract to module function, address misc review comments
Co-authored-by: Bénédikt Tran <[email protected]>
1 parent c8dda97 commit e550c2a

File tree

3 files changed

+83
-60
lines changed

3 files changed

+83
-60
lines changed

Lib/test/test_ast/test_ast.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import _ast_unparse
2+
import _ast
23
import ast
34
import builtins
45
import contextlib
@@ -111,9 +112,15 @@ def cleanup():
111112

112113
msg = "type object 'ast.AST' has no attribute '_fields'"
113114
# Both examples used to crash:
114-
with self.assertRaisesRegex(AttributeError, msg), self.assertWarns(DeprecationWarning):
115+
with (
116+
self.assertRaisesRegex(AttributeError, msg),
117+
self.assertWarns(DeprecationWarning),
118+
):
115119
ast.AST(arg1=123)
116-
with self.assertRaisesRegex(AttributeError, msg), self.assertWarns(DeprecationWarning):
120+
with (
121+
self.assertRaisesRegex(AttributeError, msg),
122+
self.assertWarns(DeprecationWarning),
123+
):
117124
ast.AST()
118125

119126
def test_node_garbage_collection(self):
@@ -429,12 +436,15 @@ def _construct_ast_class(self, cls):
429436
elif typ is object:
430437
kwargs[name] = b'capybara'
431438
elif isinstance(typ, type) and issubclass(typ, ast.AST):
432-
if typ._is_abstract():
439+
if _ast._is_abstract(typ):
433440
# Use an arbitrary concrete subclass
434-
concrete = next(sub for sub in typ.__subclasses__() if not sub._is_abstract())
435-
kwargs[name] = self._construct_ast_class(concrete)
441+
concrete = next((sub for sub in typ.__subclasses__()
442+
if not _ast._is_abstract(sub)), None)
443+
msg = f"abstract node class {typ} has no concrete subclasses"
444+
self.assertIsNotNone(concrete, msg)
436445
else:
437-
kwargs[name] = self._construct_ast_class(typ)
446+
concrete = typ
447+
kwargs[name] = self._construct_ast_class(concrete)
438448
return cls(**kwargs)
439449

440450
def test_arguments(self):
@@ -581,8 +591,8 @@ def test_nodeclasses(self):
581591
self.assertEqual(x.foobarbaz, 42)
582592

583593
# Directly instantiating abstract node types is allowed (but deprecated)
584-
with self.assertWarns(DeprecationWarning):
585-
ast.stmt()
594+
self.assertWarns(DeprecationWarning, ast.stmt)
595+
self.assertWarns(DeprecationWarning, ast.expr_context)
586596

587597
def test_no_fields(self):
588598
# this used to fail because Sub._fields was None
@@ -593,7 +603,8 @@ def test_invalid_sum(self):
593603
pos = dict(lineno=2, col_offset=3)
594604
with self.assertWarns(DeprecationWarning):
595605
# Creating instances of ast.expr is deprecated
596-
m = ast.Module([ast.Expr(ast.expr(**pos), **pos)], [])
606+
e = ast.expr(**pos)
607+
m = ast.Module([ast.Expr(e, **pos)], [])
597608
with self.assertRaises(TypeError) as cm:
598609
compile(m, "<test>", "exec")
599610
self.assertIn("but got expr()", str(cm.exception))
@@ -1145,16 +1156,18 @@ class CopyTests(unittest.TestCase):
11451156
def iter_ast_classes():
11461157
"""Iterate over the (native) subclasses of ast.AST recursively.
11471158
1148-
This excludes the special class ast.Index since its constructor
1149-
returns an integer.
1159+
This excludes:
1160+
* abstract AST nodes
1161+
* the special class ast.Index, since its constructor returns
1162+
an integer.
11501163
"""
11511164
def do(cls):
11521165
if cls.__module__ != 'ast':
11531166
return
11541167
if cls is ast.Index:
11551168
return
11561169
# Don't attempt to create instances of abstract AST nodes
1157-
if cls._is_abstract():
1170+
if _ast._is_abstract(cls):
11581171
return
11591172

11601173
yield cls

Parser/asdl_c.py

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -887,11 +887,10 @@ def visitModule(self, mod):
887887
}
888888
else if (contains == 1) {
889889
if (PyErr_WarnFormat(
890-
PyExc_DeprecationWarning, 1,
891-
"Instantiating abstract AST node class %T is deprecated. "
892-
"This will become an error in Python 3.20",
893-
self
894-
) < 0) {
890+
PyExc_DeprecationWarning, 1,
891+
"Instantiating abstract AST node class %T is deprecated. "
892+
"This will become an error in Python 3.20", self) < 0)
893+
{
895894
return -1;
896895
}
897896
}
@@ -1458,23 +1457,6 @@ def visitModule(self, mod):
14581457
return result;
14591458
}
14601459
1461-
/* Helper for checking if a node class is abstract in the tests. */
1462-
static PyObject *
1463-
ast_is_abstract(PyObject *cls, void *Py_UNUSED(ignored)) {
1464-
struct ast_state *state = get_ast_state();
1465-
if (state == NULL) {
1466-
return NULL;
1467-
}
1468-
int contains = PySet_Contains(state->abstract_types, cls);
1469-
if (contains == -1) {
1470-
return NULL;
1471-
}
1472-
else if (contains == 1) {
1473-
Py_RETURN_TRUE;
1474-
}
1475-
Py_RETURN_FALSE;
1476-
}
1477-
14781460
static PyMemberDef ast_type_members[] = {
14791461
{"__dictoffset__", Py_T_PYSSIZET, offsetof(AST_object, dict), Py_READONLY},
14801462
{NULL} /* Sentinel */
@@ -1486,7 +1468,6 @@ def visitModule(self, mod):
14861468
PyDoc_STR("__replace__($self, /, **fields)\\n--\\n\\n"
14871469
"Return a copy of the AST node with new values "
14881470
"for the specified fields.")},
1489-
{"_is_abstract", _PyCFunction_CAST(ast_is_abstract), METH_CLASS | METH_NOARGS, NULL},
14901471
{NULL}
14911472
};
14921473
@@ -2001,6 +1982,30 @@ def emit_defaults(self, name, fields, depth):
20011982
class ASTModuleVisitor(PickleVisitor):
20021983

20031984
def visitModule(self, mod):
1985+
self.emit("""
1986+
/* Helper for checking if a node class is abstract in the tests. */
1987+
static PyObject *
1988+
ast_is_abstract(PyObject *Py_UNUSED(module), PyObject *cls) {
1989+
struct ast_state *state = get_ast_state();
1990+
if (state == NULL) {
1991+
return NULL;
1992+
}
1993+
int contains = PySet_Contains(state->abstract_types, cls);
1994+
if (contains == -1) {
1995+
return NULL;
1996+
}
1997+
else if (contains == 1) {
1998+
Py_RETURN_TRUE;
1999+
}
2000+
Py_RETURN_FALSE;
2001+
}
2002+
2003+
static struct PyMethodDef astmodule_methods[] = {
2004+
{"_is_abstract", ast_is_abstract, METH_O, NULL},
2005+
{NULL} /* Sentinel */
2006+
};
2007+
""".strip(), 0, reflow=False)
2008+
self.emit("", 0)
20042009
self.emit("static int", 0)
20052010
self.emit("astmodule_exec(PyObject *m)", 0)
20062011
self.emit("{", 0)
@@ -2041,7 +2046,8 @@ def visitModule(self, mod):
20412046
.m_name = "_ast",
20422047
// The _ast module uses a per-interpreter state (PyInterpreterState.ast)
20432048
.m_size = 0,
2044-
.m_slots = astmodule_slots,
2049+
.m_methods = astmodule_methods,
2050+
.m_slots = astmodule_slots
20452051
};
20462052
20472053
PyMODINIT_FUNC

Python/Python-ast.c

Lines changed: 28 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)