guppylang 0.1.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.
- guppylang-0.1.0/PKG-INFO +139 -0
- guppylang-0.1.0/README.md +117 -0
- guppylang-0.1.0/guppylang/__init__.py +5 -0
- guppylang-0.1.0/guppylang/ast_util.py +327 -0
- guppylang-0.1.0/guppylang/cfg/__init__.py +0 -0
- guppylang-0.1.0/guppylang/cfg/analysis.py +186 -0
- guppylang-0.1.0/guppylang/cfg/bb.py +166 -0
- guppylang-0.1.0/guppylang/cfg/builder.py +514 -0
- guppylang-0.1.0/guppylang/cfg/cfg.py +69 -0
- guppylang-0.1.0/guppylang/checker/__init__.py +0 -0
- guppylang-0.1.0/guppylang/checker/cfg_checker.py +241 -0
- guppylang-0.1.0/guppylang/checker/core.py +209 -0
- guppylang-0.1.0/guppylang/checker/expr_checker.py +944 -0
- guppylang-0.1.0/guppylang/checker/func_checker.py +207 -0
- guppylang-0.1.0/guppylang/checker/stmt_checker.py +154 -0
- guppylang-0.1.0/guppylang/compiler/__init__.py +0 -0
- guppylang-0.1.0/guppylang/compiler/cfg_compiler.py +177 -0
- guppylang-0.1.0/guppylang/compiler/core.py +120 -0
- guppylang-0.1.0/guppylang/compiler/expr_compiler.py +331 -0
- guppylang-0.1.0/guppylang/compiler/func_compiler.py +121 -0
- guppylang-0.1.0/guppylang/compiler/stmt_compiler.py +92 -0
- guppylang-0.1.0/guppylang/custom.py +251 -0
- guppylang-0.1.0/guppylang/declared.py +73 -0
- guppylang-0.1.0/guppylang/decorator.py +299 -0
- guppylang-0.1.0/guppylang/error.py +195 -0
- guppylang-0.1.0/guppylang/gtypes.py +654 -0
- guppylang-0.1.0/guppylang/hugr/__init__.py +0 -0
- guppylang-0.1.0/guppylang/hugr/hugr.py +799 -0
- guppylang-0.1.0/guppylang/hugr/ops.py +475 -0
- guppylang-0.1.0/guppylang/hugr/raw.py +23 -0
- guppylang-0.1.0/guppylang/hugr/tys.py +306 -0
- guppylang-0.1.0/guppylang/hugr/val.py +60 -0
- guppylang-0.1.0/guppylang/hugr/visualise.py +285 -0
- guppylang-0.1.0/guppylang/module.py +303 -0
- guppylang-0.1.0/guppylang/nodes.py +184 -0
- guppylang-0.1.0/guppylang/prelude/__init__.py +0 -0
- guppylang-0.1.0/guppylang/prelude/_internal.py +327 -0
- guppylang-0.1.0/guppylang/prelude/builtins.py +885 -0
- guppylang-0.1.0/guppylang/prelude/quantum.py +130 -0
- guppylang-0.1.0/pyproject.toml +50 -0
guppylang-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: guppylang
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary:
|
|
5
|
+
Home-page: https://github.com/CQCL/guppy
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Author: Mark Koch
|
|
8
|
+
Author-email: mark.koch@quantinuum.com
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Dist: graphviz (>=0.20.1,<0.21.0)
|
|
16
|
+
Requires-Dist: networkx (>=3.2.1,<4.0.0)
|
|
17
|
+
Requires-Dist: pydantic (>=2.5.3,<3.0.0)
|
|
18
|
+
Requires-Dist: typing-extensions (>=4.9.0,<5.0.0)
|
|
19
|
+
Project-URL: Repository, https://github.com/CQCL/guppy
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# Guppy
|
|
23
|
+
|
|
24
|
+
Guppy is a quantum programming language that is fully embedded into Python.
|
|
25
|
+
It allows you to write high-level hybrid quantum programs with classical control flow and mid-circuit measurements using Pythonic syntax:
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from guppylang import guppy, Qubit, quantum
|
|
29
|
+
|
|
30
|
+
guppy.load(quantum)
|
|
31
|
+
|
|
32
|
+
# Teleports the state in `src` to `tgt`.
|
|
33
|
+
@guppy
|
|
34
|
+
def teleport(src: Qubit, tgt: Qubit) -> Qubit:
|
|
35
|
+
# Create ancilla and entangle it with src and tgt
|
|
36
|
+
tmp = Qubit()
|
|
37
|
+
tmp, tgt = cx(h(tmp), tgt)
|
|
38
|
+
src, tmp = cx(src, tmp)
|
|
39
|
+
|
|
40
|
+
# Apply classical corrections
|
|
41
|
+
if measure(h(src)):
|
|
42
|
+
tgt = z(tgt)
|
|
43
|
+
if measure(tmp):
|
|
44
|
+
tgt = x(tgt)
|
|
45
|
+
return tgt
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
More examples and tutorials are available [here][examples].
|
|
49
|
+
|
|
50
|
+
[examples]: ./examples/
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
## Install
|
|
54
|
+
|
|
55
|
+
Guppy can be installed via `pip`. Requires Python >= 3.10.
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
pip install guppylang
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
## Usage
|
|
63
|
+
|
|
64
|
+
See the [Getting Started][getting-started] guide and the other [examples].
|
|
65
|
+
|
|
66
|
+
[getting-started]: ./examples/1-Getting-Started.md
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
## Development
|
|
70
|
+
|
|
71
|
+
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.
|
|
72
|
+
|
|
73
|
+
### Prerequisites
|
|
74
|
+
|
|
75
|
+
- Python >= 3.10
|
|
76
|
+
- [Poetry](https://python-poetry.org/docs/#installation)
|
|
77
|
+
- [Rust](https://www.rust-lang.org/tools/install) >= 1.75.0 (only needed for tests)
|
|
78
|
+
|
|
79
|
+
### Installing
|
|
80
|
+
|
|
81
|
+
Run the following to setup your virtual environment and install dependencies:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
poetry install --with validation
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Note that the `--with validation` flag is optional and only needed to run integration tests.
|
|
88
|
+
|
|
89
|
+
You can then activate the virtual environment and work within it with:
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
poetry shell
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Consider using [direnv](https://github.com/direnv/direnv/wiki/Python#poetry) to
|
|
96
|
+
automate this when entering and leaving a directory.
|
|
97
|
+
|
|
98
|
+
To run a single command in the shell, just prefix it with `poetry run`.
|
|
99
|
+
|
|
100
|
+
### Pre-commit
|
|
101
|
+
|
|
102
|
+
Install the pre-commit hook by running:
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
poetry run pre-commit install
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
### Testing
|
|
110
|
+
|
|
111
|
+
Run tests using
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
poetry run pytest -v
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
You have to install extra dependencies to test automatic circuit conversion from `pytket`:
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
poetry install --with pytket
|
|
121
|
+
poetry run pytest -v # Now rerun tests
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
Integration test cases can be exported to a directory using
|
|
126
|
+
|
|
127
|
+
```sh
|
|
128
|
+
poetry run pytest --export-test-cases=guppy-exports
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
which will create a directory `./guppy-exports` populated with hugr modules serialised in JSON.
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
This project is licensed under Apache License, Version 2.0 ([LICENCE][] or http://www.apache.org/licenses/LICENSE-2.0).
|
|
137
|
+
|
|
138
|
+
[LICENCE]: ./LICENCE
|
|
139
|
+
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Guppy
|
|
2
|
+
|
|
3
|
+
Guppy is a quantum programming language that is fully embedded into Python.
|
|
4
|
+
It allows you to write high-level hybrid quantum programs with classical control flow and mid-circuit measurements using Pythonic syntax:
|
|
5
|
+
|
|
6
|
+
```python
|
|
7
|
+
from guppylang import guppy, Qubit, quantum
|
|
8
|
+
|
|
9
|
+
guppy.load(quantum)
|
|
10
|
+
|
|
11
|
+
# Teleports the state in `src` to `tgt`.
|
|
12
|
+
@guppy
|
|
13
|
+
def teleport(src: Qubit, tgt: Qubit) -> Qubit:
|
|
14
|
+
# Create ancilla and entangle it with src and tgt
|
|
15
|
+
tmp = Qubit()
|
|
16
|
+
tmp, tgt = cx(h(tmp), tgt)
|
|
17
|
+
src, tmp = cx(src, tmp)
|
|
18
|
+
|
|
19
|
+
# Apply classical corrections
|
|
20
|
+
if measure(h(src)):
|
|
21
|
+
tgt = z(tgt)
|
|
22
|
+
if measure(tmp):
|
|
23
|
+
tgt = x(tgt)
|
|
24
|
+
return tgt
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
More examples and tutorials are available [here][examples].
|
|
28
|
+
|
|
29
|
+
[examples]: ./examples/
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
Guppy can be installed via `pip`. Requires Python >= 3.10.
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
pip install guppylang
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
See the [Getting Started][getting-started] guide and the other [examples].
|
|
44
|
+
|
|
45
|
+
[getting-started]: ./examples/1-Getting-Started.md
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
## Development
|
|
49
|
+
|
|
50
|
+
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.
|
|
51
|
+
|
|
52
|
+
### Prerequisites
|
|
53
|
+
|
|
54
|
+
- Python >= 3.10
|
|
55
|
+
- [Poetry](https://python-poetry.org/docs/#installation)
|
|
56
|
+
- [Rust](https://www.rust-lang.org/tools/install) >= 1.75.0 (only needed for tests)
|
|
57
|
+
|
|
58
|
+
### Installing
|
|
59
|
+
|
|
60
|
+
Run the following to setup your virtual environment and install dependencies:
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
poetry install --with validation
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Note that the `--with validation` flag is optional and only needed to run integration tests.
|
|
67
|
+
|
|
68
|
+
You can then activate the virtual environment and work within it with:
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
poetry shell
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Consider using [direnv](https://github.com/direnv/direnv/wiki/Python#poetry) to
|
|
75
|
+
automate this when entering and leaving a directory.
|
|
76
|
+
|
|
77
|
+
To run a single command in the shell, just prefix it with `poetry run`.
|
|
78
|
+
|
|
79
|
+
### Pre-commit
|
|
80
|
+
|
|
81
|
+
Install the pre-commit hook by running:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
poetry run pre-commit install
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
### Testing
|
|
89
|
+
|
|
90
|
+
Run tests using
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
poetry run pytest -v
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
You have to install extra dependencies to test automatic circuit conversion from `pytket`:
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
poetry install --with pytket
|
|
100
|
+
poetry run pytest -v # Now rerun tests
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
Integration test cases can be exported to a directory using
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
poetry run pytest --export-test-cases=guppy-exports
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
which will create a directory `./guppy-exports` populated with hugr modules serialised in JSON.
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
This project is licensed under Apache License, Version 2.0 ([LICENCE][] or http://www.apache.org/licenses/LICENSE-2.0).
|
|
116
|
+
|
|
117
|
+
[LICENCE]: ./LICENCE
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import textwrap
|
|
3
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Generic, Optional, TypeVar, cast
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from guppylang.gtypes import GuppyType
|
|
9
|
+
|
|
10
|
+
AstNode = (
|
|
11
|
+
ast.AST
|
|
12
|
+
| ast.operator
|
|
13
|
+
| ast.expr
|
|
14
|
+
| ast.arg
|
|
15
|
+
| ast.stmt
|
|
16
|
+
| ast.Name
|
|
17
|
+
| ast.keyword
|
|
18
|
+
| ast.FunctionDef
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
T = TypeVar("T", covariant=True)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AstVisitor(Generic[T]):
|
|
25
|
+
"""
|
|
26
|
+
Note: This class is based on the implementation of `ast.NodeVisitor` but
|
|
27
|
+
allows extra arguments to be passed to the `visit` functions.
|
|
28
|
+
|
|
29
|
+
Original documentation:
|
|
30
|
+
|
|
31
|
+
A node visitor base class that walks the abstract syntax tree and calls a
|
|
32
|
+
visitor function for every node found. This function may return a value
|
|
33
|
+
which is forwarded by the `visit` method.
|
|
34
|
+
|
|
35
|
+
This class is meant to be subclassed, with the subclass adding visitor
|
|
36
|
+
methods.
|
|
37
|
+
|
|
38
|
+
Per default the visitor functions for the nodes are ``'visit_'`` +
|
|
39
|
+
class name of the node. So a `TryFinally` node visit function would
|
|
40
|
+
be `visit_TryFinally`. This behavior can be changed by overriding
|
|
41
|
+
the `visit` method. If no visitor function exists for a node
|
|
42
|
+
(return value `None`) the `generic_visit` visitor is used instead.
|
|
43
|
+
|
|
44
|
+
Don't use the `NodeVisitor` if you want to apply changes to nodes during
|
|
45
|
+
traversing. For this a special visitor exists (`NodeTransformer`) that
|
|
46
|
+
allows modifications.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def visit(self, node: Any, *args: Any, **kwargs: Any) -> T:
|
|
50
|
+
"""Visit a node."""
|
|
51
|
+
method = "visit_" + node.__class__.__name__
|
|
52
|
+
visitor = getattr(self, method, self.generic_visit)
|
|
53
|
+
return visitor(node, *args, **kwargs)
|
|
54
|
+
|
|
55
|
+
def generic_visit(self, node: Any, *args: Any, **kwargs: Any) -> T:
|
|
56
|
+
"""Called if no explicit visitor function exists for a node."""
|
|
57
|
+
raise NotImplementedError(f"visit_{node.__class__.__name__} is not implemented")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class AstSearcher(ast.NodeVisitor):
|
|
61
|
+
"""Visitor that searches for occurrences of specific nodes in an AST."""
|
|
62
|
+
|
|
63
|
+
matcher: Callable[[ast.AST], bool]
|
|
64
|
+
dont_recurse_into: set[type[ast.AST]]
|
|
65
|
+
found: list[ast.AST]
|
|
66
|
+
is_first_node: bool
|
|
67
|
+
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
matcher: Callable[[ast.AST], bool],
|
|
71
|
+
dont_recurse_into: set[type[ast.AST]] | None = None,
|
|
72
|
+
) -> None:
|
|
73
|
+
self.matcher = matcher
|
|
74
|
+
self.dont_recurse_into = dont_recurse_into or set()
|
|
75
|
+
self.found = []
|
|
76
|
+
self.is_first_node = True
|
|
77
|
+
|
|
78
|
+
def generic_visit(self, node: ast.AST) -> None:
|
|
79
|
+
if self.matcher(node):
|
|
80
|
+
self.found.append(node)
|
|
81
|
+
if self.is_first_node or type(node) not in self.dont_recurse_into:
|
|
82
|
+
self.is_first_node = False
|
|
83
|
+
super().generic_visit(node)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def find_nodes(
|
|
87
|
+
matcher: Callable[[ast.AST], bool],
|
|
88
|
+
node: ast.AST,
|
|
89
|
+
dont_recurse_into: set[type[ast.AST]] | None = None,
|
|
90
|
+
) -> list[ast.AST]:
|
|
91
|
+
"""Returns all nodes in the AST that satisfy the matcher."""
|
|
92
|
+
v = AstSearcher(matcher, dont_recurse_into)
|
|
93
|
+
v.visit(node)
|
|
94
|
+
return v.found
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def name_nodes_in_ast(node: Any) -> list[ast.Name]:
|
|
98
|
+
"""Returns all `Name` nodes occurring in an AST."""
|
|
99
|
+
found = find_nodes(lambda n: isinstance(n, ast.Name), node)
|
|
100
|
+
return cast(list[ast.Name], found)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def return_nodes_in_ast(node: Any) -> list[ast.Return]:
|
|
104
|
+
"""Returns all `Return` nodes occurring in an AST."""
|
|
105
|
+
found = find_nodes(lambda n: isinstance(n, ast.Return), node, {ast.FunctionDef})
|
|
106
|
+
return cast(list[ast.Return], found)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def breaks_in_loop(node: Any) -> list[ast.Break]:
|
|
110
|
+
"""Returns all `Break` nodes occurring in a loop.
|
|
111
|
+
|
|
112
|
+
Note that breaks in nested loops are excluded.
|
|
113
|
+
"""
|
|
114
|
+
found = find_nodes(
|
|
115
|
+
lambda n: isinstance(n, ast.Break), node, {ast.For, ast.While, ast.FunctionDef}
|
|
116
|
+
)
|
|
117
|
+
return cast(list[ast.Break], found)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class ContextAdjuster(ast.NodeTransformer):
|
|
121
|
+
"""Updates the `ast.Context` indicating if expressions occur on the LHS or RHS."""
|
|
122
|
+
|
|
123
|
+
ctx: ast.expr_context
|
|
124
|
+
|
|
125
|
+
def __init__(self, ctx: ast.expr_context) -> None:
|
|
126
|
+
self.ctx = ctx
|
|
127
|
+
|
|
128
|
+
def visit(self, node: ast.AST) -> ast.AST:
|
|
129
|
+
return cast(ast.AST, super().visit(node))
|
|
130
|
+
|
|
131
|
+
def visit_Name(self, node: ast.Name) -> ast.Name:
|
|
132
|
+
return with_loc(node, ast.Name(id=node.id, ctx=self.ctx))
|
|
133
|
+
|
|
134
|
+
def visit_Starred(self, node: ast.Starred) -> ast.Starred:
|
|
135
|
+
return with_loc(node, ast.Starred(value=self.visit(node.value), ctx=self.ctx))
|
|
136
|
+
|
|
137
|
+
def visit_Tuple(self, node: ast.Tuple) -> ast.Tuple:
|
|
138
|
+
return with_loc(
|
|
139
|
+
node, ast.Tuple(elts=[self.visit(elt) for elt in node.elts], ctx=self.ctx)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
def visit_List(self, node: ast.List) -> ast.List:
|
|
143
|
+
return with_loc(
|
|
144
|
+
node, ast.List(elts=[self.visit(elt) for elt in node.elts], ctx=self.ctx)
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def visit_Subscript(self, node: ast.Subscript) -> ast.Subscript:
|
|
148
|
+
# Don't adjust the slice!
|
|
149
|
+
return with_loc(
|
|
150
|
+
node,
|
|
151
|
+
ast.Subscript(value=self.visit(node.value), slice=node.slice, ctx=self.ctx),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
def visit_Attribute(self, node: ast.Attribute) -> ast.Attribute:
|
|
155
|
+
return with_loc(
|
|
156
|
+
node,
|
|
157
|
+
ast.Attribute(value=self.visit(node.value), attr=node.attr, ctx=self.ctx),
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@dataclass(frozen=True, eq=False)
|
|
162
|
+
class TemplateReplacer(ast.NodeTransformer):
|
|
163
|
+
"""Replaces nodes in a template."""
|
|
164
|
+
|
|
165
|
+
replacements: Mapping[str, ast.AST | Sequence[ast.AST]]
|
|
166
|
+
default_loc: ast.AST
|
|
167
|
+
|
|
168
|
+
def _get_replacement(self, x: str) -> ast.AST | Sequence[ast.AST]:
|
|
169
|
+
if x not in self.replacements:
|
|
170
|
+
msg = f"No replacement for `{x}` is given"
|
|
171
|
+
raise ValueError(msg)
|
|
172
|
+
return self.replacements[x]
|
|
173
|
+
|
|
174
|
+
def visit_Name(self, node: ast.Name) -> ast.AST:
|
|
175
|
+
repl = self._get_replacement(node.id)
|
|
176
|
+
if not isinstance(repl, ast.expr):
|
|
177
|
+
msg = f"Replacement for `{node.id}` must be an expression"
|
|
178
|
+
raise TypeError(msg)
|
|
179
|
+
|
|
180
|
+
# Update the context
|
|
181
|
+
adjuster = ContextAdjuster(node.ctx)
|
|
182
|
+
return with_loc(repl, adjuster.visit(repl))
|
|
183
|
+
|
|
184
|
+
def visit_Expr(self, node: ast.Expr) -> ast.AST | Sequence[ast.AST]:
|
|
185
|
+
if isinstance(node.value, ast.Name):
|
|
186
|
+
repl = self._get_replacement(node.value.id)
|
|
187
|
+
repls = [repl] if not isinstance(repl, Sequence) else repl
|
|
188
|
+
# Wrap expressions to turn them into statements
|
|
189
|
+
return [
|
|
190
|
+
with_loc(r, ast.Expr(value=r)) if isinstance(r, ast.expr) else r
|
|
191
|
+
for r in repls
|
|
192
|
+
]
|
|
193
|
+
return self.generic_visit(node)
|
|
194
|
+
|
|
195
|
+
def generic_visit(self, node: ast.AST) -> ast.AST:
|
|
196
|
+
# Insert the default location
|
|
197
|
+
node = super().generic_visit(node)
|
|
198
|
+
return with_loc(self.default_loc, node)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def template_replace(
|
|
202
|
+
template: str, default_loc: ast.AST, **kwargs: ast.AST | Sequence[ast.AST]
|
|
203
|
+
) -> list[ast.stmt]:
|
|
204
|
+
"""Turns a template into a proper AST by substituting all placeholders."""
|
|
205
|
+
nodes = ast.parse(textwrap.dedent(template)).body
|
|
206
|
+
replacer = TemplateReplacer(kwargs, default_loc)
|
|
207
|
+
new_nodes = []
|
|
208
|
+
for n in nodes:
|
|
209
|
+
new = replacer.visit(n)
|
|
210
|
+
if isinstance(new, list):
|
|
211
|
+
new_nodes.extend(new)
|
|
212
|
+
else:
|
|
213
|
+
new_nodes.append(new)
|
|
214
|
+
return new_nodes
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def line_col(node: ast.AST) -> tuple[int, int]:
|
|
218
|
+
"""Returns the line and column of an ast node."""
|
|
219
|
+
return node.lineno, node.col_offset
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def set_location_from(node: ast.AST, loc: ast.AST) -> None:
|
|
223
|
+
"""Copy source location from one AST node to the other."""
|
|
224
|
+
node.lineno = loc.lineno
|
|
225
|
+
node.col_offset = loc.col_offset
|
|
226
|
+
node.end_lineno = loc.end_lineno
|
|
227
|
+
node.end_col_offset = loc.end_col_offset
|
|
228
|
+
|
|
229
|
+
source, file, line_offset = get_source(loc), get_file(loc), get_line_offset(loc)
|
|
230
|
+
assert source is not None
|
|
231
|
+
assert file is not None
|
|
232
|
+
assert line_offset is not None
|
|
233
|
+
annotate_location(node, source, file, line_offset)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def annotate_location(
|
|
237
|
+
node: ast.AST, source: str, file: str, line_offset: int, recurse: bool = True
|
|
238
|
+
) -> None:
|
|
239
|
+
node.line_offset = line_offset # type: ignore[attr-defined]
|
|
240
|
+
node.file = file # type: ignore[attr-defined]
|
|
241
|
+
node.source = source # type: ignore[attr-defined]
|
|
242
|
+
|
|
243
|
+
if recurse:
|
|
244
|
+
for _field, value in ast.iter_fields(node):
|
|
245
|
+
if isinstance(value, list):
|
|
246
|
+
for item in value:
|
|
247
|
+
if isinstance(item, ast.AST):
|
|
248
|
+
annotate_location(item, source, file, line_offset, recurse)
|
|
249
|
+
elif isinstance(value, ast.AST):
|
|
250
|
+
annotate_location(value, source, file, line_offset, recurse)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def get_file(node: AstNode) -> str | None:
|
|
254
|
+
"""Tries to retrieve a file annotation from an AST node."""
|
|
255
|
+
try:
|
|
256
|
+
file = node.file # type: ignore[union-attr]
|
|
257
|
+
return file if isinstance(file, str) else None
|
|
258
|
+
except AttributeError:
|
|
259
|
+
return None
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def get_source(node: AstNode) -> str | None:
|
|
263
|
+
"""Tries to retrieve a source annotation from an AST node."""
|
|
264
|
+
try:
|
|
265
|
+
source = node.source # type: ignore[union-attr]
|
|
266
|
+
return source if isinstance(source, str) else None
|
|
267
|
+
except AttributeError:
|
|
268
|
+
return None
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def get_line_offset(node: AstNode) -> int | None:
|
|
272
|
+
"""Tries to retrieve a line offset annotation from an AST node."""
|
|
273
|
+
try:
|
|
274
|
+
line_offset = node.line_offset # type: ignore[union-attr]
|
|
275
|
+
return line_offset if isinstance(line_offset, int) else None
|
|
276
|
+
except AttributeError:
|
|
277
|
+
return None
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
A = TypeVar("A", bound=ast.AST)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def with_loc(loc: ast.AST, node: A) -> A:
|
|
284
|
+
"""Copy source location from one AST node to the other."""
|
|
285
|
+
set_location_from(node, loc)
|
|
286
|
+
return node
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def with_type(ty: "GuppyType", node: A) -> A:
|
|
290
|
+
"""Annotates an AST node with a type."""
|
|
291
|
+
node.type = ty # type: ignore[attr-defined]
|
|
292
|
+
return node
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def get_type_opt(node: AstNode) -> Optional["GuppyType"]:
|
|
296
|
+
"""Tries to retrieve a type annotation from an AST node."""
|
|
297
|
+
from guppylang.gtypes import GuppyType
|
|
298
|
+
|
|
299
|
+
try:
|
|
300
|
+
ty = node.type # type: ignore[union-attr]
|
|
301
|
+
return ty if isinstance(ty, GuppyType) else None
|
|
302
|
+
except AttributeError:
|
|
303
|
+
return None
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def get_type(node: AstNode) -> "GuppyType":
|
|
307
|
+
"""Retrieve a type annotation from an AST node.
|
|
308
|
+
|
|
309
|
+
Fails if the node is not annotated.
|
|
310
|
+
"""
|
|
311
|
+
ty = get_type_opt(node)
|
|
312
|
+
assert ty is not None
|
|
313
|
+
return ty
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def has_empty_body(func_ast: ast.FunctionDef) -> bool:
|
|
317
|
+
"""Returns `True` if the body of a function definition is empty.
|
|
318
|
+
|
|
319
|
+
This is the case if the body only contains a single `pass` statement or an ellipsis
|
|
320
|
+
`...` expression.
|
|
321
|
+
"""
|
|
322
|
+
if len(func_ast.body) == 0:
|
|
323
|
+
return True
|
|
324
|
+
if len(func_ast.body) > 1:
|
|
325
|
+
return False
|
|
326
|
+
[n] = func_ast.body
|
|
327
|
+
return isinstance(n, ast.Expr) and isinstance(n.value, ast.Ellipsis)
|
|
File without changes
|