Bug 2447898 - python-executing fails to build with Python 3.15: executing._exceptions.VerifierFailure: ast.<something> is not created from LOAD_FAST
Summary: python-executing fails to build with Python 3.15: executing._exceptions.Verif...
Keywords:
Status: CLOSED RAWHIDE
Alias: None
Product: Fedora
Classification: Fedora
Component: python-executing
Version: rawhide
Hardware: Unspecified
OS: Unspecified
unspecified
unspecified
Target Milestone: ---
Assignee: Roman Inflianskas
QA Contact:
URL:
Whiteboard:
Depends On:
Blocks: PYTHON3.15
TreeView+ depends on / blocked
 
Reported: 2026-03-16 09:18 UTC by Karolina Surma
Modified: 2026-03-19 15:14 UTC (History)
8 users (show)

Fixed In Version:
Clone Of:
Environment:
Last Closed: 2026-03-19 15:14:53 UTC
Type: Bug
Embargoed:


Attachments (Terms of Use)

Description Karolina Surma 2026-03-16 09:18:02 UTC
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.


Note You need to log in before you can comment on or make changes to this bug.