rapydscript-ns 0.9.2 → 0.9.4

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 (88) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/PYTHON_GAPS.md +352 -0
  3. package/README.md +176 -32
  4. package/TODO.md +1 -128
  5. package/bin/rapydscript +70 -70
  6. package/language-service/index.js +242 -11
  7. package/memory/project_string_impl.md +43 -0
  8. package/package.json +1 -1
  9. package/release/baselib-plain-pretty.js +248 -38
  10. package/release/baselib-plain-ugly.js +8 -8
  11. package/release/compiler.js +778 -277
  12. package/release/signatures.json +30 -30
  13. package/src/ast.pyj +10 -1
  14. package/src/baselib-builtins.pyj +56 -2
  15. package/src/baselib-containers.pyj +25 -1
  16. package/src/baselib-errors.pyj +7 -3
  17. package/src/baselib-internal.pyj +51 -6
  18. package/src/baselib-str.pyj +18 -5
  19. package/src/lib/asyncio.pyj +534 -0
  20. package/src/lib/base64.pyj +399 -0
  21. package/src/lib/bisect.pyj +73 -0
  22. package/src/lib/collections.pyj +228 -4
  23. package/src/lib/csv.pyj +494 -0
  24. package/src/lib/heapq.pyj +98 -0
  25. package/src/lib/html.pyj +382 -0
  26. package/src/lib/http/__init__.pyj +98 -0
  27. package/src/lib/http/client.pyj +304 -0
  28. package/src/lib/http/cookies.pyj +236 -0
  29. package/src/lib/logging.pyj +672 -0
  30. package/src/lib/pprint.pyj +455 -0
  31. package/src/lib/pythonize.pyj +20 -20
  32. package/src/lib/statistics.pyj +0 -0
  33. package/src/lib/string.pyj +357 -0
  34. package/src/lib/textwrap.pyj +329 -0
  35. package/src/lib/urllib/__init__.pyj +14 -0
  36. package/src/lib/urllib/error.pyj +66 -0
  37. package/src/lib/urllib/parse.pyj +475 -0
  38. package/src/lib/urllib/request.pyj +86 -0
  39. package/src/monaco-language-service/analyzer.js +5 -2
  40. package/src/monaco-language-service/completions.js +26 -0
  41. package/src/monaco-language-service/diagnostics.js +203 -4
  42. package/src/monaco-language-service/scope.js +1 -0
  43. package/src/output/codegen.pyj +4 -1
  44. package/src/output/functions.pyj +152 -6
  45. package/src/output/loops.pyj +17 -2
  46. package/src/output/modules.pyj +1 -1
  47. package/src/output/operators.pyj +15 -0
  48. package/src/output/stream.pyj +0 -1
  49. package/src/parse.pyj +108 -24
  50. package/src/tokenizer.pyj +19 -3
  51. package/test/async_generators.pyj +144 -0
  52. package/test/asyncio.pyj +307 -0
  53. package/test/base64.pyj +202 -0
  54. package/test/baselib.pyj +23 -0
  55. package/test/bisect.pyj +178 -0
  56. package/test/chainmap.pyj +185 -0
  57. package/test/csv.pyj +405 -0
  58. package/test/float_special.pyj +64 -0
  59. package/test/heapq.pyj +174 -0
  60. package/test/html.pyj +212 -0
  61. package/test/http.pyj +259 -0
  62. package/test/imports.pyj +79 -72
  63. package/test/logging.pyj +356 -0
  64. package/test/long.pyj +130 -0
  65. package/test/parenthesized_with.pyj +141 -0
  66. package/test/pprint.pyj +232 -0
  67. package/test/python_compat.pyj +3 -5
  68. package/test/python_modulo.pyj +76 -0
  69. package/test/python_modulo_off.pyj +21 -0
  70. package/test/statistics.pyj +224 -0
  71. package/test/str.pyj +14 -0
  72. package/test/string.pyj +245 -0
  73. package/test/textwrap.pyj +172 -0
  74. package/test/type_display.pyj +48 -0
  75. package/test/type_enforcement.pyj +164 -0
  76. package/test/unit/index.js +94 -6
  77. package/test/unit/language-service-completions.js +121 -0
  78. package/test/unit/language-service-scope.js +32 -0
  79. package/test/unit/language-service.js +190 -5
  80. package/test/unit/run-language-service.js +17 -3
  81. package/test/unit/web-repl.js +2401 -13
  82. package/test/urllib.pyj +193 -0
  83. package/tools/compile.js +1 -1
  84. package/tools/embedded_compiler.js +7 -7
  85. package/tools/export.js +4 -2
  86. package/web-repl/main.js +1 -1
  87. package/web-repl/rapydscript.js +7 -5
  88. package/test/omit_function_metadata.pyj +0 -20
