guppylang-internals 0.21.0__py3-none-any.whl
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_internals/__init__.py +3 -0
- guppylang_internals/ast_util.py +350 -0
- guppylang_internals/cfg/__init__.py +0 -0
- guppylang_internals/cfg/analysis.py +230 -0
- guppylang_internals/cfg/bb.py +221 -0
- guppylang_internals/cfg/builder.py +606 -0
- guppylang_internals/cfg/cfg.py +117 -0
- guppylang_internals/checker/__init__.py +0 -0
- guppylang_internals/checker/cfg_checker.py +388 -0
- guppylang_internals/checker/core.py +550 -0
- guppylang_internals/checker/errors/__init__.py +0 -0
- guppylang_internals/checker/errors/comptime_errors.py +106 -0
- guppylang_internals/checker/errors/generic.py +45 -0
- guppylang_internals/checker/errors/linearity.py +300 -0
- guppylang_internals/checker/errors/type_errors.py +344 -0
- guppylang_internals/checker/errors/wasm.py +34 -0
- guppylang_internals/checker/expr_checker.py +1413 -0
- guppylang_internals/checker/func_checker.py +269 -0
- guppylang_internals/checker/linearity_checker.py +821 -0
- guppylang_internals/checker/stmt_checker.py +447 -0
- guppylang_internals/compiler/__init__.py +0 -0
- guppylang_internals/compiler/cfg_compiler.py +233 -0
- guppylang_internals/compiler/core.py +613 -0
- guppylang_internals/compiler/expr_compiler.py +989 -0
- guppylang_internals/compiler/func_compiler.py +97 -0
- guppylang_internals/compiler/hugr_extension.py +224 -0
- guppylang_internals/compiler/qtm_platform_extension.py +0 -0
- guppylang_internals/compiler/stmt_compiler.py +212 -0
- guppylang_internals/decorator.py +246 -0
- guppylang_internals/definition/__init__.py +0 -0
- guppylang_internals/definition/common.py +214 -0
- guppylang_internals/definition/const.py +74 -0
- guppylang_internals/definition/custom.py +492 -0
- guppylang_internals/definition/declaration.py +171 -0
- guppylang_internals/definition/extern.py +89 -0
- guppylang_internals/definition/function.py +302 -0
- guppylang_internals/definition/overloaded.py +150 -0
- guppylang_internals/definition/parameter.py +82 -0
- guppylang_internals/definition/pytket_circuits.py +405 -0
- guppylang_internals/definition/struct.py +392 -0
- guppylang_internals/definition/traced.py +151 -0
- guppylang_internals/definition/ty.py +51 -0
- guppylang_internals/definition/value.py +115 -0
- guppylang_internals/definition/wasm.py +61 -0
- guppylang_internals/diagnostic.py +523 -0
- guppylang_internals/dummy_decorator.py +76 -0
- guppylang_internals/engine.py +295 -0
- guppylang_internals/error.py +107 -0
- guppylang_internals/experimental.py +92 -0
- guppylang_internals/ipython_inspect.py +28 -0
- guppylang_internals/nodes.py +427 -0
- guppylang_internals/py.typed +0 -0
- guppylang_internals/span.py +150 -0
- guppylang_internals/std/__init__.py +0 -0
- guppylang_internals/std/_internal/__init__.py +0 -0
- guppylang_internals/std/_internal/checker.py +573 -0
- guppylang_internals/std/_internal/compiler/__init__.py +0 -0
- guppylang_internals/std/_internal/compiler/arithmetic.py +136 -0
- guppylang_internals/std/_internal/compiler/array.py +569 -0
- guppylang_internals/std/_internal/compiler/either.py +131 -0
- guppylang_internals/std/_internal/compiler/frozenarray.py +68 -0
- guppylang_internals/std/_internal/compiler/futures.py +30 -0
- guppylang_internals/std/_internal/compiler/list.py +348 -0
- guppylang_internals/std/_internal/compiler/mem.py +13 -0
- guppylang_internals/std/_internal/compiler/option.py +78 -0
- guppylang_internals/std/_internal/compiler/prelude.py +271 -0
- guppylang_internals/std/_internal/compiler/qsystem.py +48 -0
- guppylang_internals/std/_internal/compiler/quantum.py +118 -0
- guppylang_internals/std/_internal/compiler/tket_bool.py +55 -0
- guppylang_internals/std/_internal/compiler/tket_exts.py +59 -0
- guppylang_internals/std/_internal/compiler/wasm.py +135 -0
- guppylang_internals/std/_internal/compiler.py +0 -0
- guppylang_internals/std/_internal/debug.py +95 -0
- guppylang_internals/std/_internal/util.py +271 -0
- guppylang_internals/tracing/__init__.py +0 -0
- guppylang_internals/tracing/builtins_mock.py +62 -0
- guppylang_internals/tracing/frozenlist.py +57 -0
- guppylang_internals/tracing/function.py +186 -0
- guppylang_internals/tracing/object.py +551 -0
- guppylang_internals/tracing/state.py +69 -0
- guppylang_internals/tracing/unpacking.py +194 -0
- guppylang_internals/tracing/util.py +86 -0
- guppylang_internals/tys/__init__.py +0 -0
- guppylang_internals/tys/arg.py +115 -0
- guppylang_internals/tys/builtin.py +382 -0
- guppylang_internals/tys/common.py +110 -0
- guppylang_internals/tys/const.py +114 -0
- guppylang_internals/tys/errors.py +178 -0
- guppylang_internals/tys/param.py +251 -0
- guppylang_internals/tys/parsing.py +425 -0
- guppylang_internals/tys/printing.py +174 -0
- guppylang_internals/tys/subst.py +112 -0
- guppylang_internals/tys/ty.py +876 -0
- guppylang_internals/tys/var.py +49 -0
- guppylang_internals-0.21.0.dist-info/METADATA +253 -0
- guppylang_internals-0.21.0.dist-info/RECORD +98 -0
- guppylang_internals-0.21.0.dist-info/WHEEL +4 -0
- guppylang_internals-0.21.0.dist-info/licenses/LICENCE +201 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from collections import deque
|
|
2
|
+
from collections.abc import Iterator
|
|
3
|
+
from typing import Generic, TypeVar
|
|
4
|
+
|
|
5
|
+
from guppylang_internals.cfg.analysis import (
|
|
6
|
+
AssignmentAnalysis,
|
|
7
|
+
DefAssignmentDomain,
|
|
8
|
+
LivenessAnalysis,
|
|
9
|
+
LivenessDomain,
|
|
10
|
+
MaybeAssignmentDomain,
|
|
11
|
+
Result,
|
|
12
|
+
)
|
|
13
|
+
from guppylang_internals.cfg.bb import BB, BBStatement, VariableStats
|
|
14
|
+
from guppylang_internals.nodes import InoutReturnSentinel
|
|
15
|
+
|
|
16
|
+
T = TypeVar("T", bound=BB)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BaseCFG(Generic[T]):
|
|
20
|
+
"""Abstract base class for control-flow graphs."""
|
|
21
|
+
|
|
22
|
+
bbs: list[T]
|
|
23
|
+
entry_bb: T
|
|
24
|
+
exit_bb: T
|
|
25
|
+
|
|
26
|
+
live_before: Result[LivenessDomain[str]]
|
|
27
|
+
ass_before: Result[DefAssignmentDomain[str]]
|
|
28
|
+
maybe_ass_before: Result[MaybeAssignmentDomain[str]]
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self, bbs: list[T], entry_bb: T | None = None, exit_bb: T | None = None
|
|
32
|
+
):
|
|
33
|
+
self.bbs = bbs
|
|
34
|
+
if entry_bb:
|
|
35
|
+
self.entry_bb = entry_bb
|
|
36
|
+
if exit_bb:
|
|
37
|
+
self.exit_bb = exit_bb
|
|
38
|
+
self.live_before = {}
|
|
39
|
+
self.ass_before = {}
|
|
40
|
+
self.maybe_ass_before = {}
|
|
41
|
+
|
|
42
|
+
def ancestors(self, *bbs: T) -> Iterator[T]:
|
|
43
|
+
"""Returns an iterator over all ancestors of the given BBs in BFS order."""
|
|
44
|
+
queue = deque(bbs)
|
|
45
|
+
visited = set()
|
|
46
|
+
while queue:
|
|
47
|
+
bb = queue.popleft()
|
|
48
|
+
if bb in visited:
|
|
49
|
+
continue
|
|
50
|
+
visited.add(bb)
|
|
51
|
+
yield bb
|
|
52
|
+
queue += bb.predecessors
|
|
53
|
+
|
|
54
|
+
def update_reachable(self) -> None:
|
|
55
|
+
"""Sets the reachability flags on the BBs in this CFG."""
|
|
56
|
+
queue = {self.entry_bb}
|
|
57
|
+
while queue:
|
|
58
|
+
bb = queue.pop()
|
|
59
|
+
if not bb.reachable:
|
|
60
|
+
bb.reachable = True
|
|
61
|
+
for succ in bb.successors:
|
|
62
|
+
queue.add(succ)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class CFG(BaseCFG[BB]):
|
|
66
|
+
"""A control-flow graph of unchecked basic blocks."""
|
|
67
|
+
|
|
68
|
+
def __init__(self) -> None:
|
|
69
|
+
super().__init__([])
|
|
70
|
+
self.entry_bb = self.new_bb()
|
|
71
|
+
self.exit_bb = self.new_bb()
|
|
72
|
+
|
|
73
|
+
def new_bb(self, *preds: BB, statements: list[BBStatement] | None = None) -> BB:
|
|
74
|
+
"""Adds a new basic block to the CFG."""
|
|
75
|
+
bb = BB(
|
|
76
|
+
len(self.bbs), self, predecessors=list(preds), statements=statements or []
|
|
77
|
+
)
|
|
78
|
+
self.bbs.append(bb)
|
|
79
|
+
for p in preds:
|
|
80
|
+
p.successors.append(bb)
|
|
81
|
+
return bb
|
|
82
|
+
|
|
83
|
+
def link(self, src_bb: BB, tgt_bb: BB) -> None:
|
|
84
|
+
"""Adds a control-flow edge between two basic blocks."""
|
|
85
|
+
src_bb.successors.append(tgt_bb)
|
|
86
|
+
tgt_bb.predecessors.append(src_bb)
|
|
87
|
+
|
|
88
|
+
def dummy_link(self, src_bb: BB, tgt_bb: BB) -> None:
|
|
89
|
+
"""Adds a dummy control-flow edge between two basic blocks that is provably
|
|
90
|
+
never taken.
|
|
91
|
+
|
|
92
|
+
For example, a `if False: ...` statement emits such a dummy link.
|
|
93
|
+
"""
|
|
94
|
+
src_bb.dummy_successors.append(tgt_bb)
|
|
95
|
+
tgt_bb.dummy_predecessors.append(src_bb)
|
|
96
|
+
|
|
97
|
+
def analyze(
|
|
98
|
+
self,
|
|
99
|
+
def_ass_before: set[str],
|
|
100
|
+
maybe_ass_before: set[str],
|
|
101
|
+
inout_vars: list[str],
|
|
102
|
+
) -> dict[BB, VariableStats[str]]:
|
|
103
|
+
stats = {bb: bb.compute_variable_stats() for bb in self.bbs}
|
|
104
|
+
# Mark all borrowed variables as implicitly used in the exit BB
|
|
105
|
+
stats[self.exit_bb].used |= {x: InoutReturnSentinel(var=x) for x in inout_vars}
|
|
106
|
+
# This also means borrowed variables are always live, so we can use them as the
|
|
107
|
+
# initial value in the liveness analysis. This solves the edge case that
|
|
108
|
+
# borrowed variables should be considered live, even if the exit is actually
|
|
109
|
+
# unreachable (to avoid linearity violations later).
|
|
110
|
+
inout_live = {x: self.exit_bb for x in inout_vars}
|
|
111
|
+
self.live_before = LivenessAnalysis(
|
|
112
|
+
stats, initial=inout_live, include_unreachable=True
|
|
113
|
+
).run(self.bbs)
|
|
114
|
+
self.ass_before, self.maybe_ass_before = AssignmentAnalysis(
|
|
115
|
+
stats, def_ass_before, maybe_ass_before, include_unreachable=True
|
|
116
|
+
).run_unpacked(self.bbs)
|
|
117
|
+
return stats
|
|
File without changes
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
"""Type checking code for control-flow graphs
|
|
2
|
+
|
|
3
|
+
Operates on CFGs produced by the `CFGBuilder`. Produces a `CheckedCFG` consisting of
|
|
4
|
+
`CheckedBB`s with inferred type signatures.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import ast
|
|
8
|
+
import collections
|
|
9
|
+
from collections.abc import Iterator, Sequence
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import ClassVar, Generic, TypeVar, cast
|
|
12
|
+
|
|
13
|
+
from guppylang_internals.ast_util import line_col
|
|
14
|
+
from guppylang_internals.cfg.bb import BB
|
|
15
|
+
from guppylang_internals.cfg.cfg import CFG, BaseCFG
|
|
16
|
+
from guppylang_internals.checker.core import (
|
|
17
|
+
Context,
|
|
18
|
+
Globals,
|
|
19
|
+
Locals,
|
|
20
|
+
Place,
|
|
21
|
+
V,
|
|
22
|
+
Variable,
|
|
23
|
+
)
|
|
24
|
+
from guppylang_internals.checker.expr_checker import ExprSynthesizer, to_bool
|
|
25
|
+
from guppylang_internals.checker.stmt_checker import StmtChecker
|
|
26
|
+
from guppylang_internals.definition.value import ValueDef
|
|
27
|
+
from guppylang_internals.diagnostic import Error, Note
|
|
28
|
+
from guppylang_internals.error import GuppyError
|
|
29
|
+
from guppylang_internals.tys.param import Parameter
|
|
30
|
+
from guppylang_internals.tys.ty import InputFlags, Type
|
|
31
|
+
|
|
32
|
+
Row = Sequence[V]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class Signature(Generic[V]):
|
|
37
|
+
"""The signature of a basic block.
|
|
38
|
+
|
|
39
|
+
Stores the input/output variables with their types. Generic over the representation
|
|
40
|
+
of program variables.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
input_row: Row[V]
|
|
44
|
+
output_rows: Sequence[Row[V]] # One for each successor
|
|
45
|
+
|
|
46
|
+
dummy_output_rows: Sequence[Row[V]] = field(default_factory=list)
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def empty() -> "Signature[V]":
|
|
50
|
+
return Signature([], [], [])
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(eq=False) # Disable equality to recover hash from `object`
|
|
54
|
+
class CheckedBB(BB, Generic[V]):
|
|
55
|
+
"""Basic block annotated with an input and output type signature.
|
|
56
|
+
|
|
57
|
+
The signature is generic over the representation of program variables.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
sig: Signature[V] = field(default_factory=Signature.empty)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class CheckedCFG(BaseCFG[CheckedBB[V]], Generic[V]):
|
|
64
|
+
input_tys: list[Type]
|
|
65
|
+
output_ty: Type
|
|
66
|
+
|
|
67
|
+
def __init__(self, input_tys: list[Type], output_ty: Type) -> None:
|
|
68
|
+
super().__init__([])
|
|
69
|
+
self.input_tys = input_tys
|
|
70
|
+
self.output_ty = output_ty
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def check_cfg(
|
|
74
|
+
cfg: CFG,
|
|
75
|
+
inputs: Row[Variable],
|
|
76
|
+
return_ty: Type,
|
|
77
|
+
generic_params: dict[str, Parameter],
|
|
78
|
+
func_name: str,
|
|
79
|
+
globals: Globals,
|
|
80
|
+
) -> CheckedCFG[Place]:
|
|
81
|
+
"""Type checks a control-flow graph.
|
|
82
|
+
|
|
83
|
+
Annotates the basic blocks with input and output type signatures and removes
|
|
84
|
+
unreachable blocks. Note that the inputs/outputs are annotated in the form of
|
|
85
|
+
*places* rather than just variables.
|
|
86
|
+
"""
|
|
87
|
+
# First, we need to run program analysis
|
|
88
|
+
ass_before = {v.name for v in inputs}
|
|
89
|
+
inout_vars = [v for v in inputs if InputFlags.Inout in v.flags]
|
|
90
|
+
cfg.analyze(ass_before, ass_before, [v.name for v in inout_vars])
|
|
91
|
+
|
|
92
|
+
# We start by compiling the entry BB
|
|
93
|
+
checked_cfg: CheckedCFG[Variable] = CheckedCFG([v.ty for v in inputs], return_ty)
|
|
94
|
+
checked_cfg.entry_bb = check_bb(
|
|
95
|
+
cfg.entry_bb, checked_cfg, inputs, return_ty, generic_params, globals
|
|
96
|
+
)
|
|
97
|
+
compiled = {cfg.entry_bb: checked_cfg.entry_bb}
|
|
98
|
+
|
|
99
|
+
# Visit all control-flow edges in BFS order. We can't just do a normal loop over
|
|
100
|
+
# all BBs since the input types for a BB are computed by checking a predecessor.
|
|
101
|
+
# We do BFS instead of DFS to get a better error ordering.
|
|
102
|
+
queue = collections.deque(
|
|
103
|
+
(checked_cfg.entry_bb, i, succ)
|
|
104
|
+
# We enumerate the successor starting from the back, so we start with the `True`
|
|
105
|
+
# branch. This way, we find errors in a more natural order
|
|
106
|
+
for i, succ in reverse_enumerate(
|
|
107
|
+
cfg.entry_bb.successors + cfg.entry_bb.dummy_successors
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
while len(queue) > 0:
|
|
111
|
+
pred, num_output, bb = queue.popleft()
|
|
112
|
+
pred_outputs = [*pred.sig.output_rows, *pred.sig.dummy_output_rows]
|
|
113
|
+
input_row = pred_outputs[num_output]
|
|
114
|
+
|
|
115
|
+
if bb in compiled:
|
|
116
|
+
# If the BB was already compiled, we just have to check that the signatures
|
|
117
|
+
# match.
|
|
118
|
+
check_rows_match(input_row, compiled[bb].sig.input_row, bb, globals)
|
|
119
|
+
else:
|
|
120
|
+
# Otherwise, check the BB and enqueue its successors
|
|
121
|
+
checked_bb = check_bb(
|
|
122
|
+
bb, checked_cfg, input_row, return_ty, generic_params, globals
|
|
123
|
+
)
|
|
124
|
+
queue += [
|
|
125
|
+
# We enumerate the successor starting from the back, so we start with
|
|
126
|
+
# the `True` branch. This way, we find errors in a more natural order
|
|
127
|
+
(checked_bb, i, succ)
|
|
128
|
+
for i, succ in reverse_enumerate(bb.successors)
|
|
129
|
+
]
|
|
130
|
+
compiled[bb] = checked_bb
|
|
131
|
+
|
|
132
|
+
# Link up BBs in the checked CFG, excluding the unreachable ones
|
|
133
|
+
if bb.reachable:
|
|
134
|
+
compiled[bb].predecessors.append(pred)
|
|
135
|
+
pred.successors[num_output] = compiled[bb]
|
|
136
|
+
|
|
137
|
+
# The exit BB might be unreachable. In that case it won't be visited above and we
|
|
138
|
+
# have to handle it here
|
|
139
|
+
if cfg.exit_bb not in compiled:
|
|
140
|
+
assert not cfg.exit_bb.reachable
|
|
141
|
+
compiled[cfg.exit_bb] = CheckedBB(
|
|
142
|
+
cfg.exit_bb.idx, checked_cfg, reachable=False, sig=Signature(inout_vars, [])
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
required_bbs = [bb for bb in cfg.bbs if bb.reachable or bb.is_exit]
|
|
146
|
+
checked_cfg.bbs = [compiled[bb] for bb in required_bbs]
|
|
147
|
+
checked_cfg.exit_bb = compiled[cfg.exit_bb]
|
|
148
|
+
checked_cfg.live_before = {compiled[bb]: cfg.live_before[bb] for bb in required_bbs}
|
|
149
|
+
checked_cfg.ass_before = {compiled[bb]: cfg.ass_before[bb] for bb in required_bbs}
|
|
150
|
+
checked_cfg.maybe_ass_before = {
|
|
151
|
+
compiled[bb]: cfg.maybe_ass_before[bb] for bb in required_bbs
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# Finally, run the linearity check
|
|
155
|
+
from guppylang_internals.checker.linearity_checker import check_cfg_linearity
|
|
156
|
+
|
|
157
|
+
linearity_checked_cfg = check_cfg_linearity(checked_cfg, func_name, globals)
|
|
158
|
+
return linearity_checked_cfg
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@dataclass(frozen=True)
|
|
162
|
+
class VarNotDefinedError(Error):
|
|
163
|
+
title: ClassVar[str] = "Variable not defined"
|
|
164
|
+
span_label: ClassVar[str] = "`{var}` is not defined"
|
|
165
|
+
var: str
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@dataclass(frozen=True)
|
|
169
|
+
class VarMaybeNotDefinedError(Error):
|
|
170
|
+
title: ClassVar[str] = "Variable not defined"
|
|
171
|
+
var: str
|
|
172
|
+
|
|
173
|
+
@dataclass(frozen=True)
|
|
174
|
+
class BadBranch(Note):
|
|
175
|
+
span_label: ClassVar[str] = "... if this expression is `{truth_value}`"
|
|
176
|
+
var: str
|
|
177
|
+
truth_value: bool
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def rendered_span_label(self) -> str:
|
|
181
|
+
s = f"`{self.var}` might be undefined"
|
|
182
|
+
if self.children:
|
|
183
|
+
s += " ..."
|
|
184
|
+
return s
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@dataclass(frozen=True)
|
|
188
|
+
class BranchTypeError(Error):
|
|
189
|
+
title: ClassVar[str] = "Different types"
|
|
190
|
+
span_label: ClassVar[str] = "{ident} may refer to different types"
|
|
191
|
+
ident: str
|
|
192
|
+
|
|
193
|
+
@dataclass(frozen=True)
|
|
194
|
+
class TypeHint(Note):
|
|
195
|
+
span_label: ClassVar[str] = "This is of type `{ty}`"
|
|
196
|
+
ty: Type
|
|
197
|
+
|
|
198
|
+
@dataclass(frozen=True)
|
|
199
|
+
class GlobalHint(Note):
|
|
200
|
+
message: ClassVar[str] = (
|
|
201
|
+
"{ident} may be shadowing a global {defn.description} definition of type "
|
|
202
|
+
"`{defn.ty}` on some branches"
|
|
203
|
+
)
|
|
204
|
+
defn: ValueDef
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@dataclass(frozen=True)
|
|
208
|
+
class GlobalShadowError(Error):
|
|
209
|
+
title: ClassVar[str] = "Global variable conditionally shadowed"
|
|
210
|
+
span_label: ClassVar[str] = "{ident} may be shadowing a global variable"
|
|
211
|
+
ident: str
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def check_bb(
|
|
215
|
+
bb: BB,
|
|
216
|
+
checked_cfg: CheckedCFG[Variable],
|
|
217
|
+
inputs: Row[Variable],
|
|
218
|
+
return_ty: Type,
|
|
219
|
+
generic_params: dict[str, Parameter],
|
|
220
|
+
globals: Globals,
|
|
221
|
+
) -> CheckedBB[Variable]:
|
|
222
|
+
cfg = bb.containing_cfg
|
|
223
|
+
|
|
224
|
+
# For the entry BB we have to separately check that all used variables are
|
|
225
|
+
# defined. For all other BBs, this will be checked when compiling a predecessor.
|
|
226
|
+
if bb == cfg.entry_bb:
|
|
227
|
+
assert len(bb.predecessors) == 0
|
|
228
|
+
for x, use in bb.vars.used.items():
|
|
229
|
+
if (
|
|
230
|
+
x not in cfg.ass_before[bb]
|
|
231
|
+
and x not in globals
|
|
232
|
+
and x not in generic_params
|
|
233
|
+
):
|
|
234
|
+
raise GuppyError(VarNotDefinedError(use, x))
|
|
235
|
+
|
|
236
|
+
# Check the basic block
|
|
237
|
+
ctx = Context(globals, Locals({v.name: v for v in inputs}), generic_params)
|
|
238
|
+
checked_stmts = StmtChecker(ctx, bb, return_ty).check_stmts(bb.statements)
|
|
239
|
+
|
|
240
|
+
# If we branch, we also have to check the branch predicate
|
|
241
|
+
if len(bb.successors) > 1:
|
|
242
|
+
assert bb.branch_pred is not None
|
|
243
|
+
bb.branch_pred, ty = ExprSynthesizer(ctx).synthesize(bb.branch_pred)
|
|
244
|
+
bb.branch_pred, _ = to_bool(bb.branch_pred, ty, ctx)
|
|
245
|
+
|
|
246
|
+
for succ in bb.successors + bb.dummy_successors:
|
|
247
|
+
for x, use_bb in cfg.live_before[succ].items():
|
|
248
|
+
# Check that the variables requested by the successor are defined
|
|
249
|
+
if (
|
|
250
|
+
x not in ctx.locals
|
|
251
|
+
and x not in ctx.globals
|
|
252
|
+
and x not in ctx.generic_params
|
|
253
|
+
):
|
|
254
|
+
# If the variable is defined on *some* paths, we can give a more
|
|
255
|
+
# informative error message
|
|
256
|
+
if x in cfg.maybe_ass_before[use_bb]:
|
|
257
|
+
err = VarMaybeNotDefinedError(use_bb.vars.used[x], x)
|
|
258
|
+
if bad_branch := diagnose_maybe_undefined(use_bb, x, cfg):
|
|
259
|
+
branch_expr, truth_value = bad_branch
|
|
260
|
+
note = VarMaybeNotDefinedError.BadBranch(
|
|
261
|
+
branch_expr, x, truth_value
|
|
262
|
+
)
|
|
263
|
+
err.add_sub_diagnostic(note)
|
|
264
|
+
raise GuppyError(err)
|
|
265
|
+
raise GuppyError(VarNotDefinedError(use_bb.vars.used[x], x))
|
|
266
|
+
|
|
267
|
+
# Finally, we need to compute the signature of the basic block
|
|
268
|
+
outputs = [
|
|
269
|
+
[ctx.locals[x] for x in cfg.live_before[succ] if x in ctx.locals]
|
|
270
|
+
for succ in bb.successors
|
|
271
|
+
]
|
|
272
|
+
dummy_outputs = [
|
|
273
|
+
[ctx.locals[x] for x in cfg.live_before[succ] if x in ctx.locals]
|
|
274
|
+
for succ in bb.dummy_successors
|
|
275
|
+
]
|
|
276
|
+
|
|
277
|
+
# Also prepare the successor list so we can fill it in later
|
|
278
|
+
checked_bb = CheckedBB(
|
|
279
|
+
bb.idx,
|
|
280
|
+
checked_cfg,
|
|
281
|
+
checked_stmts,
|
|
282
|
+
reachable=bb.reachable,
|
|
283
|
+
sig=Signature(inputs, outputs, dummy_outputs),
|
|
284
|
+
)
|
|
285
|
+
checked_bb.successors = [None] * len(bb.successors) # type: ignore[list-item]
|
|
286
|
+
checked_bb.branch_pred = bb.branch_pred
|
|
287
|
+
return checked_bb
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def check_rows_match(
|
|
291
|
+
row1: Row[Variable], row2: Row[Variable], bb: BB, globals: Globals
|
|
292
|
+
) -> None:
|
|
293
|
+
"""Checks that the types of two rows match up.
|
|
294
|
+
|
|
295
|
+
Otherwise, an error is thrown, alerting the user that a variable has different
|
|
296
|
+
types on different control-flow paths.
|
|
297
|
+
"""
|
|
298
|
+
map1, map2 = {v.name: v for v in row1}, {v.name: v for v in row2}
|
|
299
|
+
for x in map1.keys() | map2.keys():
|
|
300
|
+
# If block signature lengths don't match but no undefined error was thrown, some
|
|
301
|
+
# variables may be shadowing global variables.
|
|
302
|
+
v1 = map1.get(x) or cast(ValueDef, globals[x])
|
|
303
|
+
assert isinstance(v1, Variable | ValueDef)
|
|
304
|
+
v2 = map2.get(x) or cast(ValueDef, globals[x])
|
|
305
|
+
assert isinstance(v2, Variable | ValueDef)
|
|
306
|
+
if v1.ty != v2.ty:
|
|
307
|
+
# In the error message, we want to mention the variable that was first
|
|
308
|
+
# defined at the start.
|
|
309
|
+
if (
|
|
310
|
+
v1.defined_at
|
|
311
|
+
and v2.defined_at
|
|
312
|
+
and line_col(v2.defined_at) < line_col(v1.defined_at)
|
|
313
|
+
):
|
|
314
|
+
v1, v2 = v2, v1
|
|
315
|
+
# We shouldn't mention temporary variables (starting with `%`)
|
|
316
|
+
# in error messages:
|
|
317
|
+
ident = "Expression" if v1.name.startswith("%") else f"Variable `{v1.name}`"
|
|
318
|
+
use = bb.containing_cfg.live_before[bb][v1.name].vars.used[v1.name]
|
|
319
|
+
err = BranchTypeError(use, ident)
|
|
320
|
+
# We don't add a location to the type hint for the global variable,
|
|
321
|
+
# since it could lead to cross-file diagnostics (which are not
|
|
322
|
+
# supported) or refer to long function definitions.
|
|
323
|
+
sub1 = (
|
|
324
|
+
BranchTypeError.TypeHint(v1.defined_at, v1.ty)
|
|
325
|
+
if isinstance(v1, Variable)
|
|
326
|
+
else BranchTypeError.GlobalHint(None, v1)
|
|
327
|
+
)
|
|
328
|
+
sub2 = (
|
|
329
|
+
BranchTypeError.TypeHint(v2.defined_at, v2.ty)
|
|
330
|
+
if isinstance(v2, Variable)
|
|
331
|
+
else BranchTypeError.GlobalHint(None, v2)
|
|
332
|
+
)
|
|
333
|
+
err.add_sub_diagnostic(sub1)
|
|
334
|
+
err.add_sub_diagnostic(sub2)
|
|
335
|
+
raise GuppyError(err)
|
|
336
|
+
else:
|
|
337
|
+
# TODO: Remove once https://github.com/CQCL/guppylang/issues/827 is done.
|
|
338
|
+
# If either is a global variable, don't allow shadowing even if types match.
|
|
339
|
+
if not (isinstance(v1, Variable) and isinstance(v2, Variable)):
|
|
340
|
+
local_var = v1 if isinstance(v1, Variable) else v2
|
|
341
|
+
ident = (
|
|
342
|
+
"Expression"
|
|
343
|
+
if local_var.name.startswith("%")
|
|
344
|
+
else f"Variable `{local_var.name}`"
|
|
345
|
+
)
|
|
346
|
+
glob_err = GlobalShadowError(local_var.defined_at, ident)
|
|
347
|
+
raise GuppyError(glob_err)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def diagnose_maybe_undefined(
|
|
351
|
+
bb: BB, x: str, cfg: BaseCFG[BB]
|
|
352
|
+
) -> tuple[ast.expr, bool] | None:
|
|
353
|
+
"""Given a BB and a variable `x`, tries to find a branch where one of the successors
|
|
354
|
+
leads to an assignment of `x` while the other one does not.
|
|
355
|
+
|
|
356
|
+
Returns the branch condition and a flag whether the value being `True` leads to the
|
|
357
|
+
undefined path. Returns `None` if no such branch can be found.
|
|
358
|
+
"""
|
|
359
|
+
assert x in cfg.maybe_ass_before[bb]
|
|
360
|
+
# Find all BBs that can reach this BB and which ones of those assign `x`
|
|
361
|
+
ancestors = list(cfg.ancestors(bb))
|
|
362
|
+
assigns = [anc for anc in ancestors if x in anc.vars.assigned]
|
|
363
|
+
# Compute which ancestors can possibly reach an assignment
|
|
364
|
+
reaches_assignment = set(cfg.ancestors(*assigns))
|
|
365
|
+
# Try to find a branching BB where one of paths can reach an assignment, while the
|
|
366
|
+
# other one cannot
|
|
367
|
+
for anc in ancestors:
|
|
368
|
+
match anc.successors:
|
|
369
|
+
case [true_succ, false_succ]:
|
|
370
|
+
assert anc.branch_pred is not None
|
|
371
|
+
true_reaches_assignment = true_succ in reaches_assignment
|
|
372
|
+
false_reaches_assignment = false_succ in reaches_assignment
|
|
373
|
+
if true_reaches_assignment != false_reaches_assignment:
|
|
374
|
+
return anc.branch_pred, true_reaches_assignment
|
|
375
|
+
return None
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
T = TypeVar("T")
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def reverse_enumerate(xs: list[T]) -> Iterator[tuple[int, T]]:
|
|
382
|
+
"""Enumerates a list in reverse order.
|
|
383
|
+
|
|
384
|
+
Equivalent to `reversed(list(enumerate(data)))` without creating an intermediate
|
|
385
|
+
list.
|
|
386
|
+
"""
|
|
387
|
+
for i in range(len(xs) - 1, -1, -1):
|
|
388
|
+
yield i, xs[i]
|