sonolus.py 0.1.4__py3-none-any.whl → 0.1.5__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.

Potentially problematic release.


This version of sonolus.py might be problematic. Click here for more details.

Files changed (77) hide show
  1. sonolus/backend/finalize.py +18 -10
  2. sonolus/backend/interpret.py +7 -7
  3. sonolus/backend/ir.py +24 -0
  4. sonolus/backend/optimize/__init__.py +0 -0
  5. sonolus/backend/{allocate.py → optimize/allocate.py} +4 -3
  6. sonolus/backend/{constant_evaluation.py → optimize/constant_evaluation.py} +7 -7
  7. sonolus/backend/{coalesce.py → optimize/copy_coalesce.py} +3 -3
  8. sonolus/backend/optimize/dead_code.py +185 -0
  9. sonolus/backend/{dominance.py → optimize/dominance.py} +2 -17
  10. sonolus/backend/{flow.py → optimize/flow.py} +6 -5
  11. sonolus/backend/{inlining.py → optimize/inlining.py} +4 -17
  12. sonolus/backend/{liveness.py → optimize/liveness.py} +69 -65
  13. sonolus/backend/optimize/optimize.py +44 -0
  14. sonolus/backend/{passes.py → optimize/passes.py} +1 -1
  15. sonolus/backend/optimize/simplify.py +191 -0
  16. sonolus/backend/{ssa.py → optimize/ssa.py} +31 -18
  17. sonolus/backend/place.py +17 -25
  18. sonolus/backend/utils.py +10 -0
  19. sonolus/backend/visitor.py +360 -101
  20. sonolus/build/compile.py +8 -8
  21. sonolus/build/engine.py +10 -5
  22. sonolus/script/archetype.py +419 -137
  23. sonolus/script/array.py +25 -8
  24. sonolus/script/array_like.py +297 -0
  25. sonolus/script/bucket.py +73 -11
  26. sonolus/script/containers.py +234 -51
  27. sonolus/script/debug.py +8 -8
  28. sonolus/script/easing.py +147 -105
  29. sonolus/script/effect.py +60 -0
  30. sonolus/script/engine.py +71 -4
  31. sonolus/script/globals.py +66 -32
  32. sonolus/script/instruction.py +79 -25
  33. sonolus/script/internal/builtin_impls.py +138 -27
  34. sonolus/script/internal/constant.py +139 -0
  35. sonolus/script/internal/context.py +14 -5
  36. sonolus/script/internal/dict_impl.py +65 -0
  37. sonolus/script/internal/generic.py +6 -9
  38. sonolus/script/internal/impl.py +38 -13
  39. sonolus/script/internal/introspection.py +5 -2
  40. sonolus/script/{math.py → internal/math_impls.py} +28 -28
  41. sonolus/script/internal/native.py +3 -3
  42. sonolus/script/internal/random.py +67 -0
  43. sonolus/script/internal/range.py +81 -0
  44. sonolus/script/internal/transient.py +51 -0
  45. sonolus/script/internal/tuple_impl.py +113 -0
  46. sonolus/script/interval.py +234 -16
  47. sonolus/script/iterator.py +120 -167
  48. sonolus/script/level.py +24 -0
  49. sonolus/script/num.py +79 -47
  50. sonolus/script/options.py +78 -12
  51. sonolus/script/particle.py +37 -4
  52. sonolus/script/pointer.py +4 -4
  53. sonolus/script/print.py +22 -1
  54. sonolus/script/project.py +8 -0
  55. sonolus/script/{graphics.py → quad.py} +75 -12
  56. sonolus/script/record.py +44 -13
  57. sonolus/script/runtime.py +50 -1
  58. sonolus/script/sprite.py +197 -112
  59. sonolus/script/text.py +2 -0
  60. sonolus/script/timing.py +72 -0
  61. sonolus/script/transform.py +296 -66
  62. sonolus/script/ui.py +134 -78
  63. sonolus/script/values.py +6 -13
  64. sonolus/script/vec.py +118 -3
  65. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/METADATA +1 -1
  66. sonolus_py-0.1.5.dist-info/RECORD +89 -0
  67. sonolus/backend/dead_code.py +0 -80
  68. sonolus/backend/optimize.py +0 -37
  69. sonolus/backend/simplify.py +0 -47
  70. sonolus/script/comptime.py +0 -160
  71. sonolus/script/random.py +0 -14
  72. sonolus/script/range.py +0 -58
  73. sonolus_py-0.1.4.dist-info/RECORD +0 -84
  74. /sonolus/script/{callbacks.py → internal/callbacks.py} +0 -0
  75. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/WHEEL +0 -0
  76. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/entry_points.txt +0 -0
  77. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/licenses/LICENSE +0 -0
