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,427 @@
1
+ """Custom AST nodes used by Guppy"""
2
+
3
+ import ast
4
+ from collections.abc import Mapping
5
+ from enum import Enum
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ from guppylang_internals.ast_util import AstNode
9
+ from guppylang_internals.tys.const import Const
10
+ from guppylang_internals.tys.subst import Inst
11
+ from guppylang_internals.tys.ty import FunctionType, StructType, TupleType, Type
12
+
13
+ if TYPE_CHECKING:
14
+ from guppylang_internals.cfg.cfg import CFG
15
+ from guppylang_internals.checker.cfg_checker import CheckedCFG
16
+ from guppylang_internals.checker.core import Place, Variable
17
+ from guppylang_internals.definition.common import DefId
18
+ from guppylang_internals.definition.struct import StructField
19
+ from guppylang_internals.tys.param import ConstParam
20
+
21
+
22
+ class PlaceNode(ast.expr):
23
+ place: "Place"
24
+
25
+ _fields = ("place",)
26
+
27
+
28
+ class GlobalName(ast.Name):
29
+ id: str
30
+ def_id: "DefId"
31
+
32
+ _fields = (
33
+ "id",
34
+ "def_id",
35
+ )
36
+
37
+
38
+ class GenericParamValue(ast.Name):
39
+ id: str
40
+ param: "ConstParam"
41
+
42
+ _fields = (
43
+ "id",
44
+ "param",
45
+ )
46
+
47
+
48
+ class LocalCall(ast.expr):
49
+ func: ast.expr
50
+ args: list[ast.expr]
51
+
52
+ _fields = (
53
+ "func",
54
+ "args",
55
+ )
56
+
57
+
58
+ class GlobalCall(ast.expr):
59
+ def_id: "DefId"
60
+ args: list[ast.expr]
61
+ type_args: Inst # Inferred type arguments
62
+
63
+ _fields = (
64
+ "def_id",
65
+ "args",
66
+ "type_args",
67
+ )
68
+
69
+
70
+ class TensorCall(ast.expr):
71
+ """A call to a tuple of functions. Behaves like a local call, but more
72
+ unpacking of tuples is required at compilation"""
73
+
74
+ func: ast.expr
75
+ args: list[ast.expr]
76
+ tensor_ty: FunctionType
77
+
78
+ _fields = (
79
+ "func",
80
+ "args",
81
+ "tensor_ty",
82
+ )
83
+
84
+
85
+ class TypeApply(ast.expr):
86
+ value: ast.expr
87
+ inst: Inst
88
+
89
+ _fields = (
90
+ "value",
91
+ "inst",
92
+ )
93
+
94
+
95
+ class PartialApply(ast.expr):
96
+ """A partial function application.
97
+
98
+ This node is emitted when methods are loaded as values, since this requires
99
+ partially applying the `self` argument.
100
+ """
101
+
102
+ func: ast.expr
103
+ args: list[ast.expr]
104
+
105
+ _fields = (
106
+ "func",
107
+ "args",
108
+ )
109
+
110
+
111
+ class FieldAccessAndDrop(ast.expr):
112
+ """A field access on a struct, dropping all the remaining other fields."""
113
+
114
+ value: ast.expr
115
+ struct_ty: "StructType"
116
+ field: "StructField"
117
+
118
+ _fields = (
119
+ "value",
120
+ "struct_ty",
121
+ "field",
122
+ )
123
+
124
+
125
+ class SubscriptAccessAndDrop(ast.expr):
126
+ """A subscript element access on an object, dropping all the remaining items."""
127
+
128
+ item: "Variable"
129
+ item_expr: ast.expr
130
+ getitem_expr: ast.expr
131
+ original_expr: ast.Subscript
132
+
133
+ _fields = ("item", "item_expr", "getitem_expr", "original_expr")
134
+
135
+
136
+ class TupleAccessAndDrop(ast.expr):
137
+ """A subscript element access on a tuple, dropping all the remaining items."""
138
+
139
+ value: ast.expr
140
+ tuple_ty: TupleType
141
+ index: int
142
+
143
+ _fields = ("value", "tuple_ty", "index")
144
+
145
+
146
+ class MakeIter(ast.expr):
147
+ """Creates an iterator using the `__iter__` magic method.
148
+
149
+ This node is inserted in `for` loops and list comprehensions.
150
+ """
151
+
152
+ value: ast.expr
153
+ unwrap_size_hint: bool
154
+
155
+ # Node that triggered the creation of this iterator. For example, a for loop stmt.
156
+ # It is not mentioned in `_fields` so that it is not visible to AST visitors
157
+ origin_node: ast.AST
158
+
159
+ _fields = ("value",)
160
+
161
+ def __init__(
162
+ self, value: ast.expr, origin_node: ast.AST, unwrap_size_hint: bool = True
163
+ ) -> None:
164
+ super().__init__(value)
165
+ self.origin_node = origin_node
166
+ self.unwrap_size_hint = unwrap_size_hint
167
+
168
+
169
+ class IterHasNext(ast.expr):
170
+ """Checks if an iterator has a next element using the `__hasnext__` magic method.
171
+
172
+ This node is inserted in `for` loops and list comprehensions.
173
+ """
174
+
175
+ value: ast.expr
176
+
177
+ _fields = ("value",)
178
+
179
+
180
+ class IterNext(ast.expr):
181
+ """Obtains the next element of an iterator using the `__next__` magic method.
182
+
183
+ This node is inserted in `for` loops and list comprehensions.
184
+ """
185
+
186
+ value: ast.expr
187
+
188
+ _fields = ("value",)
189
+
190
+
191
+ class IterEnd(ast.expr):
192
+ """Finalises an iterator using the `__end__` magic method.
193
+
194
+ This node is inserted in `for` loops and list comprehensions. It is needed to
195
+ consume linear iterators once they are finished.
196
+ """
197
+
198
+ value: ast.expr
199
+
200
+ _fields = ("value",)
201
+
202
+
203
+ class DesugaredGenerator(ast.expr):
204
+ """A single desugared generator in a list comprehension.
205
+
206
+ Stores assignments of the original generator targets as well as dummy variables for
207
+ the iterator and hasnext test.
208
+ """
209
+
210
+ iter_assign: ast.Assign
211
+ next_call: ast.expr
212
+ iter: ast.expr
213
+ target: ast.expr
214
+ ifs: list[ast.expr]
215
+
216
+ used_outer_places: "list[Place]"
217
+
218
+ _fields = (
219
+ "iter_assign",
220
+ "next_call",
221
+ "iter",
222
+ "target",
223
+ "ifs",
224
+ )
225
+
226
+
227
+ class DesugaredGeneratorExpr(ast.expr):
228
+ """A desugared generator expression."""
229
+
230
+ elt: ast.expr
231
+ generators: list[DesugaredGenerator]
232
+
233
+ _fields = (
234
+ "elt",
235
+ "generators",
236
+ )
237
+
238
+
239
+ class DesugaredListComp(ast.expr):
240
+ """A desugared list comprehension."""
241
+
242
+ elt: ast.expr
243
+ generators: list[DesugaredGenerator]
244
+
245
+ _fields = (
246
+ "elt",
247
+ "generators",
248
+ )
249
+
250
+
251
+ class DesugaredArrayComp(ast.expr):
252
+ """A desugared array comprehension."""
253
+
254
+ elt: ast.expr
255
+ generator: DesugaredGenerator
256
+ length: Const
257
+ elt_ty: Type
258
+
259
+ _fields = (
260
+ "elt",
261
+ "generator",
262
+ "length",
263
+ "elt_ty",
264
+ )
265
+
266
+
267
+ class ComptimeExpr(ast.expr):
268
+ """A compile-time evaluated `py(...)` expression."""
269
+
270
+ value: ast.expr
271
+
272
+ _fields = ("value",)
273
+
274
+
275
+ class ResultExpr(ast.expr):
276
+ """A `result(tag, value)` expression."""
277
+
278
+ value: ast.expr
279
+ base_ty: Type
280
+ #: Array length in case this is an array result, otherwise `None`
281
+ array_len: Const | None
282
+ tag: str
283
+
284
+ _fields = ("value", "base_ty", "array_len", "tag")
285
+
286
+ @property
287
+ def args(self) -> list[ast.expr]:
288
+ return [self.value]
289
+
290
+
291
+ class ExitKind(Enum):
292
+ ExitShot = 0 # Exit the current shot
293
+ Panic = 1 # Panic the program ending all shots
294
+
295
+
296
+ class PanicExpr(ast.expr):
297
+ """A `panic(msg, *args)` or `exit(msg, *args)` expression ."""
298
+
299
+ kind: ExitKind
300
+ signal: int
301
+ msg: str
302
+ values: list[ast.expr]
303
+
304
+ _fields = ("kind", "signal", "msg", "values")
305
+
306
+
307
+ class BarrierExpr(ast.expr):
308
+ """A `barrier(*args)` expression."""
309
+
310
+ args: list[ast.expr]
311
+ func_ty: FunctionType
312
+ _fields = ("args", "func_ty")
313
+
314
+
315
+ class StateResultExpr(ast.expr):
316
+ """A `state_result(tag, *args)` expression."""
317
+
318
+ tag: str
319
+ args: list[ast.expr]
320
+ func_ty: FunctionType
321
+ #: Array length in case this is an array result, otherwise `None`
322
+ array_len: Const | None
323
+ _fields = ("tag", "args", "func_ty", "has_array_input")
324
+
325
+
326
+ AnyCall = (
327
+ LocalCall | GlobalCall | TensorCall | BarrierExpr | ResultExpr | StateResultExpr
328
+ )
329
+
330
+
331
+ class InoutReturnSentinel(ast.expr):
332
+ """An invisible expression corresponding to an implicit use of borrowed vars
333
+ whenever a function returns."""
334
+
335
+ var: "Place | str"
336
+
337
+ _fields = ("var",)
338
+
339
+
340
+ class UnpackPattern(ast.expr):
341
+ """The LHS of an unpacking assignment like `a, *bs, c = ...` or
342
+ `[a, *bs, c] = ...`."""
343
+
344
+ #: Patterns occurring on the left of the starred target
345
+ left: list[ast.expr]
346
+
347
+ #: The starred target or `None` if there is none
348
+ starred: ast.expr | None
349
+
350
+ #: Patterns occurring on the right of the starred target. This will be an empty list
351
+ #: if there is no starred target
352
+ right: list[ast.expr]
353
+
354
+ _fields = ("left", "starred", "right")
355
+
356
+
357
+ class TupleUnpack(ast.expr):
358
+ """The LHS of an unpacking assignment of a tuple."""
359
+
360
+ #: The (possibly starred) unpacking pattern
361
+ pattern: UnpackPattern
362
+
363
+ _fields = ("pattern",)
364
+
365
+
366
+ class IterableUnpack(ast.expr):
367
+ """The LHS of an unpacking assignment of an iterable type."""
368
+
369
+ #: The (possibly starred) unpacking pattern
370
+ pattern: UnpackPattern
371
+
372
+ #: Comprehension that collects the RHS iterable into an array
373
+ compr: DesugaredArrayComp
374
+
375
+ #: Dummy variable that the RHS should be bound to. This variable is referenced in
376
+ #: `compr`
377
+ rhs_var: PlaceNode
378
+
379
+ # Don't mention the comprehension in _fields to avoid visitors recursing it
380
+ _fields = ("pattern",)
381
+
382
+ def __init__(
383
+ self, pattern: UnpackPattern, compr: DesugaredArrayComp, rhs_var: PlaceNode
384
+ ) -> None:
385
+ super().__init__(pattern)
386
+ self.compr = compr
387
+ self.rhs_var = rhs_var
388
+
389
+
390
+ #: Any unpacking operation.
391
+ AnyUnpack = TupleUnpack | IterableUnpack
392
+
393
+
394
+ class NestedFunctionDef(ast.FunctionDef):
395
+ cfg: "CFG"
396
+ ty: FunctionType
397
+ docstring: str | None
398
+
399
+ def __init__(self, cfg: "CFG", ty: FunctionType, *args: Any, **kwargs: Any) -> None:
400
+ super().__init__(*args, **kwargs)
401
+ self.cfg = cfg
402
+ self.ty = ty
403
+
404
+
405
+ class CheckedNestedFunctionDef(ast.FunctionDef):
406
+ def_id: "DefId"
407
+ cfg: "CheckedCFG[Place]"
408
+ ty: FunctionType
409
+
410
+ #: Mapping from names to variables captured by this function, together with an AST
411
+ #: node witnessing a use of the captured variable in the function body.
412
+ captured: Mapping[str, tuple["Variable", AstNode]]
413
+
414
+ def __init__(
415
+ self,
416
+ def_id: "DefId",
417
+ cfg: "CheckedCFG[Place]",
418
+ ty: FunctionType,
419
+ captured: Mapping[str, tuple["Variable", AstNode]],
420
+ *args: Any,
421
+ **kwargs: Any,
422
+ ) -> None:
423
+ super().__init__(*args, **kwargs)
424
+ self.def_id = def_id
425
+ self.cfg = cfg
426
+ self.ty = ty
427
+ self.captured = captured
File without changes
@@ -0,0 +1,150 @@
1
+ """Source spans representing locations in the code being compiled."""
2
+
3
+ import ast
4
+ import linecache
5
+ from dataclasses import dataclass
6
+ from typing import TypeAlias
7
+
8
+ from guppylang_internals.ast_util import get_file, get_line_offset
9
+ from guppylang_internals.error import InternalGuppyError
10
+ from guppylang_internals.ipython_inspect import normalize_ipython_dummy_files
11
+
12
+
13
+ @dataclass(frozen=True, order=True)
14
+ class Loc:
15
+ """A location in a source file."""
16
+
17
+ file: str
18
+
19
+ #: Line number starting at 1
20
+ line: int
21
+
22
+ #: Column number starting at 1
23
+ column: int
24
+
25
+ def __str__(self) -> str:
26
+ """Returns the string representation of this source location."""
27
+ file = normalize_ipython_dummy_files(self.file)
28
+ return f"{file}:{self.line}:{self.column}"
29
+
30
+ def shift_left(self, cols: int) -> "Loc":
31
+ """Returns a new location shifted to left by the given number of columns."""
32
+ assert self.column >= cols
33
+ return Loc(self.file, self.line, self.column - cols)
34
+
35
+ def shift_right(self, cols: int) -> "Loc":
36
+ """Returns a new location shifted to right by the given number of columns."""
37
+ return Loc(self.file, self.line, self.column + cols)
38
+
39
+
40
+ @dataclass(frozen=True)
41
+ class Span:
42
+ """A continuous sequence of source code within a file."""
43
+
44
+ #: Starting location of the span (inclusive)
45
+ start: Loc
46
+
47
+ # Ending location of the span (exclusive)
48
+ end: Loc
49
+
50
+ def __post_init__(self) -> None:
51
+ if self.start.file != self.end.file:
52
+ raise InternalGuppyError("Span: Source spans multiple files")
53
+ if self.start > self.end:
54
+ raise InternalGuppyError("Span: Start after end")
55
+
56
+ def __contains__(self, x: "Span | Loc") -> bool:
57
+ """Determines whether another span or location is completely contained in this
58
+ span."""
59
+ if self.file != x.file:
60
+ return False
61
+ if isinstance(x, Span):
62
+ return self.start <= x.start <= self.end <= x.end
63
+ return self.start <= x <= self.end
64
+
65
+ def __and__(self, other: "Span") -> "Span | None":
66
+ """Returns the intersection with the given span or `None` if they don't
67
+ intersect."""
68
+ if self.file != other.file:
69
+ return None
70
+ if self.start > other.end or other.start > self.end:
71
+ return None
72
+ return Span(max(self.start, other.start), min(self.end, other.end))
73
+
74
+ def __len__(self) -> int:
75
+ """Returns the length of a single-line span in columns.
76
+
77
+ Querying the length of multiline spans raises an `InternalGuppyError`.
78
+ """
79
+ if self.is_multiline:
80
+ raise InternalGuppyError("Span: Tried to compute length of multi-line span")
81
+ return self.end.column - self.start.column
82
+
83
+ @property
84
+ def file(self) -> str:
85
+ """The file containing this span."""
86
+ return self.start.file
87
+
88
+ @property
89
+ def is_multiline(self) -> bool:
90
+ """Whether this source sequence spans multiple lines."""
91
+ return self.start.line != self.end.line
92
+
93
+ def shift_left(self, cols: int) -> "Span":
94
+ """Returns a new span that is shifted to the left by the given number of
95
+ columns."""
96
+ return Span(self.start.shift_left(cols), self.end.shift_left(cols))
97
+
98
+ def shift_right(self, cols: int) -> "Span":
99
+ """Returns a new span that is shifted to the right by the given number of
100
+ columns."""
101
+ return Span(self.start.shift_right(cols), self.end.shift_right(cols))
102
+
103
+
104
+ #: Objects in the compiler that are associated with a source span
105
+ ToSpan: TypeAlias = ast.AST | Span
106
+
107
+
108
+ def to_span(x: ToSpan) -> Span:
109
+ """Extracts a source span from an object."""
110
+ if isinstance(x, Span):
111
+ return x
112
+ file, line_offset = get_file(x), get_line_offset(x)
113
+ assert file is not None
114
+ assert line_offset is not None
115
+ # x.lineno and line_offset both start at 1, so we have to subtract 1
116
+ start = Loc(file, x.lineno + line_offset - 1, x.col_offset)
117
+ end = Loc(
118
+ file,
119
+ (x.end_lineno or x.lineno) + line_offset - 1,
120
+ x.end_col_offset or x.col_offset,
121
+ )
122
+ return Span(start, end)
123
+
124
+
125
+ #: List of source lines in a file
126
+ SourceLines: TypeAlias = list[str]
127
+
128
+
129
+ class SourceMap:
130
+ """Map holding the source code for all files accessed by the compiler.
131
+
132
+ Can be used to look up the source code associated with a span.
133
+ """
134
+
135
+ sources: dict[str, SourceLines]
136
+
137
+ def __init__(self) -> None:
138
+ self.sources = {}
139
+
140
+ def add_file(self, file: str, content: str | None = None) -> None:
141
+ """Registers a new source file."""
142
+ if content is None:
143
+ self.sources[file] = [line.rstrip() for line in linecache.getlines(file)]
144
+ else:
145
+ self.sources[file] = content.splitlines(keepends=False)
146
+
147
+ def span_lines(self, span: Span, prefix_lines: int = 0) -> list[str]:
148
+ return self.sources[span.file][
149
+ span.start.line - prefix_lines - 1 : span.end.line
150
+ ]
File without changes
File without changes