python-executing fails to build with Python 3.15.0a7. 21 test failures appear with different ast objects not created from the expected LOAD_FAST, see example failure: ___________________ TestFiles.test_sample_files[_parser.py] ____________________ self = <tests.test_main.TestFiles object at 0x7f53d6d5c460> full_filename = '/builddir/build/BUILD/python-executing-2.2.1-build/executing-2.2.1/tests/samples/_parser.py' result_filename = '/builddir/build/BUILD/python-executing-2.2.1-build/executing-2.2.1/tests/sample_results/_parser-py-3.15.json' @pytest.mark.parametrize("full_filename,result_filename", list(sample_files("samples"))) def test_sample_files(self, full_filename, result_filename): self.start_time = time.time() > result = self.check_filename(full_filename, check_names=True) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ tests/test_main.py:764: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ tests/test_main.py:885: in check_filename result = list(self.check_code(code, nodes, decorators, check_names=check_names)) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ tests/test_main.py:1457: in check_code for x in self.check_code(inst.argval, nodes, decorators, check_names=check_names): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ tests/test_main.py:1241: in check_code ex = Source.executing(frame) ^^^^^^^^^^^^^^^^^^^^^^^ executing/executing.py:233: in executing node_finder = NodeFinder(frame, stmts, tree, lasti, source) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ executing/_position_node_finder.py:128: in __init__ self.verify(self.result, instruction) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = <executing._position_node_finder.PositionNodeFinder object at 0x7f53d27c08a0> node = Call(func=Name(id='range', ctx=Load()), args=[Constant(value=32, kind=None)], keywords=[]) instruction = Instruction(opname='LOAD_FAST', opcode=84, arg=0, argval='.0', argrepr='.0', offset=0, start_offset=0, starts_line=Tru...umber=22, label=None, positions=Positions(lineno=22, end_lineno=22, col_offset=39, end_col_offset=48), cache_info=None) 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) E executing._exceptions.VerifierFailure: ast.Call is not created from LOAD_FAST executing/_position_node_finder.py:991: VerifierFailure ----------------------------- Captured stdout call ----------------------------- check /builddir/build/BUILD/python-executing-2.2.1-build/executing-2.2.1/tests/samples/_parser.py VerifierFailure: ast.Call is not created from LOAD_FAST instruction: LOAD_FAST 0 (.0) node: Call(func=Name(id='range', lineno=22, col_offset=39, end_lineno=22, end_col_offset=44), args=[Constant(value=32, lineno=22, col_offset=45, end_lineno=22, end_col_offset=47)], lineno=22, col_offset=39, end_lineno=22, end_col_offset=48) parent node: comprehension source code: 18: match_to_number, 19: ) 20: from ._types import Key, ParseFloat, Pos 21: 22: ASCII_CTRL = frozenset(chr(i) for i in range(32)) | frozenset(chr(127)) 23: 24: # Neither of these sets include quotation mark or backslash. They are 25: # currently handled as separate cases in the parser functions. 26: ILLEGAL_BASIC_STR_CHARS = ASCII_CTRL - frozenset("\t") 27: ILLEGAL_MULTILINE_BASIC_STR_CHARS = ASCII_CTRL - frozenset("\t\n") None bytecode: https://docs.python.org/3.15/whatsnew/3.15.html For the build logs, see: https://copr-be.cloud.fedoraproject.org/results/@python/python3.15/fedora-rawhide-x86_64/10222410-python-executing/ For all our attempts to build python-executing with Python 3.15, see: https://copr.fedorainfracloud.org/coprs/g/python/python3.15/package/python-executing/ Testing and mass rebuild of packages is happening in copr. You can follow these instructions to test locally in mock if your package builds with Python 3.15: https://copr.fedorainfracloud.org/coprs/g/python/python3.15/ Let us know here if you have any questions. Python 3.15 is planned to be included in Fedora 45. To make that update smoother, we're building Fedora packages with all pre-releases of Python 3.15. A build failure prevents us from testing all dependent packages (transitive [Build]Requires), so if this package is required a lot, it's important for us to get it fixed soon. We'd appreciate help from the people who know this package best, but if you don't want to work on this now, let us know so we can try to work around it on our side.
https://src.fedoraproject.org/rpms/python-executing/c/4d0d056b76cafe39bbee532e66ffbde2260887a8?branch=rawhide