rapydscript-ns 0.9.2 → 0.9.3

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 (151) hide show
  1. package/.agignore +1 -1
  2. package/.github/workflows/ci.yml +38 -38
  3. package/=template.pyj +5 -5
  4. package/CHANGELOG.md +19 -0
  5. package/HACKING.md +103 -103
  6. package/LICENSE +24 -24
  7. package/PYTHON_GAPS.md +420 -0
  8. package/README.md +153 -29
  9. package/TODO.md +16 -118
  10. package/add-toc-to-readme +2 -2
  11. package/bin/export +75 -75
  12. package/bin/rapydscript +70 -70
  13. package/bin/web-repl-export +102 -102
  14. package/build +2 -2
  15. package/language-service/index.js +237 -8
  16. package/memory/project_string_impl.md +43 -0
  17. package/package.json +1 -1
  18. package/publish.py +37 -37
  19. package/release/baselib-plain-pretty.js +248 -38
  20. package/release/baselib-plain-ugly.js +8 -8
  21. package/release/compiler.js +778 -277
  22. package/release/signatures.json +30 -30
  23. package/session.vim +4 -4
  24. package/setup.cfg +2 -2
  25. package/src/ast.pyj +4 -1
  26. package/src/baselib-builtins.pyj +56 -2
  27. package/src/baselib-containers.pyj +2 -0
  28. package/src/baselib-errors.pyj +7 -3
  29. package/src/baselib-internal.pyj +51 -6
  30. package/src/baselib-str.pyj +5 -3
  31. package/src/compiler.pyj +36 -36
  32. package/src/errors.pyj +30 -30
  33. package/src/lib/aes.pyj +646 -646
  34. package/src/lib/asyncio.pyj +534 -0
  35. package/src/lib/base64.pyj +399 -0
  36. package/src/lib/bisect.pyj +73 -0
  37. package/src/lib/collections.pyj +1 -1
  38. package/src/lib/copy.pyj +120 -120
  39. package/src/lib/csv.pyj +494 -0
  40. package/src/lib/elementmaker.pyj +83 -83
  41. package/src/lib/encodings.pyj +126 -126
  42. package/src/lib/gettext.pyj +569 -569
  43. package/src/lib/heapq.pyj +98 -0
  44. package/src/lib/html.pyj +382 -0
  45. package/src/lib/http/__init__.pyj +98 -0
  46. package/src/lib/http/client.pyj +304 -0
  47. package/src/lib/http/cookies.pyj +236 -0
  48. package/src/lib/itertools.pyj +580 -580
  49. package/src/lib/logging.pyj +672 -0
  50. package/src/lib/math.pyj +193 -193
  51. package/src/lib/operator.pyj +11 -11
  52. package/src/lib/pythonize.pyj +20 -20
  53. package/src/lib/random.pyj +118 -118
  54. package/src/lib/react.pyj +74 -74
  55. package/src/lib/string.pyj +357 -0
  56. package/src/lib/textwrap.pyj +329 -0
  57. package/src/lib/traceback.pyj +63 -63
  58. package/src/lib/urllib/__init__.pyj +14 -0
  59. package/src/lib/urllib/error.pyj +66 -0
  60. package/src/lib/urllib/parse.pyj +475 -0
  61. package/src/lib/urllib/request.pyj +86 -0
  62. package/src/lib/uuid.pyj +77 -77
  63. package/src/monaco-language-service/analyzer.js +5 -2
  64. package/src/monaco-language-service/completions.js +26 -0
  65. package/src/monaco-language-service/diagnostics.js +202 -3
  66. package/src/monaco-language-service/dts.js +550 -550
  67. package/src/monaco-language-service/scope.js +1 -0
  68. package/src/output/comments.pyj +45 -45
  69. package/src/output/exceptions.pyj +201 -201
  70. package/src/output/functions.pyj +152 -6
  71. package/src/output/jsx.pyj +164 -164
  72. package/src/output/loops.pyj +17 -2
  73. package/src/output/modules.pyj +1 -1
  74. package/src/output/operators.pyj +15 -0
  75. package/src/output/stream.pyj +0 -1
  76. package/src/output/treeshake.pyj +182 -182
  77. package/src/output/utils.pyj +72 -72
  78. package/src/parse.pyj +80 -17
  79. package/src/string_interpolation.pyj +72 -72
  80. package/src/tokenizer.pyj +1 -1
  81. package/src/unicode_aliases.pyj +576 -576
  82. package/src/utils.pyj +192 -192
  83. package/test/_import_one.pyj +37 -37
  84. package/test/_import_two/__init__.pyj +11 -11
  85. package/test/_import_two/level2/deep.pyj +4 -4
  86. package/test/_import_two/other.pyj +6 -6
  87. package/test/_import_two/sub.pyj +13 -13
  88. package/test/aes_vectors.pyj +421 -421
  89. package/test/annotations.pyj +80 -80
  90. package/test/async_generators.pyj +144 -0
  91. package/test/asyncio.pyj +307 -0
  92. package/test/base64.pyj +202 -0
  93. package/test/bisect.pyj +178 -0
  94. package/test/csv.pyj +405 -0
  95. package/test/decorators.pyj +77 -77
  96. package/test/docstrings.pyj +39 -39
  97. package/test/elementmaker_test.pyj +45 -45
  98. package/test/float_special.pyj +64 -0
  99. package/test/functions.pyj +151 -151
  100. package/test/generators.pyj +41 -41
  101. package/test/generic.pyj +370 -370
  102. package/test/heapq.pyj +174 -0
  103. package/test/html.pyj +212 -0
  104. package/test/http.pyj +259 -0
  105. package/test/imports.pyj +79 -72
  106. package/test/internationalization.pyj +73 -73
  107. package/test/lint.pyj +164 -164
  108. package/test/logging.pyj +356 -0
  109. package/test/long.pyj +130 -0
  110. package/test/loops.pyj +85 -85
  111. package/test/numpy.pyj +734 -734
  112. package/test/parenthesized_with.pyj +141 -0
  113. package/test/python_compat.pyj +3 -5
  114. package/test/python_modulo.pyj +76 -0
  115. package/test/python_modulo_off.pyj +21 -0
  116. package/test/repl.pyj +121 -121
  117. package/test/scoped_flags.pyj +76 -76
  118. package/test/str.pyj +14 -0
  119. package/test/string.pyj +245 -0
  120. package/test/textwrap.pyj +172 -0
  121. package/test/type_display.pyj +48 -0
  122. package/test/type_enforcement.pyj +164 -0
  123. package/test/unit/index.js +14 -6
  124. package/test/unit/language-service-completions.js +119 -0
  125. package/test/unit/language-service-dts.js +543 -543
  126. package/test/unit/language-service-hover.js +455 -455
  127. package/test/unit/language-service-scope.js +32 -0
  128. package/test/unit/language-service.js +127 -3
  129. package/test/unit/run-language-service.js +17 -3
  130. package/test/unit/web-repl.js +2094 -29
  131. package/test/urllib.pyj +193 -0
  132. package/tools/compile.js +1 -1
  133. package/tools/compiler.d.ts +367 -367
  134. package/tools/completer.js +131 -131
  135. package/tools/embedded_compiler.js +7 -7
  136. package/tools/gettext.js +185 -185
  137. package/tools/ini.js +65 -65
  138. package/tools/msgfmt.js +187 -187
  139. package/tools/repl.js +223 -223
  140. package/tools/test.js +118 -118
  141. package/tools/utils.js +128 -128
  142. package/tools/web_repl.js +95 -95
  143. package/try +41 -41
  144. package/web-repl/env.js +196 -196
  145. package/web-repl/index.html +163 -163
  146. package/web-repl/main.js +1 -1
  147. package/web-repl/prism.css +139 -139
  148. package/web-repl/prism.js +113 -113
  149. package/web-repl/rapydscript.js +224 -224
  150. package/web-repl/sha1.js +25 -25
  151. package/test/omit_function_metadata.pyj +0 -20
