xbintsc 0.5.22 → 0.5.31

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 (391) hide show
  1. package/AGENTS.md +12 -3
  2. package/README.md +46 -11
  3. package/README.zh-CN.md +32 -6
  4. package/dist/scripts/support-matrix.d.ts +18 -0
  5. package/dist/scripts/support-matrix.js +141 -0
  6. package/dist/scripts/support-matrix.js.map +1 -0
  7. package/dist/src/ast/common.d.ts +2 -2
  8. package/dist/src/ast/declarations.d.ts +7 -1
  9. package/dist/src/ast/expressions.d.ts +5 -0
  10. package/dist/src/ast/kinds.d.ts +2 -0
  11. package/dist/src/ast/kinds.js +2 -0
  12. package/dist/src/ast/kinds.js.map +1 -1
  13. package/dist/src/binder/binder.d.ts +2 -1
  14. package/dist/src/binder/binder.js +2 -0
  15. package/dist/src/binder/binder.js.map +1 -1
  16. package/dist/src/binder/declarations.d.ts +9 -0
  17. package/dist/src/binder/declarations.js +64 -0
  18. package/dist/src/binder/declarations.js.map +1 -1
  19. package/dist/src/binder/references.js +85 -3
  20. package/dist/src/binder/references.js.map +1 -1
  21. package/dist/src/binder/types.d.ts +32 -0
  22. package/dist/src/binder/types.js.map +1 -1
  23. package/dist/src/canonical.d.ts +29 -0
  24. package/dist/src/canonical.js +35 -0
  25. package/dist/src/canonical.js.map +1 -0
  26. package/dist/src/checker/assignability.d.ts +13 -0
  27. package/dist/src/checker/assignability.js +146 -0
  28. package/dist/src/checker/assignability.js.map +1 -0
  29. package/dist/src/checker/calls.d.ts +13 -0
  30. package/dist/src/checker/calls.js +127 -0
  31. package/dist/src/checker/calls.js.map +1 -0
  32. package/dist/src/checker/checker.d.ts +26 -0
  33. package/dist/src/checker/checker.js +31 -0
  34. package/dist/src/checker/checker.js.map +1 -0
  35. package/dist/src/checker/declarations.d.ts +9 -0
  36. package/dist/src/checker/declarations.js +15 -0
  37. package/dist/src/checker/declarations.js.map +1 -0
  38. package/dist/src/checker/flow.d.ts +21 -0
  39. package/dist/src/checker/flow.js +241 -0
  40. package/dist/src/checker/flow.js.map +1 -0
  41. package/dist/src/checker/globals.d.ts +9 -0
  42. package/dist/src/checker/globals.js +29 -0
  43. package/dist/src/checker/globals.js.map +1 -0
  44. package/dist/src/checker/source.d.ts +38 -0
  45. package/dist/src/checker/source.js +48 -0
  46. package/dist/src/checker/source.js.map +1 -0
  47. package/dist/src/checker/type-infer.d.ts +18 -0
  48. package/dist/src/checker/type-infer.js +173 -0
  49. package/dist/src/checker/type-infer.js.map +1 -0
  50. package/dist/src/checker/type-model.d.ts +73 -0
  51. package/dist/src/checker/type-model.js +151 -0
  52. package/dist/src/checker/type-model.js.map +1 -0
  53. package/dist/src/checker/type-system.d.ts +58 -0
  54. package/dist/src/checker/type-system.js +569 -0
  55. package/dist/src/checker/type-system.js.map +1 -0
  56. package/dist/src/checker/typecheck-types.d.ts +22 -0
  57. package/dist/src/checker/typecheck-types.js +109 -0
  58. package/dist/src/checker/typecheck-types.js.map +1 -0
  59. package/dist/src/checker/typecheck.d.ts +62 -0
  60. package/dist/src/checker/typecheck.js +574 -0
  61. package/dist/src/checker/typecheck.js.map +1 -0
  62. package/dist/src/checker/undefined.d.ts +14 -0
  63. package/dist/src/checker/undefined.js +32 -0
  64. package/dist/src/checker/undefined.js.map +1 -0
  65. package/dist/src/checker/walk.d.ts +10 -0
  66. package/dist/src/checker/walk.js +15 -0
  67. package/dist/src/checker/walk.js.map +1 -0
  68. package/dist/src/cli/help.d.ts +7 -0
  69. package/dist/src/cli/help.js +64 -0
  70. package/dist/src/cli/help.js.map +1 -0
  71. package/dist/src/cli/hints.js +13 -1
  72. package/dist/src/cli/hints.js.map +1 -1
  73. package/dist/src/cli/main.js +12 -48
  74. package/dist/src/cli/main.js.map +1 -1
  75. package/dist/src/codegen/generator/calls/access.js +8 -8
  76. package/dist/src/codegen/generator/calls/access.js.map +1 -1
  77. package/dist/src/codegen/generator/calls/invocation.js +9 -4
  78. package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
  79. package/dist/src/codegen/generator/calls/super.d.ts +9 -1
  80. package/dist/src/codegen/generator/calls/super.js +56 -5
  81. package/dist/src/codegen/generator/calls/super.js.map +1 -1
  82. package/dist/src/codegen/generator/context.d.ts +13 -2
  83. package/dist/src/codegen/generator/context.js +25 -19
  84. package/dist/src/codegen/generator/context.js.map +1 -1
  85. package/dist/src/codegen/generator/debug.js +2 -1
  86. package/dist/src/codegen/generator/debug.js.map +1 -1
  87. package/dist/src/codegen/generator/expressions/primary.d.ts +4 -1
  88. package/dist/src/codegen/generator/expressions/primary.js +68 -8
  89. package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
  90. package/dist/src/codegen/generator/generator.js +11 -1
  91. package/dist/src/codegen/generator/generator.js.map +1 -1
  92. package/dist/src/codegen/generator/module.d.ts +2 -0
  93. package/dist/src/codegen/generator/module.js +62 -26
  94. package/dist/src/codegen/generator/module.js.map +1 -1
  95. package/dist/src/codegen/generator/state.d.ts +11 -1
  96. package/dist/src/codegen/generator/state.js.map +1 -1
  97. package/dist/src/codegen/generator/statements/variables.js +20 -3
  98. package/dist/src/codegen/generator/statements/variables.js.map +1 -1
  99. package/dist/src/codegen/generator/tables.d.ts +2 -0
  100. package/dist/src/codegen/generator/tables.js +4 -0
  101. package/dist/src/codegen/generator/tables.js.map +1 -1
  102. package/dist/src/driver/build-key.d.ts +2 -0
  103. package/dist/src/driver/build-key.js +1 -0
  104. package/dist/src/driver/build-key.js.map +1 -1
  105. package/dist/src/driver/bundler/graph.js +5 -1
  106. package/dist/src/driver/bundler/graph.js.map +1 -1
  107. package/dist/src/driver/bundler/merge.js +16 -1
  108. package/dist/src/driver/bundler/merge.js.map +1 -1
  109. package/dist/src/driver/bundler/types.d.ts +12 -0
  110. package/dist/src/driver/compiler.d.ts +1 -1
  111. package/dist/src/driver/compiler.js +4 -0
  112. package/dist/src/driver/compiler.js.map +1 -1
  113. package/dist/src/driver/fingerprint.js +2 -1
  114. package/dist/src/driver/fingerprint.js.map +1 -1
  115. package/dist/src/driver/frontend.d.ts +8 -6
  116. package/dist/src/driver/frontend.js +4 -0
  117. package/dist/src/driver/frontend.js.map +1 -1
  118. package/dist/src/driver/options.d.ts +6 -0
  119. package/dist/src/driver/version.d.ts +1 -1
  120. package/dist/src/driver/version.js +1 -1
  121. package/dist/src/extensions/catalog.js +2 -1
  122. package/dist/src/extensions/catalog.js.map +1 -1
  123. package/dist/src/extensions/gui/html.js +20 -6
  124. package/dist/src/extensions/gui/html.js.map +1 -1
  125. package/dist/src/extensions/gui/index.js +63 -0
  126. package/dist/src/extensions/gui/index.js.map +1 -1
  127. package/dist/src/extensions/media/index.d.ts +27 -0
  128. package/dist/src/extensions/media/index.js +60 -0
  129. package/dist/src/extensions/media/index.js.map +1 -0
  130. package/dist/src/extensions/registry.d.ts +50 -0
  131. package/dist/src/extensions/registry.js +26 -0
  132. package/dist/src/extensions/registry.js.map +1 -1
  133. package/dist/src/index.d.ts +2 -0
  134. package/dist/src/index.js +2 -0
  135. package/dist/src/index.js.map +1 -1
  136. package/dist/src/parser/declarations/classes.js +7 -0
  137. package/dist/src/parser/declarations/classes.js.map +1 -1
  138. package/dist/src/parser/expressions.js +14 -1
  139. package/dist/src/parser/expressions.js.map +1 -1
  140. package/dist/src/parser/literals.js +6 -2
  141. package/dist/src/parser/literals.js.map +1 -1
  142. package/dist/tests/binder/binder.test.js +29 -0
  143. package/dist/tests/binder/binder.test.js.map +1 -1
  144. package/dist/tests/canonical.test.d.ts +1 -0
  145. package/dist/tests/canonical.test.js +19 -0
  146. package/dist/tests/canonical.test.js.map +1 -0
  147. package/dist/tests/checker/checker.test.d.ts +1 -0
  148. package/dist/tests/checker/checker.test.js +245 -0
  149. package/dist/tests/checker/checker.test.js.map +1 -0
  150. package/dist/tests/cli/check.test.js +20 -0
  151. package/dist/tests/cli/check.test.js.map +1 -1
  152. package/dist/tests/cli/hints.test.js +15 -0
  153. package/dist/tests/cli/hints.test.js.map +1 -1
  154. package/dist/tests/cli/process.test.js +12 -0
  155. package/dist/tests/cli/process.test.js.map +1 -1
  156. package/dist/tests/conformance/matrix.test.d.ts +6 -0
  157. package/dist/tests/conformance/matrix.test.js +23 -0
  158. package/dist/tests/conformance/matrix.test.js.map +1 -0
  159. package/dist/tests/conformance/test262/manifest.d.ts +115 -0
  160. package/dist/tests/conformance/test262/manifest.js +257 -0
  161. package/dist/tests/conformance/test262/manifest.js.map +1 -0
  162. package/dist/tests/conformance/test262.test.d.ts +11 -0
  163. package/dist/tests/conformance/test262.test.js +74 -0
  164. package/dist/tests/conformance/test262.test.js.map +1 -0
  165. package/dist/tests/docs/structure.test.js +9 -11
  166. package/dist/tests/docs/structure.test.js.map +1 -1
  167. package/dist/tests/e2e/child-process.test.js +3 -2
  168. package/dist/tests/e2e/child-process.test.js.map +1 -1
  169. package/dist/tests/e2e/compile-builtins.test.js +21 -0
  170. package/dist/tests/e2e/compile-builtins.test.js.map +1 -1
  171. package/dist/tests/e2e/compile-objects-classes.test.js +54 -0
  172. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
  173. package/dist/tests/e2e/differential-syntax.test.d.ts +5 -0
  174. package/dist/tests/e2e/differential-syntax.test.js +95 -0
  175. package/dist/tests/e2e/differential-syntax.test.js.map +1 -0
  176. package/dist/tests/e2e/differential-values.test.js +15 -0
  177. package/dist/tests/e2e/differential-values.test.js.map +1 -1
  178. package/dist/tests/e2e/gui-browser.test.d.ts +13 -0
  179. package/dist/tests/e2e/gui-browser.test.js +163 -0
  180. package/dist/tests/e2e/gui-browser.test.js.map +1 -0
  181. package/dist/tests/e2e/gui-example-browser.test.js +2 -2
  182. package/dist/tests/e2e/gui-example-browser.test.js.map +1 -1
  183. package/dist/tests/e2e/gui-example-canvas.test.js +8 -5
  184. package/dist/tests/e2e/gui-example-canvas.test.js.map +1 -1
  185. package/dist/tests/e2e/gui-example-svg.test.js +4 -3
  186. package/dist/tests/e2e/gui-example-svg.test.js.map +1 -1
  187. package/dist/tests/e2e/gui-example.test.js +4 -3
  188. package/dist/tests/e2e/gui-example.test.js.map +1 -1
  189. package/dist/tests/e2e/gui-helpers.d.ts +3 -0
  190. package/dist/tests/e2e/gui-helpers.js +6 -3
  191. package/dist/tests/e2e/gui-helpers.js.map +1 -1
  192. package/dist/tests/e2e/gui-media-disabled.test.d.ts +9 -0
  193. package/dist/tests/e2e/gui-media-disabled.test.js +42 -0
  194. package/dist/tests/e2e/gui-media-disabled.test.js.map +1 -0
  195. package/dist/tests/e2e/gui-media.test.d.ts +6 -4
  196. package/dist/tests/e2e/gui-media.test.js +20 -16
  197. package/dist/tests/e2e/gui-media.test.js.map +1 -1
  198. package/dist/tests/e2e/harness.d.ts +9 -0
  199. package/dist/tests/e2e/harness.js +14 -2
  200. package/dist/tests/e2e/harness.js.map +1 -1
  201. package/dist/tests/e2e/native-extensions.test.js +2 -1
  202. package/dist/tests/e2e/native-extensions.test.js.map +1 -1
  203. package/dist/tests/e2e/property-descriptors.test.js +20 -0
  204. package/dist/tests/e2e/property-descriptors.test.js.map +1 -1
  205. package/dist/tests/e2e/unicode-output.test.d.ts +16 -0
  206. package/dist/tests/e2e/unicode-output.test.js +67 -0
  207. package/dist/tests/e2e/unicode-output.test.js.map +1 -0
  208. package/dist/tests/e2e/weak-collections.test.js +16 -2
  209. package/dist/tests/e2e/weak-collections.test.js.map +1 -1
  210. package/dist/tests/extensions/media.test.d.ts +1 -0
  211. package/dist/tests/extensions/media.test.js +58 -0
  212. package/dist/tests/extensions/media.test.js.map +1 -0
  213. package/dist/tests/extensions/registry.test.js +8 -0
  214. package/dist/tests/extensions/registry.test.js.map +1 -1
  215. package/dist/tests/helpers.d.ts +1 -0
  216. package/dist/tests/helpers.js +1 -0
  217. package/dist/tests/helpers.js.map +1 -1
  218. package/dist/tests/lexer/identifiers.test.js +12 -0
  219. package/dist/tests/lexer/identifiers.test.js.map +1 -1
  220. package/dist/tests/lexer/scanner.test.js +8 -0
  221. package/dist/tests/lexer/scanner.test.js.map +1 -1
  222. package/dist/tests/lexer/strings.test.js +30 -0
  223. package/dist/tests/lexer/strings.test.js.map +1 -1
  224. package/dist/tests/parser/declarations.test.js +20 -0
  225. package/dist/tests/parser/declarations.test.js.map +1 -1
  226. package/dist/tests/parser/expressions.test.js +36 -0
  227. package/dist/tests/parser/expressions.test.js.map +1 -1
  228. package/dist/tests/platform.d.ts +32 -0
  229. package/dist/tests/platform.js +40 -0
  230. package/dist/tests/platform.js.map +1 -0
  231. package/dist/tests/platform.test.d.ts +12 -0
  232. package/dist/tests/platform.test.js +51 -0
  233. package/dist/tests/platform.test.js.map +1 -0
  234. package/dist/tests/version.test.js +4 -4
  235. package/dist/tests/version.test.js.map +1 -1
  236. package/doc/ai/README.md +10 -4
  237. package/doc/ai/build-recipe.md +1 -0
  238. package/doc/ai/cli.md +18 -6
  239. package/doc/ai/contributing.md +59 -2
  240. package/doc/ai/extensions.md +46 -23
  241. package/doc/ai/language-support.md +25 -16
  242. package/doc/ai/troubleshooting.md +9 -0
  243. package/doc/ai/zh-CN/README.md +9 -4
  244. package/doc/ai/zh-CN/build-recipe.md +1 -0
  245. package/doc/ai/zh-CN/cli.md +14 -4
  246. package/doc/ai/zh-CN/contributing.md +49 -3
  247. package/doc/ai/zh-CN/extensions.md +29 -8
  248. package/doc/ai/zh-CN/language-support.md +23 -14
  249. package/doc/ai/zh-CN/troubleshooting.md +8 -0
  250. package/doc/gui/api.md +45 -0
  251. package/doc/gui/build.md +37 -21
  252. package/doc/gui/known-issues.md +17 -6
  253. package/doc/gui/media.md +26 -11
  254. package/doc/gui/runtime.md +18 -0
  255. package/doc/gui-scripts.md +7 -0
  256. package/doc/gui.md +43 -33
  257. package/doc/implemented/binder.md +2 -0
  258. package/doc/implemented/checker.md +117 -0
  259. package/doc/implemented/cli.md +13 -1
  260. package/doc/implemented/codegen.md +3 -2
  261. package/doc/implemented/driver.md +2 -2
  262. package/doc/implemented/operators.md +2 -1
  263. package/doc/implemented/parser.md +3 -2
  264. package/doc/implemented/pipeline.md +8 -1
  265. package/doc/implemented/stdlib.md +2 -1
  266. package/doc/implemented/tests.md +3 -1
  267. package/doc/implemented.md +2 -0
  268. package/doc/npm-package.md +417 -0
  269. package/doc/requirements.md +4 -1
  270. package/doc/support-matrix.md +83 -0
  271. package/doc/unimplemented/classes.md +3 -0
  272. package/doc/unimplemented/deviations.md +3 -2
  273. package/doc/unimplemented/toolchain.md +1 -1
  274. package/doc/unimplemented/types.md +24 -8
  275. package/doc/unimplemented.md +1 -1
  276. package/doc/zh-CN/gui/api.md +41 -0
  277. package/doc/zh-CN/gui/build.md +28 -16
  278. package/doc/zh-CN/gui/known-issues.md +13 -5
  279. package/doc/zh-CN/gui/media.md +21 -10
  280. package/doc/zh-CN/gui/runtime.md +16 -0
  281. package/doc/zh-CN/gui-scripts.md +5 -0
  282. package/doc/zh-CN/gui.md +37 -32
  283. package/doc/zh-CN/implemented/binder.md +2 -0
  284. package/doc/zh-CN/implemented/checker.md +99 -0
  285. package/doc/zh-CN/implemented/cli.md +12 -1
  286. package/doc/zh-CN/implemented/codegen.md +3 -2
  287. package/doc/zh-CN/implemented/driver.md +2 -2
  288. package/doc/zh-CN/implemented/operators.md +1 -1
  289. package/doc/zh-CN/implemented/parser.md +3 -2
  290. package/doc/zh-CN/implemented/pipeline.md +3 -1
  291. package/doc/zh-CN/implemented/stdlib.md +2 -1
  292. package/doc/zh-CN/implemented/tests.md +3 -1
  293. package/doc/zh-CN/implemented.md +2 -0
  294. package/doc/zh-CN/npm-package.md +391 -0
  295. package/doc/zh-CN/post/announce.md +47 -47
  296. package/doc/zh-CN/requirements.md +4 -1
  297. package/doc/zh-CN/support-matrix.md +82 -0
  298. package/doc/zh-CN/unimplemented/classes.md +3 -0
  299. package/doc/zh-CN/unimplemented/deviations.md +3 -2
  300. package/doc/zh-CN/unimplemented/toolchain.md +1 -1
  301. package/doc/zh-CN/unimplemented/types.md +16 -6
  302. package/doc/zh-CN/unimplemented.md +1 -1
  303. package/llms.txt +4 -1
  304. package/package.json +3 -1
  305. package/runtime/ext_gui/audio.cpp +316 -0
  306. package/runtime/ext_gui/browser.cpp +547 -0
  307. package/runtime/ext_gui/browser_escape.cpp +85 -0
  308. package/runtime/ext_gui/browser_loop.cpp +83 -0
  309. package/runtime/ext_gui/canvas.cpp +9 -3
  310. package/runtime/ext_gui/canvas_raster.cpp +19 -1
  311. package/runtime/ext_gui/dom_api.cpp +36 -0
  312. package/runtime/ext_gui/dom_api_document.cpp +96 -5
  313. package/runtime/ext_gui/dom_api_events.cpp +20 -0
  314. package/runtime/ext_gui/dom_api_internal.h +19 -0
  315. package/runtime/ext_gui/dom_api_proto.cpp +6 -0
  316. package/runtime/ext_gui/gui.cpp +90 -23
  317. package/runtime/ext_gui/gui_engine.h +44 -0
  318. package/runtime/ext_gui/gui_media.cpp +69 -2
  319. package/runtime/ext_gui/media.cpp +34 -488
  320. package/runtime/ext_gui/media.h +54 -19
  321. package/runtime/ext_gui/media_api.h +65 -0
  322. package/runtime/ext_gui/window.cpp +9 -0
  323. package/runtime/ext_media/media.cpp +507 -0
  324. package/runtime/ext_media/media_bridge.c +35 -0
  325. package/runtime/ext_node/path/path.c +7 -7
  326. package/runtime/ext_node/url/url.c +4 -2
  327. package/runtime/rt.h +15 -0
  328. package/runtime/xt_constructors.c +2 -1
  329. package/runtime/xt_containers/objects.inc +22 -2
  330. package/runtime/xt_io/exception.inc +4 -2
  331. package/runtime/xt_stdlib/math.inc +62 -0
  332. package/runtime/xt_stdlib2/date.inc +7 -1
  333. package/runtime/xt_stdlib2/objects.inc +14 -0
  334. package/scripts/build-gui.ts +6 -72
  335. package/scripts/build-media.ts +245 -0
  336. package/scripts/support-matrix.ts +167 -0
  337. package/src/ast/common.ts +2 -0
  338. package/src/ast/declarations.ts +12 -1
  339. package/src/ast/expressions.ts +6 -0
  340. package/src/ast/kinds.ts +2 -0
  341. package/src/binder/binder.ts +3 -0
  342. package/src/binder/declarations.ts +70 -0
  343. package/src/binder/references.ts +84 -3
  344. package/src/binder/types.ts +30 -0
  345. package/src/canonical.ts +37 -0
  346. package/src/checker/assignability.ts +134 -0
  347. package/src/checker/calls.ts +135 -0
  348. package/src/checker/checker.ts +47 -0
  349. package/src/checker/declarations.ts +26 -0
  350. package/src/checker/flow.ts +236 -0
  351. package/src/checker/globals.ts +40 -0
  352. package/src/checker/source.ts +53 -0
  353. package/src/checker/type-infer.ts +185 -0
  354. package/src/checker/type-model.ts +212 -0
  355. package/src/checker/type-system.ts +578 -0
  356. package/src/checker/typecheck-types.ts +112 -0
  357. package/src/checker/typecheck.ts +586 -0
  358. package/src/checker/undefined.ts +43 -0
  359. package/src/checker/walk.ts +16 -0
  360. package/src/cli/help.ts +65 -0
  361. package/src/cli/hints.ts +13 -1
  362. package/src/cli/main.ts +12 -49
  363. package/src/codegen/generator/calls/access.ts +8 -8
  364. package/src/codegen/generator/calls/invocation.ts +8 -5
  365. package/src/codegen/generator/calls/super.ts +67 -5
  366. package/src/codegen/generator/context.ts +31 -19
  367. package/src/codegen/generator/debug.ts +2 -1
  368. package/src/codegen/generator/expressions/primary.ts +84 -13
  369. package/src/codegen/generator/generator.ts +11 -1
  370. package/src/codegen/generator/module.ts +65 -24
  371. package/src/codegen/generator/state.ts +11 -1
  372. package/src/codegen/generator/statements/variables.ts +20 -3
  373. package/src/codegen/generator/tables.ts +5 -0
  374. package/src/driver/build-key.ts +3 -0
  375. package/src/driver/bundler/graph.ts +6 -2
  376. package/src/driver/bundler/merge.ts +14 -1
  377. package/src/driver/bundler/types.ts +13 -0
  378. package/src/driver/compiler.ts +5 -1
  379. package/src/driver/fingerprint.ts +2 -1
  380. package/src/driver/frontend.ts +13 -2
  381. package/src/driver/options.ts +6 -0
  382. package/src/driver/version.ts +1 -1
  383. package/src/extensions/catalog.ts +2 -1
  384. package/src/extensions/gui/html.ts +23 -10
  385. package/src/extensions/gui/index.ts +65 -0
  386. package/src/extensions/media/index.ts +67 -0
  387. package/src/extensions/registry.ts +72 -0
  388. package/src/index.ts +2 -0
  389. package/src/parser/declarations/classes.ts +7 -0
  390. package/src/parser/expressions.ts +13 -1
  391. package/src/parser/literals.ts +6 -2
