rooklang 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/README.md +32 -7
  2. package/README.ru.md +32 -7
  3. package/dist/ast/declarations.d.ts +20 -1
  4. package/dist/ast/expressions.d.ts +31 -1
  5. package/dist/ast/markup.d.ts +6 -1
  6. package/dist/ast/statements.d.ts +25 -1
  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 +7 -0
  16. package/dist/checker/assignments.js +48 -8
  17. package/dist/checker/base.d.ts +2 -1
  18. package/dist/checker/base.js +6 -12
  19. package/dist/checker/builtins.js +15 -5
  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 +16 -2
  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 +41 -12
  33. package/dist/checker/closing.d.ts +21 -0
  34. package/dist/checker/closing.js +164 -0
  35. package/dist/checker/completion.d.ts +2 -2
  36. package/dist/checker/completion.js +2 -2
  37. package/dist/checker/context.d.ts +19 -0
  38. package/dist/checker/control-flow.d.ts +6 -0
  39. package/dist/checker/control-flow.js +34 -0
  40. package/dist/checker/declarations.js +1 -0
  41. package/dist/checker/decode.d.ts +8 -0
  42. package/dist/checker/decode.js +32 -0
  43. package/dist/checker/error-results.d.ts +6 -0
  44. package/dist/checker/error-results.js +31 -0
  45. package/dist/checker/expressions.d.ts +5 -1
  46. package/dist/checker/expressions.js +13 -34
  47. package/dist/checker/flow.js +8 -4
  48. package/dist/checker/helpers.d.ts +1 -1
  49. package/dist/checker/helpers.js +9 -2
  50. package/dist/checker/iterables.d.ts +3 -1
  51. package/dist/checker/iterables.js +13 -0
  52. package/dist/checker/iterators.d.ts +4 -1
  53. package/dist/checker/iterators.js +12 -4
  54. package/dist/checker/json.d.ts +5 -0
  55. package/dist/checker/json.js +92 -0
  56. package/dist/checker/loops.js +23 -3
  57. package/dist/checker/members.js +7 -0
  58. package/dist/checker/operators.d.ts +2 -2
  59. package/dist/checker/operators.js +7 -5
  60. package/dist/checker/queues.d.ts +2 -2
  61. package/dist/checker/queues.js +2 -2
  62. package/dist/checker/resolution.js +5 -3
  63. package/dist/checker/signals.d.ts +8 -0
  64. package/dist/checker/signals.js +48 -0
  65. package/dist/checker/statements.d.ts +5 -0
  66. package/dist/checker/statements.js +19 -0
  67. package/dist/checker/topics.d.ts +6 -0
  68. package/dist/checker/topics.js +17 -0
  69. package/dist/checker/transfer.d.ts +3 -1
  70. package/dist/checker/transfer.js +25 -5
  71. package/dist/checker/try.d.ts +6 -0
  72. package/dist/checker/try.js +19 -0
  73. package/dist/checker/types/constructors.d.ts +1 -1
  74. package/dist/checker/types/constructors.js +2 -2
  75. package/dist/checker/types/generics.js +1 -1
  76. package/dist/checker/types/iterables.d.ts +13 -0
  77. package/dist/checker/types/iterables.js +52 -0
  78. package/dist/checker/types/model.d.ts +25 -0
  79. package/dist/checker/types/params.js +22 -3
  80. package/dist/checker/types/printing.js +4 -3
  81. package/dist/checker/types/relations.js +19 -25
  82. package/dist/checker/types/topic.d.ts +6 -0
  83. package/dist/checker/types/topic.js +23 -0
  84. package/dist/checker/types.d.ts +2 -0
  85. package/dist/checker/types.js +2 -0
  86. package/dist/cli.js +18 -36
  87. package/dist/codegen/calls.d.ts +23 -0
  88. package/dist/codegen/calls.js +88 -7
  89. package/dist/codegen/control-statements.d.ts +19 -3
  90. package/dist/codegen/control-statements.js +105 -20
  91. package/dist/codegen/decode.d.ts +14 -0
  92. package/dist/codegen/decode.js +93 -0
  93. package/dist/codegen/emitter.d.ts +10 -41
  94. package/dist/codegen/emitter.js +9 -4
  95. package/dist/codegen/expressions.d.ts +9 -0
  96. package/dist/codegen/expressions.js +15 -24
  97. package/dist/codegen/js.d.ts +2 -2
  98. package/dist/codegen/js.js +5 -2
  99. package/dist/codegen/options.d.ts +40 -0
  100. package/dist/codegen/options.js +1 -0
  101. package/dist/codegen/runtime.d.ts +7 -5
  102. package/dist/codegen/runtime.js +28 -20
  103. package/dist/codegen/simple-statements.d.ts +10 -4
  104. package/dist/codegen/simple-statements.js +40 -15
  105. package/dist/codegen/statements.js +64 -4
  106. package/dist/codegen/try.d.ts +5 -0
  107. package/dist/codegen/try.js +40 -0
  108. package/dist/compile.d.ts +9 -0
  109. package/dist/compile.js +19 -6
  110. package/dist/completion.d.ts +4 -2
  111. package/dist/completion.js +10 -5
  112. package/dist/declarations.js +32 -6
  113. package/dist/decorator-copy.js +5 -2
  114. package/dist/goroutines.d.ts +1 -1
  115. package/dist/goroutines.js +5 -4
  116. package/dist/index.d.ts +6 -2
  117. package/dist/index.js +14 -5
  118. package/dist/lexer/base.d.ts +2 -0
  119. package/dist/lexer/base.js +6 -1
  120. package/dist/lexer/lexer.js +3 -0
  121. package/dist/lexer/literals.d.ts +5 -0
  122. package/dist/lexer/literals.js +51 -1
  123. package/dist/lexer/markup.js +3 -4
  124. package/dist/lexer/tables.js +2 -0
  125. package/dist/lexer/token.d.ts +4 -3
  126. package/dist/lexer/token.js +0 -2
  127. package/dist/loader.js +40 -13
  128. package/dist/parser/base.d.ts +4 -0
  129. package/dist/parser/base.js +2 -1
  130. package/dist/parser/classes.d.ts +2 -0
  131. package/dist/parser/classes.js +88 -23
  132. package/dist/parser/control-flow.d.ts +3 -1
  133. package/dist/parser/control-flow.js +21 -24
  134. package/dist/parser/declarations.d.ts +7 -4
  135. package/dist/parser/declarations.js +45 -6
  136. package/dist/parser/expressions.js +27 -3
  137. package/dist/parser/jumps.d.ts +11 -0
  138. package/dist/parser/jumps.js +77 -0
  139. package/dist/parser/literals.d.ts +9 -0
  140. package/dist/parser/literals.js +45 -6
  141. package/dist/parser/markup.js +1 -1
  142. package/dist/parser/simple-statements.d.ts +1 -1
  143. package/dist/parser/simple-statements.js +16 -9
  144. package/dist/parser/statements.d.ts +0 -3
  145. package/dist/parser/statements.js +22 -25
  146. package/dist/parser/syntax.d.ts +11 -1
  147. package/dist/parser/syntax.js +16 -2
  148. package/dist/parser/types.js +8 -3
  149. package/dist/project-modules.d.ts +12 -0
  150. package/dist/project-modules.js +106 -0
  151. package/dist/run-file.d.ts +6 -0
  152. package/dist/run-file.js +111 -0
  153. package/dist/runtime/sources.js +11 -5
  154. package/dist/runtime-module.d.ts +1 -0
  155. package/dist/runtime-module.js +3 -0
  156. package/dist/source-map.d.ts +17 -0
  157. package/dist/source-map.js +75 -0
  158. package/dist/ts-types.d.ts +4 -0
  159. package/dist/ts-types.js +20 -3
  160. package/dist/typescript/convert-objects.d.ts +2 -1
  161. package/dist/typescript/convert-objects.js +18 -9
  162. package/dist/vite.js +13 -5
  163. package/dist/walk-types.js +1 -0
  164. package/dist/walk.d.ts +0 -2
  165. package/dist/walk.js +11 -28
  166. package/package.json +1 -1
  167. package/src/runtime/chan.js +15 -5
  168. package/src/runtime/channel.js +28 -0
  169. package/src/runtime/classes.js +32 -0
  170. package/src/runtime/core.js +10 -1
  171. package/src/runtime/decode.js +190 -0
  172. package/src/runtime/go.js +14 -5
  173. package/src/runtime/index.js +6 -0
  174. package/src/runtime/select.js +14 -9
  175. package/src/runtime/signals.js +59 -0
  176. package/src/runtime/threads.js +32 -5
  177. package/src/runtime/topic.js +118 -0
  178. package/src/runtime/try.js +45 -0
