rooklang 0.3.3 → 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 (170) 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 +2 -0
  16. package/dist/checker/assignments.js +14 -0
  17. package/dist/checker/base.js +6 -6
  18. package/dist/checker/builtins.js +15 -5
  19. package/dist/checker/bytes.d.ts +2 -2
  20. package/dist/checker/bytes.js +2 -2
  21. package/dist/checker/calls.js +3 -0
  22. package/dist/checker/channel-objects.d.ts +3 -0
  23. package/dist/checker/channel-objects.js +40 -0
  24. package/dist/checker/channels.d.ts +1 -0
  25. package/dist/checker/channels.js +16 -2
  26. package/dist/checker/checker.d.ts +1 -1
  27. package/dist/checker/checker.js +19 -0
  28. package/dist/checker/class-members.d.ts +22 -0
  29. package/dist/checker/class-members.js +105 -0
  30. package/dist/checker/classes.d.ts +5 -4
  31. package/dist/checker/classes.js +41 -12
  32. package/dist/checker/closing.d.ts +21 -0
  33. package/dist/checker/closing.js +164 -0
  34. package/dist/checker/completion.d.ts +2 -2
  35. package/dist/checker/completion.js +2 -2
  36. package/dist/checker/context.d.ts +13 -0
  37. package/dist/checker/control-flow.d.ts +5 -0
  38. package/dist/checker/control-flow.js +17 -0
  39. package/dist/checker/declarations.js +1 -0
  40. package/dist/checker/decode.d.ts +8 -0
  41. package/dist/checker/decode.js +32 -0
  42. package/dist/checker/expressions.d.ts +5 -1
  43. package/dist/checker/expressions.js +13 -34
  44. package/dist/checker/flow.js +8 -4
  45. package/dist/checker/helpers.d.ts +1 -1
  46. package/dist/checker/helpers.js +9 -2
  47. package/dist/checker/iterables.d.ts +3 -1
  48. package/dist/checker/iterables.js +13 -0
  49. package/dist/checker/iterators.d.ts +4 -1
  50. package/dist/checker/iterators.js +12 -4
  51. package/dist/checker/json.d.ts +5 -0
  52. package/dist/checker/json.js +92 -0
  53. package/dist/checker/loops.js +23 -3
  54. package/dist/checker/members.js +7 -0
  55. package/dist/checker/operators.d.ts +2 -2
  56. package/dist/checker/operators.js +7 -5
  57. package/dist/checker/queues.d.ts +2 -2
  58. package/dist/checker/queues.js +2 -2
  59. package/dist/checker/resolution.js +5 -3
  60. package/dist/checker/signals.d.ts +8 -0
  61. package/dist/checker/signals.js +48 -0
  62. package/dist/checker/topics.d.ts +6 -0
  63. package/dist/checker/topics.js +17 -0
  64. package/dist/checker/transfer.d.ts +3 -1
  65. package/dist/checker/transfer.js +25 -5
  66. package/dist/checker/try.d.ts +6 -0
  67. package/dist/checker/try.js +19 -0
  68. package/dist/checker/types/constructors.d.ts +1 -1
  69. package/dist/checker/types/constructors.js +2 -2
  70. package/dist/checker/types/generics.js +1 -1
  71. package/dist/checker/types/iterables.d.ts +13 -0
  72. package/dist/checker/types/iterables.js +52 -0
  73. package/dist/checker/types/model.d.ts +25 -0
  74. package/dist/checker/types/params.js +22 -3
  75. package/dist/checker/types/printing.js +4 -3
  76. package/dist/checker/types/relations.js +19 -25
  77. package/dist/checker/types/topic.d.ts +6 -0
  78. package/dist/checker/types/topic.js +23 -0
  79. package/dist/checker/types.d.ts +2 -0
  80. package/dist/checker/types.js +2 -0
  81. package/dist/cli.js +18 -36
  82. package/dist/codegen/calls.d.ts +23 -0
  83. package/dist/codegen/calls.js +88 -7
  84. package/dist/codegen/control-statements.d.ts +10 -2
  85. package/dist/codegen/control-statements.js +56 -19
  86. package/dist/codegen/decode.d.ts +14 -0
  87. package/dist/codegen/decode.js +93 -0
  88. package/dist/codegen/emitter.d.ts +10 -41
  89. package/dist/codegen/emitter.js +9 -4
  90. package/dist/codegen/expressions.d.ts +9 -0
  91. package/dist/codegen/expressions.js +15 -24
  92. package/dist/codegen/js.d.ts +2 -2
  93. package/dist/codegen/js.js +5 -2
  94. package/dist/codegen/options.d.ts +40 -0
  95. package/dist/codegen/options.js +1 -0
  96. package/dist/codegen/runtime.d.ts +7 -3
  97. package/dist/codegen/runtime.js +27 -13
  98. package/dist/codegen/simple-statements.d.ts +10 -4
  99. package/dist/codegen/simple-statements.js +40 -15
  100. package/dist/codegen/statements.js +64 -4
  101. package/dist/codegen/try.d.ts +5 -0
  102. package/dist/codegen/try.js +40 -0
  103. package/dist/compile.d.ts +9 -0
  104. package/dist/compile.js +19 -6
  105. package/dist/completion.d.ts +4 -2
  106. package/dist/completion.js +10 -5
  107. package/dist/declarations.js +32 -6
  108. package/dist/decorator-copy.js +5 -2
  109. package/dist/goroutines.d.ts +1 -1
  110. package/dist/goroutines.js +5 -4
  111. package/dist/index.d.ts +6 -2
  112. package/dist/index.js +14 -5
  113. package/dist/lexer/base.d.ts +2 -0
  114. package/dist/lexer/base.js +6 -1
  115. package/dist/lexer/lexer.js +3 -0
  116. package/dist/lexer/literals.d.ts +5 -0
  117. package/dist/lexer/literals.js +51 -1
  118. package/dist/lexer/markup.js +3 -4
  119. package/dist/lexer/tables.js +2 -0
  120. package/dist/lexer/token.d.ts +4 -3
  121. package/dist/lexer/token.js +0 -2
  122. package/dist/loader.js +30 -8
  123. package/dist/parser/base.d.ts +4 -0
  124. package/dist/parser/base.js +2 -1
  125. package/dist/parser/classes.d.ts +2 -0
  126. package/dist/parser/classes.js +88 -23
  127. package/dist/parser/control-flow.d.ts +3 -1
  128. package/dist/parser/control-flow.js +21 -24
  129. package/dist/parser/declarations.d.ts +7 -4
  130. package/dist/parser/declarations.js +45 -6
  131. package/dist/parser/expressions.js +27 -3
  132. package/dist/parser/jumps.d.ts +11 -0
  133. package/dist/parser/jumps.js +77 -0
  134. package/dist/parser/literals.d.ts +9 -0
  135. package/dist/parser/literals.js +45 -6
  136. package/dist/parser/markup.js +1 -1
  137. package/dist/parser/simple-statements.d.ts +1 -1
  138. package/dist/parser/simple-statements.js +16 -9
  139. package/dist/parser/statements.d.ts +0 -3
  140. package/dist/parser/statements.js +22 -25
  141. package/dist/parser/syntax.d.ts +11 -1
  142. package/dist/parser/syntax.js +16 -2
  143. package/dist/parser/types.js +8 -3
  144. package/dist/project-modules.d.ts +12 -0
  145. package/dist/project-modules.js +106 -0
  146. package/dist/run-file.d.ts +6 -0
  147. package/dist/run-file.js +111 -0
  148. package/dist/runtime/sources.js +11 -5
  149. package/dist/source-map.d.ts +17 -0
  150. package/dist/source-map.js +75 -0
  151. package/dist/ts-types.js +4 -1
  152. package/dist/typescript/convert-objects.d.ts +2 -1
  153. package/dist/typescript/convert-objects.js +18 -9
  154. package/dist/vite.js +12 -4
  155. package/dist/walk-types.js +1 -0
  156. package/dist/walk.d.ts +0 -2
  157. package/dist/walk.js +11 -28
  158. package/package.json +1 -1
  159. package/src/runtime/chan.js +15 -5
  160. package/src/runtime/channel.js +28 -0
  161. package/src/runtime/classes.js +32 -0
  162. package/src/runtime/core.js +10 -1
  163. package/src/runtime/decode.js +190 -0
  164. package/src/runtime/go.js +3 -0
  165. package/src/runtime/index.js +6 -0
  166. package/src/runtime/select.js +14 -9
  167. package/src/runtime/signals.js +59 -0
  168. package/src/runtime/threads.js +32 -5
  169. package/src/runtime/topic.js +118 -0
  170. package/src/runtime/try.js +45 -0
