rapydscript-ns 0.8.2 → 0.8.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 (141) 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 +39 -0
  5. package/HACKING.md +103 -103
  6. package/LICENSE +24 -24
  7. package/PYTHON_DIFFERENCES_REPORT.md +291 -0
  8. package/PYTHON_FEATURE_COVERAGE.md +106 -15
  9. package/README.md +831 -52
  10. package/TODO.md +4 -286
  11. package/add-toc-to-readme +2 -2
  12. package/bin/export +75 -75
  13. package/bin/rapydscript +70 -70
  14. package/bin/web-repl-export +102 -102
  15. package/build +2 -2
  16. package/language-service/index.js +4623 -0
  17. package/language-service/language-service.d.ts +40 -0
  18. package/package.json +9 -7
  19. package/publish.py +37 -37
  20. package/release/baselib-plain-pretty.js +2006 -229
  21. package/release/baselib-plain-ugly.js +70 -3
  22. package/release/compiler.js +11554 -3870
  23. package/release/signatures.json +31 -29
  24. package/session.vim +4 -4
  25. package/setup.cfg +2 -2
  26. package/src/ast.pyj +93 -1
  27. package/src/baselib-builtins.pyj +99 -2
  28. package/src/baselib-containers.pyj +107 -4
  29. package/src/baselib-errors.pyj +44 -0
  30. package/src/baselib-internal.pyj +124 -5
  31. package/src/baselib-itertools.pyj +97 -97
  32. package/src/baselib-str.pyj +32 -1
  33. package/src/compiler.pyj +36 -36
  34. package/src/errors.pyj +30 -30
  35. package/src/lib/aes.pyj +646 -646
  36. package/src/lib/collections.pyj +1 -1
  37. package/src/lib/copy.pyj +120 -0
  38. package/src/lib/elementmaker.pyj +83 -83
  39. package/src/lib/encodings.pyj +126 -126
  40. package/src/lib/gettext.pyj +569 -569
  41. package/src/lib/itertools.pyj +580 -580
  42. package/src/lib/math.pyj +193 -193
  43. package/src/lib/numpy.pyj +10 -10
  44. package/src/lib/operator.pyj +11 -11
  45. package/src/lib/pythonize.pyj +20 -20
  46. package/src/lib/random.pyj +118 -118
  47. package/src/lib/re.pyj +470 -470
  48. package/src/lib/react.pyj +74 -0
  49. package/src/lib/traceback.pyj +63 -63
  50. package/src/lib/uuid.pyj +77 -77
  51. package/src/monaco-language-service/analyzer.js +131 -9
  52. package/src/monaco-language-service/builtins.js +17 -2
  53. package/src/monaco-language-service/completions.js +170 -1
  54. package/src/monaco-language-service/diagnostics.js +25 -3
  55. package/src/monaco-language-service/dts.js +550 -550
  56. package/src/monaco-language-service/index.js +17 -0
  57. package/src/monaco-language-service/scope.js +3 -0
  58. package/src/output/classes.pyj +128 -11
  59. package/src/output/codegen.pyj +17 -3
  60. package/src/output/comments.pyj +45 -45
  61. package/src/output/exceptions.pyj +201 -105
  62. package/src/output/functions.pyj +13 -16
  63. package/src/output/jsx.pyj +164 -0
  64. package/src/output/literals.pyj +28 -2
  65. package/src/output/loops.pyj +0 -9
  66. package/src/output/modules.pyj +2 -5
  67. package/src/output/operators.pyj +22 -2
  68. package/src/output/statements.pyj +2 -2
  69. package/src/output/stream.pyj +1 -13
  70. package/src/output/treeshake.pyj +182 -182
  71. package/src/output/utils.pyj +72 -72
  72. package/src/parse.pyj +434 -114
  73. package/src/string_interpolation.pyj +72 -72
  74. package/src/tokenizer.pyj +29 -0
  75. package/src/unicode_aliases.pyj +576 -576
  76. package/src/utils.pyj +192 -192
  77. package/test/_import_one.pyj +37 -37
  78. package/test/_import_two/__init__.pyj +11 -11
  79. package/test/_import_two/level2/deep.pyj +4 -4
  80. package/test/_import_two/other.pyj +6 -6
  81. package/test/_import_two/sub.pyj +13 -13
  82. package/test/aes_vectors.pyj +421 -421
  83. package/test/annotations.pyj +80 -80
  84. package/test/baselib.pyj +4 -4
  85. package/test/classes.pyj +56 -17
  86. package/test/collections.pyj +5 -5
  87. package/test/decorators.pyj +77 -77
  88. package/test/docstrings.pyj +39 -39
  89. package/test/elementmaker_test.pyj +45 -45
  90. package/test/functions.pyj +151 -151
  91. package/test/generators.pyj +41 -41
  92. package/test/generic.pyj +370 -370
  93. package/test/imports.pyj +72 -72
  94. package/test/internationalization.pyj +73 -73
  95. package/test/lint.pyj +164 -164
  96. package/test/loops.pyj +85 -85
  97. package/test/numpy.pyj +734 -734
  98. package/test/omit_function_metadata.pyj +20 -20
  99. package/test/python_compat.pyj +326 -0
  100. package/test/python_features.pyj +129 -29
  101. package/test/regexp.pyj +55 -55
  102. package/test/repl.pyj +121 -121
  103. package/test/scoped_flags.pyj +76 -76
  104. package/test/slice.pyj +105 -0
  105. package/test/str.pyj +25 -0
  106. package/test/unit/fixtures/fibonacci_expected.js +1 -1
  107. package/test/unit/index.js +2296 -71
  108. package/test/unit/language-service-builtins.js +70 -0
  109. package/test/unit/language-service-bundle.js +5 -5
  110. package/test/unit/language-service-completions.js +180 -0
  111. package/test/unit/language-service-dts.js +543 -543
  112. package/test/unit/language-service-hover.js +455 -455
  113. package/test/unit/language-service-index.js +350 -0
  114. package/test/unit/language-service-scope.js +255 -0
  115. package/test/unit/language-service.js +625 -4
  116. package/test/unit/run-language-service.js +1 -0
  117. package/test/unit/web-repl.js +437 -0
  118. package/tools/build-language-service.js +2 -2
  119. package/tools/cli.js +547 -547
  120. package/tools/compile.js +219 -219
  121. package/tools/compiler.js +0 -24
  122. package/tools/completer.js +131 -131
  123. package/tools/embedded_compiler.js +251 -251
  124. package/tools/export.js +3 -37
  125. package/tools/gettext.js +185 -185
  126. package/tools/ini.js +65 -65
  127. package/tools/msgfmt.js +187 -187
  128. package/tools/repl.js +223 -223
  129. package/tools/test.js +118 -118
  130. package/tools/utils.js +128 -128
  131. package/tools/web_repl.js +95 -95
  132. package/try +41 -41
  133. package/web-repl/env.js +196 -74
  134. package/web-repl/index.html +163 -163
  135. package/web-repl/main.js +252 -254
  136. package/web-repl/prism.css +139 -139
  137. package/web-repl/prism.js +113 -113
  138. package/web-repl/rapydscript.js +227 -139
  139. package/web-repl/sha1.js +25 -25
  140. package/hack_demo.pyj +0 -112
  141. package/web-repl/language-service.js +0 -4187
