rooklang 0.3.3 → 0.4.0

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 (244) hide show
  1. package/README.md +34 -7
  2. package/README.ru.md +34 -7
  3. package/dist/ast/declarations.d.ts +39 -4
  4. package/dist/ast/expressions.d.ts +34 -8
  5. package/dist/ast/markup.d.ts +6 -1
  6. package/dist/ast/statements.d.ts +28 -2
  7. package/dist/auto-import.d.ts +11 -0
  8. package/dist/auto-import.js +81 -0
  9. package/dist/breaks.d.ts +6 -0
  10. package/dist/breaks.js +36 -0
  11. package/dist/build.d.ts +7 -0
  12. package/dist/build.js +23 -2
  13. package/dist/checker/array-literals.d.ts +6 -0
  14. package/dist/checker/array-literals.js +59 -0
  15. package/dist/checker/assignments.d.ts +4 -0
  16. package/dist/checker/assignments.js +23 -3
  17. package/dist/checker/base.d.ts +8 -2
  18. package/dist/checker/base.js +7 -7
  19. package/dist/checker/builtins.js +22 -8
  20. package/dist/checker/bytes.d.ts +2 -2
  21. package/dist/checker/bytes.js +2 -2
  22. package/dist/checker/calls.js +3 -0
  23. package/dist/checker/channel-objects.d.ts +3 -0
  24. package/dist/checker/channel-objects.js +40 -0
  25. package/dist/checker/channels.d.ts +1 -0
  26. package/dist/checker/channels.js +17 -4
  27. package/dist/checker/checker.d.ts +1 -1
  28. package/dist/checker/checker.js +19 -0
  29. package/dist/checker/class-members.d.ts +22 -0
  30. package/dist/checker/class-members.js +105 -0
  31. package/dist/checker/classes.d.ts +5 -4
  32. package/dist/checker/classes.js +48 -13
  33. package/dist/checker/closing.d.ts +21 -0
  34. package/dist/checker/closing.js +164 -0
  35. package/dist/checker/completion.d.ts +3 -3
  36. package/dist/checker/completion.js +4 -3
  37. package/dist/checker/component-bindings.d.ts +7 -0
  38. package/dist/checker/component-bindings.js +45 -0
  39. package/dist/checker/component-values.js +10 -7
  40. package/dist/checker/components.d.ts +2 -2
  41. package/dist/checker/components.js +8 -3
  42. package/dist/checker/context.d.ts +41 -5
  43. package/dist/checker/context.js +23 -0
  44. package/dist/checker/control-flow.d.ts +11 -5
  45. package/dist/checker/control-flow.js +65 -40
  46. package/dist/checker/declarations.d.ts +2 -2
  47. package/dist/checker/declarations.js +4 -2
  48. package/dist/checker/decode.d.ts +8 -0
  49. package/dist/checker/decode.js +32 -0
  50. package/dist/checker/decorator-exports.js +4 -0
  51. package/dist/checker/decorator-includes.d.ts +9 -0
  52. package/dist/checker/decorator-includes.js +71 -0
  53. package/dist/checker/decorator-members.d.ts +8 -10
  54. package/dist/checker/decorator-members.js +28 -31
  55. package/dist/checker/decorator-wrappers.d.ts +1 -1
  56. package/dist/checker/decorator-wrappers.js +3 -2
  57. package/dist/checker/decorators.d.ts +14 -5
  58. package/dist/checker/decorators.js +52 -25
  59. package/dist/checker/expressions.d.ts +6 -2
  60. package/dist/checker/expressions.js +20 -40
  61. package/dist/checker/flow.d.ts +25 -3
  62. package/dist/checker/flow.js +56 -12
  63. package/dist/checker/functions.d.ts +2 -2
  64. package/dist/checker/functions.js +9 -5
  65. package/dist/checker/helpers.d.ts +5 -2
  66. package/dist/checker/helpers.js +16 -4
  67. package/dist/checker/imports.js +5 -2
  68. package/dist/checker/iterables.d.ts +3 -1
  69. package/dist/checker/iterables.js +13 -0
  70. package/dist/checker/iterators.d.ts +4 -1
  71. package/dist/checker/iterators.js +12 -4
  72. package/dist/checker/json.d.ts +5 -0
  73. package/dist/checker/json.js +92 -0
  74. package/dist/checker/loops.d.ts +2 -0
  75. package/dist/checker/loops.js +50 -5
  76. package/dist/checker/markup.js +14 -2
  77. package/dist/checker/members.js +10 -1
  78. package/dist/checker/operators.d.ts +2 -2
  79. package/dist/checker/operators.js +7 -5
  80. package/dist/checker/queues.d.ts +2 -2
  81. package/dist/checker/queues.js +2 -2
  82. package/dist/checker/resolution.js +5 -3
  83. package/dist/checker/shared-state.d.ts +11 -0
  84. package/dist/checker/shared-state.js +37 -0
  85. package/dist/checker/signals.d.ts +8 -0
  86. package/dist/checker/signals.js +48 -0
  87. package/dist/checker/statements.d.ts +5 -2
  88. package/dist/checker/statements.js +51 -22
  89. package/dist/checker/tagged-unions.d.ts +37 -0
  90. package/dist/checker/tagged-unions.js +132 -0
  91. package/dist/checker/topics.d.ts +6 -0
  92. package/dist/checker/topics.js +17 -0
  93. package/dist/checker/transfer.d.ts +3 -1
  94. package/dist/checker/transfer.js +25 -5
  95. package/dist/checker/try.d.ts +6 -0
  96. package/dist/checker/try.js +19 -0
  97. package/dist/checker/types/constructors.d.ts +1 -1
  98. package/dist/checker/types/constructors.js +2 -2
  99. package/dist/checker/types/generics.js +1 -1
  100. package/dist/checker/types/iterables.d.ts +13 -0
  101. package/dist/checker/types/iterables.js +52 -0
  102. package/dist/checker/types/model.d.ts +25 -0
  103. package/dist/checker/types/params.js +22 -3
  104. package/dist/checker/types/printing.js +4 -3
  105. package/dist/checker/types/relations.js +19 -25
  106. package/dist/checker/types/topic.d.ts +6 -0
  107. package/dist/checker/types/topic.js +23 -0
  108. package/dist/checker/types.d.ts +2 -0
  109. package/dist/checker/types.js +2 -0
  110. package/dist/checker/untyped-globals.d.ts +1 -0
  111. package/dist/checker/untyped-globals.js +3 -1
  112. package/dist/cli.js +18 -36
  113. package/dist/codegen/attributes.d.ts +4 -0
  114. package/dist/codegen/attributes.js +24 -2
  115. package/dist/codegen/calls.d.ts +23 -0
  116. package/dist/codegen/calls.js +88 -7
  117. package/dist/codegen/component-helpers.d.ts +6 -1
  118. package/dist/codegen/component-helpers.js +6 -1
  119. package/dist/codegen/component-props.d.ts +11 -2
  120. package/dist/codegen/component-props.js +50 -5
  121. package/dist/codegen/components.d.ts +6 -5
  122. package/dist/codegen/components.js +44 -21
  123. package/dist/codegen/control-flow.d.ts +2 -0
  124. package/dist/codegen/control-flow.js +23 -9
  125. package/dist/codegen/control-statements.d.ts +10 -2
  126. package/dist/codegen/control-statements.js +56 -19
  127. package/dist/codegen/creation.js +5 -2
  128. package/dist/codegen/decode.d.ts +14 -0
  129. package/dist/codegen/decode.js +93 -0
  130. package/dist/codegen/early-returns.d.ts +4 -3
  131. package/dist/codegen/early-returns.js +17 -5
  132. package/dist/codegen/emitter.d.ts +13 -41
  133. package/dist/codegen/emitter.js +27 -8
  134. package/dist/codegen/expressions.d.ts +9 -0
  135. package/dist/codegen/expressions.js +15 -26
  136. package/dist/codegen/functions.d.ts +11 -1
  137. package/dist/codegen/functions.js +31 -3
  138. package/dist/codegen/js.d.ts +2 -2
  139. package/dist/codegen/js.js +5 -2
  140. package/dist/codegen/live.d.ts +60 -0
  141. package/dist/codegen/live.js +29 -0
  142. package/dist/codegen/mutations.d.ts +7 -0
  143. package/dist/codegen/mutations.js +43 -2
  144. package/dist/codegen/options.d.ts +42 -0
  145. package/dist/codegen/options.js +1 -0
  146. package/dist/codegen/reactive.d.ts +4 -59
  147. package/dist/codegen/reactive.js +34 -72
  148. package/dist/codegen/runtime.d.ts +7 -3
  149. package/dist/codegen/runtime.js +33 -14
  150. package/dist/codegen/shared.d.ts +22 -0
  151. package/dist/codegen/shared.js +93 -0
  152. package/dist/codegen/simple-statements.d.ts +11 -5
  153. package/dist/codegen/simple-statements.js +40 -15
  154. package/dist/codegen/statements.d.ts +5 -0
  155. package/dist/codegen/statements.js +99 -4
  156. package/dist/codegen/try.d.ts +5 -0
  157. package/dist/codegen/try.js +40 -0
  158. package/dist/codegen/updates.d.ts +11 -3
  159. package/dist/codegen/updates.js +52 -4
  160. package/dist/codegen/web-components.d.ts +2 -2
  161. package/dist/codegen/web-components.js +2 -2
  162. package/dist/compile.d.ts +9 -0
  163. package/dist/compile.js +25 -8
  164. package/dist/completion.d.ts +4 -2
  165. package/dist/completion.js +10 -5
  166. package/dist/declarations.js +32 -6
  167. package/dist/decorator-copy.d.ts +1 -1
  168. package/dist/decorator-copy.js +6 -2
  169. package/dist/decorator-names.d.ts +14 -0
  170. package/dist/decorator-names.js +56 -0
  171. package/dist/decorators.d.ts +2 -3
  172. package/dist/decorators.js +36 -14
  173. package/dist/goroutines.d.ts +1 -1
  174. package/dist/goroutines.js +5 -4
  175. package/dist/index.d.ts +6 -2
  176. package/dist/index.js +14 -5
  177. package/dist/inline-packages.d.ts +2 -1
  178. package/dist/inline-packages.js +9 -4
  179. package/dist/lexer/base.d.ts +2 -0
  180. package/dist/lexer/base.js +6 -1
  181. package/dist/lexer/lexer.js +3 -0
  182. package/dist/lexer/literals.d.ts +5 -0
  183. package/dist/lexer/literals.js +51 -1
  184. package/dist/lexer/markup.js +3 -4
  185. package/dist/lexer/tables.js +2 -0
  186. package/dist/lexer/token.d.ts +4 -3
  187. package/dist/lexer/token.js +0 -2
  188. package/dist/loader.js +30 -8
  189. package/dist/names.d.ts +4 -0
  190. package/dist/names.js +29 -0
  191. package/dist/parser/base.d.ts +5 -0
  192. package/dist/parser/base.js +2 -1
  193. package/dist/parser/classes.d.ts +2 -0
  194. package/dist/parser/classes.js +88 -23
  195. package/dist/parser/control-flow.d.ts +8 -1
  196. package/dist/parser/control-flow.js +49 -26
  197. package/dist/parser/declarations.d.ts +5 -12
  198. package/dist/parser/declarations.js +14 -79
  199. package/dist/parser/decorators.d.ts +6 -2
  200. package/dist/parser/decorators.js +43 -19
  201. package/dist/parser/expressions.js +27 -3
  202. package/dist/parser/jumps.d.ts +11 -0
  203. package/dist/parser/jumps.js +77 -0
  204. package/dist/parser/literals.d.ts +9 -0
  205. package/dist/parser/literals.js +45 -6
  206. package/dist/parser/markup.js +1 -1
  207. package/dist/parser/params.d.ts +21 -0
  208. package/dist/parser/params.js +143 -0
  209. package/dist/parser/simple-statements.d.ts +2 -1
  210. package/dist/parser/simple-statements.js +38 -15
  211. package/dist/parser/statements.d.ts +5 -5
  212. package/dist/parser/statements.js +39 -35
  213. package/dist/parser/syntax.d.ts +11 -1
  214. package/dist/parser/syntax.js +16 -2
  215. package/dist/parser/types.js +8 -3
  216. package/dist/project-modules.d.ts +12 -0
  217. package/dist/project-modules.js +106 -0
  218. package/dist/run-file.d.ts +6 -0
  219. package/dist/run-file.js +111 -0
  220. package/dist/runtime/sources.js +13 -6
  221. package/dist/source-map.d.ts +17 -0
  222. package/dist/source-map.js +75 -0
  223. package/dist/ts-types.js +4 -1
  224. package/dist/typescript/convert-objects.d.ts +2 -1
  225. package/dist/typescript/convert-objects.js +18 -9
  226. package/dist/vite.js +12 -4
  227. package/dist/walk-types.js +1 -0
  228. package/dist/walk.d.ts +0 -2
  229. package/dist/walk.js +14 -31
  230. package/package.json +2 -2
  231. package/src/runtime/chan.js +15 -5
  232. package/src/runtime/channel.js +28 -0
  233. package/src/runtime/classes.js +32 -0
  234. package/src/runtime/core.js +10 -1
  235. package/src/runtime/decode.js +221 -0
  236. package/src/runtime/go.js +3 -0
  237. package/src/runtime/index.js +7 -0
  238. package/src/runtime/markup.js +27 -17
  239. package/src/runtime/select.js +14 -9
  240. package/src/runtime/shared.js +17 -0
  241. package/src/runtime/signals.js +59 -0
  242. package/src/runtime/threads.js +32 -5
  243. package/src/runtime/topic.js +118 -0
  244. package/src/runtime/try.js +45 -0