@@ -10,5 +10,12 @@ export declare abstract class AssignmentChecker extends InitializerChecker {
10
10
  protected checkAssignment(node: ast.AssignmentStatement): void;
11
11
  /** The type a value assigned to `target` must have. */
12
12
  protected checkTarget(target: ast.Expression): Type;
13
+ /** `this.id = id` in the constructor of the class that has the readonly field `id`. */
14
+ private initializes;
13
15
  protected checkReturn(node: ast.ReturnStatement): void;
16
+ /**
17
+ * `return null, err` in a function of `(User, error)`: null takes the place of a value only
18
+ * together with an error that is not null. Returns whether the null is checked here.
19
+ */
20
+ private nullWithError;
14
21
  }
@@ -1,6 +1,7 @@
1
+ import { guardedByError, unpackedResults } from "./error-results.js";
1
2
  import { isUntyped, countValues, callName, isCall } from "./helpers.js";
2
3
  import { InitializerChecker } from "./initializers.js";
3
- import { BOOL, ANY, isAssignable, isNullable, nonNull, typeToString, UNKNOWN, } from "./types.js";
4
+ import { BOOL, ANY, isAssignable, isNullable, nonNull, nullable, typeToString, UNKNOWN, } from "./types.js";
4
5
  // Where values go: assignments to variables, fields and elements, and `return`. Both unpack a call
