rapydscript-ns 0.8.3 → 0.9.0

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 (72) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +1351 -141
  3. package/TODO.md +12 -6
  4. package/language-service/index.js +184 -26
  5. package/package.json +1 -1
  6. package/release/baselib-plain-pretty.js +5895 -1928
  7. package/release/baselib-plain-ugly.js +140 -3
  8. package/release/compiler.js +16282 -5408
  9. package/release/signatures.json +25 -22
  10. package/src/ast.pyj +94 -1
  11. package/src/baselib-builtins.pyj +362 -3
  12. package/src/baselib-bytes.pyj +664 -0
  13. package/src/baselib-containers.pyj +99 -0
  14. package/src/baselib-errors.pyj +45 -1
  15. package/src/baselib-internal.pyj +346 -49
  16. package/src/baselib-itertools.pyj +17 -4
  17. package/src/baselib-str.pyj +46 -4
  18. package/src/lib/abc.pyj +317 -0
  19. package/src/lib/copy.pyj +120 -0
  20. package/src/lib/dataclasses.pyj +532 -0
  21. package/src/lib/enum.pyj +125 -0
  22. package/src/lib/pythonize.pyj +1 -1
  23. package/src/lib/re.pyj +35 -1
  24. package/src/lib/react.pyj +74 -0
  25. package/src/lib/typing.pyj +577 -0
  26. package/src/monaco-language-service/builtins.js +19 -4
  27. package/src/monaco-language-service/diagnostics.js +40 -19
  28. package/src/output/classes.pyj +161 -25
  29. package/src/output/codegen.pyj +16 -2
  30. package/src/output/exceptions.pyj +97 -1
  31. package/src/output/functions.pyj +87 -5
  32. package/src/output/jsx.pyj +164 -0
  33. package/src/output/literals.pyj +28 -2
  34. package/src/output/loops.pyj +5 -2
  35. package/src/output/modules.pyj +1 -1
  36. package/src/output/operators.pyj +108 -36
  37. package/src/output/statements.pyj +2 -2
  38. package/src/output/stream.pyj +1 -0
  39. package/src/parse.pyj +496 -128
  40. package/src/tokenizer.pyj +38 -4
  41. package/test/abc.pyj +291 -0
  42. package/test/arithmetic_nostrict.pyj +88 -0
  43. package/test/arithmetic_types.pyj +169 -0
  44. package/test/baselib.pyj +91 -0
  45. package/test/bytes.pyj +467 -0
  46. package/test/classes.pyj +1 -0
  47. package/test/comparison_ops.pyj +173 -0
  48. package/test/dataclasses.pyj +253 -0
  49. package/test/enum.pyj +134 -0
  50. package/test/eval_exec.pyj +56 -0
  51. package/test/format.pyj +148 -0
  52. package/test/object.pyj +64 -0
  53. package/test/python_compat.pyj +17 -15
  54. package/test/python_features.pyj +89 -21
  55. package/test/regexp.pyj +29 -1
  56. package/test/tuples.pyj +96 -0
  57. package/test/typing.pyj +469 -0
  58. package/test/unit/index.js +2292 -70
  59. package/test/unit/language-service.js +674 -4
  60. package/test/unit/web-repl.js +1106 -0
  61. package/test/vars_locals_globals.pyj +94 -0
  62. package/tools/cli.js +11 -0
  63. package/tools/compile.js +5 -0
  64. package/tools/embedded_compiler.js +15 -4
  65. package/tools/lint.js +16 -19
  66. package/tools/repl.js +1 -1
  67. package/web-repl/env.js +122 -0
  68. package/web-repl/main.js +1 -3
  69. package/web-repl/rapydscript.js +125 -3
  70. package/PYTHON_DIFFERENCES_REPORT.md +0 -291
  71. package/PYTHON_FEATURE_COVERAGE.md +0 -200
  72. package/hack_demo.pyj +0 -112
package/src/tokenizer.pyj CHANGED
@@ -90,7 +90,7 @@ KEYWORDS_ATOM = "False None True"
90
90
  # see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar
91
91
  RESERVED_WORDS = ("break case class catch const continue debugger default delete do else export extends"
92
92
  " finally for function if import in instanceof new return switch this throw try typeof var void"
93
- " while with yield enum implements static private package let public protected interface await null true false" )
93
+ " while with yield implements static private package let public protected interface await null true false" )
94
94
 