@@ -0,0 +1,350 @@
1
+ /*
2
+ * test/unit/language-service-index.js
3
+ *
4
+ * Tests for src/monaco-language-service/index.js (RapydScriptLanguageService).
5
+ * Focuses on the automatic virtualFiles population from open Monaco models,
6
+ * which enables cross-file return-type inference without explicit setVirtualFiles calls.
7
+ *
8
+ * Usage:
9
+ * node test/unit/language-service-index.js # run all tests
10
+ * node test/unit/language-service-index.js <test-name> # run a single test
11
+ */
12
+ "use strict";
13
+
14
+ var assert = require("assert");
15
+ var path = require("path");
16
+ var url = require("url");
17
+ var fs = require("fs");
18
+ var compiler_module = require("../../tools/compiler");
19
+ var utils = require("../../tools/utils");
20
+ var colored = utils.safe_colored;
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Mock Monaco helpers
24
+ // ---------------------------------------------------------------------------
25
+
26
+ /**
27
+ * Create a minimal mock Monaco environment.
28
+ * initial_models: models that exist before registerRapydScript is called.
29
+ */
30
+ function make_mock_monaco(initial_models) {
31
+ var models = (initial_models || []).slice();
32
+ var create_listeners = [];
33
+ var dispose_listeners = [];
34
+
35
+ var mock = {
36
+ MarkerSeverity: { Error: 8, Warning: 4, Info: 2, Hint: 1 },
37
+ languages: {
38
+ getLanguages: function() { return []; },
39
+ register: function() {},
40
+ registerCompletionItemProvider: function() { return { dispose: function() {} }; },
41
+ registerSignatureHelpProvider: function() { return { dispose: function() {} }; },
42
+ registerHoverProvider: function() { return { dispose: function() {} }; },
43
+ setMonarchTokensProvider: function() {},
44
+ setLanguageConfiguration: function() {},
45
+ CompletionItemKind: { Function: 1, Variable: 6, Class: 5, Module: 8, Keyword: 17, Method: 0 },
46
+ },
47
+ editor: {
48
+ getModels: function() { return models.slice(); },
49
+ setModelMarkers: function() {},
50
+ onDidCreateModel: function(cb) { create_listeners.push(cb); return { dispose: function() {} }; },
51
+ onWillDisposeModel: function(cb) { dispose_listeners.push(cb); return { dispose: function() {} }; },
52
+ },
53
+ // Test helpers — simulate adding/removing a model after service creation
54
+ _addModel: function(model) {
55
+ models.push(model);
56
+ create_listeners.forEach(function(cb) { cb(model); });
57
+ },
58
+ _disposeModel: function(model) {
59
+ models = models.filter(function(m) { return m !== model; });
60
+ dispose_listeners.forEach(function(cb) { cb(model); });
61
+ },
62
+ };
63
+ return mock;
64
+ }
65
+
66
+ /**
67
+ * Create a minimal mock Monaco TextModel.
68
+ * @param {string} code - source content
69
+ * @param {string} uri_path - the path portion of the URI (e.g. '/scripts/utils.py')
70
+ * @param {string} [lang] - language id (default: 'rapydscript')
71
+ */
72
+ function make_model(code, uri_path, lang) {
73
+ lang = lang || 'rapydscript';
74
+ var listeners = [];
75
+ var current_code = code;
76
+ return {
77
+ getLanguageId: function() { return lang; },
78
+ getValue: function() { return current_code; },
79
+ uri: { path: uri_path || '/unnamed.py' },
80
+ onDidChangeContent: function(cb) { listeners.push(cb); return { dispose: function() {} }; },
81
+ // Test helper — simulate an edit
82
+ _setContent: function(new_code) {
83
+ current_code = new_code;
84
+ listeners.forEach(function(cb) { cb({ changes: [] }); });
85
+ },
86
+ };
87
+ }
88
+
89
+ /** Wait for all pending setTimeout(fn, 0) callbacks to fire. */
90
+ function flush(ms) {
91
+ return new Promise(function(resolve) { setTimeout(resolve, ms || 20); });
92
+ }
93
+
94
+ // ---------------------------------------------------------------------------
95
+ // Test definitions (each test returns a Promise or is sync)
96
+ // ---------------------------------------------------------------------------
97
+
98
+ function make_tests(registerRapydScript, RS) {
99
+
100
+ var builtins_src = fs.readFileSync(
101
+ path.join(__dirname, '../../dev/baselib-plain-pretty.js'), 'utf-8'
102
+ );
103
+
104
+ function make_service(models, extra_opts) {
105
+ var compiler = RS.web_repl ? RS.web_repl() : RS;
106
+ var monaco = make_mock_monaco(models);
107
+ var svc = registerRapydScript(monaco, Object.assign({
108
+ compiler: compiler,
109
+ }, extra_opts || {}));
110
+ return { svc: svc, monaco: monaco };
111
+ }
112
+
113
+ var TESTS = [
114
+
115
+ // ── Module name derivation ────────────────────────────────────────
116
+
117
+ {
118
+ name: "module_name_from_uri_path",
119
+ description: "ModelState derives the module name from the URI path (strip dir + .py extension)",
120
+ run_async: function() {
121
+ var utils_model = make_model("def foo():\n return []\n", "/scripts/utils.py");
122
+ var env = make_service([utils_model]);
123
+ return flush().then(function() {
124
+ assert.ok(
125
+ Object.prototype.hasOwnProperty.call(env.svc._virtualFiles, 'utils'),
126
+ "Expected 'utils' in _virtualFiles after model _run(); got keys: " +
127
+ JSON.stringify(Object.keys(env.svc._virtualFiles))
128
+ );
129
+ assert.strictEqual(
130
+ env.svc._virtualFiles['utils'],
131
+ "def foo():\n return []\n",
132
+ "virtualFiles['utils'] should contain the model source"
133
+ );
134
+ });
135
+ },
136
+ },
137
+
138
+ {
139
+ name: "module_name_pyj_extension",
140
+ description: "ModelState also handles .pyj extensions",
141
+ run_async: function() {
142
+ var model = make_model("x = 1\n", "/lib/helpers.pyj");
143
+ var env = make_service([model]);
144
+ return flush().then(function() {
145
+ assert.ok(
146
+ Object.prototype.hasOwnProperty.call(env.svc._virtualFiles, 'helpers'),
147
+ "Expected 'helpers' in _virtualFiles for .pyj model"
148
+ );
149
+ });
150
+ },
151
+ },
152
+
153
+ // ── Auto-population on model attach ───────────────────────────────
154
+
155
+ {
156
+ name: "virtual_files_populated_on_attach",
157
+ description: "Opening a second model after service creation populates _virtualFiles",
158
+ run_async: function() {
159
+ var main_model = make_model("from utils import get_items\n", "/main.py");
160
+ var utils_model = make_model("def get_items():\n return []\n", "/utils.py");
161
+ var env = make_service([main_model]);
162
+ // utils.py is opened after initial service creation
163
+ env.monaco._addModel(utils_model);
164
+ return flush().then(function() {
165
+ assert.ok(
166
+ Object.prototype.hasOwnProperty.call(env.svc._virtualFiles, 'utils'),
167
+ "Expected 'utils' in _virtualFiles after model added dynamically"
168
+ );
169
+ });
170
+ },
171
+ },
172
+
173
+ // ── Cleanup on model detach ───────────────────────────────────────
174
+
175
+ {
176
+ name: "virtual_files_cleaned_on_detach",
177
+ description: "Closing a model removes its entry from _virtualFiles",
178
+ run_async: function() {
179
+ var utils_model = make_model("def get_items():\n return []\n", "/utils.py");
180
+ var env = make_service([utils_model]);
181
+ return flush().then(function() {
182
+ assert.ok(
183
+ Object.prototype.hasOwnProperty.call(env.svc._virtualFiles, 'utils'),
184
+ "Pre-condition: 'utils' should be in _virtualFiles before detach"
185
+ );
186
+ env.monaco._disposeModel(utils_model);
187
+ assert.ok(
188
+ !Object.prototype.hasOwnProperty.call(env.svc._virtualFiles, 'utils'),
189
+ "Expected 'utils' to be removed from _virtualFiles after model detach"
190
+ );
191
+ });
192
+ },
193
+ },
194
+
195
+ // ── Content updates ───────────────────────────────────────────────
196
+
197
+ {
198
+ name: "virtual_files_updated_on_content_change",
199
+ description: "Editing a model updates its entry in _virtualFiles",
200
+ run_async: function() {
201
+ var model = make_model("def get_items():\n return []\n", "/utils.py");
202
+ // Use parseDelay:0 so the re-run fires immediately after content change.
203
+ var env = make_service([model], { parseDelay: 0 });
204
+ return flush().then(function() {
205
+ model._setContent("def get_items():\n return {}\n");
206
+ return flush(20);
207
+ }).then(function() {
208
+ assert.strictEqual(
209
+ env.svc._virtualFiles['utils'],
210
+ "def get_items():\n return {}\n",
211
+ "_virtualFiles['utils'] should reflect the updated content"
212
+ );
213
+ });
214
+ },
215
+ },
216
+
217
+ // ── Cross-file inferred return type via auto-populated virtualFiles ─
218
+
219
+ {
220
+ name: "cross_file_inferred_return_type_list",
221
+ description: "Importing from an open model resolves inferred return_type='list' for completions",
222
+ run_async: function() {
223
+ var builtins_mod = require(path.join(__dirname, '../../src/monaco-language-service/builtins.js'));
224
+ var SourceAnalyzer = require(url.fileURLToPath(url.pathToFileURL(
225
+ path.join(__dirname, '../../src/monaco-language-service/analyzer.js')
226
+ ).href));
227
+
228
+ var utils_model = make_model(
229
+ "def get_items():\n return []\n",
230
+ "/utils.py"
231
+ );
232
+ var env = make_service([utils_model]);
233
+
234
+ return flush().then(function() {
235
+ // _virtualFiles['utils'] should now be populated automatically
236
+ assert.ok(
237
+ env.svc._virtualFiles['utils'],
238
+ "Pre-condition: virtualFiles['utils'] must be set"
239
+ );
240
+
241
+ // Simulate the current file importing from utils
242
+ var CompletionEngine = require(url.fileURLToPath(url.pathToFileURL(
243
+ path.join(__dirname, '../../src/monaco-language-service/completions.js')
244
+ ).href));
245
+ var analyzer = new SourceAnalyzer.SourceAnalyzer(RS);
246
+ var engine = new CompletionEngine.CompletionEngine(analyzer, {
247
+ virtualFiles: env.svc._virtualFiles,
248
+ builtinNames: ['print', 'len'],
249
+ builtinsRegistry: new builtins_mod.BuiltinsRegistry(),
250
+ });
251
+
252
+ var scopeMap = analyzer.analyze([
253
+ "from utils import get_items",
254
+ "items = get_items()",
255
+ "pass",
256
+ ].join("\n"), { virtualFiles: env.svc._virtualFiles });
257
+
258
+ // items. should suggest list members
259
+ var MockKind = { Function: 1, Variable: 6 };
260
+ var list = engine.getCompletions(scopeMap, { lineNumber: 3, column: 8 }, "items.", MockKind);
261
+ var labels = list.suggestions.map(function(s) { return s.label; });
262
+ assert.ok(labels.indexOf('append') !== -1,
263
+ "Expected 'append' in completions for items. (inferred list); got: " + JSON.stringify(labels));
264
+ });
265
+ },
266
+ },
267
+
268
+ ];
269
+
270
+ return TESTS;
271
+ }
272
+
273
+ // ---------------------------------------------------------------------------
274
+ // Async-aware runner
275
+ // ---------------------------------------------------------------------------
276
+
277
+ function run_tests(TESTS, filter) {
278
+ var tests = filter
279
+ ? TESTS.filter(function(t) { return t.name === filter; })
280
+ : TESTS;
281
+
282
+ if (tests.length === 0) {
283
+ console.error(colored("No test found: " + filter, "red"));
284
+ process.exit(1);
285
+ }
286
+
287
+ var failures = [];
288
+
289
+ function run_next(i) {
290
+ if (i >= tests.length) {
291
+ console.log("");
292
+ if (failures.length) {
293
+ console.log(colored(failures.length + " test(s) failed.", "red"));
294
+ process.exit(1);
295
+ } else {
296
+ console.log(colored("All " + tests.length + " language-service-index tests passed!", "green"));
297
+ process.exit(0);
298
+ }
299
+ return;
300
+ }
301
+
302
+ var test = tests[i];
303
+
304
+ function on_pass() {
305
+ console.log(colored("PASS " + test.name, "green") + " \u2013 " + test.description);
306
+ run_next(i + 1);
307
+ }
308
+ function on_fail(e) {
309
+ failures.push(test.name);
310
+ console.log(colored("FAIL " + test.name, "red") +
311
+ "\n " + (e.message || String(e)) + "\n");
312
+ run_next(i + 1);
313
+ }
314
+
315
+ if (typeof test.run_async === "function") {
316
+ Promise.resolve().then(function() {
317
+ return test.run_async();
318
+ }).then(on_pass).catch(on_fail);
319
+ } else {
320
+ try {
321
+ test.run();
322
+ on_pass();
323
+ } catch (e) {
324
+ on_fail(e);
325
+ }
326
+ }
327
+ }
328
+
329
+ run_next(0);
330
+ }
331
+
332
+ // ---------------------------------------------------------------------------
333
+ // Entry point
334
+ // ---------------------------------------------------------------------------
335
+
336
+ var index_path = url.pathToFileURL(
337
+ path.join(__dirname, "../../src/monaco-language-service/index.js")
338
+ ).href;
339
+
340
+ var filter = process.argv[2] || null;
341
+
342
+ import(index_path).then(function(mod) {
343
+ var registerRapydScript = mod.registerRapydScript;
344
+ var RS = compiler_module.create_compiler();
345
+ var TESTS = make_tests(registerRapydScript, RS);
346
+ run_tests(TESTS, filter);
347
+ }).catch(function(e) {
348
+ console.error(colored("Failed to load index module:", "red"), e);
349
+ process.exit(1);
350
+ });
@@ -770,6 +770,261 @@ function make_tests(SourceAnalyzer, RS) {
770
770
  },
