Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Doc/library/ast.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -2205,10 +2205,10 @@ Async and await
Apart from the node classes, the :mod:`ast` module defines these utility functions
and classes for traversing abstract syntax trees:

.. function:: parse(source, filename='<unknown>', mode='exec', *, type_comments=False, feature_version=None, optimize=-1)
.. function:: parse(source, filename='<unknown>', mode='exec', *, type_comments=False, feature_version=None, optimize=-1, module=None)

Parse the source into an AST node. Equivalent to ``compile(source,
filename, mode, flags=FLAGS_VALUE, optimize=optimize)``,
filename, mode, flags=FLAGS_VALUE, optimize=optimize, module=module)``,
where ``FLAGS_VALUE`` is ``ast.PyCF_ONLY_AST`` if ``optimize <= 0``
and ``ast.PyCF_OPTIMIZED_AST`` otherwise.

Expand DownExpand Up@@ -2261,6 +2261,9 @@ and classes for traversing abstract syntax trees:
The minimum supported version for ``feature_version`` is now ``(3, 7)``.
The ``optimize`` argument was added.

.. versionadded:: next
Added the *module* parameter.


.. function:: unparse(ast_obj)

Expand Down
11 changes: 10 additions & 1 deletion Doc/library/functions.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -292,7 +292,9 @@ are always available. They are listed here in alphabetical order.
:func:`property`.


.. function:: compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)
.. function:: compile(source, filename, mode, flags=0, \
dont_inherit=False, optimize=-1, \
*, module=None)

Compile the *source* into a code or AST object. Code objects can be executed
by :func:`exec` or :func:`eval`. *source* can either be a normal string, a
Expand DownExpand Up@@ -334,6 +336,10 @@ are always available. They are listed here in alphabetical order.
``__debug__`` is true), ``1`` (asserts are removed, ``__debug__`` is false)
or ``2`` (docstrings are removed too).

The optional argument *module* specifies the module name.
It is needed to unambiguous :ref:`filter <warning-filter>` syntax warnings
by module name.

This function raises :exc:`SyntaxError` if the compiled source is invalid,
and :exc:`ValueError` if the source contains null bytes.

Expand DownExpand Up@@ -371,6 +377,9 @@ are always available. They are listed here in alphabetical order.
``ast.PyCF_ALLOW_TOP_LEVEL_AWAIT`` can now be passed in flags to enable
support for top-level ``await``, ``async for``, and ``async with``.

.. versionadded:: next
Added the *module* parameter.


.. class:: complex(number=0, /)
complex(string, /)
Expand Down
10 changes: 9 additions & 1 deletion Doc/library/importlib.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -459,7 +459,7 @@ ABC hierarchy::
.. versionchanged:: 3.4
Raises :exc:`ImportError` instead of :exc:`NotImplementedError`.

.. staticmethod:: source_to_code(data, path='<string>')
.. staticmethod:: source_to_code(data, path='<string>', fullname=None)

Create a code object from Python source.

Expand All@@ -471,11 +471,19 @@ ABC hierarchy::
With the subsequent code object one can execute it in a module by
running ``exec(code, module.__dict__)``.

The optional argument *fullname* specifies the module name.
It is needed to unambiguous :ref:`filter <warning-filter>` syntax
warnings by module name.

.. versionadded:: 3.4

.. versionchanged:: 3.5
Made the method static.

.. versionadded:: next
Added the *fullname* parameter.


.. method:: exec_module(module)

Implementation of :meth:`Loader.exec_module`.
Expand Down
8 changes: 7 additions & 1 deletion Doc/library/symtable.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,11 +21,17 @@ tables.
Generating Symbol Tables
------------------------

.. function:: symtable(code, filename, compile_type)
.. function:: symtable(code, filename, compile_type, *, module=None)

Return the toplevel :class:`SymbolTable` for the Python source *code*.
*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.
It is needed to unambiguous :ref:`filter <warning-filter>` syntax warnings
by module name.

.. versionadded:: next
Added the *module* parameter.