@@ -0,0 +1,578 @@
1
+ /**
2
+ * A deliberately small structural type model for the checker.
3
+ *
4
+ * The goal is not to reimplement TypeScript's type system. It is to know
5
+ * enough about annotations, interfaces, classes, arrays, functions and simple
6
+ * generics to answer "is this value definitely the wrong shape?" without
7
+ * guessing. Anything the model does not understand becomes `any`, and `any` is
8
+ * compatible with everything — so unsupported syntax can only hide a bug, never
9
+ * reject working code.
10
+ */
11
+
12
+ import {
13
+ ModifierKind,
14
+ SyntaxKind,
15
+ type ArrayTypeNode,
16
+ type BinaryExpression,
17
+ type CallExpression,
18
+ type ClassDeclaration,
19
+ type Expression,
20
+ type FunctionDeclaration,
21
+ type FunctionTypeNode,
22
+ type Identifier,
23
+ type InterfaceDeclaration,
24
+ type IntersectionTypeNode,
25
+ type LiteralTypeNode,
26
+ type MethodDeclaration,
27
+ type MethodSignature,
28
+ type Node,
29
+ type Parameter,
30
+ type PropertyAccessExpression,
31
+ type ParenthesizedTypeNode,
32
+ type PropertyDeclaration,
33
+ type PropertySignature,
34
+ type SourceFileNode,
35
+ type TupleTypeNode,
36
+ type TypeAliasDeclaration,
37
+ type TypeLiteralNode,
38
+ type TypeNode,
39
+ type TypeOperatorNode,
40
+ type TypeReference,
41
+ type UnionTypeNode,
42
+ type VariableDeclaration,
43
+ } from "../ast/nodes.js";
44
+ import { ASSIGNMENT_OPERATORS } from "../codegen/generator/tables.js";
45
+ import { classMemberName } from "../binder/helpers.js";
46
+ import { SymbolKind, type BindResult, type ClassInfo, type SymbolInfo } from "../binder/binder.js";
47
+ import { walk } from "./walk.js";
48
+ import {
49
+ ANY,
50
+ isParameterProperty,
51
+ isThisParameter,
52
+ primitive,
53
+ printType,
54
+ unionOf,
55
+ unwrap,
56
+ widenType,
57
+ type ParameterInfo,
58
+ type PropertyInfo,
59
+ type Type,
60
+ type TypeParameterInfo,
61
+ } from "./type-model.js";
62
+ import { inferTypeOfExpression } from "./type-infer.js";
63
+
64
+ type TypeDeclaration =
65
+ | { readonly kind: "interface"; readonly node: InterfaceDeclaration }
66
+ | { readonly kind: "alias"; readonly node: TypeAliasDeclaration }
67
+ | { readonly kind: "class"; readonly node: ClassDeclaration };
68
+
69
+ /** Resolve type syntax and symbol annotations into the small model above. */
70
+ export class TypeResolver {
71
+ private readonly declarations = new Map<string, TypeDeclaration | undefined>();
72
+ private readonly symbolTypes = new Map<number, Type>();
73
+ private readonly resolvingSymbols = new Set<number>();
74
+ private readonly resolvingAliases = new Set<string>();
75
+ private readonly resolvingInterfaces = new Set<string>();
76
+ private readonly resolvingClasses = new Set<number>();
77
+ private readonly classCache = new Map<string, Type>();
78
+ private readonly dynamicClasses = new Set<number>();
79
+ private readonly symbolsById = new Map<number, SymbolInfo>();
80
+
81
+ constructor(
82
+ readonly sourceFile: SourceFileNode,
83
+ readonly binding: BindResult,
84
+ ) {
85
+ this.collectDeclarations();
86
+ this.collectDynamicClasses();
87
+ for (const symbol of binding.symbolOfDeclaration.values()) {
88
+ if (symbol) this.symbolsById.set(symbol.id, symbol);
89
+ }
90
+ }
91
+
92
+ typeOfSymbolId(id: number): Type | undefined {
93
+ const symbol = this.symbolsById.get(id);
94
+ return symbol ? this.typeOfSymbol(symbol) : undefined;
95
+ }
96
+
97
+ /** The instance type of the class an `instanceof` right-hand side names. */
98
+ classInstanceFromIdentifier(expression: Expression): Type | undefined {
99
+ const info = this.classOfIdentifier(expression);
100
+ return info ? this.classType(info.node as ClassDeclaration) : undefined;
101
+ }
102
+
103
+ // -- declarations --------------------------------------------------------
104
+
105
+ private collectDeclarations(): void {
106
+ walk(this.sourceFile, (node) => {
107
+ if (node.kind === SyntaxKind.InterfaceDeclaration) {
108
+ this.addDeclaration((node as InterfaceDeclaration).name.text, { kind: "interface", node: node as InterfaceDeclaration });
109
+ } else if (node.kind === SyntaxKind.TypeAliasDeclaration) {
110
+ this.addDeclaration((node as TypeAliasDeclaration).name.text, { kind: "alias", node: node as TypeAliasDeclaration });
111
+ } else if (node.kind === SyntaxKind.ClassDeclaration) {
112
+ const declaration = node as ClassDeclaration;
113
+ if (declaration.name) this.addDeclaration(declaration.name.text, { kind: "class", node: declaration });
114
+ }
115
+ });
116
+ }
117
+
118
+ private addDeclaration(name: string, declaration: TypeDeclaration): void {
119
+ // A duplicate means bundling or shadowing made the name ambiguous; treating
120
+ // it as `any` is the only safe answer without a full binder for types.
121
+ this.declarations.set(name, this.declarations.has(name) ? undefined : declaration);
122
+ }
123
+
124
+ private collectDynamicClasses(): void {
125
+ walk(this.sourceFile, (node) => {
126
+ if (node.kind === SyntaxKind.BinaryExpression) {
127
+ const binary = node as BinaryExpression;
128
+ const owner = this.classOfPrototypeExpression(binary.left);
129
+ if (owner) this.dynamicClasses.add(owner.id);
130
+ if (binary.left.kind === SyntaxKind.PropertyAccessExpression) {
131
+ const nested = this.classOfPrototypeExpression((binary.left as unknown as { expression: Expression }).expression);
132
+ if (nested) this.dynamicClasses.add(nested.id);
133
+ }
134
+ } else if (node.kind === SyntaxKind.CallExpression) {
135
+ const call = node as CallExpression;
136
+ const first = call.arguments[0];
137
+ const owner = first ? this.classOfPrototypeExpression(first) : undefined;
138
+ if (owner) this.dynamicClasses.add(owner.id);
139
+ }
140
+ });
141
+ // A constructor that assigns `this[key]` or `Object.assign(this, ...)`
142
+ // creates members no static shape can enumerate.
143
+ for (const info of this.binding.classes) {
144
+ if (this.classHasDynamicMembers(info.node)) this.dynamicClasses.add(info.id);
145
+ }
146
+ }
147
+
148
+ private classHasDynamicMembers(node: Node): boolean {
149
+ let dynamic = false;
150
+ walk(node, (child) => {
151
+ if (dynamic) return;
152
+ if (child.kind === SyntaxKind.BinaryExpression) {
153
+ const binary = child as BinaryExpression;
154
+ if (!ASSIGNMENT_OPERATORS.has(binary.operator)) return;
155
+ if (binary.left.kind !== SyntaxKind.ElementAccessExpression) return;
156
+ if (unwrap((binary.left as unknown as { expression: Expression }).expression).kind === SyntaxKind.ThisKeyword) dynamic = true;
157
+ return;
158
+ }
159
+ if (child.kind !== SyntaxKind.CallExpression) return;
160
+ const call = child as CallExpression;
161
+ const callee = call.expression;
162
+ if (callee.kind !== SyntaxKind.PropertyAccessExpression) return;
163
+ const access = callee as PropertyAccessExpression;
164
+ if (access.expression.kind !== SyntaxKind.Identifier || access.name.kind !== SyntaxKind.Identifier) return;
165
+ const owner = (access.expression as Identifier).text;
166
+ const method = (access.name as Identifier).text;
167
+ const mutates = owner === "Object" && (method === "assign" || method === "defineProperty" || method === "defineProperties");
168
+ const first = call.arguments[0];
169
+ if (mutates && first && unwrap(first).kind === SyntaxKind.ThisKeyword) dynamic = true;
170
+ });
171
+ return dynamic;
172
+ }
173
+
174
+ /** `this.x = ...` members, typed `any` because the assignment is the only source. */
175
+ private collectThisProperties(node: Node, properties: Map<string, PropertyInfo>): void {
176
+ walk(node, (child) => {
177
+ if (child.kind !== SyntaxKind.BinaryExpression) return;
178
+ const binary = child as BinaryExpression;
179
+ if (!ASSIGNMENT_OPERATORS.has(binary.operator)) return;
180
+ if (binary.left.kind !== SyntaxKind.PropertyAccessExpression) return;
181
+ const access = binary.left as PropertyAccessExpression;
182
+ if (unwrap(access.expression).kind !== SyntaxKind.ThisKeyword) return;
183
+ if (access.name.kind !== SyntaxKind.Identifier) return;
184
+ const name = (access.name as Identifier).text;
185
+ if (!properties.has(name)) properties.set(name, { type: ANY, optional: false });
186
+ });
187
+ }
188
+
189
+ private classOfPrototypeExpression(expression: Expression): ClassInfo | undefined {
190
+ const unwrapped = unwrap(expression);
191
+ if (unwrapped.kind !== SyntaxKind.PropertyAccessExpression) return undefined;
192
+ const access = unwrapped as { expression: Expression; name: Identifier };
193
+ if (access.name.kind !== SyntaxKind.Identifier || access.name.text !== "prototype") return undefined;
194
+ return this.classOfIdentifier(access.expression);
195
+ }
196
+
197
+ private classOfIdentifier(expression: Expression): ClassInfo | undefined {
198
+ const identifier = unwrap(expression);
199
+ if (identifier.kind !== SyntaxKind.Identifier) return undefined;
200
+ const symbol = this.binding.symbolOfIdentifier.get(identifier as Identifier);
201
+ if (!symbol || symbol.kind !== SymbolKind.Class || symbol.declarations.length !== 1) return undefined;
202
+ return this.binding.classOfNode.get(symbol.declarations[0]!);
203
+ }
204
+
205
+ // -- type syntax ---------------------------------------------------------
206
+
207
+ resolve(node: TypeNode | undefined, typeParams: ReadonlyMap<string, Type> = new Map()): Type {
208
+ if (!node) return ANY;
209
+ switch (node.kind) {
210
+ case SyntaxKind.AnyKeywordType:
211
+ case SyntaxKind.UnknownKeywordType:
212
+ return ANY;
213
+ case SyntaxKind.NumberKeywordType:
214
+ return primitive("number");
215
+ case SyntaxKind.StringKeywordType:
216
+ return primitive("string");
217
+ case SyntaxKind.BooleanKeywordType:
218
+ return primitive("boolean");
219
+ case SyntaxKind.BigIntKeywordType:
220
+ return primitive("bigint");
221
+ case SyntaxKind.SymbolKeywordType:
222
+ return primitive("symbol");
223
+ case SyntaxKind.NullKeywordType:
224
+ return primitive("null");
225
+ case SyntaxKind.UndefinedKeywordType:
226
+ return primitive("undefined");
227
+ case SyntaxKind.VoidKeywordType:
228
+ return primitive("void");
229
+ case SyntaxKind.NeverKeywordType:
230
+ return ANY;
231
+ case SyntaxKind.ObjectKeywordType:
232
+ case SyntaxKind.ThisKeywordType:
233
+ return ANY;
234
+ case SyntaxKind.LiteralType: {
235
+ const literal = (node as LiteralTypeNode).literal;
236
+ if (literal.kind === SyntaxKind.StringLiteral) return { kind: "literal", value: (literal as { value: string }).value, primitive: "string" };
237
+ if (literal.kind === SyntaxKind.NumericLiteral) return { kind: "literal", value: (literal as { value: number }).value, primitive: "number" };
238
+ if (literal.kind === SyntaxKind.TrueKeyword || literal.kind === SyntaxKind.FalseKeyword) {
239
+ return { kind: "literal", value: literal.kind === SyntaxKind.TrueKeyword, primitive: "boolean" };
240
+ }
241
+ return primitive("null");
242
+ }
243
+ case SyntaxKind.ArrayType:
244
+ return { kind: "array", element: this.resolve((node as ArrayTypeNode).elementType, typeParams) };
245
+ case SyntaxKind.TupleType:
246
+ return { kind: "tuple", elements: (node as TupleTypeNode).elements.map((element) => this.resolve(element, typeParams)) };
247
+ case SyntaxKind.UnionType:
248
+ return unionOf((node as UnionTypeNode).types.map((element) => this.resolve(element, typeParams)));
249
+ case SyntaxKind.IntersectionType: {
250
+ const parts = (node as IntersectionTypeNode).types.map((element) => this.resolve(element, typeParams));
251
+ const properties = new Map<string, PropertyInfo>();
252
+ for (const part of parts) {
253
+ if (part.kind !== "object") return ANY;
254
+ for (const [name, property] of part.properties) properties.set(name, property);
255
+ }
256
+ return { kind: "object", properties, closed: true };
257
+ }
258
+ case SyntaxKind.ParenthesizedType:
259
+ return this.resolve((node as ParenthesizedTypeNode).type, typeParams);
260
+ case SyntaxKind.OptionalType:
261
+ case SyntaxKind.RestType:
262
+ return this.resolve((node as unknown as { type: TypeNode }).type, typeParams);
263
+ case SyntaxKind.TypeOperator:
264
+ return this.resolve((node as TypeOperatorNode).type, typeParams);
265
+ case SyntaxKind.FunctionType:
266
+ return this.functionType(node as FunctionTypeNode, typeParams);
267
+ case SyntaxKind.TypeLiteral:
268
+ return this.typeLiteral(node as TypeLiteralNode, typeParams);
269
+ case SyntaxKind.TypeReference:
270
+ return this.typeReference(node as TypeReference, typeParams);
271
+ default:
272
+ return ANY;
273
+ }
274
+ }
275
+
276
+ private functionType(node: FunctionTypeNode, outer: ReadonlyMap<string, Type>): Type {
277
+ const typeParams = new Map(outer);
278
+ const declared: TypeParameterInfo[] = [];
279
+ for (const parameter of node.typeParameters) {
280
+ const name = parameter.name.text;
281
+ typeParams.set(name, { kind: "typeParam", name });
282
+ declared.push({ name });
283
+ }
284
+ return {
285
+ kind: "function",
286
+ parameters: node.parameters.filter((parameter) => !isThisParameter(parameter)).map((parameter) => this.parameterInfo(parameter, typeParams)),
287
+ returnType: this.resolve(node.returnType, typeParams),
288
+ typeParameters: declared,
289
+ };
290
+ }
291
+
292
+ parameterInfo(parameter: Parameter, typeParams: ReadonlyMap<string, Type>): ParameterInfo {
293
+ return {
294
+ type: this.resolve(parameter.type, typeParams),
295
+ optional: parameter.questionToken || parameter.initializer !== undefined || parameter.dotDotDotToken,
296
+ rest: parameter.dotDotDotToken,
297
+ };
298
+ }
299
+
300
+ private typeLiteral(node: TypeLiteralNode, typeParams: ReadonlyMap<string, Type>): Type {
301
+ const properties = new Map<string, PropertyInfo>();
302
+ let index: Type | undefined;
303
+ for (const member of node.members) {
304
+ if (member.kind === SyntaxKind.PropertySignature) {
305
+ const property = member as PropertySignature;
306
+ this.addProperty(properties, classMemberName(property.name), this.resolve(property.type, typeParams), property.questionToken);
307
+ } else if (member.kind === SyntaxKind.MethodSignature) {
308
+ const method = member as MethodSignature;
309
+ properties.set(classMemberName(method.name), {
310
+ type: {
311
+ kind: "function",
312
+ parameters: method.parameters.filter((parameter) => !isThisParameter(parameter)).map((parameter) => this.parameterInfo(parameter, typeParams)),
313
+ returnType: this.resolve(method.returnType, typeParams),
314
+ typeParameters: method.typeParameters.map((parameter) => ({ name: parameter.name.text })),
315
+ },
316
+ optional: method.questionToken,
317
+ });
318
+ } else if (member.kind === SyntaxKind.IndexSignature) {
319
+ const parameter = (member as unknown as { parameters: Parameter[] }).parameters[0];
320
+ index = this.resolve(parameter?.type, typeParams);
321
+ }
322
+ }
323
+ return index ? { kind: "object", properties, index, closed: false } : { kind: "object", properties, closed: true };
324
+ }
325
+
326
+ private addProperty(properties: Map<string, PropertyInfo>, name: string, type: Type, optional: boolean): void {
327
+ if (!name) return;
328
+ properties.set(name, { type, optional });
329
+ }
330
+
331
+ private typeReference(node: TypeReference, typeParams: ReadonlyMap<string, Type>): Type {
332
+ const name = node.typeName.kind === SyntaxKind.Identifier ? (node.typeName as Identifier).text : "";
333
+ if (!name) return ANY;
334
+ if (typeParams.has(name)) return typeParams.get(name)!;
335
+ const builtin = this.builtinType(name, node.typeArguments.map((argument) => this.resolve(argument, typeParams)));
336
+ if (builtin) return builtin;
337
+ const declaration = this.declarations.get(name);
338
+ if (!declaration) return { kind: "typeParam", name };
339
+ if (declaration.kind === "interface") return this.interfaceType(declaration.node, node.typeArguments.map((argument) => this.resolve(argument, typeParams)));
340
+ if (declaration.kind === "alias") return this.aliasType(declaration.node, node.typeArguments.map((argument) => this.resolve(argument, typeParams)));
341
+ return this.classType(declaration.node, node.typeArguments.map((argument) => this.resolve(argument, typeParams)));
342
+ }
343
+
344
+ private builtinType(name: string, args: readonly Type[]): Type | undefined {
345
+ switch (name) {
346
+ case "Array":
347
+ case "ReadonlyArray":
348
+ return { kind: "array", element: args[0] ?? ANY };
349
+ case "String":
350
+ return primitive("string");
351
+ case "Number":
352
+ return primitive("number");
353
+ case "Boolean":
354
+ return primitive("boolean");
355
+ case "Record":
356
+ return { kind: "object", properties: new Map(), index: args[1] ?? ANY, closed: false };
357
+ case "Partial":
358
+ case "Required":
359
+ case "Readonly":
360
+ case "Pick":
361
+ case "Omit":
362
+ case "Exclude":
363
+ case "Extract":
364
+ case "NonNullable":
365
+ case "ReturnType":
366
+ case "Parameters":
367
+ case "Promise":
368
+ case "PromiseLike":
369
+ case "Map":
370
+ case "Set":
371
+ case "WeakMap":
372
+ case "WeakSet":
373
+ case "Date":
374
+ case "RegExp":
375
+ case "Error":
376
+ case "Function":
377
+ case "Object":
378
+ return ANY;
379
+ default:
380
+ return undefined;
381
+ }
382
+ }
383
+
384
+ private bindTypeParameters(declaration: { typeParameters: readonly { name: Identifier }[] }, args: readonly Type[]): Map<string, Type> {
385
+ const params = new Map<string, Type>();
386
+ declaration.typeParameters.forEach((parameter, index) => {
387
+ params.set(parameter.name.text, args[index] ?? { kind: "typeParam", name: parameter.name.text });
388
+ });
389
+ return params;
390
+ }
391
+
392
+ private aliasType(node: TypeAliasDeclaration, args: readonly Type[]): Type {
393
+ const name = node.name.text;
394
+ if (this.resolvingAliases.has(name)) return ANY;
395
+ this.resolvingAliases.add(name);
396
+ const params = this.bindTypeParameters(node, args);
397
+ const resolved = this.resolve(node.type, params);
398
+ this.resolvingAliases.delete(name);
399
+ return resolved;
400
+ }
401
+
402
+ private interfaceType(node: InterfaceDeclaration, args: readonly Type[] = []): Type {
403
+ const name = node.name.text;
404
+ if (this.resolvingInterfaces.has(name)) return ANY;
405
+ this.resolvingInterfaces.add(name);
406
+ const result = this.buildInterfaceType(node, args);
407
+ this.resolvingInterfaces.delete(name);
408
+ return result;
409
+ }
410
+
411
+ private buildInterfaceType(node: InterfaceDeclaration, args: readonly Type[] = []): Type {
412
+ const params = this.bindTypeParameters(node, args);
413
+ const properties = new Map<string, PropertyInfo>();
414
+ let index: Type | undefined;
415
+ for (const clause of node.heritage) {
416
+ if (clause.token !== "extends") continue;
417
+ const parent = clause.types[0]?.expression;
418
+ if (!parent || parent.kind !== SyntaxKind.Identifier) return ANY;
419
+ const parentDecl = this.declarations.get((parent as Identifier).text);
420
+ if (!parentDecl || parentDecl.kind !== "interface") return ANY;
421
+ const parentType = this.interfaceType(parentDecl.node, []);
422
+ if (parentType.kind !== "object") return ANY;
423
+ for (const [name, property] of parentType.properties) properties.set(name, property);
424
+ }
425
+ for (const member of node.members) {
426
+ if (member.kind === SyntaxKind.PropertySignature) {
427
+ const property = member as PropertySignature;
428
+ this.addProperty(properties, classMemberName(property.name), this.resolve(property.type, params), property.questionToken);
429
+ } else if (member.kind === SyntaxKind.MethodSignature) {
430
+ const method = member as MethodSignature;
431
+ properties.set(classMemberName(method.name), {
432
+ type: {
433
+ kind: "function",
434
+ parameters: method.parameters.filter((parameter) => !isThisParameter(parameter)).map((parameter) => this.parameterInfo(parameter, params)),
435
+ returnType: this.resolve(method.returnType, params),
436
+ typeParameters: method.typeParameters.map((parameter) => ({ name: parameter.name.text })),
437
+ },
438
+ optional: method.questionToken,
439
+ });
440
+ } else if (member.kind === SyntaxKind.IndexSignature) {
441
+ const parameter = (member as unknown as { parameters: Parameter[] }).parameters[0];
442
+ index = this.resolve(parameter?.type, params);
443
+ }
444
+ }
445
+ return index ? { kind: "object", properties, index, closed: false, label: node.name.text } : { kind: "object", properties, closed: true, label: node.name.text };
446
+ }
447
+
448
+ private classType(node: ClassDeclaration, args: readonly Type[] = []): Type {
449
+ const info = this.binding.classOfNode.get(node);
450
+ if (!info) return ANY;
451
+ if (this.resolvingClasses.has(info.id)) return ANY;
452
+ this.resolvingClasses.add(info.id);
453
+ const result = this.buildClassType(info, node, args);
454
+ this.resolvingClasses.delete(info.id);
455
+ return result;
456
+ }
457
+
458
+ private buildClassType(info: ClassInfo, node: ClassDeclaration, args: readonly Type[]): Type {
459
+ if (this.dynamicClasses.has(info.id)) return ANY;
460
+ if (info.parentExpression && !info.parent) return ANY;
461
+ const cacheKey = `${info.id}<${args.map((argument) => printType(argument)).join(",")}>`;
462
+ const cached = this.classCache.get(cacheKey);
463
+ if (cached) return cached;
464
+ const params = this.bindTypeParameters(node, args);
465
+ const properties = new Map<string, PropertyInfo>();
466
+ if (info.parent) {
467
+ const parentType = this.classType(info.parent.node as ClassDeclaration, []);
468
+ if (parentType.kind !== "object") return ANY;
469
+ for (const [name, property] of parentType.properties) properties.set(name, property);
470
+ }
471
+ for (const member of node.members) {
472
+ if (member.kind === SyntaxKind.PropertyDeclaration) {
473
+ const property = member as PropertyDeclaration;
474
+ if (property.modifiers.some((modifier) => modifier.modifierKind === ModifierKind.Static)) continue;
475
+ const name = classMemberName(property.name);
476
+ const type = property.type ? this.resolve(property.type, params) : property.initializer ? widenType(this.inferExpression(property.initializer)) : ANY;
477
+ this.addProperty(properties, name, type, property.questionToken);
478
+ } else if (member.kind === SyntaxKind.MethodDeclaration) {
479
+ const method = member as MethodDeclaration;
480
+ if (method.modifiers.some((modifier) => modifier.modifierKind === ModifierKind.Static)) continue;
481
+ const name = classMemberName(method.name);
482
+ if (method.accessor === "get") {
483
+ properties.set(name, { type: this.resolve(method.returnType, params), optional: false });
484
+ } else if (method.accessor === "set") {
485
+ if (!properties.has(name)) {
486
+ const parameter = method.parameters[0];
487
+ properties.set(name, { type: parameter ? this.resolve(parameter.type, params) : ANY, optional: false });
488
+ }
489
+ } else {
490
+ properties.set(name, {
491
+ type: {
492
+ kind: "function",
493
+ parameters: method.parameters.filter((parameter) => !isThisParameter(parameter)).map((parameter) => this.parameterInfo(parameter, params)),
494
+ returnType: this.resolve(method.returnType, params),
495
+ typeParameters: [],
496
+ },
497
+ optional: method.optional,
498
+ });
499
+ }
500
+ }
501
+ }
502
+ this.collectThisProperties(node, properties);
503
+ if (info.ctor) {
504
+ const parameters = (info.ctor.node as unknown as { parameters?: Parameter[] }).parameters ?? [];
505
+ for (const parameter of parameters) {
506
+ if (!isParameterProperty(parameter)) continue;
507
+ if (parameter.name.kind !== SyntaxKind.Identifier) continue;
508
+ properties.set(parameter.name.text, { type: this.resolve(parameter.type, params), optional: parameter.questionToken });
509
+ }
510
+ }
511
+ const type: Type = { kind: "object", properties, closed: true, label: `class '${node.name?.text ?? info.name}'` };
512
+ this.classCache.set(cacheKey, type);
513
+ return type;
514
+ }
515
+
516
+ /** The instance shape of a class declaration (`this` inside its methods). */
517
+ classInstanceType(node: ClassDeclaration): Type {
518
+ return this.classType(node);
519
+ }
520
+
521
+ // -- symbols -------------------------------------------------------------
522
+
523
+ setSymbolType(symbolId: number, type: Type): void {
524
+ this.symbolTypes.set(symbolId, type);
525
+ }
526
+
527
+ typeOfSymbol(symbol: SymbolInfo): Type {
528
+ const cached = this.symbolTypes.get(symbol.id);
529
+ if (cached) return cached;
530
+ if (this.resolvingSymbols.has(symbol.id)) return ANY;
531
+ this.resolvingSymbols.add(symbol.id);
532
+ const type = this.computeSymbolType(symbol);
533
+ this.resolvingSymbols.delete(symbol.id);
534
+ this.symbolTypes.set(symbol.id, type);
535
+ return type;
536
+ }
537
+
538
+ private computeSymbolType(symbol: SymbolInfo): Type {
539
+ const declaration = symbol.declarations[0];
540
+ if (!declaration) return ANY;
541
+ if (symbol.kind === SymbolKind.Import) return ANY;
542
+ if (symbol.kind === SymbolKind.Class) return ANY;
543
+ if (symbol.kind === SymbolKind.Function && declaration.kind === SyntaxKind.FunctionDeclaration) {
544
+ // Overloads: no single signature is authoritative.
545
+ if (symbol.declarations.length > 1) return ANY;
546
+ const fn = declaration as FunctionDeclaration;
547
+ const typeParams = new Map<string, Type>();
548
+ const declared: TypeParameterInfo[] = [];
549
+ for (const parameter of fn.typeParameters) {
550
+ typeParams.set(parameter.name.text, { kind: "typeParam", name: parameter.name.text });
551
+ declared.push({ name: parameter.name.text });
552
+ }
553
+ return {
554
+ kind: "function",
555
+ parameters: fn.parameters.filter((parameter) => !isThisParameter(parameter)).map((parameter) => this.parameterInfo(parameter, typeParams)),
556
+ returnType: this.resolve(fn.returnType, typeParams),
557
+ typeParameters: declared,
558
+ label: symbol.name,
559
+ };
560
+ }
561
+ if (declaration.kind === SyntaxKind.VariableDeclaration) {
562
+ const variable = declaration as VariableDeclaration;
563
+ if (variable.type) return this.resolve(variable.type);
564
+ if (variable.initializer) return this.inferExpression(variable.initializer);
565
+ return ANY;
566
+ }
567
+ if (declaration.kind === SyntaxKind.Parameter) {
568
+ const parameter = declaration as Parameter;
569
+ return this.resolve(parameter.type);
570
+ }
571
+ return ANY;
572
+ }
573
+
574
+
575
+ inferExpression(expression: Expression, lookup?: (symbol: SymbolInfo) => Type | undefined): Type {
576
+ return inferTypeOfExpression(this, expression, lookup);
577
+ }
578
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Type-inference helpers used by the checker: generic substitution, array
3
+ * element lookup and property lookup. They live on a prototype mixin so the
4
+ * main walker stays within the repository's per-file line budget.
5
+ */
6
+
7
+ import { SyntaxKind, type CallExpression } from "../ast/nodes.js";
8
+ import { ANY, OBJECT_PROTOTYPE_NAMES, unionMembers, unionOf, type Type } from "./type-model.js";
9
+ import type { Flow } from "./flow.js";
10
+ import type { CheckContext, TypeChecker } from "./typecheck.js";
11
+
12
+ export interface TypeCheckerMembers {
13
+ inferTypeArguments(this: TypeChecker, callee: Extract<Type, { kind: "function" }>, call: CallExpression, flow: Flow, _context: CheckContext): Map<string, Type>;
14
+ inferFromPattern(this: TypeChecker, pattern: Type, actual: Type, substitutions: Map<string, Type>, names: ReadonlySet<string>): void;
15
+ substitute(this: TypeChecker, type: Type, substitutions: ReadonlyMap<string, Type>): Type;
16
+ lookupProperty(this: TypeChecker, base: Type, name: string): { type: Type; missing: boolean };
17
+ lookupIn(this: TypeChecker, type: Type, name: string): Type | undefined;
18
+ }
19
+
20
+ export const typeCheckerMembers: TypeCheckerMembers = {
21
+ inferTypeArguments(callee: Extract<Type, { kind: "function" }>, call: CallExpression, flow: Flow, _context: CheckContext): Map<string, Type> {
22
+ const substitutions = new Map<string, Type>();
23
+ const names = new Set(callee.typeParameters.map((parameter) => parameter.name));
24
+ callee.typeParameters.forEach((parameter, index) => {
25
+ const argument = call.typeArguments[index];
26
+ if (argument) substitutions.set(parameter.name, this.resolver.resolve(argument));
27
+ });
28
+ for (let index = 0; index < call.arguments.length; index++) {
29
+ const argument = call.arguments[index]!;
30
+ if (argument.kind === SyntaxKind.SpreadElement) continue;
31
+ const parameter = callee.parameters[Math.min(index, callee.parameters.length - 1)];
32
+ if (!parameter) continue;
33
+ const argumentType = this.resolver.inferExpression(argument, (symbol) => this.lookup(symbol.id, flow));
34
+ const pattern = parameter.rest && parameter.type.kind === "array" ? parameter.type.element : parameter.type;
35
+ this.inferFromPattern(pattern, argumentType, substitutions, names);
36
+ }
37
+ return substitutions;
38
+ },
39
+
40
+ inferFromPattern(pattern: Type, actual: Type, substitutions: Map<string, Type>, names: ReadonlySet<string>): void {
41
+ if (pattern.kind === "typeParam" && names.has(pattern.name) && !substitutions.has(pattern.name)) {
42
+ substitutions.set(pattern.name, actual);
43
+ return;
44
+ }
45
+ if (pattern.kind === "array" && actual.kind === "array") {
46
+ this.inferFromPattern(pattern.element, actual.element, substitutions, names);
47
+ return;
48
+ }
49
+ if (pattern.kind === "union") {
50
+ for (const member of pattern.members) this.inferFromPattern(member, actual, substitutions, names);
51
+ }
52
+ },
53
+
54
+ substitute(type: Type, substitutions: ReadonlyMap<string, Type>): Type {
55
+ if (substitutions.size === 0) return type;
56
+ switch (type.kind) {
57
+ case "typeParam":
58
+ return substitutions.get(type.name) ?? type;
59
+ case "array":
60
+ return { kind: "array", element: this.substitute(type.element, substitutions) };
61
+ case "tuple":
62
+ return { kind: "tuple", elements: type.elements.map((element) => this.substitute(element, substitutions)) };
63
+ case "union":
64
+ return unionOf(type.members.map((member) => this.substitute(member, substitutions)));
65
+ case "object": {
66
+ const properties = new Map<string, { type: Type; optional: boolean }>();
67
+ for (const [name, property] of type.properties) properties.set(name, { type: this.substitute(property.type, substitutions), optional: property.optional });
68
+ return { kind: "object", properties, ...(type.index ? { index: this.substitute(type.index, substitutions) } : {}), closed: type.closed, ...(type.label ? { label: type.label } : {}) };
69
+ }
70
+ case "function":
71
+ return {
72
+ kind: "function",
73
+ parameters: type.parameters.map((parameter) => ({ ...parameter, type: this.substitute(parameter.type, substitutions) })),
74
+ returnType: this.substitute(type.returnType, substitutions),
75
+ typeParameters: type.typeParameters,
76
+ ...(type.label ? { label: type.label } : {}),
77
+ };
78
+ default:
79
+ return type;
80
+ }
81
+ },
82
+
83
+ lookupProperty(base: Type, name: string): { type: Type; missing: boolean } {
84
+ const members = unionMembers(base).filter(
85
+ (member) => !(member.kind === "primitive" && (member.name === "null" || member.name === "undefined")),
86
+ );
87
+ if (members.length === 0) return { type: ANY, missing: false };
88
+ const types: Type[] = [];
89
+ let missing = false;
90
+ for (const member of members) {
91
+ const found = this.lookupIn(member, name);
92
+ if (found === undefined) missing = true;
93
+ else types.push(found);
94
+ }
95
+ if (missing) return { type: ANY, missing: true };
96
+ return { type: unionOf(types), missing: false };
97
+ },
98
+
99
+ lookupIn(type: Type, name: string): Type | undefined {
100
+ if (type.kind === "object") {
101
+ const property = type.properties.get(name);
102
+ if (property) return property.type;
103
+ if (type.index) return type.index;
104
+ if (OBJECT_PROTOTYPE_NAMES.has(name)) return ANY;
105
+ return type.closed ? undefined : ANY;
106
+ }
107
+ // Arrays, tuples, functions and primitives have members this model does
108
+ // not enumerate; a missing read there is never reported.
109
+ if (type.kind === "array" || type.kind === "tuple" || type.kind === "function") return ANY;
110
+ return ANY;
111
+ },
112
+ };