5
6
  // that returns several values.
6
7
  export class AssignmentChecker extends InitializerChecker {
@@ -26,12 +27,15 @@ export class AssignmentChecker extends InitializerChecker {
26
27
  this.error(`${prefix} ${callName(value)} returns ${countValues(returned)}`, value);
27
28
  return Array(count).fill(UNKNOWN);
28
29
  }
30
+ // `return f()` passes the results on as they are; variables get the values before an error
31
+ // as nullable (error-results.ts).
32
+ const types = context === 'return' ? type.types : unpackedResults(type.types);
29
33
  if (declared) {
30
- for (const element of type.types)
34
+ for (const element of types)
31
35
  this.expectAssignable(element, declared, value);
32
- return type.types.map(() => declared);
36
+ return types.map(() => declared);
33
37
  }
34
- return type.types;
38
+ return types;
35
39
  }
36
40
  checkAssignment(node) {
37
41
  const { targets, values, operator } = node;
@@ -115,6 +119,12 @@ export class AssignmentChecker extends InitializerChecker {
115
119
  this.error(`cannot assign to method "${target.property.name}"`, target);
116
120
  return UNKNOWN;
117
121
  }
122
+ if (member.accessor === 'get') {
123
+ this.error(`cannot assign to "${target.property.name}": it has a getter and no setter`, target);
124
+ }
125
+ else if (member.readonly && !this.initializes(target, member)) {
126
+ this.error(`cannot assign to "${target.property.name}": it is readonly, so only the constructor of ${member.owner?.name ?? 'its class'} assigns it`, target);
127
+ }
118
128
  return member.type;
119
129
  }
120
130
  case 'IndexExpression': {
@@ -137,6 +147,14 @@ export class AssignmentChecker extends InitializerChecker {
137
147
  return UNKNOWN;
138
148
  }
139
149
  }
