Skip to content

feat!: Explicit effects system, prevent calls from annotated funcs to those with fewer effects#1723

Open
acl-cqc wants to merge 61 commits into
mainfrom
acl/max_effects
Open

feat!: Explicit effects system, prevent calls from annotated funcs to those with fewer effects#1723
acl-cqc wants to merge 61 commits into
mainfrom
acl/max_effects

Conversation

@acl-cqc
Copy link
Copy Markdown
Contributor

@acl-cqc acl-cqc commented May 7, 2026

closes #1745.

However, note this is pure typechecking, and does not add any new order edges (ideally we would use the effect-system information to replace EXTENSION_OPS_WITH_SIDE_EFFECTS but leaving that to the PR that solves #1746.

  • The annotation takes a list of Effect; I've added both internal and frontend enums but these might be combined. At present the only known effect is ANY.
  • Unannotated functions are assumed to have [Effect.ANY]
  • Callable types can be given effects via an @effects modifier; without which, you get [ANY] by default.
    • Callables are invariant. We could perhaps be more flexible, while we are using Order edges not tokens, but that would be introducing subtyping; moreover this is consistent, and will be improved in Shorthand syntax for Effect Variables #1760 (as this does cause some degradations).
  • Nested functions are assumed to have the same effects as their containing function (for now; this could cause issues if you want to return one as a Callable return value)
  • Changes made to stdlib, and compiler changes to support them by making conversion-to-bool, enum and struct constructors all "pure", are all gathered together in 4523d81 (you can see without the PR without these: https://github.com/Quantinuum/guppylang/pull/1723/changes/BASE..51745bfdb8c55a24c57d783a0a5d1b91c3613781

Do I want more tests of the quantum stdlib? Any ideas what?

BREAKING CHANGE: Functions taking Callable will be able to accept either only Callables with effects (the default) or (if annotated) those without, rather than both; those returning Callables will need an explicit annotation if the returnee is pure.

@hugrbot
Copy link
Copy Markdown
Collaborator

hugrbot commented May 7, 2026

This PR contains breaking changes to the public Python API.

Breaking changes summary
guppylang-internals/src/guppylang_internals/definition/overloaded.py:0: OverloadNoMatchError.__init__(max_effects_from):
Parameter was added as required

guppylang-internals/src/guppylang_internals/checker/modifier_checker.py:23: check_modified_block(max_effects_from):
Parameter was added as required

guppylang-internals/src/guppylang_internals/checker/cfg_checker.py:73: check_cfg(first_modifier_node):
Positional parameter was moved
Details: position: from 6 to 7 (+1)

guppylang-internals/src/guppylang_internals/checker/cfg_checker.py:73: check_cfg(max_effects_from):
Parameter was added as required

guppylang-internals/src/guppylang_internals/checker/cfg_checker.py:228: check_bb(max_effects_from):
Parameter was added as required


@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

🐰 Bencher Report

Branchacl/max_effects
TestbedLinux
Click to view all benchmark results
Benchmarkhugr_bytesBenchmark Result
bytes x 1e3
(Result Δ%)
Upper Boundary
bytes x 1e3
(Limit %)
hugr_nodesBenchmark Result
nodes
(Result Δ%)
Upper Boundary
nodes
(Limit %)
tests/benchmarks/test_big_array.py::test_big_array_compile📈 view plot
🚷 view threshold
158.77 x 1e3
(0.00%)Baseline: 158.77 x 1e3
160.36 x 1e3
(99.01%)
📈 view plot
🚷 view threshold
6,641.00
(0.00%)Baseline: 6,641.00
6,707.41
(99.01%)
tests/benchmarks/test_ctrl_flow.py::test_many_ctrl_flow_compile📈 view plot
🚷 view threshold
27.53 x 1e3
(-0.02%)Baseline: 27.53 x 1e3
27.81 x 1e3
(98.99%)
📈 view plot
🚷 view threshold
1,074.00
(0.00%)Baseline: 1,074.00
1,084.74
(99.01%)
tests/benchmarks/test_queue_push_pop.py::test_queue_push_benchmark_compile📈 view plot
🚷 view threshold
10.91 x 1e3
(0.00%)Baseline: 10.91 x 1e3
11.02 x 1e3
(99.01%)
📈 view plot
🚷 view threshold
308.00
(0.00%)Baseline: 308.00
311.08
(99.01%)
tests/benchmarks/test_queue_push_pop.py::test_queue_push_pop_benchmark_compile📈 view plot
🚷 view threshold
14.84 x 1e3
(+0.01%)Baseline: 14.84 x 1e3
14.98 x 1e3
(99.02%)
📈 view plot
🚷 view threshold
435.00
(0.00%)Baseline: 435.00
439.35
(99.01%)
🐰 View full continuous benchmarking report in Bencher

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 7, 2026

Codecov Report

❌ Patch coverage is 93.62745% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.95%. Comparing base (82b9c77) to head (4523d81).

Files with missing lines Patch % Lines
...g-internals/src/guppylang_internals/tys/parsing.py 66.66% 5 Missing ⚠️
...ls/src/guppylang_internals/checker/func_checker.py 82.35% 3 Missing ⚠️
guppylang/src/guppylang/decorator.py 86.66% 2 Missing ⚠️
guppylang/src/guppylang/std/effects.py 77.77% 2 Missing ⚠️
...-internals/src/guppylang_internals/tys/__init__.py 92.30% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1723      +/-   ##
==========================================
- Coverage   92.98%   92.95%   -0.03%     
==========================================
  Files         135      137       +2     
  Lines       13056    13174     +118     
==========================================
+ Hits        12140    12246     +106     
- Misses        916      928      +12     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 7, 2026

Merging this PR will not alter performance

✅ 9 untouched benchmarks


Comparing acl/max_effects (4523d81) with main (2dc589a)

Open in CodSpeed

@acl-cqc acl-cqc changed the title feat: Add "max_effects" param to @guppy, prevent calls from such funcs to those without feat!: Explicit effects system, prevent calls from annotated funcs to those with fewer effects May 26, 2026
arrow = (
"->"
if ty.declared_effects is None
else f"-{Effect.format_list(ty.declared_effects)}->"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This suggests we may want to work harder to separate out cases where the effects are relevant from cases where they are not, and only render them when they are. (E.g. TooManyEffectsError and OverloadNoMatchError, where the effects are not even in a FunctionType; but then TypeMismatchError does need to render effects in detail in the mismatch is between Callables differing only in effects)...this does show up quite a bit in error messages ATM

raise GuppyError(LinearComptimeError(node.right, ty))
case ast.Call(func=ast.Name(id="effects")) as fx:
if not isinstance(ty, FunctionType):
raise GuppyError(InvalidFlagError(node.right))
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This InvalidFlagError is not right, it's probably a new kind of error specific to @effects (a construct of only very limited use i.e. in connection with Callable!)...I haven't put in tests of each case: LHS not a Callable type; multiple @effects; individual effects not valid.

if not isinstance(e, ast.Name):
raise GuppyError(InvalidFlagError(node.right))
try:
effects.append(Effect.__from_str__(e.id))
Copy link
Copy Markdown
Contributor Author

@acl-cqc acl-cqc May 26, 2026

Choose a reason for hiding this comment

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

I mean, you need the relevant import ANY in scope to make python 3.12 happy, but 3.14 is happy even without that. Moreover - just like other type modifiers - the effects(ANY) doesn't even reflect what you've imported....

for inp in sig.inputs
)
s += ", ..." if has_var_args else ""
# TODO Not clear how to display effects in a Python-like syntax? (skip for now)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

perhaps def foo(x : int) -> int (with effects []) ?

Comment thread guppylang-internals/src/guppylang_internals/tys/ty.py Outdated
from collections.abc import Callable, Sequence
from types import FrameType

from guppylang import Effect
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This seems wrong - guppylang_internals is now referring to public guppylang - because here we have decorators used in stdlib etc. Maybe a good argument to just combine the enums in guppylang-internals and re-export?

before being used."""

@custom_function(compiler=ReadFutureBoolCompiler())
# We do *not* model the pipeline as a side-effect
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

is this a good comment? Should I just drop it?

@acl-cqc acl-cqc force-pushed the acl/max_effects branch from 98f85c5 to 6976d3b Compare May 26, 2026 18:24
@acl-cqc acl-cqc marked this pull request as ready for review May 26, 2026 19:07
@acl-cqc acl-cqc requested a review from a team as a code owner May 26, 2026 19:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

kwarg to @guppy for ordering requirements

3 participants