You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			1046 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			1046 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			Python
		
	
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 ._utils import mangled_name
 | 
						|
 | 
						|
from functools import lru_cache
 | 
						|
import itertools
 | 
						|
 | 
						|
# 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)
 | 
						|
 | 
						|
 | 
						|
@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.frame=frame
 | 
						|
 | 
						|
        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
 | 
						|
 | 
						|
        if sys.version_info >= (3, 14) and instruction.opname == "CALL":
 | 
						|
            before = self.instruction_before(instruction)
 | 
						|
            if (
 | 
						|
                before is not None
 | 
						|
                and before.opname == "LOAD_SPECIAL"
 | 
						|
                and before.argrepr in ("__enter__","__aenter__")
 | 
						|
                and before.positions == instruction.positions
 | 
						|
                and isinstance(node.parent, ast.withitem)
 | 
						|
                and node is node.parent.context_expr
 | 
						|
            ):
 | 
						|
                return node.parent.parent
 | 
						|
 | 
						|
        if sys.version_info >= (3, 14) and isinstance(node, ast.UnaryOp) and isinstance(node.op,ast.Not) and instruction.opname !="UNARY_NOT":
 | 
						|
            # fix for https://github.com/python/cpython/issues/137843
 | 
						|
            return node.operand
 | 
						|
 | 
						|
 | 
						|
        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_FAST_BORROW", 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 in ("LOAD_FAST","LOAD_FAST_BORROW") 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
 | 
						|
 | 
						|
        if sys.version_info >= (3,14):
 | 
						|
 | 
						|
 | 
						|
            if header_length := self.annotation_header_size():
 | 
						|
 | 
						|
                last_offset=list(self.bc_dict.keys())[-1]
 | 
						|
                if (
 | 
						|
                    not (header_length*2 < instruction.offset <last_offset-4)
 | 
						|
                ):
 | 
						|
                    # https://github.com/python/cpython/issues/135700
 | 
						|
                    raise KnownIssue("synthetic opcodes in annotations are just bound to the first node")
 | 
						|
 | 
						|
                if self.frame.f_code.co_name=="__annotate__" and instruction.opname=="STORE_SUBSCR":
 | 
						|
                    raise KnownIssue("synthetic code to store annotation")
 | 
						|
 | 
						|
                if self.frame.f_code.co_name=="__annotate__" and isinstance(node,ast.AnnAssign):
 | 
						|
                    raise KnownIssue("some opcodes in the annotation are just bound specific nodes")
 | 
						|
 | 
						|
            if isinstance(node,(ast.TypeAlias)) and  self.frame.f_code.co_name==node.name.id :
 | 
						|
                raise KnownIssue("some opcodes in the annotation are just bound TypeAlias")
 | 
						|
 | 
						|
            if instruction.opname == "STORE_NAME" and instruction.argrepr == "__annotate__":
 | 
						|
                raise KnownIssue("just a store of the annotation")
 | 
						|
 | 
						|
            if instruction.opname == "IS_OP" and isinstance(node,ast.Name):
 | 
						|
                raise KnownIssue("part of a check that a name like `all` is a builtin")
 | 
						|
 | 
						|
 | 
						|
 | 
						|
    def annotation_header_size(self)->int:
 | 
						|
        if sys.version_info >=(3,14):
 | 
						|
            header=[inst.opname for inst in itertools.islice(self.bc_dict.values(),8)]
 | 
						|
 | 
						|
            if len(header)==8:
 | 
						|
                if header[0] in ("COPY_FREE_VARS","MAKE_CELL"):
 | 
						|
                    del header[0]
 | 
						|
                    header_size=8
 | 
						|
                else:
 | 
						|
                    del header[7]
 | 
						|
                    header_size=7
 | 
						|
 | 
						|
                if header==[
 | 
						|
                    "RESUME",
 | 
						|
                    "LOAD_FAST_BORROW",
 | 
						|
                    "LOAD_SMALL_INT",
 | 
						|
                    "COMPARE_OP",
 | 
						|
                    "POP_JUMP_IF_FALSE",
 | 
						|
                    "NOT_TAKEN",
 | 
						|
                    "LOAD_COMMON_CONSTANT",
 | 
						|
                ]:
 | 
						|
                    return header_size
 | 
						|
 | 
						|
        return 0
 | 
						|
 | 
						|
    @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","LOAD_FAST_BORROW")) 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"):
 | 
						|
            arg=instruction.argrepr.removesuffix("=")
 | 
						|
 | 
						|
            if arg!="[]" and node_match( ast.AugAssign, op=op_type_map[arg]):
 | 
						|
                # 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_FAST_BORROW",
 | 
						|
                "LOAD_GLOBAL",
 | 
						|
                "LOAD_DEREF",
 | 
						|
                "LOAD_FROM_DICT_OR_DEREF",
 | 
						|
                "LOAD_FAST_BORROW_LOAD_FAST_BORROW",
 | 
						|
            ),
 | 
						|
        ) and (
 | 
						|
            mangled_name(node) in instruction.argval if isinstance(instruction.argval,tuple)
 | 
						|
            else instruction.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","LOAD_SMALL_INT"), 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","STORE_GLOBAL"), 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 sys.version_info >= (3, 14):
 | 
						|
                    if inst_match("LOAD_FAST_BORROW_LOAD_FAST_BORROW",argval=(".defaults",".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","LOAD_FAST_BORROW")) 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","LOAD_FAST_BORROW")) and isinstance(node.parent,ast.TypeVar):
 | 
						|
                return
 | 
						|
 | 
						|
            if inst_match("CALL_INTRINSIC_2",argrepr="INTRINSIC_SET_TYPEPARAM_DEFAULT") and node_match((ast.TypeVar,ast.ParamSpec,ast.TypeVarTuple)):
 | 
						|
                return
 | 
						|
 | 
						|
        if sys.version_info >= (3, 14):
 | 
						|
            if inst_match("BINARY_OP",argrepr="[]") and node_match(ast.Subscript):
 | 
						|
                return
 | 
						|
            if inst_match("LOAD_FAST_BORROW", argval="__classdict__"):
 | 
						|
                return
 | 
						|
            if inst_match(("STORE_NAME","LOAD_NAME"), argval="__conditional_annotations__"):
 | 
						|
                return
 | 
						|
 | 
						|
            if inst_match("LOAD_FAST_BORROW_LOAD_FAST_BORROW") and node_match(ast.Name) and node.id in instruction.argval:
 | 
						|
                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
 | 
						|
            )
 | 
						|
        )
 |