150
+ /** `this.id = id` in the constructor of the class that has the readonly field `id`. */
151
+ initializes(target, member) {
152
+ return (target.object.kind === 'ThisExpression' &&
153
+ this.fn?.isConstructor === true &&
154
+ this.cls !== null &&
155
+ !this.cls.isStatic &&
156
+ this.cls.info === member.owner);
157
+ }
140
158
  checkReturn(node) {
141
159
  const fn = this.fn;
142
160
  if (!fn)
@@ -187,11 +205,33 @@ export class AssignmentChecker extends InitializerChecker {
187
205
  if (values.length !== expected.length) {
188
206
  this.error(`wrong number of return values: expected ${expected.length}, got ${values.length}`, node);
189
207
  }
190
- values.forEach((value, i) => {
208
+ const types = values.map((value, i) => this.checkValue(value, expected[i] ?? null));
209
+ types.forEach((type, i) => {
191
210
  const target = expected[i];
192
- const type = this.checkValue(value, target ?? null);
193
- if (target)
194
- this.expectAssignable(type, target, value, ' in return');
211
+ if (!target)
212
+ return;
213
+ if (type.kind === 'null' && this.nullWithError(expected, types, values, i))
214
+ return;
215
+ this.expectAssignable(type, target, values[i], ' in return');
195
216
  });
196
217
  }
218
+ /**
219
+ * `return null, err` in a function of `(User, error)`: null takes the place of a value only
220
+ * together with an error that is not null. Returns whether the null is checked here.
221
+ */
222
+ nullWithError(expected, types, values, i) {
223
+ const last = expected.length - 1;
224
+ if (!guardedByError(expected).includes(i) || types.length !== expected.length)
225
+ return false;
226
+ const error = types[last];
227
+ const target = typeToString(expected[i]);
228
+ if (error.kind === 'null') {
229
+ this.error(`cannot return null with no error: make the result ${typeToString(nullable(expected[i]))} if the value may be null without an error`, values[i]);
230
+ }
231
+ else if (isNullable(error)) {
232
+ const name = values[last].kind === 'Identifier' ? values[last].name : 'err';
233
+ this.error(`null in place of ${target} needs an error that is not null: check it first, e.g. if ${name} != null { return null, ${name} }`, values[i]);
234
+ }
235
+ return true;
236
+ }
197
237
  }
@@ -18,6 +18,8 @@ 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
+ /** The error for a value that may be null where it must not be. */
22
+ protected abstract nullError(node: ast.Expression): void;
21
23
  protected abstract checkFunction(params: readonly ast.Parameter[], type: FunctionType, results: Type[] | null, body: ast.BlockStatement | ast.Expression, options?: {
22
24
  isConstructor?: boolean;
23
25
  isComponent?: boolean;
@@ -94,7 +96,6 @@ export declare abstract class CheckerBase {
94
96
  constructor(program: ast.Program, options: CheckOptions);
95
97
  protected error(message: string, node: ast.NodeBase): void;
96
98
  protected expectAssignable(source: Type, target: Type, node: ast.NodeBase, context?: string): void;
97
- protected nullError(node: ast.Expression): void;
98
99
  protected withScope<T>(check: () => T): T;
99
100
  protected withClass<T>(context: ClassContext | null, check: () => T): T;
100
101
  protected lookupValue(name: string): Binding | undefined;
@@ -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 →
7
+ // declarations → class members → classes → resolution → initializers → assignments → statements → control flow →
8
+ // functions → expressions → array literals → operators → markup → forwarding → components → component values →
9
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.
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;
@@ -66,12 +66,6 @@ export class CheckerBase {
66
66
  }
67
67
  this.error(message, node);
68
68
  }
69
- nullError(node) {
70
- const name = node.kind === 'Identifier' ? node.name : null;
71
- this.error(name !== null
72
- ? `"${name}" may be null: check it with "if ${name} != null" or use "?."`
73
- : 'this value may be null: check it for null or use "?."', node);
74
- }
75
69
  withScope(check) {
76
70
  const saved = this.scope;
77
71
  this.scope = new Scope(saved);
@@ -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
9
  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";
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,19 +227,28 @@ 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], [])],
250
+ ['Channel', CHANNEL_OBJECT],
251
+ ['Topic', TOPIC_OBJECT],
242
252
  ...[...UNTYPED_GLOBALS, ...BUFFERS].map((name) => [name, ANY]),
243
253
  ]);
244
254
  /** Built-in types that a library from TypeScript's lib files replaces: untyped ones and the DOM. */
@@ -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,18 @@
1
1
  import { assignedNames } from "../names.js";
2
+ import { cannotWait } from "../parser/syntax.js";
2
3
  import { chanDirection, chanOf, elementOf, isChan } from "./channel.js";
3
4
  import { GoroutineChecker } from "./goroutines.js";
4
5
  import { isUntyped } from "./helpers.js";
5
6
  import { isPromise } from "./promise.js";
6
7
  import { BUFFERS } from "./transfer.js";
7
- import { ANY, BOOL, ERROR, instantiate, isNullable, nonNull, NUMBER, typeToString, typedArrayOf, UNKNOWN, } from "./types.js";
8
+ import { ANY, BOOL, ERROR, instantiate, isNullable, isTopic, nonNull, NUMBER, topicElement, typeToString, typedArrayOf, UNKNOWN, } from "./types.js";
8
9
  // Channels: `chan<number>(10)` makes one, `ch <- v` sends a value, `<-ch` receives one (in
9
10
  // operators.ts), `for v in ch` receives them until it is closed (in control-flow.ts), and
10
11
  // `select` waits for the first of several. `transfer(x)` moves a buffer to another thread.
11
12
  // `chan(stream)` receives the chunks of a ReadableStream, and `stream(ch)` reads a channel.
