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
package/dist/build.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
2
- import { dirname, join, relative, resolve, sep } from 'node:path';
2
+ import { basename, dirname, join, relative, resolve, sep } from 'node:path';
3
3
  import { compile } from "./index.js";
4
+ import { sourceMapComment } from "./source-map.js";
4
5
  import { SourceFile } from "./source.js";
5
6
  /**
6
7
  * Compiles `.rk` files and directories (recursively), together with every `.rk` file they
@@ -47,6 +48,8 @@ export function build(entries, options = {}) {
47
48
  code: result.code,
48
49
  declarations: result.declarations,
49
50
  worker: result.worker,
51
+ map: result.map,
52
+ workerMap: result.workerMap,
50
53
  });
51
54
  }
52
55
  for (const specifier of result.dependencies) {
@@ -62,15 +65,33 @@ export function build(entries, options = {}) {
62
65
  return { outputs: [], errors, warnings };
63
66
  // The output keeps the folder structure below the given directories and all the files.
64
67
  const root = commonDirectory(queue, directories);
65
- const outputs = compiled.map(({ source, code, declarations: types, worker }) => {
68
+ const outputs = compiled.map((each) => {
69
+ const { source, declarations: types } = each;
66
70
  const target = options.outDir ? join(resolve(options.outDir), relative(root, source)) : source;
67
71
  const base = target.replace(/\.rk$/, '');
72
+ const maps = [];
73
+ // The code ends with where its map is, and the map says where the source is, from there.
74
+ const mapped = (code, output, map) => {
75
+ if (options.sourceMap === false || map === undefined)
76
+ return code;
77
+ const sources = [relative(dirname(output), source).split(sep).join('/')];
78
+ maps.push({
79
+ output: `${output}.map`,
80
+ code: JSON.stringify({ ...map, file: basename(output), sources }),
81
+ });
82
+ return `${code}${sourceMapComment(`${basename(output)}.map`)}`;
83
+ };
84
+ const code = mapped(each.code, `${base}.js`, each.map);
85
+ const worker = each.worker === undefined
86
+ ? undefined
87
+ : mapped(each.worker, `${base}.worker.js`, each.workerMap);
68
88
  return {
69
89
  source,
70
90
  output: `${base}.js`,
71
91
  code,
72
92
  ...(types === undefined ? {} : { declarations: { output: `${base}.d.ts`, code: types } }),
73
93
  ...(worker === undefined ? {} : { worker: { output: `${base}.worker.js`, code: worker } }),
94
+ ...(maps.length > 0 ? { maps } : {}),
74
95
  };
75
96
  });
76
97
  return { outputs, errors, warnings };
@@ -0,0 +1,6 @@
1
+ import type * as ast from '../ast.ts';
2
+ import { ExpressionChecker } from './expressions.ts';
3
+ import { type Type } from './types.ts';
4
+ export declare abstract class ArrayLiteralChecker extends ExpressionChecker {
5
+ protected checkArrayLiteral(node: ast.ArrayLiteral, expected: Type | null): Type;
6
+ }
@@ -0,0 +1,59 @@
1
+ import { ExpressionChecker } from "./expressions.js";
2
+ import { ANY, arrayOf, commonType, NEVER, nonNull, typeToString, UNKNOWN, } from "./types.js";
3
+ // Array literals: the type of their elements is the expected one, or the common type of all of
4
+ // them, `[1, null]` is a `[]?number`. A literal without spreads also keeps the types of its
5
+ // elements by position, so that a call infers `K` and `V` of a tuple `[K, V]` of a `.d.ts` from
6
+ // `["a", 1]`: `new Map([["a", 1]])` is a `Map<string, number>` (types/params.ts).
7
+ export class ArrayLiteralChecker extends ExpressionChecker {
8
+ checkArrayLiteral(node, expected) {
9
+ const context = expected ? nonNull(expected) : null;
10
+ const expectedElement = context?.kind === 'array' ? context.element : context?.kind === 'any' ? ANY : null;
11
+ let element = expectedElement ?? NEVER;
12
+ const positions = [];
13
+ for (const item of node.elements) {
14
+ let type;
15
+ if (item.kind === 'SpreadElement') {
16
+ const spread = this.checkValue(item.argument, expectedElement && arrayOf(expectedElement));
17
+ if (spread.kind === 'array')
18
+ type = spread.element;
19
+ else if (spread.kind === 'any' || spread.kind === 'unknown')
20
+ type = spread;
21
+ // JS spreads any iterable: a Set, a typed array, a `[]byte`.
22
+ else if (spread.kind === 'object' && spread.iterates)
23
+ type = spread.iterates;
24
+ else {
25
+ this.error(`cannot spread ${typeToString(spread)}: it is not an array or an iterable`, item.argument);
26
+ type = UNKNOWN;
27
+ }
28
+ }
29
+ else {
30
+ type = this.checkValue(item, expectedElement);
31
+ positions.push(type);
32
+ }
33
+ if (expectedElement) {
34
+ this.expectAssignable(type, expectedElement, item.kind === 'SpreadElement' ? item.argument : item);
35
+ }
36
+ else {
37
+ element = commonElement(element, type);
38
+ }
39
+ }
40
+ return positions.length === node.elements.length && positions.length > 0
41
+ ? arrayOf(element, positions)
42
+ : arrayOf(element);
43
+ }
44
+ }
45
+ /**
46
+ * The common type of the elements so far and the next one; of arrays with positions, by position,
47
+ * so that `[["a", 1], ["b", "x"]]` gives a `V` of `number | string`.
48
+ */
49
+ function commonElement(element, next) {
50
+ const common = commonType(element, next);
51
+ if (common.kind !== 'array' ||
52
+ element.kind !== 'array' ||
53
+ next.kind !== 'array' ||
54
+ element.positions?.length !== next.positions?.length ||
55
+ !element.positions) {
56
+ return common;
57
+ }
58
+ return arrayOf(common.element, element.positions.map((each, i) => commonType(each, next.positions[i])));
59
+ }
@@ -2,6 +2,8 @@ import type * as ast from '../ast.ts';
2
2
  import { InitializerChecker } from './initializers.ts';