sonolus/script/array.py CHANGED
@@ -5,29 +5,43 @@ from collections.abc import Iterable
5
5
  from typing import Any, Self, final
6
6
 
7
7
  from sonolus.backend.place import BlockPlace
8
+ from sonolus.script.array_like import ArrayLike
8
9
  from sonolus.script.debug import assert_unreachable
9
10
  from sonolus.script.internal.context import ctx
10
11
  from sonolus.script.internal.error import InternalError
11
12
  from sonolus.script.internal.generic import GenericValue
12
13
  from sonolus.script.internal.impl import meta_fn, validate_value
13
14
  from sonolus.script.internal.value import Value
14
- from sonolus.script.iterator import ArrayLike
15
15
  from sonolus.script.num import Num
16
16
 
17
17
 
18
18
  @final
19
19
  class Array[T, Size](GenericValue, ArrayLike[T]):
20
+ """A fixed size array of values.
21
+
22
+ Usage:
23
+ ```python
24
+ array_1 = Array(1, 2, 3)
25
+ array_2 = Array[int, 0]()
26
+ ```
27
+ """
28
+
20
29
  _value: list[T] | BlockPlace
21
30
 
22
31
  @classmethod
23
32
  @meta_fn
24
33
  def element_type(cls) -> type[T] | type[Value]:
25
- return cls._get_type_arg_(T)
34
+ """Return the type of elements in this array type."""
35
+ return cls.type_var_value(T)
26
36
 
27
37
  @classmethod
28
38
  @meta_fn
29
39
  def size(cls) -> int:
30
- return cls._get_type_arg_(Size)
40
+ """Return the size of this array type.
41
+
42
+ On instances, use `len(array)` instead.
43
+ """
44
+ return cls.type_var_value(Size)
31
45
 
32
46
  def __new__(cls, *args: T) -> Array[T, Any]:
33
47
  if cls._type_args_ is None:
@@ -130,7 +144,7 @@ class Array[T, Size](GenericValue, ArrayLike[T]):
130
144
 
131
145
  def _copy_(self) -> Self:
132
146
  if ctx():
133
- place = ctx().alloc(size=self.size())
147
+ place = ctx().alloc(size=self._size_())
134
148
  result: Self = self._from_place_(place)
135
149
  result._copy_from_(self)
136
150
  return result
@@ -145,16 +159,17 @@ class Array[T, Size](GenericValue, ArrayLike[T]):
145
159
  else:
146
160
  return cls._with_value([cls.element_type()._alloc_() for _ in range(cls.size())])
147
161
 
162
+ def __len__(self):
163
+ return self.size()
164
+
148
165
  @meta_fn
149
166
  def __getitem__(self, index: Num) -> T:
150
167
  index: Num = Num._accept_(index)
151
- if index._is_py_():
168
+ if index._is_py_() and 0 <= index._as_py_() < self.size():
152
169
  const_index = index._as_py_()
153
170
  if isinstance(const_index, float) and not const_index.is_integer():
154
171
  raise ValueError("Array index must be an integer")
155
172
  const_index = int(const_index)
156
- if not 0 <= const_index < self.size():
157
- raise IndexError("Array index out of range")
158
173
  if isinstance(self._value, list):
159
174
  if ctx():
160
175
  return self._value[const_index]._get_()
@@ -215,7 +230,9 @@ class Array[T, Size](GenericValue, ArrayLike[T]):
215
230
  dst._copy_from_(value)
216
231
 
217
232
  def __eq__(self, other):
218
- if self.size() != other.size():
233
+ if not isinstance(other, ArrayLike):
234
+ return False
235
+ if len(self) != len(other):
219
236
  return False
220
237
  i = 0
221
238
  while i < self.size():
