Source code for guppylang.decorator

import ast
import builtins
import inspect
from collections.abc import Callable
from types import FrameType
from typing import (
    TYPE_CHECKING,
    Any,
    NamedTuple,
    ParamSpec,
    TypedDict,
    TypeVar,
    cast,
    overload,
)

from guppylang_internals.ast_util import annotate_location
from guppylang_internals.definition.alias import RawTypeAliasDef
from guppylang_internals.definition.common import DefId
from guppylang_internals.definition.const import RawConstDef
from guppylang_internals.definition.custom import RawCustomFunctionDef
from guppylang_internals.definition.declaration import RawFunctionDecl
from guppylang_internals.definition.enum import RawEnumDef
from guppylang_internals.definition.extern import RawExternDef
from guppylang_internals.definition.function import RawFunctionDef
from guppylang_internals.definition.overloaded import OverloadedFunctionDef
from guppylang_internals.definition.parameter import (
    ConstVarDef,
    ParamDef,
    RawConstVarDef,
    TypeVarDef,
)
from guppylang_internals.definition.protocol import RawProtocolDef
from guppylang_internals.definition.pytket_circuits import (
    RawLoadPytketDef,
    RawPytketDef,
)
from guppylang_internals.definition.struct import RawStructDef
from guppylang_internals.definition.traced import RawTracedFunctionDef
from guppylang_internals.dummy_decorator import (
    _dummy_custom_decorator,
    _DummyGuppy,
    sphinx_running,
)
from guppylang_internals.engine import DEF_STORE
from guppylang_internals.metadata.common import FunctionMetadata
from guppylang_internals.metadata.expected_qubits import MetadataExpectedQubitsHint
from guppylang_internals.span import Loc, SourceMap, Span
from guppylang_internals.tracing.util import hide_trace
from guppylang_internals.tys.ty import (
    FunctionType,
    NoneType,
    NumericType,
    UnitaryFlags,
)
from hugr import val as hv
from typing_extensions import Unpack, dataclass_transform

from guppylang.defs import (
    GuppyDefinition,
    GuppyEnumDefinition,
    GuppyFunctionDefinition,
    GuppyTypeVarDefinition,
)
from guppylang.library import _get_link_name

K = TypeVar("K")
S = TypeVar("S")
T = TypeVar("T")
F = TypeVar("F", bound=Callable[..., Any])
P = ParamSpec("P")
Decorator = Callable[[S], T]

AnyRawFunctionDef = (
    RawFunctionDef,
    RawCustomFunctionDef,
    RawFunctionDecl,
    RawPytketDef,
    RawLoadPytketDef,
    OverloadedFunctionDef,
)

__all__ = (
    "GuppyKwargs",
    "custom_guppy_decorator",
    "expected_qubits",
    "guppy",
    "metadata",
)