@@ -0,0 +1,86 @@
1
+ ###########################################################
2
+ # RapydScript Standard Library
3
+ # urllib.request — HTTP requests via the Fetch API
4
+ ###########################################################
5
+ #
6
+ # Provides:
7
+ # urlopen(url[, data[, timeout]]) — open a URL; returns a Promise
8
+ #
9
+ # Usage (inside an async function):
10
+ # from urllib.request import urlopen
11
+ #
12
+ # async def fetch_json(url):
13
+ # resp = await urlopen(url)
14
+ # data = await resp.json()
15
+ # return data
16
+ #
17
+ # The response object exposes:
18
+ # .status -- HTTP status code (int)
19
+ # .reason -- HTTP status text (str)
20
+ # .url -- final URL after any redirects (str)
21
+ # .headers -- plain dict of lowercase header names → values
22
+ # .read() -- Promise → response body as a string
23
+ # .json() -- Promise → parsed JSON response body
24
+ # .close() -- no-op (for API compatibility)
25
+ #
26
+ # Raises urllib.error.HTTPError for non-2xx responses.
27
+ # Raises urllib.error.URLError for network/timeout failures.
28
+ #
29
+ # Note: urlopen() wraps the browser/Node Fetch API and therefore returns a
30
+ # Promise. It must be awaited inside an async function. There is no
31
+ # synchronous version because JavaScript has no synchronous HTTP.
32
+
33
+ from urllib.error import URLError, HTTPError
34
+
35
+
36
+ def urlopen(url, data=None, timeout=None):
37
+ """Open url, returning a Promise that resolves to a response object.
38
+
39
+ url -- the URL to open (string)
40
+ data -- if given, the request uses POST and sends data as the body
41
+ timeout -- seconds before an AbortController cancels the request (float)
42
+
43
+ On success the Promise resolves to an object with .status, .reason, .url,
44
+ .headers, .read(), .json(), and .close() (see module docstring).
45
+ On HTTP error (non-2xx status) the Promise rejects with HTTPError.
46
+ On network failure the Promise rejects with URLError.
47
+ """
48
+ v"""
49
+ var _opts = { method: data != null ? 'POST' : 'GET' };
50
+ if (data != null) {
51
+ _opts.body = data;
52
+ _opts.headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
53
+ }
54
+ var _abort = null;
55
+ if (timeout != null && typeof AbortController !== 'undefined') {
56
+ var _ctrl = new AbortController();
57
+ _opts.signal = _ctrl.signal;
58
+ _abort = setTimeout(function() { _ctrl.abort(); }, timeout * 1000);
59
+ }
60
+ return Promise.resolve()
61
+ .then(function() { return fetch(url, _opts); })
62
+ .then(function(resp) {
63
+ if (_abort) { clearTimeout(_abort); _abort = null; }
64
+ var hdrs = {};
65
+ if (resp.headers && typeof resp.headers.forEach === 'function') {
66
+ resp.headers.forEach(function(v, k) { hdrs[k] = v; });
67
+ }
68
+ if (!resp.ok) {
69
+ throw new HTTPError(resp.url, resp.status, resp.statusText, hdrs, null);
70
+ }
71
+ return {
72
+ status: resp.status,
73
+ reason: resp.statusText,
74
+ url: resp.url,
75
+ headers: hdrs,
76
+ read: function() { return resp.text(); },
77
+ json: function() { return resp.json(); },
78
+ close: function() {}
79
+ };
80
+ })
81
+ .catch(function(e) {
82
+ if (_abort) { clearTimeout(_abort); _abort = null; }
83
+ if (e instanceof HTTPError) throw e;
84
+ throw new URLError(String(e));
85
+ });
86
+ """
package/src/lib/uuid.pyj CHANGED
@@ -1,77 +1,77 @@
1
- # vim:fileencoding=utf-8
2
- # License: BSD Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
3
- # globals: crypto
4
- from __python__ import hash_literals
5
-
6
- from encodings import hexlify, urlsafe_b64decode, urlsafe_b64encode
7
-
8
- RFC_4122 = 1
9
-
10
- if jstype(crypto) is 'object' and crypto.getRandomValues:
11
- random_bytes = def (num):
12
- ans = Uint8Array(num or 16)
13
- crypto.getRandomValues(ans)
14
- return ans
15
- else:
16
- random_bytes = def (num):
17
- ans = Uint8Array(num or 16)
18
- for i in range(ans.length):
19
- ans[i] = Math.floor(Math.random() * 256)
20
- return ans
21
-
22
-
23
- def uuid4_bytes():
24
- data = random_bytes()
25
- data[6] = 0b01000000 | (data[6] & 0b1111)
26
- data[8] = (((data[8] >> 4) & 0b11 | 0b1000) << 4) | (data[8] & 0b1111)
27
- return data
28
-
29
-
30
- def as_str():
31
- h = this.hex
32
- return h[:8] + '-' + h[8:12] + '-' + h[12:16] + '-' + h[16:20] + '-' + h[20:]
33
-
34
-
35
- def uuid4():
36
- b = uuid4_bytes()
37
- return {
38
- 'hex': hexlify(b),
39
- 'bytes': b,
40
- 'variant': RFC_4122,
41
- 'version': 4,
42
- '__str__': as_str,
43
- 'toString': as_str,
44
- }
45
-
46
-
47
- def num_to_string(numbers, alphabet, pad_to_length):
48
- ans = v'[]'
49
- alphabet_len = alphabet.length
50
- numbers = Array.prototype.slice.call(numbers)
51
- for v'var i = 0; i < numbers.length - 1; i++':
52
- x = divmod(numbers[i], alphabet_len)
53
- numbers[i] = x[0]
54
- numbers[i+1] += x[1]
55
- for v'var i = 0; i < numbers.length; i++':
56
- number = numbers[i]
57
- while number:
58
- x = divmod(number, alphabet_len)
59
- number = x[0]
60
- ans.push(alphabet[x[1]])
61
- if pad_to_length and pad_to_length > ans.length:
62
- ans.push(alphabet[0].repeat(pad_to_length - ans.length))
63
- return ans.join('')
64
-
65
-
66
- def short_uuid():
67
- # A totally random uuid encoded using only URL and filename safe characters
68
- return urlsafe_b64encode(random_bytes(), '')
69
-
70
-
71
- def short_uuid4():
72
- # A uuid4 encoded using only URL and filename safe characters
73
- return urlsafe_b64encode(uuid4_bytes(), '')
74
-
75
-
76
- def decode_short_uuid(val):
77
- return urlsafe_b64decode(val + '==')
1
+ # vim:fileencoding=utf-8
2
+ # License: BSD Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
3
+ # globals: crypto
4
+ from __python__ import hash_literals
5
+
6
+ from encodings import hexlify, urlsafe_b64decode, urlsafe_b64encode
7
+
8
+ RFC_4122 = 1
9
+
10
+ if jstype(crypto) is 'object' and crypto.getRandomValues:
11
+ random_bytes = def (num):
12
+ ans = Uint8Array(num or 16)
13
+ crypto.getRandomValues(ans)
14
+ return ans
15
+ else:
16
+ random_bytes = def (num):
17
+ ans = Uint8Array(num or 16)
18
+ for i in range(ans.length):
19
+ ans[i] = Math.floor(Math.random() * 256)
20
+ return ans
21
+
22
+
23
+ def uuid4_bytes():
24
+ data = random_bytes()
25
+ data[6] = 0b01000000 | (data[6] & 0b1111)
26
+ data[8] = (((data[8] >> 4) & 0b11 | 0b1000) << 4) | (data[8] & 0b1111)
27
+ return data
28
+
29
+
30
+ def as_str():
31
+ h = this.hex
32
+ return h[:8] + '-' + h[8:12] + '-' + h[12:16] + '-' + h[16:20] + '-' + h[20:]
33
+
34
+
35
+ def uuid4():
36
+ b = uuid4_bytes()
37
+ return {
38
+ 'hex': hexlify(b),
39
+ 'bytes': b,
40
+ 'variant': RFC_4122,
41
+ 'version': 4,
42
+ '__str__': as_str,
43
+ 'toString': as_str,
44
+ }
45
+
46
+
47
+ def num_to_string(numbers, alphabet, pad_to_length):
48
+ ans = v'[]'
49
+ alphabet_len = alphabet.length
50
+ numbers = Array.prototype.slice.call(numbers)
51
+ for v'var i = 0; i < numbers.length - 1; i++':
52
+ x = divmod(numbers[i], alphabet_len)
53
+ numbers[i] = x[0]
54
+ numbers[i+1] += x[1]
55
+ for v'var i = 0; i < numbers.length; i++':
56
+ number = numbers[i]
57
+ while number:
58
+ x = divmod(number, alphabet_len)
59
+ number = x[0]
60
+ ans.push(alphabet[x[1]])
61
+ if pad_to_length and pad_to_length > ans.length:
62
+ ans.push(alphabet[0].repeat(pad_to_length - ans.length))
63
+ return ans.join('')
64
+
65
+
66
+ def short_uuid():
67
+ # A totally random uuid encoded using only URL and filename safe characters
68
+ return urlsafe_b64encode(random_bytes(), '')
69
+
70
+
71
+ def short_uuid4():
72
+ # A uuid4 encoded using only URL and filename safe characters
73
+ return urlsafe_b64encode(uuid4_bytes(), '')
74
+
75
+
76
+ def decode_short_uuid(val):
77
+ return urlsafe_b64decode(val + '==')
@@ -236,6 +236,7 @@ class ScopeBuilder {
236
236
  return_type: opts.return_type || null,
237
237
  source_module: opts.source_module || null,
238
238
  original_name: opts.original_name || null,
239
+ is_bare_import: opts.is_bare_import || false,
239
240
  });
