.. function:: symtable(code, filename, compile_type, *, module=None)
Return the toplevel :class:`SymbolTable` for the Python source *code*.
+ *code* can be a string, a bytes object, or an AST object,
+ as for the builtin :func:`compile`.
*filename* is the name of the file containing the code. *compile_type* is
like the *mode* argument to :func:`compile`.
The optional argument *module* specifies the module name.
.. versionadded:: 3.15
Added the *module* parameter.
+ .. versionchanged:: next
+ *code* can now be an AST object.
+
Examining Symbol Tables
-----------------------
(Contributed by Jay Berry in :gh:`148846`.)
+symtable
+--------
+
+* :func:`symtable.symtable` now accepts an AST object,
+ like the builtin :func:`compile`.
+ (Contributed by Serhiy Storchaka in :gh:`153844`.)
+
+
tkinter
-------
def symtable(code, filename, compile_type, *, module=None):
""" Return the toplevel *SymbolTable* for the source code.
+ *code* can be a string, a bytes object, or an AST object.
*filename* is the name of the file with the code
and *compile_type* is the *compile()* mode argument.
"""
Test the API of the symtable module.
"""
+import ast
import symtable
import warnings
import unittest
self.assertEqual(len([x for x in ids if x == 'x']), 1)
+class ASTInputTests(unittest.TestCase):
+ maxDiff = None
+
+ def dump(self, table):
+ return (table.get_name(), table.get_type(), table.get_lineno(),
+ [repr(symbol) for symbol in table.get_symbols()],
+ [self.dump(child) for child in table.get_children()])
+
+ def test_exec(self):
+ top = symtable.symtable(ast.parse(TEST_CODE), "?", "exec")
+ self.assertIsNotNone(find_block(top, "Mine"))
+
+ def test_eval(self):
+ table = symtable.symtable(ast.parse("a + b", mode="eval"), "?", "eval")
+ self.assertEqual(sorted(table.get_identifiers()), ["a", "b"])
+
+ def test_single(self):
+ table = symtable.symtable(ast.parse("x = 1", mode="single"),
+ "?", "single")
+ self.assertIn("x", table.get_identifiers())
+
+ def test_same_result_as_string(self):
+ cases = [
+ (TEST_CODE, "exec"),
+ ("from __future__ import annotations\n"
+ "def f(x: int) -> int: return x\n", "exec"),
+ ("[x*y for x in a]", "eval"),
+ ("def f(): pass\n", "single"),
+ ]
+ for source, mode in cases:
+ with self.subTest(source=source, mode=mode):
+ from_str = symtable.symtable(source, "?", mode)
+ from_ast = symtable.symtable(ast.parse(source, mode=mode),
+ "?", mode)
+ self.assertEqual(self.dump(from_ast), self.dump(from_str))
+
+ def test_synthesized_ast(self):
+ # An AST created programmatically, without any source.
+ node = ast.Module(body=[
+ ast.FunctionDef(
+ name="f",
+ args=ast.arguments(args=[ast.arg(arg="x")]),
+ body=[ast.Return(ast.Name("x", ast.Load()))])])
+ ast.fix_missing_locations(node)
+ top = symtable.symtable(node, "?", "exec")
+ f = find_block(top, "f")
+ self.assertTrue(f.lookup("x").is_parameter())
+
+ def test_mode_mismatch(self):
+ tree = ast.parse("x = 1")
+ for mode in ("eval", "single"):
+ with self.subTest(mode=mode):
+ with self.assertRaises(TypeError):
+ symtable.symtable(tree, "?", mode)
+ with self.assertRaises(TypeError):
+ symtable.symtable(ast.parse("x", mode="eval"), "?", "exec")
+
+ def test_invalid_ast(self):
+ node = ast.Expression(ast.Name("x", ast.Store()))
+ ast.fix_missing_locations(node)
+ with self.assertRaises(ValueError):
+ symtable.symtable(node, "?", "eval")
+
+ def test_misplaced_future_import(self):
+ # The parser does not enforce the placement of future imports in
+ # an existing AST; the symbol table construction does.
+ tree = ast.parse("x = 1\nfrom __future__ import annotations\n")
+ with self.assertRaises(SyntaxError):
+ symtable.symtable(tree, "?", "exec")
+
+
class CommandLineTest(unittest.TestCase):
maxDiff = None
--- /dev/null
+:func:`symtable.symtable` now accepts an AST object,
+like the builtin :func:`compile`.
"symtable($module, source, filename, startstr, /, *, module=None)\n"
"--\n"
"\n"
-"Return symbol and scope dictionaries used internally by compiler.");
+"Return symbol and scope dictionaries used internally by compiler.\n"
+"\n"
+"The source can be a string, a bytes object, or an AST object.");
#define _SYMTABLE_SYMTABLE_METHODDEF \
{"symtable", _PyCFunction_CAST(_symtable_symtable), METH_FASTCALL|METH_KEYWORDS, _symtable_symtable__doc__},
return return_value;
}
-/*[clinic end generated code: output=0137be60c487c841 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=23523cada784726e input=a9049054013a1b77]*/
#include "Python.h"
+#include "pycore_ast.h" // PyAST_Check()
+#include "pycore_pyarena.h" // _PyArena_New()
#include "pycore_pythonrun.h" // _Py_SourceAsString()
#include "pycore_symtable.h" // struct symtable
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=f4685845a7100605]*/
+static struct symtable *
+symtable_from_ast(PyObject *source, PyObject *filename, int compile_mode)
+{
+ PyArena *arena = _PyArena_New();
+ if (arena == NULL) {
+ return NULL;
+ }
+ struct symtable *st = NULL;
+ mod_ty mod = PyAST_obj2mod(source, arena, compile_mode);
+ if (mod == NULL || !_PyAST_Validate(mod)) {
+ goto finally;
+ }
+ _PyFutureFeatures future;
+ if (!_PyFuture_FromAST(mod, filename, &future)) {
+ goto finally;
+ }
+ st = _PySymtable_Build(mod, filename, &future);
+finally:
+ _PyArena_Free(arena);
+ return st;
+}
+
+
/*[clinic input]
_symtable.symtable
module as modname: object = None
Return symbol and scope dictionaries used internally by compiler.
+
+The source can be a string, a bytes object, or an AST object.
[clinic start generated code]*/
static PyObject *
_symtable_symtable_impl(PyObject *module, PyObject *source,
PyObject *filename, const char *startstr,
PyObject *modname)
-/*[clinic end generated code: output=235ec5a87a9ce178 input=fbf9adaa33c7070d]*/
+/*[clinic end generated code: output=235ec5a87a9ce178 input=6cadac0485f576a7]*/
{
struct symtable *st;
PyObject *t;
- int start;
- PyCompilerFlags cf = _PyCompilerFlags_INIT;
- PyObject *source_copy = NULL;
-
- cf.cf_flags = PyCF_SOURCE_IS_UTF8;
-
- const char *str = _Py_SourceAsString(source, "symtable", "string or bytes", &cf, &source_copy);
- if (str == NULL) {
- return NULL;
- }
+ int compile_mode;
if (strcmp(startstr, "exec") == 0)
- start = Py_file_input;
+ compile_mode = 0;
else if (strcmp(startstr, "eval") == 0)
- start = Py_eval_input;
+ compile_mode = 1;
else if (strcmp(startstr, "single") == 0)
- start = Py_single_input;
+ compile_mode = 2;
else {
PyErr_SetString(PyExc_ValueError,
"symtable() arg 3 must be 'exec' or 'eval' or 'single'");
- Py_XDECREF(source_copy);
return NULL;
}
if (modname == Py_None) {
PyErr_Format(PyExc_TypeError,
"symtable() argument 'module' must be str or None, not %T",
modname);
- Py_XDECREF(source_copy);
return NULL;
}
- st = _Py_SymtableStringObjectFlags(str, filename, start, &cf, modname);
- Py_XDECREF(source_copy);
+
+ if (PyAST_Check(source)) {
+ st = symtable_from_ast(source, filename, compile_mode);
+ }
+ else {
+ static const int starts[] = {
+ Py_file_input, Py_eval_input, Py_single_input};
+ PyCompilerFlags cf = _PyCompilerFlags_INIT;
+ PyObject *source_copy = NULL;
+
+ cf.cf_flags = PyCF_SOURCE_IS_UTF8;
+ const char *str = _Py_SourceAsString(source, "symtable",
+ "string, bytes or AST",
+ &cf, &source_copy);
+ if (str == NULL) {
+ return NULL;
+ }
+ st = _Py_SymtableStringObjectFlags(str, filename,
+ starts[compile_mode], &cf,
+ modname);
+ Py_XDECREF(source_copy);
+ }
if (st == NULL) {
return NULL;
}