@@ -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
+ 'pprint', 'pythonize', 'random', 're', 'react', 'statistics', 'string', 'textwrap', 'traceback', 'typing', 'urllib', 'uuid',
32
37
  // Pseudo-modules for language feature flags (from __python__ import ...)
33
38
  '__python__', '__builtins__',
34
39
  ];
@@ -48,7 +53,7 @@ export const BASE_BUILTINS = (
48
53
  ' NodeList alert console Node Symbol NamedNodeMap ρσ_eslice ρσ_delslice Number' +
49
54
  ' Boolean encodeURIComponent decodeURIComponent setTimeout setInterval' +
50
55
  ' setImmediate clearTimeout clearInterval clearImmediate requestAnimationFrame' +
51
- ' id repr sorted __name__ equals get_module ρσ_str jstype divmod NaN super Ellipsis slice all any next __import__ ρσ_new ρσ_object_new hash ρσ_object_setattr ρσ_object_getattr ρσ_object_delattr ExceptionGroup BaseExceptionGroup tuple bytes bytearray format object vars locals globals'
56
+ ' id repr sorted __name__ equals get_module ρσ_str jstype divmod NaN super Ellipsis slice all any next __import__ ρσ_new ρσ_object_new hash ρσ_object_setattr ρσ_object_getattr ρσ_object_delattr ExceptionGroup BaseExceptionGroup tuple bytes bytearray format object vars locals globals type'
52
57
  ).split(' ');
53
58
 
54
59
  // ---------------------------------------------------------------------------
@@ -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
  }
@@ -33,6 +33,7 @@ export class SymbolInfo {
33
33
  this.return_type = opts.return_type || null; // annotated return type, e.g. 'list', 'str', 'MyClass'
34
34
  this.source_module = opts.source_module || null; // module name when kind='import' (from X import Y)
35
35
  this.original_name = opts.original_name || null; // pre-alias name for imports (from X import Y as Z → 'Y')
36
+ this.is_bare_import = opts.is_bare_import || false; // true for `import X` (vs `from X import Y`)
36
37
  }
37
38
  }
38
39
 
@@ -7,7 +7,7 @@ from __python__ import hash_literals
7
7
  from utils import noop
8
8
  from parse import PRECEDENCE
