From af7a6561789271ac4d443d6f5279b56b74e7bdc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 4 Mar 2026 14:33:59 +0000 Subject: [PATCH 01/19] Sync lock file --- uv.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index 8e554528f..b80770105 100644 --- a/uv.lock +++ b/uv.lock @@ -977,7 +977,7 @@ requires-dist = [ { name = "tket", specifier = ">=0.12.7" }, { name = "tket-exts", specifier = "~=0.12.0" }, { name = "typing-extensions", specifier = ">=4.9.0,<5" }, - { name = "wasmtime", specifier = ">=38.0,<41.1" }, + { name = "wasmtime", specifier = ">=38.0,<42.1" }, ] provides-extras = ["pytket"] From 319877c2e8b48b6aa652fb7597c542f2f2f863a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 4 Mar 2026 14:34:22 +0000 Subject: [PATCH 02/19] Begin library docs --- docs/.gitignore | 1 + docs/build.sh | 1 + docs/library-docs/__init__.py | 0 docs/library-docs/conf.py | 28 ++++++++++++++++++++++++++++ docs/library-docs/index.rst | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 64 insertions(+) create mode 100644 docs/.gitignore create mode 100644 docs/library-docs/__init__.py create mode 100644 docs/library-docs/conf.py create mode 100644 docs/library-docs/index.rst diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000..378eac25d --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +build diff --git a/docs/build.sh b/docs/build.sh index 0cedc7167..f3fc90baa 100755 --- a/docs/build.sh +++ b/docs/build.sh @@ -5,3 +5,4 @@ mkdir build touch build/.nojekyll # Disable jekyll to keep files starting with underscores uv run --group docs sphinx-build -b html ./api-docs ./build/api-docs +uv run --group docs sphinx-build -b html ./library-docs ./build/library-docs diff --git a/docs/library-docs/__init__.py b/docs/library-docs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs/library-docs/conf.py b/docs/library-docs/conf.py new file mode 100644 index 000000000..d901b4fbd --- /dev/null +++ b/docs/library-docs/conf.py @@ -0,0 +1,28 @@ +# Configuration file for the Sphinx documentation builder. +# See https://www.sphinx-doc.org/en/master/usage/configuration.html + +project = "Guppy Libraries" +copyright = "2026, Quantinuum" +author = "Quantinuum" + +extensions = [ + "sphinx.ext.napoleon", + "sphinx.ext.autodoc", + "sphinx.ext.coverage", + "sphinx.ext.viewcode", + "sphinx.ext.intersphinx", +] + +html_theme = "furo" + +html_title = "Guppy library docs" + +html_theme_options = {} + +html_static_path = [] + +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +intersphinx_mapping = { + "python": ("https://docs.python.org/3/", None), +} diff --git a/docs/library-docs/index.rst b/docs/library-docs/index.rst new file mode 100644 index 000000000..a50311aaa --- /dev/null +++ b/docs/library-docs/index.rst @@ -0,0 +1,34 @@ +Guppy Libraries Documentation +############################# + +An introductory tale into how Guppy libraries (are supposed to) work, how to write them, and how to use them. + +.. note:: + This page concerns Guppy libraries usage for independently compiling a set of functions to a Hugr package, which can + be independently distributed and e.g. cached for optimization purposes. If you are just looking to open source your + Guppy code, the standard packaging mechanisms of Python suffice, and you can ignore this page. + +.. + TODO: Mark parts that are in progress as such! + +- Foreword +- Defining a Guppy library + + - Simple functions as interface + - What is supported, what is not + - ``guppy.library`` call and its arguments + - Defining headers to program against + +- Compiling the library, and what to do with the Hugr package + +- User Program + + - Programming and compiling against the headers + - Oh no, its not a runnable Hugr yet (but still a valid one) + +- Providing the library Hugr package to the user program executor + + - This is the most open / underspecified part + - Should be able to do this manually via ``hugr-py`` + - But also via simply passing more things to selene + - Mention auto-discovery functions From 6e4f9c3211477875af405f71d2c2e5c7e2d98a21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 4 Mar 2026 16:19:04 +0000 Subject: [PATCH 03/19] Moar docs --- docs/library-docs/conf.py | 2 + docs/library-docs/index.md | 18 ++++ docs/library-docs/index.rst | 34 ------- docs/library-docs/overview.md | 67 +++++++++++++ docs/library-docs/proposals.md | 9 ++ docs/library-docs/proposals/collection.md | 4 + docs/library-docs/proposals/generic.md | 27 ++++++ docs/library-docs/proposals/stubs.md | 7 ++ pyproject.toml | 7 +- uv.lock | 109 ++++++++++++++++++++++ 10 files changed, 249 insertions(+), 35 deletions(-) create mode 100644 docs/library-docs/index.md delete mode 100644 docs/library-docs/index.rst create mode 100644 docs/library-docs/overview.md create mode 100644 docs/library-docs/proposals.md create mode 100644 docs/library-docs/proposals/collection.md create mode 100644 docs/library-docs/proposals/generic.md create mode 100644 docs/library-docs/proposals/stubs.md diff --git a/docs/library-docs/conf.py b/docs/library-docs/conf.py index d901b4fbd..c1e9bb4c6 100644 --- a/docs/library-docs/conf.py +++ b/docs/library-docs/conf.py @@ -11,6 +11,8 @@ "sphinx.ext.coverage", "sphinx.ext.viewcode", "sphinx.ext.intersphinx", + "sphinx_copybutton", + "myst_parser", ] html_theme = "furo" diff --git a/docs/library-docs/index.md b/docs/library-docs/index.md new file mode 100644 index 000000000..276a0cbac --- /dev/null +++ b/docs/library-docs/index.md @@ -0,0 +1,18 @@ +# Guppy Libraries Documentation + +An introductory tale into how Guppy libraries (are supposed to) work, how to write them, and how to use them. + +```{note} +This docs collection concerns Guppy libraries usage for independently compiling a set of functions to a Hugr package, +which can be independently distributed and e.g. cached for optimization purposes. If you are just looking to open +source your Guppy code, the standard packaging mechanisms of Python suffice, and you can ignore this page. +``` + +- Foreword + +```{toctree} +:maxdepth: 2 + +overview +proposals +``` diff --git a/docs/library-docs/index.rst b/docs/library-docs/index.rst deleted file mode 100644 index a50311aaa..000000000 --- a/docs/library-docs/index.rst +++ /dev/null @@ -1,34 +0,0 @@ -Guppy Libraries Documentation -############################# - -An introductory tale into how Guppy libraries (are supposed to) work, how to write them, and how to use them. - -.. note:: - This page concerns Guppy libraries usage for independently compiling a set of functions to a Hugr package, which can - be independently distributed and e.g. cached for optimization purposes. If you are just looking to open source your - Guppy code, the standard packaging mechanisms of Python suffice, and you can ignore this page. - -.. - TODO: Mark parts that are in progress as such! - -- Foreword -- Defining a Guppy library - - - Simple functions as interface - - What is supported, what is not - - ``guppy.library`` call and its arguments - - Defining headers to program against - -- Compiling the library, and what to do with the Hugr package - -- User Program - - - Programming and compiling against the headers - - Oh no, its not a runnable Hugr yet (but still a valid one) - -- Providing the library Hugr package to the user program executor - - - This is the most open / underspecified part - - Should be able to do this manually via ``hugr-py`` - - But also via simply passing more things to selene - - Mention auto-discovery functions diff --git a/docs/library-docs/overview.md b/docs/library-docs/overview.md new file mode 100644 index 000000000..524f87cbd --- /dev/null +++ b/docs/library-docs/overview.md @@ -0,0 +1,67 @@ +# Overview + +## Defining a library + +A Guppy library is, at its simplest, a collection of functions that are compiled together into a Hugr package. +In contrast to the usual executable Hugr packages (like those produced from compiling single entrypoint functions), such +library Hugr packages feature (usually multiple) *public* functions, possibly with arguments and non-``None`` return +types. + +A developer may define a bunch of functions beyond this collection, for example for internally sharing functionality +between the public functions. Furthermore, developers may want to create different libraries / Hugr packages from a +single codebase, requiring to export different public functions in each such library. +In Guppy, a library is defined by calling the ``guppy.library(...)`` function, passing the functions to be exported as +public. +Any functions reachable via those functions are still included in the Hugr package, but with private visibility. + +For example, a library may be defined as follows: + +```python +from guppylang import guppy + + +@guppy +def my_func() -> None: + pass + + +@guppy +def another_func(x: int) -> None: + pass + + +@guppy +def a_third_func(x: int) -> int: + return x + 1 + + +# Creates the library object, but does not compile it +library = guppy.library( + my_func, + another_func, + a_third_func, +) +``` + +- Defining stubs to program against + +Currently, a library does not support the following: + +- Exporting generic functions of any kind (see [the proposal](proposals/generic.md)) +- Automatically collecting functions to be included in a library (see [the proposal](proposals/collection.md)) +- Automatically generating stubs for a library (see [the proposal](proposals/stubs.md)) + +## Compiling a library + +- Compiling the library, and what to do with the Hugr package + +## Using a library + +- Programming and compiling against the headers +- Oh no, its not a runnable Hugr yet (but still a valid one) +- Providing the library Hugr package to the user program executor + + - This is the most open / underspecified part + - Should be able to do this manually via ``hugr-py`` + - But also via simply passing more things to selene + - Mention auto-discovery functions diff --git a/docs/library-docs/proposals.md b/docs/library-docs/proposals.md new file mode 100644 index 000000000..87ab697c0 --- /dev/null +++ b/docs/library-docs/proposals.md @@ -0,0 +1,9 @@ +# Proposals + +A collection of proposals for QOL features related to Guppy libraries. + +```{toctree} +:glob: + +proposals/* +``` diff --git a/docs/library-docs/proposals/collection.md b/docs/library-docs/proposals/collection.md new file mode 100644 index 000000000..b8a883ff6 --- /dev/null +++ b/docs/library-docs/proposals/collection.md @@ -0,0 +1,4 @@ +# (Proposal) More ways to define a library / auto-collection + +- Mention auto-collection of functions, e.g. via ``@guppy.export(...)`` decorators +- Mention collecting these with different keys diff --git a/docs/library-docs/proposals/generic.md b/docs/library-docs/proposals/generic.md new file mode 100644 index 000000000..aa2eb92b0 --- /dev/null +++ b/docs/library-docs/proposals/generic.md @@ -0,0 +1,27 @@ +# (Proposal) Exporting generic functions + +- Need to restrict / make finite the monomorphisations to be included in the library +- For private functions, this is easy: Just include all the variants that are used +- For public functions, usage is not yet clear (since they may be called arbitrarily) +- Thus, developer needs to specify which monomorphisations to *at least* include in the library, for example via + +```python +from guppylang import guppy + +T = guppy.type_var("T") + + +# Choice 1, includes at least the monomorphisations for int and float, and any privately required ones +@guppy.instances([{T: int}, {T: float}, ...]) # may be unpacked, as QOL for manual specification +def my_generic_func(x: T) -> T: ... + + +# Choice 2, different syntax, same effect +@guppy(instances=[{T: int}, {T: float}, ...]) # may be unpacked, as QOL for manual specification +def my_generic_func(x: T) -> T: ... +``` + +## Optional QOL features + +- Allow to easily specify a range of types for each type variable, and form their product + - This could perhaps be done via `itertools.product` or similar, but this has to be investigated diff --git a/docs/library-docs/proposals/stubs.md b/docs/library-docs/proposals/stubs.md new file mode 100644 index 000000000..ee39c0a93 --- /dev/null +++ b/docs/library-docs/proposals/stubs.md @@ -0,0 +1,7 @@ +# (Proposal) Generating stubs automatically + +- Show how to generate stubs +- Dealing with structs +- Dealing with local types mentioned in signatures +- Limitations of automatic generation +- Validate stubs with new functionality in guppylang diff --git a/pyproject.toml b/pyproject.toml index 2e8556c11..da024b7ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,10 @@ [dependency-groups] -docs = ["furo>=2024.8.6", "sphinx >=7.2.6,<9"] +docs = [ + "furo>=2024.8.6", + "myst-parser>=4.0.1", + "sphinx >=7.2.6,<9", + "sphinx-copybutton>=0.5.2", +] dev = [ { include-group = "lint" }, { include-group = "test" }, diff --git a/uv.lock b/uv.lock index b80770105..75e098184 100644 --- a/uv.lock +++ b/uv.lock @@ -33,7 +33,9 @@ dev = [ ] docs = [ { name = "furo", specifier = ">=2024.8.6" }, + { name = "myst-parser", specifier = ">=4.0.1" }, { name = "sphinx", specifier = ">=7.2.6,<9" }, + { name = "sphinx-copybutton", specifier = ">=0.5.2" }, ] examples = [ { name = "matplotlib", specifier = ">=3.9.2" }, @@ -1594,6 +1596,37 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8b/6d/bd967a5d1738665445e8bed1dd89f61c56eceb6a128b2f56674fef0504d6/lief-0.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:c2514f8d5c9061af83f737d2d67c3d36b55453fa74a3911bb9631ca86efcbb9c", size = 3637355, upload-time = "2026-01-24T16:02:33.18Z" }, ] +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "mdurl", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.11' and python_full_version < '3.14'", +] +dependencies = [ + { name = "mdurl", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + [[package]] name = "markupsafe" version = "3.0.3" @@ -1766,6 +1799,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, ] +[[package]] +name = "mdit-py-plugins" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "markdown-it-py", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + [[package]] name = "miette-py" source = { editable = "miette-py" } @@ -1846,6 +1901,47 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] +[[package]] +name = "myst-parser" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "docutils", marker = "python_full_version < '3.11'" }, + { name = "jinja2", marker = "python_full_version < '3.11'" }, + { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "mdit-py-plugins", marker = "python_full_version < '3.11'" }, + { name = "pyyaml", marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/a5/9626ba4f73555b3735ad86247a8077d4603aa8628537687c839ab08bfe44/myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4", size = 93985, upload-time = "2025-02-12T10:53:03.833Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/df/76d0321c3797b54b60fef9ec3bd6f4cfd124b9e422182156a1dd418722cf/myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d", size = 84579, upload-time = "2025-02-12T10:53:02.078Z" }, +] + +[[package]] +name = "myst-parser" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.11' and python_full_version < '3.14'", +] +dependencies = [ + { name = "docutils", marker = "python_full_version >= '3.11'" }, + { name = "jinja2", marker = "python_full_version >= '3.11'" }, + { name = "markdown-it-py", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "mdit-py-plugins", marker = "python_full_version >= '3.11'" }, + { name = "pyyaml", marker = "python_full_version >= '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/fa/7b45eef11b7971f0beb29d27b7bfe0d747d063aa29e170d9edd004733c8a/myst_parser-5.0.0.tar.gz", hash = "sha256:f6f231452c56e8baa662cc352c548158f6a16fcbd6e3800fc594978002b94f3a", size = 98535, upload-time = "2026-01-15T09:08:18.036Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/ac/686789b9145413f1a61878c407210e41bfdb097976864e0913078b24098c/myst_parser-5.0.0-py3-none-any.whl", hash = "sha256:ab31e516024918296e169139072b81592336f2fef55b8986aa31c9f04b5f7211", size = 84533, upload-time = "2026-01-15T09:08:16.788Z" }, +] + [[package]] name = "nbclient" version = "0.5.13" @@ -3437,6 +3533,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3c/dd/018ce05c532a22007ac58d4f45232514cd9d6dd0ee1dc374e309db830983/sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b", size = 22496, upload-time = "2023-07-08T18:40:52.659Z" }, ] +[[package]] +name = "sphinx-copybutton" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/2b/a964715e7f5295f77509e59309959f4125122d648f86b4fe7d70ca1d882c/sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd", size = 23039, upload-time = "2023-04-14T08:10:22.998Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/48/1ea60e74949eecb12cdd6ac43987f9fd331156388dcc2319b45e2ebb81bf/sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e", size = 13343, upload-time = "2023-04-14T08:10:20.844Z" }, +] + [[package]] name = "sphinxcontrib-applehelp" version = "2.0.0" From 18f39f96d96fa3f0e36008180ba924aa4ba05a35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Wed, 4 Mar 2026 16:26:43 +0000 Subject: [PATCH 04/19] Propose impls --- docs/library-docs/proposals/impls.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 docs/library-docs/proposals/impls.md diff --git a/docs/library-docs/proposals/impls.md b/docs/library-docs/proposals/impls.md new file mode 100644 index 000000000..130292d3e --- /dev/null +++ b/docs/library-docs/proposals/impls.md @@ -0,0 +1,17 @@ +# (Proposal) Easily referencing headers to implement + +A new syntax, that allows function definitions to easily reference the declarations they are providing an implementation +for. +Signatures are validated similar to [the stub validation](./stubs.md). +The declarations link name and other keyword args should be largely copied onto the definition. +The main use case is not running risk of desyncing the link name when doing something like this. + +```python +from guppylang import guppy +from some_lib import some_declaration + + +@guppy.impl(some_declaration) # or @guppy.implements(...) +def my_implementation(...) -> ...: + ... +``` From ebcb0170ff73b5ba2fbb80e9308e7d546125708e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 5 Mar 2026 10:49:56 +0000 Subject: [PATCH 05/19] Expand --- docs/library-docs/overview.md | 105 ++++++++++++++++++++--- docs/library-docs/proposals/discovery.md | 1 + 2 files changed, 92 insertions(+), 14 deletions(-) create mode 100644 docs/library-docs/proposals/discovery.md diff --git a/docs/library-docs/overview.md b/docs/library-docs/overview.md index 524f87cbd..2a95d7514 100644 --- a/docs/library-docs/overview.md +++ b/docs/library-docs/overview.md @@ -43,25 +43,102 @@ library = guppy.library( ) ``` -- Defining stubs to program against +The following are not yet supported: -Currently, a library does not support the following: +- Exporting generic functions of any kind (see the [proposal](proposals/generic.md)) +- Automatically collecting functions to be included in a library (see the [proposal](proposals/collection.md)) -- Exporting generic functions of any kind (see [the proposal](proposals/generic.md)) -- Automatically collecting functions to be included in a library (see [the proposal](proposals/collection.md)) -- Automatically generating stubs for a library (see [the proposal](proposals/stubs.md)) +## Compiling a library and creating stubs -## Compiling a library +Once a library object is created, it can be compiled into a hugr package, which can be distributed and used +independently of the source code: -- Compiling the library, and what to do with the Hugr package +```python +from guppylang import guppy +from hugr.package import Package + +library = guppy.library(...) # As above + +# Hugr package contains any specified functions as public, and otherwise reachable functions as private +hugr_package: Package = library.compile() +with open("library.hugr", "wb") as f: + f.write(hugr_package.to_bytes()) +``` + +However, this hugr package does not expose an interface that can be programmed against by consumers of the library *in +Guppy*. For this, the library author must define *stubs* for all exposed functions, utilising `@guppy.declare(...)` +decorators. + +For the example above, this would look like: + +```python +from guppylang import guppy + + +@guppy.declare +def my_func() -> None: ... + + +@guppy.declare +def another_func(x: int) -> None: ... + + +@guppy.declare +def a_third_func(x: int) -> int: ... +``` + +Creation of these stubs is a manual process; it is currently not possible to automatically generate stubs for a +library or validate that definitions faithfully implement their corresponding stubs (see the +[proposal](proposals/stubs.md) for both). Furthermore, it is currently not possible to define the stubs, and +subsequently reference the stubs in the library function definitions for easier consistency checks (see the +[proposal](proposals/impls.md) for that). + +Finally, these stubs (in `*.pyi` files) should be distributed using regular Python packaging mechanisms, so that users +of the library can install and program against them. This distribution may or may not contain the hugr package as well. ## Using a library -- Programming and compiling against the headers -- Oh no, its not a runnable Hugr yet (but still a valid one) -- Providing the library Hugr package to the user program executor +Let's call the library in the example above `super_guppy`. +That is, the library author has published a distribution containing the stubs above, to be imported `from super_guppy`. +A consumer of the library has installed that distribution using their favourite package manager (either from an index, +or by downloading the stub repository). - - This is the most open / underspecified part - - Should be able to do this manually via ``hugr-py`` - - But also via simply passing more things to selene - - Mention auto-discovery functions +The consumer may now program against the API as follows: + +```python +from guppylang import guppy +from guppylang.std.builtins import result +from super_guppy import my_func, a_third_func + + +@guppy +def consumer_func() -> None: + my_func() + result("library_call", a_third_func(5)) + + +@guppy +def main() -> None: + consumer_func() +``` + +In this case, the consumer aims to create an executable hugr package (e.g. by calling `main.compile()` and creating a +package with a single, argument-less entrypoint). However, the created hugr package is incomplete: It lacks the function +bodies of the library functions, and thus cannot be executed. + +Thus, `hugr-py` MUST provide means to link the library hugr package into the consumer hugr package. For convenience, the +library hugr package may be provided to the consumer program executor, so that it can be automatically linked in before +lowering to QIR. For example, using selene, this may look like: + +```python +main.emulator(n_qubits=0, libraries=[hugr_package]).with_shots(100).run() +``` + +Currently, hugr packages have to be manually downloaded / imported from whatever distribution mechanism the library +author chose. In the future, library authors may opt to distribute hugr packages in Python wheels as well, and have +consuming code auto-collect these from the Python environment (see the [proposal](proposals/discovery.md) for this). + +```{note} +A consumer of `super_guppy` may as well be another library author. Dependency of a library should be specified as usual +in Python requirements by depending on the header distributions (or through other common mechanisms). +``` diff --git a/docs/library-docs/proposals/discovery.md b/docs/library-docs/proposals/discovery.md new file mode 100644 index 000000000..74f751258 --- /dev/null +++ b/docs/library-docs/proposals/discovery.md @@ -0,0 +1 @@ +# (Proposal) Automatic discovery of Hugr packages in installed distributions From bd7068295170a62de523c874968cbe2954e49bbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 5 Mar 2026 11:05:54 +0000 Subject: [PATCH 06/19] Propose Hugr discovery --- docs/library-docs/proposals/discovery.md | 73 ++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/docs/library-docs/proposals/discovery.md b/docs/library-docs/proposals/discovery.md index 74f751258..7609ece2b 100644 --- a/docs/library-docs/proposals/discovery.md +++ b/docs/library-docs/proposals/discovery.md @@ -1 +1,74 @@ # (Proposal) Automatic discovery of Hugr packages in installed distributions + +This proposal concerns how Hugr packages are distributed and discovered. +At a high level, Hugr packages should be distributed as part of Python wheels, and discovered by Guppy at runtime +without the need for manual configuration or other custom user code. + +The proposal is to leverage the +Python [entry points mechanism](https://packaging.python.org/en/latest/specifications/entry-points/) for this (as one of +the common drivers behind +[plugin discovery](https://packaging.python.org/en/latest/guides/creating-and-discovering-plugins/#using-package-metadata) +in Python). Library authors can define an entry point in the group `guppylang.hugr` in their `pyproject.toml`, and +specify a loader function that returns a list of paths to Hugr packages to be loaded. + +For example, assuming the library was compiled to a `library.hugr` binary file, a library author may include the +following in a top-level `loader.py` inside their distribution: + +```python +from pathlib import Path + + +def load_hugrs() -> list[Path]: + return [Path(__file__).parent / "library.hugr"] +``` + +Additionally, they would include the `library.hugr` file, and the following configuration in the `pyproject.toml` of +their distribution (e.g. `super-guppy-hugr`): + +```toml +[project] +name = "super-guppy-hugr" +version = "0.1.0" +description = "HUGR distribution for {self.metadata.name}" + +[project.entry-points."guppylang.hugr"] +hugr_loader = "loader:load_hugrs" +``` + +```{note} +Hugr packages may also be included in stub distributions, and discovered in the same way by including the loader and +the entry point configuration. They do not have to be separate distributions. +``` + +`guppylang` can now auto-discover Hugr packages by looking for entry points in the `guppylang.hugr` group, and calling +the corresponding loader. An example implementation of this discovery mechanism may look like the following: + +```python +from pathlib import Path +from importlib.metadata import entry_points + + +def discover_hugr_packages() -> list[Path]: + eps = entry_points(group='guppylang.hugr') + hugr_packages = [] + for ep in eps: + # Can use `ep.dist.name` to get the distribution name, if needed for logging, debugging, or filtering. + loader_func = ep.load() + hugr_packages.extend(loader_func()) + + return hugr_packages +``` + +Finally, consumers of the library would simply install the `super-guppy-hugr` distribution, and call the discovery +function before executing their Hugr code, to ensure that the library Hugr package is linked in: + +```python +from guppylang.library import discover_hugr_packages + +main = ... # Define some consuming entry point + +main.emulator(n_qubits=..., libraries=discover_hugr_packages()).with_shots(100).run() +``` + +Optionally, omission of `libraries` may lead to internal calls to automatic discovery, and other means of configuring +the used Hugr packages may be provided as well (e.g. environment variables, or a configuration file). From 17bda6bc2883dbed4dad731eff0fae170aad860a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 5 Mar 2026 11:06:57 +0000 Subject: [PATCH 07/19] Case Hugr correctly --- docs/library-docs/overview.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/library-docs/overview.md b/docs/library-docs/overview.md index 2a95d7514..eeba0c8e8 100644 --- a/docs/library-docs/overview.md +++ b/docs/library-docs/overview.md @@ -50,7 +50,7 @@ The following are not yet supported: ## Compiling a library and creating stubs -Once a library object is created, it can be compiled into a hugr package, which can be distributed and used +Once a library object is created, it can be compiled into a Hugr package, which can be distributed and used independently of the source code: ```python @@ -65,7 +65,7 @@ with open("library.hugr", "wb") as f: f.write(hugr_package.to_bytes()) ``` -However, this hugr package does not expose an interface that can be programmed against by consumers of the library *in +However, this Hugr package does not expose an interface that can be programmed against by consumers of the library *in Guppy*. For this, the library author must define *stubs* for all exposed functions, utilising `@guppy.declare(...)` decorators. @@ -94,7 +94,7 @@ subsequently reference the stubs in the library function definitions for easier [proposal](proposals/impls.md) for that). Finally, these stubs (in `*.pyi` files) should be distributed using regular Python packaging mechanisms, so that users -of the library can install and program against them. This distribution may or may not contain the hugr package as well. +of the library can install and program against them. This distribution may or may not contain the Hugr package as well. ## Using a library @@ -122,20 +122,20 @@ def main() -> None: consumer_func() ``` -In this case, the consumer aims to create an executable hugr package (e.g. by calling `main.compile()` and creating a -package with a single, argument-less entrypoint). However, the created hugr package is incomplete: It lacks the function +In this case, the consumer aims to create an executable Hugr package (e.g. by calling `main.compile()` and creating a +package with a single, argument-less entrypoint). However, the created Hugr package is incomplete: It lacks the function bodies of the library functions, and thus cannot be executed. -Thus, `hugr-py` MUST provide means to link the library hugr package into the consumer hugr package. For convenience, the -library hugr package may be provided to the consumer program executor, so that it can be automatically linked in before +Thus, `hugr-py` MUST provide means to link the library Hugr package into the consumer Hugr package. For convenience, the +library Hugr package may be provided to the consumer program executor, so that it can be automatically linked in before lowering to QIR. For example, using selene, this may look like: ```python main.emulator(n_qubits=0, libraries=[hugr_package]).with_shots(100).run() ``` -Currently, hugr packages have to be manually downloaded / imported from whatever distribution mechanism the library -author chose. In the future, library authors may opt to distribute hugr packages in Python wheels as well, and have +Currently, Hugr packages have to be manually downloaded / imported from whatever distribution mechanism the library +author chose. In the future, library authors may opt to distribute Hugr packages in Python wheels as well, and have consuming code auto-collect these from the Python environment (see the [proposal](proposals/discovery.md) for this). ```{note} From 5303842e89b1d102f927abc3f4019706cdd0cf80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 5 Mar 2026 11:16:38 +0000 Subject: [PATCH 08/19] Open question --- docs/library-docs/proposals/discovery.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/library-docs/proposals/discovery.md b/docs/library-docs/proposals/discovery.md index 7609ece2b..713265414 100644 --- a/docs/library-docs/proposals/discovery.md +++ b/docs/library-docs/proposals/discovery.md @@ -72,3 +72,8 @@ main.emulator(n_qubits=..., libraries=discover_hugr_packages()).with_shots(100). Optionally, omission of `libraries` may lead to internal calls to automatic discovery, and other means of configuring the used Hugr packages may be provided as well (e.g. environment variables, or a configuration file). + +## Open questions + +- How does a user sort out which Hugr packages to use with multiple Hugr packages for the same stubs installed? + It is unclear how this case can be discovered (e.g. for a nice error message if they do not choose). From 1dd66530791ade451d1614a71e7579cdcf62fefc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 5 Mar 2026 11:34:25 +0000 Subject: [PATCH 09/19] Collection proposal --- docs/library-docs/proposals/collection.md | 82 ++++++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/docs/library-docs/proposals/collection.md b/docs/library-docs/proposals/collection.md index b8a883ff6..5d8c90dcd 100644 --- a/docs/library-docs/proposals/collection.md +++ b/docs/library-docs/proposals/collection.md @@ -1,4 +1,82 @@ # (Proposal) More ways to define a library / auto-collection -- Mention auto-collection of functions, e.g. via ``@guppy.export(...)`` decorators -- Mention collecting these with different keys +Currently, functions must be manually fed into `guppy.library(...)` to be included in the library. This proposal +concerns additional ways to define (members of) a library, and automatically collect functions to be included in it. + +A potential addition would be a `@guppy(export=...)` argument, that registers the function to be included in *the* +library in some global Guppy store (like the `DEF_STORE`). A function could then be included simply by adding that +argument, e.g.: + +```python +from guppylang import guppy + + +@guppy(export=True) +def included() -> None: + ... + + +@guppy # or @guppy(export=False) for explicit exclusion +def not_included() -> None: + ... +``` + +This is similar to a `public` / `private` modifier, and indirectly determines the visibility of the function in the Hugr +package. It may be added to `@guppy.struct` and `@guppy.enum` as well, to include the functions on these constructs in +the library as public as well. + +A call to `guppy.library` could then be simplified to an explicit opt-in to auto-collection: + +```python +from guppylang import guppy + +# ... the functions above ... + +# Includes `included`, but not `not_included` as a public member. +library = guppy.library(auto_collect=True) +``` + +## Optional: Keys + +In addition to supporting `True | False | None` for the `export` argument, it may be useful to support string keys as +well, to allow for more fine-grained control over the produced packages in which the function is included (concerning +both the visibility in the produced Hugr package and the stubs that are generated, e.g. +through [the stub proposal](stubs.md)). + +For example, one may want to include certain (core) functions in all versions of the library, but specify certain sets +of functions to be included (akin to `extras` from Python packages): + +```python +from guppylang import guppy + + +@guppy(export=True) +def included_in_all_libs() -> None: + ... + + +@guppy(export='key1') +def included_when_key_1() -> None: + ... + + +@guppy(export='key2') +def included_when_key_2() -> None: + ... + + +@guppy(export='key3') +def included_when_key_3() -> None: + ... +``` + +Auto-collection may then take a range of keys to include: + +```python +from guppylang import guppy + +# ... the functions above ... + +# Includes all functions with export=True, export='key1', and export='key2', but not export='key3' +library = guppy.library(auto_collect=['key1', 'key2']) +``` From 42f709a0d72058650be7953efdfcd6b180611a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 5 Mar 2026 14:06:16 +0000 Subject: [PATCH 10/19] Stub proposal --- docs/library-docs/proposals/stubs.md | 52 +++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/docs/library-docs/proposals/stubs.md b/docs/library-docs/proposals/stubs.md index ee39c0a93..9e4a56828 100644 --- a/docs/library-docs/proposals/stubs.md +++ b/docs/library-docs/proposals/stubs.md @@ -1,7 +1,49 @@ # (Proposal) Generating stubs automatically -- Show how to generate stubs -- Dealing with structs -- Dealing with local types mentioned in signatures -- Limitations of automatic generation -- Validate stubs with new functionality in guppylang +One of the more arduous tasks of creating a library is writing the stubs for it. +This proposal aims to provide tooling to simplify this process and potentially fully automate it. +There are existing stub generators for Python (like [ +`stubgen` from `mypy`](https://mypy.readthedocs.io/en/stable/stubgen.html), tooling from [ +`pyright`](https://github.com/microsoft/pyright/blob/main/docs/type-stubs.md#generating-type-stubs-from-command-line), +and more), but these usually do not fit the needs of Guppy libraries, since we not only need to transform signatures, +but also make sure that we use `@guppy.declare` instead of `@guppy`. + +These stub generators are often meant to provide a start for writing stubs for the library, but often require manual +labor to clean, fix, and clarify generated code. + +```{note} +Nonetheless, it might be worthwhile testing existing stub generators on real-world examples for Guppy libraries. Perhaps +the only thing we need to worry about is the decorator transformation after all. +``` + +Difficulties surrounding stub generation include: + +- Types mentioned in signatures may come from imports (which you want to preserve, since importing from their + implementing module may reveal implementation details), or be defined locally, in which case you need to include them + in the stubs as well. +- Pythonic public re-exports should be preserved / made available in the stubs +- When depending on types provided from imports (i.e. defined in other files), these types need to be included in the + stubs for that other file +- Structs and enums need to be included in the stubs as well, with their methods as stubbed functions + +## Validation + +When writing stubs (especially those generated automatically), it is important to be able to validate that a stub +faithfully represents the definition it corresponds to (or rather, that the definition can be substituted for the stub +without violating the [Liskov substitution principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle)). +Checking may be optionally (i.e. via a flag) strengthened to require equality rather than a subtyping relationship, to +effectively check that: + +1. A definition can replace the stub without violating LSP, *and* +2. The stub is the most specific signature that can be used for the definition + +This can be extended to take two arbitrary function signatures (with accompanying flags, i.e. from +`@guppy(unitary=True)`), and subsequently check LSP on them. + +Such a validation mechanism would be useful for the following use cases: + +1. Verifying (automatically, and fit for use in CI) that a definition did not observably break against the stub it + corresponds to (at least with the typed contract, written contracts cannot be checked). +2. Verifying (mainly for our test cases) that the automatically generated stubs work as intended, and one did not + discover a new edge case where they do not. +3. Probably more. From f3a54d5ec00bf5a86fadb0f31c8505aa0ce15cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Thu, 5 Mar 2026 14:10:31 +0000 Subject: [PATCH 11/19] Add link names --- docs/library-docs/overview.md | 16 ++++++++++++++++ docs/library-docs/proposals/stubs.md | 3 ++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/library-docs/overview.md b/docs/library-docs/overview.md index eeba0c8e8..f357494a2 100644 --- a/docs/library-docs/overview.md +++ b/docs/library-docs/overview.md @@ -43,6 +43,22 @@ library = guppy.library( ) ``` +For private functions, the name their Hugr nodes receive is not important. +However, public functions need to have a stable (and unique) name for the Hugr to be valid and useful. +Guppy includes a default name inference mechanism based on the fully qualified name of the function (including any +parent types and the module path), but it is also possible to explicitly specify the name (e.g. to avoid conflicts, make +a backward-compatible change, or to provide a more user-friendly name) using the `link_name` argument of the `@guppy` +decorator: + +```python +from guppylang import guppy + + +@guppy(link_name="my.custom.link.name") +def my_func() -> None: + pass +``` + The following are not yet supported: - Exporting generic functions of any kind (see the [proposal](proposals/generic.md)) diff --git a/docs/library-docs/proposals/stubs.md b/docs/library-docs/proposals/stubs.md index 9e4a56828..72b8a40ee 100644 --- a/docs/library-docs/proposals/stubs.md +++ b/docs/library-docs/proposals/stubs.md @@ -6,7 +6,8 @@ There are existing stub generators for Python (like [ `stubgen` from `mypy`](https://mypy.readthedocs.io/en/stable/stubgen.html), tooling from [ `pyright`](https://github.com/microsoft/pyright/blob/main/docs/type-stubs.md#generating-type-stubs-from-command-line), and more), but these usually do not fit the needs of Guppy libraries, since we not only need to transform signatures, -but also make sure that we use `@guppy.declare` instead of `@guppy`. +but also make sure that we use `@guppy.declare` instead of `@guppy`. Link names should be explicitly specified in the +stubs, to make them independent of changes in the default link name inference mechanisms. These stub generators are often meant to provide a start for writing stubs for the library, but often require manual labor to clean, fix, and clarify generated code. From af04b70f010f46bdc73bda691b3ea645f7541f52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 6 Mar 2026 10:31:53 +0000 Subject: [PATCH 12/19] fix: improve wording in overview.md for clarity --- docs/library-docs/overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/library-docs/overview.md b/docs/library-docs/overview.md index f357494a2..cc0b49f1a 100644 --- a/docs/library-docs/overview.md +++ b/docs/library-docs/overview.md @@ -7,7 +7,7 @@ In contrast to the usual executable Hugr packages (like those produced from comp library Hugr packages feature (usually multiple) *public* functions, possibly with arguments and non-``None`` return types. -A developer may define a bunch of functions beyond this collection, for example for internally sharing functionality +A developer may define several functions beyond this collection, for example for internally sharing functionality between the public functions. Furthermore, developers may want to create different libraries / Hugr packages from a single codebase, requiring to export different public functions in each such library. In Guppy, a library is defined by calling the ``guppy.library(...)`` function, passing the functions to be exported as From 7b79531ddfc914f01e12271294a3ef0d36e9d621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 6 Mar 2026 10:32:23 +0000 Subject: [PATCH 13/19] typos --- docs/library-docs/overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/library-docs/overview.md b/docs/library-docs/overview.md index cc0b49f1a..db592b752 100644 --- a/docs/library-docs/overview.md +++ b/docs/library-docs/overview.md @@ -144,7 +144,7 @@ bodies of the library functions, and thus cannot be executed. Thus, `hugr-py` MUST provide means to link the library Hugr package into the consumer Hugr package. For convenience, the library Hugr package may be provided to the consumer program executor, so that it can be automatically linked in before -lowering to QIR. For example, using selene, this may look like: +compiling to binary. For example, using selene, this may look like: ```python main.emulator(n_qubits=0, libraries=[hugr_package]).with_shots(100).run() From 31c6d35ebab2dcc1db881bd325b2b355b5520f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 6 Mar 2026 10:52:09 +0000 Subject: [PATCH 14/19] More clearly link --- docs/library-docs/overview.md | 37 ++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/docs/library-docs/overview.md b/docs/library-docs/overview.md index db592b752..71bfba194 100644 --- a/docs/library-docs/overview.md +++ b/docs/library-docs/overview.md @@ -142,7 +142,42 @@ In this case, the consumer aims to create an executable Hugr package (e.g. by ca package with a single, argument-less entrypoint). However, the created Hugr package is incomplete: It lacks the function bodies of the library functions, and thus cannot be executed. -Thus, `hugr-py` MUST provide means to link the library Hugr package into the consumer Hugr package. For convenience, the +Thus, `hugr-py` MUST provide means to link the library Hugr package into the consumer Hugr package. The proposed +mechanism for this is to allow operations on Hugr packages that (a) allow to add modules and extensions from other +packages, and (b) allow to combine multiple modules inside the same package into a single module. In code, this may look +like: + +```python +from hugr.package import Package + +package_a: Package = ... +package_b: Package = ... +package_c: Package = ... + +# (a) add modules and extensions from other packages +package_a.add_from(package_b).add_from(package_c) + +# (b) combine multiple modules into a single module inside the package +package_a.link_modules() +# Now package_a contains a single module with all the functions +# from package_a, package_b, and package_c +``` + +There are no guarantees about the order of modules being linked together, or whether that is pairwise or all at once. +A package may also expose more fine-grained control over the linking process, for example by allowing to "link" in +entire packages, steering the reduction ordering: + +```python +# Extensions and modules are added from package_b, +# and its module is linked into the module of package_a +package_a.link(package_b) + +# In the future, keywords may be added to allow e.g. privatization of all +# functions from package_b (i.e. swallowing), so for example: +package_a.link(package_b, privatize=True) +``` + +For convenience, the library Hugr package may be provided to the consumer program executor, so that it can be automatically linked in before compiling to binary. For example, using selene, this may look like: From 43d1c20589f741c14b7bb83f7b1427bb52d51e8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Fri, 6 Mar 2026 10:54:05 +0000 Subject: [PATCH 15/19] Add note --- docs/library-docs/overview.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/library-docs/overview.md b/docs/library-docs/overview.md index 71bfba194..29778c2bd 100644 --- a/docs/library-docs/overview.md +++ b/docs/library-docs/overview.md @@ -177,6 +177,10 @@ package_a.link(package_b) package_a.link(package_b, privatize=True) ``` +```{note} +It is yet unclear whether this process should be done in-place or producing copies (which may cause a significant slowdown). +``` + For convenience, the library Hugr package may be provided to the consumer program executor, so that it can be automatically linked in before compiling to binary. For example, using selene, this may look like: From 94e24eecff416695b679c5ed3d9505cac1e040f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 9 Mar 2026 10:13:30 +0000 Subject: [PATCH 16/19] Add entrypoint note --- docs/library-docs/overview.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/library-docs/overview.md b/docs/library-docs/overview.md index 29778c2bd..8c559a4e8 100644 --- a/docs/library-docs/overview.md +++ b/docs/library-docs/overview.md @@ -163,6 +163,10 @@ package_a.link_modules() # from package_a, package_b, and package_c ``` +Whenever two or more modules are linked together, the resulting module will keep the *single* non-module entrypoint +across all modules, if it exists. When more than one module has a non-module entrypoint (i.e. more than one module is +executable), an error is raised. + There are no guarantees about the order of modules being linked together, or whether that is pairwise or all at once. A package may also expose more fine-grained control over the linking process, for example by allowing to "link" in entire packages, steering the reduction ordering: From 288e410dc1993150198e3145ee492d36b52f475c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 23 Mar 2026 14:32:00 +0000 Subject: [PATCH 17/19] Remove foreword --- docs/library-docs/index.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/library-docs/index.md b/docs/library-docs/index.md index 276a0cbac..52a205edd 100644 --- a/docs/library-docs/index.md +++ b/docs/library-docs/index.md @@ -8,8 +8,6 @@ which can be independently distributed and e.g. cached for optimization purposes source your Guppy code, the standard packaging mechanisms of Python suffice, and you can ignore this page. ``` -- Foreword - ```{toctree} :maxdepth: 2 From 843950386e2e988762152b2b1485b2ce31c4ae64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 23 Mar 2026 14:39:58 +0000 Subject: [PATCH 18/19] Update overview --- docs/library-docs/overview.md | 43 ++++++++++------------------------- 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/docs/library-docs/overview.md b/docs/library-docs/overview.md index 8c559a4e8..0b829a647 100644 --- a/docs/library-docs/overview.md +++ b/docs/library-docs/overview.md @@ -109,8 +109,9 @@ library or validate that definitions faithfully implement their corresponding st subsequently reference the stubs in the library function definitions for easier consistency checks (see the [proposal](proposals/impls.md) for that). -Finally, these stubs (in `*.pyi` files) should be distributed using regular Python packaging mechanisms, so that users -of the library can install and program against them. This distribution may or may not contain the Hugr package as well. +Finally, these stubs (importable, thus in `*.py` files instead of `*.pyi` as usual) should be distributed using regular +Python packaging mechanisms, so that users of the library can install and program against them. This distribution may or +may not contain the Hugr package as well. ## Using a library @@ -142,10 +143,8 @@ In this case, the consumer aims to create an executable Hugr package (e.g. by ca package with a single, argument-less entrypoint). However, the created Hugr package is incomplete: It lacks the function bodies of the library functions, and thus cannot be executed. -Thus, `hugr-py` MUST provide means to link the library Hugr package into the consumer Hugr package. The proposed -mechanism for this is to allow operations on Hugr packages that (a) allow to add modules and extensions from other -packages, and (b) allow to combine multiple modules inside the same package into a single module. In code, this may look -like: +As of https://github.com/Quantinuum/hugr/commit/329c243fffff0d6c4437664a012361619a0a425e, `hugr-py` provides the means +to link one or more library Hugr packages into the consumer Hugr package. This may look like: ```python from hugr.package import Package @@ -154,12 +153,9 @@ package_a: Package = ... package_b: Package = ... package_c: Package = ... -# (a) add modules and extensions from other packages -package_a.add_from(package_b).add_from(package_c) +package_d = package_a.link(package_b, package_c) -# (b) combine multiple modules into a single module inside the package -package_a.link_modules() -# Now package_a contains a single module with all the functions +# Now package_d contains a single module with all the functions # from package_a, package_b, and package_c ``` @@ -168,29 +164,14 @@ across all modules, if it exists. When more than one module has a non-module ent executable), an error is raised. There are no guarantees about the order of modules being linked together, or whether that is pairwise or all at once. -A package may also expose more fine-grained control over the linking process, for example by allowing to "link" in -entire packages, steering the reduction ordering: +The user can exert a certain level of control by gradually linking packages together, using multiple calls to `link`. +Furthermore, future versions of `hugr-py` may provide even more control with extensions / alternatives to `link`. -```python -# Extensions and modules are added from package_b, -# and its module is linked into the module of package_a -package_a.link(package_b) - -# In the future, keywords may be added to allow e.g. privatization of all -# functions from package_b (i.e. swallowing), so for example: -package_a.link(package_b, privatize=True) -``` - -```{note} -It is yet unclear whether this process should be done in-place or producing copies (which may cause a significant slowdown). -``` - -For convenience, the -library Hugr package may be provided to the consumer program executor, so that it can be automatically linked in before -compiling to binary. For example, using selene, this may look like: +For convenience, the library Hugr package may be provided to the consumer program executor, so that it can be +automatically linked in before compiling to binary. For example, using selene, this may look like: ```python -main.emulator(n_qubits=0, libraries=[hugr_package]).with_shots(100).run() +main.emulator(n_qubits=0, libs=[lib_package]).with_shots(100).run() ``` Currently, Hugr packages have to be manually downloaded / imported from whatever distribution mechanism the library From ff7df0318abd130f0224e9f079d91e2686452ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20R=C3=BCsch?= Date: Mon, 23 Mar 2026 14:51:56 +0000 Subject: [PATCH 19/19] Update stubs --- docs/library-docs/proposals/stubs.md | 36 ++++++++++++++++------------ 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/docs/library-docs/proposals/stubs.md b/docs/library-docs/proposals/stubs.md index 72b8a40ee..a2f39e392 100644 --- a/docs/library-docs/proposals/stubs.md +++ b/docs/library-docs/proposals/stubs.md @@ -1,31 +1,37 @@ # (Proposal) Generating stubs automatically One of the more arduous tasks of creating a library is writing the stubs for it. -This proposal aims to provide tooling to simplify this process and potentially fully automate it. +This proposal aims to provide tooling to simplify this process and partially automate it. There are existing stub generators for Python (like [ `stubgen` from `mypy`](https://mypy.readthedocs.io/en/stable/stubgen.html), tooling from [ `pyright`](https://github.com/microsoft/pyright/blob/main/docs/type-stubs.md#generating-type-stubs-from-command-line), -and more), but these usually do not fit the needs of Guppy libraries, since we not only need to transform signatures, -but also make sure that we use `@guppy.declare` instead of `@guppy`. Link names should be explicitly specified in the -stubs, to make them independent of changes in the default link name inference mechanisms. +and more). +These stub generators are often meant to provide a start for writing stubs for a given chunk of Python, but often +require manual labor to clean, fix, and clarify generated code. While the aim of this proposal is not to necessarily +fully automate the process, there are additional considerations which, if not automated, can cause great slowdowns for +library developer workflows. -These stub generators are often meant to provide a start for writing stubs for the library, but often require manual -labor to clean, fix, and clarify generated code. - -```{note} -Nonetheless, it might be worthwhile testing existing stub generators on real-world examples for Guppy libraries. Perhaps -the only thing we need to worry about is the decorator transformation after all. -``` - -Difficulties surrounding stub generation include: +General difficulties around stub generation, that the above generators have (more or less successfully) resolved, +include: - Types mentioned in signatures may come from imports (which you want to preserve, since importing from their implementing module may reveal implementation details), or be defined locally, in which case you need to include them in the stubs as well. - Pythonic public re-exports should be preserved / made available in the stubs - When depending on types provided from imports (i.e. defined in other files), these types need to be included in the - stubs for that other file -- Structs and enums need to be included in the stubs as well, with their methods as stubbed functions + stubs for that other file, even if they may be unused there + +Special difficulties for the Guppy ecosystem and compile time Python include: + +- Instead of preserving the `@guppy` decorator (or whatever custom decorator was used), we have to use `@guppy.declare` + in the stubs +- The link name should always be fixed in the stubs, to avoid changes in the default link name inference mechanism + breaking the stubs (and thus the library interface) without any observable change in the source code of the library +- Structs and enums need to be included in the stubs for typing reasons, but the interaction with nominal typing is + unclear +- Guppy functions may be generated by Python "factory" functions, that may provide some types / arguments at compile + time, especially for generic numeric parameters. Thus, we need a very careful mix of resolving types, but recalling + the original source and definition sites if they cannot be fully resolved (e.g. boiled to a single number). ## Validation