@@ -0,0 +1,48 @@
1
+ import { ChannelChecker, typeName } from "./channels.js";
2
+ import { isUntyped } from "./helpers.js";
3
+ import { isNullable, typeToString } from "./types.js";
4
+ // Signals and cancellation as channels (runtime/signals.js): `signal("SIGTERM")` gets the signals
5
+ // of the process, `done(request.signal)` the reason of an AbortSignal, and `abortSignal(stop)` is an
6
+ // AbortSignal that aborts when the channel gets a value or is closed. Their types are in
7
+ // builtins.ts; the names that signal() takes, the AbortSignal that done() takes and the type that
8
+ // abortSignal() gives, from the library if it has one, are here.
9
+ const SIGNAL = /^SIG[A-Z0-9]+$/;
10
+ export class SignalChecker extends ChannelChecker {
11
+ checkCall(node, callee, expected = null) {
12
+ const result = super.checkCall(node, callee, expected);
13
+ const { callee: name } = node;
14
+ if (name.kind !== 'Identifier' ||
15
+ this.lookupValue(name.name) !== this.globals.values.get(name.name)) {
16
+ return result;
17
+ }
18
+ switch (name.name) {
19
+ case 'signal':
20
+ this.checkSignalNames(node);
21
+ return result;
22
+ case 'done': {
23
+ const [argument] = node.arguments;
24
+ const type = argument && argument.kind !== 'SpreadElement' && this.checkedTypes.get(argument);
25
+ if (argument && type && !isUntyped(type) && typeName(type) !== 'AbortSignal') {
26
+ this.error(`done() takes an AbortSignal, such as request.signal, not ${typeToString(type)}${isNullable(type) ? ': check it for null first' : ''}`, argument);
27
+ }
28
+ return result;
29
+ }
30
+ // An AbortSignal of the library, for `fetch(url, { signal: abortSignal(stop) })`.
31
+ case 'abortSignal':
32
+ return this.options.library?.type('AbortSignal') ?? result;
33
+ default:
34
+ return result;
35
+ }
36
+ }
37
+ /** `signal("SIGINT", "SIGTERM")`: at least one name, and the names are those of signals. */
38
+ checkSignalNames(node) {
39
+ if (node.arguments.length === 0) {
40
+ this.error('signal() takes the signals to listen to, e.g. signal("SIGINT", "SIGTERM")', node);
41
+ }
42
+ for (const argument of node.arguments) {
43
+ if (argument.kind === 'StringLiteral' && !SIGNAL.test(argument.value)) {
44
+ this.error(`"${argument.value}" is not the name of a signal: they start with SIG, e.g. signal("SIGINT", "SIGTERM")`, argument);
45
+ }
46
+ }
47
+ }
48
+ }
@@ -12,10 +12,11 @@ export declare abstract class StatementChecker extends AssignmentChecker {
12
12
  */
13
13
  protected checkEffect(node: ast.EffectStatement): void;
14
14
  /**
15
- * `mount() { ... }`: a function that runs once the markup is in the document, like a handler.
16
- * It may return the function that cleans up, or nothing.
15
+ * `mount { ... }`: a function that runs once the markup is in the document, like a handler. It
16
+ * cleans up with `defer`, as an effect does, so it returns nothing.
17
17
  */
18
18
  protected checkMount(node: ast.MountStatement): void;
19
+ private checkDeferring;
19
20
  /**
20
21
  * `delete scores[name]` removes a key of an object used as a map, `{ [string]: T }`, or a field
21
22
  * that may be missing, of type `?T`; values of type `any` are not checked.
@@ -30,4 +31,6 @@ export declare abstract class StatementChecker extends AssignmentChecker {
30
31
  private linkError;
31
32
  /** The types of the variables of a declaration: the written one, or those of their values. */
32
33
  private initializerTypes;
34
+ /** `derived total number { ... }`: the block runs like the body of a closure and returns the value. */
35
+ private derivedBlockType;
33
36
  }