3
3
  import { type Type } from './types.ts';
4
4
  export declare abstract class AssignmentChecker extends InitializerChecker {
5
+ /** The name of the `derived` whose block is being checked: it computes, so it changes no state. */
6
+ protected derivedBlock: string | null;
5
7
  /**
6
8
  * `a, b = f()` or `return f()`: the call must return exactly `count` values. Returns their
7
9
  * types.
@@ -10,6 +12,8 @@ export declare abstract class AssignmentChecker extends InitializerChecker {
10
12
  protected checkAssignment(node: ast.AssignmentStatement): void;
11
13
  /** The type a value assigned to `target` must have. */
12
14
  protected checkTarget(target: ast.Expression): Type;
15
+ /** `this.id = id` in the constructor of the class that has the readonly field `id`. */
16
+ private initializes;
13
17
  protected checkReturn(node: ast.ReturnStatement): void;
14
18
  /**
15
19
  * `return null, err` in a function of `(User, error)`: null takes the place of a value only
@@ -1,3 +1,4 @@
1
+ import { placeOf } from "./context.js";
1
2
  import { guardedByError, unpackedResults } from "./error-results.js";
2
3
  import { isUntyped, countValues, callName, isCall } from "./helpers.js";
3
4
  import { InitializerChecker } from "./initializers.js";
@@ -5,6 +6,8 @@ import { BOOL, ANY, isAssignable, isNullable, nonNull, nullable, typeToString, U
5
6
  // Where values go: assignments to variables, fields and elements, and `return`. Both unpack a call
6
7
  // that returns several values.
7
8
  export class AssignmentChecker extends InitializerChecker {
9
+ /** The name of the `derived` whose block is being checked: it computes, so it changes no state. */
10
+ derivedBlock = null;
8
11
  /**
9
12
  * `a, b = f()` or `return f()`: the call must return exactly `count` values. Returns their
10
13
  * types.
@@ -66,13 +69,13 @@ export class AssignmentChecker extends InitializerChecker {
66
69
  const binding = this.narrowableBinding(target);
67
70
  const declared = binding?.type;
68
71
  if (binding && declared && isNullable(declared) && !isNullable(valueTypes[i] ?? UNKNOWN)) {
69
- this.flow.set(binding, nonNull(declared));
72
+ this.flow.set(placeOf(binding), nonNull(declared));
70
73
  }
71
74
  });
72
75
  }
73
76
  /** The type a value assigned to `target` must have. */
74
77
  checkTarget(target) {
75
- if (this.checkDecoratorWrite(target))
78
+ if (this.checkDecoratorWrite(target) || this.checkSharedWrite(target))
76
79
  return UNKNOWN;
77
80
  switch (target.kind) {
78
81
  case 'Identifier': {
@@ -92,13 +95,16 @@ export class AssignmentChecker extends InitializerChecker {
92
95
  else if (binding.kind === 'ref') {
93
96
  this.error(`cannot assign to "${target.name}": a ref always points to its element`, target);
94
97
  }
98
+ else if (binding.kind === 'state' && this.derivedBlock !== null) {
99
+ this.error(`derived "${this.derivedBlock}" cannot change the state "${target.name}": change it in an event handler or an effect`, target);
100
+ }
95
101
  else if (binding.kind === 'loop') {
96
102
  this.error(`cannot assign to loop variable "${target.name}"`, target);
97
103
  }
98
104
  else if (binding.kind === 'prop') {
99
105
  this.error(this.decorator
100
106
  ? `cannot assign to "${target.name}": parameters of a decorator are read-only`
101
- : `cannot assign to "${target.name}": component properties are read-only; to change the parent's state, pass a function`, target);
107
+ : `cannot assign to "${target.name}": component properties are read-only; to change it, declare it with bind, e.g. comp ${this.component?.node.name.name ?? 'Tabs'}(bind ${target.name} ...)`, target);
102
108
  }
103
109
  else if (binding.kind !== 'let' &&
104
110
  binding.kind !== 'state' &&
@@ -119,6 +125,12 @@ export class AssignmentChecker extends InitializerChecker {
119
125
  this.error(`cannot assign to method "${target.property.name}"`, target);
120
126
  return UNKNOWN;
121
127
  }
128
+ if (member.accessor === 'get') {
129
+ this.error(`cannot assign to "${target.property.name}": it has a getter and no setter`, target);
130
+ }
131
+ else if (member.readonly && !this.initializes(target, member)) {
132
+ this.error(`cannot assign to "${target.property.name}": it is readonly, so only the constructor of ${member.owner?.name ?? 'its class'} assigns it`, target);
133
+ }
122
134
  return member.type;
123
135
  }
124
136
  case 'IndexExpression': {
@@ -141,6 +153,14 @@ export class AssignmentChecker extends InitializerChecker {
141
153
  return UNKNOWN;
142
154
  }
143
155
  }
156
+ /** `this.id = id` in the constructor of the class that has the readonly field `id`. */
157
+ initializes(target, member) {
158
+ return (target.object.kind === 'ThisExpression' &&
159
+ this.fn?.isConstructor === true &&
160
+ this.cls !== null &&
161
+ !this.cls.isStatic &&
162
+ this.cls.info === member.owner);
163
+ }
144
164
  checkReturn(node) {
145
165
  const fn = this.fn;
146
166
  if (!fn)
@@ -1,6 +1,6 @@
1
1
  import type * as ast from '../ast.ts';
2
2
  import type { Diagnostic } from '../diagnostics.ts';
3
- import { type CheckOptions, type BindingKind, type Binding, type ComponentInfo, type DecoratorInfo, type AliasEntry, Scope, type FunctionContext, type ClassContext, type Flow } from './context.ts';
3
+ import { type CheckOptions, type BindingKind, type Binding, type ComponentInfo, type DecoratorInfo, type AliasEntry, Scope, type FunctionContext, type ClassContext, type Flow, type Place } from './context.ts';
4
4
  import { type ClassInfo, type FunctionType, type Member, type Type, type TypeParam } from './types.ts';
5
5
  export declare abstract class CheckerBase {
6
6
  protected abstract checkClassBodies(node: ast.ClassDeclaration, info: ClassInfo): void;
@@ -18,6 +18,13 @@ export declare abstract class CheckerBase {
18
18
  protected abstract checkTry(node: ast.TryStatement): void;
19
19
  protected abstract forget(target: ast.Expression): void;
20
20
  protected abstract narrowableBinding(node: ast.Expression): Binding | undefined;
21
+ /** A variable or a path of data fields that conditions narrow, with its type at this point. */
22
+ protected abstract narrowable(node: ast.Expression): {
23
+ place: Place;
24
+ type: Type;
25
+ } | undefined;
26
+ /** Whether an assignment to `field` of `place` may change the member of a union it is. */
27
+ protected abstract isTagOf(place: Place, field: string): boolean;
21
28
  /** The error for a value that may be null where it must not be. */
22
29
  protected abstract nullError(node: ast.Expression): void;
23
30
  protected abstract checkFunction(params: readonly ast.Parameter[], type: FunctionType, results: Type[] | null, body: ast.BlockStatement | ast.Expression, options?: {
@@ -44,7 +51,6 @@ export declare abstract class CheckerBase {
44
51
  protected abstract orderByNeeds(decorators: readonly DecoratorInfo[]): DecoratorInfo[];
45
52
  protected abstract wrappedType(info: ComponentInfo, own: Type, seen: Set<ComponentInfo>): Type;
46
53
  protected abstract checkDecoratorMember(node: ast.DecoratorMember): Type;
47
- protected abstract checkDecoratorGet(node: ast.DecoratorGet): Type;
48
54
  protected abstract checkDecoratorWrite(target: ast.Expression): boolean;
49
55
  protected abstract checkComponentUse(node: ast.ElementExpression, tag: ast.Identifier): Type;
50
56
  protected abstract checkRefDeclaration(node: ast.VariableDeclaration): void;
@@ -4,13 +4,13 @@ import { Scope, globalScope, } from "./context.js";
4
4
  import { DOCUMENT_FRAGMENT, eventType, HTML_ELEMENT, NODE } from "./dom.js";
5
5
  import { explainMismatch, isAssignable, LazyMap, memberNames, nonNull, property, typeToString, UNKNOWN, } from "./types.js";
6
6
  // The type checker is a chain of layers, one per area, like the code generator: base → imports →
7
- // declarations → classes → resolution → initializers → assignments → statements → control flow →
8
- // functions → expressions → operators → markup → forwarding → components → component values →
9
- // refs → styles → decorator wrappers → decorator exports → decorators → decorator members →
10
- // members → calls → goroutines → channels → queues → bytes → loops → go for → iterators →
11
- // completion → Checker (checker.ts). Statements, expressions and markup check each other
12
- // recursively, so methods of later layers that earlier ones call are declared here as abstract.
13
- // This layer has the state of a check, diagnostics, scopes and names, and the DOM types.
7
+ // shared state → declarations → class members → classes → resolution → initializers → assignments → statements → control flow →
8
+ // tagged unions → functions → expressions → array literals → operators → markup → forwarding → component bindings → components → component values →
9
+ // refs → styles → decorator wrappers → decorator exports → decorators → decorator includes → decorator members →
10
+ // members → calls → goroutines → channels → signals → queues → topics → decode → try → bytes → loops → go for →
11
+ // iterators → closing → completion → Checker (checker.ts). Statements, expressions and markup
12
+ // check each other recursively, so methods of later layers that earlier ones call are declared
13
+ // here as abstract. This layer has the state of a check, diagnostics, scopes and names, and the DOM types.
14
14
  export class CheckerBase {
15
15
  program;
16
16
  options;
@@ -3,10 +3,11 @@ import { BIGINT_OBJECT } from "./bigint.js";
3
3
  import { DOM_TYPES, EVENT } from "./dom.js";
4
4
  import { SVG_TYPES } from "./dom-svg.js";
5
5
  import { chanOf } from "./channel.js";
6
+ import { CHANNEL_OBJECT, TOPIC_OBJECT } from "./channel-objects.js";
6
7
  import { PROMISE } from "./promise.js";
7
8
  import { BUFFERS } from "./transfer.js";
8
- import { UNTYPED_GLOBALS } from "./untyped-globals.js";
9
- import { ANY, arrayOf, BIGINT, BOOL, ERROR, ERROR_CLASS, func, method, NUMBER, nullable, property, STRING, } from "./types.js";
9
+ import { UNTYPED_CLASSES, UNTYPED_GLOBALS } from "./untyped-globals.js";
10
+ import { ANY, arrayOf, BIGINT, BOOL, ERROR, ERROR_CLASS, func, method, NUMBER, nullable, property, STRING, topicOf, union, } from "./types.js";
10
11
  // Types of the JS standard library that Rook programs use most. Anything not listed here
11
12
  // can still be reached through the untyped globals below.
12
13
  function object(name, members, call) {
@@ -226,20 +227,32 @@ export const GLOBAL_VALUES = new Map([
226
227
  ['NaN', NUMBER],
227
228
  ['Infinity', NUMBER],
228
229
  ['Error', ERROR_CLASS.value],
229
- // `error("message")` creates an Error, so the result is never null.
230
- ['error', func([STRING], [ERROR_CLASS.instance])],
231
- // `close(ch)`: the channel takes no more values (a `<-chan T` cannot be closed: channels.ts).
232
- ['close', func([chanOf(ANY)], [])],
230
+ // `error("message")` creates an Error, so the result is never null; `error("load user", err)`
231
+ // wraps an error that is not null.
232
+ ['error', func([STRING, ERROR_CLASS.instance], [ERROR_CLASS.instance], { required: 1 })],
233
+ // `close(ch)`: the channel takes no more values (a `<-chan T` cannot be closed: channels.ts);
234
+ // `close(t)`: the topic stops listening and sending.
235
+ ['close', func([union([chanOf(ANY), topicOf(ANY)])], [])],
233
236
  // `transfer(buffer)`: the buffer moves to the thread it goes to (what it takes: channels.ts).
234
237
  ['transfer', func([MOVED], [MOVED], { typeParams: [MOVED] })],
235
238
  // Channels of time, as in Go: the time once after `ms`, or every `ms` until `close(ticks)`.
236
239
  ['after', func([NUMBER], [chanOf(NUMBER, 'receive')])],
237
240
  ['tick', func([NUMBER], [chanOf(NUMBER)])],
241
+ // Signals of the process, until `close(stop)`; an AbortSignal as a channel, and a channel as one
242
+ // (what they take and give: signals.ts).
243
+ ['signal', func([], [chanOf(STRING)], { rest: STRING })],
244
+ ['done', func([ANY], [chanOf(ANY, 'receive')])],
245
+ ['abortSignal', func([chanOf(ANY, 'receive')], [ANY])],
238
246
  // `stream(ch)`: a ReadableStream of the values of a channel (its type: channels.ts).
239
247
  ['stream', func([chanOf(ANY, 'receive')], [ANY])],
240
248
  // `cancel(task)` stops a goroutine: it takes the Promise of `go` (channels.ts).
241
249
  ['cancel', func([ANY], [])],
242
- ...[...UNTYPED_GLOBALS, ...BUFFERS].map((name) => [name, ANY]),
250
+ ['Channel', CHANNEL_OBJECT],
251
+ ['Topic', TOPIC_OBJECT],
252
+ ...[...UNTYPED_GLOBALS, ...UNTYPED_CLASSES, ...BUFFERS].map((name) => [
253
+ name,
254
+ ANY,
255
+ ]),
243
256
  ]);
244
257
  /** Built-in types that a library from TypeScript's lib files replaces: untyped ones and the DOM. */
245
258
  export const LIBRARY_TYPES = new Set([
@@ -250,6 +263,7 @@ export const LIBRARY_TYPES = new Set([
250
263
  'Promise',
251
264
  'URL',
252
265
  'Event',
266
+ ...UNTYPED_CLASSES,
253
267
  ...BUFFERS,
254
268
  ...[...DOM_TYPES.keys()].filter((name) => name !== 'Content'),
255
269
  ...SVG_TYPES.keys(),
@@ -268,7 +282,7 @@ export const GLOBAL_TYPES = new Map([
268
282
  ['any', ANY],
269
283
  ['error', ERROR],
270
284
  ['Error', ERROR_CLASS.instance],
271
- ...['Date', 'RegExp', 'Map', 'Set', 'URL', ...BUFFERS].map((name) => [name, ANY]),
285
+ ...['Date', 'RegExp', 'Map', 'Set', 'URL', ...UNTYPED_CLASSES, ...BUFFERS].map((name) => [name, ANY]),
272
286
  ['Promise', PROMISE],
273
287
  ...DOM_TYPES,
274
288
  ...SVG_TYPES,
@@ -1,7 +1,7 @@
1
1
  import type * as ast from '../ast.ts';
2
- import { QueueChecker } from './queues.ts';
2
+ import { TryChecker } from './try.ts';
3
3
  import { type Type } from './types.ts';
4
- export declare abstract class BytesChecker extends QueueChecker {
4
+ export declare abstract class BytesChecker extends TryChecker {
5
5
  protected checkArrayLiteral(node: ast.ArrayLiteral, expected: Type | null): Type;
6
6
  private isElements;
7
7
  /** `make([]float64, n)`: a typed array of `n` zeros. */
@@ -1,11 +1,11 @@
1
- import { QueueChecker } from "./queues.js";
1
+ import { TryChecker } from "./try.js";
2
2
  import { isUntyped } from "./helpers.js";
3
3
  import { holdsBigInts } from "../typed-arrays.js";
4
4
  import { BIGINT, isBytes, nonNull, NUMBER, STRING, typedArrayOf, typeToString, UNKNOWN, } from "./types.js";
5
5
  // `[]byte`, a Uint8Array, and the other typed arrays, `[]float64`: an array literal where one is
6
6
  // expected, `let data []byte = [1, 2]`; and the conversions of Go between text and its UTF-8
7
7
  // bytes, `[]byte("rook")` and `string(data)`.
8
- export class BytesChecker extends QueueChecker {
8
+ export class BytesChecker extends TryChecker {
9
9
  checkArrayLiteral(node, expected) {
10
10
  const typed = expected && nonNull(expected);
11
11
  const name = typed && typedArrayOf(typed);
@@ -59,6 +59,9 @@ export class CallChecker extends MemberChecker {
59
59
  }
60
60
  const { info } = callee;
61
61
  this.ensureClassResolved(info);
62
+ if (info.abstract) {
63
+ this.error(`cannot create an instance of the abstract class "${info.name}": create one of a class that extends it`, node);
64
+ }
62
65
  const { type, owner } = constructorOf(info);
63
66
  if (owner && owner.ctorVisibility !== 'public') {
64
67
  const current = this.cls?.info;
@@ -0,0 +1,3 @@
1
+ import { type ObjectType } from './types.ts';
2
+ export declare const CHANNEL_OBJECT: ObjectType;
3
+ export declare const TOPIC_OBJECT: ObjectType;
@@ -0,0 +1,40 @@
1
+ import { chanOf } from "./channel.js";
2
+ import { PROMISE } from "./promise.js";
3
+ import { ANY, arrayOf, BOOL, ERROR, func, instantiate, method, nullable, NUMBER, property, STRING, topicOf, VOID, } from "./types.js";
4
+ // `Channel` and `Topic`: what `<-`, `close` and the rest compile to, by name, for code that needs
5
+ // them as functions. `Channel.EMPTY` and `Topic.EMPTY` are the zero values of their types: closed,
6
+ // and a `chan any` and a `topic any`, so that they go where a channel or topic of any values is
7
+ // expected (src/runtime/channel.js, src/runtime/topic.js).
8
+ const T = { kind: 'param', name: 'T' };
9
+ function generic(params, results) {
10
+ return method(func(params, results, { typeParams: [T] }));
11
+ }
12
+ function object(name, members) {
13
+ return { kind: 'object', name, members: new Map(Object.entries(members)), call: null };
14
+ }
15
+ export const CHANNEL_OBJECT = object('Channel', {
16
+ EMPTY: property(chanOf(ANY)),
17
+ make: generic([NUMBER, T], [chanOf(T)]),
18
+ send: generic([chanOf(T, 'send'), T], [instantiate(PROMISE, [VOID])]),
19
+ // The zero value of its type after close; null from Channel.EMPTY.
20
+ receive: generic([chanOf(T, 'receive')], [instantiate(PROMISE, [nullable(T)])]),
21
+ close: method(func([chanOf(ANY)], [])),
22
+ // An async iterator of the values, for `for await` of JS.
23
+ range: generic([chanOf(T, 'receive')], [ANY]),
24
+ select: method(func([arrayOf(ANY), BOOL], [instantiate(PROMISE, [arrayOf(ANY)])])),
25
+ try: method(func([chanOf(ANY), BOOL, ANY], [nullable(arrayOf(ANY))], { required: 2 })),
26
+ fromStream: method(func([ANY, NUMBER, ANY], [chanOf(ANY), chanOf(ERROR, 'receive')])),
27
+ stream: generic([chanOf(T, 'receive')], [ANY]),
28
+ after: method(func([NUMBER], [chanOf(NUMBER, 'receive')])),
29
+ tick: method(func([NUMBER], [chanOf(NUMBER)])),
30
+ signal: method(func([], [chanOf(STRING)], { rest: STRING })),
31
+ done: method(func([ANY], [chanOf(ANY, 'receive')])),
32
+ abortSignal: method(func([chanOf(ANY, 'receive')], [ANY])),
33
+ });
34
+ export const TOPIC_OBJECT = object('Topic', {
35
+ EMPTY: property(topicOf(ANY)),
36
+ make: generic([STRING, T], [topicOf(T)]),
37
+ publish: generic([topicOf(T), T], []),
38
+ listen: generic([topicOf(T)], [chanOf(T, 'receive')]),
39
+ close: method(func([topicOf(ANY)], [])),
40
+ });
@@ -1,6 +1,7 @@
1
1
  import type * as ast from '../ast.ts';
2
2
  import { GoroutineChecker } from './goroutines.ts';
3
3
  import { type Type } from './types.ts';
4
+ export declare function typeName(type: Type): string | null;
4
5
  export declare abstract class ChannelChecker extends GoroutineChecker {
5
6
  protected checkChan(node: ast.ChanExpression): Type;
6
7
  protected checkStreamChan(node: ast.StreamChanExpression): Type;
@@ -1,17 +1,17 @@
1
- import { assignedNames } from "../names.js";
1
+ import { cannotWait } from "../parser/syntax.js";
2
2
  import { chanDirection, chanOf, elementOf, isChan } from "./channel.js";
3
3
  import { GoroutineChecker } from "./goroutines.js";
4
4
  import { isUntyped } from "./helpers.js";
5
5
  import { isPromise } from "./promise.js";
6
6
  import { BUFFERS } from "./transfer.js";
7
- import { ANY, BOOL, ERROR, instantiate, isNullable, nonNull, NUMBER, typeToString, typedArrayOf, UNKNOWN, } from "./types.js";
7
+ import { ANY, BOOL, ERROR, instantiate, isNullable, isTopic, nonNull, NUMBER, topicElement, typeToString, typedArrayOf, UNKNOWN, } from "./types.js";
8
8
  // Channels: `chan<number>(10)` makes one, `ch <- v` sends a value, `<-ch` receives one (in
9
9
  // operators.ts), `for v in ch` receives them until it is closed (in control-flow.ts), and
10
10
  // `select` waits for the first of several. `transfer(x)` moves a buffer to another thread.
11
11
  // `chan(stream)` receives the chunks of a ReadableStream, and `stream(ch)` reads a channel.
12
12
  /** What `transfer(x)` moves: a buffer, or a view whose buffer moves. */
13
13
  const MOVABLE = new Set(BUFFERS);
14
- function typeName(type) {
14
+ export function typeName(type) {
15
15
  if (type.kind === 'class')
16
16
  return type.info.name;
17
17
  if (type.kind === 'object')
@@ -50,11 +50,20 @@ export class ChannelChecker extends GoroutineChecker {
50
50
  this.expectAssignable(this.checkValue(node.size, NUMBER), NUMBER, node.size);
51
51
  return { kind: 'tuple', types: [chanOf(element), chanOf(ERROR, 'receive')] };
52
52
  }
53
+ // A send to a channel waits until the channel takes the value, and one to a topic waits for
54
+ // nobody, so only the first needs an async function.
53
55
  checkSend(node) {
54
56
  const nullChannel = this.inSelectCase;
55
57
  this.inSelectCase = false;
56
58
  const type = this.checkValue(node.channel);
57
59
  const channel = nullChannel && isNullable(type) ? nonNull(type) : type;
60
+ if (isTopic(channel)) {
61
+ const element = topicElement(channel);
62
+ this.expectAssignable(this.checkValue(node.value, element), element, node.value);
63
+ return;
64
+ }
65
+ if (node.cannotWait)
66
+ this.error(cannotWait('<-', node.cannotWait.inDefer), node.cannotWait);
58
67
  if (!isChan(channel)) {
59
68
  if (!isUntyped(channel)) {
60
69
  this.error(`"<-" sends to a channel, not to ${typeToString(channel)}`, node.channel);
@@ -105,7 +114,7 @@ export class ChannelChecker extends GoroutineChecker {
105
114
  : ANY;
106
115
  }
107
116
  checkSelect(node) {
108
- this.dropNarrowing(assignedNames(node));
117
+ this.dropNarrowing(node);
109
118
  const entry = this.flow;
110
119
  for (const { names, operation, body } of node.cases) {
111
120
  this.flow = new Map(entry);
@@ -114,6 +123,10 @@ export class ChannelChecker extends GoroutineChecker {
114
123
  this.inSelectCase = operation !== null;
115
124
  if (operation?.kind === 'SendStatement') {
116
125
  this.checkSend(operation);
126
+ const channel = this.checkedTypes.get(operation.channel);
127
+ if (channel && isTopic(isNullable(channel) ? nonNull(channel) : channel)) {
128
+ this.error('a send to a topic waits for nobody, so it is not a case of "select": send it before the select, e.g. "cart <- event"', operation.channel);
129
+ }
117
130
  }
118
131
  else if (operation) {
119
132
  // `case v, ok = <-ch:` declares the variables of the case.
@@ -1,4 +1,4 @@
1
1
  import type * as ast from '../ast.ts';
2
2
  import type { CheckOptions, CheckResult } from './context.ts';
3
- export type { CheckOptions, CheckResult, Completion, CompletionContext, CompletionResult, ImportResult, Library, ModuleExports, } from './context.ts';
3
+ export type { CheckOptions, CheckResult, Completion, CompletionContext, CompletionResult, ImportResult, Library, ModuleExports, TextEdit, } from './context.ts';
4
4
  export declare function check(program: ast.Program, options?: CheckOptions): CheckResult;
@@ -6,6 +6,7 @@ class Checker extends CompletionChecker {
6
6
  this.checkEndlessRecursion();
7
7
  this.checkIterators();
8
8
  this.checkHtmlTags();
9
+ this.checkClosing();
9
10
  this.diagnostics.sort((a, b) => a.start - b.start);
10
11
  this.warnings.sort((a, b) => a.start - b.start);
11
12
  return {
@@ -13,9 +14,27 @@ class Checker extends CompletionChecker {
13
14
  warnings: this.warnings,
14
15
  ...this.collectDeclarations(),
15
16
  types: this.checkedTypes,
17
+ dataClasses: this.dataClasses(),
16
18
  ...(this.completion ? { completion: this.completion } : {}),
17
19
  };
18
20
  }
21
+ /** The names of the module that are data classes: its own, and those it imports. */
22
+ dataClasses() {
23
+ const names = new Set();
24
+ for (const statement of this.program.body) {
25
+ const declared = statement.kind === 'ClassDeclaration'
26
+ ? [statement.name.name]
27
+ : statement.kind === 'ImportDeclaration'
28
+ ? statement.namedImports.map((specifier) => specifier.local.name)
29
+ : [];
30
+ for (const name of declared) {
31
+ const type = this.lookupValue(name)?.type;
32
+ if (type?.kind === 'classValue' && type.info.data === true)
33
+ names.add(name);
34
+ }
35
+ }
36
+ return names;
37
+ }
19
38
  }
20
39
  export function check(program, options = {}) {
21
40
  return new Checker(program, options).run();
@@ -0,0 +1,22 @@
1
+ import type * as ast from '../ast.ts';
2
+ import { DeclarationChecker } from './declarations.ts';
3
+ import { type ClassInfo, type FunctionType, type Member } from './types.ts';
4
+ export declare abstract class ClassMemberChecker extends DeclarationChecker {
5
+ /** The signatures of getters and setters: their members have the type of the value. */
6
+ protected readonly accessorSignatures: Map<ast.MethodDeclaration, FunctionType>;
7
+ protected declareMember(info: ClassInfo, isStatic: boolean, name: ast.Identifier, member: Member): void;
8
+ /** `get area() number` and `set area(v number)` make one member, of the type of the value. */
9
+ protected declareAccessor(info: ClassInfo, node: ast.MethodDeclaration, accessor: 'get' | 'set'): void;
10
+ /** `constructor(private name string)`: the parameters that are properties of the class too. */
11
+ protected declareParamProperties(node: ast.ConstructorDeclaration, info: ClassInfo, ctor: FunctionType): void;
12
+ /**
13
+ * Only an abstract class has abstract methods, and a class that is not abstract implements
14
+ * those of its bases.
15
+ */
16
+ protected checkAbstract(node: ast.ClassDeclaration, info: ClassInfo): void;
17
+ /**
18
+ * In JS a field is set on each instance and so hides a getter or setter of the prototype: a
19
+ * field and an accessor of the same name in a class and its base would not work together.
20
+ */
21
+ protected checkAccessorOverride(info: ClassInfo, name: ast.Identifier, isStatic: boolean): void;
22
+ }