95
95
  KEYWORDS_BEFORE_EXPRESSION = "return yield new del raise elif else if"
96
96
 
@@ -104,7 +104,7 @@ IDENTIFIER_PAT = /^[a-z_$][_a-z0-9$]*$/i
104
104
 
105
105
  def is_string_modifier(val):
106
106
  for ch in val:
107
- if 'vrufVRUF'.indexOf(ch) is -1:
107
+ if 'vrufbVRUFB'.indexOf(ch) is -1:
108
108
  return False
109
109
  return True
110
110
 
@@ -177,6 +177,7 @@ def tokenizer(raw_text, filename):
177
177
  'index_or_slice': v'[ false ]',
178
178
  'expecting_object_literal_key': False, # This is set by the parser when it is expecting an object literal key
179
179
  'prev_was_comma': False, # True when the previous token was a ',' — used to detect positional-only '/' separator
180
+ 'jsx_depth': 0, # >0 when inside JSX children content
180
181
  }
181
182
  def peek():
182
183
  return S.text.charAt(S.pos)
@@ -362,6 +363,8 @@ def tokenizer(raw_text, filename):
362
363
  return False
363
364
  elif ch is '.':
364
365
  return (has_dot = True) if not has_dot and not has_x and not has_e else False
366
+ elif ch is 'j' or ch is 'J':
367
+ return False # imaginary suffix — stop here; handled after loop
365
368
  return is_alphanumeric_char(ch.charCodeAt(0))
366
369
  )
367
370
  if prefix:
@@ -369,6 +372,9 @@ def tokenizer(raw_text, filename):
369
372
 
370
373
  valid = parse_js_number(num)
371
374
  if not isNaN(valid):
375
+ if peek() is 'j' or peek() is 'J':
376
+ next()
377
+ return token("imaginary", valid)
372
378
  return token("num", valid)
373
379
  else:
374
380
  parse_error("Invalid syntax: " + num)
@@ -639,6 +645,23 @@ def tokenizer(raw_text, filename):
639
645
  if not ch:
640
646
  return token("eof")
641
647
 
648
+ # JSX children mode: when inside JSX content, read raw text until { or <
649
+ if S.jsx_depth > 0 and ch is not '{' and ch is not '<':
650
+ result = ""
651
+ while S.pos < S.text.length:
652
+ c = S.text.charAt(S.pos)
653
+ if c is '{' or c is '<':
654
+ break
655
+ if c is '\n':
656
+ S.line += 1
657
+ S.col = 0
658
+ S.newline_before = True
659
+ else:
660
+ S.col += 1
661
+ result += c
662
+ S.pos += 1
663
+ return token("jsx_text", result)
664
+
642
665
  code = ch.charCodeAt(0)
643
666
  tmp_ = code
644
667
  if tmp_ is 34 or tmp_ is 39: # double-quote (") or single quote (')
@@ -680,7 +703,7 @@ def tokenizer(raw_text, filename):
680
703
 
681
704
  if is_identifier_start(code):
682
705
  tok = read_word()
683
- if '\'"'.indexOf(peek()) is not -1 and is_string_modifier(tok.value):
706
+ if (peek() is "'" or peek() is '"') and is_string_modifier(tok.value):
684
707
  mods = tok.value.toLowerCase()
685
708
  start_pos_for_string = S.tokpos
686
709
  stok = read_string(mods.indexOf('r') is not -1, mods.indexOf('v') is not -1)
@@ -689,7 +712,7 @@ def tokenizer(raw_text, filename):
689
712
  tok.col += start_pos_for_string - tok.pos
690
713
  return handle_interpolated_string(stok.value, tok)
691
714
  tok.value = stok.value
692
- tok.type = stok.type
715
+ tok.type = 'bytes_literal' if mods.indexOf('b') is not -1 else stok.type
693
716
  return tok
694
717
 
695
718
  parse_error("Unexpected character «" + ch + "»")
@@ -700,4 +723,15 @@ def tokenizer(raw_text, filename):
700
723
  S = nc
701
724
  return S
702
725
 
