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
@@ -0,0 +1,92 @@
1
+ import { isQueue, isTopic, memberNames, typedArrayOf, typeToString } from "./types.js";
2
+ // What JSON can hold, for `decode<T>(x)` (decode.ts): numbers, strings, bool, null, literals,
3
+ // arrays, unions and objects without methods. A string is JSON text to decode.
4
+ /** Types that JSON has no way to hold, by name, with what to decode instead. */
5
+ const NOT_IN_JSON = new Map([
6
+ ['Date', 'JSON has no Date: decode a string and make the Date from it, e.g. new Date(s)'],
7
+ ['Map', 'JSON has no Map: decode an object or an array of pairs and make the Map from it'],
8
+ ['Set', 'JSON has no Set: decode an array and make the Set from it, e.g. new Set(items)'],
9
+ ['RegExp', 'JSON has no RegExp: decode a string and make it with new RegExp(s)'],
10
+ ['Promise', 'a Promise cannot come from JSON'],
11
+ ['chan', 'a channel cannot come from JSON'],
12
+ ['chan<-', 'a channel cannot come from JSON'],
13
+ ['<-chan', 'a channel cannot come from JSON'],
14
+ ]);
15
+ /** Why a value of the type cannot come from JSON, or null if it can. */
16
+ export function notDecoded(type, seen = new Set()) {
17
+ if (seen.has(type))
18
+ return null;
19
+ seen.add(type);
20
+ const inner = (types) => {
21
+ for (const each of types) {
22
+ const reason = notDecoded(each, seen);
23
+ if (reason !== null)
24
+ return reason;
25
+ }
26
+ return null;
27
+ };
28
+ switch (type.kind) {
29
+ case 'number':
30
+ case 'string':
31
+ case 'bool':
32
+ case 'null':
33
+ case 'literal':
34
+ case 'any':
35
+ case 'unknown':
36
+ return null;
37
+ case 'bigint':
38
+ return 'JSON has no bigint: decode a string and make it with BigInt(s)';
39
+ case 'nullable':
40
+ return inner([type.type]);
41
+ case 'array':
42
+ return inner([type.element]);
43
+ case 'union':
44
+ return inner(type.types);
45
+ case 'param':
46
+ return `${type.name} is a type parameter: decode needs a type known when the program compiles`;
47
+ case 'function':
48
+ return 'a function cannot come from JSON';
49
+ case 'component':
50
+ return 'a component cannot come from JSON';
51
+ case 'classValue':
52
+ return `the class ${type.info.name} cannot come from JSON`;
53
+ case 'class':
54
+ return (NOT_IN_JSON.get(type.info.name) ??
55
+ `decode gives plain objects, without the methods of the class ${type.info.name}: decode an interface with its fields`);
56
+ case 'object': {
57
+ const name = type.instanceOf?.template.name ?? type.name;
58
+ const known = name === null ? undefined : NOT_IN_JSON.get(name);
59
+ if (known !== undefined)
60
+ return known;
61
+ if (isQueue(type))
62
+ return 'JSON has no queue: decode an array and make the queue from it';
63
+ if (isTopic(type))
64
+ return 'a topic cannot come from JSON';
65
+ if (typedArrayOf(type) !== null)
66
+ return `JSON has no ${typeToString(type)}`;
67
+ if (type.call || type.construct) {
68
+ return `${typeToString(type)} is a function and cannot come from JSON`;
69
+ }
70
+ const members = [...memberNames(type.members)].flatMap((name) => type.members.get(name) ?? []);
71
+ if (members.some((member) => member.method)) {
72
+ return `${typeToString(type)} has methods, and JSON gives only data: decode an interface with its fields`;
73
+ }
74
+ return inner([...members.map((member) => member.type), ...(type.index ? [type.index] : [])]);
75
+ }
76
+ default:
77
+ return `${typeToString(type)} cannot come from JSON`;
78
+ }
79
+ }
80
+ /** Whether a value of the type is always a string: then decode reads it as JSON text. */
81
+ export function isText(type) {
82
+ switch (type.kind) {
83
+ case 'string':
84
+ return true;
85
+ case 'literal':
86
+ return typeof type.value === 'string';
87
+ case 'union':
88
+ return type.types.every(isText);
89
+ default:
90
+ return false;
91
+ }
92
+ }
@@ -1,10 +1,11 @@
1
1
  import { assignedNames } from "../names.js";
