rapydscript-ns 0.8.1 → 0.8.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 (58) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/CONTRIBUTORS +3 -2
  3. package/PYTHON_DIFFERENCES_REPORT.md +291 -0
  4. package/PYTHON_FEATURE_COVERAGE.md +200 -0
  5. package/README.md +480 -79
  6. package/TODO.md +6 -318
  7. package/hack_demo.pyj +112 -0
  8. package/language-service/index.js +4474 -0
  9. package/language-service/language-service.d.ts +40 -0
  10. package/package.json +9 -10
  11. package/src/ast.pyj +30 -6
  12. package/src/baselib-builtins.pyj +181 -11
  13. package/src/baselib-containers.pyj +154 -5
  14. package/src/baselib-errors.pyj +3 -0
  15. package/src/baselib-internal.pyj +40 -1
  16. package/src/baselib-str.pyj +42 -1
  17. package/src/lib/collections.pyj +1 -1
  18. package/src/lib/numpy.pyj +10 -10
  19. package/src/monaco-language-service/analyzer.js +132 -22
  20. package/src/monaco-language-service/builtins.js +22 -2
  21. package/src/monaco-language-service/completions.js +224 -3
  22. package/src/monaco-language-service/diagnostics.js +55 -5
  23. package/src/monaco-language-service/index.js +26 -5
  24. package/src/monaco-language-service/scope.js +3 -0
  25. package/src/output/classes.pyj +20 -3
  26. package/src/output/codegen.pyj +38 -3
  27. package/src/output/functions.pyj +35 -25
  28. package/src/output/loops.pyj +64 -11
  29. package/src/output/modules.pyj +1 -4
  30. package/src/output/operators.pyj +67 -1
  31. package/src/output/statements.pyj +7 -3
  32. package/src/output/stream.pyj +6 -13
  33. package/src/parse.pyj +94 -14
  34. package/src/tokenizer.pyj +1 -0
  35. package/test/baselib.pyj +4 -4
  36. package/test/classes.pyj +56 -17
  37. package/test/collections.pyj +5 -5
  38. package/test/python_compat.pyj +326 -0
  39. package/test/python_features.pyj +1271 -0
  40. package/test/slice.pyj +105 -0
  41. package/test/str.pyj +25 -0
  42. package/test/unit/fixtures/fibonacci_expected.js +1 -1
  43. package/test/unit/index.js +119 -7
  44. package/test/unit/language-service-builtins.js +70 -0
  45. package/test/unit/language-service-bundle.js +83 -0
  46. package/test/unit/language-service-completions.js +289 -0
  47. package/test/unit/language-service-index.js +350 -0
  48. package/test/unit/language-service-scope.js +255 -0
  49. package/test/unit/language-service.js +158 -1
  50. package/test/unit/run-language-service.js +2 -0
  51. package/test/unit/web-repl.js +134 -0
  52. package/tools/build-language-service.js +2 -2
  53. package/tools/compiler.js +0 -24
  54. package/tools/export.js +3 -37
  55. package/tools/lint.js +1 -1
  56. package/tools/self.js +1 -9
  57. package/web-repl/rapydscript.js +6 -40
  58. package/web-repl/language-service.js +0 -4084
@@ -1,7 +1,7 @@
1
1
  # vim:fileencoding=utf-8
2
2
  # License: BSD
3
3
 
4
- # globals: exports, console, ρσ_iterator_symbol, ρσ_kwargs_symbol, ρσ_arraylike, ρσ_list_contains, ρσ_list_constructor, ρσ_str, ρσ_int, ρσ_float
4
+ # globals: exports, console, ρσ_iterator_symbol, ρσ_kwargs_symbol, ρσ_arraylike, ρσ_list_contains, ρσ_list_constructor, ρσ_str, ρσ_int, ρσ_float, ρσ_slice
5
5
 
6
6
  def ρσ_eslice(arr, step, start, end):
7
7
  if jstype(arr) is 'string' or v'arr instanceof String':
@@ -179,6 +179,11 @@ def ρσ_interpolate_kwargs_constructor(apply, f, supplied_args):
179
179
  def ρσ_getitem(obj, key):
180
180
  if obj.__getitem__:
181
181
  return obj.__getitem__(key)