@@ -119,6 +119,12 @@ export class AssignmentChecker extends InitializerChecker {
119
119
  this.error(`cannot assign to method "${target.property.name}"`, target);
120
120
  return UNKNOWN;
121
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
+ }
122
128
  return member.type;
123
129
  }
124
130
  case 'IndexExpression': {
@@ -141,6 +147,14 @@ export class AssignmentChecker extends InitializerChecker {
141
147
  return UNKNOWN;
142
148
  }
143
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
+ }
144
158
  checkReturn(node) {
145
159
  const fn = this.fn;
146
160
  if (!fn)
@@ -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;
@@ -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;
@@ -1,9 +1,10 @@
1
1
  import { inlinePackages } from "../inline-packages.js";
2
- import { DeclarationChecker } from "./declarations.js";
2
+ import { ClassMemberChecker } from "./class-members.js";
3
3
  import { isSuperCall, assignsField } from "./helpers.js";
4
+ import { notCopied } from "./transfer.js";
4
5
  import { explainMismatch, findClassMember, hasZeroValue, isAssignable, isSubclass, typeToString, UNKNOWN, } from "./types.js";
5
6
  // Classes: their base class and members, method bodies, overrides and `implements`; and the declarations a module exports.
6
- export class ClassChecker extends DeclarationChecker {
7
+ export class ClassChecker extends ClassMemberChecker {
7
8
  ensureClassResolved(info) {
8
9
  const resolve = this.unresolvedClasses.get(info);
9
10
  if (!resolve || this.resolvingClasses.has(info))
@@ -49,6 +50,9 @@ export class ClassChecker extends DeclarationChecker {
49
50
  this.error(`cannot extend ${typeToString(base)}: it is not a class`, node.superClass);
50
51
  }
51
52
  }
53
+ if (node.data)
54
+ this.checkDataBase(node, info);
55
+ info.abstract = node.abstract === true;
52
56
  for (const member of node.members) {
53
57
  switch (member.kind) {
54
58
  case 'FieldDeclaration': {
@@ -62,20 +66,31 @@ export class ClassChecker extends DeclarationChecker {
62
66
  else
63
67
  type = this.inferredType(valueType, value);
64
68
  }
69
+ // The fields of a data class are what goes to another thread.
70
+ const reason = node.data && !member.isStatic ? notCopied(type) : null;
71
+ if (reason !== null) {
72
+ this.error(`the fields of a data class go to other threads as copies: ${reason}`, member.type ?? member.name);
73
+ }
65
74
  this.declareMember(info, member.isStatic, member.name, {
66
75
  type,
67
76
  method: false,
68
77
  visibility: member.visibility,
69
78
  owner: info,
79
+ ...(member.readonly ? { readonly: true } : {}),
70
80
  });
71
81
  break;
72
82
  }
73
83
  case 'MethodDeclaration':
84
+ if (member.accessor) {
85
+ this.declareAccessor(info, member, member.accessor);
86
+ break;
87
+ }
74
88
  this.declareMember(info, member.isStatic, member.name, {
75
89
  type: this.signature(member.params, member.results, member.async),
76
90
  method: true,
77
91
  visibility: member.visibility,
78
92
  owner: info,
93
+ ...(member.abstract ? { abstract: true } : {}),
79
94
  });
80
95
  break;
81
96
  case 'ConstructorDeclaration':
@@ -83,15 +98,20 @@ export class ClassChecker extends DeclarationChecker {
83
98
  this.error('a class can have only one constructor', member);
84
99
  info.ctor = this.signature(member.params, []);
85
100
  info.ctorVisibility = member.visibility;
101
+ this.declareParamProperties(member, info, info.ctor);
86
102
  break;
87
103
  }
88
104
  }
89
105
  }
90
- declareMember(info, isStatic, name, member) {
91
- const members = isStatic ? info.statics : info.members;
92
- if (members.has(name.name))
93
- this.error(`duplicate member "${name.name}"`, name);
94
- members.set(name.name, member);
106
+ /** A data class extends only a data class: its fields go with it, and its class is restored. */
107
+ checkDataBase(node, info) {
108
+ const base = info.superClass;
109
+ if (info.untypedBase) {
110
+ this.error('a data class cannot extend a class of JS: its copy in another thread could not get the class back', node.superClass);
111
+ }
112
+ else if (base && base.data !== true) {
113
+ this.error(`a data class extends only a data class: mark ${base.name} as one, e.g. data class ${base.name}`, node.superClass);
114
+ }
95
115
  }
96
116
  /** Method bodies and the checks that need every member of the class. */
97
117
  checkClassBodies(node, info) {
@@ -101,13 +121,18 @@ export class ClassChecker extends DeclarationChecker {
101
121
  let constructorNode = null;
102
122
  for (const member of node.members) {
103
123
  if (member.kind === 'MethodDeclaration') {
104
- const type = (member.isStatic ? info.statics : info.members).get(member.name.name)?.type;
124
+ const { body } = member;
125
+ const type = member.accessor
126
+ ? this.accessorSignatures.get(member)
127
+ : (member.isStatic ? info.statics : info.members).get(member.name.name)?.type;
105
128
  if (type?.kind !== 'function')
106
129
  continue;
107
- this.withClass({ info, isStatic: member.isStatic }, () => this.checkFunction(member.params, type, type.results, member.body, {
108
- async: member.async,
109
- }));
130
+ if (body) {
131
+ this.withClass({ info, isStatic: member.isStatic }, () => this.checkFunction(member.params, type, type.results, body, { async: member.async }));
132
+ }
110
133
  this.checkOverride(info, member);
134
+ if (member.accessor)
135
+ this.checkAccessorOverride(info, member.name, member.isStatic);
111
136
  }
112
137
  else if (member.kind === 'ConstructorDeclaration' && info.ctor) {
113
138
  constructorNode = member;
@@ -119,9 +144,13 @@ export class ClassChecker extends DeclarationChecker {
119
144
  }
120
145
  }
121
146
  }
147
+ this.checkAbstract(node, info);
122
148
  // Fields without a zero value must get one at once or in the constructor.
123
149
  for (const member of node.members) {
124
- if (member.kind !== 'FieldDeclaration' || member.isStatic || member.value)
150
+ if (member.kind !== 'FieldDeclaration')
151
+ continue;
152
+ this.checkAccessorOverride(info, member.name, member.isStatic);
153
+ if (member.isStatic || member.value)
125
154
  continue;
126
155
  const type = info.members.get(member.name.name)?.type;
127
156
  if (!type || hasZeroValue(type))
@@ -0,0 +1,21 @@
1
+ import type * as ast from '../ast.ts';
2
+ import type { Binding, BindingKind } from './context.ts';
3
+ import { IteratorChecker } from './iterators.ts';
4
+ import type { Type } from './types.ts';
5
+ export declare abstract class ClosingChecker extends IteratorChecker {
6
+ /** The identifier that declares a binding of this module. */
7
+ private readonly declarations;
8
+ /** What each name refers to: its declaration, or the name of a predeclared value. */
9
+ private readonly references;
10
+ protected declareValue(id: ast.Identifier, kind: BindingKind, type: Type | null): Binding;
11
+ protected checkIdentifier(node: ast.Identifier): Type;
12
+ /** The functions declared in the module, by their names, which `use` looks arguments up in. */
13
+ private functions;
14
+ protected checkClosing(): void;
15
+ /** `chan<T>()`, `topic<T>()`, `tick(ms)`, `signal(name)` and their `Channel.make` and the like. */
16
+ private made;
17
+ /** `Channel.close` and the like, as a name, when `Channel` is the predeclared one. */
18
+ private predeclaredMember;
19
+ /** What the code around a name of a channel does with it. */
20
+ private use;
21
+ }