@@ -0,0 +1,297 @@
1
+ from __future__ import annotations
2
+
3
+ import random
4
+ from abc import ABC, abstractmethod
5
+ from collections.abc import Callable, Sequence
6
+ from typing import Any
7
+
8
+ from sonolus.script.iterator import SonolusIterator
9
+ from sonolus.script.num import Num
10
+ from sonolus.script.record import Record
11
+ from sonolus.script.values import copy
12
+
13
+ # Note: we don't use Range in this file because Range itself inherits from ArrayLike
14
+
15
+
16
+ class ArrayLike[T](Sequence, ABC):
17
+ """Mixin for array-like objects.
18
+
19
+ Inheritors must implement `__len__`, `__getitem__`, and `__setitem__`.
20
+
21
+ Usage:
22
+ ```python
23
+ class MyArrayLike[T](Record, ArrayLike[T]):
24
+ def __len__(self) -> int:
25
+ ...
26
+
27
+ def __getitem__(self, index: Num) -> T:
28
+ ...
29
+
30
+ def __setitem__(self, index: Num, value: T):
31
+ ...
32
+ ```
33
+ """
34
+
35
+ @abstractmethod
36
+ def __len__(self) -> int:
37
+ """Return the length of the array."""
38
+
39
+ @abstractmethod
40
+ def __getitem__(self, index: Num) -> T:
41
+ """Return the item at the given index.
42
+
43
+ Args:
44
+ index: The index of the item. Must be an integer between 0 and `len(self) - 1`.
45
+ """
46
+
47
+ @abstractmethod
48
+ def __setitem__(self, index: Num, value: T):
49
+ """Set the value of the item at the given index.
50
+
51
+ Args:
52
+ index: The index of the item. Must be an integer between 0 and `len(self) - 1`.
53
+ value: The value to set.
54
+ """
55
+
56
+ def __iter__(self) -> SonolusIterator[T]:
57
+ """Return an iterator over the array."""
58
+ return _ArrayIterator(0, self)
59
+
60
+ def __contains__(self, value: Any) -> bool:
61
+ """Return whether any element in the array is equal to the given value.
62
+
63
+ Args:
64
+ value: The value to check for.
65
+ """
66
+ i = 0
67
+ while i < len(self):
68
+ if self[i] == value:
69
+ return True
70
+ i += 1
71
+ return False
72
+
73
+ def __reversed__(self):
74
+ """Return a reversed view of the array."""
75
+ return _ArrayReverser(self)
76
+
77
+ def _enumerate_(self, start: Num = 0) -> SonolusIterator[T]:
78
+ return _ArrayEnumerator(0, start, self)
79
+
80
+ def index(self, value: T, start: Num = 0, stop: Num | None = None) -> Num:
81
+ """Return the index of the value in the array equal to the given value.
82
+
83
+ Args:
84
+ value: The value to search for.
85
+ start: The index to start searching from.
86
+ stop: The index to stop searching at. If `None`, search to the end of the array.
87
+ """
88
+ if stop is None:
89
+ stop = len(self)
90
+ i = start
91
+ while i < stop:
92
+ if self[i] == value:
93
+ return i
94
+ i += 1
95
+ return -1
96
+
97
+ def count(self, value: T) -> Num:
98
+ """Return the number of elements in the array equal to the given value.
99
+
100
+ Args:
101
+ value: The value to count.
102
+ """
103
+ count = 0
104
+ i = 0
105
+ while i < len(self):
106
+ if self[i] == value:
107
+ count += 1
108
+ i += 1
109
+ return count
110
+
111
+ def last_index(self, value: T) -> Num:
112
+ """Return the last index of the value in the array equal to the given value.
113
+
114
+ Args:
115
+ value: The value to search for.
116
+ """
117
+ i = len(self) - 1
118
+ while i >= 0:
119
+ if self[i] == value:
120
+ return i
121
+ i -= 1
122
+ return -1
123
+
124
+ def index_of_max(self, *, key: Callable[T, Any] | None = None) -> Num:
125
+ """Return the index of the maximum value in the array.
126
+
127
+ Args:
128
+ key: A one-argument ordering function to use for comparison like the one used in `max()`.
129
+ """
130
+ if len(self) == 0:
131
+ return -1
132
+ if key is None:
133
+ key = _identity
134
+ max_index = 0
135
+ i = 1
136
+ while i < len(self):
137
+ if key(self[i]) > key(self[max_index]):
138
+ max_index = i
139
+ i += 1
140
+ return max_index
141
+
142
+ def index_of_min(self, *, key: Callable[T, Any] | None = None) -> Num:
143
+ """Return the index of the minimum value in the array.
144
+
145
+ Args:
146
+ key: A one-argument ordering function to use for comparison like the one used in `min()`.
147
+ """
148
+ if len(self) == 0:
149
+ return -1
150
+ if key is None:
151
+ key = _identity
152
+ min_index = 0
153
+ i = 1
154
+ while i < len(self):
155
+ if key(self[i]) < key(self[min_index]):
156
+ min_index = i
157
+ i += 1
158
+ return min_index
159
+
160
+ def _max_(self, key: Callable[T, Any] | None = None) -> T:
161
+ return self[self.index_of_max(key=key)]
162
+
163
+ def _min_(self, key: Callable[T, Any] | None = None) -> T:
164
+ return self[self.index_of_min(key=key)]
165
+
166
+ def swap(self, i: Num, j: Num, /):
167
+ """Swap the values at the given indices.
168
+
169
+ Args:
170
+ i: The first index.
171
+ j: The second index.
172
+ """
173
+ temp = copy(self[i])
174
+ self[i] = self[j]
175
+ self[j] = temp
176
+
177
+ def sort(self, *, key: Callable[T, Any] | None = None, reverse: bool = False):
178
+ """Sort the values in the array in place.
179
+
180
+ Args:
181
+ key: A one-argument ordering function to use for comparison.
182
+ reverse: If `True`, sort in descending order, otherwise sort in ascending order.
183
+ """
184
+ if len(self) < 15 or key is not None:
185
+ if key is None:
186
+ key = _identity
187
+ _insertion_sort(self, 0, len(self), key, reverse)
188
+ else:
189
+ # Heap sort is unstable, so if there's a key, we can't rely on it
190
+ _heap_sort(self, 0, len(self), reverse)
191
+
192
+ def shuffle(self):
193
+ """Shuffle the values in the array in place."""
194
+ random.shuffle(self) # type: ignore
195
+
196
+ def reverse(self):
197
+ """Reverse the values in the array in place."""
198
+ i = 0
199
+ j = len(self) - 1
200
+ while i < j:
201
+ self.swap(i, j)
202
+ i += 1
203
+ j -= 1
204
+
205
+
206
+ def _identity[T](value: T) -> T:
207
+ return value
208
+
209
+
210
+ def _insertion_sort[T](array: ArrayLike[T], start: Num, end: Num, key: Callable[T, Any], reverse: bool):
211
+ i = start + 1
212
+ if reverse:
213
+ while i < end:
214
+ j = i
215
+ while j > start and key(array[j - 1]) < key(array[j]):
216
+ array.swap(j - 1, j)
217
+ j -= 1
218
+ i += 1
219
+ else:
220
+ while i < end:
221
+ j = i
222
+ while j > start and key(array[j - 1]) > key(array[j]):
223
+ array.swap(j - 1, j)
224
+ j -= 1
225
+ i += 1
226
+
227
+
228
+ def _heapify[T](array: ArrayLike[T], end: Num, index: Num, reverse: bool):
229
+ while True:
230
+ left = index * 2 + 1
231
+ right = left + 1
232
+ largest = index
233
+ if left < end and (array[left] > array[largest]) != reverse:
234
+ largest = left
235
+ if right < end and (array[right] > array[largest]) != reverse:
236
+ largest = right
237
+ if largest == index:
238
+ break
239
+ array.swap(index, largest)
240
+ index = largest
241
+
242
+
243
+ def _heap_sort[T](array: ArrayLike[T], start: Num, end: Num, reverse: bool):
244
+ i = end // 2 - 1
245
+ while i >= start:
246
+ _heapify(array, end, i, reverse)
247
+ i -= 1
248
+ i = end - 1
249
+ while i > start:
250
+ array.swap(start, i)
251
+ _heapify(array, i, start, reverse)
252
+ i -= 1
253
+
254
+
255
+ class _ArrayIterator[V: ArrayLike](Record, SonolusIterator):
256
+ i: int
257
+ array: V
258
+
259
+ def has_next(self) -> bool:
260
+ return self.i < len(self.array)
261
+
262
+ def get(self) -> V:
263
+ return self.array[self.i]
264
+
265
+ def advance(self):
266
+ self.i += 1
267
+
268
+
269
+ class _ArrayReverser[V: ArrayLike](Record, ArrayLike):
270
+ array: V
271
+
272
+ def __len__(self) -> int:
273
+ return len(self.array)
274
+
275
+ def __getitem__(self, index: Num) -> V:
276
+ return self.array[len(self) - 1 - index]
277
+
278
+ def __setitem__(self, index: Num, value: V):
279
+ self.array[len(self) - 1 - index] = value
280
+
281
+ def reversed(self) -> ArrayLike[V]:
282
+ return self.array
283
+
284
+
285
+ class _ArrayEnumerator[V: ArrayLike](Record, SonolusIterator):
286
+ i: int
287
+ offset: int
288
+ array: V
289
+
290
+ def has_next(self) -> bool:
291
+ return self.i < len(self.array)
292
+
293
+ def get(self) -> tuple[int, Any]:
294
+ return self.i + self.offset, self.array[self.i]
295
+
296
+ def advance(self):
297
+ self.i += 1
sonolus/script/bucket.py CHANGED
@@ -11,15 +11,28 @@ from sonolus.script.internal.impl import meta_fn
11
11
  from sonolus.script.internal.introspection import get_field_specifiers
