Skip to content

Fix executor return-type detection under from __future__ import annotations (closes #3061)#3062

Open
ayush268 wants to merge 4 commits into
unitaryfoundation:mainfrom
ayush268:fix/executor-pep563-annotations
Open

Fix executor return-type detection under from __future__ import annotations (closes #3061)#3062
ayush268 wants to merge 4 commits into
unitaryfoundation:mainfrom
ayush268:fix/executor-pep563-annotations

Conversation

@ayush268

@ayush268 ayush268 commented Jun 19, 2026

Copy link
Copy Markdown

Fixes #3061

Executor read the executor's return type via
inspect.getfullargspec(executor).annotations, which returns the raw __annotations__ dict. When the executor is defined in a module using from __future__ import annotations, annotations are strings, so the return type became the string "float" instead of the float type. The membership checks in evaluate() then matched nothing and raised ValueError: Could not parse executed results from executor with type float, even though the executor returned a valid float. This also affected measurement/density-matrix executors and any technique that builds an Executor internally (PEC, ZNE, ...).

Resolve the annotation with typing.get_type_hints(), which evaluates stringized (PEP 563) annotations back into concrete types, with a fallback to the raw annotations when a hint cannot be resolved.

Add regression tests in a module that uses
from __future__ import annotations.

Description

Executor.__init__ reads the executor's return type via inspect.getfullargspec(executor).annotations, which returns the raw __annotations__ dict. When the executor is defined in a module using from __future__ import annotations (PEP 563), all annotations are strings, so _executor_return_type becomes the string "float" instead of the float type. The membership checks in evaluate() (... in FloatLike, etc.) compare against concrete type objects, match nothing, and the method raises:

ValueError: Could not parse executed results from executor with type float.

…even though the executor returns a valid float. This also affects measurement/density-matrix executors and any technique that builds an Executor internally (PEC, ZNE, …).

Reproducer

from __future__ import annotations  # the only thing that matters

import cirq
from mitiq import Executor

def executor(circuit) -> float:
    return 1.0

q = cirq.LineQubit(0)
circuit = cirq.Circuit(cirq.X(q))
print(Executor(executor).evaluate(circuit))  # raises ValueError

Fix

Resolve the annotation with typing.get_type_hints(), which evaluates stringized annotations back into concrete types using the executor's __globals__, with a fallback to the raw annotations if a hint can't be resolved (e.g. an unresolvable forward reference).

Tests

Adds mitiq/executor/tests/test_executor_pep563.py with regression tests in a module that uses from __future__ import annotations. They fail on main and pass with this change.


License

  • I license this contribution under the terms of the GNU GPL, version 3 and grant Unitary Foundation the right to provide additional permissions as described in section 7 of the GNU GPL, version 3.

Before opening the PR, please ensure you have completed the following where appropriate.

@ayush268 ayush268 changed the title Fix executor return-type detection under from __future__ import annotations (closes #3061 Fix executor return-type detection under from __future__ import annotations (closes #3061) Jun 19, 2026
Comment thread mitiq/executor/executor.py Outdated
# an unresolvable forward reference).
try:
executor_annotation = typing.get_type_hints(executor)
except Exception:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exception gets raised by line 84?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added detailed comment

(e.g. ``"float"`` -> ``float``) so that float / measurement /
density-matrix executors are detected correctly.

See https://github.com/unitaryfoundation/mitiq for the originating issue.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add the issue # or remove this line.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

`Executor` read the executor's return type via
`inspect.getfullargspec(executor).annotations`, which returns the raw
`__annotations__` dict. When the executor is defined in a module using
`from __future__ import annotations`, annotations are strings, so the
return type became the string `"float"` instead of the `float` type.
The membership checks in `evaluate()` then matched nothing and raised
`ValueError: Could not parse executed results from executor with type
float`, even though the executor returned a valid float. This also
affected measurement/density-matrix executors and any technique that
builds an `Executor` internally (PEC, ZNE, ...).

Resolve the annotation with `typing.get_type_hints()`, which evaluates
stringized (PEP 563) annotations back into concrete types, with a
fallback to the raw annotations when a hint cannot be resolved.

Add regression tests in a module that uses
`from __future__ import annotations`.
@ayush268 ayush268 force-pushed the fix/executor-pep563-annotations branch from 851de8f to 3e38cbd Compare June 20, 2026 05:12
@ayush268

Copy link
Copy Markdown
Author

The exception can be a NameError or TypeError, updated with comments in the code as well.

Example code to reproduce:

from __future__ import annotations
from typing import TYPE_CHECKING, get_type_hints

# ---- Case 1: NameError ----
# A name imported ONLY for type-checkers does not exist at runtime, so
# get_type_hints() cannot evaluate the (stringized) annotation.
if TYPE_CHECKING:
    from some_heavy_pkg import CircuitLike  # never imported at runtime

def executor_typecheck_only(circuit: CircuitLike) -> float:
    return 1.0

# ---- Case 2: TypeError ----
# get_type_hints() expects a module/class/method/function. A plain callable
# *object* (an instance with __call__) is not a valid argument.
class CallableExecutor:
    def __call__(self, circuit) -> float:
        return 1.0

callable_obj = CallableExecutor()

get_type_hints(executor_typecheck_only)  # raises NameError
get_type_hints(callable_obj)             # raises TypeError

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Executor return-type detection breaks under from __future__ import annotations (PEP 563)

2 participants