guppylang-internals 0.24.0__tar.gz → 0.25.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/CHANGELOG.md +25 -0
  2. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/PKG-INFO +3 -3
  3. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/pyproject.toml +3 -3
  4. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/__init__.py +1 -1
  5. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/ast_util.py +21 -0
  6. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/cfg/bb.py +20 -0
  7. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/cfg/builder.py +101 -3
  8. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/checker/core.py +4 -0
  9. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/checker/errors/generic.py +32 -1
  10. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/checker/errors/type_errors.py +14 -0
  11. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/checker/expr_checker.py +46 -10
  12. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/checker/func_checker.py +1 -1
  13. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/checker/linearity_checker.py +65 -0
  14. guppylang_internals-0.25.0/src/guppylang_internals/checker/modifier_checker.py +116 -0
  15. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/checker/stmt_checker.py +48 -1
  16. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/compiler/core.py +90 -53
  17. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/compiler/expr_compiler.py +49 -114
  18. guppylang_internals-0.25.0/src/guppylang_internals/compiler/modifier_compiler.py +174 -0
  19. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/compiler/stmt_compiler.py +15 -8
  20. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/definition/custom.py +35 -1
  21. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/definition/declaration.py +3 -4
  22. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/definition/parameter.py +8 -3
  23. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/definition/pytket_circuits.py +13 -41
  24. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/definition/struct.py +7 -4
  25. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/definition/ty.py +3 -3
  26. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/experimental.py +5 -0
  27. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/nodes.py +124 -0
  28. guppylang_internals-0.25.0/src/guppylang_internals/std/_internal/compiler/array.py +381 -0
  29. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/std/_internal/compiler/tket_exts.py +9 -2
  30. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/tracing/unpacking.py +19 -20
  31. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/tys/arg.py +18 -3
  32. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/tys/builtin.py +2 -5
  33. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/tys/const.py +33 -4
  34. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/tys/param.py +31 -16
  35. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/tys/parsing.py +8 -21
  36. guppylang_internals-0.25.0/src/guppylang_internals/tys/qubit.py +27 -0
  37. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/tys/subst.py +8 -26
  38. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/tys/ty.py +31 -21
  39. guppylang_internals-0.24.0/src/guppylang_internals/std/_internal/compiler/array.py +0 -569
  40. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/.gitignore +0 -0
  41. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/LICENCE +0 -0
  42. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/README.md +0 -0
  43. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/cfg/__init__.py +0 -0
  44. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/cfg/analysis.py +0 -0
  45. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/cfg/cfg.py +0 -0
  46. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/checker/__init__.py +0 -0
  47. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/checker/cfg_checker.py +0 -0
  48. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/checker/errors/__init__.py +0 -0
  49. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/checker/errors/comptime_errors.py +0 -0
  50. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/checker/errors/linearity.py +0 -0
  51. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/checker/errors/wasm.py +0 -0
  52. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/compiler/__init__.py +0 -0
  53. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/compiler/cfg_compiler.py +0 -0
  54. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/compiler/func_compiler.py +0 -0
  55. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/compiler/hugr_extension.py +0 -0
  56. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/compiler/qtm_platform_extension.py +0 -0
  57. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/decorator.py +0 -0
  58. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/definition/__init__.py +0 -0
  59. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/definition/common.py +0 -0
  60. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/definition/const.py +0 -0
  61. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/definition/extern.py +0 -0
  62. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/definition/function.py +0 -0
  63. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/definition/overloaded.py +0 -0
  64. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/definition/traced.py +0 -0
  65. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/definition/value.py +0 -0
  66. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/definition/wasm.py +0 -0
  67. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/diagnostic.py +0 -0
  68. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/dummy_decorator.py +0 -0
  69. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/engine.py +0 -0
  70. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/error.py +0 -0
  71. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/ipython_inspect.py +0 -0
  72. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/py.typed +0 -0
  73. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/span.py +0 -0
  74. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/std/__init__.py +0 -0
  75. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/std/_internal/__init__.py +0 -0
  76. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/std/_internal/checker.py +0 -0
  77. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/std/_internal/compiler/__init__.py +0 -0
  78. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/std/_internal/compiler/arithmetic.py +0 -0
  79. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/std/_internal/compiler/either.py +0 -0
  80. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/std/_internal/compiler/frozenarray.py +0 -0
  81. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/std/_internal/compiler/futures.py +0 -0
  82. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/std/_internal/compiler/list.py +0 -0
  83. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/std/_internal/compiler/mem.py +0 -0
  84. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/std/_internal/compiler/option.py +0 -0
  85. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/std/_internal/compiler/prelude.py +0 -0
  86. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/std/_internal/compiler/qsystem.py +0 -0
  87. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/std/_internal/compiler/quantum.py +0 -0
  88. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/std/_internal/compiler/tket_bool.py +0 -0
  89. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/std/_internal/compiler/wasm.py +0 -0
  90. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/std/_internal/compiler.py +0 -0
  91. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/std/_internal/debug.py +0 -0
  92. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/std/_internal/util.py +0 -0
  93. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/tracing/__init__.py +0 -0
  94. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/tracing/builtins_mock.py +0 -0
  95. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/tracing/frozenlist.py +0 -0
  96. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/tracing/function.py +0 -0
  97. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/tracing/object.py +0 -0
  98. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/tracing/state.py +0 -0
  99. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/tracing/util.py +0 -0
  100. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/tys/__init__.py +0 -0
  101. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/tys/common.py +0 -0
  102. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/tys/errors.py +0 -0
  103. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/tys/printing.py +0 -0
  104. {guppylang_internals-0.24.0 → guppylang_internals-0.25.0}/src/guppylang_internals/tys/var.py +0 -0