9
9
  from ast import (
10
- AST_Array, AST_Assign, AST_BaseCall, AST_Binary, AST_BlockStatement, AST_Break,
10
+ AST_Array, AST_Assign, AST_BaseCall, AST_BigInt, AST_Binary, AST_BlockStatement, AST_Break,
11
11
  AST_Class, AST_Conditional, AST_Constant, AST_Continue,
12
12
  AST_Debugger, AST_Definitions, AST_Directive, AST_Do, AST_Dot, is_node_type,
13
13
  AST_EmptyStatement, AST_Exit, AST_ExpressiveObject, AST_ForIn,
@@ -530,6 +530,9 @@ def generate_code():
530
530
  DEFPRINT(AST_Number, def(self, output):
531
531
  output.print(make_num(self.value))
532
532
  )
533
+ DEFPRINT(AST_BigInt, def(self, output):
534
+ output.print(self.value + "n")
535
+ )
533
536
  DEFPRINT(AST_RegExp, print_regexp)
534
537
 
535
538
  # -----[ JSX ]-----
@@ -45,11 +45,69 @@ def function_args(argnames, output, strip_first):
45
45
 
46
46
  def function_preamble(node, output, offset):
47
47
  a = node.argnames
48
- if a is None or a is undefined or a.is_simple_func:
48
+ if a is None or a is undefined:
49
+ return
50
+ type_enforce = node.type_enforce
51
+ if a.is_simple_func and not type_enforce:
49
52
  return
50
53
  # If this function has optional parameters/*args/**kwargs declare it differently
51
54
  fname = node.name.name if node.name else anonfunc
52
55
  kw = 'arguments[arguments.length-1]'
56
+
57
+ # === Type enforcement: max positional-arg count check ===
58
+ if type_enforce:
59
+ max_pos = 0
60
+ for c, arg in enumerate(a):
61
+ if c >= offset and not arg.kwonly:
62
+ max_pos += 1
63
+ if a.is_simple_func:
64
+ # Simple function: named JS params; arguments.length gives true count
65
+ if not a.starargs:
66
+ # Strip a trailing kwargs-marker object so a `*args, **kwargs`
67
+ # caller does not get blamed for an extra positional argument
68
+ # (matches the complex-func variant below).
69
+ output.indent()
70
+ output.spaced('var', 'ρσ_nargs', '=', 'arguments.length')
71
+ output.end_statement()
72
+ output.indent()
73
+ output.print('if (ρσ_nargs > 0 && arguments[ρσ_nargs-1] !== null && typeof arguments[ρσ_nargs-1] === "object" && arguments[ρσ_nargs-1][ρσ_kwargs_symbol] === true) ρσ_nargs--')
74
+ output.end_statement()
75
+ max_msg = fname + '() takes ' + max_pos + ' positional argument' + ('' if max_pos == 1 else 's') + ' but '
76
+ output.indent()
77
+ output.print('if (ρσ_nargs > ' + max_pos + ') throw new TypeError(' + JSON.stringify(max_msg) + ' + ρσ_nargs + " were given")')
78
+ output.end_statement()
79
+ # Required-arg and type-annotation checks (variables are direct JS params)
80
+ for c, arg in enumerate(a):
81
+ if c < offset:
82
+ continue
83
+ if not arg.kwonly:
84
+ miss_msg = fname + '() missing required positional argument: \'' + arg.name + '\''
85
+ output.indent()
86
+ output.print('if (' + arg.name + ' === undefined) throw new TypeError(' + JSON.stringify(miss_msg) + ')')
87
+ output.end_statement()
88
+ if arg.annotation:
89
+ annot_name = arg.annotation.name or ''
90
+ type_msg = fname + '() argument \'' + arg.name + '\' must be ' + annot_name
91
+ output.indent()
92
+ output.print('if (' + arg.name + ' !== undefined && !ρσ_instanceof(' + arg.name + ', ')
93
+ arg.annotation.print(output)
94
+ output.print(')) throw new TypeError(' + JSON.stringify(type_msg) + ')')
95
+ output.end_statement()
96
+ return # simple func: only enforcement, no complex preamble
97
+
98
+ # Complex function: must handle arguments object; strip trailing kwargs marker
99
+ if not a.starargs:
100
+ output.indent()
101
+ output.spaced('var', 'ρσ_nargs', '=', 'arguments.length')
102
+ output.end_statement()
103
+ output.indent()
104
+ output.print('if (ρσ_nargs > 0 && arguments[ρσ_nargs-1] !== null && typeof arguments[ρσ_nargs-1] === "object" && arguments[ρσ_nargs-1][ρσ_kwargs_symbol] === true) ρσ_nargs--')
105
+ output.end_statement()
106
+ max_msg = fname + '() takes ' + max_pos + ' positional argument' + ('' if max_pos == 1 else 's') + ' but '
107
+ output.indent()
108
+ output.print('if (ρσ_nargs > ' + max_pos + ') throw new TypeError(' + JSON.stringify(max_msg) + ' + ρσ_nargs + " were given")')
109
+ output.end_statement()
110
+
53
111
  # Define all formal parameters
54
112
  positional_count = 0
55
113
  for c, arg in enumerate(a):
@@ -89,6 +147,22 @@ def function_preamble(node, output, offset):
89
147
  output.indent()
90
148
  output.spaced('if', '(' + kw, '===', 'null', '||', 'typeof', kw, '!==', '"object"', '||', kw, '[ρσ_kwargs_symbol]', '!==', 'true)', kw, '=', '{}')
91
149
  output.end_statement()
150
+ # === Type enforcement: positional-only args must not appear in kwargs ===
151
+ if type_enforce:
152
+ posonly_count = a.posonly_count or 0
153
+ for pi in range(posonly_count):
154
+ if pi < offset:
155
+ continue
156
+ parg = a[pi]
157
+ posonly_msg = fname + '() got some positional-only arguments passed as keyword arguments: \'' + parg.name + '\''
158
+ output.indent()
159
+ output.spaced('if', '(Object.prototype.hasOwnProperty.call(' + kw + ',', '"' + parg.name + '"))')
160
+ output.with_block(def():
161
+ output.indent()
162
+ output.print('throw new TypeError(' + JSON.stringify(posonly_msg) + ')')
163
+ output.end_statement()
164
+ )
165
+ output.newline()
92
166
  # Read values from the kwargs object for non-positional-only formal parameters with defaults
93
167
  if a.has_defaults:
94
168
  for dname in Object.keys(a.defaults):
@@ -138,6 +212,29 @@ def function_preamble(node, output, offset):
138
212
  output.end_statement()
139
213
  )