771
771
  },
772
772
 
773
+ // ── Return type inference ─────────────────────────────────────────
774
+
775
+ {
776
+ name: "infer_return_type_list_literal",
777
+ description: "return [] infers return_type = 'list'",
778
+ run: function () {
779
+ var m = analyze("def get_items():\n return []");
780
+ var sym = find(m.getAllSymbols(), "get_items");
781
+ assert.ok(sym, "Expected 'get_items' symbol");
782
+ assert.strictEqual(sym.return_type, "list",
783
+ "Expected return_type 'list', got: " + sym.return_type);
784
+ },
785
+ },
786
+
787
+ {
788
+ name: "infer_return_type_dict_literal",
789
+ description: "return {} infers return_type = 'dict'",
790
+ run: function () {
791
+ var m = analyze("def get_map():\n return {}");
792
+ var sym = find(m.getAllSymbols(), "get_map");
793
+ assert.ok(sym, "Expected 'get_map' symbol");
794
+ assert.strictEqual(sym.return_type, "dict",
795
+ "Expected return_type 'dict', got: " + sym.return_type);
796
+ },
797
+ },
798
+
799
+ {
800
+ name: "infer_return_type_string_literal",
801
+ description: "return 'hello' infers return_type = 'str'",
802
+ run: function () {
803
+ var m = analyze("def get_name():\n return 'hello'");
804
+ var sym = find(m.getAllSymbols(), "get_name");
805
+ assert.ok(sym, "Expected 'get_name' symbol");
806
+ assert.strictEqual(sym.return_type, "str",
807
+ "Expected return_type 'str', got: " + sym.return_type);
808
+ },
809
+ },
810
+
811
+ {
812
+ name: "infer_return_type_number_literal",
813
+ description: "return 42 infers return_type = 'number'",
814
+ run: function () {
815
+ var m = analyze("def get_count():\n return 42");
816
+ var sym = find(m.getAllSymbols(), "get_count");
817
+ assert.ok(sym, "Expected 'get_count' symbol");
818
+ assert.strictEqual(sym.return_type, "number",
819
+ "Expected return_type 'number', got: " + sym.return_type);
820
+ },
821
+ },
822
+
823
+ {
824
+ name: "infer_return_type_bool_literal",
825
+ description: "return True infers return_type = 'bool'",
826
+ run: function () {
827
+ var m = analyze("def is_valid():\n return True");
828
+ var sym = find(m.getAllSymbols(), "is_valid");
829
+ assert.ok(sym, "Expected 'is_valid' symbol");
830
+ assert.strictEqual(sym.return_type, "bool",
831
+ "Expected return_type 'bool', got: " + sym.return_type);
832
+ },
833
+ },
834
+
835
+ {
836
+ name: "infer_return_type_constructor_call",
837
+ description: "return MyClass() infers return_type = 'MyClass'",
838
+ run: function () {
839
+ var m = analyze([
840
+ "class MyClass:",
841
+ " def __init__(self):",
842
+ " pass",
843
+ "def make():",
844
+ " return MyClass()",
845
+ ].join("\n"));
846
+ var sym = find(m.getAllSymbols(), "make");
847
+ assert.ok(sym, "Expected 'make' symbol");
848
+ assert.strictEqual(sym.return_type, "MyClass",
849
+ "Expected return_type 'MyClass', got: " + sym.return_type);
850
+ },
851
+ },
852
+
853
+ {
854
+ name: "infer_return_type_local_variable",
855
+ description: "return local_var infers type from local variable's inferred_class",
856
+ run: function () {
857
+ var m = analyze([
858
+ "def get_items():",
859
+ " result = []",
860
+ " return result",
861
+ ].join("\n"));
862
+ var sym = find(m.getAllSymbols(), "get_items");
863
+ assert.ok(sym, "Expected 'get_items' symbol");
864
+ assert.strictEqual(sym.return_type, "list",
865
+ "Expected return_type 'list' via local var, got: " + sym.return_type);
866
+ },
867
+ },
868
+
869
+ {
870
+ name: "infer_return_type_consistent_multiple_returns",
871
+ description: "Multiple returns of the same type still infer correctly",
872
+ run: function () {
873
+ var m = analyze([
874
+ "def get_name(flag):",
875
+ " if flag:",
876
+ " return 'yes'",
877
+ " return 'no'",
878
+ ].join("\n"));
879
+ var sym = find(m.getAllSymbols(), "get_name");
880
+ assert.ok(sym, "Expected 'get_name' symbol");
881
+ assert.strictEqual(sym.return_type, "str",
882
+ "Expected return_type 'str' for consistent returns, got: " + sym.return_type);
883
+ },
884
+ },
885
+
886
+ {
887
+ name: "infer_return_type_mixed_returns_no_inference",
888
+ description: "Mixed return types produce no inference (return_type stays null)",
889
+ run: function () {
890
+ var m = analyze([
891
+ "def ambiguous(flag):",
892
+ " if flag:",
893
+ " return []",
894
+ " return 'nope'",
895
+ ].join("\n"));
896
+ var sym = find(m.getAllSymbols(), "ambiguous");
897
+ assert.ok(sym, "Expected 'ambiguous' symbol");
898
+ assert.strictEqual(sym.return_type, null,
899
+ "Expected return_type null for mixed types, got: " + sym.return_type);
900
+ },
901
+ },
902
+
903
+ {
904
+ name: "infer_return_type_bare_return_ignored",
905
+ description: "Bare return (no value) does not block inference from typed returns",
906
+ run: function () {
907
+ var m = analyze([
908
+ "def maybe_get(flag):",
909
+ " if not flag:",
910
+ " return",
911
+ " return []",
912
+ ].join("\n"));
913
+ var sym = find(m.getAllSymbols(), "maybe_get");
914
+ assert.ok(sym, "Expected 'maybe_get' symbol");
915
+ assert.strictEqual(sym.return_type, "list",
916
+ "Expected return_type 'list' ignoring bare return, got: " + sym.return_type);
917
+ },
918
+ },
919
+
920
+ {
921
+ name: "infer_return_type_explicit_annotation_not_overwritten",
922
+ description: "An explicit -> annotation is not replaced by inference",
923
+ run: function () {
924
+ var m = analyze("def get_name() -> str:\n return 42");
925
+ var sym = find(m.getAllSymbols(), "get_name");
926
+ assert.ok(sym, "Expected 'get_name' symbol");
927
+ assert.strictEqual(sym.return_type, "str",
928
+ "Explicit annotation should win, got: " + sym.return_type);
929
+ },
930
+ },
931
+
932
+ {
933
+ name: "infer_return_type_nested_function_not_confused",
934
+ description: "Returns inside a nested function do not affect the outer function's type",
935
+ run: function () {
936
+ var m = analyze([
937
+ "def outer():",
938
+ " def inner():",
939
+ " return 'text'",
940
+ " return []",
941
+ ].join("\n"));
942
+ var outer = find(m.getAllSymbols(), "outer");
943
+ var inner = find(m.getAllSymbols(), "inner");
944
+ assert.ok(outer, "Expected 'outer' symbol");
945
+ assert.ok(inner, "Expected 'inner' symbol");
946
+ assert.strictEqual(outer.return_type, "list",
947
+ "outer return_type should be 'list', got: " + outer.return_type);
948
+ assert.strictEqual(inner.return_type, "str",
949
+ "inner return_type should be 'str', got: " + inner.return_type);
950
+ },
951
+ },
952
+
953
+ // ── Nested classes ────────────────────────────────────────────────
954
+
955
+ {
956
+ name: "nested_class_creates_inner_frame",
957
+ description: "A nested class definition creates its own 'class' frame inside the outer class frame",
958
+ run: function () {
959
+ var m = analyze([
960
+ "class Outer:",
961
+ " class Inner:",
962
+ " def method(self):",
963
+ " pass",
964
+ ].join("\n"));
965
+ var cls_frames = m.frames.filter(function (f) { return f.kind === "class"; });
966
+ assert.ok(cls_frames.length >= 2, "Expected at least 2 class frames (Outer + Inner), got " + cls_frames.length);
967
+ var names = cls_frames.map(function (f) { return f.name; });
968
+ assert.ok(names.indexOf("Outer") >= 0, "Expected 'Outer' class frame");
969
+ assert.ok(names.indexOf("Inner") >= 0, "Expected 'Inner' class frame");
970
+ },
971
+ },
972
+
973
+ {
974
+ name: "nested_class_symbol_in_outer_scope",
975
+ description: "The nested class name appears as a 'class' symbol in the outer class frame",
976
+ run: function () {
977
+ var m = analyze([
978
+ "class Outer:",
979
+ " class Inner:",
980
+ " pass",
981
+ ].join("\n"));
982
+ var outer_frame = m.frames.find(function (f) { return f.kind === "class" && f.name === "Outer"; });
983
+ assert.ok(outer_frame, "Expected 'Outer' class frame");
984
+ var inner_sym = outer_frame.getSymbol("Inner");
985
+ assert.ok(inner_sym, "Expected 'Inner' symbol in Outer's class frame");
986
+ assert.strictEqual(inner_sym.kind, "class");
987
+ },
988
+ },
989
+
990
+ {
991
+ name: "nested_class_methods_in_inner_frame",
992
+ description: "Methods of the nested class appear in the inner class frame, not the outer one",
993
+ run: function () {
994
+ var m = analyze([
995
+ "class Outer:",
996
+ " class Inner:",
997
+ " def greet(self):",
998
+ " pass",
999
+ " def do_outer(self):",
1000
+ " pass",
1001
+ ].join("\n"));
1002
+ var inner_frame = m.frames.find(function (f) { return f.kind === "class" && f.name === "Inner"; });
1003
+ assert.ok(inner_frame, "Expected 'Inner' class frame");
1004
+ assert.ok(inner_frame.getSymbol("greet"), "Expected 'greet' in Inner frame");
1005
+ var outer_frame = m.frames.find(function (f) { return f.kind === "class" && f.name === "Outer"; });
1006
+ assert.ok(outer_frame, "Expected 'Outer' class frame");
1007
+ assert.ok(!outer_frame.getSymbol("greet"), "'greet' should NOT be in Outer frame");
1008
+ assert.ok(outer_frame.getSymbol("do_outer"), "Expected 'do_outer' in Outer frame");
1009
+ },
1010
+ },
1011
+
1012
+ {
1013
+ name: "nested_class_appears_in_module_scope",
1014
+ description: "The outer class appears in module scope; the inner class does not pollute module scope",
1015
+ run: function () {
1016
+ var m = analyze([
1017
+ "class Outer:",
1018
+ " class Inner:",
1019
+ " pass",
1020
+ ].join("\n"));
1021
+ var module_frame = m.frames.find(function (f) { return f.kind === "module"; });
1022
+ assert.ok(module_frame, "Expected module frame");
1023
+ assert.ok(module_frame.getSymbol("Outer"), "Expected 'Outer' in module scope");
1024
+ assert.ok(!module_frame.getSymbol("Inner"), "'Inner' should NOT appear directly in module scope");
1025
+ },
1026
+ },
1027
+
773
1028
  ];
774
1029
 
775
1030
  return TESTS;