Examining Symbol Tables
Expand Down
7 changes: 7 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -307,6 +307,13 @@ Other language changes
not only integers or floats, although this does not improve precision.
(Contributed by Serhiy Storchaka in :gh:`67795`.)

* Many functions related to compiling or parsing Python code, such as
:func:`compile`, :func:`ast.parse`, :func:`symtable.symtable`,
and :func:`importlib.abc.InspectLoader.source_to_code`, now allow to pass
the module name. It is needed to unambiguous :ref:`filter <warning-filter>`
syntax warnings by module name.
(Contributed by Serhiy Storchaka in :gh:`135801`.)


New modules
===========
Expand Down
9 changes: 6 additions & 3 deletions Include/internal/pycore_compile.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,7 +32,8 @@ PyAPI_FUNC(PyCodeObject*) _PyAST_Compile(
PyObject *filename,
PyCompilerFlags *flags,
int optimize,
struct _arena *arena);
struct _arena *arena,
PyObject *module);

/* AST preprocessing */
extern int _PyCompile_AstPreprocess(
Expand All@@ -41,7 +42,8 @@ extern int _PyCompile_AstPreprocess(
PyCompilerFlags *flags,
int optimize,
struct _arena *arena,
int syntax_check_only);
int syntax_check_only,
PyObject *module);

extern int _PyAST_Preprocess(
struct _mod *,
Expand All@@ -50,7 +52,8 @@ extern int _PyAST_Preprocess(
int optimize,
int ff_features,
int syntax_check_only,
int enable_warnings);
int enable_warnings,
PyObject *module);


typedef struct{
Expand Down
3 changes: 2 additions & 1 deletion Include/internal/pycore_parser.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,7 +48,8 @@ extern struct _mod* _PyParser_ASTFromString(
PyObject* filename,
int mode,
PyCompilerFlags *flags,
PyArena *arena);
PyArena *arena,
PyObject *module);

extern struct _mod* _PyParser_ASTFromFile(
FILE *fp,
Expand Down
3 changes: 2 additions & 1 deletion Include/internal/pycore_pyerrors.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -123,7 +123,8 @@ extern void _PyErr_SetNone(PyThreadState *tstate, PyObject *exception);
extern PyObject* _PyErr_NoMemory(PyThreadState *tstate);

extern int _PyErr_EmitSyntaxWarning(PyObject *msg, PyObject *filename, int lineno, int col_offset,
int end_lineno, int end_col_offset);
int end_lineno, int end_col_offset,
PyObject *module);
extern void _PyErr_RaiseSyntaxError(PyObject *msg, PyObject *filename, int lineno, int col_offset,
int end_lineno, int end_col_offset);

Expand Down
6 changes: 6 additions & 0 deletions Include/internal/pycore_pythonrun.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,6 +33,12 @@ extern const char* _Py_SourceAsString(
PyCompilerFlags *cf,
PyObject **cmd_copy);

extern PyObject * _Py_CompileStringObjectWithModule(
const char *str,
PyObject *filename, int start,
PyCompilerFlags *flags, int optimize,
PyObject *module);