140
214
  output.newline()
215
+ elif type_enforce and (a.posonly_count or 0) > 0:
216
+ # No kwargs block, but function has positional-only params.
217
+ # A caller using foo(posonly_name=val) still passes a kwargs object as the
218
+ # last argument; detect and reject it here.
219
+ posonly_count = a.posonly_count or 0
220
+ posonly_names = []
221
+ for pi in range(posonly_count):
222
+ if pi >= offset:
223
+ posonly_names.push(a[pi].name)
224
+ if posonly_names.length > 0:
225
+ output.indent()
226
+ output.spaced('var', 'ρσ_kw_chk', '=', 'arguments[arguments.length-1]')
227
+ output.end_statement()
228
+ output.indent()
229
+ output.spaced('if', '(ρσ_kw_chk !== null && typeof ρσ_kw_chk === "object" && ρσ_kw_chk[ρσ_kwargs_symbol] === true)')
230
+ output.with_block(def():
231
+ for pname in posonly_names:
232
+ posonly_msg = fname + '() got some positional-only arguments passed as keyword arguments: \'' + pname + '\''
233
+ output.indent()
234
+ output.print('if (Object.prototype.hasOwnProperty.call(ρσ_kw_chk, "' + pname + '")) throw new TypeError(' + JSON.stringify(posonly_msg) + ')')
235
+ output.end_statement()
236
+ )
237
+ output.newline()
141
238
 
142
239
  if a.starargs is not undefined:
143
240
  # Define the *args parameter, putting in whatever is left after assigning the formal parameters and the options object
@@ -160,6 +257,44 @@ def function_preamble(node, output, offset):
160
257
  output.spaced(kw, '=', 'ρσ_kwargs_to_dict(' + kw + ')')
161
258
  output.end_statement()
162
259
 