12
13
  /** What `transfer(x)` moves: a buffer, or a view whose buffer moves. */
13
14
  const MOVABLE = new Set(BUFFERS);
14
- function typeName(type) {
15
+ export function typeName(type) {
15
16
  if (type.kind === 'class')
16
17
  return type.info.name;
17
18
  if (type.kind === 'object')
@@ -50,11 +51,20 @@ export class ChannelChecker extends GoroutineChecker {
50
51
  this.expectAssignable(this.checkValue(node.size, NUMBER), NUMBER, node.size);
51
52
  return { kind: 'tuple', types: [chanOf(element), chanOf(ERROR, 'receive')] };
52
53
  }
54
+ // A send to a channel waits until the channel takes the value, and one to a topic waits for
55
+ // nobody, so only the first needs an async function.
53
56
  checkSend(node) {
54
57
  const nullChannel = this.inSelectCase;
55
58
  this.inSelectCase = false;
56
59
  const type = this.checkValue(node.channel);
57
60
  const channel = nullChannel && isNullable(type) ? nonNull(type) : type;
61
+ if (isTopic(channel)) {
62
+ const element = topicElement(channel);
63
+ this.expectAssignable(this.checkValue(node.value, element), element, node.value);
64
+ return;
65
+ }
66
+ if (node.cannotWait)
67
+ this.error(cannotWait('<-', node.cannotWait.inDefer), node.cannotWait);
58
68
  if (!isChan(channel)) {
59
69
  if (!isUntyped(channel)) {
60
70
  this.error(`"<-" sends to a channel, not to ${typeToString(channel)}`, node.channel);
@@ -114,6 +124,10 @@ export class ChannelChecker extends GoroutineChecker {
114
124
  this.inSelectCase = operation !== null;
115
125
  if (operation?.kind === 'SendStatement') {
116
126
  this.checkSend(operation);
127
+ const channel = this.checkedTypes.get(operation.channel);
128
+ if (channel && isTopic(isNullable(channel) ? nonNull(channel) : channel)) {
129
+ 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);
130
+ }
117
131
  }
118
132
  else if (operation) {
119
133
  // `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
+ }
@@ -0,0 +1,105 @@
1
+ import { DeclarationChecker } from "./declarations.js";
2
+ import { notCopied } from "./transfer.js";
3
+ import { findClassMember, isAssignable, typeToString, UNKNOWN, } from "./types.js";
4
+ // The members of classes that are more than a field or a method: getters and setters, the
5
+ // properties of constructor parameters, abstract methods; ClassChecker declares and checks them.
6
+ export class ClassMemberChecker extends DeclarationChecker {
7
+ /** The signatures of getters and setters: their members have the type of the value. */
8
+ accessorSignatures = new Map();
9
+ declareMember(info, isStatic, name, member) {
10
+ const members = isStatic ? info.statics : info.members;
11
+ if (members.has(name.name))
12
+ this.error(`duplicate member "${name.name}"`, name);
13
+ members.set(name.name, member);
14
+ }
15
+ /** `get area() number` and `set area(v number)` make one member, of the type of the value. */
16
+ declareAccessor(info, node, accessor) {
17
+ const signature = this.signature(node.params, node.results);
18
+ const type = (accessor === 'get' ? signature.results[0] : signature.params[0]) ?? UNKNOWN;
19
+ const members = node.isStatic ? info.statics : info.members;
20
+ const other = members.get(node.name.name);
21
+ this.accessorSignatures.set(node, signature);
22
+ if (other?.owner === info &&
23
+ (other.accessor === 'get' || other.accessor === 'set') &&
24
+ other.accessor !== accessor) {
25
+ const [getter, setter] = accessor === 'get' ? [type, other.type] : [other.type, type];
26
+ if (!isAssignable(getter, setter) || !isAssignable(setter, getter)) {
27
+ this.error(`the getter and the setter of "${node.name.name}" have different types: ` +
28
+ `${typeToString(getter)} and ${typeToString(setter)}`, node.name);
29
+ }
30
+ members.set(node.name.name, { ...other, type: getter, accessor: 'both' });
31
+ return;
32
+ }
33
+ this.declareMember(info, node.isStatic, node.name, {
34
+ type,
35
+ method: false,
36
+ visibility: node.visibility,
37
+ owner: info,
38
+ accessor,
39
+ ...(node.abstract ? { abstract: true } : {}),
40
+ });
41
+ }
42
+ /** `constructor(private name string)`: the parameters that are properties of the class too. */
43
+ declareParamProperties(node, info, ctor) {
44
+ node.params.forEach((param, i) => {
45
+ if (!param.property)
46
+ return;
47
+ const type = ctor.params[i] ?? UNKNOWN;
48
+ const reason = info.data === true ? notCopied(type) : null;
49
+ if (reason !== null) {
50
+ this.error(`the fields of a data class go to other threads as copies: ${reason}`, param.type ?? param.name);
51
+ }
52
+ this.declareMember(info, false, param.name, {
53
+ type,
54
+ method: false,
55
+ visibility: param.property.visibility,
56
+ owner: info,
57
+ ...(param.property.readonly ? { readonly: true } : {}),
58
+ });
59
+ });
60
+ }
61
+ /**
62
+ * Only an abstract class has abstract methods, and a class that is not abstract implements
63
+ * those of its bases.
64
+ */
65
+ checkAbstract(node, info) {
66
+ if (node.abstract)
67
+ return;
68
+ for (const member of node.members) {
69
+ if (member.kind !== 'MethodDeclaration' || !member.abstract)
70
+ continue;
71
+ this.error(`"${member.name.name}" is abstract, so its class must be too: abstract class ${info.name}`, member.name);
72
+ }
73
+ const missing = new Set();
74
+ for (let base = info.superClass; base; base = base.superClass) {
75
+ for (const [name, member] of base.members) {
76
+ const found = member.abstract ? findClassMember(info, name, false) : undefined;
77
+ if (found?.abstract)
78
+ missing.add(`${found.owner?.name ?? base.name}.${name}`);
79
+ }
80
+ }
81
+ if (missing.size === 0)
82
+ return;
83
+ this.error(`class "${info.name}" does not implement ${[...missing].join(', ')}: implement ` +
84
+ `${missing.size === 1 ? 'it' : 'them'} or make the class abstract, e.g. abstract class ${info.name}`, node.name);
85
+ }
86
+ /**
87
+ * In JS a field is set on each instance and so hides a getter or setter of the prototype: a
88
+ * field and an accessor of the same name in a class and its base would not work together.
89
+ */
90
+ checkAccessorOverride(info, name, isStatic) {
91
+ const own = (isStatic ? info.statics : info.members).get(name.name);
92
+ const base = info.superClass && findClassMember(info.superClass, name.name, isStatic);
93
+ // Only the classes of Rook have `data` set: lib.dom declares many accessors as fields.
94
+ if (!own || !base || own.method || base.method || base.owner?.data === undefined)
95
+ return;
96
+ if (own.accessor && !base.accessor) {
97
+ this.error(`"${name.name}" is a field in ${base.owner.name}, which would hide a getter or setter ` +
98
+ 'here: the field is set on each instance; override it with a field', name);
99
+ }
100
+ else if (!own.accessor && base.accessor) {
101
+ this.error(`"${name.name}" is a getter or setter in ${base.owner.name}, which a field here would ` +
102
+ 'hide: override it with a getter or setter', name);
103
+ }
104
+ }
105
+ }
@@ -1,14 +1,15 @@
1
1
  import type * as ast from '../ast.ts';
2
2
  import { type ModulePackages } from '../inline-packages.ts';
3
3
  import type { ModuleExports } from './context.ts';
4
- import { DeclarationChecker } from './declarations.ts';
5
- import { type ClassInfo, type Member } from './types.ts';
6
- export declare abstract class ClassChecker extends DeclarationChecker {
4
+ import { ClassMemberChecker } from './class-members.ts';
5
+ import { type ClassInfo } from './types.ts';
6
+ export declare abstract class ClassChecker extends ClassMemberChecker {
7
7
  protected ensureClassResolved(info: ClassInfo): void;
8
8
  /** Resolves the base class and the types of all members (but does not check method bodies). */
9
9
  protected resolveClass(node: ast.ClassDeclaration, info: ClassInfo): void;
10
10
  protected resolveClassMembers(node: ast.ClassDeclaration, info: ClassInfo): void;
11
- protected declareMember(info: ClassInfo, isStatic: boolean, name: ast.Identifier, member: Member): void;
11
+ /** A data class extends only a data class: its fields go with it, and its class is restored. */
12
+ private checkDataBase;
12
13
  /** Method bodies and the checks that need every member of the class. */
13
14
  protected checkClassBodies(node: ast.ClassDeclaration, info: ClassInfo): void;
14
15
  protected checkClassMembers(node: ast.ClassDeclaration, info: ClassInfo): void;