Typhon-Language 0.1.2__py3-none-any.whl → 0.1.4__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 (57) hide show
  1. Typhon/Driver/configs.py +14 -0
  2. Typhon/Driver/debugging.py +148 -5
  3. Typhon/Driver/diagnostic.py +4 -3
  4. Typhon/Driver/language_server.py +25 -0
  5. Typhon/Driver/run.py +1 -1
  6. Typhon/Driver/translate.py +16 -11
  7. Typhon/Driver/utils.py +39 -1
  8. Typhon/Grammar/_typhon_parser.py +2920 -2718
  9. Typhon/Grammar/parser.py +80 -53
  10. Typhon/Grammar/parser_helper.py +68 -87
  11. Typhon/Grammar/syntax_errors.py +41 -20
  12. Typhon/Grammar/token_factory_custom.py +541 -485
  13. Typhon/Grammar/tokenizer_custom.py +52 -0
  14. Typhon/Grammar/typhon_ast.py +754 -76
  15. Typhon/Grammar/typhon_ast_error.py +438 -0
  16. Typhon/Grammar/unparse_custom.py +25 -0
  17. Typhon/LanguageServer/__init__.py +3 -0
  18. Typhon/LanguageServer/client/__init__.py +42 -0
  19. Typhon/LanguageServer/client/pyrefly.py +115 -0
  20. Typhon/LanguageServer/client/pyright.py +173 -0
  21. Typhon/LanguageServer/semantic_tokens.py +446 -0
  22. Typhon/LanguageServer/server.py +376 -0
  23. Typhon/LanguageServer/utils.py +65 -0
  24. Typhon/SourceMap/ast_match_based_map.py +199 -152
  25. Typhon/SourceMap/ast_matching.py +102 -87
  26. Typhon/SourceMap/datatype.py +275 -264
  27. Typhon/SourceMap/defined_name_retrieve.py +145 -0
  28. Typhon/Transform/comprehension_to_function.py +2 -5
  29. Typhon/Transform/const_member_to_final.py +12 -7
  30. Typhon/Transform/extended_patterns.py +139 -0
  31. Typhon/Transform/forbidden_statements.py +25 -0
  32. Typhon/Transform/if_while_let.py +122 -11
  33. Typhon/Transform/inline_statement_block_capture.py +22 -15
  34. Typhon/Transform/optional_operators_to_checked.py +14 -6
  35. Typhon/Transform/placeholder_to_function.py +0 -1
  36. Typhon/Transform/record_to_dataclass.py +22 -238
  37. Typhon/Transform/scope_check_rename.py +109 -29
  38. Typhon/Transform/transform.py +16 -12
  39. Typhon/Transform/type_abbrev_desugar.py +11 -15
  40. Typhon/Transform/type_annotation_check_expand.py +2 -2
  41. Typhon/Transform/utils/__init__.py +0 -0
  42. Typhon/Transform/utils/imports.py +83 -0
  43. Typhon/Transform/{utils.py → utils/jump_away.py} +2 -38
  44. Typhon/Transform/utils/make_class.py +135 -0
  45. Typhon/Transform/visitor.py +25 -0
  46. Typhon/Typing/pyrefly.py +145 -0
  47. Typhon/Typing/pyright.py +141 -144
  48. Typhon/Typing/result_diagnostic.py +1 -1
  49. Typhon/__main__.py +15 -1
  50. {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/METADATA +13 -6
  51. typhon_language-0.1.4.dist-info/RECORD +65 -0
  52. {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/WHEEL +1 -1
  53. typhon_language-0.1.4.dist-info/licenses/LICENSE +201 -0
  54. typhon_language-0.1.2.dist-info/RECORD +0 -48
  55. typhon_language-0.1.2.dist-info/licenses/LICENSE +0 -21
  56. {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/entry_points.txt +0 -0
  57. {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/top_level.txt +0 -0
@@ -1,264 +1,275 @@
1
- from dataclasses import dataclass
2
- from ..Grammar.typhon_ast import PosAttributes, PosRange, get_pos_attributes_if_exists
3
- from ..Driver.debugging import debug_verbose_print
4
- from intervaltree import IntervalTree, Interval
5
- from typing import Iterable
6
- import ast
7
-
8
-
9
- @dataclass(frozen=True, unsafe_hash=True, order=True)
10
- class Pos:
11
- line: int
12
- column: int
13
-
14
- @staticmethod
15
- def from_start_pos_attributes(attr: PosAttributes) -> "Pos":
16
- return Pos(line=attr["lineno"], column=attr["col_offset"])
17
-
18
- @staticmethod
19
- def from_end_pos_attributes(attr: PosAttributes) -> "Pos | None":
20
- if attr["end_lineno"] is None or attr["end_col_offset"] is None:
21
- return None
22
- return Pos(line=attr["end_lineno"], column=attr["end_col_offset"])
23
-
24
- def col_back(self) -> "Pos":
25
- if self.column == 0:
26
- return self
27
- else:
28
- return Pos(line=self.line, column=self.column - 1)
29
-
30
- def col_forward(self) -> "Pos":
31
- return Pos(line=self.line, column=self.column + 1)
32
-
33
- def calc_offset(self, other: "Pos") -> "Pos":
34
- line_offset = other.line - self.line
35
- if line_offset == 0:
36
- column_offset = other.column - self.column
37
- else:
38
- column_offset = other.column
39
- return Pos(line=line_offset, column=column_offset)
40
-
41
- def apply_offset(self, offset: "Range") -> "Range":
42
- # offset is (start_offset, offset_from_start_to_end)
43
- start_column_offset = 0
44
- if offset.start.line == 0:
45
- start_column_offset = self.column + offset.start.column
46
- new_start_column = start_column_offset
47
- end_column_start = 0
48
- if offset.end.line == 0:
49
- end_column_start = new_start_column
50
- debug_verbose_print(
51
- f"Applying offset: pos={self}, offset={offset} -> new_start_column={
52
- new_start_column
53
- }, end_column_start={end_column_start} = {
54
- Range(
55
- Pos(self.line + offset.start.line, new_start_column),
56
- Pos(
57
- self.line + offset.start.line + offset.end.line,
58
- end_column_start + offset.end.column,
59
- ),
60
- )
61
- }"
62
- )
63
- return Range(
64
- Pos(self.line + offset.start.line, new_start_column),
65
- Pos(
66
- self.line + offset.start.line + offset.end.line,
67
- end_column_start + offset.end.column,
68
- ),
69
- )
70
-
71
-
72
- # Common central datatype for source range
73
- @dataclass(frozen=True, unsafe_hash=True, order=True)
74
- class Range:
75
- start: Pos
76
- end: Pos
77
-
78
- def contains(self, pos: Pos) -> bool:
79
- return self.start <= pos < self.end
80
-
81
- def includes(self, other: "Range") -> bool:
82
- return self.start <= other.start and other.end <= self.end
83
-
84
- def calc_offset(self, other: "Range") -> "Range":
85
- offset = self.start.calc_offset(other.start)
86
- end_offset = other.start.calc_offset(other.end)
87
- return Range(offset, end_offset)
88
-
89
- @staticmethod
90
- def from_positions(
91
- start_line: int, start_column: int, end_line: int, end_column: int
92
- ) -> "Range":
93
- return Range(
94
- start=Pos(line=start_line, column=start_column),
95
- end=Pos(line=end_line, column=end_column),
96
- )
97
-
98
- @staticmethod
99
- def from_pos_range(pos_range: PosRange) -> "Range":
100
- return Range(
101
- start=Pos(line=pos_range["lineno"], column=pos_range["col_offset"]),
102
- end=Pos(line=pos_range["end_lineno"], column=pos_range["end_col_offset"]),
103
- )
104
-
105
- @staticmethod
106
- def from_pos_attr(attr: PosAttributes) -> "Range | None":
107
- if attr["end_lineno"] is None or attr["end_col_offset"] is None:
108
- return None
109
- return Range(
110
- start=Pos(line=attr["lineno"], column=attr["col_offset"]),
111
- end=Pos(line=attr["end_lineno"], column=attr["end_col_offset"]),
112
- )
113
-
114
- @staticmethod
115
- def from_pos_attr_may_not_end(attr: PosAttributes) -> "Range":
116
- start_line = attr["lineno"]
117
- start_column = attr["col_offset"]
118
- end_line = (
119
- attr["end_lineno"] if attr["end_lineno"] is not None else attr["lineno"]
120
- )
121
- end_column = (
122
- attr["end_col_offset"]
123
- if attr["end_col_offset"] is not None
124
- else attr["col_offset"] + 1
125
- )
126
- if end_line == start_line and start_column == end_column:
127
- end_column += 1 # Ensure non-zero length
128
- return Range(
129
- start=Pos(line=start_line, column=start_column),
130
- end=Pos(line=end_line, column=end_column),
131
- )
132
-
133
- @staticmethod
134
- def from_interval(interval: Interval) -> "Range":
135
- return Range(start=interval.begin, end=interval.end) # type: ignore[misc]
136
-
137
- @staticmethod
138
- def from_ast_node(node: ast.AST) -> "Range | None":
139
- attr = get_pos_attributes_if_exists(node)
140
- if attr is None:
141
- return None
142
- return Range.from_pos_attr(attr)
143
-
144
- @staticmethod
145
- def from_syntax_error(e: SyntaxError) -> "Range":
146
- start_line = e.lineno or 0
147
- start_column = e.offset or 0
148
- end_line = e.end_lineno or start_line
149
- end_column = e.end_offset or (start_column + 1)
150
- return Range(
151
- start=Pos(line=start_line, column=start_column),
152
- end=Pos(line=end_line, column=end_column),
153
- )
154
-
155
- @staticmethod
156
- def merge_ranges(ranges: Iterable["Range"]) -> "Range | None":
157
- if not ranges:
158
- return None
159
- print("Merging ranges: ", ranges)
160
- ranges = list(ranges)
161
- start = min(r.start for r in ranges)
162
- end = max([r.end for r in ranges] + [start])
163
- print(f"Merged range: start={start}, end={end}")
164
- return Range(start=start, end=end)
165
-
166
- def of_string(self, source: str) -> str:
167
- lines = source.splitlines()
168
- return self.of_lines(lines)
169
-
170
- def of_lines(self, lines: list[str]) -> str:
171
- # Range is 1 based. Convert to 0 based for string slicing.
172
- start_line = self.start.line - 1
173
- end_line = self.end.line - 1
174
- start_column = self.start.column - 1
175
- end_column = self.end.column - 1
176
-
177
- def get_in_line(line: str, start_col: int, end_col: int) -> str:
178
- return line[start_col : min(end_col, len(line))]
179
-
180
- if start_line >= len(lines) or end_line >= len(lines):
181
- return ""
182
- if start_line == end_line:
183
- the_line = lines[start_line]
184
- return get_in_line(the_line, start_column, end_column)
185
- else:
186
- result_lines: list[str] = []
187
- result_lines.append(lines[start_line][start_column:])
188
- for line_num in range(start_line, end_line):
189
- result_lines.append(lines[line_num])
190
- result_lines.append(get_in_line(lines[end_line], 0, end_column))
191
- return "\n".join(result_lines)
192
-
193
-
194
- type RangeInterval[T] = tuple[Range, T]
195
-
196
-
197
- # Wrapper around IntervalTree for Range keys
198
- class RangeIntervalTree[T]:
199
- def __init__(self):
200
- self.interval_tree = IntervalTree()
201
-
202
- def add(self, range: Range, data: T):
203
- self.interval_tree.addi( # type: ignore[misc]
204
- range.start,
205
- range.end,
206
- data,
207
- )
208
-
209
- def at(self, pos: Pos) -> list[RangeInterval[T]]:
210
- intervals = self.interval_tree.at(pos) # type: ignore[misc]
211
- return [
212
- (Range.from_interval(interval), interval.data) # type: ignore[misc]
213
- for interval in intervals # type: ignore[misc]
214
- ]
215
-
216
- def overlap(self, range: Range) -> list[RangeInterval[T]]:
217
- intervals = self.interval_tree.overlap(range.start, range.end) # type: ignore[misc]
218
- return [
219
- (Range.from_interval(interval), interval.data) # type: ignore[misc]
220
- for interval in intervals # type: ignore[misc]
221
- ]
222
-
223
- def envelop(self, range: Range) -> list[RangeInterval[T]]:
224
- intervals = self.interval_tree.envelop(range.start, range.end) # type: ignore[misc]
225
- return [
226
- (Range.from_interval(interval), interval.data) # type: ignore[misc]
227
- for interval in intervals # type: ignore[misc]
228
- ]
229
-
230
- def _container_intervals(self, range: Range) -> list[Interval]:
231
- # TODO: How to write this even IntervalTree doesn't have containers method?
232
- start_intervals = self.interval_tree.at(range.start) # type: ignore[misc]
233
- if not start_intervals:
234
- return []
235
- return [
236
- interval # type: ignore[misc]
237
- for interval in start_intervals # type: ignore[misc]
238
- if (interval.contains_point(range.end) or interval.end == range.end) # type: ignore[misc]
239
- ]
240
-
241
- def containers(self, range: Range) -> list[RangeInterval[T]]:
242
- containers = self._container_intervals(range)
243
- result: list[RangeInterval[T]] = []
244
- for interval in containers:
245
- result.append((Range.from_interval(interval), interval.data)) # type: ignore[misc]
246
- return result
247
-
248
- def minimal_containers(self, range: Range) -> list[RangeInterval[T]]:
249
- containers = self._container_intervals(range)
250
- if not containers:
251
- return []
252
- result: list[RangeInterval[T]] = []
253
- interval: Interval
254
- for i, interval in enumerate(containers):
255
- is_minimal = True
256
- for j, other_interval in enumerate(containers):
257
- if (
258
- i != j and interval.contains_interval(other_interval) # type: ignore[misc]
259
- ):
260
- is_minimal = False
261
- break
262
- if is_minimal:
263
- result.append((Range.from_interval(interval), interval.data)) # type: ignore[misc]
264
- return result
1
+ from dataclasses import dataclass
2
+ from ..Grammar.typhon_ast import PosAttributes, PosRange, get_pos_attributes_if_exists
3
+ from ..Driver.debugging import debug_verbose_print
4
+ from intervaltree import IntervalTree, Interval
5
+ from typing import Iterable
6
+ import ast
7
+
8
+ from basedpyright.langserver import main
9
+
10
+
11
+ @dataclass(frozen=True, unsafe_hash=True, order=True)
12
+ class Pos:
13
+ line: int
14
+ column: int
15
+
16
+ @staticmethod
17
+ def from_start_pos_attributes(attr: PosAttributes) -> "Pos":
18
+ return Pos(line=attr["lineno"], column=attr["col_offset"])
19
+
20
+ @staticmethod
21
+ def from_end_pos_attributes(attr: PosAttributes) -> "Pos | None":
22
+ if attr["end_lineno"] is None or attr["end_col_offset"] is None:
23
+ return None
24
+ return Pos(line=attr["end_lineno"], column=attr["end_col_offset"])
25
+
26
+ def col_back(self) -> "Pos":
27
+ if self.column == 0:
28
+ return self
29
+ else:
30
+ return Pos(line=self.line, column=self.column - 1)
31
+
32
+ def col_forward(self) -> "Pos":
33
+ return Pos(line=self.line, column=self.column + 1)
34
+
35
+ def calc_offset(self, other: "Pos") -> "Pos":
36
+ line_offset = other.line - self.line
37
+ if line_offset == 0:
38
+ column_offset = other.column - self.column
39
+ else:
40
+ column_offset = other.column
41
+ return Pos(line=line_offset, column=column_offset)
42
+
43
+ def apply_offset(self, offset: "Range") -> "Range":
44
+ # offset is (start_offset, offset_from_start_to_end)
45
+ start_column_offset = 0
46
+ if offset.start.line == 0:
47
+ start_column_offset = self.column + offset.start.column
48
+ new_start_column = start_column_offset
49
+ end_column_start = 0
50
+ if offset.end.line == 0:
51
+ end_column_start = new_start_column
52
+ debug_verbose_print(
53
+ f"Applying offset: pos={self}, offset={offset} -> new_start_column={
54
+ new_start_column
55
+ }, end_column_start={end_column_start} = {
56
+ Range(
57
+ Pos(self.line + offset.start.line, new_start_column),
58
+ Pos(
59
+ self.line + offset.start.line + offset.end.line,
60
+ end_column_start + offset.end.column,
61
+ ),
62
+ )
63
+ }"
64
+ )
65
+ return Range(
66
+ Pos(self.line + offset.start.line, new_start_column),
67
+ Pos(
68
+ self.line + offset.start.line + offset.end.line,
69
+ end_column_start + offset.end.column,
70
+ ),
71
+ )
72
+
73
+
74
+ # Common central datatype for source range. All are 0 based.
75
+ @dataclass(frozen=True, unsafe_hash=True, order=True)
76
+ class Range:
77
+ start: Pos
78
+ end: Pos
79
+
80
+ def contains(self, pos: Pos) -> bool:
81
+ return self.start <= pos < self.end
82
+
83
+ def includes(self, other: "Range") -> bool:
84
+ return self.start <= other.start and other.end <= self.end
85
+
86
+ def calc_offset(self, other: "Range") -> "Range":
87
+ offset = self.start.calc_offset(other.start)
88
+ end_offset = other.start.calc_offset(other.end)
89
+ return Range(offset, end_offset)
90
+
91
+ @staticmethod
92
+ def from_positions(
93
+ start_line: int, start_column: int, end_line: int, end_column: int
94
+ ) -> "Range":
95
+ return Range(
96
+ start=Pos(line=start_line, column=start_column),
97
+ end=Pos(line=end_line, column=end_column),
98
+ )
99
+
100
+ @staticmethod
101
+ def from_pos_range(pos_range: PosRange) -> "Range":
102
+ return Range(
103
+ start=Pos(line=pos_range["lineno"] - 1, column=pos_range["col_offset"]),
104
+ end=Pos(
105
+ line=pos_range["end_lineno"] - 1, column=pos_range["end_col_offset"]
106
+ ),
107
+ )
108
+
109
+ @staticmethod
110
+ def from_pos_attr(attr: PosAttributes) -> "Range | None":
111
+ # Python ast position is 1-based for line, 0-based for column
112
+ if attr["end_lineno"] is None or attr["end_col_offset"] is None:
113
+ return None
114
+ return Range(
115
+ start=Pos(line=attr["lineno"] - 1, column=attr["col_offset"]),
116
+ end=Pos(line=attr["end_lineno"] - 1, column=attr["end_col_offset"]),
117
+ )
118
+
119
+ @staticmethod
120
+ def from_pos_attr_may_not_end(attr: PosAttributes) -> "Range":
121
+ start_line = attr["lineno"]
122
+ start_column = attr["col_offset"]
123
+ end_line = (
124
+ attr["end_lineno"] if attr["end_lineno"] is not None else attr["lineno"]
125
+ )
126
+ end_column = (
127
+ attr["end_col_offset"]
128
+ if attr["end_col_offset"] is not None
129
+ else attr["col_offset"] + 1
130
+ )
131
+ if end_line == start_line and start_column == end_column:
132
+ end_column += 1 # Ensure non-zero length
133
+ return Range(
134
+ start=Pos(line=start_line - 1, column=start_column),
135
+ end=Pos(line=end_line - 1, column=end_column),
136
+ )
137
+
138
+ @staticmethod
139
+ def from_interval(interval: Interval) -> "Range":
140
+ return Range(start=interval.begin, end=interval.end) # type: ignore[misc]
141
+
142
+ @staticmethod
143
+ def from_ast_node(node: ast.AST) -> "Range | None":
144
+ attr = get_pos_attributes_if_exists(node)
145
+ if attr is None:
146
+ return None
147
+ return Range.from_pos_attr(attr)
148
+
149
+ @staticmethod
150
+ def from_syntax_error(e: SyntaxError) -> "Range":
151
+ start_line = e.lineno or 1
152
+ start_column = e.offset or 0
153
+ end_line = e.end_lineno or start_line
154
+ end_column = e.end_offset or (start_column + 1)
155
+ return Range(
156
+ start=Pos(line=start_line - 1, column=start_column),
157
+ end=Pos(line=end_line - 1, column=end_column),
158
+ )
159
+
160
+ @staticmethod
161
+ def merge_ranges(ranges: Iterable["Range"]) -> "Range | None":
162
+ if not ranges:
163
+ return None
164
+ debug_verbose_print("Merging ranges: ", ranges)
165
+ ranges = list(ranges)
166
+ start = min(r.start for r in ranges)
167
+ end = max([r.end for r in ranges] + [start])
168
+ debug_verbose_print(f"Merged range: start={start}, end={end}")
169
+ return Range(start=start, end=end)
170
+
171
+ def of_string(self, source: str) -> str:
172
+ lines = source.splitlines()
173
+ return self.of_lines(lines)
174
+
175
+ def of_lines(self, lines: list[str]) -> str:
176
+ start_line = self.start.line
177
+ end_line = self.end.line
178
+ start_column = self.start.column
179
+ end_column = self.end.column
180
+
181
+ def get_in_line(line: str, start_col: int, end_col: int) -> str:
182
+ return line[start_col : min(end_col, len(line))]
183
+
184
+ if start_line >= len(lines) or end_line >= len(lines):
185
+ return ""
186
+ if start_line == end_line:
187
+ the_line = lines[start_line]
188
+ return get_in_line(the_line, start_column, end_column)
189
+ else:
190
+ result_lines: list[str] = []
191
+ result_lines.append(lines[start_line][start_column:])
192
+ for line_num in range(start_line, end_line):
193
+ result_lines.append(lines[line_num])
194
+ result_lines.append(get_in_line(lines[end_line], 0, end_column))
195
+ return "\n".join(result_lines)
196
+
197
+ def deconstruct_str(self):
198
+ return f"Range(Pos({self.start.line}, {self.start.column}), Pos({self.end.line}, {self.end.column}))"
199
+
200
+
201
+ type RangeInterval[T] = tuple[Range, T]
202
+
203
+
204
+ # Wrapper around IntervalTree for Range keys
205
+ class RangeIntervalTree[T]:
206
+ def __init__(self):
207
+ self.interval_tree = IntervalTree()
208
+
209
+ def add(self, range: Range, data: T):
210
+ self.interval_tree.addi( # type: ignore[misc]
211
+ range.start,
212
+ range.end,
213
+ data,
214
+ )
215
+
216
+ def at(self, pos: Pos) -> list[RangeInterval[T]]:
217
+ intervals = self.interval_tree.at(pos) # type: ignore[misc]
218
+ return [
219
+ (Range.from_interval(interval), interval.data) # type: ignore[misc]
220
+ for interval in intervals # type: ignore[misc]
221
+ ]
222
+
223
+ def overlap(self, range: Range) -> list[RangeInterval[T]]:
224
+ intervals = self.interval_tree.overlap(range.start, range.end) # type: ignore[misc]
225
+ return [
226
+ (Range.from_interval(interval), interval.data) # type: ignore[misc]
227
+ for interval in intervals # type: ignore[misc]
228
+ ]
229
+
230
+ def envelop(self, range: Range) -> list[RangeInterval[T]]:
231
+ intervals = self.interval_tree.envelop(range.start, range.end) # type: ignore[misc]
232
+ return [
233
+ (Range.from_interval(interval), interval.data) # type: ignore[misc]
234
+ for interval in intervals # type: ignore[misc]
235
+ ]
236
+
237
+ def _container_intervals(self, range: Range) -> list[Interval]:
238
+ # TODO: How to write this even IntervalTree doesn't have containers method?
239
+ start_intervals = self.interval_tree.at(range.start) # type: ignore[misc]
240
+ if not start_intervals:
241
+ return []
242
+ return [
243
+ interval # type: ignore[misc]
244
+ for interval in start_intervals # type: ignore[misc]
245
+ if (interval.contains_point(range.end) or interval.end == range.end) # type: ignore[misc]
246
+ ]
247
+
248
+ def containers(self, range: Range) -> list[RangeInterval[T]]:
249
+ containers = self._container_intervals(range)
250
+ result: list[RangeInterval[T]] = []
251
+ for interval in containers:
252
+ result.append((Range.from_interval(interval), interval.data)) # type: ignore[misc]
253
+ return result
254
+
255
+ def minimal_containers(self, range: Range) -> list[RangeInterval[T]]:
256
+ containers = self._container_intervals(range)
257
+ debug_verbose_print(f"Minimal containers for range {range}: {containers}")
258
+ if not containers:
259
+ return []
260
+ result: list[RangeInterval[T]] = []
261
+ interval: Interval
262
+ for i, interval in enumerate(containers):
263
+ is_minimal = True
264
+ for j, other_interval in enumerate(containers):
265
+ if (
266
+ i != j
267
+ and interval.contains_interval(other_interval) # type: ignore[misc]
268
+ and not interval.range_matches(other_interval) # type: ignore[misc]
269
+ ):
270
+ is_minimal = False
271
+ break
272
+ if is_minimal:
273
+ debug_verbose_print(f" Minimal container: {interval}")
274
+ result.append((Range.from_interval(interval), interval.data)) # type: ignore[misc]
275
+ return result