182
+ if v'typeof ρσ_slice !== "undefined" && key instanceof ρσ_slice':
183
+ return ρσ_eslice(obj,
184
+ v'(key.step !== null && key.step !== undefined) ? key.step : 1',
185
+ v'(key.start !== null && key.start !== undefined) ? key.start : undefined',
186
+ v'(key.stop !== null && key.stop !== undefined) ? key.stop : undefined')
182
187
  if jstype(key) is 'number' and key < 0:
183
188
  key += obj.length
184
189
  return obj[key]
@@ -186,6 +191,10 @@ def ρσ_getitem(obj, key):
186
191
  def ρσ_setitem(obj, key, val):
187
192
  if obj.__setitem__:
188
193
  obj.__setitem__(key, val)
194
+ elif v'typeof ρσ_slice !== "undefined" && key instanceof ρσ_slice':
195
+ ρσ_splice(obj, val,
196
+ v'(key.start !== null && key.start !== undefined) ? key.start : 0',
197
+ v'(key.stop !== null && key.stop !== undefined) ? key.stop : obj.length')
189
198
  else:
190
199
  if jstype(key) is 'number' and key < 0:
191
200
  key += obj.length
@@ -195,6 +204,11 @@ def ρσ_setitem(obj, key, val):
195
204
  def ρσ_delitem(obj, key):
196
205
  if obj.__delitem__:
197
206
  obj.__delitem__(key)
207
+ elif v'typeof ρσ_slice !== "undefined" && key instanceof ρσ_slice':
208
+ ρσ_delslice(obj,
209
+ v'(key.step !== null && key.step !== undefined) ? key.step : 1',
210
+ v'(key.start !== null && key.start !== undefined) ? key.start : undefined',
211
+ v'(key.stop !== null && key.stop !== undefined) ? key.stop : undefined')
198
212
  elif jstype(obj.splice) is 'function':
199
213
  obj.splice(key, 1)
200
214
  else:
@@ -282,6 +296,16 @@ def ρσ_op_mul(a, b):
282
296
  if b is not None and jstype(b.__rmul__) is 'function': return b.__rmul__(a)
283
297
  if (jstype(a) is 'string' or v'a instanceof String') and jstype(b) is 'number': return a.repeat(b)
284
298
  if (jstype(b) is 'string' or v'b instanceof String') and jstype(a) is 'number': return b.repeat(a)
299
+ if Array.isArray(a) and jstype(b) is 'number':
300
+ result = v'[]'
301
+ for v'var ρσ_mi = 0; ρσ_mi < b; ρσ_mi++':
302
+ result = result.concat(a) # noqa:undef
303
+ return ρσ_list_constructor(result)
304
+ if Array.isArray(b) and jstype(a) is 'number':
305
+ result = v'[]'
306
+ for v'var ρσ_mi = 0; ρσ_mi < a; ρσ_mi++':
307
+ result = result.concat(b) # noqa:undef
308
+ return ρσ_list_constructor(result)
285
309
  return a * b
286
310
 
287
311
  def ρσ_op_truediv(a, b):
@@ -329,6 +353,21 @@ def ρσ_op_rshift(a, b):
329
353
  if b is not None and jstype(b.__rrshift__) is 'function': return b.__rrshift__(a)
330
354
  return a >> b
331
355
 
356
+ # List-concatenation helpers (always available, no overload_operators required).
357
+ # ρσ_list_add: used for the + operator; creates a new list when both sides are arrays.
358
+ # NOTE: the fallback uses v'a + b' (verbatim JS) to avoid recursive self-compilation.
359
+ def ρσ_list_add(a, b):
360
+ if Array.isArray(a) and Array.isArray(b):
361
+ return ρσ_list_constructor(a.concat(b))
362
+ return v'a + b'
363
+
364
+ # ρσ_list_iadd: used for +=; extends a in-place when both sides are arrays (Python semantics).
365
+ def ρσ_list_iadd(a, b):
366
+ if Array.isArray(a) and Array.isArray(b):
367
+ v'Array.prototype.push.apply(a, b)'
368
+ return a
369
+ return v'a + b'
370
+
332
371
  # Unary operator overloading helpers
333
372
  def ρσ_op_neg(a):
334
373
  if a is not None and jstype(a.__neg__) is 'function': return a.__neg__()