2
2
  import { BytesChecker } from "./bytes.js";
3
3
  import { chanDirection, elementOf, isChan } from "./channel.js";
4
- import { isMap, loopEntry } from "./iterables.js";
5
- import { ANY, isNullable, NUMBER, STRING, typeToString, UNKNOWN, } from "./types.js";
4
+ import { asyncIterated, isMap, loopEntry } from "./iterables.js";
5
+ import { ANY, isNullable, isTopic, NUMBER, STRING, topicElement, typeToString, UNKNOWN, } from "./types.js";
6
6
  // `for ... in`: over arrays, strings, the iterables of the lib files (a Map gives its keys and
7
- // values), channels, which wait for their values, and iterators, `func(yield func(T) bool)`.
7
+ // values), channels and async iterables, such as the streams of Node, which wait for their values,
8
+ // and iterators, `func(yield func(T) bool)`.
8
9
  export class LoopChecker extends BytesChecker {
9
10
  checkForIn(node) {
10
11
  const iterable = this.checkValue(node.iterable);
@@ -26,6 +27,16 @@ export class LoopChecker extends BytesChecker {
26
27
  this.error(`${typeToString(iterable)} has no keys: loop over its values, e.g. "for value in items"`, node.key);
27
28
  }
28
29
  }
30
+ else if (asyncIterated(iterable) !== null) {
31
+ // `for await` of JS: each value waits for the Promise of next().
32
+ value = asyncIterated(iterable);
33
+ if (this.fn && !this.fn.isAsync) {
34
+ this.error(`"for ... in" ${typeToString(iterable)} waits for its values, so it is allowed only in async functions and at the top level of a module`, node.iterable);
35
+ }
36
+ if (node.key) {
37
+ this.error(`${typeToString(iterable)} has no keys: loop over its values, e.g. "for value in items"`, node.key);
38
+ }
39
+ }
29
40
  else if (iterable.kind === 'function' && iterable.iterator) {
30
41
  [key, value] = this.yielded(iterable, node);
31
42
  }
@@ -41,6 +52,15 @@ export class LoopChecker extends BytesChecker {
41
52
  if (node.key)
42
53
  this.error('a channel has no keys: write "for value in ch"', node.key);
43
54
  }
55
+ else if (isTopic(iterable)) {
56
+ // Each value waits for another tab to send one, until the topic is closed.
57
+ value = topicElement(iterable);
58
+ if (this.fn && !this.fn.isAsync) {
59
+ this.error('"for ... in" a topic waits for its values, so it is allowed only in async functions and at the top level of a module', node.iterable);
60
+ }
61
+ if (node.key)
62
+ this.error('a topic has no keys: write "for value in t"', node.key);
63
+ }
44
64
  else if (iterable.kind === 'string') {
45
65
  value = STRING;
46
66
  }
@@ -39,6 +39,13 @@ export class MemberChecker extends DecoratorMemberChecker {
39
39
  }
40
40
  if (node.kind === 'MemberExpression') {
41
41
  const member = this.findMember(object, node.property.name, node.property);
42
+ const { name } = node.property;
43
+ if (member !== 'any' && member?.accessor === 'set') {
44
+ this.error(`cannot read "${name}": it has a setter and no getter`, node.property);
45
+ }
46
+ else if (member !== 'any' && member?.abstract && node.object.kind === 'SuperExpression') {
47
+ this.error(`super.${name} is abstract in ${member.owner?.name ?? 'the base class'}: it has no code to run`, node.property);
48
+ }
42
49
  return [member === 'any' ? ANY : (member?.type ?? UNKNOWN), shortCircuits];
43
50
  }
44
51
  const keyed = this.keyedIndex(object, node.index);
@@ -1,7 +1,7 @@
1
1
  import type * as ast from '../ast.ts';
2
- import { ExpressionChecker } from './expressions.ts';
2
+ import { ArrayLiteralChecker } from './array-literals.ts';
3
3
  import { type Type } from './types.ts';
4
- export declare abstract class OperatorChecker extends ExpressionChecker {
4
+ export declare abstract class OperatorChecker extends ArrayLiteralChecker {
5
5
  /** In the operation of a `select` case, a channel may be null: that case never goes on. */
6
6
  protected inSelectCase: boolean;
7
7
  protected checkUnary(node: ast.UnaryExpression): Type;
@@ -1,12 +1,12 @@
1
1
  import { bigintBinary, bigintUnary } from "./bigint.js";
2
- import { ExpressionChecker } from "./expressions.js";
2
+ import { ArrayLiteralChecker } from "./array-literals.js";
3
3
  import { withNarrowing } from "./flow.js";
4
4
  import { isUntyped, isNumeric, instanceTypeOf } from "./helpers.js";
5
5
  import { chanDirection, elementOf, isChan, received } from "./channel.js";
6
6
  import { awaited } from "./promise.js";
7
- import { ANY, BOOL, commonType, isAssignable, isComparable, isNullable, nonNull, nullable, NUMBER, STRING, typeToString, UNKNOWN, widenLiterals, } from "./types.js";
7
+ import { ANY, BOOL, commonType, isAssignable, isComparable, isNullable, isTopic, nonNull, nullable, NUMBER, STRING, topicElement, typeToString, UNKNOWN, widenLiterals, } from "./types.js";
8
8
  // Unary, binary and conditional operators.
9
- export class OperatorChecker extends ExpressionChecker {
9
+ export class OperatorChecker extends ArrayLiteralChecker {
10
10
  /** In the operation of a `select` case, a channel may be null: that case never goes on. */
11
11
  inSelectCase = false;
12
12
  checkUnary(node) {
@@ -19,8 +19,8 @@ export class OperatorChecker extends ExpressionChecker {
19
19
  return STRING;
20
20
  // What the Promise gives; the results of an async function are unpacked from it.
21
21
  case 'await':
22
- if (isChan(type)) {
23
- this.error('"await" does not wait for a channel: receive from it, e.g. "<-ch"', node);
22
+ if (isChan(type) || isTopic(type)) {
23
+ this.error(`"await" does not wait for a ${isChan(type) ? 'channel' : 'topic'}: receive from it, e.g. "<-ch"`, node);
24
24
  }
25
25
  return awaited(type);
26
26
  case '<-': {
@@ -31,6 +31,8 @@ export class OperatorChecker extends ExpressionChecker {
31
31
  }
32
32
  return received(elementOf(channel));
33
33
  }
34
+ if (isTopic(channel))
35
+ return received(topicElement(channel));
34
36
  if (!isUntyped(type)) {
35
37
  this.error(`"<-" receives from a channel, not from ${typeToString(type)}`, node.argument);
36
38
  }
@@ -1,6 +1,6 @@
1
1
  import type * as ast from '../ast.ts';
2
- import { ChannelChecker } from './channels.ts';
2
+ import { SignalChecker } from './signals.ts';
3
3
  import { type Type } from './types.ts';
4
- export declare abstract class QueueChecker extends ChannelChecker {
4
+ export declare abstract class QueueChecker extends SignalChecker {
5
5
  protected checkQueue(node: ast.QueueExpression): Type;
6
6
  }
@@ -1,9 +1,9 @@
1
- import { ChannelChecker } from "./channels.js";
1
+ import { SignalChecker } from "./signals.js";
2
2
  import { isUntyped } from "./helpers.js";
3
3
  import { arrayOf, queueOf, typeToString } from "./types.js";
4
4
  // Queues: `queue<number>()` makes an empty one, `queue<number>(items)` one with the values of an
5
5
  // array or of another iterable. The members of `queue T` are in types/queue.ts.
6
- export class QueueChecker extends ChannelChecker {
6
+ export class QueueChecker extends SignalChecker {
7
7
  checkQueue(node) {
8
8
  const element = this.resolveType(node.element);
9
9
  if (node.items) {
@@ -2,7 +2,7 @@ import { holdsBigInts, TYPED_ELEMENTS } from "../typed-arrays.js";
2
2
  import { chanOf } from "./channel.js";
3
3
  import { ClassChecker } from "./classes.js";
4
4
  import { CONTENT } from "./dom.js";
5
- import { ANY, arrayOf, BIGINT, bindParams, builtInTypedArray, classInstance, func, instantiate, isAssignable, isNullable, literal, nullable, NUMBER, queueOf, substitute, typeToString, union, UNKNOWN, } from "./types.js";
5
+ import { ANY, arrayOf, BIGINT, bindParams, builtInTypedArray, classInstance, func, instantiate, isAssignable, isNullable, literal, nullable, NUMBER, queueOf, topicOf, substitute, typeToString, union, UNKNOWN, } from "./types.js";
6
6
  // Type annotations as types: names, aliases, generics with their arguments; and the type a variable gets from its value.
7
7
  export class TypeResolver extends ClassChecker {
8
8
  resolveType(node) {
@@ -17,6 +17,8 @@ export class TypeResolver extends ClassChecker {
17
17
  return chanOf(this.resolveType(node.element), node.direction);
18
18
  case 'QueueType':
19
19
  return queueOf(this.resolveType(node.element));
20
+ case 'TopicType':
21
+ return topicOf(this.resolveType(node.element));
20
22
  case 'NullableType':
21
23
  return nullable(this.resolveType(node.type));
22
24
  case 'FuncType':
@@ -81,8 +83,8 @@ export class TypeResolver extends ClassChecker {
81
83
  if (entry === undefined) {
82
84
  this.error(this.lookupValue(name.name)
83
85
  ? `"${name.name}" is a value, not a type`
84
- : name.name === 'queue'
85
- ? 'a queue type is written with the type of its values after it, e.g. "queue number"'
86
+ : name.name === 'queue' || name.name === 'topic'
87
+ ? `a ${name.name} type is written with the type of its values after it, e.g. "${name.name} number"`
86
88
  : `unknown type "${name.name}"`, name);
87
89
  return UNKNOWN;
88
90
  }
@@ -0,0 +1,8 @@
1
+ import type * as ast from '../ast.ts';
2
+ import { ChannelChecker } from './channels.ts';
3
+ import { type Type } from './types.ts';
4
+ export declare abstract class SignalChecker extends ChannelChecker {
5
+ protected checkCall(node: ast.CallExpression, callee: Type, expected?: Type | null): Type;
6
+ /** `signal("SIGINT", "SIGTERM")`: at least one name, and the names are those of signals. */
7
+ private checkSignalNames;
8
+ }
@@ -0,0 +1,48 @@
1
+ import { ChannelChecker, typeName } from "./channels.js";
2
+ import { isUntyped } from "./helpers.js";
3
+ import { isNullable, typeToString } from "./types.js";
4
+ // Signals and cancellation as channels (runtime/signals.js): `signal("SIGTERM")` gets the signals
5
+ // of the process, `done(request.signal)` the reason of an AbortSignal, and `abortSignal(stop)` is an
6
+ // AbortSignal that aborts when the channel gets a value or is closed. Their types are in
7
+ // builtins.ts; the names that signal() takes, the AbortSignal that done() takes and the type that
8
+ // abortSignal() gives, from the library if it has one, are here.
9
+ const SIGNAL = /^SIG[A-Z0-9]+$/;
10
+ export class SignalChecker extends ChannelChecker {
11
+ checkCall(node, callee, expected = null) {
12
+ const result = super.checkCall(node, callee, expected);
13
+ const { callee: name } = node;
14
+ if (name.kind !== 'Identifier' ||
15
+ this.lookupValue(name.name) !== this.globals.values.get(name.name)) {
16
+ return result;
17
+ }
18
+ switch (name.name) {
19
+ case 'signal':
20
+ this.checkSignalNames(node);
21
+ return result;
22
+ case 'done': {
23
+ const [argument] = node.arguments;
24
+ const type = argument && argument.kind !== 'SpreadElement' && this.checkedTypes.get(argument);
25
+ if (argument && type && !isUntyped(type) && typeName(type) !== 'AbortSignal') {
26
+ this.error(`done() takes an AbortSignal, such as request.signal, not ${typeToString(type)}${isNullable(type) ? ': check it for null first' : ''}`, argument);
27
+ }
28
+ return result;
29
+ }
30
+ // An AbortSignal of the library, for `fetch(url, { signal: abortSignal(stop) })`.
31
+ case 'abortSignal':
32
+ return this.options.library?.type('AbortSignal') ?? result;
33
+ default:
34
+ return result;
35
+ }
36
+ }
37
+ /** `signal("SIGINT", "SIGTERM")`: at least one name, and the names are those of signals. */
38
+ checkSignalNames(node) {
39
+ if (node.arguments.length === 0) {
40
+ this.error('signal() takes the signals to listen to, e.g. signal("SIGINT", "SIGTERM")', node);
41
+ }
42
+ for (const argument of node.arguments) {
43
+ if (argument.kind === 'StringLiteral' && !SIGNAL.test(argument.value)) {
44
+ this.error(`"${argument.value}" is not the name of a signal: they start with SIG, e.g. signal("SIGINT", "SIGTERM")`, argument);
45
+ }
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,6 @@
1
+ import type * as ast from '../ast.ts';
2
+ import { QueueChecker } from './queues.ts';
3
+ import { type Type } from './types.ts';
4
+ export declare abstract class TopicChecker extends QueueChecker {
5
+ protected checkTopic(node: ast.TopicExpression): Type;
6
+ }
@@ -0,0 +1,17 @@
1
+ import { QueueChecker } from "./queues.js";
2
+ import { notBroadcast } from "./transfer.js";
3
+ import { STRING, topicOf } from "./types.js";
4
+ // Topics: `topic<T>(name)` makes one, and its values go to the other tabs of the site as copies,
5
+ // so they must be data without channels. `t <- v` sends (channels.ts), `<-t` receives
6
+ // (operators.ts) and `for v in t` receives them until it is closed (loops.ts).
7
+ export class TopicChecker extends QueueChecker {
8
+ checkTopic(node) {
9
+ const element = this.resolveType(node.element);
10
+ const reason = notBroadcast(element);
11
+ if (reason !== null) {
12
+ this.error(`the values of a topic go to other tabs as copies: ${reason}`, node.element);
13
+ }
14
+ this.expectAssignable(this.checkValue(node.name, STRING), STRING, node.name);
15
+ return topicOf(element);
16
+ }
17
+ }
@@ -2,4 +2,6 @@ import { type Type } from './types.ts';
2
2
  /** Buffers and their views: copied whole, or moved by `transfer(x)` (channels.ts). */
3
3
  export declare const BUFFERS: readonly string[];
4
4
  /** Why a value of the type cannot be copied to a worker, or null if it can. */
5
- export declare function notCopied(type: Type, seen?: Set<Type>): string | null;
5
+ export declare function notCopied(type: Type, seen?: Set<Type>, tab?: boolean): string | null;
6
+ /** Why a value of the type cannot go to another tab through a topic, or null if it can. */
7
+ export declare function notBroadcast(type: Type): string | null;
@@ -1,6 +1,7 @@
1
1
  import { isQueue, memberNames, queueElement, typeToString } from "./types.js";
2
2
  // What can go to a worker and back: a worker gets a copy of each value (the structured clone of
3
- // postMessage), and a copy has the data of a value but not its methods.
3
+ // postMessage), and a copy has the data of a value but not its methods. What goes to another tab
4
+ // through a topic is a copy as well, and it cannot hold a channel.
4
5
  /** Buffers and their views: copied whole, or moved by `transfer(x)` (channels.ts). */
5
6
  export const BUFFERS = [
6
7
  'ArrayBuffer',
@@ -23,6 +24,8 @@ const COPIED = new Set([
23
24
  'chan',
24
25
  'chan<-',
25
26
  '<-chan',
27
+ // A topic is copied as its name, and the other thread listens to it on its own.
28
+ 'topic',
26
29
  'Map',
27
30
  'Set',
28
31
  'Date',
@@ -35,13 +38,13 @@ const COPIED = new Set([
35
38
  ...BUFFERS,
36
39
  ]);
37
40
  /** Why a value of the type cannot be copied to a worker, or null if it can. */
38
- export function notCopied(type, seen = new Set()) {
41
+ export function notCopied(type, seen = new Set(), tab = false) {
39
42
  if (seen.has(type))
40
43
  return null;
41
44
  seen.add(type);
42
45
  const inner = (types) => {
43
46
  for (const each of types) {
44
- const reason = notCopied(each, seen);
47
+ const reason = notCopied(each, seen, tab);
45
48
  if (reason !== null)
46
49
  return reason;
47
50
  }
@@ -61,15 +64,28 @@ export function notCopied(type, seen = new Set()) {
61
64
  return 'a component cannot be copied';
62
65
  case 'classValue':
63
66
  return `the class ${type.info.name} cannot be copied`;
67
+ // A data class goes with its class (runtime/classes.js), but not to another tab, which may not
68
+ // have it.
64
69
  case 'class':
65
- return type.info.name === 'Error'
66
- ? null
70
+ if (type.info.name === 'Error')
71
+ return null;
72
+ if (type.info.data === true) {
73
+ return tab
74
+ ? `${typeToString(type)} cannot go to another tab, which may not have its class: send an object of an interface`
75
+ : inner(type.args ?? []);
76
+ }
77
+ return type.info.data === false && !tab
78
+ ? `a copy of ${typeToString(type)} would lose its class and methods: mark it to keep them, e.g. data class ${type.info.name}`
67
79
  : `a copy of ${typeToString(type)} would lose its class and methods`;
68
80
  case 'object': {
69
81
  // A queue goes as its values, and the other thread makes a queue of them (src/runtime/threads.js).
70
82
  if (isQueue(type))
71
83
  return inner([queueElement(type)]);
72
84
  const name = type.instanceOf?.template.name ?? type.name;
85
+ // The id of a channel means something only to the workers of the tab that made it.
86
+ if (tab && (name === 'chan' || name === 'chan<-' || name === '<-chan')) {
87
+ return `${typeToString(type)} cannot go to another tab: a channel works only within one`;
88
+ }
73
89
  if (name !== null && COPIED.has(name))
74
90
  return inner(type.instanceOf?.args ?? []);
75
91
  if (type.call || type.construct)
@@ -84,3 +100,7 @@ export function notCopied(type, seen = new Set()) {
84
100
  return null;
85
101
  }
86
102
  }
103
+ /** Why a value of the type cannot go to another tab through a topic, or null if it can. */
104
+ export function notBroadcast(type) {
105
+ return notCopied(type, new Set(), true);
106
+ }
@@ -0,0 +1,6 @@
1
+ import type * as ast from '../ast.ts';
2
+ import { DecodeChecker } from './decode.ts';
3
+ import { type Type } from './types.ts';
4
+ export declare abstract class TryChecker extends DecodeChecker {
5
+ protected checkTryExpression(node: ast.TryExpression): Type;
6
+ }
@@ -0,0 +1,19 @@
1
+ import { DecodeChecker } from "./decode.js";
2
+ import { isErrorType } from "./error-results.js";
3
+ import { ERROR } from "./types.js";
4
+ // `try expr`: an exception in the expression, or a rejected Promise that an `await` in it waits
5
+ // for, becomes an error, as if the expression returned it (codegen/try.ts): `try JSON.parse(text)`
6
+ // gives `(any, error)`, `try save()` of a function without results gives the error alone, and of
7
+ // `(User, error)` the exception is one more way to get that error.
8
+ export class TryChecker extends DecodeChecker {
9
+ checkTryExpression(node) {
10
+ const type = this.checkExpression(node.argument, null);
11
+ if (type.kind === 'void' || isErrorType(type))
12
+ return ERROR;
13
+ if (type.kind !== 'tuple')
14
+ return { kind: 'tuple', types: [type, ERROR] };
15
+ return isErrorType(type.types.at(-1))
16
+ ? type
17
+ : { kind: 'tuple', types: [...type.types, ERROR] };
18
+ }
19
+ }
@@ -1,5 +1,5 @@
1
1
  import { type ArrayType, type ClassInfo, type FunctionType, type LiteralType, type Member, type Type, type TypeParam } from './model.ts';
2
- export declare function arrayOf(element: Type): ArrayType;
2
+ export declare function arrayOf(element: Type, positions?: Type[]): ArrayType;
3
3
  export declare function nullable(type: Type): Type;
4
4
  export declare function literal(value: string | number | boolean): LiteralType;
5
5
  /** The type that a literal belongs to: `"all"` → `string`. */
@@ -1,8 +1,8 @@
1
1
  import { BOOL, NEVER, NULL, NUMBER, STRING, } from "./model.js";
2
2
  import { typesEqual } from "./relations.js";
3
3
  // Making types: arrays, nullable types, literals, unions, functions, members, classes.
4
- export function arrayOf(element) {
5
- return { kind: 'array', element };
4
+ export function arrayOf(element, positions) {
5
+ return positions ? { kind: 'array', element, positions } : { kind: 'array', element };
6
6
  }
7
7
  export function nullable(type) {
8
8
  switch (type.kind) {
@@ -43,7 +43,7 @@ export function instantiate(template, args) {
43
43
  }
44
44
  // The indexes and the values of a loop, substituted when first used: the template of a
45
45
  // `.d.ts` may get them after its instances.
46
- for (const key of ['index', 'numberIndex', 'iterates']) {
46
+ for (const key of ['index', 'numberIndex', 'iterates', 'asyncIterates']) {
47
47
  let substituted;
48
48
  Object.defineProperty(instance, key, {
49
49
  enumerable: true,
@@ -0,0 +1,13 @@
1
+ import { type ObjectType, type Type } from './model.ts';
2
+ /** The values that `for` gives of a type it goes over at once, or null. */
3
+ export declare function iteratedBy(type: Type): Type | null;
4
+ /**
5
+ * Whether `source` fits `Iterable<T>` or another interface that `for` goes over: its values must
6
+ * fit. An array or a string, which have no members here, fits one whose members may be left out;
7
+ * an object goes on to the check of its members (null).
8
+ */
9
+ export declare function iterableAssignable(source: Type, target: ObjectType, assuming: [Type, Type][]): boolean | null;
10
+ /** `{ [string]: T }` accepts an object whose own index and fields are all `T`. */
11
+ export declare function indexAssignable(source: Type, index: Type, assuming: [Type, Type][]): boolean;
12
+ /** `ArrayLike<T>` from a `.d.ts` accepts an array, a string or a typed array of `T`. */
13
+ export declare function numberIndexAssignable(source: Type, index: Type, assuming: [Type, Type][]): boolean;
@@ -0,0 +1,52 @@
1
+ import { isNullable } from "./constructors.js";
2
+ import { memberNames } from "./lazy.js";
3
+ import { STRING } from "./model.js";
4
+ import { assignable } from "./relations.js";
5
+ // What types from a `.d.ts` that are gone over or indexed accept: `Iterable<T>` takes what `for`
6
+ // goes over with values that fit, an array or a Set; `{ [string]: T }` an object of fields of `T`;
7
+ // `ArrayLike<T>` an array, a string or a typed array.
8
+ /** The values that `for` gives of a type it goes over at once, or null. */
9
+ export function iteratedBy(type) {
10
+ if (type.kind === 'array')
11
+ return type.element;
12
+ if (type.kind === 'string')
13
+ return STRING;
14
+ return type.kind === 'object' ? (type.iterates ?? null) : null;
15
+ }
16
+ /**
17
+ * Whether `source` fits `Iterable<T>` or another interface that `for` goes over: its values must
18
+ * fit. An array or a string, which have no members here, fits one whose members may be left out;
19
+ * an object goes on to the check of its members (null).
20
+ */
21
+ export function iterableAssignable(source, target, assuming) {
22
+ const values = iteratedBy(source);
23
+ if (values === null || !assignable(values, target.iterates, assuming))
24
+ return values === null ? null : false;
25
+ if (source.kind === 'object')
26
+ return null;
27
+ return [...memberNames(target.members)].every((name) => {
28
+ const member = target.members.get(name);
29
+ return !member || (!member.method && isNullable(member.type));
30
+ });
31
+ }
32
+ /** `{ [string]: T }` accepts an object whose own index and fields are all `T`. */
33
+ export function indexAssignable(source, index, assuming) {
34
+ if (source.kind !== 'object')
35
+ return false;
36
+ if (source.index && !assignable(source.index, index, assuming))
37
+ return false;
38
+ return [...memberNames(source.members)].every((name) => {
39
+ const member = source.members.get(name);
40
+ return !member || member.method || assignable(member.type, index, assuming);
41
+ });
42
+ }
43
+ /** `ArrayLike<T>` from a `.d.ts` accepts an array, a string or a typed array of `T`. */
44
+ export function numberIndexAssignable(source, index, assuming) {
45
+ if (source.kind === 'array')
46
+ return assignable(source.element, index, assuming);
47
+ if (source.kind === 'string')
48
+ return assignable(source, index, assuming);
49
+ return (source.kind === 'object' &&
50
+ source.numberIndex !== undefined &&
51
+ assignable(source.numberIndex, index, assuming));
52
+ }
@@ -22,6 +22,11 @@ export declare const UNKNOWN: SimpleType;
22
22
  export interface ArrayType {
23
23
  kind: 'array';
24
24
  element: Type;
25
+ /**
26
+ * The types by position of a tuple of a `.d.ts`, `[K, V]`, or of an array literal: only for
27
+ * inferring type parameters, since the array may change. `element` is the union of them.
28
+ */
29
+ positions?: Type[];
25
30
  }
26
31
  /** `?T`; the inner type itself is never nullable. */
27
32
  export interface NullableType {
@@ -66,6 +71,15 @@ export interface Member {
66
71
  visibility: Visibility;
67
72
  /** The class that declares the member, for visibility checks. */
68
73
  owner: ClassInfo | null;
74
+ /** `readonly id string`: only its value and the constructor of its class assign it. */
75
+ readonly?: true;
76
+ /**
77
+ * A getter, a setter or both, as in `get area() number`: the member is a value of its type, which
78
+ * a getter alone cannot be assigned and a setter alone cannot be read.
79
+ */
80
+ accessor?: 'get' | 'set' | 'both';
81
+ /** `abstract area() number`: a subclass that is not abstract implements it. */
82
+ abstract?: true;
69
83
  }
70
84
  /** Interfaces, object type literals and the types of object literals. */
71
85
  export interface ObjectType {
@@ -82,6 +96,8 @@ export interface ObjectType {
82
96
  numberIndex?: Type;
83
97
  /** What `for v in x` gives: the values of `[Symbol.iterator]()` in a `.d.ts`. */
84
98
  iterates?: Type;
99
+ /** What `for v in x` waits for: the values of `[Symbol.asyncIterator]()`, as of a Node stream. */
100
+ asyncIterates?: Type;
85
101
  /** A generic interface, `interface Box<T>`: its members use these parameters. */
86
102
  typeParams?: TypeParam[];
87
103
  /** `Box<number>`: the generic interface and the type arguments it was made from. */
@@ -105,6 +121,15 @@ export interface ClassInfo {
105
121
  /** Type arguments for a generic base class: the defaults of a class from a `.d.ts`. */
106
122
  superArgs?: Type[];
107
123
  ctorVisibility: Visibility;
124
+ /** `abstract class Shape`: `new` makes only instances of its subclasses. */
125
+ abstract?: boolean;
126
+ /**
127
+ * `data class Point`: its fields are data, and its instances go to other threads with their
128
+ * class. False for the other classes of Rook; not set for those of a `.d.ts`.
129
+ */
130
+ data?: boolean;
131
+ /** What `for v in x` waits for: the values of `[Symbol.asyncIterator]()` in a `.d.ts`. */
132
+ asyncIterates?: Type;
108
133
  instance: ClassType;
109
134
  value: ClassValueType;
110
135
  }