240
241
  scope.addSymbol(sym);
241
242
  return sym;
@@ -362,8 +363,10 @@ class ScopeBuilder {
362
363
  if (name) {
363
364
  this._add_symbol({
364
365
  name,
365
- kind: 'import',
366
- defined_at: pos_from_token(node.start),
366
+ kind: 'import',
367
+ defined_at: pos_from_token(node.start),
368
+ source_module: node.key || null,
369
+ is_bare_import: true,
367
370
  });
368
371
  }
369
372
 
@@ -523,6 +523,32 @@ export class CompletionEngine {
523
523
  }
524
524
  }
525
525
 
526
+ // 1.1. Module member completions — `import X; X.attr`
527
+ // When the symbol is a bare import, parse the module source and
528
+ // expose its top-level symbols as dot-completions.
529
+ if (!scope_matched && obj_sym && obj_sym.is_bare_import && obj_sym.source_module) {
530
+ const mod_src = this._virtualFiles[obj_sym.source_module]
531
+ || this._stdlibFiles[obj_sym.source_module];
532
+ if (mod_src) {
533
+ let mod_map;
534
+ try { mod_map = this._analyzer.analyze(mod_src, {}); } catch (_e) { /* ignore */ }
535
+ if (mod_map) {
536
+ const mod_frame = mod_map.frames.find(f => f.kind === 'module');
537
+ if (mod_frame) {
538
+ scope_matched = true;
539
+ for (const [name, sym] of mod_frame.symbols) {
540
+ if (!ctx.prefix || name.startsWith(ctx.prefix)) {
541
+ if (!seen.has(name)) {
542
+ seen.add(name);
543
+ items.push(symbol_to_item(sym, range, monacoKind, '0'));
544
+ }
545
+ }
546
+ }
547
+ }
548
+ }
549
+ }
550
+ }
551
+
526
552
  // 1.5. Built-in type members — list, str, dict, number.