260
+ # === Type enforcement: required-arg, required-kwonly, and type-annotation checks ===
261
+ # These run after all variable resolution (kwargs reading, starargs setup, dict conversion)
262
+ # so each variable holds its final value when checked.
263
+ if type_enforce:
264
+ # Required positional args (no default, not kwonly)
265
+ for c, arg in enumerate(a):
266
+ if c < offset:
267
+ continue
268
+ if arg.kwonly:
269
+ continue
270
+ if not Object.prototype.hasOwnProperty.call(a.defaults, arg.name):
271
+ miss_msg = fname + '() missing required positional argument: \'' + arg.name + '\''
272
+ output.indent()
273
+ output.print('if (' + arg.name + ' === undefined) throw new TypeError(' + JSON.stringify(miss_msg) + ')')
274
+ output.end_statement()
275
+ # Required keyword-only args (no default)
276
+ for c, arg in enumerate(a):
277
+ if not arg.kwonly:
278
+ continue
279
+ if not Object.prototype.hasOwnProperty.call(a.defaults, arg.name):
280
+ miss_kw = fname + '() missing required keyword-only argument: \'' + arg.name + '\''
281
+ output.indent()
282
+ output.print('if (' + arg.name + ' === undefined) throw new TypeError(' + JSON.stringify(miss_kw) + ')')
283
+ output.end_statement()
284
+ # Type annotation checks (skip *args and **kwargs)
285
+ for c, arg in enumerate(a):
286
+ if c < offset:
287
+ continue
288
+ if not arg.annotation:
289
+ continue
290
+ annot_name = arg.annotation.name or ''
291
+ type_msg = fname + '() argument \'' + arg.name + '\' must be ' + annot_name
292
+ output.indent()
293
+ output.print('if (' + arg.name + ' !== undefined && !ρσ_instanceof(' + arg.name + ', ')
294
+ arg.annotation.print(output)
295
+ output.print(')) throw new TypeError(' + JSON.stringify(type_msg) + ')')
296
+ output.end_statement()
297
+
163
298
  def has_annotations(self):
164
299
  if self.return_annotation:
165
300
  return True
@@ -169,8 +304,6 @@ def has_annotations(self):
169
304
  return False
170
305
 
171
306
  def function_annotation(self, output, strip_first, name):
172
- if output.options.omit_function_metadata:
173
- return
174
307
  fname = name or (self.name.name if self.name else anonfunc)
175
308
  props = Object.create(None)
176
309
 
@@ -263,27 +396,37 @@ def function_definition(self, output, strip_first, as_expression):
263
396
  output.set_indentation(output.next_indent())
264
397
  output.spaced('(function()', '{'), output.newline()
265
398
  output.indent(), output.spaced('var', anonfunc, '='), output.space()
266
- if self.is_async:
399
+ # Async generators (async def with yield) are emitted as a sync wrapper that
400
+ # returns the async iterator directly (matching Python semantics, where calling
401
+ # an async generator function returns the iterator immediately without await).
402
+ # The inner js_generator is `async function*`. The outer wrapper is sync.
403
+ if self.is_async and not self.is_generator:
267
404
  output.print("async"), output.space()
268
405
  output.print("function"), output.space()
269
406
  if self.name:
270
407
  self.name.print(output)
271
408
 
272
409
  if self.is_generator:
410
+ inner_decl = 'async function* js_generator' if self.is_async else 'function* js_generator'
273
411
  output.print('()'), output.space()
274
412
  output.with_block(def():
275
413
  output.indent()
276
- output.print('function* js_generator')
414
+ output.print(inner_decl)
277
415
  function_args(self.argnames, output, strip_first)
278
416
  print_bracketed(self, output, True, function_preamble)
279
417
  output.newline()
280
418
  output.indent()
281
419
  output.spaced('var', 'result', '=', 'js_generator.apply(this,', 'arguments)')
282
420
  output.end_statement()
283
- # Python's generator objects use a separate method to send data to the generator
421
+ # Python's generator objects use a separate method to send data to the generator.
422
+ # For async generators, asend() is the async equivalent — alias it to next as well.
284
423
  output.indent()
285
424
  output.spaced('result.send', '=', 'result.next')
286
425
  output.end_statement()
426
+ if self.is_async:
427
+ output.indent()
428
+ output.spaced('result.asend', '=', 'result.next')
429
+ output.end_statement()
287
430
  output.indent()
288
431
  output.spaced('return', 'result')
289
432
  output.end_statement()
@@ -443,12 +586,15 @@ def print_function_call(self, output):
443
586
  )
444
587
  elif is_node_type(self.expression, AST_SymbolRef) and self.expression.name is 'print':
445
588
  # Bare print(...) → console.log(...) to avoid clobbering window.print
589
+ # Wrap each arg in ρσ_str() so type() and other objects display like Python
446
590
  output.print('console.log')
447
591
  output.with_parens(def():
448
592
  for i, a in enumerate(self.args):
449
593
  if i:
450
594
  output.comma()
595
+ output.print('ρσ_str(')
451
596
  a.print(output)
597
+ output.print(')')
452
598
  )