/* Stack size, in "pointers". This must be large enough, so
* no two calls to check recursion depth are more than this far
Expand Down
3 changes: 2 additions & 1 deletion Include/internal/pycore_symtable.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -188,7 +188,8 @@ extern struct symtable* _Py_SymtableStringObjectFlags(
const char *str,
PyObject *filename,
int start,
PyCompilerFlags *flags);
PyCompilerFlags *flags,
PyObject *module);

int _PyFuture_FromAST(
struct _mod * mod,
Expand Down
5 changes: 3 additions & 2 deletions Lib/ast.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,7 +24,7 @@


def parse(source, filename='<unknown>', mode='exec', *,
type_comments=False, feature_version=None, optimize=-1):
type_comments=False, feature_version=None, optimize=-1, module=None):
"""
Parse the source into an AST node.
Equivalent to compile(source, filename, mode, PyCF_ONLY_AST).
Expand All@@ -44,7 +44,8 @@ def parse(source, filename='<unknown>', mode='exec', *,
feature_version = minor
# Else it should be an int giving the minor version for 3.x.
return compile(source, filename, mode, flags,
_feature_version=feature_version, optimize=optimize)
_feature_version=feature_version, optimize=optimize,
module=module)


def literal_eval(node_or_string):
Expand Down
9 changes: 5 additions & 4 deletions Lib/importlib/_bootstrap_external.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -819,13 +819,14 @@ def get_source(self, fullname):
name=fullname) from exc
return decode_source(source_bytes)

def source_to_code(self, data, path, *, _optimize=-1):
def source_to_code(self, data, path, fullname=None, *, _optimize=-1):
"""Return the code object compiled from source.

The 'data' argument can be any object type that compile() supports.
"""
return _bootstrap._call_with_frames_removed(compile, data, path, 'exec',
dont_inherit=True, optimize=_optimize)
dont_inherit=True, optimize=_optimize,
module=fullname)

def get_code(self, fullname):
"""Concrete implementation of InspectLoader.get_code.
Expand DownExpand Up@@ -894,7 +895,7 @@ def get_code(self, fullname):
source_path=source_path)
if source_bytes is None:
source_bytes = self.get_data(source_path)
code_object = self.source_to_code(source_bytes, source_path)
code_object = self.source_to_code(source_bytes, source_path, fullname)
_bootstrap._verbose_message('code object from{}', source_path)
if (not sys.dont_write_bytecode and bytecode_path is not None and
source_mtime is not None):
Expand DownExpand Up@@ -1186,7 +1187,7 @@ def get_source(self, fullname):
return ''

def get_code(self, fullname):
return compile('', '<string>', 'exec', dont_inherit=True)
return compile('', '<string>', 'exec', dont_inherit=True, module=fullname)

def create_module(self, spec):
"""Use default semantics for module creation."""
Expand Down
11 changes: 5 additions & 6 deletions Lib/importlib/abc.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,7 +108,7 @@ def get_code(self, fullname):
source = self.get_source(fullname)
if source is None:
return None
return self.source_to_code(source)
return self.source_to_code(source, '<string>', fullname)

@abc.abstractmethod
def get_source(self, fullname):
Expand All@@ -120,12 +120,12 @@ def get_source(self, fullname):
raise ImportError

@staticmethod
def source_to_code(data, path='<string>'):
def source_to_code(data, path='<string>', fullname=None):
"""Compile 'data' into a code object.

The 'data' argument can be anything that compile() can handle. The'path'
argument should be where the data was retrieved (when applicable)."""
return compile(data, path, 'exec', dont_inherit=True)
return compile(data, path, 'exec', dont_inherit=True, module=fullname)

exec_module = _bootstrap_external._LoaderBasics.exec_module
load_module = _bootstrap_external._LoaderBasics.load_module
Expand DownExpand Up@@ -163,9 +163,8 @@ def get_code(self, fullname):
try:
path = self.get_filename(fullname)
except ImportError:
return self.source_to_code(source)
else:
return self.source_to_code(source, path)
path = '<string>'
return self.source_to_code(source, path, fullname)

_register(
ExecutionLoader,
Expand Down
2 changes: 1 addition & 1 deletion Lib/modulefinder.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -334,7 +334,7 @@ def load_module(self, fqname, fp, pathname, file_info):
self.msgout(2, "load_module ->", m)
return m
if type == _PY_SOURCE:
co = compile(fp.read(), pathname, 'exec')
co = compile(fp.read(), pathname, 'exec', module=fqname)
elif type == _PY_COMPILED:
try:
data = fp.read()
Expand Down
2 changes: 1 addition & 1 deletion Lib/profiling/sampling/_sync_coordinator.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -182,7 +182,7 @@ def _execute_script(script_path: str, script_args: List[str], cwd: str) -> None:

try:
# Compile and execute the script
code = compile(source_code, script_path, 'exec')
code = compile(source_code, script_path, 'exec', module='__main__')
exec(code,{'__name__': '__main__', '__file__': script_path})
except SyntaxError as e:
raise TargetError(f"Syntax error in script{script_path}:{e}") from e
Expand Down
2 changes: 1 addition & 1 deletion Lib/profiling/tracing/__init__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -185,7 +185,7 @@ def main():
progname = args[0]
sys.path.insert(0, os.path.dirname(progname))
with io.open_code(progname) as fp:
code = compile(fp.read(), progname, 'exec')
code = compile(fp.read(), progname, 'exec', module='__main__')
spec = importlib.machinery.ModuleSpec(name='__main__', loader=None,
origin=progname)
module = importlib.util.module_from_spec(spec)
Expand Down
6 changes: 3 additions & 3 deletions Lib/runpy.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -247,7 +247,7 @@ def _get_main_module_details(error=ImportError):
sys.modules[main_name] = saved_main


def _get_code_from_file(fname):
def _get_code_from_file(fname, module):
# Check for a compiled file first
from pkgutil import read_code
code_path = os.path.abspath(fname)
Expand All@@ -256,7 +256,7 @@ def _get_code_from_file(fname):
if code is None:
# That didn't work, so try it as normal source code
with io.open_code(code_path) as f:
code = compile(f.read(), fname, 'exec')
code = compile(f.read(), fname, 'exec', module=module)
return code

def run_path(path_name, init_globals=None, run_name=None):
Expand All@@ -283,7 +283,7 @@ def run_path(path_name, init_globals=None, run_name=None):
if isinstance(importer, type(None)):
# Not a valid sys.path entry, so run the code directly
# execfile() doesn't help as we want to allow compiled files
code = _get_code_from_file(path_name)
code = _get_code_from_file(path_name, run_name)
return _run_module_code(code, init_globals, run_name,
pkg_name=pkg_name, script_name=path_name)
else:
Expand Down
4 changes: 2 additions & 2 deletions Lib/symtable.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,13 +17,13 @@

__all__ = ["symtable", "SymbolTableType", "SymbolTable", "Class", "Function", "Symbol"]

def symtable(code, filename, compile_type):
def symtable(code, filename, compile_type, *, module=None):
""" Return the toplevel *SymbolTable* for the source code.

*filename* is the name of the file with the code
and *compile_type* is the *compile()* mode argument.
"""
top = _symtable.symtable(code, filename, compile_type)
top = _symtable.symtable(code, filename, compile_type, module=module)
return _newSymbolTable(top, filename)

class SymbolTableFactory:
Expand Down
10 changes: 10 additions & 0 deletions Lib/test/test_ast/test_ast.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -1083,6 +1083,16 @@ def test_filter_syntax_warnings_by_module(self):
self.assertEqual(wm.filename, '<unknown>')
self.assertIs(wm.category, SyntaxWarning)

with warnings.catch_warnings(record=True) as wlog:
warnings.simplefilter('error')
warnings.filterwarnings('always', module=r'package\.module\z')
warnings.filterwarnings('error', module=r'<unknown>')
ast.parse(source, filename, module='package.module')
self.assertEqual(sorted(wm.lineno for wm in wlog), [4, 7, 10])
for wm in wlog:
self.assertEqual(wm.filename, filename)
self.assertIs(wm.category, SyntaxWarning)


class CopyTests(unittest.TestCase):
"""Test copying and pickling AST nodes."""
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_builtin.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -1103,7 +1103,8 @@ def test_exec_filter_syntax_warnings_by_module(self):

with warnings.catch_warnings(record=True) as wlog:
warnings.simplefilter('error')
warnings.filterwarnings('always', module=r'<string>\z')
warnings.filterwarnings('always', module=r'package.module\z')
warnings.filterwarnings('error', module=r'<string>')
exec(source,{'__name__': 'package.module', '__file__': filename})
self.assertEqual(sorted(wm.lineno for wm in wlog), [4, 7, 10, 13, 14, 21])
for wm in wlog:
Expand Down
Loading
Loading