527
553
  // Used when inferred_class names a built-in type, not a user class.
528
554
  if (!scope_matched && this._builtins && obj_sym && obj_sym.inferred_class) {
@@ -20,15 +20,20 @@ const MESSAGES = {
20
20
  'def-after-use': 'The symbol "{name}" is defined (at line {line}) after it is used',
21
21
  'dup-key': 'Duplicate key "{name}" in object literal',
22
22
  'dup-method': 'The method "{name}" was defined previously at line: {line}',
23
+ 'infinite-loop': 'This while loop may never exit (condition is always true and there is no await in the body)',
24
+ // type_enforcement diagnostics
25
+ 'type-posonly-kwarg': '"{name}" is positional-only and cannot be passed as a keyword argument to {func}()',
26
+ 'type-too-many-args': '{func}() takes at most {max} positional argument{plural} but {got} were given',
27
+ 'type-missing-kwonly': '"{name}" is a required keyword-only argument of {func}() and must be passed by name',
23
28
  };
24
29
 
25
30
  // Built-in stdlib modules that are always available in RapydScript (bundled
26
31
  // with the compiler from src/lib/). These should never produce 'Unknown module'
27
32
  // errors regardless of what virtualFiles or stdlibFiles are configured.
28
33
  export const STDLIB_MODULES = [
29
- 'abc', 'aes', 'collections', 'contextlib', 'copy', 'dataclasses', 'datetime', 'elementmaker', 'encodings', 'enum',
30
- 'functools', 'gettext', 'io', 'itertools', 'json', 'math', 'numpy', 'operator',
31
- 'pythonize', 'random', 're', 'react', 'traceback', 'typing', 'uuid',
34
+ 'abc', 'aes', 'asyncio', 'base64', 'bisect', 'collections', 'contextlib', 'copy', 'csv', 'dataclasses', 'datetime', 'elementmaker', 'encodings', 'enum',
35
+ 'functools', 'gettext', 'heapq', 'html', 'http', 'io', 'itertools', 'json', 'logging', 'math', 'numpy', 'operator',
36
+ 'pythonize', 'random', 're', 'react', 'string', 'textwrap', 'traceback', 'typing', 'urllib', 'uuid',
32
37
  // Pseudo-modules for language feature flags (from __python__ import ...)
33
38
  '__python__', '__builtins__',
34
39
  ];
@@ -497,6 +502,42 @@ function Linter(RS, toplevel, code, builtins, knownModules) {
497
502
  if (node.alias) this.add_binding(node.alias.name);
498
503
  };
499
504
 
505
+ this.handle_while = function() {
506
+ const node = this.current_node;
507
+ const cond = node.condition;
508
+ const RS = this.RS;
509
+
510
+ const is_trivially_true = (
511
+ (cond instanceof RS.AST_True) ||
512
+ (cond instanceof RS.AST_Number && cond.value !== 0)
513
+ );
514
+ if (!is_trivially_true) return;
515
+
516
+ let has_await = false;
517
+ const await_finder = {
518
+ _visit: function(n, cont) {
519
+ if (n instanceof RS.AST_Await) { has_await = true; return; }
520
+ if (cont) cont();
521
+ }
522
+ };
523
+ if (node.body) node.body._walk(await_finder);
524
+
525
+ if (!has_await) {
526
+ const sline = node.start ? node.start.line : 1;
527
+ const scol = node.start ? node.start.col : 0;
528
+ const eline = (node.condition && node.condition.end) ? node.condition.end.line : sline;
529
+ const ecol = (node.condition && node.condition.end) ? node.condition.end.col : scol;
530
+ this.messages.push({
531
+ start_line: sline, start_col: scol,
532
+ end_line: eline, end_col: ecol,
533
+ ident: 'infinite-loop',
534
+ message: MESSAGES['infinite-loop'],
535
+ level: WARN,
536
+ name: '',
537
+ });
538
+ }
539
+ };
540
+
500
541
  // The visitor function called by toplevel.walk()
501
542
  this._visit = function(node, cont) {
502
543
  if (node.lint_visited) return;
@@ -530,6 +571,7 @@ function Linter(RS, toplevel, code, builtins, knownModules) {
530
571
  else if (node instanceof RS.AST_EmptyStatement) this.handle_empty_statement();
531
572
  else if (node instanceof RS.AST_WithClause) this.handle_with_clause();
532
573
  else if (node instanceof RS.AST_Object) this.handle_object_literal();
574
+ else if (node instanceof RS.AST_While) this.handle_while();
533
575
 
534
576
  if (node instanceof RS.AST_Scope) this.handle_scope();
535
577
 
@@ -580,6 +622,143 @@ function Linter(RS, toplevel, code, builtins, knownModules) {
580
622
  };
581
623
  }
582
624
 
625
+ // ---------------------------------------------------------------------------
626
+ // Type-enforcement static checker
627
+ // Runs a two-pass walk when any function in the file has type_enforce=true:
628
+ // Pass 1 — collect signatures of enforced functions.
629
+ // Pass 2 — inspect call sites and report violations.
630
+ // ---------------------------------------------------------------------------
631
+
632
+ function check_type_enforcement(RS, toplevel, messages) {
633
+ // Pass 1: collect signatures of functions compiled with type_enforce=true
634
+ const sigs = Object.create(null);
635
+
636
+ const col1 = {
637
+ _visit: function(node, cont) {
638
+ if (node instanceof RS.AST_Lambda && node.type_enforce && node.name) {
639
+ const a = node.argnames;
640
+ const is_m = node instanceof RS.AST_Method;
641
+ const off = (is_m && !node.static) ? 1 : 0;
642
+ const posonly_names = [];
643
+ const kwonly_req = [];
644
+ let max_pos = 0;
645
+ const defs = (a.defaults) || {};
646
+ for (let i = off; i < a.length; i++) {
647
+ const arg = a[i];
648
+ if (arg.posonly) posonly_names.push(arg.name);
649
+ if (arg.kwonly) {
650
+ if (!has_prop(defs, arg.name)) kwonly_req.push(arg.name);
651
+ continue;
652
+ }
653
+ max_pos++;
654
+ }
655
+ sigs[node.name.name] = {
656
+ node,
657
+ posonly_names,
658
+ kwonly_req,
659
+ max_pos,
660
+ has_starargs: !!a.starargs,
661
+ has_kwargs: !!a.kwargs,
662
+ };
663
+ }
664
+ if (cont) cont();
665
+ }
666
+ };
667
+ toplevel.walk(col1);
668
+
669
+ if (Object.keys(sigs).length === 0) return;
670
+
671
+ // Pass 2: check call sites
672
+ const col2 = {
673
+ _visit: function(node, cont) {
674
+ if (node instanceof RS.AST_BaseCall) {
675
+ let fname = null;
676
+ const expr = node.expression;
677
+ if (expr instanceof RS.AST_SymbolRef) fname = expr.name;
678
+ else if (expr instanceof RS.AST_Dot) fname = expr.property;
679
+
680
+ if (fname && has_prop(sigs, fname)) {
681
+ const sig = sigs[fname];
682
+ const args = node.args;
683
+ const pos_count = args ? args.length : 0;
684
+ const named_kw = (args && args.kwargs) ? args.kwargs : [];
685
+ const kw_expand = args && args.kwarg_items && args.kwarg_items.length > 0;
686
+
687
+ // Positional-only args passed as named kwargs
688
+ sig.posonly_names.forEach(argname => {
689
+ for (let ki = 0; ki < named_kw.length; ki++) {
690
+ const knode = named_kw[ki][0];
691
+ const kname = knode.name || (knode.value !== undefined ? String(knode.value) : null);
692
+ if (kname === argname) {
693
+ const msg = MESSAGES['type-posonly-kwarg']
694
+ .replace('{name}', argname)
695
+ .replace('{func}', fname);
696
+ messages.push({
697
+ ident: 'type-posonly-kwarg',
698
+ message: msg,
699
+ level: ERROR,
700
+ name: argname,
701
+ start_line: knode.start ? knode.start.line : 1,
702
+ start_col: knode.start ? knode.start.col : 0,
703
+ end_line: knode.end ? knode.end.line : undefined,
704
+ end_col: knode.end ? knode.end.col : undefined,
705
+ });
706
+ }
707
+ }
708
+ });
709
+
710
+ // Too many positional args (skip when *args or **expansion present)
711
+ if (!sig.has_starargs && !kw_expand && pos_count > sig.max_pos) {
712
+ const plural = sig.max_pos === 1 ? '' : 's';
713
+ const msg = MESSAGES['type-too-many-args']
714
+ .replace('{func}', fname)
715
+ .replace('{max}', String(sig.max_pos))
716
+ .replace('{plural}', plural)
717
+ .replace('{got}', String(pos_count));
718
+ messages.push({
719
+ ident: 'type-too-many-args',
720
+ message: msg,
721
+ level: ERROR,
722
+ name: fname,
723
+ start_line: node.start ? node.start.line : 1,
724
+ start_col: node.start ? node.start.col : 0,
725
+ end_line: node.end ? node.end.line : undefined,
726
+ end_col: node.end ? node.end.col : undefined,
727
+ });
728
+ }
729
+
730
+ // Required keyword-only args not provided (skip when **expansion present)
731
+ if (!kw_expand) {
732
+ sig.kwonly_req.forEach(argname => {
733
+ const provided = named_kw.some(pair => {
734
+ const kn = pair[0].name || (pair[0].value !== undefined ? String(pair[0].value) : null);
735
+ return kn === argname;
736
+ });
737
+ if (!provided) {
738
+ const msg = MESSAGES['type-missing-kwonly']
739
+ .replace('{name}', argname)
740
+ .replace('{func}', fname);
741
+ messages.push({
742
+ ident: 'type-missing-kwonly',
743
+ message: msg,
744
+ level: ERROR,
745
+ name: argname,
746
+ start_line: node.start ? node.start.line : 1,
747
+ start_col: node.start ? node.start.col : 0,
748
+ end_line: node.end ? node.end.line : undefined,
749
+ end_col: node.end ? node.end.col : undefined,
750
+ });
751
+ }
752
+ });
753
+ }
754
+ }
755
+ }
756
+ if (cont) cont();
757
+ }
758
+ };
759
+ toplevel.walk(col2);
760
+ }
761
+
583
762
  // ---------------------------------------------------------------------------
584
763
  // Convert lint message → Monaco IMarkerData
585
764
  // markerSeverity should be { Error, Warning, Info, Hint } numeric values
@@ -709,6 +888,26 @@ export class Diagnostics {
709
888
  toplevel.walk(linter);
710
889
  messages = linter.resolve(noqa);
711
890
 
891
+ // --- Type enforcement static checks ---
892
+ // Run when type_enforcement is active at the file level (either via
893
+ // pythonFlags constructor arg or from __python__ import type_enforcement).
894
+ const te_active =
895
+ (this._scoped_flags && this._scoped_flags.type_enforcement) ||
896
+ (toplevel.scoped_flags && toplevel.scoped_flags.type_enforcement);
897
+ if (te_active) {
898
+ const te_messages = [];
899
+ check_type_enforcement(RS, toplevel, te_messages);
900
+ // Filter noqa, then convert to markers inline
901
+ te_messages.forEach(m => {
902
+ if (noqa && has_prop(noqa, m.ident)) return;
903
+ messages.push(m);
904
+ });
905
+ messages.sort((a, b) => {
906
+ const dl = (a.start_line || 0) - (b.start_line || 0);
907
+ return dl !== 0 ? dl : (a.start_col || 0) - (b.start_col || 0);
908
+ });
909
+ }
910
+
712
911
  return messages.map(m => to_marker(m, markerSeverity));
713
912
  }
714
913
  }