12
12
  from sonolus.script.internal.native import native_function
13
13
  from sonolus.script.interval import Interval
14
- from sonolus.script.pointer import deref
14
+ from sonolus.script.pointer import _deref
15
15
  from sonolus.script.record import Record
16
16
  from sonolus.script.sprite import Sprite
17
17
 
18
18
 
19
19
  class JudgmentWindow(Record):
20
+ """The window for judging the accuracy of a hit.
21
+
22
+ Usage:
23
+ ```
24
+ JudgmentWindow(perfect: Interval, great: Interval, good: Interval)
25
+ ```
26
+ """
27
+
20
28
  perfect: Interval
29
+ """Interval for a perfect hit."""
30
+
21
31
  great: Interval
32
+ """Interval for a great hit."""
33
+
22
34
  good: Interval
35
+ """Interval for a good hit."""
23
36
 
24
37
  def update(
25
38
  self,
@@ -27,6 +40,13 @@ class JudgmentWindow(Record):
27
40
  great: Interval | None = None,
28
41
  good: Interval | None = None,
29
42
  ):
43
+ """Update the window with the given intervals.
44
+
45
+ Args:
46
+ perfect: The interval for a perfect hit.
47
+ great: The interval for a great hit.
48
+ good: The interval for a good hit.
49
+ """
30
50
  if perfect is not None:
31
51
  self.perfect = perfect
32
52
  if great is not None:
@@ -35,6 +55,15 @@ class JudgmentWindow(Record):
35
55
  self.good = good
36
56
 
37
57
  def judge(self, actual: float, target: float) -> Judgment:
58
+ """Judge the accuracy of a hit.
59
+
60
+ Args:
61
+ actual: The actual time of the hit.
62
+ target: The target time of the hit.
63
+
64
+ Returns:
65
+ The judgment of the hit.
66
+ """
38
67
  return _judge(
39
68
  actual,
40
69
  target,
@@ -44,6 +73,7 @@ class JudgmentWindow(Record):
44
73
  )
45
74
 
46
75
  def __mul__(self, other: float | int) -> JudgmentWindow:
76
+ """Multiply the intervals by a scalar."""
47
77
  return JudgmentWindow(
48
78
  self.perfect * other,
49
79
  self.great * other,
@@ -52,6 +82,8 @@ class JudgmentWindow(Record):
52
82
 
53
83
 
54
84
  class Judgment(IntEnum):
85
+ """The judgment of a hit."""
86
+
55
87
  MISS = 0
56
88
  PERFECT = 1
57
89
  GREAT = 2
@@ -80,18 +112,26 @@ def _judge(
80
112
 
81
113
 
82
114
  class Bucket(Record):
115
+ """A bucket for entity judgment results.
116
+
117
+ Usage:
118
+ ```Bucket(id: int)```
119
+ """
120
+
83
121
  id: int
122
+ """Bucket ID."""
84
123
 
85
124
  @property
86
125
  @meta_fn
87
126
  def window(self) -> JudgmentWindow:
127
+ """The judgment window of the bucket."""
88
128
  if not ctx():
89
129
  raise RuntimeError("Bucket window access outside of compilation")
90
130
  match ctx().global_state.mode:
91
131
  case Mode.PLAY:
92
- return deref(ctx().blocks.LevelBucket, self.id * JudgmentWindow._size_(), JudgmentWindow)
132
+ return _deref(ctx().blocks.LevelBucket, self.id * JudgmentWindow._size_(), JudgmentWindow)
93
133
  case Mode.WATCH:
94
- return deref(ctx().blocks.LevelBucket, self.id * JudgmentWindow._size_(), JudgmentWindow)
134
+ return _deref(ctx().blocks.LevelBucket, self.id * JudgmentWindow._size_(), JudgmentWindow)
95
135
  case _:
96
136
  raise RuntimeError("Invalid mode for bucket window access")
97
137
 
@@ -104,7 +144,7 @@ class Bucket(Record):
104
144
 
105
145
 
106
146
  @dataclass
107
- class BucketSprite:
147
+ class _BucketSprite:
108
148
  id: int
109
149
  fallback_id: int | None
110
150
  x: int
@@ -128,8 +168,8 @@ class BucketSprite:
128
168
 
129
169
 
130
170
  @dataclass
131
- class BucketInfo:
132
- sprites: list[BucketSprite]
171
+ class _BucketInfo:
172
+ sprites: list[_BucketSprite]
133
173
  unit: str | None = None
134
174
 
135
175
  def to_dict(self):
@@ -150,12 +190,14 @@ def bucket_sprite(
150
190
  w: int,
151
191
  h: int,
152
192
  rotation: float = 0,
153
- ) -> BucketSprite:
154
- return BucketSprite(sprite.id, fallback_sprite.id if fallback_sprite else None, x, y, w, h, rotation)
193
+ ) -> _BucketSprite:
194
+ """Define a sprite for a bucket."""
195
+ return _BucketSprite(sprite.id, fallback_sprite.id if fallback_sprite else None, x, y, w, h, rotation)
155
196
 
156
197
 
157
- def bucket(*, sprites: list[BucketSprite], unit: str | None = None) -> Any:
158
- return BucketInfo(sprites, unit)
198
+ def bucket(*, sprites: list[_BucketSprite], unit: str | None = None) -> Any:
199
+ """Define a bucket with the given sprites and unit."""
200
+ return _BucketInfo(sprites, unit)
159
201
 
160
202
 
161
203
  type Buckets = NewType("Buckets", Any)
@@ -163,6 +205,26 @@ type Buckets = NewType("Buckets", Any)
163
205
 
164
206
  @dataclass_transform()
165
207
  def buckets[T](cls: type[T]) -> T | Buckets:
208
+ """Decorator to define a buckets class.
209
+
210
+ Usage:
211
+ ```python
212
+ @buckets
213
+ class Buckets:
214
+ note: Bucket = bucket(
215
+ sprites=[
216
+ bucket_sprite(
217
+ sprite=Skin.note,
218
+ x=0,
219
+ y=0,
220
+ w=2,
221
+ h=2,
222
+ )
223
+ ],
224
+ unit=StandardText.MILLISECOND_UNIT,
225
+ )
226
+ ```
227
+ """
166
228
  if len(cls.__bases__) != 1:
167
229
  raise ValueError("Buckets class must not inherit from any class (except object)")
168
230
  instance = cls()
@@ -174,7 +236,7 @@ def buckets[T](cls: type[T]) -> T | Buckets:
174
236
  annotation_values = annotation.__metadata__
175
237
  if annotation_type is not Bucket:
176
238
  raise TypeError(f"Invalid annotation for buckets: {annotation}, expected annotation of type Bucket")
177
- if len(annotation_values) != 1 or not isinstance(annotation_values[0], BucketInfo):
239
+ if len(annotation_values) != 1 or not isinstance(annotation_values[0], _BucketInfo):
178
240
  raise TypeError(
179
241
  f"Invalid annotation for buckets: {annotation}, expected a single BucketInfo annotation value"
180
242
  )