summaryrefslogtreecommitdiffstats
path: root/contrib
diff options
context:
space:
mode:
authorrobot-piglet <[email protected]>2025-09-16 14:42:04 +0300
committerrobot-piglet <[email protected]>2025-09-16 14:54:34 +0300
commit30351fcdf2948b8ea9afe9476a1b6aa2b56384d7 (patch)
treecd45037f6bcca203afed5d4759e3c034dcb40a90 /contrib
parent91172f9b69f179f6afba6485d257f3f026bffaa6 (diff)
Intermediate changes
commit_hash:b6c37a893bfe6bb817a0e38d120edcc594c55985
Diffstat (limited to 'contrib')
-rw-r--r--contrib/python/executing/.dist-info/METADATA3
-rw-r--r--contrib/python/executing/executing/_position_node_finder.py167
-rw-r--r--contrib/python/executing/executing/_utils.py139
-rw-r--r--contrib/python/executing/executing/executing.py64
-rw-r--r--contrib/python/executing/executing/version.py2
-rw-r--r--contrib/python/executing/ya.make3
6 files changed, 260 insertions, 118 deletions
diff --git a/contrib/python/executing/.dist-info/METADATA b/contrib/python/executing/.dist-info/METADATA
index 6e4ac6516d3..65953e6c8f7 100644
--- a/contrib/python/executing/.dist-info/METADATA
+++ b/contrib/python/executing/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: executing
-Version: 2.2.0
+Version: 2.2.1
Summary: Get the currently executing AST node of a frame, and other information
Home-page: https://github.com/alexmojaki/executing
Author: Alex Hall
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE.txt
diff --git a/contrib/python/executing/executing/_position_node_finder.py b/contrib/python/executing/executing/_position_node_finder.py
index 0f8344106f2..27028d26ec9 100644
--- a/contrib/python/executing/executing/_position_node_finder.py
+++ b/contrib/python/executing/executing/_position_node_finder.py
@@ -5,8 +5,10 @@ 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
@@ -25,51 +27,6 @@ def node_and_parents(node: EnhancedAST) -> Iterator[EnhancedAST]:
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))
@@ -115,6 +72,7 @@ class PositionNodeFinder(object):
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
@@ -302,6 +260,23 @@ class PositionNodeFinder(object):
# 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:
@@ -383,6 +358,7 @@ class PositionNodeFinder(object):
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")
@@ -423,7 +399,7 @@ class PositionNodeFinder(object):
):
raise KnownIssue(f"can not map {instruction.opname} to two ast nodes")
- if instruction.opname == "LOAD_FAST" and instruction.argval == "__class__":
+ if instruction.opname in ("LOAD_FAST","LOAD_FAST_BORROW") and instruction.argval == "__class__":
# example:
# class T:
# def a():
@@ -444,6 +420,60 @@ class PositionNodeFinder(object):
# 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 (
@@ -551,7 +581,7 @@ class PositionNodeFinder(object):
# call to context.__exit__
return
- if inst_match(("CALL", "LOAD_FAST")) and node_match(
+ 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
@@ -657,11 +687,12 @@ class PositionNodeFinder(object):
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 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)
@@ -693,11 +724,15 @@ class PositionNodeFinder(object):
"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",
),
- argval=mangled_name(node),
+ ) and (
+ mangled_name(node) in instruction.argval if isinstance(instruction.argval,tuple)
+ else instruction.argval == mangled_name(node)
):
return
@@ -707,7 +742,7 @@ class PositionNodeFinder(object):
return
if node_match(ast.Constant) and inst_match(
- "LOAD_CONST", argval=cast(ast.Constant, node).value
+ ("LOAD_CONST","LOAD_SMALL_INT"), argval=cast(ast.Constant, node).value
):
return
@@ -767,7 +802,7 @@ class PositionNodeFinder(object):
if(
inst_match("CALL_INTRINSIC_1", argrepr="INTRINSIC_TYPEALIAS")
or inst_match(
- ("STORE_NAME", "STORE_FAST", "STORE_DEREF"), argrepr=node.name.id
+ ("STORE_NAME", "STORE_FAST", "STORE_DEREF","STORE_GLOBAL"), argrepr=node.name.id
)
or inst_match("CALL")
):
@@ -807,6 +842,10 @@ class PositionNodeFinder(object):
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
@@ -840,7 +879,7 @@ class PositionNodeFinder(object):
if inst_match("LOAD_FAST", argval="__classdict__"):
return
- if inst_match("LOAD_FAST") and node_match(
+ if inst_match(("LOAD_FAST","LOAD_FAST_BORROW")) and node_match(
(
ast.FunctionDef,
ast.ClassDef,
@@ -866,7 +905,21 @@ class PositionNodeFinder(object):
# the node is the first node in the body
return
- if inst_match("LOAD_FAST") and isinstance(node.parent,ast.TypeVar):
+ 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
diff --git a/contrib/python/executing/executing/_utils.py b/contrib/python/executing/executing/_utils.py
new file mode 100644
index 00000000000..42511844eb1
--- /dev/null
+++ b/contrib/python/executing/executing/_utils.py
@@ -0,0 +1,139 @@
+
+import ast
+import sys
+import dis
+from typing import cast, Any,Iterator
+import types
+
+
+
+def assert_(condition, message=""):
+ # type: (Any, str) -> None
+ """
+ Like an assert statement, but unaffected by -O
+ :param condition: value that is expected to be truthy
+ :type message: Any
+ """
+ if not condition:
+ raise AssertionError(str(message))
+
+
+if sys.version_info >= (3, 4):
+ # noinspection PyUnresolvedReferences
+ _get_instructions = dis.get_instructions
+ from dis import Instruction as _Instruction
+
+ class Instruction(_Instruction):
+ lineno = None # type: int
+else:
+ from collections import namedtuple
+
+ class Instruction(namedtuple('Instruction', 'offset argval opname starts_line')):
+ lineno = None # type: int
+
+ from dis import HAVE_ARGUMENT, EXTENDED_ARG, hasconst, opname, findlinestarts, hasname
+
+ # Based on dis.disassemble from 2.7
+ # Left as similar as possible for easy diff
+
+ def _get_instructions(co):
+ # type: (types.CodeType) -> Iterator[Instruction]
+ code = co.co_code
+ linestarts = dict(findlinestarts(co))
+ n = len(code)
+ i = 0
+ extended_arg = 0
+ while i < n:
+ offset = i
+ c = code[i]
+ op = ord(c)
+ lineno = linestarts.get(i)
+ argval = None
+ i = i + 1
+ if op >= HAVE_ARGUMENT:
+ oparg = ord(code[i]) + ord(code[i + 1]) * 256 + extended_arg
+ extended_arg = 0
+ i = i + 2
+ if op == EXTENDED_ARG:
+ extended_arg = oparg * 65536
+
+ if op in hasconst:
+ argval = co.co_consts[oparg]
+ elif op in hasname:
+ argval = co.co_names[oparg]
+ elif opname[op] == 'LOAD_FAST':
+ argval = co.co_varnames[oparg]
+ yield Instruction(offset, argval, opname[op], lineno)
+
+def get_instructions(co):
+ # type: (types.CodeType) -> Iterator[EnhancedInstruction]
+ lineno = co.co_firstlineno
+ for inst in _get_instructions(co):
+ inst = cast(EnhancedInstruction, inst)
+ lineno = inst.starts_line or lineno
+ assert_(lineno)
+ inst.lineno = lineno
+ yield inst
+
+
+# Type class used to expand out the definition of AST to include fields added by this library
+# It's not actually used for anything other than type checking though!
+class EnhancedAST(ast.AST):
+ parent = None # type: EnhancedAST
+
+# Type class used to expand out the definition of AST to include fields added by this library
+# It's not actually used for anything other than type checking though!
+class EnhancedInstruction(Instruction):
+ _copied = None # type: bool
+
+
+
+
+
+def mangled_name(node):
+ # type: (EnhancedAST) -> str
+ """
+
+ Parameters:
+ node: the node which should be mangled
+ name: the name of the node
+
+ Returns:
+ The mangled name of `node`
+ """
+
+ function_class_types=(ast.FunctionDef, ast.ClassDef, ast.AsyncFunctionDef)
+
+ 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, function_class_types):
+ 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")
+
+ 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!="" and child not in parent.decorator_list:
+ return "_" + class_name + name
+
+
+
+ return name
diff --git a/contrib/python/executing/executing/executing.py b/contrib/python/executing/executing/executing.py
index 5cf117e18c5..dd1e1d79855 100644
--- a/contrib/python/executing/executing/executing.py
+++ b/contrib/python/executing/executing/executing.py
@@ -40,8 +40,8 @@ from operator import attrgetter
from pathlib import Path
from threading import RLock
from tokenize import detect_encoding
-from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator, List, Optional, Sequence, Set, Sized, Tuple, \
- Type, TypeVar, Union, cast
+from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator, List, Optional, Sequence, Set, Sized, Tuple, Type, TypeVar, Union, cast
+from ._utils import mangled_name,assert_, EnhancedAST,EnhancedInstruction,Instruction,get_instructions
if TYPE_CHECKING: # pragma: no cover
from asttokens import ASTTokens, ASTText
@@ -52,48 +52,8 @@ function_node_types = (ast.FunctionDef, ast.AsyncFunctionDef) # type: Tuple[Type
cache = lru_cache(maxsize=None)
-# Type class used to expand out the definition of AST to include fields added by this library
-# It's not actually used for anything other than type checking though!
-class EnhancedAST(ast.AST):
- parent = None # type: EnhancedAST
-
-
-class Instruction(dis.Instruction):
- lineno = None # type: int
-
-
-# Type class used to expand out the definition of AST to include fields added by this library
-# It's not actually used for anything other than type checking though!
-class EnhancedInstruction(Instruction):
- _copied = None # type: bool
-
-
-
-def assert_(condition, message=""):
- # type: (Any, str) -> None
- """
- Like an assert statement, but unaffected by -O
- :param condition: value that is expected to be truthy
- :type message: Any
- """
- if not condition:
- raise AssertionError(str(message))
-
-
-def get_instructions(co):
- # type: (types.CodeType) -> Iterator[EnhancedInstruction]
- lineno = co.co_firstlineno
- for inst in dis.get_instructions(co):
- inst = cast(EnhancedInstruction, inst)
- lineno = inst.starts_line or lineno
- assert_(lineno)
- inst.lineno = lineno
- yield inst
-
-
TESTING = 0
-
class NotOneValueFound(Exception):
def __init__(self,msg,values=[]):
# type: (str, Sequence) -> None
@@ -581,11 +541,11 @@ class SentinelNodeFinder(object):
elif op_name in ('LOAD_ATTR', 'LOAD_METHOD', 'LOOKUP_METHOD'):
typ = ast.Attribute
ctx = ast.Load
- extra_filter = lambda e: attr_names_match(e.attr, instruction.argval)
+ 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: e.id == instruction.argval
+ extra_filter = lambda e:mangled_name(e) == instruction.argval
elif op_name in ('COMPARE_OP', 'IS_OP', 'CONTAINS_OP'):
typ = ast.Compare
extra_filter = lambda e: len(e.ops) == 1
@@ -595,10 +555,11 @@ class SentinelNodeFinder(object):
elif op_name.startswith('STORE_ATTR'):
ctx = ast.Store
typ = ast.Attribute
- extra_filter = lambda e: attr_names_match(e.attr, instruction.argval)
+ extra_filter = lambda e:mangled_name(e) == instruction.argval
else:
raise RuntimeError(op_name)
+
with lock:
exprs = {
cast(EnhancedAST, node)
@@ -1126,19 +1087,6 @@ def find_node_ipython(frame, lasti, stmts, source):
return decorator, node
-def attr_names_match(attr, argval):
- # type: (str, str) -> bool
- """
- Checks that the user-visible attr (from ast) can correspond to
- the argval in the bytecode, i.e. the real attribute fetched internally,
- which may be mangled for private attributes.
- """
- if attr == argval:
- return True
- if not attr.startswith("__"):
- return False
- return bool(re.match(r"^_\w+%s$" % attr, argval))
-
def node_linenos(node):
# type: (ast.AST) -> Iterator[int]
diff --git a/contrib/python/executing/executing/version.py b/contrib/python/executing/executing/version.py
index e212710ff7b..1ef59ad4406 100644
--- a/contrib/python/executing/executing/version.py
+++ b/contrib/python/executing/executing/version.py
@@ -1 +1 @@
-__version__ = '2.2.0' \ No newline at end of file
+__version__ = '2.2.1' \ No newline at end of file
diff --git a/contrib/python/executing/ya.make b/contrib/python/executing/ya.make
index 32d111cc478..4cdb448b48d 100644
--- a/contrib/python/executing/ya.make
+++ b/contrib/python/executing/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(2.2.0)
+VERSION(2.2.1)
LICENSE(MIT)
@@ -14,6 +14,7 @@ PY_SRCS(
executing/_exceptions.py
executing/_position_node_finder.py
executing/_pytest_utils.py
+ executing/_utils.py
executing/executing.py
executing/version.py
)