diff --git a/guppylang/decorator.py b/guppylang/decorator.py index 3dc19338..4595e004 100644 --- a/guppylang/decorator.py +++ b/guppylang/decorator.py @@ -4,7 +4,7 @@ from dataclasses import dataclass, field from pathlib import Path from types import ModuleType -from typing import Any, TypeVar, overload +from typing import Any, TypeVar, cast, overload from hugr import ops from hugr import tys as ht @@ -41,6 +41,7 @@ PyFunc, find_guppy_module_in_py_module, get_calling_frame, + sphinx_running, ) from guppylang.tys.subst import Inst from guppylang.tys.ty import NumericType @@ -429,7 +430,7 @@ def get_module( return module def compile_module(self, id: ModuleIdentifier | None = None) -> ModulePointer: - """Compiles the local module into a Hugr.""" + """Compiles the xlocal module into a Hugr.""" module = self.get_module(id) if not module: err = ( @@ -461,7 +462,56 @@ def registered_modules(self) -> KeysView[ModuleIdentifier]: return self._modules.keys() -guppy = _Guppy() +class _GuppyDummy: + """A dummy class with the same interface as `@guppy` that is used during sphinx + builds to mock the decorator. + """ + + def __call__(self, arg: PyFunc | GuppyModule) -> Any: + if isinstance(arg, GuppyModule): + return lambda f: f + return arg + + def init_module(self, *args: Any, **kwargs: Any) -> None: + pass + + def extend_type(self, *args: Any, **kwargs: Any) -> Any: + return lambda cls: cls + + def type(self, *args: Any, **kwargs: Any) -> Any: + return lambda cls: cls + + def struct(self, *args: Any, **kwargs: Any) -> Any: + return lambda cls: cls + + def type_var(self, *args: Any, **kwargs: Any) -> Any: + return lambda cls: cls + + def nat_var(self, *args: Any, **kwargs: Any) -> Any: + return lambda cls: cls + + def custom(self, *args: Any, **kwargs: Any) -> Any: + return lambda f: f + + def hugr_op(self, *args: Any, **kwargs: Any) -> Any: + return lambda f: f + + def declare(self, arg: PyFunc | GuppyModule) -> Any: + if isinstance(arg, GuppyModule): + return lambda f: f + return arg + + def constant(self, *args: Any, **kwargs: Any) -> Any: + return None + + def extern(self, *args: Any, **kwargs: Any) -> Any: + return None + + def load(self, *args: Any, **kwargs: Any) -> None: + pass + + +guppy = cast(_Guppy, _GuppyDummy()) if sphinx_running() else _Guppy() def _parse_expr_string(ty_str: str, parse_err: str) -> ast.expr: diff --git a/guppylang/module.py b/guppylang/module.py index 018698c6..822f4271 100644 --- a/guppylang/module.py +++ b/guppylang/module.py @@ -104,6 +104,11 @@ def load( Keyword args may be used to specify alias names for the imports. """ + # Note that we shouldn't evaluate imports during a sphinx build since the @guppy + # decorator is mocked in that case + if sphinx_running(): + return + modules: set[GuppyModule] = set() defs: dict[DefId, CheckedDef] = {} names: dict[str, DefId] = {} @@ -158,6 +163,11 @@ def load( def load_all(self, mod: GuppyModule | ModuleType) -> None: """Imports all public members of a module.""" + # Note that we shouldn't evaluate imports during a sphinx build since the @guppy + # decorator is mocked in that case + if sphinx_running(): + return + if isinstance(mod, GuppyModule): mod.check() self.load( @@ -422,3 +432,15 @@ def get_calling_frame() -> FrameType | None: return frame frame = frame.f_back return None + + +def sphinx_running() -> bool: + """Checks if this module was imported during a sphinx build.""" + # This is the most general solution available at the moment. + # See: https://github.com/sphinx-doc/sphinx/issues/9805 + try: + import sphinx # type: ignore[import-untyped, import-not-found, unused-ignore] + + return hasattr(sphinx, "application") + except ImportError: + return False