726
+ next_token.jsx_enter = def():
727
+ S.jsx_depth += 1
728
+ S.indentation_matters.push(False)
729
+
730
+ next_token.jsx_exit = def():
731
+ if S.jsx_depth > 0:
732
+ S.jsx_depth -= 1
733
+ S.indentation_matters.pop()
734
+ # After < in JSX context, / is always an operator (e.g. </tag>), never a regex
735
+ S.regex_allowed = False
736
+
703
737
  return next_token
package/test/abc.pyj ADDED
@@ -0,0 +1,291 @@
1
+ # globals: assrt
2
+ # vim:fileencoding=utf-8
3
+ #
4
+ # abc.pyj — tests for the abc standard library module.
5
+
6
+ ae = assrt.equal
7
+ ade = assrt.deepEqual
8
+ ok = assrt.ok
9
+
10
+ from abc import ABC, ABCMeta, abstractmethod, Protocol, runtime_checkable, get_cache_token
11
+
12
+ # ── 1. @abstractmethod marks the function ─────────────────────────────────────
13
+
14
+ def _tagged(): pass
15
+ abstractmethod(_tagged)
16
+ ok(_tagged.__isabstractmethod__)
17
+
18
+ # ── 2. Abstract class cannot be instantiated ──────────────────────────────────
19
+
20
+ class _Shape(ABC):
21
+ @abstractmethod
22
+ def area(self): pass
23
+
24
+ _raised = False
25
+ try:
26
+ _Shape()
27
+ except TypeError:
28
+ _raised = True
29
+ ok(_raised)
30
+
31
+ # The error message contains the abstract method name
32
+ _msg = ''
33
+ try:
34
+ _Shape()
35
+ except TypeError as e:
36
+ _msg = str(e)
37
+ ok('_Shape' in _msg)
38
+ ok('area' in _msg)
39
+
40
+ # ── 3. Concrete subclass CAN be instantiated ──────────────────────────────────
41
+
42
+ class _Circle(_Shape):
43
+ def __init__(self, r):
44
+ self.r = r
45
+ def area(self):
46
+ return 3.14159 * self.r * self.r
47
+
48
+ _c = _Circle(5)
49
+ ok(_c is not None)
50
+ ae(_c.r, 5)
51
+ ok(abs(_c.area() - 78.53975) < 0.001)
52
+
53
+ # ── 4. isinstance checks work for concrete subclasses ─────────────────────────
54
+
55
+ ok(isinstance(_c, _Circle))
56
+ ok(isinstance(_c, _Shape))
57
+ ok(isinstance(_c, ABC))
58
+
59
+ # ── 5. Multiple abstract methods listed in TypeError (alphabetical order) ──────
60
+
61
+ class _Multi(ABC):
62
+ @abstractmethod
63
+ def bar(self): pass
64
+ @abstractmethod
65
+ def foo(self): pass
66
+
67
+ _m_raised = False
68
+ _m_msg = ''
69
+ try:
70
+ _Multi()
71
+ except TypeError as e:
72
+ _m_raised = True
73
+ _m_msg = str(e)
74
+ ok(_m_raised)
75
+ ok('bar' in _m_msg)
76
+ ok('foo' in _m_msg)
77
+ ok('methods' in _m_msg) # plural
78
+
79
+ # ── 6. Partial implementation still raises TypeError ──────────────────────────
80
+
81
+ class _PartialMulti(_Multi):
82
+ def foo(self):
83
+ return 1
84
+
85
+ _partial_raised = False
86
+ try:
87
+ _PartialMulti()
88
+ except TypeError:
89
+ _partial_raised = True
90
+ ok(_partial_raised)
91
+
92
+ # Full implementation works
93
+ class _FullMulti(_Multi):
94
+ def foo(self): return 1
95
+ def bar(self): return 2
96
+
97
+ _fm = _FullMulti()
98
+ ae(_fm.foo(), 1)
99
+ ae(_fm.bar(), 2)
100
+
101
+ # ── 7. __abstractmethods__ set on the class ───────────────────────────────────
102
+
103
+ ok('area' in _Shape.__abstractmethods__)
104
+ ae(len(_Shape.__abstractmethods__), 1)
105
+ ae(len(_Circle.__abstractmethods__), 0)
106
+
107
+ # ── 8. Abstract class with own __init__ ───────────────────────────────────────
108
+
109
+ class _Vehicle(ABC):
110
+ def __init__(self, name):
111
+ self.name = name
112
+
113
+ @abstractmethod
114
+ def drive(self): pass
115
+
116
+ class _Car(_Vehicle):
117
+ def drive(self):
118
+ return 'driving ' + self.name
119
+
120
+ _car = _Car('Tesla')
121
+ ae(_car.name, 'Tesla')
122
+ ae(_car.drive(), 'driving Tesla')
123
+
124
+ _veh_raised = False
125
+ try:
126
+ _Vehicle('Bus')
127
+ except TypeError:
128
+ _veh_raised = True
129
+ ok(_veh_raised)
130
+
131
+ # ── 9. Multi-level abstract inheritance ───────────────────────────────────────
132
+
133
+ class _A(ABC):
134
+ @abstractmethod
135
+ def m1(self): pass
136
+
137
+ class _B(_A):
138
+ @abstractmethod
139
+ def m2(self): pass
140
+
141
+ _b_raised = False
142
+ try:
143
+ _B()
144
+ except TypeError:
145
+ _b_raised = True
146
+ ok(_b_raised)
147
+
148
+ class _C(_B):
149
+ def m1(self): return 'one'
150
+ def m2(self): return 'two'
151
+
152
+ _cobj = _C()
153
+ ae(_cobj.m1(), 'one')
154
+ ae(_cobj.m2(), 'two')
155
+
156
+ # ── 10. ABC.register() — virtual subclass isinstance ─────────────────────────
157
+
158
+ class _MyABC(ABC):
159
+ @abstractmethod
160
+ def do_it(self): pass
161
+
162
+ class _External:
163
+ def do_it(self):
164
+ return 42
165
+
166
+ _MyABC.register(_External)
167
+ ok(isinstance(_External(), _MyABC))
168
+
169
+ # Non-registered class without inheritance is NOT an instance
170
+ class _Unrelated:
171
+ def do_it(self): return 99
172
+
173
+ ok(not isinstance(_Unrelated(), _MyABC))
174
+
175
+ # ── 11. ABC.register() used as a class decorator ──────────────────────────────
176
+
177
+ class _Base(ABC):
178
+ @abstractmethod
179
+ def compute(self): pass
180
+
181
+ @_Base.register
182
+ class _Virtual:
183
+ def compute(self): return 7
184
+
185
+ ok(isinstance(_Virtual(), _Base))
186
+
187
+ # ── 12. get_cache_token() returns 0 ──────────────────────────────────────────
188
+
189
+ ae(get_cache_token(), 0)
190
+
191
+ # ── 13. Protocol + @runtime_checkable — structural isinstance ──────────────────
192
+
193
+ @runtime_checkable
194
+ class _Drawable(Protocol):
195
+ def draw(self): pass
196
+
197
+ class _Canvas:
198
+ def draw(self):
199
+ return 'painting'
200
+
201
+ class _NotDrawable:
202
+ def paint(self): pass
203
+
204
+ ok(isinstance(_Canvas(), _Drawable))
205
+ ok(not isinstance(_NotDrawable(), _Drawable))
206
+ ok(not isinstance(42, _Drawable))
207
+ ok(not isinstance(None, _Drawable))
208
+
209
+ # ── 14. Protocol without @runtime_checkable — normal inheritance check ─────────
210
+
211
+ class _Closeable(Protocol):
212
+ def close(self): pass
213
+
214
+ class _ManagedFile(_Closeable):
215
+ def close(self):
216
+ return 'closed'
217
+
218
+ ok(isinstance(_ManagedFile(), _ManagedFile))
219
+ ok(isinstance(_ManagedFile(), _Closeable)) # direct subclass => True
220
+
221
+ class _OtherFile:
222
+ def close(self): return 'also closed'
223
+
224
+ # Without runtime_checkable, structural check does NOT happen
225
+ ok(not isinstance(_OtherFile(), _Closeable))
226
+
227
+ # ── 15. Protocol with @abstractmethod enforces instantiation guard ─────────────
228
+
229
+ class _AbstractProto(Protocol):
230
+ @abstractmethod
231
+ def required(self): pass
232
+
233
+ _ap_raised = False
234
+ try:
235
+ _AbstractProto()
236
+ except TypeError:
237
+ _ap_raised = True
238
+ ok(_ap_raised)
239
+
240
+ # Concrete implementation is instantiable
241
+ class _ConcreteProto(_AbstractProto):
242
+ def required(self): return 'done'
243
+
244
+ ok(_ConcreteProto() is not None)
245
+
246
+ # ── 16. Protocol __protocol_attrs__ contains non-dunder method names ───────────
247
+
248
+ @runtime_checkable
249
+ class _Sizeable(Protocol):
250
+ def size(self): pass
251
+ def count(self): pass
252
+
253
+ ok('size' in _Sizeable.__protocol_attrs__)
254
+ ok('count' in _Sizeable.__protocol_attrs__)
255
+
256
+ class _Box:
257
+ def size(self): return 10
258
+ def count(self): return 5
259
+
260
+ class _Half:
261
+ def size(self): return 3
262
+
263
+ ok(isinstance(_Box(), _Sizeable))
264
+ ok(not isinstance(_Half(), _Sizeable))
265
+
266
+ # ── 17. Protocol protocol_attrs inheritance (sub-protocols) ───────────────────
267
+
268
+ @runtime_checkable
269
+ class _Reader(Protocol):
270
+ def read(self): pass
271
+
272
+ @runtime_checkable
273
+ class _Writer(Protocol):
274
+ def write(self, data): pass
275
+
276
+ @runtime_checkable
277
+ class _ReadWriter(_Reader, _Writer, Protocol):
278
+ pass
279
+
280
+ ok('read' in _ReadWriter.__protocol_attrs__)
281
+ ok('write' in _ReadWriter.__protocol_attrs__)
282
+
283
+ class _FullRW:
284
+ def read(self): return 'data'
285
+ def write(self, data): pass
286
+
287
+ class _ReadOnly:
288
+ def read(self): return 'data'
289
+
290
+ ok(isinstance(_FullRW(), _ReadWriter))
291
+ ok(not isinstance(_ReadOnly(), _ReadWriter))
@@ -0,0 +1,88 @@
1
+ # globals: assrt
2
+ # vim:fileencoding=utf-8
3
+ #
4
+ # arithmetic_nostrict.pyj
5
+ # Tests for no_strict_arithmetic: when strict_arithmetic is disabled, incompatible
6
+ # operand types fall back to JavaScript's native coercion instead of raising TypeError.
7
+ # overload_operators must be active for the ρσ_op_*_ns helpers to be dispatched.
8
+
9
+ from __python__ import overload_operators, no_strict_arithmetic
10
+
11
+ ae = assrt.equal
12
+ ade = assrt.deepEqual
13
+ ok = assrt.ok
14
+
15
+ def _no_error(fn):
16
+ try:
17
+ fn()
18
+ return True
19
+ except:
20
+ return False
21
+
22
+ # ── 1. Valid numeric arithmetic still works ───────────────────────────────────
23
+
24
+ ae(1 + 2, 3)
25
+ ae(10 - 3, 7)
26
+ ae(3 * 4, 12)
27
+ ae(7 / 2, 3.5)
28
+ ae(7 // 2, 3)
29
+ ae(7 % 3, 1)
30
+ ae(2 ** 8, 256)
31
+
32
+ # ── 2. Valid string concatenation still works ─────────────────────────────────
33
+
34
+ ae('hello' + ' world', 'hello world')
35
+
36
+ # ── 3. Valid list concatenation still works ───────────────────────────────────
37
+
38
+ ade([1, 2] + [3, 4], [1, 2, 3, 4])
39
+
40
+ # ── 4. String / list repetition still works ──────────────────────────────────
41
+
42
+ ae('ha' * 3, 'hahaha')
43
+ ae(3 * 'ha', 'hahaha')
44
+ ade([0] * 3, [0, 0, 0])
45
+
46
+ # ── 5. Mixed types do NOT raise TypeError ─────────────────────────────────────
47
+
48
+ ok(_no_error(def(): return 1 + 'x';))
49
+ ok(_no_error(def(): return 'x' + 1;))
50
+ ok(_no_error(def(): return 1 - 'x';))
51
+ ok(_no_error(def(): return None + 1;))
52
+
53
+ # ── 6. JS coercion semantics for mixed types ──────────────────────────────────
54
+
55
+ # int + str coerces via JS (number becomes string)
56
+ ae(1 + 'x', '1x')
57
+ ae('x' + 2, 'x2')
58
+
59
+ # number - string coerces string to NaN
60
+ ok(isNaN(1 - 'x'))
61
+
62
+ # ── 7. Dunder methods still dispatch ──────────────────────────────────────────
63
+
64
+ class _Meter:
65
+ def __init__(self, n):
66
+ self.n = n
67
+ def __add__(self, other):
68
+ return _Meter(self.n + other.n)
69
+ def __mul__(self, scalar):
70
+ return _Meter(self.n * scalar)
71
+
72
+ m = _Meter(3) + _Meter(4)
73
+ ae(m.n, 7)
74
+ m2 = _Meter(5) * 2
75
+ ae(m2.n, 10)
76
+
77
+ # ── 8. Augmented assignment does not raise TypeError ──────────────────────────
78
+
79
+ ok(_no_error(def():
80
+ x = 1
81
+ x += 'oops'
82
+ ))
83
+
84
+ # ── 9. Comparison operators fall through to JS ────────────────────────────────
85
+
86
+ # JS comparisons with mixed types don't throw; they may produce surprising results
87
+ ok(_no_error(def(): return 1 < 'x';))
88
+ ok(_no_error(def(): return None >= 0;))
@@ -0,0 +1,169 @@
1
+ # globals: assrt
2
+ # vim:fileencoding=utf-8
3
+ #
4
+ # arithmetic_types.pyj
5
+ # Tests for Python-style arithmetic type coercion: incompatible operand types
6
+ # raise TypeError instead of silently coercing (as JavaScript would).
7
+ # overload_operators must be active for the ρσ_op_* helpers to be dispatched.
8
+
9
+ from __python__ import overload_operators, strict_arithmetic
10
+
11
+ ae = assrt.equal
12
+ ade = assrt.deepEqual
13
+ ok = assrt.ok
14
+
15
+ def _raises_type_error(fn):
16
+ try:
17
+ fn()
18
+ return False
19
+ except TypeError:
20
+ return True
21
+
22
+ # ── 1. Valid numeric arithmetic ──────────────────────────────────────────────
23
+
24
+ ae(1 + 2, 3)
25
+ ae(1.5 + 2.5, 4.0)
26
+ ae(10 - 3, 7)
27
+ ae(3 * 4, 12)
28
+ ae(7 / 2, 3.5)
29
+ ae(7 // 2, 3)
30
+ ae(7 % 3, 1)
31
+ ae(2 ** 8, 256)
32
+
33
+ # ── 2. Valid string concatenation ────────────────────────────────────────────
34
+
35
+ ae('hello' + ' world', 'hello world')
36
+ ae('a' + 'b' + 'c', 'abc')
37
+
38
+ # ── 3. Valid list concatenation ──────────────────────────────────────────────
39
+
40
+ ade([1, 2] + [3, 4], [1, 2, 3, 4])
41
+ ade([] + [1], [1])
42
+
43
+ # ── 4. Valid string/list repetition ──────────────────────────────────────────
44
+
45
+ ae('ha' * 3, 'hahaha')
46
+ ae(3 * 'ha', 'hahaha')
47
+ ade([0] * 3, [0, 0, 0])
48
+ ade(3 * [1, 2], [1, 2, 1, 2, 1, 2])
49
+
50
+ # ── 5. bool treated as int for arithmetic (Python semantics) ─────────────────
51
+
52
+ ae(True + 1, 2)
53
+ ae(False + 0, 0)
54
+ ae(True + True, 2)
55
+ ae(True * 5, 5)
56
+ ae(False * 100, 0)
57
+ ae(True + 1.5, 2.5)
58
+
59
+ # bool * str and bool * list repetition
60
+ ae(True * 'x', 'x')
61
+ ae(False * 'x', '')
62
+ ade(True * [1, 2], [1, 2])
63
+ ade(False * [1, 2], [])
64
+
65
+ # ── 6. int + str raises TypeError ────────────────────────────────────────────
66
+
67
+ ok(_raises_type_error(def(): return 1 + 'x';))
68
+ ok(_raises_type_error(def(): return 'x' + 1;))
69
+ ok(_raises_type_error(def(): return 1 + '1';))
70
+
71
+ # ── 7. numeric - non-numeric raises TypeError ────────────────────────────────
72
+
73
+ ok(_raises_type_error(def(): return 1 - 'x';))
74
+ ok(_raises_type_error(def(): return 'a' - 'b';))
75
+ ok(_raises_type_error(def(): return [1] - [2];))
76
+
77
+ # ── 8. numeric / non-numeric raises TypeError ────────────────────────────────
78
+
79
+ ok(_raises_type_error(def(): return 1 / 'x';))
80
+ ok(_raises_type_error(def(): return 'a' / 2;))
81
+
82
+ # ── 9. numeric // non-numeric raises TypeError ───────────────────────────────
83
+
84
+ ok(_raises_type_error(def(): return 5 // 'x';))
85
+ ok(_raises_type_error(def(): return 'a' // 2;))
86
+
87
+ # ── 10. numeric % non-numeric raises TypeError ───────────────────────────────
88
+
89
+ ok(_raises_type_error(def(): return 5 % 'x';))
90
+ ok(_raises_type_error(def(): return 'a' % 2;))
91
+
92
+ # ── 11. numeric ** non-numeric raises TypeError ──────────────────────────────
93
+
94
+ ok(_raises_type_error(def(): return 2 ** 'x';))
95
+ ok(_raises_type_error(def(): return 'a' ** 2;))
96
+
97
+ # ── 12. mixed str * non-numeric raises TypeError ─────────────────────────────
98
+
99
+ ok(_raises_type_error(def(): return 'ha' * 'ha';))
100
+ ok(_raises_type_error(def(): return 'ha' * [1];))
101
+
102
+ # ── 13. list + non-list raises TypeError ─────────────────────────────────────
103
+
104
+ ok(_raises_type_error(def(): return [1, 2] + 1;))
105
+ ok(_raises_type_error(def(): return [1, 2] + 'x';))
106
+ ok(_raises_type_error(def(): return 1 + [1, 2];))
107
+
108
+ # ── 14. None as operand raises TypeError ─────────────────────────────────────
109
+
110
+ ok(_raises_type_error(def(): return None + 1;))
111
+ ok(_raises_type_error(def(): return 1 + None;))
112
+ ok(_raises_type_error(def(): return None + 'x';))
113
+
114
+ # ── 15. TypeError message contains operand type names ────────────────────────
115
+
116
+ msg = ''
117
+ try:
118
+ v = 1 + 'x'
119
+ except TypeError as e:
120
+ msg = str(e)
121
+ ok(msg.indexOf('int') >= 0)
122
+ ok(msg.indexOf('str') >= 0)
123
+ ok(msg.indexOf('+') >= 0)
124
+
125
+ msg2 = ''
126
+ try:
127
+ v = 2.5 - 'y'
128
+ except TypeError as e:
129
+ msg2 = str(e)
130
+ ok(msg2.indexOf('float') >= 0)
131
+ ok(msg2.indexOf('str') >= 0)
132
+ ok(msg2.indexOf('-') >= 0)
133
+
134
+ # ── 16. augmented assignment also raises TypeError ───────────────────────────
135
+
136
+ ok(_raises_type_error(def():
137
+ x = 1
138
+ x += 'oops'
139
+ ))
140
+ ok(_raises_type_error(def():
141
+ x = 'hello'
142
+ x -= 1
143
+ ))
144
+
145
+ # ── 17. dunder methods still take priority over type checks ──────────────────
146
+
147
+ class _Meter:
148
+ def __init__(self, n):
149
+ self.n = n
150
+ def __add__(self, other):
151
+ return _Meter(self.n + other.n)
152
+ def __mul__(self, scalar):
153
+ return _Meter(self.n * scalar)
154
+
155
+ m = _Meter(3) + _Meter(4)
156
+ ae(m.n, 7)
157
+ m2 = _Meter(5) * 2
158
+ ae(m2.n, 10)
159
+
160
+ # ── 18. radd/rsub reflected methods still work ───────────────────────────────
161
+
162
+ class _Vec:
163
+ def __init__(self, x):
164
+ self.x = x
165
+ def __radd__(self, other):
166
+ return _Vec(other + self.x)
167
+
168
+ v = 10 + _Vec(5)
169
+ ae(v.x, 15)