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.
Files changed (98) hide show
  1. guppylang_internals/__init__.py +3 -0
  2. guppylang_internals/ast_util.py +350 -0
  3. guppylang_internals/cfg/__init__.py +0 -0
  4. guppylang_internals/cfg/analysis.py +230 -0
  5. guppylang_internals/cfg/bb.py +221 -0
  6. guppylang_internals/cfg/builder.py +606 -0
  7. guppylang_internals/cfg/cfg.py +117 -0
  8. guppylang_internals/checker/__init__.py +0 -0
  9. guppylang_internals/checker/cfg_checker.py +388 -0
  10. guppylang_internals/checker/core.py +550 -0
  11. guppylang_internals/checker/errors/__init__.py +0 -0
  12. guppylang_internals/checker/errors/comptime_errors.py +106 -0
  13. guppylang_internals/checker/errors/generic.py +45 -0
  14. guppylang_internals/checker/errors/linearity.py +300 -0
  15. guppylang_internals/checker/errors/type_errors.py +344 -0
  16. guppylang_internals/checker/errors/wasm.py +34 -0
  17. guppylang_internals/checker/expr_checker.py +1413 -0
  18. guppylang_internals/checker/func_checker.py +269 -0
  19. guppylang_internals/checker/linearity_checker.py +821 -0
  20. guppylang_internals/checker/stmt_checker.py +447 -0
  21. guppylang_internals/compiler/__init__.py +0 -0
  22. guppylang_internals/compiler/cfg_compiler.py +233 -0
  23. guppylang_internals/compiler/core.py +613 -0
  24. guppylang_internals/compiler/expr_compiler.py +989 -0
  25. guppylang_internals/compiler/func_compiler.py +97 -0
  26. guppylang_internals/compiler/hugr_extension.py +224 -0
  27. guppylang_internals/compiler/qtm_platform_extension.py +0 -0
  28. guppylang_internals/compiler/stmt_compiler.py +212 -0
  29. guppylang_internals/decorator.py +246 -0
  30. guppylang_internals/definition/__init__.py +0 -0
  31. guppylang_internals/definition/common.py +214 -0
  32. guppylang_internals/definition/const.py +74 -0
  33. guppylang_internals/definition/custom.py +492 -0
  34. guppylang_internals/definition/declaration.py +171 -0
  35. guppylang_internals/definition/extern.py +89 -0
  36. guppylang_internals/definition/function.py +302 -0
  37. guppylang_internals/definition/overloaded.py +150 -0
  38. guppylang_internals/definition/parameter.py +82 -0
  39. guppylang_internals/definition/pytket_circuits.py +405 -0
  40. guppylang_internals/definition/struct.py +392 -0
  41. guppylang_internals/definition/traced.py +151 -0
  42. guppylang_internals/definition/ty.py +51 -0
  43. guppylang_internals/definition/value.py +115 -0
  44. guppylang_internals/definition/wasm.py +61 -0
  45. guppylang_internals/diagnostic.py +523 -0
  46. guppylang_internals/dummy_decorator.py +76 -0
  47. guppylang_internals/engine.py +295 -0
  48. guppylang_internals/error.py +107 -0
  49. guppylang_internals/experimental.py +92 -0
  50. guppylang_internals/ipython_inspect.py +28 -0
  51. guppylang_internals/nodes.py +427 -0
  52. guppylang_internals/py.typed +0 -0
  53. guppylang_internals/span.py +150 -0
  54. guppylang_internals/std/__init__.py +0 -0
  55. guppylang_internals/std/_internal/__init__.py +0 -0
  56. guppylang_internals/std/_internal/checker.py +573 -0
  57. guppylang_internals/std/_internal/compiler/__init__.py +0 -0
  58. guppylang_internals/std/_internal/compiler/arithmetic.py +136 -0
  59. guppylang_internals/std/_internal/compiler/array.py +569 -0
  60. guppylang_internals/std/_internal/compiler/either.py +131 -0
  61. guppylang_internals/std/_internal/compiler/frozenarray.py +68 -0
  62. guppylang_internals/std/_internal/compiler/futures.py +30 -0
  63. guppylang_internals/std/_internal/compiler/list.py +348 -0
  64. guppylang_internals/std/_internal/compiler/mem.py +13 -0
  65. guppylang_internals/std/_internal/compiler/option.py +78 -0
  66. guppylang_internals/std/_internal/compiler/prelude.py +271 -0
  67. guppylang_internals/std/_internal/compiler/qsystem.py +48 -0
  68. guppylang_internals/std/_internal/compiler/quantum.py +118 -0
  69. guppylang_internals/std/_internal/compiler/tket_bool.py +55 -0
  70. guppylang_internals/std/_internal/compiler/tket_exts.py +59 -0
  71. guppylang_internals/std/_internal/compiler/wasm.py +135 -0
  72. guppylang_internals/std/_internal/compiler.py +0 -0
  73. guppylang_internals/std/_internal/debug.py +95 -0
  74. guppylang_internals/std/_internal/util.py +271 -0
  75. guppylang_internals/tracing/__init__.py +0 -0
  76. guppylang_internals/tracing/builtins_mock.py +62 -0
  77. guppylang_internals/tracing/frozenlist.py +57 -0
  78. guppylang_internals/tracing/function.py +186 -0
  79. guppylang_internals/tracing/object.py +551 -0
  80. guppylang_internals/tracing/state.py +69 -0
  81. guppylang_internals/tracing/unpacking.py +194 -0
  82. guppylang_internals/tracing/util.py +86 -0
  83. guppylang_internals/tys/__init__.py +0 -0
  84. guppylang_internals/tys/arg.py +115 -0
  85. guppylang_internals/tys/builtin.py +382 -0
  86. guppylang_internals/tys/common.py +110 -0
  87. guppylang_internals/tys/const.py +114 -0
  88. guppylang_internals/tys/errors.py +178 -0
  89. guppylang_internals/tys/param.py +251 -0
  90. guppylang_internals/tys/parsing.py +425 -0
  91. guppylang_internals/tys/printing.py +174 -0
  92. guppylang_internals/tys/subst.py +112 -0
  93. guppylang_internals/tys/ty.py +876 -0
  94. guppylang_internals/tys/var.py +49 -0
  95. guppylang_internals-0.21.0.dist-info/METADATA +253 -0
  96. guppylang_internals-0.21.0.dist-info/RECORD +98 -0
  97. guppylang_internals-0.21.0.dist-info/WHEEL +4 -0
  98. 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]