[docs] class GuppyKwargs(TypedDict, total=False): """Typed dictionary specifying the optional keyword arguments for the `@guppy` decorator. """ unitary: bool controllable: bool daggerable: bool
[docs] class GuppyStructKwargs(TypedDict, total=False): """Typed dictionary specifying the optional keyword arguments for the `@guppy.struct` decorator. """ frozen: bool link_name: str
[docs] class GuppyEnumKwargs(TypedDict, total=False): """Typed dictionary specifying the optional keyword arguments for the `@guppy.enum` decorator. """ link_name: str
class _Guppy: """Class for the `@guppy` decorator.""" @overload def __call__( self, /, **kwargs: Unpack[GuppyKwargs] ) -> Decorator[Callable[P, T], GuppyFunctionDefinition[P, T]]: ... @overload def __call__(self, f: Callable[P, T], /) -> GuppyFunctionDefinition[P, T]: ... def __call__( self, *args: Any, **kwargs: Unpack[GuppyKwargs] ) -> ( GuppyFunctionDefinition[P, T] | Decorator[Callable[P, T], GuppyFunctionDefinition[P, T]] ): def decorator( f: Callable[P, T], kwargs: GuppyKwargs ) -> GuppyFunctionDefinition[P, T]: parsed = _parse_kwargs(kwargs) _add_generic_metadata(f, parsed.metadata) defn = RawFunctionDef( DefId.fresh(), f.__name__, None, f, unitary_flags=parsed.flags, metadata=parsed.metadata, link_name=_get_link_name(f), ) DEF_STORE.register_def(defn, get_calling_frame()) return GuppyFunctionDefinition(defn) return _with_optional_kwargs(decorator, args, kwargs) @overload def comptime( self, /, **kwargs: Unpack[GuppyKwargs] ) -> Decorator[Callable[P, T], GuppyFunctionDefinition[P, T]]: ... @overload def comptime(self, f: Callable[P, T], /) -> GuppyFunctionDefinition[P, T]: ... def comptime( self, *args: Any, **kwargs: Unpack[GuppyKwargs] ) -> ( GuppyFunctionDefinition[P, T] | Decorator[Callable[P, T], GuppyFunctionDefinition[P, T]] ): """Registers a function to be executed at compile-time during Guppy compilation, enabling the use of arbitrary Python features as long as they don't depend on runtime values. .. code-block:: python from guppylang import guppy from guppylang.std.builtins import array @guppy.comptime def print_arrays(arr1: array[str, 10], arr2: array[str, 10]) -> None: for s1, s2 in zip(arr1, arr2): print(f"({s1}, {s2})") """ def decorator( f: Callable[P, T], kwargs: GuppyKwargs ) -> GuppyFunctionDefinition[P, T]: parsed = _parse_kwargs(kwargs) _add_generic_metadata(f, parsed.metadata) defn = RawTracedFunctionDef( DefId.fresh(), f.__name__, None, f, unitary_flags=parsed.flags, metadata=parsed.metadata, ) DEF_STORE.register_def(defn, get_calling_frame()) return GuppyFunctionDefinition(defn) return _with_optional_kwargs(decorator, args, kwargs) @dataclass_transform() def struct( self, *args: Any, **kwargs: Unpack[GuppyStructKwargs] ) -> builtins.type[T]: """Registers a class as a Guppy struct. .. code-block:: python from guppylang import guppy @guppy.struct class MyStruct: field1: int field2: int @guppy def add_fields(self: "MyStruct") -> int: return self.field2 + self.field2 # Add optional parameters @guppy.struct(link_name="my_struct") class MyStruct2: field1: int field2: int """ def decorator( cls: builtins.type[T], kwargs: GuppyStructKwargs ) -> GuppyDefinition: defn = RawStructDef( DefId.fresh(), cls.__name__, None, cls, frozen=kwargs.pop("frozen", False), # Mutable by default link_name=_get_link_name(cls), ) frame = get_calling_frame() DEF_STORE.register_def(defn, frame) for val in cls.__dict__.values(): if isinstance(val, GuppyDefinition): DEF_STORE.register_type_member(defn.id, val.wrapped.name, val.id) # Prior to Python 3.13, the `__firstlineno__` attribute on classes is not # set. However, we need this information to precisely look up the source for # the class later. If it's not there, we can set it from the calling frame: if not hasattr(cls, "__firstlineno__"): cls.__firstlineno__ = frame.f_lineno # type: ignore[attr-defined] # We're pretending to return the class unchanged, but in fact we return # a `GuppyDefinition` that handles the comptime logic return GuppyDefinition(defn) return _with_optional_kwargs(decorator, args, kwargs) # type: ignore[return-value] @dataclass_transform() def enum(self, *args: Any, **kwargs: Unpack[GuppyEnumKwargs]) -> builtins.type[T]: """Registers a class as a Guppy enum. .. code-block:: python from guppylang import guppy @guppy.enum class MyEnum: Variant1 = {"a": int, "b": qubit} Variant2 = {"a": int} @guppy def method_on_enum(self: MyEnum) -> int: return 1 .. """ def decorator( cls: builtins.type[T], kwargs: GuppyEnumKwargs ) -> GuppyEnumDefinition: defn = RawEnumDef( DefId.fresh(), cls.__name__, None, cls, link_name=_get_link_name(cls), ) frame = get_calling_frame() DEF_STORE.register_def(defn, frame) for val in cls.__dict__.values(): if isinstance(val, GuppyDefinition): DEF_STORE.register_type_member(defn.id, val.wrapped.name, val.id) # Prior to Python 3.13, the `__firstlineno__` attribute on classes is not # set. However, we need this information to precisely look up the source for # the class later. If it's not there, we can set it from the calling frame: if not hasattr(cls, "__firstlineno__"): cls.__firstlineno__ = frame.f_lineno # type: ignore[attr-defined] # We're pretending to return the class unchanged, but in fact we return # a `GuppyDefinition` that handles the comptime logic return GuppyEnumDefinition(defn) return _with_optional_kwargs(decorator, args, kwargs) # type: ignore[return-value] @dataclass_transform() def protocol(self, cls: builtins.type[T]) -> builtins.type[T]: """Registers a class as a Guppy protocol. .. code-block:: python from guppylang import guppy @guppy.protocol class MyProtocol: @guppy.require def describe(self: Self) -> str: ... """ defn = RawProtocolDef(DefId.fresh(), cls.__name__, None, cls) frame = get_calling_frame() DEF_STORE.register_def(defn, frame) for val in cls.__dict__.values(): if isinstance(val, GuppyDefinition): DEF_STORE.register_type_member(defn.id, val.wrapped.name, val.id) # We need the `__firstlineno__` attribute to look up the source later. cls = _set_firstlineno(cls, frame) # We're pretending to return the class unchanged, but in fact we return # a `GuppyDefinition` that handles the comptime logic return GuppyDefinition(defn) # type: ignore[return-value] def require( self, *args: Any, **kwargs: Unpack[GuppyKwargs] ) -> ( GuppyFunctionDefinition[P, T] | Decorator[Callable[P, T], GuppyFunctionDefinition[P, T]] ): """Declares a required method for a protocol.""" def decorator( f: Callable[P, T], kwargs: GuppyKwargs ) -> GuppyFunctionDefinition[P, T]: parsed = _parse_kwargs(kwargs) _add_generic_metadata(f, parsed.metadata) defn = RawFunctionDecl( DefId.fresh(), f.__name__, None, f, unitary_flags=parsed.flags, link_name=_get_link_name(f), metadata=parsed.metadata, ) DEF_STORE.register_def(defn, get_calling_frame()) return GuppyFunctionDefinition(defn) return _with_optional_kwargs(decorator, args, kwargs) def type_var( self, name: str, copyable: bool = True, droppable: bool = True, ) -> TypeVar: """Creates a new type variable. .. code-block:: python from guppylang import guppy T = guppy.type_var("T") @guppy def identity(x: T) -> T: return x """ defn = TypeVarDef(DefId.fresh(), name, None, copyable, droppable) DEF_STORE.register_def(defn, get_calling_frame()) # We're pretending to return a `typing.TypeVar`, but in fact we return a special # `GuppyDefinition` that pretends to be a TypeVar at runtime return GuppyTypeVarDefinition(defn, TypeVar(name)) # type: ignore[return-value] def nat_var(self, name: str) -> TypeVar: """Creates a new nat variable.""" defn = ConstVarDef(DefId.fresh(), name, None, NumericType(NumericType.Kind.Nat)) DEF_STORE.register_def(defn, get_calling_frame()) # We're pretending to return a `typing.TypeVar`, but in fact we return a special # `GuppyDefinition` that pretends to be a TypeVar at runtime return GuppyTypeVarDefinition(defn, TypeVar(name)) # type: ignore[return-value] def const_var(self, name: str, ty: str) -> TypeVar: """Creates a new const type variable.""" type_ast = _parse_expr_string( ty, f"Not a valid Guppy type: `{ty}`", DEF_STORE.sources ) defn = RawConstVarDef(DefId.fresh(), name, None, type_ast) DEF_STORE.register_def(defn, get_calling_frame()) # We're pretending to return a `typing.TypeVar`, but in fact we return a special # `GuppyDefinition` that pretends to be a TypeVar at runtime return GuppyTypeVarDefinition(defn, TypeVar(name)) # type: ignore[return-value] def type_alias(self, name: str, ty: str, params: list[Any] | None = None) -> Any: """Creates a new type alias. .. code-block:: python from guppylang import guppy, array Row = guppy.type_alias("Row", "array[int, 4]") @guppy def sum_row(row: Row) -> int: return row[0] + row[1] + row[2] + row[3] Generic aliases are supported by passing a list of type variables as ``params``. The order determines how the alias is instantiated (e.g. ``Alias[int, bool]`` binds the first param to ``int`` and the second to ``bool``): .. code-block:: python T = guppy.type_var("T") U = guppy.type_var("U") Pair = guppy.type_alias("Pair", "tuple[T, U]", params=[T, U]) When ``params`` is omitted, free type variables are collected from the body in order of first appearance. """ type_ast = _parse_expr_string( ty, f"Not a valid Guppy type: `{ty}`", DEF_STORE.sources ) explicit_params = _params_from_list(params) if params is not None else None defn = RawTypeAliasDef( DefId.fresh(), name, type_ast, type_ast, explicit_params, ) DEF_STORE.register_def(defn, get_calling_frame()) return GuppyDefinition(defn) @overload def declare( self, /, **kwargs: Unpack[GuppyKwargs] ) -> Decorator[Callable[P, T], GuppyFunctionDefinition[P, T]]: ... @overload def declare(self, f: Callable[P, T], /) -> GuppyFunctionDefinition[P, T]: ... def declare( self, *args: Any, **kwargs: Unpack[GuppyKwargs] ) -> ( GuppyFunctionDefinition[P, T] | Decorator[Callable[P, T], GuppyFunctionDefinition[P, T]] ): """Declares a Guppy function without defining it.""" def decorator( f: Callable[P, T], kwargs: GuppyKwargs ) -> GuppyFunctionDefinition[P, T]: parsed = _parse_kwargs(kwargs) _add_generic_metadata(f, parsed.metadata) defn = RawFunctionDecl( DefId.fresh(), f.__name__, None, f, unitary_flags=parsed.flags, link_name=_get_link_name(f), metadata=parsed.metadata, ) DEF_STORE.register_def(defn, get_calling_frame()) return GuppyFunctionDefinition(defn) return _with_optional_kwargs(decorator, args, kwargs) def overload( self, *funcs: Any ) -> Callable[[Callable[P, T]], GuppyFunctionDefinition[P, T]]: """Collects multiple function definitions into one overloaded function. Consider the following example: .. code-block:: python @guppy.declare def variant1(x: int, y: int) -> int: ... @guppy.declare def variant2(x: float) -> int: ... @guppy.overload(variant1, variant2) def combined(): ... Now, `combined` may be called with either one `float` or two `int` arguments, delegating to the implementation with the matching signature: .. code-block:: python combined(4.2) # Calls `variant1` combined(42, 43) # Calls `variant2` Note that the compiler will pick the *first* implementation with matching signature and ignore all following ones, even if they would also match. For example, if we added a third variant .. code-block:: python @guppy.declare def variant3(x: int) -> int: ... @guppy.overload(variant1, variant2, variant3) def combined_new(): ... then a call `combined_new(42)` will still select the `variant1` implementation `42` is a valid argument for `variant1` and `variant1` comes before `variant3` in the `@guppy.overload` annotation. """ funcs = list(funcs) if len(funcs) < 2: raise ValueError("Overload requires at least two functions") func_ids = [] for func in funcs: if not isinstance(func, GuppyDefinition): raise TypeError(f"Not a Guppy definition: {func}") if not isinstance(func.wrapped, AnyRawFunctionDef): raise TypeError( f"Not a Guppy function definition: {func.wrapped.description} " f"`{func.wrapped.name}`" ) func_ids.append(func.id) def decorator(f: Callable[P, T]) -> GuppyFunctionDefinition[P, T]: dummy_sig = FunctionType([], NoneType()) defn = OverloadedFunctionDef( DefId.fresh(), f.__name__, None, dummy_sig, func_ids ) DEF_STORE.register_def(defn, get_calling_frame()) return GuppyFunctionDefinition(defn) return decorator def constant(self, name: str, ty: str, value: hv.Value) -> T: # type: ignore[type-var] # Since we're returning a free type variable """Adds a constant to a module, backed by a `hugr.val.Value`.""" type_ast = _parse_expr_string( ty, f"Not a valid Guppy type: `{ty}`", DEF_STORE.sources ) defn = RawConstDef(DefId.fresh(), name, None, type_ast, value) DEF_STORE.register_def(defn, get_calling_frame()) # We're pretending to return a free type variable, but in fact we return # a `GuppyDefinition` that handles the comptime logic return GuppyDefinition(defn) # type: ignore[return-value] def _extern( self, name: str, ty: str, symbol: str | None = None, constant: bool = True, ) -> T: # type: ignore[type-var] # Since we're returning a free type variable """Adds an extern symbol to a module.""" type_ast = _parse_expr_string( ty, f"Not a valid Guppy type: `{ty}`", DEF_STORE.sources ) defn = RawExternDef( DefId.fresh(), name, None, symbol or name, constant, type_ast ) DEF_STORE.register_def(defn, get_calling_frame()) # We're pretending to return a free type variable, but in fact we return # a `GuppyDefinition` that handles the comptime logic return GuppyDefinition(defn) # type: ignore[return-value] def pytket( self, input_circuit: Any ) -> Callable[[Callable[P, T]], GuppyFunctionDefinition[P, T]]: """Backs a function declaration by the given pytket circuit. The declaration signature needs to match the circuit definition in terms of number of qubit inputs and measurement outputs. There is no linearity checking inside pytket circuit functions. Any measurements inside the circuit get returned as bools, but the qubits do not get consumed and the pytket circuit function does not require ownership. You should either make sure you discard all qubits you know are measured during the circuit, or avoid measurements in the circuit and measure in Guppy afterwards. Note this decorator doesn't support passing inputs as arrays (use `load_pytket` instead). .. code-block:: python from pytket.circuit import Circuit from guppylang import guppy circ = Circuit(1) circ.H(0) circ.measure_all() @guppy.pytket(circ) def guppy_circ(q: qubit) -> bool: ... @guppy def foo(q: qubit) -> bool: return guppy_circ(q)""" from pytket.circuit import Circuit # Decoupled import if not isinstance(input_circuit, Circuit): err_msg = "Only pytket circuits can be passed to guppy.pytket" raise TypeError(err_msg) from None def func(f: Callable[P, T]) -> GuppyFunctionDefinition[P, T]: defn = RawPytketDef(DefId.fresh(), f.__name__, None, f, input_circuit) DEF_STORE.register_def(defn, get_calling_frame()) return GuppyFunctionDefinition(defn) return func def load_pytket( self, name: str, input_circuit: Any, *, use_arrays: bool = True, ) -> GuppyFunctionDefinition[..., Any]: """Load a pytket :py:class:`~pytket.circuit.Circuit` as a Guppy function. By default, each qubit register is represented by an array input (and each bit register as an array output), with the order being determined lexicographically. The default registers are 'q' and 'c' respectively. You can disable array usage and pass individual qubits by passing `use_arrays=False`. .. code-block:: python from pytket import Circuit from guppylang import guppy circ = Circuit(2) reg = circ.add_q_register("extra_reg", 3) circ.measure_register(reg, "extra_bits") guppy_circ = guppy.load_pytket("guppy_circ", circ) @guppy def foo(default_reg: array[qubit, 2], extra_reg: array[qubit, 3]) -> array[bool, 3]: # Note that the default_reg name is 'q' so it has to come after 'e...' # lexicographically. return guppy_circ(extra_reg, default_reg) Any symbolic parameters in the circuit need to be passed as a lexicographically sorted array (if arrays are enabled, else individually in that order) as values of type `angle`. The function name is determined by the function variable you bind the ` load_pytket`method call to, however the name string passed to the method should match this variable for error reporting purposes. There is no linearity checking inside pytket circuit functions. Any measurements inside the circuit get returned as bools, but the qubits do not get consumed and the pytket circuit function does not require ownership. You should either make sure you discard all qubits you know are measured during the circuit, or avoid measurements in the circuit and measure in Guppy afterwards. """ from pytket.circuit import Circuit # Decoupled import if not isinstance(input_circuit, Circuit): err_msg = "Only pytket circuits can be passed to guppy.load_pytket" raise TypeError(err_msg) from None span = _find_load_call(DEF_STORE.sources) defn = RawLoadPytketDef( DefId.fresh(), name, None, span, input_circuit, use_arrays ) DEF_STORE.register_def(defn, get_calling_frame()) return GuppyFunctionDefinition(defn) def metadata(key: str, value: Any) -> Any: """Decorator to attach metadata to a Guppy function. It must be placed below the @guppy decorator. .. code-block:: python from guppylang import guppy from guppylang.decorator import metadata @guppy.declare @metadata("key1", "value1") @metadata("key2", "value2") def main() -> None: pass main.compile() During compilation, the node corresponding to the `main` function will have the following metadata attached: {key1: value1, key2: value2}. """ def decorator(f: Any) -> Any: if isinstance(f, GuppyDefinition): raise TypeError( "@metadata must be placed below the @guppy decorator, not above it" ) f.__guppy_metadata__ = { **getattr(f, "__guppy_metadata__", {}), key: value, } return f return decorator
[docs] def expected_qubits(num: int) -> Any: """Decorator to attach an expected number of qubits to a Guppy function. It must be placed below the @guppy decorator. .. code-block:: python from guppylang import guppy from guppylang.decorator import expected_qubit @guppy.declare @expected_qubits(2) def main() -> None: pass main.compile() """ return metadata(MetadataExpectedQubitsHint.KEY, num)
def _parse_expr_string(ty_str: str, parse_err: str, sources: SourceMap) -> ast.expr: """Helper function to parse expressions that are provided as strings. Tries to infer the source location were the given string was defined by inspecting the call stack. """ try: expr_ast = ast.parse(ty_str, mode="eval").body except SyntaxError: raise SyntaxError(parse_err) from None # Try to annotate the type AST with source information. This requires us to # inspect the stack frame of the caller if caller_frame := get_calling_frame(): info = inspect.getframeinfo(caller_frame) if caller_module := inspect.getmodule(caller_frame): sources.add_file(info.filename) source_lines, _ = inspect.getsourcelines(caller_module) source = "".join(source_lines) annotate_location(expr_ast, source, info.filename, 1) # Modify the AST so that all sub-nodes span the entire line. We # can't give a better location since we don't know the column # offset of the `ty` argument for node in [expr_ast, *ast.walk(expr_ast)]: node.lineno = node.end_lineno = info.lineno # type: ignore[attr-defined] node.col_offset = 0 # type: ignore[attr-defined] node.end_col_offset = len(source_lines[info.lineno - 1]) - 1 # type: ignore[attr-defined] return expr_ast def _find_load_call(sources: SourceMap) -> Span | None: """Helper function to find location where pytket circuit was loaded. Tries to define a source code span by inspecting the call stack. """ # Go back as first frame outside of compiler modules is 'pretty_errors_wrapped'. if load_frame := get_calling_frame(): info = inspect.getframeinfo(load_frame) filename = info.filename lineno = info.lineno sources.add_file(filename) # If we don't support python <= 3.10, this can be done better with # info.positions which gives you exact offsets. # For now over approximate and make the span cover the entire line. if load_module := inspect.getmodule(load_frame): source_lines, _ = inspect.getsourcelines(load_module) max_offset = len(source_lines[lineno - 1]) - 1 start = Loc(filename, lineno, 0) end = Loc(filename, lineno, max_offset) return Span(start, end) return None def _set_firstlineno(cls: builtins.type[T], frame: FrameType) -> builtins.type[T]: """Helper function to set the `__firstlineno__` attribute on a class if it is not already there. Prior to Python 3.13, the `__firstlineno__` attribute on classes is not set. However, we need this information to precisely look up the source for the class later. If it's not there, we can set it from the calling frame. """ if not hasattr(cls, "__firstlineno__"): cls.__firstlineno__ = frame.f_lineno # type: ignore[attr-defined] return cls
[docs] def custom_guppy_decorator(f: F) -> F: """Decorator to mark user-defined decorators that wrap builtin `guppy` decorators. Example: .. code-block:: python @custom_guppy_decorator def my_guppy(f): # Some custom logic here ... return guppy(f) @my_guppy def main() -> int: ... If the `custom_guppy_decorator` were missing, then the `@my_guppy` annotation would not produce a valid guppy definition. """ f.__code__ = f.__code__.replace(co_name="__custom_guppy_decorator__") return f
[docs] def get_calling_frame() -> FrameType: """Finds the first frame that called this function outside the compiler modules.""" frame = inspect.currentframe() while frame: # Skip frame if we're inside a user-defined decorator that wraps the `guppy` # decorator. Those are functions with a special `__code__.co_name` of # "__custom_guppy_decorator__". if frame.f_code.co_name == "__custom_guppy_decorator__": frame = frame.f_back continue module = inspect.getmodule(frame) if module is None: return frame if module.__file__ != __file__: return frame frame = frame.f_back raise RuntimeError("Couldn't obtain stack frame for definition")
def _with_optional_kwargs( decorator: Callable[[S, K], T], args: tuple[Any, ...], kwargs: K ) -> T | Callable[[S], T]: """Helper function to define decorators that may be used directly (`@decorator`) but also with optional keyword arguments (`@decorator(kwarg=value)`). """ match args: case (): return lambda f: decorator(f, kwargs) case (f,): if kwargs: err = "Unexpected keyword arguments" raise TypeError(err) return decorator(f, kwargs) case _: err = "Unexpected positional arguments" raise TypeError(err)
[docs] class ParsedGuppyKwargs(NamedTuple): flags: UnitaryFlags metadata: FunctionMetadata
@hide_trace def _parse_kwargs(kwargs: GuppyKwargs) -> ParsedGuppyKwargs: """Parses the kwargs dict specified in the `@guppy` decorator into `UnitaryFlags` and other metadata that will be passed onto the compiled function as is. """ metadata = FunctionMetadata() flags = UnitaryFlags.NoFlags if kwargs.pop("unitary", False): flags |= UnitaryFlags.Unitary if kwargs.pop("controllable", False): flags |= UnitaryFlags.Control if kwargs.pop("daggerable", False): flags |= UnitaryFlags.Dagger metadata.set_unitary_flags(flags.value) if "link_name" in kwargs: raise TypeError( "`link_name` keyword argument has been removed from the `@guppy` decorator," " use the `@link_name` decorator from `guppylang.library` instead." ) if remaining := next(iter(kwargs), None): err = f"Unknown keyword argument: `{remaining}`" raise TypeError(err) return ParsedGuppyKwargs( flags=flags, metadata=metadata, ) @hide_trace def _add_generic_metadata(f: Callable[..., Any], metadata: FunctionMetadata) -> None: """Adds the given metadata to the function's `__guppy_metadata__` attribute, which is used by the compiler to store metadata for Guppy functions. """ custom_metadata = getattr(f, "__guppy_metadata__", {}) assert isinstance(custom_metadata, dict) for key, value in custom_metadata.items(): if key == MetadataExpectedQubitsHint.KEY: metadata.set_expected_qubits(value) else: metadata.set_generic_metadata(key, value) def _params_from_list(params: list[Any]) -> list[ParamDef]: """Validate a list of Guppy type-variable definitions for use as alias params. Each entry must be a type variable created with :func:`guppy.type_var`, :func:`guppy.nat_var`, or :func:`guppy.const_var`. The underlying :class:`~guppylang_internals.definition.parameter.ParamDef`\\ s are returned in order; they are converted to :class:`~guppylang_internals.tys.param.Parameter`\\ s later (in :meth:`ParsedTypeAliasDef.check`) where the globals needed to resolve ``const_var`` types are available. """ result: list[ParamDef] = [] for p in params: defn = p.wrapped if isinstance(p, GuppyDefinition) else None if not isinstance(defn, ParamDef): raise TypeError( "type_alias params must be type variables created with " f"guppy.type_var(), guppy.nat_var(), or guppy.const_var(), got {p!r}" ) result.append(defn) return result guppy = cast("_Guppy", _DummyGuppy()) if sphinx_running() else _Guppy() if not TYPE_CHECKING and sphinx_running(): metadata = _dummy_custom_decorator()