fixed subscription table
This commit is contained in:
28
.venv/lib/python3.12/site-packages/executing/__init__.py
Normal file
28
.venv/lib/python3.12/site-packages/executing/__init__.py
Normal file
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
Get information about what a frame is currently doing. Typical usage:
|
||||
|
||||
import executing
|
||||
|
||||
node = executing.Source.executing(frame).node
|
||||
# node will be an AST node or None
|
||||
"""
|
||||
|
||||
from collections import namedtuple
|
||||
_VersionInfo = namedtuple('_VersionInfo', ('major', 'minor', 'micro'))
|
||||
from .executing import Source, Executing, only, NotOneValueFound, cache, future_flags
|
||||
|
||||
from ._pytest_utils import is_pytest_compatible
|
||||
|
||||
try:
|
||||
from .version import __version__ # type: ignore[import]
|
||||
if "dev" in __version__:
|
||||
raise ValueError
|
||||
except Exception:
|
||||
# version.py is auto-generated with the git tag when building
|
||||
__version__ = "???"
|
||||
__version_info__ = _VersionInfo(-1, -1, -1)
|
||||
else:
|
||||
__version_info__ = _VersionInfo(*map(int, __version__.split('.')))
|
||||
|
||||
|
||||
__all__ = ["Source","is_pytest_compatible"]
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
22
.venv/lib/python3.12/site-packages/executing/_exceptions.py
Normal file
22
.venv/lib/python3.12/site-packages/executing/_exceptions.py
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
class KnownIssue(Exception):
|
||||
"""
|
||||
Raised in case of an known problem. Mostly because of cpython bugs.
|
||||
Executing.node gets set to None in this case.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class VerifierFailure(Exception):
|
||||
"""
|
||||
Thrown for an unexpected mapping from instruction to ast node
|
||||
Executing.node gets set to None in this case.
|
||||
"""
|
||||
|
||||
def __init__(self, title, node, instruction):
|
||||
# type: (object, object, object) -> None
|
||||
self.node = node
|
||||
self.instruction = instruction
|
||||
|
||||
super().__init__(title) # type: ignore[call-arg]
|
@@ -0,0 +1,992 @@
|
||||
import ast
|
||||
import sys
|
||||
import dis
|
||||
from types import CodeType, FrameType
|
||||
from typing import Any, Callable, Iterator, Optional, Sequence, Set, Tuple, Type, Union, cast
|
||||
from .executing import EnhancedAST, NotOneValueFound, Source, only, function_node_types, assert_
|
||||
from ._exceptions import KnownIssue, VerifierFailure
|
||||
|
||||
from functools import lru_cache
|
||||
|
||||
# the code in this module can use all python>=3.11 features
|
||||
|
||||
|
||||
def parents(node: EnhancedAST) -> Iterator[EnhancedAST]:
|
||||
while True:
|
||||
if hasattr(node, "parent"):
|
||||
node = node.parent
|
||||
yield node
|
||||
else:
|
||||
break # pragma: no mutate
|
||||
|
||||
|
||||
def node_and_parents(node: EnhancedAST) -> Iterator[EnhancedAST]:
|
||||
yield node
|
||||
yield from parents(node)
|
||||
|
||||
|
||||
def mangled_name(node: EnhancedAST) -> str:
|
||||
"""
|
||||
|
||||
Parameters:
|
||||
node: the node which should be mangled
|
||||
name: the name of the node
|
||||
|
||||
Returns:
|
||||
The mangled name of `node`
|
||||
"""
|
||||
if isinstance(node, ast.Attribute):
|
||||
name = node.attr
|
||||
elif isinstance(node, ast.Name):
|
||||
name = node.id
|
||||
elif isinstance(node, (ast.alias)):
|
||||
name = node.asname or node.name.split(".")[0]
|
||||
elif isinstance(node, (ast.FunctionDef, ast.ClassDef, ast.AsyncFunctionDef)):
|
||||
name = node.name
|
||||
elif isinstance(node, ast.ExceptHandler):
|
||||
assert node.name
|
||||
name = node.name
|
||||
elif sys.version_info >= (3,12) and isinstance(node,ast.TypeVar):
|
||||
name=node.name
|
||||
else:
|
||||
raise TypeError("no node to mangle for type "+repr(type(node)))
|
||||
|
||||
if name.startswith("__") and not name.endswith("__"):
|
||||
|
||||
parent,child=node.parent,node
|
||||
|
||||
while not (isinstance(parent,ast.ClassDef) and child not in parent.bases):
|
||||
if not hasattr(parent,"parent"):
|
||||
break # pragma: no mutate
|
||||
|
||||
parent,child=parent.parent,parent
|
||||
else:
|
||||
class_name=parent.name.lstrip("_")
|
||||
if class_name!="":
|
||||
return "_" + class_name + name
|
||||
|
||||
|
||||
|
||||
return name
|
||||
|
||||
|
||||
@lru_cache(128) # pragma: no mutate
|
||||
def get_instructions(code: CodeType) -> list[dis.Instruction]:
|
||||
return list(dis.get_instructions(code))
|
||||
|
||||
|
||||
types_cmp_issue_fix = (
|
||||
ast.IfExp,
|
||||
ast.If,
|
||||
ast.Assert,
|
||||
ast.While,
|
||||
)
|
||||
|
||||
types_cmp_issue = types_cmp_issue_fix + (
|
||||
ast.ListComp,
|
||||
ast.SetComp,
|
||||
ast.DictComp,
|
||||
ast.GeneratorExp,
|
||||
)
|
||||
|
||||
op_type_map = {
|
||||
"**": ast.Pow,
|
||||
"*": ast.Mult,
|
||||
"@": ast.MatMult,
|
||||
"//": ast.FloorDiv,
|
||||
"/": ast.Div,
|
||||
"%": ast.Mod,
|
||||
"+": ast.Add,
|
||||
"-": ast.Sub,
|
||||
"<<": ast.LShift,
|
||||
">>": ast.RShift,
|
||||
"&": ast.BitAnd,
|
||||
"^": ast.BitXor,
|
||||
"|": ast.BitOr,
|
||||
}
|
||||
|
||||
|
||||
class PositionNodeFinder(object):
|
||||
"""
|
||||
Mapping bytecode to ast-node based on the source positions, which where introduced in pyhon 3.11.
|
||||
In general every ast-node can be exactly referenced by its begin/end line/col_offset, which is stored in the bytecode.
|
||||
There are only some exceptions for methods and attributes.
|
||||
"""
|
||||
|
||||
def __init__(self, frame: FrameType, stmts: Set[EnhancedAST], tree: ast.Module, lasti: int, source: Source):
|
||||
self.bc_dict={bc.offset:bc for bc in get_instructions(frame.f_code) }
|
||||
|
||||
self.source = source
|
||||
self.decorator: Optional[EnhancedAST] = None
|
||||
|
||||
# work around for https://github.com/python/cpython/issues/96970
|
||||
while self.opname(lasti) == "CACHE":
|
||||
lasti -= 2
|
||||
|
||||
try:
|
||||
# try to map with all match_positions
|
||||
self.result = self.find_node(lasti)
|
||||
except NotOneValueFound:
|
||||
typ: tuple[Type]
|
||||
# LOAD_METHOD could load "".join for long "..."%(...) BinOps
|
||||
# this can only be associated by using all positions
|
||||
if self.opname(lasti) in (
|
||||
"LOAD_METHOD",
|
||||
"LOAD_ATTR",
|
||||
"STORE_ATTR",
|
||||
"DELETE_ATTR",
|
||||
):
|
||||
# lineno and col_offset of LOAD_METHOD and *_ATTR instructions get set to the beginning of
|
||||
# the attribute by the python compiler to improved error messages (PEP-657)
|
||||
# we ignore here the start position and try to find the ast-node just by end position and expected node type
|
||||
# This is save, because there can only be one attribute ending at a specific point in the source code.
|
||||
typ = (ast.Attribute,)
|
||||
elif self.opname(lasti) in ("CALL", "CALL_KW"):
|
||||
# A CALL instruction can be a method call, in which case the lineno and col_offset gets changed by the compiler.
|
||||
# Therefore we ignoring here this attributes and searchnig for a Call-node only by end_col_offset and end_lineno.
|
||||
# This is save, because there can only be one method ending at a specific point in the source code.
|
||||
# One closing ) only belongs to one method.
|
||||
typ = (ast.Call,)
|
||||
else:
|
||||
raise
|
||||
|
||||
self.result = self.find_node(
|
||||
lasti,
|
||||
match_positions=("end_col_offset", "end_lineno"),
|
||||
typ=typ,
|
||||
)
|
||||
|
||||
instruction = self.instruction(lasti)
|
||||
assert instruction is not None
|
||||
|
||||
self.result = self.fix_result(self.result, instruction)
|
||||
|
||||
self.known_issues(self.result, instruction)
|
||||
|
||||
self.test_for_decorator(self.result, lasti)
|
||||
|
||||
# verify
|
||||
if self.decorator is None:
|
||||
self.verify(self.result, instruction)
|
||||
else:
|
||||
assert_(self.decorator in self.result.decorator_list)
|
||||
|
||||
def test_for_decorator(self, node: EnhancedAST, index: int) -> None:
|
||||
if (
|
||||
isinstance(node.parent, (ast.ClassDef, function_node_types))
|
||||
and node in node.parent.decorator_list # type: ignore[attr-defined]
|
||||
):
|
||||
node_func = node.parent
|
||||
|
||||
while True:
|
||||
# the generated bytecode looks like follow:
|
||||
|
||||
# index opname
|
||||
# ------------------
|
||||
# index-4 PRECALL (only in 3.11)
|
||||
# index-2 CACHE
|
||||
# index CALL <- the call instruction
|
||||
# ... CACHE some CACHE instructions
|
||||
|
||||
# maybe multiple other bytecode blocks for other decorators
|
||||
# index-4 PRECALL (only in 3.11)
|
||||
# index-2 CACHE
|
||||
# index CALL <- index of the next loop
|
||||
# ... CACHE some CACHE instructions
|
||||
|
||||
# index+x STORE_* the ast-node of this instruction points to the decorated thing
|
||||
|
||||
if not (
|
||||
(self.opname(index - 4) == "PRECALL" or sys.version_info >= (3, 12))
|
||||
and self.opname(index) == "CALL"
|
||||
): # pragma: no mutate
|
||||
break # pragma: no mutate
|
||||
|
||||
index += 2
|
||||
|
||||
while self.opname(index) in ("CACHE", "EXTENDED_ARG"):
|
||||
index += 2
|
||||
|
||||
if (
|
||||
self.opname(index).startswith("STORE_")
|
||||
and self.find_node(index) == node_func
|
||||
):
|
||||
self.result = node_func
|
||||
self.decorator = node
|
||||
return
|
||||
|
||||
if sys.version_info < (3, 12):
|
||||
index += 4
|
||||
|
||||
def fix_result(
|
||||
self, node: EnhancedAST, instruction: dis.Instruction
|
||||
) -> EnhancedAST:
|
||||
if (
|
||||
sys.version_info >= (3, 12, 5)
|
||||
and instruction.opname in ("GET_ITER", "FOR_ITER")
|
||||
and isinstance(node.parent, ast.For)
|
||||
and node is node.parent.iter
|
||||
):
|
||||
# node positions have changed in 3.12.5
|
||||
# https://github.com/python/cpython/issues/93691
|
||||
# `for` calls __iter__ and __next__ during execution, the calling
|
||||
# expression of these calls was the ast.For node since cpython 3.11 (see test_iter).
|
||||
# cpython 3.12.5 changed this to the `iter` node of the loop, to make tracebacks easier to read.
|
||||
# This keeps backward compatibility with older executing versions.
|
||||
|
||||
# there are also cases like:
|
||||
#
|
||||
# for a in iter(l): pass
|
||||
#
|
||||
# where `iter(l)` would be otherwise the resulting node for the `iter()` call and the __iter__ call of the for implementation.
|
||||
# keeping the old behaviour makes it possible to distinguish both cases.
|
||||
|
||||
return node.parent
|
||||
|
||||
if (
|
||||
sys.version_info >= (3, 12, 6)
|
||||
and instruction.opname in ("GET_ITER", "FOR_ITER")
|
||||
and isinstance(
|
||||
node.parent.parent,
|
||||
(ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp),
|
||||
)
|
||||
and isinstance(node.parent,ast.comprehension)
|
||||
and node is node.parent.iter
|
||||
):
|
||||
# same as above but only for comprehensions, see:
|
||||
# https://github.com/python/cpython/issues/123142
|
||||
|
||||
return node.parent.parent
|
||||
|
||||
if sys.version_info >= (3, 12,6) and instruction.opname == "CALL":
|
||||
before = self.instruction_before(instruction)
|
||||
if (
|
||||
before is not None
|
||||
and before.opname == "LOAD_CONST"
|
||||
and before.positions == instruction.positions
|
||||
and isinstance(node.parent, ast.withitem)
|
||||
and node is node.parent.context_expr
|
||||
):
|
||||
# node positions for with-statements have change
|
||||
# and is now equal to the expression which created the context-manager
|
||||
# https://github.com/python/cpython/pull/120763
|
||||
|
||||
# with context_manager:
|
||||
# ...
|
||||
|
||||
# but there is one problem to distinguish call-expressions from __exit__()
|
||||
|
||||
# with context_manager():
|
||||
# ...
|
||||
|
||||
# the call for __exit__
|
||||
|
||||
# 20 1:5 1:22 LOAD_CONST(None)
|
||||
# 22 1:5 1:22 LOAD_CONST(None)
|
||||
# 24 1:5 1:22 LOAD_CONST(None)
|
||||
# 26 1:5 1:22 CALL() # <-- same source range as context_manager()
|
||||
|
||||
# but we can use the fact that the previous load for None
|
||||
# has the same source range as the call, wich can not happen for normal calls
|
||||
|
||||
# we return the same ast.With statement at the and to preserve backward compatibility
|
||||
|
||||
return node.parent.parent
|
||||
|
||||
if (
|
||||
sys.version_info >= (3, 12,6)
|
||||
and instruction.opname == "BEFORE_WITH"
|
||||
and isinstance(node.parent, ast.withitem)
|
||||
and node is node.parent.context_expr
|
||||
):
|
||||
# handle positions changes for __enter__
|
||||
return node.parent.parent
|
||||
|
||||
return node
|
||||
|
||||
def known_issues(self, node: EnhancedAST, instruction: dis.Instruction) -> None:
|
||||
if instruction.opname in ("COMPARE_OP", "IS_OP", "CONTAINS_OP") and isinstance(
|
||||
node, types_cmp_issue
|
||||
):
|
||||
if isinstance(node, types_cmp_issue_fix):
|
||||
# this is a workaround for https://github.com/python/cpython/issues/95921
|
||||
# we can fix cases with only on comparison inside the test condition
|
||||
#
|
||||
# we can not fix cases like:
|
||||
# if a<b<c and d<e<f: pass
|
||||
# if (a<b<c)!=d!=e: pass
|
||||
# because we don't know which comparison caused the problem
|
||||
|
||||
comparisons = [
|
||||
n
|
||||
for n in ast.walk(node.test) # type: ignore[attr-defined]
|
||||
if isinstance(n, ast.Compare) and len(n.ops) > 1
|
||||
]
|
||||
|
||||
assert_(comparisons, "expected at least one comparison")
|
||||
|
||||
if len(comparisons) == 1:
|
||||
node = self.result = cast(EnhancedAST, comparisons[0])
|
||||
else:
|
||||
raise KnownIssue(
|
||||
"multiple chain comparison inside %s can not be fixed" % (node)
|
||||
)
|
||||
|
||||
else:
|
||||
# Comprehension and generators get not fixed for now.
|
||||
raise KnownIssue("chain comparison inside %s can not be fixed" % (node))
|
||||
|
||||
if (
|
||||
sys.version_info[:3] == (3, 11, 1)
|
||||
and isinstance(node, ast.Compare)
|
||||
and instruction.opname == "CALL"
|
||||
and any(isinstance(n, ast.Assert) for n in node_and_parents(node))
|
||||
):
|
||||
raise KnownIssue(
|
||||
"known bug in 3.11.1 https://github.com/python/cpython/issues/95921"
|
||||
)
|
||||
|
||||
if isinstance(node, ast.Assert):
|
||||
# pytest assigns the position of the assertion to all expressions of the rewritten assertion.
|
||||
# All the rewritten expressions get mapped to ast.Assert, which is the wrong ast-node.
|
||||
# We don't report this wrong result.
|
||||
raise KnownIssue("assert")
|
||||
|
||||
if any(isinstance(n, ast.pattern) for n in node_and_parents(node)):
|
||||
# TODO: investigate
|
||||
raise KnownIssue("pattern matching ranges seems to be wrong")
|
||||
|
||||
if (
|
||||
sys.version_info >= (3, 12)
|
||||
and isinstance(node, ast.Call)
|
||||
and isinstance(node.func, ast.Name)
|
||||
and node.func.id == "super"
|
||||
):
|
||||
# super is optimized to some instructions which do not map nicely to a Call
|
||||
|
||||
# find the enclosing function
|
||||
func = node.parent
|
||||
while hasattr(func, "parent") and not isinstance(
|
||||
func, (ast.AsyncFunctionDef, ast.FunctionDef)
|
||||
):
|
||||
|
||||
func = func.parent
|
||||
|
||||
# get the first function argument (self/cls)
|
||||
first_arg = None
|
||||
|
||||
if hasattr(func, "args"):
|
||||
args = [*func.args.posonlyargs, *func.args.args]
|
||||
if args:
|
||||
first_arg = args[0].arg
|
||||
|
||||
if (instruction.opname, instruction.argval) in [
|
||||
("LOAD_DEREF", "__class__"),
|
||||
("LOAD_FAST", first_arg),
|
||||
("LOAD_DEREF", first_arg),
|
||||
]:
|
||||
raise KnownIssue("super optimization")
|
||||
|
||||
if self.is_except_cleanup(instruction, node):
|
||||
raise KnownIssue("exeption cleanup does not belong to the last node in a except block")
|
||||
|
||||
if instruction.opname == "STORE_NAME" and instruction.argval == "__classcell__":
|
||||
# handle stores to __classcell__ as KnownIssue,
|
||||
# because they get complicated if they are used in `if` or `for` loops
|
||||
# example:
|
||||
#
|
||||
# class X:
|
||||
# # ... something
|
||||
# if some_condition:
|
||||
# def method(self):
|
||||
# super()
|
||||
#
|
||||
# The `STORE_NAME` instruction gets mapped to the `ast.If` node,
|
||||
# because it is the last element in the class.
|
||||
# This last element could be anything and gets dificult to verify.
|
||||
|
||||
raise KnownIssue("store __classcell__")
|
||||
|
||||
if (
|
||||
instruction.opname == "CALL"
|
||||
and not isinstance(node,ast.Call)
|
||||
and any(isinstance(p, ast.Assert) for p in parents(node))
|
||||
and sys.version_info >= (3, 11, 2)
|
||||
):
|
||||
raise KnownIssue("exception generation maps to condition")
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
if instruction.opname in (
|
||||
"STORE_FAST_STORE_FAST",
|
||||
"STORE_FAST_LOAD_FAST",
|
||||
"LOAD_FAST_LOAD_FAST",
|
||||
):
|
||||
raise KnownIssue(f"can not map {instruction.opname} to two ast nodes")
|
||||
|
||||
if instruction.opname == "LOAD_FAST" and instruction.argval == "__class__":
|
||||
# example:
|
||||
# class T:
|
||||
# def a():
|
||||
# super()
|
||||
# some_node # <- there is a LOAD_FAST for this node because we use super()
|
||||
|
||||
raise KnownIssue(
|
||||
f"loading of __class__ is accociated with a random node at the end of a class if you use super()"
|
||||
)
|
||||
|
||||
if (
|
||||
instruction.opname == "COMPARE_OP"
|
||||
and isinstance(node, ast.UnaryOp)
|
||||
and isinstance(node.operand,ast.Compare)
|
||||
and isinstance(node.op, ast.Not)
|
||||
):
|
||||
# work around for
|
||||
# https://github.com/python/cpython/issues/114671
|
||||
self.result = node.operand
|
||||
|
||||
@staticmethod
|
||||
def is_except_cleanup(inst: dis.Instruction, node: EnhancedAST) -> bool:
|
||||
if inst.opname not in (
|
||||
"STORE_NAME",
|
||||
"STORE_FAST",
|
||||
"STORE_DEREF",
|
||||
"STORE_GLOBAL",
|
||||
"DELETE_NAME",
|
||||
"DELETE_FAST",
|
||||
"DELETE_DEREF",
|
||||
"DELETE_GLOBAL",
|
||||
):
|
||||
return False
|
||||
|
||||
# This bytecode does something exception cleanup related.
|
||||
# The position of the instruciton seems to be something in the last ast-node of the ExceptHandler
|
||||
# this could be a bug, but it might not be observable in normal python code.
|
||||
|
||||
# example:
|
||||
# except Exception as exc:
|
||||
# enum_member._value_ = value
|
||||
|
||||
# other example:
|
||||
# STORE_FAST of e was mapped to Constant(value=False)
|
||||
# except OSError as e:
|
||||
# if not _ignore_error(e):
|
||||
# raise
|
||||
# return False
|
||||
|
||||
# STORE_FAST of msg was mapped to print(...)
|
||||
# except TypeError as msg:
|
||||
# print("Sorry:", msg, file=file)
|
||||
|
||||
if (
|
||||
isinstance(node, ast.Name)
|
||||
and isinstance(node.ctx,ast.Store)
|
||||
and inst.opname.startswith("STORE_")
|
||||
and mangled_name(node) == inst.argval
|
||||
):
|
||||
# Storing the variable is valid and no exception cleanup, if the name is correct
|
||||
return False
|
||||
|
||||
if (
|
||||
isinstance(node, ast.Name)
|
||||
and isinstance(node.ctx,ast.Del)
|
||||
and inst.opname.startswith("DELETE_")
|
||||
and mangled_name(node) == inst.argval
|
||||
):
|
||||
# Deleting the variable is valid and no exception cleanup, if the name is correct
|
||||
return False
|
||||
|
||||
return any(
|
||||
isinstance(n, ast.ExceptHandler) and n.name and mangled_name(n) == inst.argval
|
||||
for n in parents(node)
|
||||
)
|
||||
|
||||
def verify(self, node: EnhancedAST, instruction: dis.Instruction) -> None:
|
||||
"""
|
||||
checks if this node could gererate this instruction
|
||||
"""
|
||||
|
||||
op_name = instruction.opname
|
||||
extra_filter: Callable[[EnhancedAST], bool] = lambda e: True
|
||||
ctx: Type = type(None)
|
||||
|
||||
def inst_match(opnames: Union[str, Sequence[str]], **kwargs: Any) -> bool:
|
||||
"""
|
||||
match instruction
|
||||
|
||||
Parameters:
|
||||
opnames: (str|Seq[str]): inst.opname has to be equal to or in `opname`
|
||||
**kwargs: every arg has to match inst.arg
|
||||
|
||||
Returns:
|
||||
True if all conditions match the instruction
|
||||
|
||||
"""
|
||||
|
||||
if isinstance(opnames, str):
|
||||
opnames = [opnames]
|
||||
return instruction.opname in opnames and kwargs == {
|
||||
k: getattr(instruction, k) for k in kwargs
|
||||
}
|
||||
|
||||
def node_match(node_type: Union[Type, Tuple[Type, ...]], **kwargs: Any) -> bool:
|
||||
"""
|
||||
match the ast-node
|
||||
|
||||
Parameters:
|
||||
node_type: type of the node
|
||||
**kwargs: every `arg` has to be equal `node.arg`
|
||||
or `node.arg` has to be an instance of `arg` if it is a type.
|
||||
"""
|
||||
return isinstance(node, node_type) and all(
|
||||
isinstance(getattr(node, k), v)
|
||||
if isinstance(v, type)
|
||||
else getattr(node, k) == v
|
||||
for k, v in kwargs.items()
|
||||
)
|
||||
|
||||
if op_name == "CACHE":
|
||||
return
|
||||
|
||||
if inst_match("CALL") and node_match((ast.With, ast.AsyncWith)):
|
||||
# call to context.__exit__
|
||||
return
|
||||
|
||||
if inst_match(("CALL", "LOAD_FAST")) and node_match(
|
||||
(ast.ListComp, ast.GeneratorExp, ast.SetComp, ast.DictComp)
|
||||
):
|
||||
# call to the generator function
|
||||
return
|
||||
|
||||
if (
|
||||
sys.version_info >= (3, 12)
|
||||
and inst_match(("LOAD_FAST_AND_CLEAR", "STORE_FAST"))
|
||||
and node_match((ast.ListComp, ast.SetComp, ast.DictComp))
|
||||
):
|
||||
return
|
||||
|
||||
if inst_match(("CALL", "CALL_FUNCTION_EX")) and node_match(
|
||||
(ast.ClassDef, ast.Call)
|
||||
):
|
||||
return
|
||||
|
||||
if inst_match(("COMPARE_OP", "IS_OP", "CONTAINS_OP")) and node_match(
|
||||
ast.Compare
|
||||
):
|
||||
return
|
||||
|
||||
if inst_match("LOAD_NAME", argval="__annotations__") and node_match(
|
||||
ast.AnnAssign
|
||||
):
|
||||
return
|
||||
|
||||
if (
|
||||
(
|
||||
inst_match("LOAD_METHOD", argval="join")
|
||||
or inst_match("LOAD_ATTR", argval="join") # 3.12
|
||||
or inst_match(("CALL", "BUILD_STRING"))
|
||||
)
|
||||
and node_match(ast.BinOp, left=ast.Constant, op=ast.Mod)
|
||||
and isinstance(cast(ast.Constant, cast(ast.BinOp, node).left).value, str)
|
||||
):
|
||||
# "..."%(...) uses "".join
|
||||
return
|
||||
|
||||
if inst_match("STORE_SUBSCR") and node_match(ast.AnnAssign):
|
||||
# data: int
|
||||
return
|
||||
|
||||
|
||||
if inst_match(("DELETE_NAME", "DELETE_FAST")) and node_match(
|
||||
ast.Name, id=instruction.argval, ctx=ast.Del
|
||||
):
|
||||
return
|
||||
|
||||
if inst_match("BUILD_STRING") and (
|
||||
node_match(ast.JoinedStr) or node_match(ast.BinOp, op=ast.Mod)
|
||||
):
|
||||
return
|
||||
|
||||
if inst_match(("BEFORE_WITH","WITH_EXCEPT_START")) and node_match(ast.With):
|
||||
return
|
||||
|
||||
if inst_match(("STORE_NAME", "STORE_GLOBAL"), argval="__doc__") and node_match(
|
||||
ast.Constant
|
||||
):
|
||||
# store docstrings
|
||||
return
|
||||
|
||||
if (
|
||||
inst_match(("STORE_NAME", "STORE_FAST", "STORE_GLOBAL", "STORE_DEREF"))
|
||||
and node_match(ast.ExceptHandler)
|
||||
and instruction.argval == mangled_name(node)
|
||||
):
|
||||
# store exception in variable
|
||||
return
|
||||
|
||||
if (
|
||||
inst_match(("STORE_NAME", "STORE_FAST", "STORE_DEREF", "STORE_GLOBAL"))
|
||||
and node_match((ast.Import, ast.ImportFrom))
|
||||
and any(mangled_name(cast(EnhancedAST, alias)) == instruction.argval for alias in cast(ast.Import, node).names)
|
||||
):
|
||||
# store imported module in variable
|
||||
return
|
||||
|
||||
if (
|
||||
inst_match(("STORE_FAST", "STORE_DEREF", "STORE_NAME", "STORE_GLOBAL"))
|
||||
and (
|
||||
node_match((ast.FunctionDef, ast.ClassDef, ast.AsyncFunctionDef))
|
||||
or node_match(
|
||||
ast.Name,
|
||||
ctx=ast.Store,
|
||||
)
|
||||
)
|
||||
and instruction.argval == mangled_name(node)
|
||||
):
|
||||
return
|
||||
|
||||
if False:
|
||||
# TODO: match expressions are not supported for now
|
||||
if inst_match(("STORE_FAST", "STORE_NAME")) and node_match(
|
||||
ast.MatchAs, name=instruction.argval
|
||||
):
|
||||
return
|
||||
|
||||
if inst_match("COMPARE_OP", argval="==") and node_match(ast.MatchSequence):
|
||||
return
|
||||
|
||||
if inst_match("COMPARE_OP", argval="==") and node_match(ast.MatchValue):
|
||||
return
|
||||
|
||||
if inst_match("BINARY_OP") and node_match(
|
||||
ast.AugAssign, op=op_type_map[instruction.argrepr.removesuffix("=")]
|
||||
):
|
||||
# a+=5
|
||||
return
|
||||
|
||||
if node_match(ast.Attribute, ctx=ast.Del) and inst_match(
|
||||
"DELETE_ATTR", argval=mangled_name(node)
|
||||
):
|
||||
return
|
||||
|
||||
if inst_match(
|
||||
(
|
||||
"JUMP_IF_TRUE_OR_POP",
|
||||
"JUMP_IF_FALSE_OR_POP",
|
||||
"POP_JUMP_IF_TRUE",
|
||||
"POP_JUMP_IF_FALSE",
|
||||
)
|
||||
) and node_match(ast.BoolOp):
|
||||
# and/or short circuit
|
||||
return
|
||||
|
||||
if inst_match("DELETE_SUBSCR") and node_match(ast.Subscript, ctx=ast.Del):
|
||||
return
|
||||
|
||||
if (
|
||||
node_match(ast.Name, ctx=ast.Load)
|
||||
or (
|
||||
node_match(ast.Name, ctx=ast.Store)
|
||||
and isinstance(node.parent, ast.AugAssign)
|
||||
)
|
||||
) and inst_match(
|
||||
(
|
||||
"LOAD_NAME",
|
||||
"LOAD_FAST",
|
||||
"LOAD_FAST_CHECK",
|
||||
"LOAD_GLOBAL",
|
||||
"LOAD_DEREF",
|
||||
"LOAD_FROM_DICT_OR_DEREF",
|
||||
),
|
||||
argval=mangled_name(node),
|
||||
):
|
||||
return
|
||||
|
||||
if node_match(ast.Name, ctx=ast.Del) and inst_match(
|
||||
("DELETE_NAME", "DELETE_GLOBAL", "DELETE_DEREF"), argval=mangled_name(node)
|
||||
):
|
||||
return
|
||||
|
||||
if node_match(ast.Constant) and inst_match(
|
||||
"LOAD_CONST", argval=cast(ast.Constant, node).value
|
||||
):
|
||||
return
|
||||
|
||||
if node_match(
|
||||
(ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp, ast.For)
|
||||
) and inst_match(("GET_ITER", "FOR_ITER")):
|
||||
return
|
||||
|
||||
if sys.version_info >= (3, 12):
|
||||
if node_match(ast.UnaryOp, op=ast.UAdd) and inst_match(
|
||||
"CALL_INTRINSIC_1", argrepr="INTRINSIC_UNARY_POSITIVE"
|
||||
):
|
||||
return
|
||||
|
||||
if node_match(ast.Subscript) and inst_match("BINARY_SLICE"):
|
||||
return
|
||||
|
||||
if node_match(ast.ImportFrom) and inst_match(
|
||||
"CALL_INTRINSIC_1", argrepr="INTRINSIC_IMPORT_STAR"
|
||||
):
|
||||
return
|
||||
|
||||
if (
|
||||
node_match(ast.Yield) or isinstance(node.parent, ast.GeneratorExp)
|
||||
) and inst_match("CALL_INTRINSIC_1", argrepr="INTRINSIC_ASYNC_GEN_WRAP"):
|
||||
return
|
||||
|
||||
if node_match(ast.Name) and inst_match("LOAD_DEREF",argval="__classdict__"):
|
||||
return
|
||||
|
||||
if node_match(ast.TypeVar) and (
|
||||
inst_match("CALL_INTRINSIC_1", argrepr="INTRINSIC_TYPEVAR")
|
||||
or inst_match(
|
||||
"CALL_INTRINSIC_2", argrepr="INTRINSIC_TYPEVAR_WITH_BOUND"
|
||||
)
|
||||
or inst_match(
|
||||
"CALL_INTRINSIC_2", argrepr="INTRINSIC_TYPEVAR_WITH_CONSTRAINTS"
|
||||
)
|
||||
or inst_match(("STORE_FAST", "STORE_DEREF"), argrepr=mangled_name(node))
|
||||
):
|
||||
return
|
||||
|
||||
if node_match(ast.TypeVarTuple) and (
|
||||
inst_match("CALL_INTRINSIC_1", argrepr="INTRINSIC_TYPEVARTUPLE")
|
||||
or inst_match(("STORE_FAST", "STORE_DEREF"), argrepr=node.name)
|
||||
):
|
||||
return
|
||||
|
||||
if node_match(ast.ParamSpec) and (
|
||||
inst_match("CALL_INTRINSIC_1", argrepr="INTRINSIC_PARAMSPEC")
|
||||
|
||||
or inst_match(("STORE_FAST", "STORE_DEREF"), argrepr=node.name)):
|
||||
return
|
||||
|
||||
|
||||
if node_match(ast.TypeAlias):
|
||||
if(
|
||||
inst_match("CALL_INTRINSIC_1", argrepr="INTRINSIC_TYPEALIAS")
|
||||
or inst_match(
|
||||
("STORE_NAME", "STORE_FAST", "STORE_DEREF"), argrepr=node.name.id
|
||||
)
|
||||
or inst_match("CALL")
|
||||
):
|
||||
return
|
||||
|
||||
|
||||
if node_match(ast.ClassDef) and node.type_params:
|
||||
if inst_match(
|
||||
("STORE_DEREF", "LOAD_DEREF", "LOAD_FROM_DICT_OR_DEREF"),
|
||||
argrepr=".type_params",
|
||||
):
|
||||
return
|
||||
|
||||
if inst_match(("STORE_FAST", "LOAD_FAST"), argrepr=".generic_base"):
|
||||
return
|
||||
|
||||
if inst_match(
|
||||
"CALL_INTRINSIC_1", argrepr="INTRINSIC_SUBSCRIPT_GENERIC"
|
||||
):
|
||||
return
|
||||
|
||||
if inst_match("LOAD_DEREF",argval="__classdict__"):
|
||||
return
|
||||
|
||||
if node_match((ast.FunctionDef,ast.AsyncFunctionDef)) and node.type_params:
|
||||
if inst_match("CALL"):
|
||||
return
|
||||
|
||||
if inst_match(
|
||||
"CALL_INTRINSIC_2", argrepr="INTRINSIC_SET_FUNCTION_TYPE_PARAMS"
|
||||
):
|
||||
return
|
||||
|
||||
if inst_match("LOAD_FAST",argval=".defaults"):
|
||||
return
|
||||
|
||||
if inst_match("LOAD_FAST",argval=".kwdefaults"):
|
||||
return
|
||||
|
||||
if inst_match("STORE_NAME", argval="__classdictcell__"):
|
||||
# this is a general thing
|
||||
return
|
||||
|
||||
|
||||
# f-strings
|
||||
|
||||
if node_match(ast.JoinedStr) and (
|
||||
inst_match("LOAD_ATTR", argval="join")
|
||||
or inst_match(("LIST_APPEND", "CALL"))
|
||||
):
|
||||
return
|
||||
|
||||
if node_match(ast.FormattedValue) and inst_match("FORMAT_VALUE"):
|
||||
return
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
|
||||
if inst_match("NOP"):
|
||||
return
|
||||
|
||||
if inst_match("TO_BOOL") and node_match(ast.BoolOp):
|
||||
return
|
||||
|
||||
if inst_match("CALL_KW") and node_match((ast.Call, ast.ClassDef)):
|
||||
return
|
||||
|
||||
if inst_match("LOAD_FAST", argval=".type_params"):
|
||||
return
|
||||
|
||||
if inst_match("LOAD_FAST", argval="__classdict__"):
|
||||
return
|
||||
|
||||
if inst_match("LOAD_FAST") and node_match(
|
||||
(
|
||||
ast.FunctionDef,
|
||||
ast.ClassDef,
|
||||
ast.TypeAlias,
|
||||
ast.TypeVar,
|
||||
ast.Lambda,
|
||||
ast.AsyncFunctionDef,
|
||||
)
|
||||
):
|
||||
# These are loads for closure variables.
|
||||
# It is difficult to check that this is actually closure variable, see:
|
||||
# https://github.com/alexmojaki/executing/pull/80#discussion_r1716027317
|
||||
return
|
||||
|
||||
if (
|
||||
inst_match("LOAD_FAST")
|
||||
and node_match(ast.TypeAlias)
|
||||
and node.name.id == instruction.argval
|
||||
):
|
||||
return
|
||||
|
||||
if inst_match("STORE_NAME",argval="__static_attributes__"):
|
||||
# the node is the first node in the body
|
||||
return
|
||||
|
||||
if inst_match("LOAD_FAST") and isinstance(node.parent,ast.TypeVar):
|
||||
return
|
||||
|
||||
|
||||
# old verifier
|
||||
|
||||
typ: Type = type(None)
|
||||
op_type: Type = type(None)
|
||||
|
||||
if op_name.startswith(("BINARY_SUBSCR", "SLICE+")):
|
||||
typ = ast.Subscript
|
||||
ctx = ast.Load
|
||||
elif op_name.startswith("BINARY_"):
|
||||
typ = ast.BinOp
|
||||
op_type = op_type_map[instruction.argrepr]
|
||||
extra_filter = lambda e: isinstance(cast(ast.BinOp, e).op, op_type)
|
||||
elif op_name.startswith("UNARY_"):
|
||||
typ = ast.UnaryOp
|
||||
op_type = dict(
|
||||
UNARY_POSITIVE=ast.UAdd,
|
||||
UNARY_NEGATIVE=ast.USub,
|
||||
UNARY_NOT=ast.Not,
|
||||
UNARY_INVERT=ast.Invert,
|
||||
)[op_name]
|
||||
extra_filter = lambda e: isinstance(cast(ast.UnaryOp, e).op, op_type)
|
||||
elif op_name in ("LOAD_ATTR", "LOAD_METHOD", "LOOKUP_METHOD","LOAD_SUPER_ATTR"):
|
||||
typ = ast.Attribute
|
||||
ctx = ast.Load
|
||||
extra_filter = lambda e: mangled_name(e) == instruction.argval
|
||||
elif op_name in (
|
||||
"LOAD_NAME",
|
||||
"LOAD_GLOBAL",
|
||||
"LOAD_FAST",
|
||||
"LOAD_DEREF",
|
||||
"LOAD_CLASSDEREF",
|
||||
):
|
||||
typ = ast.Name
|
||||
ctx = ast.Load
|
||||
extra_filter = lambda e: cast(ast.Name, e).id == instruction.argval
|
||||
elif op_name in ("COMPARE_OP", "IS_OP", "CONTAINS_OP"):
|
||||
typ = ast.Compare
|
||||
extra_filter = lambda e: len(cast(ast.Compare, e).ops) == 1
|
||||
elif op_name.startswith(("STORE_SLICE", "STORE_SUBSCR")):
|
||||
ctx = ast.Store
|
||||
typ = ast.Subscript
|
||||
elif op_name.startswith("STORE_ATTR"):
|
||||
ctx = ast.Store
|
||||
typ = ast.Attribute
|
||||
extra_filter = lambda e: mangled_name(e) == instruction.argval
|
||||
|
||||
node_ctx = getattr(node, "ctx", None)
|
||||
|
||||
ctx_match = (
|
||||
ctx is not type(None)
|
||||
or not hasattr(node, "ctx")
|
||||
or isinstance(node_ctx, ctx)
|
||||
)
|
||||
|
||||
# check for old verifier
|
||||
if isinstance(node, typ) and ctx_match and extra_filter(node):
|
||||
return
|
||||
|
||||
# generate error
|
||||
|
||||
title = "ast.%s is not created from %s" % (
|
||||
type(node).__name__,
|
||||
instruction.opname,
|
||||
)
|
||||
|
||||
raise VerifierFailure(title, node, instruction)
|
||||
|
||||
def instruction(self, index: int) -> Optional[dis.Instruction]:
|
||||
return self.bc_dict.get(index,None)
|
||||
|
||||
def instruction_before(
|
||||
self, instruction: dis.Instruction
|
||||
) -> Optional[dis.Instruction]:
|
||||
return self.bc_dict.get(instruction.offset - 2, None)
|
||||
|
||||
def opname(self, index: int) -> str:
|
||||
i=self.instruction(index)
|
||||
if i is None:
|
||||
return "CACHE"
|
||||
return i.opname
|
||||
|
||||
extra_node_types=()
|
||||
if sys.version_info >= (3,12):
|
||||
extra_node_types = (ast.type_param,)
|
||||
|
||||
def find_node(
|
||||
self,
|
||||
index: int,
|
||||
match_positions: Sequence[str] = (
|
||||
"lineno",
|
||||
"end_lineno",
|
||||
"col_offset",
|
||||
"end_col_offset",
|
||||
),
|
||||
typ: tuple[Type, ...] = (
|
||||
ast.expr,
|
||||
ast.stmt,
|
||||
ast.excepthandler,
|
||||
ast.pattern,
|
||||
*extra_node_types,
|
||||
),
|
||||
) -> EnhancedAST:
|
||||
instruction = self.instruction(index)
|
||||
assert instruction is not None
|
||||
|
||||
position = instruction.positions
|
||||
assert position is not None and position.lineno is not None
|
||||
|
||||
return only(
|
||||
cast(EnhancedAST, node)
|
||||
for node in self.source._nodes_by_line[position.lineno]
|
||||
if isinstance(node, typ)
|
||||
if not isinstance(node, ast.Expr)
|
||||
# matchvalue.value has the same positions as matchvalue themself, so we exclude ast.MatchValue
|
||||
if not isinstance(node, ast.MatchValue)
|
||||
if all(
|
||||
getattr(position, attr) == getattr(node, attr)
|
||||
for attr in match_positions
|
||||
)
|
||||
)
|
@@ -0,0 +1,16 @@
|
||||
import sys
|
||||
|
||||
|
||||
|
||||
def is_pytest_compatible() -> bool:
|
||||
""" returns true if executing can be used for expressions inside assert statements which are rewritten by pytest
|
||||
"""
|
||||
if sys.version_info < (3, 11):
|
||||
return False
|
||||
|
||||
try:
|
||||
import pytest
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
return pytest.version_tuple >= (8, 3, 4)
|
1160
.venv/lib/python3.12/site-packages/executing/executing.py
Normal file
1160
.venv/lib/python3.12/site-packages/executing/executing.py
Normal file
File diff suppressed because it is too large
Load Diff
1
.venv/lib/python3.12/site-packages/executing/version.py
Normal file
1
.venv/lib/python3.12/site-packages/executing/version.py
Normal file
@@ -0,0 +1 @@
|
||||
__version__ = '2.2.0'
|
Reference in New Issue
Block a user