453
599
  elif (is_node_type(self.expression, AST_SymbolRef) and
454
600
  self.expression.name is 'vars' and self.args.length is 0):
@@ -119,7 +119,10 @@ def print_for_loop_body(output):
119
119
  if not (self.simple_for_index or is_simple_for_in(self)):
120
120
  # if we're using multiple iterators, unpack them
121
121
  output.indent()
122
- if output.options.js_version is 5:
122
+ # `async for` always emits ES6+ `for await ... of` syntax (regardless
123
+ # of js_version), so the loop variable holds the value directly —
124
+ # not an integer index into an array.
125
+ if output.options.js_version is 5 and not self.is_async:
123
126
  itervar = "ρσ_Iter" + output.index_counter + "[ρσ_Index" + output.index_counter + "]"
124
127
  else:
125
128
  itervar = "ρσ_Index" + output.index_counter
@@ -237,7 +240,19 @@ def print_for_in(self, output):
237
240
  )
238
241
  else:
239
242
  # regular loop
240
- if output.options.js_version is 5:
243
+ if self.is_async:
244
+ # async for: emit native `for await ... of` regardless of js_version.
245
+ # Async iteration is an ES2018 feature; if the user wrote `async for`
246
+ # they have already opted into a modern runtime. The ρσ_Iterable
247
+ # bridge is skipped because async iterators don't expose .length
248
+ # or Map keys — the JS engine handles Symbol.asyncIterator natively.
249
+ itervar = "ρσ_Iter" + output.index_counter
250
+ output.assign("var " + itervar)
251
+ write_object()
252
+ output.end_statement()
253
+ output.indent()
254
+ output.spaced('for', 'await', '(var', 'ρσ_Index' + output.index_counter, 'of', itervar + ')')
255
+ elif output.options.js_version is 5:
241
256
  output.assign("var ρσ_Iter" + output.index_counter)
242
257
  output.print("ρσ_Iterable")
243
258
  output.with_parens(write_object)
@@ -94,7 +94,7 @@ def declare_exports(module_id, exports, output, docstrings):
94
94
  def _inject_pythonize_strings(output):
95
95
  str_funcs = ('capitalize strip lstrip rstrip islower isupper isspace lower upper swapcase title'
96
96
  ' center count endswith startswith find rfind index rindex format join ljust rjust'
97
- ' partition rpartition splitlines zfill').split(' ')
97
+ ' partition rpartition replace splitlines zfill').split(' ')
98
98
  output.newline()
99
99
  output.indent()
100
100
  output.print('(function(){var _f=' + JSON.stringify(str_funcs) + ';for(var _i=0;_i<_f.length;_i++)String.prototype[_f[_i]]=\u03c1\u03c3_str.prototype[_f[_i]];})()')
@@ -321,6 +321,13 @@ def print_binary_op(self, output):
321
321
  self.right.print(output)
322
322
  output.print(')')
323
323
  return
324
+ if self.operator is '%' and self.python_mod:
325
+ output.print('ρσ_op_mod_ns(')
326
+ self.left.print(output)
327
+ output.comma()
328
+ self.right.print(output)
329
+ output.print(')')
330
+ return
324
331
  if function_ops[self.operator]:
325
332
  output.print(function_ops[self.operator])
326
333
  output.with_parens(def():
@@ -485,6 +492,14 @@ def print_assign(self, output):
485
492
  self.right.print(output)
486
493
  )
487
494
  return
495
+ if self.operator is '%=' and self.python_mod:
496
+ output.assign(self.left)
497
+ output.print('ρσ_op_mod_ns(')
498
+ self.left.print(output)
499
+ output.comma()
500
+ self.right.print(output)
501
+ output.print(')')
502
+ return
488
503
  if self.operator is '**=':
489
504
  output.assign(self.left)
490
505
  if output.options.js_version > 6:
@@ -54,7 +54,6 @@ output_stream_defaults = {
54
54
  'module_cache_dir': '',
55
55
  'js_version':5,
56
56
  'write_name': True,
57
- 'omit_function_metadata': False,
58
57
  'pythonize_strings': False,
59
58
  'repl_mode': False,
60
59
  }