@@ -525,6 +525,40 @@ define_str_func('isspace', def():
525
525
  return this.length > 0 and /^\s+$/.test(this)
526
526
  )
527
527
 
528
+ define_str_func('isalpha', def():
529
+ return this.length > 0 and /^[a-zA-Z]+$/.test(this)
530
+ )
531
+
532
+ define_str_func('isdigit', def():
533
+ return this.length > 0 and /^\d+$/.test(this)
534
+ )
535
+
536
+ define_str_func('isalnum', def():
537
+ return this.length > 0 and /^[a-zA-Z0-9]+$/.test(this)
538
+ )
539
+
540
+ define_str_func('isidentifier', def():
541
+ return this.length > 0 and /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(this)
542
+ )
543
+
544
+ define_str_func('casefold', def():
545
+ return this.toLowerCase()
546
+ )
547
+
548
+ define_str_func('removeprefix', def(prefix):
549
+ s = this.toString()
550
+ if s.startsWith(prefix):
551
+ return s.slice(prefix.length)
552
+ return s
553
+ )
554
+
555
+ define_str_func('removesuffix', def(suffix):
556
+ s = this.toString()
557
+ if suffix.length and s.endsWith(suffix):
558
+ return s.slice(0, s.length - suffix.length)
559
+ return s
560
+ )
561
+
528
562
  define_str_func('join', def(iterable):
529
563
  if Array.isArray(iterable):
530
564
  return iterable.join(this)
@@ -795,4 +829,11 @@ define_str_func('zfill', def(width):
795
829
  ρσ_str.whitespace = ' \t\n\r\x0b\x0c'
796
830
 
797
831
  v'define_str_func = undefined'
798
- v'var str = ρσ_str, repr = ρσ_repr'
832
+ def ρσ_format(value, spec):
833
+ if value is not None and value is not undefined and v'typeof value.__format__ === "function"':
834
+ return value.__format__(spec if spec is not undefined else '')
835
+ if spec is undefined or spec is '':
836
+ return ρσ_str(value)
837
+ return str.format('{:' + spec + '}', value)
838
+
839
+ v'var str = ρσ_str, repr = ρσ_repr, format = ρσ_format'
@@ -353,7 +353,7 @@ class Counter:
353
353
 
354
354
  def most_common(self, n=None):
355
355
  items = [[k, self._data[k]] for k in self._data]
356
- items.pysort(key=def(pair): return -pair[1];)
356
+ items.sort(key=def(pair): return -pair[1];)
357
357
  if n is not None:
358
358
  return items[:n]
359
359
  return items
package/src/lib/numpy.pyj CHANGED
@@ -1055,7 +1055,7 @@ def sort(a, axis=-1):
1055
1055
  a = asarray(a)
1056
1056
  if a.ndim is 1 or axis is None:
1057
1057
  data = a._data.slice()
1058
- data.sort(def(x, y): return x - y;)
1058
+ data.jssort(def(x, y): return x - y;)
1059
1059
  return ndarray(a.shape.slice(), a.dtype, data, True)
1060
1060
  # 2D sort along last axis (axis=1 or axis=-1)
1061
1061
  if axis < 0:
@@ -1066,7 +1066,7 @@ def sort(a, axis=-1):
1066
1066
  if axis is 1:
1067
1067
  for i in range(rows):
1068
1068
  row = a._data.slice(i * cols, (i + 1) * cols)
1069
- row.sort(def(x, y): return x - y;)
1069
+ row.jssort(def(x, y): return x - y;)
1070
1070
  for v in row:
1071
1071
  new_data.push(v)
1072
1072
  elif axis is 0:
@@ -1074,7 +1074,7 @@ def sort(a, axis=-1):
1074
1074
  col = []
1075
1075
  for i in range(rows):
1076
1076
  col.push(a._data[i * cols + j])
1077
- col.sort(def(x, y): return x - y;)
1077
+ col.jssort(def(x, y): return x - y;)
1078
1078
  for i in range(rows):
1079
1079
  new_data[i * cols + j] = col[i]
1080
1080
  if new_data.length is 0:
@@ -1089,14 +1089,14 @@ def argsort(a, axis=-1):
1089
1089
  for i in range(a.size):
1090
1090
  indices.push(i)
1091
1091
  data = a._data.slice()
1092
- indices.sort(def(i, j): return data[i] - data[j];)
1092
+ indices.jssort(def(i, j): return data[i] - data[j];)
1093
1093
  return ndarray([a.size], 'int32', indices, True)
1094
1094
  # Flatten and sort
1095
1095
  data = a._data.slice()
1096
1096
  indices = []
1097
1097
  for i in range(data.length):
1098
1098
  indices.push(i)
1099
- indices.sort(def(i, j): return data[i] - data[j];)
1099
+ indices.jssort(def(i, j): return data[i] - data[j];)
1100
1100
  return ndarray([indices.length], 'int32', indices, True)
1101
1101
 
1102
1102
  def searchsorted(a, v, side='left'):
@@ -1157,7 +1157,7 @@ def where(condition, x=None, y=None):
1157
1157
  def median(a, axis=None):
1158
1158
  a = asarray(a)
1159
1159
  data = a._data.slice()
1160
- data.sort(def(x, y): return x - y;)
1160
+ data.jssort(def(x, y): return x - y;)
1161
1161
  n = data.length
1162
1162
  if n % 2 is 1:
1163
1163
  return data[Math.floor(n / 2)]
@@ -1166,7 +1166,7 @@ def median(a, axis=None):
1166
1166
  def percentile(a, q, axis=None):
1167
1167
  a = asarray(a)
1168
1168
  data = a._data.slice()
1169
- data.sort(def(x, y): return x - y;)
1169
+ data.jssort(def(x, y): return x - y;)
1170
1170
  n = data.length
1171
1171
  idx = q / 100.0 * (n - 1)
1172
1172
  lo = Math.floor(idx)
@@ -1497,7 +1497,7 @@ def union1d(ar1, ar2):
1497
1497
  if not seen[key]:
1498
1498
  seen[key] = True
1499
1499
  unique.push(v)
1500
- unique.sort(def(a, b): return a - b;)
1500
+ unique.jssort(def(a, b): return a - b;)
1501
1501
  return ndarray([unique.length], 'float64', unique, True)
1502
1502
 
1503
1503
  def intersect1d(ar1, ar2):
@@ -1513,7 +1513,7 @@ def intersect1d(ar1, ar2):
1513
1513
  if s[key] and not seen[key]:
1514
1514
  seen[key] = True
1515
1515
  result.push(v)
1516
- result.sort(def(a, b): return a - b;)
1516
+ result.jssort(def(a, b): return a - b;)
1517
1517
  return ndarray([result.length], 'float64', result, True)
1518
1518
 
1519
1519
  def setdiff1d(ar1, ar2):
@@ -1529,7 +1529,7 @@ def setdiff1d(ar1, ar2):
1529
1529
  if not s[key] and not seen[key]:
1530
1530
  seen[key] = True
1531
1531
  result.push(v)
1532
- result.sort(def(a, b): return a - b;)
1532
+ result.jssort(def(a, b): return a - b;)
1533
1533
  return ndarray([result.length], 'float64', result, True)
1534
1534
 
1535
1535
  def in1d(ar1, ar2):
@@ -6,24 +6,12 @@
6
6
  // const scopeMap = analyzer.analyze(sourceCode, { virtualFiles: {...} });
7
7
 
8
8
  import { ScopeMap, ScopeFrame, SymbolInfo } from './scope.js';
9
+ import { build_scoped_flags } from './diagnostics.js';
9
10
 
10
11
  // ---------------------------------------------------------------------------
11
12
  // Helpers
12
13
  // ---------------------------------------------------------------------------
13
14
 
14
- function build_scoped_flags(flags_str) {
15
- const result = Object.create(null);
16
- if (!flags_str) return result;
17
- flags_str.split(',').forEach(flag => {
18
- flag = flag.trim();
19
- if (!flag) return;
20
- let val = true;
21
- if (flag.startsWith('no_')) { val = false; flag = flag.slice(3); }
22
- result[flag] = val;
23
- });
24
- return result;
25
- }
26
-
27
15
  /** Convert AST token position {line (1-indexed), col (0-indexed)} → {line, column} (both 1-indexed). */
28
16
  function pos_from_token(tok) {
29
17
  if (!tok) return { line: 1, column: 1 };
@@ -58,6 +46,95 @@ function dot_path_from_node(node, RS) {
58
46
  return null;
59
47
  }
60
48
 
49
+ /**
50
+ * Infer the type of a single return-value expression.
51
+ * Returns a type string ('list', 'dict', 'str', 'number', 'bool', or a class name),
52
+ * or null if the type cannot be determined from this expression alone.
53
+ *
54
+ * @param {object} node - The AST expression node (return value)
55
+ * @param {ScopeFrame} scope - The function's own scope frame (fully populated)
56
+ * @param {object} RS - RapydScript compiler object
57
+ * @returns {string|null}
58
+ */
59
+ function infer_expr_type(node, scope, RS) {
60
+ if (node instanceof RS.AST_Array) return 'list';
61
+ if (node instanceof RS.AST_Object) return 'dict';
62
+ if (node instanceof RS.AST_String) return 'str';
63
+ if (node instanceof RS.AST_Number) return 'number';
64
+ if ((RS.AST_True && node instanceof RS.AST_True) ||
65
+ (RS.AST_False && node instanceof RS.AST_False)) return 'bool';
66
+ if (RS.AST_Null && node instanceof RS.AST_Null) return null; // None → not counted
67
+
68
+ if (node instanceof RS.AST_SymbolRef) {
69
+ const sym = scope ? scope.getSymbol(node.name) : null;
70
+ return (sym && sym.inferred_class) ? sym.inferred_class : null;
71
+ }
72
+
73
+ if (node instanceof RS.AST_BaseCall && node.expression instanceof RS.AST_SymbolRef) {
74
+ const name = node.expression.name;
75
+ // PascalCase → assume class constructor, use name as type
76
+ if (/^[A-Z]/.test(name)) return name;
77
+ // Known local function with an already-resolved return_type
78
+ const sym = scope ? scope.getSymbol(name) : null;
79
+ if (sym && sym.return_type) return sym.return_type;
80
+ }
81
+
82
+ return null;
83
+ }
84
+
85
+ /**
86
+ * Walk a function node's body, collect all return-expression types, and return
87
+ * the single inferred type if every non-None return agrees on one type.
88
+ * Skips nested function bodies (their returns don't belong to this function).
89
+ * Returns null if returns disagree, are unrecognised, or the body has no typed returns.
90
+ *
91
+ * @param {object} func_node - AST_Lambda node
92
+ * @param {ScopeFrame} func_scope - The function's own scope frame (already populated)
93
+ * @param {object} RS - RapydScript compiler object
94
+ * @returns {string|null}
95
+ */
96
+ function collect_return_type(func_node, func_scope, RS) {
97
+ const types = new Set();
98
+ // Use a plain visitor object (same pattern as ScopeBuilder): _visit(node, descend)
99
+ // receives the node and a closure that walks its children; call descend() to recurse.
100
+ func_node.walk({
101
+ _visit: function (node, descend) {
102
+ // Stop descent into nested functions — their returns are not ours.
103
+ if (node !== func_node && node instanceof RS.AST_Lambda) return;
104
+ if (node instanceof RS.AST_Return && node.value) {
105
+ const t = infer_expr_type(node.value, func_scope, RS);
106
+ if (t) types.add(t);
107
+ }
108
+ if (descend) descend(); // recurse into children
109
+ },
110
+ });
111
+ return types.size === 1 ? [...types][0] : null;
112
+ }
113
+
114
+ /**
115
+ * Extract the return type annotation from a function node as a normalized string.
116
+ * Maps `-> [X]` to 'list', `-> {k: v}` to 'dict', `-> str` to 'str', etc.
117
+ * Returns null if no annotation or unrecognised shape.
118
+ * @param {object} func_node
119
+ * @param {object} RS
120
+ * @returns {string|null}
121
+ */
122
+ function extract_return_type(func_node, RS) {
123
+ const ann = func_node.return_annotation;
124
+ if (!ann) return null;
125
+ if (ann instanceof RS.AST_Array) return 'list';
126
+ if (ann instanceof RS.AST_Object) return 'dict';
127
+ if (ann instanceof RS.AST_SymbolRef ||
128
+ (RS.AST_SymbolVar && ann instanceof RS.AST_SymbolVar)) {
129
+ const n = ann.name;
130
+ if (n === 'str' || n === 'string') return 'str';
131
+ if (n === 'int' || n === 'float' || n === 'number') return 'number';
132
+ if (n === 'bool' || n === 'boolean') return 'bool';
133
+ return n; // user-defined class name or 'list'/'dict' spelled out
134
+ }
135
+ return null;
136
+ }
137
+
61
138
  /**
62
139
  * Collect parameter descriptors from an AST_Lambda node.
63
140
  * Handles regular args, positional-only (/), keyword-only (*), *args, and **kwargs.
@@ -106,10 +183,11 @@ function extract_params(lambda_node) {
106
183
 
107
184
  class ScopeBuilder {
108
185
  constructor(RS, map) {
109
- this._RS = RS;
110
- this._map = map;
111
- this._scopes = []; // stack of ScopeFrame
112
- this.current_node = null;
186
+ this._RS = RS;
187
+ this._map = map;
188
+ this._scopes = []; // stack of ScopeFrame
189
+ this.current_node = null;
190
+ this._current_from_module = null; // set while inside an AST_Import with argnames
113
191
  }
114
192
 
115
193
  _current_scope() {
@@ -155,6 +233,9 @@ class ScopeBuilder {
155
233
  params: opts.params || null,
156
234
  inferred_class: opts.inferred_class || null,
157
235
  dts_call_path: opts.dts_call_path || null,
236
+ return_type: opts.return_type || null,
237
+ source_module: opts.source_module || null,
238
+ original_name: opts.original_name || null,
158
239
  });
159
240
  scope.addSymbol(sym);
160
241
  return sym;
@@ -188,6 +269,7 @@ class ScopeBuilder {
188
269
  scope_depth: parent.depth,
189
270
  doc: extract_doc(node),
190
271
  params: extract_params(node),
272
+ return_type: extract_return_type(node, RS),
191
273
  });
192
274
  parent.addSymbol(sym);
193
275
  }
@@ -251,18 +333,28 @@ class ScopeBuilder {
251
333
  defined_at: pos_from_token(node.start),
252
334
  });
253
335
 
336
+ } else if (node instanceof RS.AST_Import && node.argnames) {
337
+ // `from X import name [as alias], ...` — record module name so children can use it.
338
+ const mod_node = node.module;
339
+ this._current_from_module = mod_node
340
+ ? (mod_node.name || mod_node.value || null)
341
+ : null;
342
+
254
343
  } else if (node instanceof RS.AST_ImportedVar) {
255
344
  // `from X import name` or `from X import name as alias`
256
- const name = (node.alias && node.alias.name) ? node.alias.name : node.name;
257
- if (name) {
345
+ const alias = (node.alias && node.alias.name) ? node.alias.name : node.name;
346
+ if (alias) {
258
347
  this._add_symbol({
259
- name,
260
- kind: 'import',
261
- defined_at: pos_from_token(node.start),
348
+ name: alias,
349
+ kind: 'import',
350
+ defined_at: pos_from_token(node.start),
351
+ source_module: this._current_from_module || null,
352
+ original_name: (node.name !== alias) ? node.name : null,
262
353
  });
263
354
  }
264
355
 
265
356
  } else if (node instanceof RS.AST_Import && !node.argnames) {
357
+ this._current_from_module = null;
266
358
  // `import X` or `import X as Y` (no from-clause)
267
359
  const name = (node.alias && node.alias.name)
268
360
  ? node.alias.name
@@ -468,6 +560,24 @@ class ScopeBuilder {
468
560
 
469
561
  if (cont) cont();
470
562
 
563
+ // ------------------------------------------------------------------
564
+ // 3b. Post-traversal: infer return type for unannotated functions.
565
+ // The function's own scope is fully populated at this point, so
566
+ // local variable inferred_class values are available for lookup.
567
+ // ------------------------------------------------------------------
568
+
569
+ if (node instanceof RS.AST_Lambda && this._scopes.length > prev_depth) {
570
+ const func_scope = this._current_scope();
571
+ const parent_frame = func_scope ? func_scope.parent : null;
572
+ const fname = node.name ? node.name.name : null;
573
+ if (fname && parent_frame) {
574
+ const sym = parent_frame.getSymbol(fname);
575
+ if (sym && !sym.return_type) {
576
+ sym.return_type = collect_return_type(node, func_scope, RS);
577
+ }
578
+ }
579
+ }
580
+
471
581
  // ------------------------------------------------------------------
472
582
  // 4. Pop the scope we pushed (if any).
473
583
  // ------------------------------------------------------------------
@@ -104,6 +104,11 @@ const STUBS = [
104
104
  return_type: 'iterable',
105
105
  doc: 'Return an iterator of elements from iterable for which function returns true.' }),
106
106
 
107
+ new BuiltinInfo({ name: 'format',
108
+ params: [p('value'), p('spec', { type: 'str', optional: true })],
109
+ return_type: 'str',
110
+ doc: 'Return value formatted according to the format spec string.\n\nEquivalent to calling `value.__format__(spec)` or applying spec as a `str.format()` format-spec field. The spec mini-language is the same as what follows `:` in f-strings and `str.format()` fields: alignment (`<>^=`), sign (`+-`), width, grouping (`,_`), precision (`.N`), and type (`bcdoxXeEfFgGns%`).\n\nExamples:\n\n format(42, \'08b\') # \'00101010\'\n format(3.14, \'.2f\') # \'3.14\'\n format(\'hi\', \'>10\') # \' hi\'\n format(42) # \'42\'' }),
111
+
107
112
  new BuiltinInfo({ name: 'float', kind: 'class',
108
113
  params: [p('x', { optional: true })],
109
114
  return_type: 'float',
@@ -114,6 +119,11 @@ const STUBS = [
114
119
  return_type: 'any',
115
120
  doc: 'Return the value of the named attribute of obj. If not found, return default (or raise AttributeError).' }),
116
121
 
122
+ new BuiltinInfo({ name: 'hash',
123
+ params: [p('obj')],
124
+ return_type: 'int',
125
+ doc: 'Return the hash value of obj. Strings and numbers are hashed by value; class instances by identity. Raises TypeError for unhashable types (list, set, dict). Objects with __hash__ dispatch to it.' }),
126
+
117
127
  new BuiltinInfo({ name: 'hasattr',
118
128
  params: [p('obj'), p('name', { type: 'str' })],
119
129
  return_type: 'bool',
@@ -139,10 +149,15 @@ const STUBS = [
139
149
  return_type: 'bool',
140
150
  doc: 'Return True if obj is an instance of classinfo (or a subclass thereof).' }),
141
151
 
152
+ new BuiltinInfo({ name: 'issubclass',
153
+ params: [p('cls'), p('classinfo')],
154
+ return_type: 'bool',
155
+ doc: 'Return True if cls is a subclass of classinfo. classinfo may be a class or tuple of classes.' }),
156
+
142
157
  new BuiltinInfo({ name: 'iter',
143
- params: [p('obj')],
158
+ params: [p('obj'), p('sentinel', { optional: true })],
144
159
  return_type: 'iterator',
145
- doc: 'Return an iterator object for obj.' }),
160
+ doc: 'iter(iterable) iterator over iterable. iter(callable, sentinel) → calls callable repeatedly until it returns sentinel.' }),
146
161
 
147
162
  new BuiltinInfo({ name: 'len',
148
163
  params: [p('s')],
@@ -209,6 +224,11 @@ const STUBS = [
209
224
  return_type: 'set',
210
225
  doc: 'Create a new set, optionally populated from an iterable.' }),
211
226
 
227
+ new BuiltinInfo({ name: 'slice', kind: 'class',
228
+ params: [p('start_or_stop', { type: 'int' }), p('stop', { type: 'int', optional: true }), p('step', { type: 'int', optional: true })],
229
+ return_type: 'slice',
230
+ doc: 'Create a slice object representing the set of indices specified by range(start, stop, step).\n\nForms:\n- `slice(stop)` — equivalent to `slice(None, stop, None)`\n- `slice(start, stop)` — equivalent to `slice(start, stop, None)`\n- `slice(start, stop, step)` — full form\n\nAttributes: `.start`, `.stop`, `.step` (each may be `None`).\n\nMethod: `.indices(length)` — returns `(start, stop, step)` normalized for a sequence of the given length.\n\nExample:\n\n s = slice(1, 5)\n lst[s] # same as lst[1:5]\n s.indices(10) # (1, 5, 1)' }),
231
+
212
232
  new BuiltinInfo({ name: 'setattr',
213
233
  params: [p('obj'), p('name', { type: 'str' }), p('value')],
214
234
  return_type: 'None',