@@ -1,8 +1,10 @@
1
1
  import { guardedByError } from "./error-results.js";
2
2
  import { AssignmentChecker } from "./assignments.js";
3
+ import { placeOf } from "./context.js";
4
+ import { isTerminating } from "./flow.js";
3
5
  import { isUntyped, isNumeric, ownStatements } from "./helpers.js";
4
6
  import { func, hasZeroValue, isNullable, nonNull, nullable, typeToString, UNKNOWN, } from "./types.js";
5
- // Statements: declarations of variables, blocks, `mount()`, `effect`. Assignments and `return`
7
+ // Statements: declarations of variables, blocks, `mount`, `effect`. Assignments and `return`
6
8
  // are in assignments.ts.
7
9
  export class StatementChecker extends AssignmentChecker {
8
10
  checkStatement(node) {
@@ -92,30 +94,22 @@ export class StatementChecker extends AssignmentChecker {
92
94
  * a handler. It cleans up with `defer`, so it returns nothing.
93
95
  */
94
96
  checkEffect(node) {
95
- for (const statement of ownStatements(node.body)) {
96
- if (statement.kind === 'ReturnStatement' && statement.values.length > 0) {
97
- this.error('an effect returns nothing; clean up with defer, e.g. defer clearInterval(timer)', statement.values[0]);
98
- }
99
- }
100
- this.checkFunction([], func([], []), null, node.body, { closure: true });
97
+ this.checkDeferring(node.body, 'an effect');
101
98
  }
102
99
  /**
103
- * `mount() { ... }`: a function that runs once the markup is in the document, like a handler.
104
- * It may return the function that cleans up, or nothing.
100
+ * `mount { ... }`: a function that runs once the markup is in the document, like a handler. It
101
+ * cleans up with `defer`, as an effect does, so it returns nothing.
105
102
  */
106
103
  checkMount(node) {
107
- const results = this.checkFunction([], func([], []), null, node.body, { closure: true });
108
- if (results.length === 0)
109
- return;
110
- const result = results.length === 1 ? nonNull(results[0]) : null;
111
- const cleans = result !== null &&
112
- (isUntyped(result) ||
113
- result.kind === 'never' ||
114
- (result.kind === 'function' && result.required === 0));
115
- if (!cleans) {
116
- const returned = results.length === 1 ? typeToString(results[0]) : `${results.length} values`;
117
- this.error(`mount() returns the function that cleans up, e.g. return () => clearInterval(timer), not ${returned}`, { start: node.start, end: node.start + 'mount'.length });
104
+ this.checkDeferring(node.body, 'mount');
105
+ }
106
+ checkDeferring(body, what) {
107
+ for (const statement of ownStatements(body)) {
108
+ if (statement.kind === 'ReturnStatement' && statement.values.length > 0) {
109
+ this.error(`${what} returns nothing; clean up with defer, e.g. defer clearInterval(timer)`, statement.values[0]);
110
+ }
118
111
  }
112
+ this.checkFunction([], func([], []), null, body, { closure: true });
119
113
  }
120
114
  /**
121
115
  * `delete scores[name]` removes a key of an object used as a map, `{ [string]: T }`, or a field
@@ -133,6 +127,8 @@ export class StatementChecker extends AssignmentChecker {
133
127
  : null;
134
128
  if (target.kind === 'IndexExpression')
135
129
  this.checkValue(target.index);
130
+ // `delete user.boss` makes the field null again.
131
+ this.forget(target);
136
132
  if (isUntyped(object))
137
133
  return;
138
134
  if (object.kind === 'array') {
@@ -180,7 +176,7 @@ export class StatementChecker extends AssignmentChecker {
180
176
  binding.type = type;
181
177
  const value = values.length === names.length ? values[i] : undefined;
182
178
  if (value && isNullable(type) && !isNullable(this.typeOfChecked(value))) {
183
- this.flow.set(binding, nonNull(type));
179
+ this.flow.set(placeOf(binding), nonNull(type));
184
180
  }
185
181
  });
186
182
  if (node.keyword === 'const' && !declared)
@@ -207,7 +203,10 @@ export class StatementChecker extends AssignmentChecker {
207
203
  initializerTypes(node, declared) {
208
204
  const { names, values } = node;
209
205
  let types;
210
- if (values.length === 0) {
206
+ if (node.body) {
207
+ types = [this.derivedBlockType(names[0].name, node.body.body, declared)];
208
+ }
209
+ else if (values.length === 0) {
211
210
  if (declared && !hasZeroValue(declared) && declared.kind !== 'unknown') {
212
211
  const type = typeToString(declared);
213
212
  this.error(`${type} has no zero value: give "${names[0].name}" a value or make it nullable with ${typeToString(nullable(declared))}`, node.type ?? node);
@@ -228,4 +227,34 @@ export class StatementChecker extends AssignmentChecker {
228
227
  }
229
228
  return types;
230
229
  }
230
+ /** `derived total number { ... }`: the block runs like the body of a closure and returns the value. */
231
+ derivedBlockType(name, body, declared) {
232
+ const ends = isTerminating(body);
233
+ if (!ends) {
234
+ this.error(`derived "${name}" must return its value at the end of the block, e.g. return total`, {
235
+ start: body.end - 1,
236
+ end: body.end,
237
+ });
238
+ }
239
+ const saved = this.derivedBlock;
240
+ const results = declared ? [declared] : null;
241
+ this.derivedBlock = name;
242
+ try {
243
+ const types = this.checkFunction([], func([], results ?? []), results, body, {
244
+ closure: true,
245
+ });
246
+ if (types.length === 1)
247
+ return types[0];
248
+ // A block that does not end with a return is reported above.
249
+ if (ends) {
250
+ this.error(types.length === 0
251
+ ? `derived "${name}" returns no value: return it, e.g. return total`
252
+ : `derived "${name}" is one value, but its block returns ${types.length}`, body);
253
+ }
254
+ return UNKNOWN;
255
+ }
256
+ finally {
257
+ this.derivedBlock = saved;
258
+ }
259
+ }
231
260
  }
@@ -0,0 +1,37 @@
1
+ import type * as ast from '../ast.ts';
2
+ import { type Narrowing, type Place } from './context.ts';
3
+ import { ControlFlowChecker } from './control-flow.ts';
4
+ import { type LiteralType, type Member, type Type } from './types.ts';
5
+ type Value = LiteralType['value'];
6
+ export declare abstract class TaggedUnionChecker extends ControlFlowChecker {
7
+ /** The paths among the checked member expressions, with the types of their fields. */
8
+ private readonly paths;
9
+ /** The types of the fields of the paths, before narrowing: what their tags are found in. */
10
+ private readonly declared;
11
+ protected isTagOf(place: Place, field: string): boolean;
12
+ protected narrowable(node: ast.Expression): {
13
+ place: Place;
14
+ type: Type;
15
+ } | undefined;
16
+ /**
17
+ * The type of `object.field` read by `node`, which is `type` unless the expression is a path
18
+ * narrowed here. Getters and methods are not paths: they may give another value each time.
19
+ */
20
+ protected pathType(node: ast.MemberExpression, object: Type, type: Type): Type;
21
+ /** The field `name` of an object or a class instance, unless it is a method or an accessor. */
22
+ protected dataField(type: Type, name: string): Member | undefined;
23
+ /** The values of the tag `name` in each member of a union; `null` when `name` is not a tag. */
24
+ protected tagValues(members: readonly Type[], name: string): Value[][] | null;
25
+ /** `d.kind == "courier"` narrows `d` too: the members with this tag, or the others. */
26
+ protected narrow(node: ast.Expression, assumeTrue: boolean): Narrowing;
27
+ /** `switch d.kind`: in a case the members with its tags, in `default` those of no case. */
28
+ protected narrowCase(node: ast.SwitchStatement, switchCase: ast.SwitchCase): Narrowing;
29
+ private narrowTag;
30
+ /**
31
+ * `{ kind: "pickup", shop: "Main st." }` where one of several objects is expected: the member
32
+ * that a tag of the literal chooses, or `null` when no tag chooses one. A tag of no member is
33
+ * reported, and the literal then gets `UNKNOWN`, so that it is not reported again.
34
+ */
35
+ protected taggedMember(node: ast.ObjectLiteral, objects: readonly Type[], union: Type): Type | null;
36
+ }
37
+ export {};
@@ -0,0 +1,132 @@
1
+ import { placeOf } from "./context.js";
2
+ import { ControlFlowChecker } from "./control-flow.js";
3
+ import { literalValue, narrowByTag } from "./flow.js";
4
+ import { findClassMember, memberTypeOf, nonNull, typeToString, unionMembers, UNKNOWN, } from "./types.js";
5
+ export class TaggedUnionChecker extends ControlFlowChecker {
6
+ /** The paths among the checked member expressions, with the types of their fields. */
7
+ paths = new WeakMap();
8
+ /** The types of the fields of the paths, before narrowing: what their tags are found in. */
9
+ declared = new WeakMap();
10
+ isTagOf(place, field) {
11
+ const type = place.path === '' ? place.binding.type : this.declared.get(place);
12
+ return type != null && this.tagValues(unionMembers(nonNull(type)), field) !== null;
13
+ }
14
+ narrowable(node) {
15
+ if (node.kind === 'Identifier') {
16
+ const binding = this.narrowableBinding(node);
17
+ if (!binding?.type)
18
+ return undefined;
19
+ const place = placeOf(binding);
20
+ return { place, type: this.flow.get(place) ?? binding.type };
21
+ }
22
+ const path = node.kind === 'MemberExpression' ? this.paths.get(node) : undefined;
23
+ return path && { place: path.place, type: this.flow.get(path.place) ?? path.type };
24
+ }
25
+ /**
26
+ * The type of `object.field` read by `node`, which is `type` unless the expression is a path
27
+ * narrowed here. Getters and methods are not paths: they may give another value each time.
28
+ */
29
+ pathType(node, object, type) {
30
+ const parent = node.optional ? undefined : this.narrowable(node.object)?.place;
31
+ const { name } = node.property;
32
+ if (!parent || !unionMembers(object).every((member) => this.dataField(member, name))) {
33
+ this.paths.delete(node);
34
+ return type;
35
+ }
36
+ const place = placeOf(parent.binding, parent.path === '' ? name : `${parent.path}.${name}`);
37
+ this.paths.set(node, { place, type });
38
+ this.declared.set(place, type);
39
+ return this.flow.get(place) ?? type;
40
+ }
41
+ /** The field `name` of an object or a class instance, unless it is a method or an accessor. */
42
+ dataField(type, name) {
43
+ let member;
44
+ if (type.kind === 'object') {
45
+ member = type.members.get(name);
46
+ }
47
+ else if (type.kind === 'class') {
48
+ this.ensureClassResolved(type.info);
49
+ member = findClassMember(type.info, name, false);
50
+ if (member && type.args)
51
+ member = { ...member, type: memberTypeOf(type, member) };
52
+ }
53
+ return member && !member.method && member.accessor === undefined ? member : undefined;
54
+ }
55
+ /** The values of the tag `name` in each member of a union; `null` when `name` is not a tag. */
56
+ tagValues(members, name) {
57
+ if (members.length < 2)
58
+ return null;
59
+ const tags = [];
60
+ for (const member of members) {
61
+ const field = this.dataField(member, name);
62
+ const types = field ? unionMembers(field.type) : [];
63
+ if (!types.every((type) => type.kind === 'literal'))
64
+ return null;
65
+ if (types.length === 0)
66
+ return null;
67
+ tags.push(types.map((type) => type.value));
68
+ }
69
+ return tags;
70
+ }
71
+ /** `d.kind == "courier"` narrows `d` too: the members with this tag, or the others. */
72
+ narrow(node, assumeTrue) {
73
+ const narrowing = super.narrow(node, assumeTrue);
74
+ if (node.kind !== 'BinaryExpression')
75
+ return narrowing;
76
+ if (node.operator !== '==' && node.operator !== '!=')
77
+ return narrowing;
78
+ const right = literalValue(node.right);
79
+ const value = right ?? literalValue(node.left);
80
+ if (value === undefined)
81
+ return narrowing;
82
+ const target = right !== undefined ? node.left : node.right;
83
+ const equal = (node.operator === '==') === assumeTrue;
84
+ return [...narrowing, ...this.narrowTag(target, [value], equal)];
85
+ }
86
+ /** `switch d.kind`: in a case the members with its tags, in `default` those of no case. */
87
+ narrowCase(node, switchCase) {
88
+ const narrowing = super.narrowCase(node, switchCase);
89
+ if (!node.discriminant)
90
+ return narrowing;
91
+ const isDefault = switchCase.tests.length === 0;
92
+ const tests = isDefault ? node.cases.flatMap((each) => each.tests) : switchCase.tests;
93
+ const values = tests.map(literalValue);
94
+ if (!isDefault && values.includes(undefined))
95
+ return narrowing;
96
+ const known = values.filter((value) => value !== undefined);
97
+ return [...narrowing, ...this.narrowTag(node.discriminant, known, !isDefault)];
98
+ }
99
+ narrowTag(target, values, equal) {
100
+ if (target.kind !== 'MemberExpression' || target.optional)
101
+ return [];
102
+ const owner = this.narrowable(target.object);
103
+ const tags = owner && this.tagValues(unionMembers(nonNull(owner.type)), target.property.name);
104
+ return owner && tags ? [[owner.place, narrowByTag(owner.type, tags, values, equal)]] : [];
105
+ }
106
+ /**
107
+ * `{ kind: "pickup", shop: "Main st." }` where one of several objects is expected: the member
108
+ * that a tag of the literal chooses, or `null` when no tag chooses one. A tag of no member is
109
+ * reported, and the literal then gets `UNKNOWN`, so that it is not reported again.
110
+ */
111
+ taggedMember(node, objects, union) {
112
+ for (const property of node.properties) {
113
+ if (property.kind === 'SpreadElement')
114
+ continue;
115
+ const name = property.key.kind === 'Identifier' ? property.key.name : property.key.value;
116
+ const value = literalValue(property.value);
117
+ const tags = value === undefined ? null : this.tagValues(objects, name);
118
+ if (value === undefined || !tags)
119
+ continue;
120
+ const chosen = objects.filter((_, i) => tags[i].includes(value));
121
+ if (chosen.length === 1)
122
+ return chosen[0];
123
+ if (chosen.length > 1)
124
+ continue;
125
+ const known = [...new Set(tags.flat())].map((each) => JSON.stringify(each));
126
+ const choices = known.length > 1 ? `${known.slice(0, -1).join(', ')} or ${known.at(-1)}` : known[0];
127
+ this.error(`${JSON.stringify(value)} is not a kind of ${typeToString(union)}: write ${choices}`, property.value);
128
+ return UNKNOWN;
129
+ }
130
+ return null;
131
+ }
132
+ }
@@ -0,0 +1,6 @@
1
+ import type * as ast from '../ast.ts';
2
+ import { QueueChecker } from './queues.ts';
3
+ import { type Type } from './types.ts';
4
+ export declare abstract class TopicChecker extends QueueChecker {
5
+ protected checkTopic(node: ast.TopicExpression): Type;
6
+ }
@@ -0,0 +1,17 @@
1
+ import { QueueChecker } from "./queues.js";
2
+ import { notBroadcast } from "./transfer.js";
3
+ import { STRING, topicOf } from "./types.js";
4
+ // Topics: `topic<T>(name)` makes one, and its values go to the other tabs of the site as copies,
5
+ // so they must be data without channels. `t <- v` sends (channels.ts), `<-t` receives
6
+ // (operators.ts) and `for v in t` receives them until it is closed (loops.ts).
7
+ export class TopicChecker extends QueueChecker {
8
+ checkTopic(node) {
9
+ const element = this.resolveType(node.element);
10
+ const reason = notBroadcast(element);
11
+ if (reason !== null) {
12
+ this.error(`the values of a topic go to other tabs as copies: ${reason}`, node.element);
13
+ }
14
+ this.expectAssignable(this.checkValue(node.name, STRING), STRING, node.name);
15
+ return topicOf(element);
16
+ }
17
+ }
@@ -2,4 +2,6 @@ import { type Type } from './types.ts';
2
2
  /** Buffers and their views: copied whole, or moved by `transfer(x)` (channels.ts). */
3
3
  export declare const BUFFERS: readonly string[];
4
4
  /** Why a value of the type cannot be copied to a worker, or null if it can. */
5
- export declare function notCopied(type: Type, seen?: Set<Type>): string | null;
5
+ export declare function notCopied(type: Type, seen?: Set<Type>, tab?: boolean): string | null;
6
+ /** Why a value of the type cannot go to another tab through a topic, or null if it can. */
7
+ export declare function notBroadcast(type: Type): string | null;
@@ -1,6 +1,7 @@
1
1
  import { isQueue, memberNames, queueElement, typeToString } from "./types.js";
2
2
  // What can go to a worker and back: a worker gets a copy of each value (the structured clone of
3
- // postMessage), and a copy has the data of a value but not its methods.
3
+ // postMessage), and a copy has the data of a value but not its methods. What goes to another tab
4
+ // through a topic is a copy as well, and it cannot hold a channel.
4
5
  /** Buffers and their views: copied whole, or moved by `transfer(x)` (channels.ts). */
5
6
  export const BUFFERS = [
6
7
  'ArrayBuffer',
@@ -23,6 +24,8 @@ const COPIED = new Set([
23
24
  'chan',
24
25
  'chan<-',
25
26
  '<-chan',
27
+ // A topic is copied as its name, and the other thread listens to it on its own.
28
+ 'topic',
26
29
  'Map',
27
30
  'Set',
28
31
  'Date',
@@ -35,13 +38,13 @@ const COPIED = new Set([
35
38
  ...BUFFERS,
36
39
  ]);
37
40
  /** Why a value of the type cannot be copied to a worker, or null if it can. */
38
- export function notCopied(type, seen = new Set()) {
41
+ export function notCopied(type, seen = new Set(), tab = false) {
39
42
  if (seen.has(type))
40
43
  return null;
41
44
  seen.add(type);
42
45
  const inner = (types) => {
43
46
  for (const each of types) {
44
- const reason = notCopied(each, seen);
47
+ const reason = notCopied(each, seen, tab);
45
48
  if (reason !== null)
46
49
  return reason;
47
50
  }
@@ -61,15 +64,28 @@ export function notCopied(type, seen = new Set()) {
61
64
  return 'a component cannot be copied';
62
65
  case 'classValue':
63
66
  return `the class ${type.info.name} cannot be copied`;
67
+ // A data class goes with its class (runtime/classes.js), but not to another tab, which may not
68
+ // have it.
64
69
  case 'class':
65
- return type.info.name === 'Error'
66
- ? null
70
+ if (type.info.name === 'Error')
71
+ return null;
72
+ if (type.info.data === true) {
73
+ return tab
74
+ ? `${typeToString(type)} cannot go to another tab, which may not have its class: send an object of an interface`
75
+ : inner(type.args ?? []);
76
+ }
77
+ return type.info.data === false && !tab
78
+ ? `a copy of ${typeToString(type)} would lose its class and methods: mark it to keep them, e.g. data class ${type.info.name}`
67
79
  : `a copy of ${typeToString(type)} would lose its class and methods`;
68
80
  case 'object': {
69
81
  // A queue goes as its values, and the other thread makes a queue of them (src/runtime/threads.js).
70
82
  if (isQueue(type))
71
83
  return inner([queueElement(type)]);
72
84
  const name = type.instanceOf?.template.name ?? type.name;
85
+ // The id of a channel means something only to the workers of the tab that made it.
86
+ if (tab && (name === 'chan' || name === 'chan<-' || name === '<-chan')) {
87
+ return `${typeToString(type)} cannot go to another tab: a channel works only within one`;
88
+ }
73
89
  if (name !== null && COPIED.has(name))
74
90
  return inner(type.instanceOf?.args ?? []);
75
91
  if (type.call || type.construct)
@@ -84,3 +100,7 @@ export function notCopied(type, seen = new Set()) {
84
100
  return null;
85
101
  }
86
102
  }
103
+ /** Why a value of the type cannot go to another tab through a topic, or null if it can. */
104
+ export function notBroadcast(type) {
105
+ return notCopied(type, new Set(), true);
106
+ }
@@ -0,0 +1,6 @@
1
+ import type * as ast from '../ast.ts';
2
+ import { DecodeChecker } from './decode.ts';
3
+ import { type Type } from './types.ts';
4
+ export declare abstract class TryChecker extends DecodeChecker {
5
+ protected checkTryExpression(node: ast.TryExpression): Type;
6
+ }
@@ -0,0 +1,19 @@
1
+ import { DecodeChecker } from "./decode.js";
2
+ import { isErrorType } from "./error-results.js";
3
+ import { ERROR } from "./types.js";
4
+ // `try expr`: an exception in the expression, or a rejected Promise that an `await` in it waits
5
+ // for, becomes an error, as if the expression returned it (codegen/try.ts): `try JSON.parse(text)`
6
+ // gives `(any, error)`, `try save()` of a function without results gives the error alone, and of
7
+ // `(User, error)` the exception is one more way to get that error.
8
+ export class TryChecker extends DecodeChecker {
9
+ checkTryExpression(node) {
10
+ const type = this.checkExpression(node.argument, null);
11
+ if (type.kind === 'void' || isErrorType(type))
12
+ return ERROR;
13
+ if (type.kind !== 'tuple')
14
+ return { kind: 'tuple', types: [type, ERROR] };
15
+ return isErrorType(type.types.at(-1))
16
+ ? type
17
+ : { kind: 'tuple', types: [...type.types, ERROR] };
18
+ }
19
+ }
@@ -1,5 +1,5 @@
1
1
  import { type ArrayType, type ClassInfo, type FunctionType, type LiteralType, type Member, type Type, type TypeParam } from './model.ts';
2
- export declare function arrayOf(element: Type): ArrayType;
2
+ export declare function arrayOf(element: Type, positions?: Type[]): ArrayType;
3
3
  export declare function nullable(type: Type): Type;
4
4
  export declare function literal(value: string | number | boolean): LiteralType;
5
5
  /** The type that a literal belongs to: `"all"` → `string`. */
@@ -1,8 +1,8 @@
1
1
  import { BOOL, NEVER, NULL, NUMBER, STRING, } from "./model.js";
2
2
  import { typesEqual } from "./relations.js";
3
3
  // Making types: arrays, nullable types, literals, unions, functions, members, classes.
4
- export function arrayOf(element) {
5
- return { kind: 'array', element };
4
+ export function arrayOf(element, positions) {
5
+ return positions ? { kind: 'array', element, positions } : { kind: 'array', element };
6
6
  }
7
7
  export function nullable(type) {
8
8
  switch (type.kind) {
@@ -43,7 +43,7 @@ export function instantiate(template, args) {
43
43
  }
44
44
  // The indexes and the values of a loop, substituted when first used: the template of a
45
45
  // `.d.ts` may get them after its instances.
46
- for (const key of ['index', 'numberIndex', 'iterates']) {
46
+ for (const key of ['index', 'numberIndex', 'iterates', 'asyncIterates']) {
47
47
  let substituted;
48
48
  Object.defineProperty(instance, key, {
49
49
  enumerable: true,
@@ -0,0 +1,13 @@
1
+ import { type ObjectType, type Type } from './model.ts';
2
+ /** The values that `for` gives of a type it goes over at once, or null. */
3
+ export declare function iteratedBy(type: Type): Type | null;
4
+ /**
5
+ * Whether `source` fits `Iterable<T>` or another interface that `for` goes over: its values must
6
+ * fit. An array or a string, which have no members here, fits one whose members may be left out;
7
+ * an object goes on to the check of its members (null).
8
+ */
9
+ export declare function iterableAssignable(source: Type, target: ObjectType, assuming: [Type, Type][]): boolean | null;
10
+ /** `{ [string]: T }` accepts an object whose own index and fields are all `T`. */
11
+ export declare function indexAssignable(source: Type, index: Type, assuming: [Type, Type][]): boolean;
12
+ /** `ArrayLike<T>` from a `.d.ts` accepts an array, a string or a typed array of `T`. */
13
+ export declare function numberIndexAssignable(source: Type, index: Type, assuming: [Type, Type][]): boolean;
@@ -0,0 +1,52 @@
1
+ import { isNullable } from "./constructors.js";
2
+ import { memberNames } from "./lazy.js";
3
+ import { STRING } from "./model.js";
4
+ import { assignable } from "./relations.js";
5
+ // What types from a `.d.ts` that are gone over or indexed accept: `Iterable<T>` takes what `for`
6
+ // goes over with values that fit, an array or a Set; `{ [string]: T }` an object of fields of `T`;
7
+ // `ArrayLike<T>` an array, a string or a typed array.
8
+ /** The values that `for` gives of a type it goes over at once, or null. */
9
+ export function iteratedBy(type) {
10
+ if (type.kind === 'array')
11
+ return type.element;
12
+ if (type.kind === 'string')
13
+ return STRING;
14
+ return type.kind === 'object' ? (type.iterates ?? null) : null;
15
+ }
16
+ /**
17
+ * Whether `source` fits `Iterable<T>` or another interface that `for` goes over: its values must
18
+ * fit. An array or a string, which have no members here, fits one whose members may be left out;
19
+ * an object goes on to the check of its members (null).
20
+ */
21
+ export function iterableAssignable(source, target, assuming) {
22
+ const values = iteratedBy(source);
23
+ if (values === null || !assignable(values, target.iterates, assuming))
24
+ return values === null ? null : false;
25
+ if (source.kind === 'object')
26
+ return null;
27
+ return [...memberNames(target.members)].every((name) => {
28
+ const member = target.members.get(name);
29
+ return !member || (!member.method && isNullable(member.type));
30
+ });
31
+ }
32
+ /** `{ [string]: T }` accepts an object whose own index and fields are all `T`. */
33
+ export function indexAssignable(source, index, assuming) {
34
+ if (source.kind !== 'object')
35
+ return false;
36
+ if (source.index && !assignable(source.index, index, assuming))
37
+ return false;
38
+ return [...memberNames(source.members)].every((name) => {
39
+ const member = source.members.get(name);
40
+ return !member || member.method || assignable(member.type, index, assuming);
41
+ });
42
+ }
43
+ /** `ArrayLike<T>` from a `.d.ts` accepts an array, a string or a typed array of `T`. */
44
+ export function numberIndexAssignable(source, index, assuming) {
45
+ if (source.kind === 'array')
46
+ return assignable(source.element, index, assuming);
47
+ if (source.kind === 'string')
48
+ return assignable(source, index, assuming);
49
+ return (source.kind === 'object' &&
50
+ source.numberIndex !== undefined &&
51
+ assignable(source.numberIndex, index, assuming));
52
+ }
@@ -22,6 +22,11 @@ export declare const UNKNOWN: SimpleType;
22
22
  export interface ArrayType {
23
23
  kind: 'array';
24
24
  element: Type;
25
+ /**
26
+ * The types by position of a tuple of a `.d.ts`, `[K, V]`, or of an array literal: only for
27
+ * inferring type parameters, since the array may change. `element` is the union of them.
28
+ */
29
+ positions?: Type[];
25
30
  }
26
31
  /** `?T`; the inner type itself is never nullable. */
27
32
  export interface NullableType {
@@ -66,6 +71,15 @@ export interface Member {
66
71
  visibility: Visibility;
67
72
  /** The class that declares the member, for visibility checks. */
68
73
  owner: ClassInfo | null;
74
+ /** `readonly id string`: only its value and the constructor of its class assign it. */
75
+ readonly?: true;
76
+ /**
77
+ * A getter, a setter or both, as in `get area() number`: the member is a value of its type, which
78
+ * a getter alone cannot be assigned and a setter alone cannot be read.
79
+ */
80
+ accessor?: 'get' | 'set' | 'both';
81
+ /** `abstract area() number`: a subclass that is not abstract implements it. */
82
+ abstract?: true;
69
83
  }
70
84
  /** Interfaces, object type literals and the types of object literals. */
71
85
  export interface ObjectType {
@@ -82,6 +96,8 @@ export interface ObjectType {
82
96
  numberIndex?: Type;
83
97
  /** What `for v in x` gives: the values of `[Symbol.iterator]()` in a `.d.ts`. */
84
98
  iterates?: Type;
99
+ /** What `for v in x` waits for: the values of `[Symbol.asyncIterator]()`, as of a Node stream. */
100
+ asyncIterates?: Type;
85
101
  /** A generic interface, `interface Box<T>`: its members use these parameters. */
86
102
  typeParams?: TypeParam[];
87
103
  /** `Box<number>`: the generic interface and the type arguments it was made from. */
@@ -105,6 +121,15 @@ export interface ClassInfo {
105
121
  /** Type arguments for a generic base class: the defaults of a class from a `.d.ts`. */
106
122
  superArgs?: Type[];
107
123
  ctorVisibility: Visibility;
124
+ /** `abstract class Shape`: `new` makes only instances of its subclasses. */
125
+ abstract?: boolean;
126
+ /**
127
+ * `data class Point`: its fields are data, and its instances go to other threads with their
128
+ * class. False for the other classes of Rook; not set for those of a `.d.ts`.
129
+ */
130
+ data?: boolean;
131
+ /** What `for v in x` waits for: the values of `[Symbol.asyncIterator]()` in a `.d.ts`. */
132
+ asyncIterates?: Type;
108
133
  instance: ClassType;
109
134
  value: ClassValueType;
110
135
  }