@@ -3,6 +3,31 @@
3
3
  First release of `guppylang_internals` package containing refactored out internal components
4
4
  from `guppylang`.
5
5
 
6
+ ## [0.25.0](https://github.com/CQCL/guppylang/compare/guppylang-internals-v0.24.0...guppylang-internals-v0.25.0) (2025-10-28)
7
+
8
+
9
+ ### ⚠ BREAKING CHANGES
10
+
11
+ * (guppy-internals) Arrays are now lowered to `borrow_array`s instead of `value_array`s so elements do no longer need to be wrapped in options during lowering.
12
+ * `checker.core.requires_monomorphization` renamed into `require_monomorphization` and now operating on all parameters simultaneously `tys.subst.BoundVarFinder` removed. Instead, use the new `bound_vars` property on types, arguments, and consts. `tys.parsing.parse_parameter` now requires a `param_var_mapping`.
13
+
14
+ ### Features
15
+
16
+ * compiler for modifiers ([#1287](https://github.com/CQCL/guppylang/issues/1287)) ([439ff1a](https://github.com/CQCL/guppylang/commit/439ff1ae6bd872bb7a6eb5441110d2febebd1e47))
17
+ * modifiers in CFG and its type checker (experimental) ([#1281](https://github.com/CQCL/guppylang/issues/1281)) ([fe85018](https://github.com/CQCL/guppylang/commit/fe8501854507c3c43cec2f26bba75198766a4a17))
18
+ * Turn type parameters into dependent telescopes ([#1154](https://github.com/CQCL/guppylang/issues/1154)) ([b56e056](https://github.com/CQCL/guppylang/commit/b56e056a6b4795c778ed8124a09a194fb1d97dda))
19
+ * update hugr, tket-exts and tket ([#1305](https://github.com/CQCL/guppylang/issues/1305)) ([6990d85](https://github.com/CQCL/guppylang/commit/6990d850170e6901f60ef1d1e718c99349105b56))
20
+ * Use `borrow_array` instead of `value_array` for array lowering ([#1166](https://github.com/CQCL/guppylang/issues/1166)) ([f9ef42b](https://github.com/CQCL/guppylang/commit/f9ef42b2baf61c3e1c2cfcf7bd1f3bcac33a1a25))
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * compilation of affine-bounded type variables ([#1308](https://github.com/CQCL/guppylang/issues/1308)) ([49ecb49](https://github.com/CQCL/guppylang/commit/49ecb497bf450d0853baec1de9c516a3804a80eb))
26
+ * Detect unsolved generic parameters even if they are unused ([#1279](https://github.com/CQCL/guppylang/issues/1279)) ([f830db0](https://github.com/CQCL/guppylang/commit/f830db00c416cfc1e9fe7ec70c612b6b558aa740)), closes [#1273](https://github.com/CQCL/guppylang/issues/1273)
27
+ * Fix bug in symbolic pytket circuit loading with arrays ([#1302](https://github.com/CQCL/guppylang/issues/1302)) ([e6b90e8](https://github.com/CQCL/guppylang/commit/e6b90e8e4d275d36514a75e87eb097383495a291)), closes [#1298](https://github.com/CQCL/guppylang/issues/1298)
28
+ * Improve track_hugr_side_effects, adding Order edges from/to Input/Output ([#1311](https://github.com/CQCL/guppylang/issues/1311)) ([3c6ce7a](https://github.com/CQCL/guppylang/commit/3c6ce7aaf7a1c93c6412501976fc97afd61a062d))
29
+ * multiline loop arguments ([#1309](https://github.com/CQCL/guppylang/issues/1309)) ([836ef72](https://github.com/CQCL/guppylang/commit/836ef722d8f8bdb02c56e5f06934246a718e68d3))
30
+
6
31
  ## [0.24.0](https://github.com/CQCL/guppylang/compare/guppylang-internals-v0.23.0...guppylang-internals-v0.24.0) (2025-09-19)
7
32
 
8
33
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: guppylang-internals
3
- Version: 0.24.0
3
+ Version: 0.25.0
4
4
  Summary: Compiler internals for `guppylang` package.
5
5
  Author-email: Mark Koch <mark.koch@quantinuum.com>, TKET development team <tket-support@quantinuum.com>
6
6
  Maintainer-email: Mark Koch <mark.koch@quantinuum.com>, TKET development team <tket-support@quantinuum.com>
@@ -219,8 +219,8 @@ Classifier: Programming Language :: Python :: 3.13
219
219
  Classifier: Programming Language :: Python :: 3.14
220
220
  Classifier: Topic :: Software Development :: Compilers
221
221
  Requires-Python: <4,>=3.10
222
- Requires-Dist: hugr~=0.13.1
223
- Requires-Dist: tket-exts~=0.11.0
222
+ Requires-Dist: hugr~=0.14.1
223
+ Requires-Dist: tket-exts~=0.12.0
224
224
  Requires-Dist: typing-extensions<5,>=4.9.0
225
225
  Provides-Extra: pytket
226
226
  Requires-Dist: pytket>=1.34; extra == 'pytket'
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "guppylang-internals"
3
- version = "0.24.0"
3
+ version = "0.25.0"
4
4
  requires-python = ">=3.10,<4"
5
5
  description = "Compiler internals for `guppylang` package."
6
6
  license = { file = "LICENCE" }
@@ -34,8 +34,8 @@ classifiers = [
34
34
 
35
35
  dependencies = [
36
36
  "typing-extensions >=4.9.0,<5",
37
- "tket-exts ~= 0.11.0",
38
- "hugr ~= 0.13.1",
37
+ "tket-exts ~= 0.12.0",
38
+ "hugr ~= 0.14.1",
39
39
  ]
40
40
 
41
41
  [project.optional-dependencies]
@@ -1,3 +1,3 @@
1
1
  # This is updated by our release-please workflow, triggered by this
2
2
  # annotation: x-release-please-version
3
- __version__ = "0.24.0"
3
+ __version__ = "0.25.0"
@@ -106,6 +106,14 @@ def return_nodes_in_ast(node: Any) -> list[ast.Return]:
106
106
  return cast(list[ast.Return], found)
107
107
 
108
108
 
109
+ def loop_in_ast(node: Any) -> list[ast.For | ast.While]:
110
+ """Returns all `For` and `While` nodes occurring in an AST."""
111
+ found = find_nodes(
112
+ lambda n: isinstance(n, ast.For | ast.While), node, {ast.FunctionDef}
113
+ )
114
+ return cast(list[ast.For | ast.While], found)
115
+
116
+
109
117
  def breaks_in_loop(node: Any) -> list[ast.Break]:
110
118
  """Returns all `Break` nodes occurring in a loop.
111
119
 
@@ -117,6 +125,19 @@ def breaks_in_loop(node: Any) -> list[ast.Break]:
117
125
  return cast(list[ast.Break], found)
118
126
 
119
127
 
128
+ def loop_controls_in_loop(node: Any) -> list[ast.Break | ast.Continue]:
129
+ """Returns all `Break` and `Continue` nodes occurring in a loop.
130
+
131
+ Note that breaks in nested loops are excluded.
132
+ """
133
+ found = find_nodes(
134
+ lambda n: isinstance(n, ast.Break | ast.Continue),
135
+ node,
136
+ {ast.For, ast.While, ast.FunctionDef},
137
+ )
138
+ return cast(list[ast.Break | ast.Continue], found)
139
+
140
+
120
141
  class ContextAdjuster(ast.NodeTransformer):
121
142
  """Updates the `ast.Context` indicating if expressions occur on the LHS or RHS."""
122
143
 
@@ -13,6 +13,7 @@ from guppylang_internals.nodes import (
13
13
  DesugaredGenerator,
14
14
  DesugaredGeneratorExpr,
15
15
  DesugaredListComp,
16
+ ModifiedBlock,
16
17
  NestedFunctionDef,
17
18
  )
18
19
 
@@ -44,6 +45,7 @@ BBStatement = (
44
45
  | ast.Expr
45
46
  | ast.Return
46
47
  | NestedFunctionDef
48
+ | ModifiedBlock
47
49
  )
48
50
 
49
51
 
@@ -219,3 +221,21 @@ class VariableVisitor(ast.NodeVisitor):
219
221
 
220
222
  # The name of the function is now assigned
221
223
  self.stats.assigned[node.name] = node
224
+
225
+ def visit_ModifiedBlock(self, node: ModifiedBlock) -> None:
226
+ for item in node.control:
227
+ self.visit(item)
228
+ for item in node.power:
229
+ self.visit(item)
230
+
231
+ # Similarly to nested functions
232
+ from guppylang_internals.cfg.analysis import LivenessAnalysis
233
+
234
+ stats = {bb: bb.compute_variable_stats() for bb in node.cfg.bbs}
235
+ live = LivenessAnalysis(stats).run(node.cfg.bbs)
236
+ assigned_before_in_bb = self.stats.assigned.keys()
237
+ self.stats.used |= {
238
+ x: using_bb.vars.used[x]
239
+ for x, using_bb in live[node.cfg.entry_bb].items()
240
+ if x not in assigned_before_in_bb
241
+ }
@@ -9,6 +9,8 @@ from guppylang_internals.ast_util import (
9
9
  AstVisitor,
10
10
  ContextAdjuster,
11
11
  find_nodes,
12
+ loop_controls_in_loop,
13
+ return_nodes_in_ast,
12
14
  set_location_from,
13
15
  template_replace,
14
16
  with_loc,
@@ -16,19 +18,34 @@ from guppylang_internals.ast_util import (
16
18
  from guppylang_internals.cfg.bb import BB, BBStatement
17
19
  from guppylang_internals.cfg.cfg import CFG
18
20
  from guppylang_internals.checker.core import Globals
19
- from guppylang_internals.checker.errors.generic import ExpectedError, UnsupportedError
21
+ from guppylang_internals.checker.errors.generic import (
22
+ ExpectedError,
23
+ UnexpectedInWithBlockError,
24
+ UnknownModifierError,
25
+ UnsupportedError,
26
+ )
27
+ from guppylang_internals.checker.errors.type_errors import WrongNumberOfArgsError
20
28
  from guppylang_internals.diagnostic import Error
21
29
  from guppylang_internals.error import GuppyError, InternalGuppyError
22
- from guppylang_internals.experimental import check_lists_enabled
30
+ from guppylang_internals.experimental import (
31
+ check_lists_enabled,
32
+ check_modifiers_enabled,
33
+ )
23
34
  from guppylang_internals.nodes import (
24
35
  ComptimeExpr,
36
+ Control,
37
+ Dagger,
25
38
  DesugaredGenerator,
26
39
  DesugaredGeneratorExpr,
27
40
  DesugaredListComp,
28
41
  IterNext,
29
42
  MakeIter,
43
+ ModifiedBlock,
44
+ Modifier,
30
45
  NestedFunctionDef,
46
+ Power,
31
47
  )
48
+ from guppylang_internals.span import Span, to_span
32
49
  from guppylang_internals.tys.ty import NoneType
33
50
 
34
51
  # In order to build expressions, need an endless stream of unique temporary variables
@@ -135,7 +152,10 @@ class CFGBuilder(AstVisitor[BB | None]):
135
152
  Builds the expression and mutates `node.value` to point to the built expression.
136
153
  Returns the BB in which the expression is available and adds the node to it.
137
154
  """
138
- if not isinstance(node, NestedFunctionDef) and node.value is not None:
155
+ if (
156
+ not isinstance(node, NestedFunctionDef | ModifiedBlock)
157
+ and node.value is not None
158
+ ):
139
159
  node.value, bb = ExprBuilder.build(node.value, self.cfg, bb)
140
160
  bb.statements.append(node)
141
161
  return bb
@@ -265,6 +285,84 @@ class CFGBuilder(AstVisitor[BB | None]):
265
285
  bb.statements.append(new_node)
266
286
  return bb
267
287
 
288
+ def visit_With(self, node: ast.With, bb: BB, jumps: Jumps) -> BB | None:
289
+ check_modifiers_enabled(node)
290
+ self._validate_modified_block(node)
291
+
292
+ cfg = CFGBuilder().build(node.body, True, self.globals)
293
+ new_node = ModifiedBlock(
294
+ cfg=cfg,
295
+ **dict(ast.iter_fields(node)),
296
+ )
297
+
298
+ for item in node.items:
299
+ item.context_expr, bb = ExprBuilder.build(item.context_expr, self.cfg, bb)
300
+ modifier = self._handle_withitem(item)
301
+ new_node.push_modifier(modifier)
302
+
303
+ set_location_from(new_node, node)
304
+ bb.statements.append(new_node)
305
+ return bb
306
+
307
+ def _handle_withitem(self, node: ast.withitem) -> Modifier:
308
+ # Check that `as` notation is not used
309
+ if node.optional_vars is not None:
310
+ span = Span(
311
+ to_span(node.context_expr).start, to_span(node.optional_vars).end
312
+ )
313
+ raise GuppyError(UnsupportedError(span, "`as` expression", singular=True))
314
+
315
+ e = node.context_expr
316
+ modifier: Modifier
317
+ match e:
318
+ case ast.Name(id="dagger"):
319
+ modifier = Dagger(e)
320
+ case ast.Call(func=ast.Name(id="dagger")):
321
+ if len(e.args) != 0:
322
+ span = Span(to_span(e.args[0]).start, to_span(e.args[-1]).end)
323
+ raise GuppyError(WrongNumberOfArgsError(span, 0, len(e.args)))
324
+ modifier = Dagger(e)
325
+ case ast.Call(func=ast.Name(id="control")):
326
+ if len(e.args) == 0:
327
+ span = Span(to_span(e.func).end, to_span(e).end)
328
+ raise GuppyError(WrongNumberOfArgsError(span, 1, len(e.args)))
329
+ modifier = Control(e, e.args)
330
+ case ast.Call(func=ast.Name(id="power")):
331
+ if len(e.args) == 0:
332
+ span = Span(to_span(e.func).end, to_span(e).end)
333
+ raise GuppyError(WrongNumberOfArgsError(span, 1, len(e.args)))
334
+ elif len(e.args) != 1:
335
+ span = Span(to_span(e.args[1]).start, to_span(e.args[-1]).end)
336
+ raise GuppyError(WrongNumberOfArgsError(span, 1, len(e.args)))
337
+ modifier = Power(e, e.args[0])
338
+ case _:
339
+ raise GuppyError(UnknownModifierError(e))
340
+ return modifier
341
+
342
+ def _validate_modified_block(self, node: ast.With) -> None:
343
+ # Check if the body contains a return statement.
344
+ return_in_body = return_nodes_in_ast(node)
345
+ if len(return_in_body) != 0:
346
+ err = UnexpectedInWithBlockError(return_in_body[0], "return", "Return")
347
+ span = Span(
348
+ to_span(node.items[0].context_expr).start,
349
+ to_span(node.items[-1].context_expr).end,
350
+ )
351
+ err.add_sub_diagnostic(UnexpectedInWithBlockError.Modifier(span))
352
+ raise GuppyError(err)
353
+
354
+ loop_controls_in_body = loop_controls_in_loop(node)
355
+ if len(loop_controls_in_body) != 0:
356
+ lc = loop_controls_in_body[0]
357
+ kind = lc.__class__.__name__
358
+ err = UnexpectedInWithBlockError(lc, "loop control", kind)
359
+ span = Span(
360
+ to_span(node.items[0].context_expr).start,
361
+ to_span(node.items[-1].context_expr).end,
362
+ )
363
+ err.add_sub_diagnostic(UnexpectedInWithBlockError.Modifier(span))
364
+ raise GuppyError(err)
365
+
268
366
  def generic_visit(self, node: ast.AST, bb: BB, jumps: Jumps) -> BB | None:
269
367
  # When adding support for new statements, we have to remember to use the
270
368
  # ExprBuilder to transform all included expressions!
@@ -117,6 +117,10 @@ class Variable:
117
117
  """Returns a new `Variable` instance with an updated definition location."""
118
118
  return replace(self, defined_at=node)
119
119
 
120
+ def add_flags(self, flags: InputFlags) -> "Variable":
121
+ """Returns a new `Variable` instance with updated flags."""
122
+ return replace(self, flags=self.flags | flags)
123
+
120
124
 
121
125
  @dataclass(frozen=True, kw_only=True)
122
126
  class ComptimeVariable(Variable):
@@ -1,7 +1,7 @@
1
1
  from dataclasses import dataclass
2
2
  from typing import ClassVar
3
3
 
4
- from guppylang_internals.diagnostic import Error
4
+ from guppylang_internals.diagnostic import Error, Note
5
5
 
6
6
 
7
7
  @dataclass(frozen=True)
@@ -43,3 +43,34 @@ class ExpectedError(Error):
43
43
  @property
44
44
  def extra(self) -> str:
45
45
  return f", got {self.got}" if self.got else ""
46
+
47
+
48
+ @dataclass(frozen=True)
49
+ class UnknownModifierError(Error):
50
+ title: ClassVar[str] = "Unknown modifier"
51
+ span_label: ClassVar[str] = (
52
+ "Expected one of {{dagger, control(...), or power(...)}}"
53
+ )
54
+
55
+
56
+ @dataclass(frozen=True)
57
+ class UnexpectedInWithBlockError(Error):
58
+ title: ClassVar[str] = "Unexpected {kind}"
59
+ span_label: ClassVar[str] = "{things} found in a `With` block"
60
+ kind: str
61
+ things: str
62
+
63
+ @dataclass(frozen=True)
64
+ class Modifier(Note):
65
+ span_label: ClassVar[str] = "modifier is used here"
66
+
67
+
68
+ @dataclass(frozen=True)
69
+ class InvalidUnderDagger(Error):
70
+ title: ClassVar[str] = "Invalid expression in dagger"
71
+ span_label: ClassVar[str] = "{things} found in a dagger context"
72
+ things: str
73
+
74
+ @dataclass(frozen=True)
75
+ class Dagger(Note):
76
+ span_label: ClassVar[str] = "dagger modifier is used here"
@@ -95,6 +95,20 @@ class TypeInferenceError(Error):
95
95
  unsolved_ty: Type
96
96
 
97
97
 
98
+ @dataclass(frozen=True)
99
+ class ParameterInferenceError(Error):
100
+ title: ClassVar[str] = "Cannot infer generic parameter"
101
+ span_label: ClassVar[str] = (
102
+ "Cannot infer generic parameter `{param}` of this function"
103
+ )
104
+ param: str
105
+
106
+ @dataclass(frozen=True)
107
+ class SignatureHint(Note):
108
+ message: ClassVar[str] = "Function signature is `{sig}`"
109
+ sig: FunctionType
110
+
111
+
98
112
  @dataclass(frozen=True)
99
113
  class IllegalConstant(Error):
100
114
  title: ClassVar[str] = "Unsupported constant"
@@ -23,6 +23,7 @@ can be used to infer a type for an expression.
23
23
  import ast
24
24
  import sys
25
25
  import traceback
26
+ from collections.abc import Sequence
26
27
  from contextlib import suppress
27
28
  from dataclasses import replace
28
29
  from types import ModuleType
@@ -74,6 +75,7 @@ from guppylang_internals.checker.errors.type_errors import (
74
75
  ModuleMemberNotFoundError,
75
76
  NonLinearInstantiateError,
76
77
  NotCallableError,
78
+ ParameterInferenceError,
77
79
  TupleIndexOutOfBoundsError,
78
80
  TypeApplyNotGenericError,
79
81
  TypeInferenceError,
@@ -130,7 +132,7 @@ from guppylang_internals.tys.builtin import (
130
132
  string_type,
131
133
  )
132
134
  from guppylang_internals.tys.const import Const, ConstValue
133
- from guppylang_internals.tys.param import ConstParam, TypeParam
135
+ from guppylang_internals.tys.param import ConstParam, TypeParam, check_all_args
134
136
  from guppylang_internals.tys.parsing import arg_from_ast
135
137
  from guppylang_internals.tys.subst import Inst, Subst
136
138
  from guppylang_internals.tys.ty import (
@@ -149,6 +151,7 @@ from guppylang_internals.tys.ty import (
149
151
  parse_function_tensor,
150
152
  unify,
151
153
  )
154
+ from guppylang_internals.tys.var import ExistentialVar
152
155
 
153
156
  if TYPE_CHECKING:
154
157
  from guppylang_internals.diagnostic import SubDiagnostic
@@ -462,8 +465,15 @@ class ExprSynthesizer(AstVisitor[tuple[ast.expr, Type]]):
462
465
  # A `value.attr` attribute access. Unfortunately, the `attr` is just a string,
463
466
  # not an AST node, so we have to compute its span by hand. This is fine since
464
467
  # linebreaks are not allowed in the identifier following the `.`
468
+ # The only exception are attributes accesses that are generated during
469
+ # desugaring (for example for iterators in `for` loops). Since those just
470
+ # inherit the span of the sugared code, we could have line breaks there.
471
+ # See https://github.com/CQCL/guppylang/issues/1301
465
472
  span = to_span(node)
466
- attr_span = Span(span.end.shift_left(len(node.attr)), span.end)
473
+ if span.start.line == span.end.line:
474
+ attr_span = Span(span.end.shift_left(len(node.attr)), span.end)
475
+ else:
476
+ attr_span = span
467
477
  if module := self._is_python_module(node.value):
468
478
  if node.attr in module.__dict__:
469
479
  val = module.__dict__[node.attr]
@@ -928,10 +938,9 @@ def check_type_apply(ty: FunctionType, node: ast.Subscript, ctx: Context) -> Ins
928
938
  err.add_sub_diagnostic(WrongNumberOfArgsError.SignatureHint(None, ty))
929
939
  raise GuppyError(err)
930
940
 
931
- return [
932
- param.check_arg(arg_from_ast(arg_expr, ctx.parsing_ctx), arg_expr)
933
- for arg_expr, param in zip(arg_exprs, ty.params, strict=True)
934
- ]
941
+ inst = [arg_from_ast(node, ctx.parsing_ctx) for node in arg_exprs]
942
+ check_all_args(ty.params, inst, "", node, arg_exprs)
943
+ return inst
935
944
 
936
945
 
937
946
  def check_num_args(
@@ -975,15 +984,17 @@ def type_check_args(
975
984
  comptime_args = iter(func_ty.comptime_args)
976
985
  for inp, func_inp in zip(inputs, func_ty.inputs, strict=True):
977
986
  a, s = ExprChecker(ctx).check(inp, func_inp.ty.substitute(subst), "argument")
987
+ subst |= s
978
988
  if InputFlags.Inout in func_inp.flags and isinstance(a, PlaceNode):
979
989
  a.place = check_place_assignable(
980
990
  a.place, ctx, a, "able to borrow subscripted elements"
981
991
  )
982
992
  if InputFlags.Comptime in func_inp.flags:
983
993
  comptime_arg = next(comptime_args)
984
- s = check_comptime_arg(a, comptime_arg.const, func_inp.ty, s)
994
+ const = comptime_arg.const.substitute(subst)
995
+ s = check_comptime_arg(a, const, func_inp.ty.substitute(subst), subst)
996
+ subst |= s
985
997
  new_args.append(a)
986
- subst |= s
987
998
  assert next(comptime_args, None) is None
988
999
 
989
1000
  # If the argument check succeeded, this means that we must have found instantiations
@@ -1103,7 +1114,7 @@ def synthesize_call(
1103
1114
 
1104
1115
  # Success implies that the substitution is closed
1105
1116
  assert all(not t.unsolved_vars for t in subst.values())
1106
- inst = [subst[v].to_arg() for v in free_vars]
1117
+ inst = check_all_solved(subst, free_vars, func_ty, node)
1107
1118
 
1108
1119
  # Finally, check that the instantiation respects the linearity requirements
1109
1120
  check_inst(func_ty, inst, node)
@@ -1182,7 +1193,7 @@ def check_call(
1182
1193
 
1183
1194
  # Success implies that the substitution is closed
1184
1195
  assert all(not t.unsolved_vars for t in subst.values())
1185
- inst = [subst[v].to_arg() for v in free_vars]
1196
+ inst = check_all_solved(subst, free_vars, func_ty, node)
1186
1197
  subst = {v: t for v, t in subst.items() if v in ty.unsolved_vars}
1187
1198
 
1188
1199
  # Finally, check that the instantiation respects the linearity requirements
@@ -1191,12 +1202,37 @@ def check_call(
1191
1202
  return inputs, subst, inst
1192
1203
 
1193
1204
 
1205
+ def check_all_solved(
1206
+ subst: Subst,
1207
+ free_vars: Sequence[ExistentialVar],
1208
+ func_ty: FunctionType,
1209
+ loc: AstNode,
1210
+ ) -> Inst:
1211
+ """Checks that a substitution solves all parameters of a function.
1212
+
1213
+ Using 3.12 generic syntax, users can declare parameters that don't occur in the
1214
+ signature. Those will remain unsolved, even after unifying all function arguments,
1215
+ so we have to perform this extra check.
1216
+
1217
+ Returns an instantiation of all free variables, or emits a user error if some are
1218
+ not solved.
1219
+ """
1220
+ for v in free_vars:
1221
+ if v not in subst:
1222
+ err = ParameterInferenceError(loc, v.display_name)
1223
+ err.add_sub_diagnostic(ParameterInferenceError.SignatureHint(None, func_ty))
1224
+ raise GuppyTypeInferenceError(err)
1225
+ return [subst[v].to_arg() for v in free_vars]
1226
+
1227
+
1194
1228
  def check_inst(func_ty: FunctionType, inst: Inst, node: AstNode) -> None:
1195
1229
  """Checks if an instantiation is valid.
1196
1230
 
1197
1231
  Makes sure that the linearity requirements are satisfied.
1198
1232
  """
1199
1233
  for param, arg in zip(func_ty.params, inst, strict=True):
1234
+ param = param.instantiate_bounds(inst)
1235
+
1200
1236
  # Give a more informative error message for linearity issues
1201
1237
  if isinstance(param, TypeParam) and isinstance(arg, TypeArg):
1202
1238
  if param.must_be_copyable and not arg.ty.copyable:
@@ -276,7 +276,7 @@ def check_signature(
276
276
  param_var_mapping: dict[str, Parameter] = {}
277
277
  if sys.version_info >= (3, 12):
278
278
  for i, param_node in enumerate(func_def.type_params):
279
- param = parse_parameter(param_node, i, globals)
279
+ param = parse_parameter(param_node, i, globals, param_var_mapping)
280
280
  param_var_mapping[param.name] = param
281
281
 
282
282
  # Figure out if this is a method
@@ -52,6 +52,7 @@ from guppylang_internals.error import GuppyError, GuppyTypeError
52
52
  from guppylang_internals.nodes import (
53
53
  AnyCall,
54
54
  BarrierExpr,
55
+ CheckedModifiedBlock,
55
56
  CheckedNestedFunctionDef,
56
57
  DesugaredArrayComp,
57
58
  DesugaredGenerator,
@@ -621,6 +622,70 @@ class BBLinearityChecker(ast.NodeVisitor):
621
622
  elif not place.ty.copyable:
622
623
  raise GuppyTypeError(ComprAlreadyUsedError(use.node, place, use.kind))
623
624
 
625
+ def visit_CheckedModifiedBlock(self, node: CheckedModifiedBlock) -> None:
626
+ # Linear usage of variables in a with statement
627
+ # ```
628
+ # with control(c1, c2, ...):
629
+ # body(q1, q2, ...) # captured variables
630
+ # ````
631
+ # is the same as to assume that this is a function call
632
+ # `WithCtrl(q1, q2, ..., c1, c2, ...)`
633
+ # where `WithCtrl` is a function that takes the control as mutable references.
634
+ # Therefore, we apply the same linearity rules as for function arguments.
635
+ # ```
636
+ # def WithCtrl(q1, q2, ..., c1, c2, ...):
637
+ # body(q1, q2, ...)
638
+ # ```
639
+
640
+ # check control
641
+ for ctrl in node.control:
642
+ for arg in ctrl.ctrl:
643
+ if isinstance(arg, PlaceNode):
644
+ self.visit_PlaceNode(arg, use_kind=UseKind.BORROW, is_call_arg=None)
645
+ else:
646
+ ty = get_type(arg)
647
+ unnamed_err = UnnamedExprNotUsedError(arg, ty)
648
+ unnamed_err.add_sub_diagnostic(UnnamedExprNotUsedError.Fix(None))
649
+ raise GuppyTypeError(unnamed_err)
650
+
651
+ # check power
652
+ for power in node.power:
653
+ if isinstance(power.iter, PlaceNode):
654
+ self.visit_PlaceNode(
655
+ power.iter, use_kind=UseKind.CONSUME, is_call_arg=None
656
+ )
657
+ else:
658
+ self.visit(power.iter)
659
+
660
+ # check captured variables
661
+ for var, use in node.captured.values():
662
+ for place in leaf_places(var):
663
+ use_kind = (
664
+ UseKind.BORROW if InputFlags.Inout in var.flags else UseKind.CONSUME
665
+ )
666
+
667
+ x = place.id
668
+ if (prev_use := self.scope.used(x)) and not place.ty.copyable:
669
+ used_err = AlreadyUsedError(use, place, use_kind)
670
+ used_err.add_sub_diagnostic(
671
+ AlreadyUsedError.PrevUse(prev_use.node, prev_use.kind)
672
+ )
673
+ if has_explicit_copy(place.ty):
674
+ used_err.add_sub_diagnostic(AlreadyUsedError.MakeCopy(None))
675
+ raise GuppyError(used_err)
676
+ self.scope.use(x, node, use_kind)
677
+
678
+ # reassign controls
679
+ for ctrl in node.control:
680
+ for arg in ctrl.ctrl:
681
+ assert isinstance(arg, PlaceNode) # Checked above
682
+ self._reassign_single_inout_arg(arg.place, arg.place.defined_at or arg)
683
+
684
+ # reassign captured variables
685
+ for var, use in node.captured.values():
686
+ if InputFlags.Inout in var.flags:
687
+ self._reassign_single_inout_arg(var, var.defined_at or use)
688
+
624
689
 
625
690
  def leaf_places(place: Place) -> Iterator[Place]:
626
691
  """Returns all leaf descendant projections of a place."""