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,164 @@
1
+ import { forEachChild } from "../walk.js";
2
+ import { IteratorChecker } from "./iterators.js";
3
+ /** What `Channel.x(ch)` and `Topic.x(t)` do with a channel without keeping it. */
4
+ const OPERATIONS = new Set(['send', 'receive', 'range', 'stream', 'publish', 'listen', 'try']);
5
+ const MESSAGES = {
6
+ chan: (name) => `"${name}" is never closed: close it when nothing more is sent, e.g. close(${name})`,
7
+ topic: (name) => `"${name}" is never closed, and a topic listens until it is closed: close it when it is no longer needed, e.g. close(${name}), in a component in the function that mount() returns`,
8
+ tick: (name) => `"${name}" is never closed, and a tick runs until it is closed: e.g. close(${name})`,
9
+ signal: (name) => `"${name}" is never closed, and it keeps the program running until it is: close it once the signal has come, e.g. close(${name})`,
10
+ };
11
+ export class ClosingChecker extends IteratorChecker {
12
+ /** The identifier that declares a binding of this module. */
13
+ declarations = new WeakMap();
14
+ /** What each name refers to: its declaration, or the name of a predeclared value. */
15
+ references = new Map();
16
+ declareValue(id, kind, type) {
17
+ const binding = super.declareValue(id, kind, type);
18
+ this.declarations.set(binding, id);
19
+ return binding;
20
+ }
21
+ checkIdentifier(node) {
22
+ const binding = this.lookupValue(node.name);
23
+ const declaration = binding && this.declarations.get(binding);
24
+ if (declaration)
25
+ this.references.set(node, declaration);
26
+ else if (binding && binding === this.globals.values.get(node.name)) {
27
+ this.references.set(node, node.name);
28
+ }
29
+ return super.checkIdentifier(node);
30
+ }
31
+ /** The functions declared in the module, by their names, which `use` looks arguments up in. */
32
+ functions = new Map();
33
+ checkClosing() {
34
+ const made = [];
35
+ const uses = new Map();
36
+ const collect = (node) => {
37
+ if (node.kind === 'FuncDeclaration')
38
+ this.functions.set(node.name, node);
39
+ forEachChild(node, collect);
40
+ };
41
+ const visit = (node, parent) => {
42
+ const what = node.kind === 'VariableDeclaration' && !node.exported && node.names.length === 1
43
+ ? node.values[0] && this.made(node.values[0])
44
+ : null;
45
+ if (node.kind === 'VariableDeclaration' && what)
46
+ made.push([node.names[0], what]);
47
+ const declaration = node.kind === 'Identifier' ? this.references.get(node) : undefined;
48
+ if (node.kind === 'Identifier' && typeof declaration === 'object' && parent) {
49
+ if (!uses.has(declaration)) {
50
+ uses.set(declaration, { closed: false, escapes: false, passed: [] });
51
+ }
52
+ this.use(node, parent, uses.get(declaration));
53
+ }
54
+ forEachChild(node, (child) => visit(child, node));
55
+ };
56
+ collect(this.program);
57
+ visit(this.program, null);
58
+ // Whether a variable or a parameter is closed, goes where it is not seen, or neither.
59
+ const fate = (id, seen) => {
60
+ const found = uses.get(id);
61
+ if (!found || seen.has(id))
62
+ return 'none';
63
+ if (found.closed)
64
+ return 'closed';
65
+ seen.add(id);
66
+ let result = found.escapes ? 'escapes' : 'none';
67
+ for (const [fn, i] of found.passed) {
68
+ const param = fn.params[i];
69
+ const inner = param && !param.rest ? fate(param.name, seen) : 'escapes';
70
+ if (inner === 'closed')
71
+ return 'closed';
72
+ if (inner === 'escapes')
73
+ result = 'escapes';
74
+ }
75
+ return result;
76
+ };
77
+ for (const [name, what] of made) {
78
+ if (fate(name, new Set()) === 'none')
79
+ this.warn(MESSAGES[what](name.name), name);
80
+ }
81
+ }
82
+ /** `chan<T>()`, `topic<T>()`, `tick(ms)`, `signal(name)` and their `Channel.make` and the like. */
83
+ made(value) {
84
+ if (value.kind === 'ChanExpression')
85
+ return 'chan';
86
+ if (value.kind === 'TopicExpression')
87
+ return 'topic';
88
+ if (value.kind !== 'CallExpression')
89
+ return null;
90
+ const { callee } = value;
91
+ if (callee.kind === 'Identifier') {
92
+ const predeclared = this.references.get(callee);
93
+ return predeclared === 'tick' || predeclared === 'signal' ? predeclared : null;
94
+ }
95
+ const member = this.predeclaredMember(callee);
96
+ if (member === 'Channel.make')
97
+ return 'chan';
98
+ if (member === 'Channel.tick')
99
+ return 'tick';
100
+ if (member === 'Channel.signal')
101
+ return 'signal';
102
+ return member === 'Topic.make' ? 'topic' : null;
103
+ }
104
+ /** `Channel.close` and the like, as a name, when `Channel` is the predeclared one. */
105
+ predeclaredMember(node) {
106
+ if (node.kind !== 'MemberExpression' || node.object.kind !== 'Identifier')
107
+ return null;
108
+ const object = this.references.get(node.object);
109
+ return (object === 'Channel' || object === 'Topic') && node.property.kind === 'Identifier'
110
+ ? `${object}.${node.property.name}`
111
+ : null;
112
+ }
113
+ /** What the code around a name of a channel does with it. */
114
+ use(node, parent, found) {
115
+ switch (parent.kind) {
116
+ case 'CallExpression': {
117
+ const i = parent.arguments.indexOf(node);
118
+ if (i < 0)
119
+ return;
120
+ const { callee } = parent;
121
+ const ref = callee.kind === 'Identifier' ? this.references.get(callee) : undefined;
122
+ const member = this.predeclaredMember(callee)?.split('.')[1];
123
+ if (ref === 'close' || member === 'close')
124
+ found.closed = true;
125
+ else if (ref === 'stream' || (member !== undefined && OPERATIONS.has(member)))
126
+ return;
127
+ else {
128
+ const fn = callee.kind === 'FuncExpression'
129
+ ? callee
130
+ : typeof ref === 'object'
131
+ ? this.functions.get(ref)
132
+ : undefined;
133
+ if (fn)
134
+ found.passed.push([fn, i]);
135
+ else
136
+ found.escapes = true;
137
+ }
138
+ return;
139
+ }
140
+ // Sending to it, receiving from it, a loop over it, comparing and assigning it keep it here;
141
+ // sending it through a channel does not.
142
+ case 'SendStatement':
143
+ if (parent.value === node)
144
+ found.escapes = true;
145
+ return;
146
+ case 'UnaryExpression':
147
+ if (parent.operator !== '<-')
148
+ found.escapes = true;
149
+ return;
150
+ case 'ForInStatement':
151
+ return;
152
+ case 'BinaryExpression':
153
+ if (!['==', '!=', '===', '!=='].includes(parent.operator))
154
+ found.escapes = true;
155
+ return;
156
+ case 'AssignmentStatement':
157
+ if (!parent.targets.includes(node))
158
+ found.escapes = true;
159
+ return;
160
+ default:
161
+ found.escapes = true;
162
+ }
163
+ }
164
+ }
@@ -1,8 +1,8 @@
1
1
  import type * as ast from '../ast.ts';
2
- import { IteratorChecker } from './iterators.ts';
2
+ import { ClosingChecker } from './closing.ts';
3
3
  import type { CompletionResult } from './context.ts';
4
4
  import { type Type } from './types.ts';
5
- export declare abstract class CompletionChecker extends IteratorChecker {
5
+ export declare abstract class CompletionChecker extends ClosingChecker {
6
6
  protected completion: CompletionResult | null;
7
7
  private atCursor;
8
8
  /** The first check of the cursor counts: a component's body, for one, is checked for each use. */
@@ -1,4 +1,4 @@
1
- import { IteratorChecker } from "./iterators.js";
1
+ import { ClosingChecker } from "./closing.js";
2
2
  import { ARRAY_MEMBERS, ATTRIBUTE_NAMES, BIGINT_MEMBERS, BOOL_MEMBERS, EVENT_ATTRIBUTES, HTML_TAGS, NUMBER_MEMBERS, STRING_MEMBERS, } from "./completion-names.js";
3
3
  import { isSubclass, literalBase, memberNames, memberTypeOf, nonNull, typeToString, union, UNKNOWN, } from "./types.js";
4
4
  // Completion in editors (src/completion.ts): the editor writes the name `options.completion` where
@@ -12,7 +12,7 @@ const PRIMITIVE_MEMBERS = {
12
12
  bigint: BIGINT_MEMBERS,
13
13
  bool: BOOL_MEMBERS,
14
14
  };
15
- export class CompletionChecker extends IteratorChecker {
15
+ export class CompletionChecker extends ClosingChecker {
16
16
  completion = null;
17
17
  atCursor(node) {
18
18
  return node.name === this.options.completion;
@@ -64,6 +64,8 @@ export interface CheckResult {
64
64
  declarations: ModuleExports;
65
65
  /** Types of checked expressions, for code generation (e.g. how to insert element children). */
66
66
  types: WeakMap<ast.Expression, Type>;
67
+ /** The data classes that the module declares or imports, by their names here. */
68
+ dataClasses: Set<string>;
67
69
  /** With the `completion` option: what can be written at the cursor, if the check reached it. */
68
70
  completion?: CompletionResult;
69
71
  }
@@ -78,6 +80,17 @@ export interface Completion {
78
80
  kind: 'variable' | 'constant' | 'function' | 'class' | 'component' | 'type' | 'property' | 'method' | 'keyword' | 'tag' | 'attribute';
79
81
  /** The type, as Rook writes it. */
80
82
  detail?: string;
83
+ /** A name exported by another module of the package: choosing it imports it (auto-import.ts). */
84
+ import?: {
85
+ specifier: string;
86
+ edit: TextEdit;
87
+ };
88
+ }
89
+ /** A change of the text of a module: what is between the offsets becomes `text`. */
90
+ export interface TextEdit {
91
+ start: number;
92
+ end: number;
93
+ text: string;
81
94
  }
82
95
  export type BindingKind = 'let' | 'const' | 'state' | 'derived' | 'ref' | 'param' | 'prop' | 'function' | 'class' | 'import' | 'loop' | 'catch' | 'builtin' | 'component';
83
96
  export interface Binding {
@@ -5,6 +5,11 @@ export declare abstract class ControlFlowChecker extends StatementChecker {
5
5
  protected checkIf(node: ast.IfStatement): void;
6
6
  protected checkFor(node: ast.ForStatement): void;
7
7
  protected checkSimpleStatement(node: ast.SimpleStatement): void;
8
+ /**
9
+ * `if const v = f(); v > 0 { }`: the variables of the initializer are in a scope of their own,
10
+ * around every branch. Returns whether `check` checked the statement inside it.
11
+ */
12
+ private checkWithInit;
8
13
  protected checkSwitch(node: ast.SwitchStatement): void;
9
14
  /**
10
15
  * `switch filter { case "all": ... default: ... }`: in a case the discriminant has the literals
@@ -7,6 +7,8 @@ import { ANY, BOOL, isAssignable, isComparable, isNullable, isSubclass, literal,
7
7
  // if, for, switch and try, and how conditions narrow the types of variables in the branches.
8
8
  export class ControlFlowChecker extends StatementChecker {
9
9
  checkIf(node) {
10
+ if (this.checkWithInit(node.init, () => this.checkIf({ ...node, init: null })))
11
+ return;
10
12
  this.checkCondition(node.condition);
11
13
  const before = this.flow;
12
14
  // Both are found from the flow before the `if`, not from the end of a branch.
@@ -52,7 +54,22 @@ export class ControlFlowChecker extends StatementChecker {
52
54
  checkSimpleStatement(node) {
53
55
  this.checkStatement(node);
54
56
  }
57
+ /**
58
+ * `if const v = f(); v > 0 { }`: the variables of the initializer are in a scope of their own,
59
+ * around every branch. Returns whether `check` checked the statement inside it.
60
+ */
61
+ checkWithInit(init, check) {
62
+ if (init === null)
63
+ return false;
64
+ this.withScope(() => {
65
+ this.checkStatement(init);
66
+ check();
67
+ });
68
+ return true;
69
+ }
55
70
  checkSwitch(node) {
71
+ if (this.checkWithInit(node.init, () => this.checkSwitch({ ...node, init: null })))
72
+ return;
56
73
  const discriminant = node.discriminant ? this.checkValue(node.discriminant) : null;
57
74
  this.dropNarrowing(assignedNames(node));
58
75
  const entry = this.flow;
@@ -34,6 +34,7 @@ export class DeclarationChecker extends ImportChecker {
34
34
  break;
35
35
  case 'ClassDeclaration': {
36
36
  const info = createClass(statement.name.name);
37
+ info.data = statement.data;
37
38
  info.typeParams = this.createTypeParams(statement.typeParams);
38
39
  this.declareType(statement.name, info.instance);
39
40
  this.declareValue(statement.name, 'class', info.value);
@@ -0,0 +1,8 @@
1
+ import type * as ast from '../ast.ts';
2
+ import { TopicChecker } from './topics.ts';
3
+ import { type Type } from './types.ts';
4
+ export declare abstract class DecodeChecker extends TopicChecker {
5
+ protected checkDecode(node: ast.DecodeExpression): Type;
6
+ /** Why decode cannot take a value of the type: it is neither JSON text nor a value from JS. */
7
+ private notDecodable;
8
+ }
@@ -0,0 +1,32 @@
1
+ import { isUntyped } from "./helpers.js";
2
+ import { TopicChecker } from "./topics.js";
3
+ import { isText, notDecoded } from "./json.js";
4
+ import { ERROR, typedArrayOf, typeToString } from "./types.js";
5
+ // `decode<User>(body)`: the types are erased, so only the compiler knows what a User is. It writes
6
+ // the type into the JS as data (codegen/decode.ts), and the runtime checks JSON text, or a value
7
+ // from JS, against it when the program runs (runtime/decode.js). So the type must be one that JSON
8
+ // can hold (json.ts). The argument is JSON text if it is a string, and a value from JS if it is any.
9
+ export class DecodeChecker extends TopicChecker {
10
+ checkDecode(node) {
11
+ const type = this.resolveType(node.type);
12
+ const reason = notDecoded(type);
13
+ if (reason !== null)
14
+ this.error(`cannot decode ${typeToString(type)}: ${reason}`, node.type);
15
+ const argument = this.checkValue(node.argument);
16
+ if (!isUntyped(argument) && !isText(argument)) {
17
+ this.error(this.notDecodable(argument), node.argument);
18
+ }
19
+ return { kind: 'tuple', types: [type, ERROR] };
20
+ }
21
+ /** Why decode cannot take a value of the type: it is neither JSON text nor a value from JS. */
22
+ notDecodable(type) {
23
+ const prefix = `decode takes JSON text, a string, or a value of type any from JS, not ${typeToString(type)}`;
24
+ if (type.kind === 'nullable' && isText(type.type)) {
25
+ return `${prefix}: the text may be null, so check it first, e.g. "if text != null"`;
26
+ }
27
+ if (type.kind === 'object' && (typedArrayOf(type) === 'Uint8Array' || type.name === 'Buffer')) {
28
+ return `${prefix}: make the text of the bytes first, e.g. decode<User>(string(data))`;
29
+ }
30
+ return `${prefix}; its type is known already`;
31
+ }
32
+ }
@@ -11,6 +11,11 @@ export declare abstract class ExpressionChecker extends FunctionChecker {
11
11
  protected abstract checkChan(node: ast.ChanExpression): Type;
12
12
  protected abstract checkStreamChan(node: ast.StreamChanExpression): Type;
13
13
  protected abstract checkQueue(node: ast.QueueExpression): Type;
14
+ protected abstract checkTopic(node: ast.TopicExpression): Type;
15
+ protected abstract checkDecode(node: ast.DecodeExpression): Type;
16
+ /** `[1, 2]`; implemented by ArrayLiteralChecker. */
17
+ protected abstract checkArrayLiteral(node: ast.ArrayLiteral, expected: Type | null): Type;
18
+ protected abstract checkTryExpression(node: ast.TryExpression): Type;
14
19
  /** `[]byte("text")`; implemented by BytesChecker. */
15
20
  protected abstract checkConversion(node: ast.ConversionExpression): Type;
16
21
  protected abstract checkMake(node: ast.MakeExpression): Type;
@@ -24,7 +29,6 @@ export declare abstract class ExpressionChecker extends FunctionChecker {
24
29
  protected thisType(node: ast.NodeBase): Type;
25
30
  /** `super.method()`: the members of the base class. */
26
31
  protected superType(node: ast.NodeBase): Type;
27
- protected checkArrayLiteral(node: ast.ArrayLiteral, expected: Type | null): Type;
28
32
  protected checkObjectLiteral(node: ast.ObjectLiteral, expected: Type | null): Type;
29
33
  /**
30
34
  * `value as Type`: the types must be related, one assignable to the other, as in TypeScript:
@@ -1,6 +1,6 @@
1
1
  import { FunctionChecker } from "./functions.js";
2
2
  import { expectsLiteral, callName, isUntyped } from "./helpers.js";
3
- import { ANY, BIGINT, arrayOf, BOOL, commonType, hasUntypedBase, instanceMembers, isAssignable, literal, NEVER, nonNull, NULL, nullable, NUMBER, STRING, typeToString, UNKNOWN, } from "./types.js";
3
+ import { ANY, BIGINT, BOOL, hasUntypedBase, instanceMembers, isAssignable, literal, nonNull, NULL, nullable, NUMBER, STRING, typeToString, UNKNOWN, } from "./types.js";
4
4
  // Expressions: names, `this`, literals of arrays and objects; the operators are in operators.ts.
5
5
  export class ExpressionChecker extends FunctionChecker {
6
6
  /** The type of an expression that must be a single value (not `void` or several results). */
@@ -35,6 +35,9 @@ export class ExpressionChecker extends FunctionChecker {
35
35
  return BIGINT;
36
36
  case 'StringLiteral':
37
37
  return expectsLiteral(expected) ? literal(node.value) : STRING;
38
+ // A RegExp of lib.es5.d.ts; the playground, which has no lib files, does not know its members.
39
+ case 'RegExpLiteral':
40
+ return this.options.library?.type('RegExp') ?? ANY;
38
41
  case 'TemplateLiteral':
39
42
  for (const expression of node.expressions)
40
43
  this.checkValue(expression);
@@ -71,6 +74,12 @@ export class ExpressionChecker extends FunctionChecker {
71
74
  return this.checkStreamChan(node);
72
75
  case 'QueueExpression':
73
76
  return this.checkQueue(node);
77
+ case 'TopicExpression':
78
+ return this.checkTopic(node);
79
+ case 'DecodeExpression':
80
+ return this.checkDecode(node);
81
+ case 'TryExpression':
82
+ return this.checkTryExpression(node);
74
83
  case 'ConversionExpression':
75
84
  return this.checkConversion(node);
76
85
  case 'MakeExpression':
@@ -104,7 +113,9 @@ export class ExpressionChecker extends FunctionChecker {
104
113
  ? `"${node.name}" is a type, not a value`
105
114
  : node.name === 'make'
106
115
  ? 'make takes the type of a typed array first, e.g. make([]float64, n)'
107
- : `"${node.name}" is not defined`, node);
116
+ : node.name === 'decode'
117
+ ? 'decode takes the type first, e.g. decode<User>(body)'
118
+ : `"${node.name}" is not defined`, node);
108
119
  return UNKNOWN;
109
120
  }
110
121
  // A component as a value: a tag can take it from there, `<section.content />`.
@@ -142,38 +153,6 @@ export class ExpressionChecker extends FunctionChecker {
142
153
  this.error(`class "${info.name}" has no base class`, node);
143
154
  return UNKNOWN;
144
155
  }
145
- checkArrayLiteral(node, expected) {
146
- const context = expected ? nonNull(expected) : null;
147
- const expectedElement = context?.kind === 'array' ? context.element : context?.kind === 'any' ? ANY : null;
148
- let element = expectedElement ?? NEVER;
149
- for (const item of node.elements) {
150
- let type;
151
- if (item.kind === 'SpreadElement') {
152
- const spread = this.checkValue(item.argument, expectedElement && arrayOf(expectedElement));
153
- if (spread.kind === 'array')
154
- type = spread.element;
155
- else if (spread.kind === 'any' || spread.kind === 'unknown')
156
- type = spread;
157
- // JS spreads any iterable: a Set, a typed array, a `[]byte`.
158
- else if (spread.kind === 'object' && spread.iterates)
159
- type = spread.iterates;
160
- else {
161
- this.error(`cannot spread ${typeToString(spread)}: it is not an array or an iterable`, item.argument);
162
- type = UNKNOWN;
163
- }
164
- }
165
- else {
166
- type = this.checkValue(item, expectedElement);
167
- }
168
- if (expectedElement) {
169
- this.expectAssignable(type, expectedElement, item.kind === 'SpreadElement' ? item.argument : item);
170
- }
171
- else {
172
- element = commonType(element, type);
173
- }
174
- }
175
- return arrayOf(element);
176
- }
177
156
  checkObjectLiteral(node, expected) {
178
157
  let target = expected ? nonNull(expected) : null;
179
158
  // `?(ScrollIntoViewOptions | bool)`: an object literal can only be the object type in it.
@@ -1,4 +1,4 @@
1
- import { containsBreak } from "../walk.js";
1
+ import { containsBreak } from "../breaks.js";
2
2
  import { BIGINT, BOOL, isNullable, nonNull, NULL, NUMBER, STRING, typesEqual, union, unionMembers, } from "./types.js";
3
3
  // Control flow for the checker: how conditions narrow the types of variables, and which statements
4
4
  // execution never continues after.
@@ -96,18 +96,22 @@ export function isTerminating(node) {
96
96
  case 'IfStatement':
97
97
  return (node.alternate !== null && isTerminating(node.consequent) && isTerminating(node.alternate));
98
98
  case 'ForStatement':
99
- return node.condition === null && !containsBreak(node.body.body);
99
+ return node.condition === null && !containsBreak(node.body.body, node.label ?? null);
100
100
  case 'SwitchStatement':
101
101
  return (node.cases.some((switchCase) => switchCase.tests.length === 0) &&
102
102
  node.cases.every((switchCase) => {
103
103
  const last = switchCase.body.at(-1);
104
- return last !== undefined && isTerminating(last) && !containsBreak(switchCase.body);
104
+ return (last !== undefined &&
105
+ isTerminating(last) &&
106
+ !containsBreak(switchCase.body, node.label ?? null));
105
107
  }));
106
108
  // A select runs one of its cases, waiting for one if there is no default.
107
109
  case 'SelectStatement':
108
110
  return node.cases.every((selectCase) => {
109
111
  const last = selectCase.body.at(-1);
110
- return last !== undefined && isTerminating(last) && !containsBreak(selectCase.body);
112
+ return (last !== undefined &&
113
+ isTerminating(last) &&
114
+ !containsBreak(selectCase.body, node.label ?? null));
111
115
  });
112
116
  case 'TryStatement':
113
117
  return ((isTerminating(node.block) && (!node.handler || isTerminating(node.handler.body))) ||
@@ -26,7 +26,7 @@ export declare function callSignature(type: Type): FunctionType | null;
26
26
  /** Whether the expected type has literal members: then a literal gets a literal type. */
27
27
  export declare function expectsLiteral(expected: Type | null): boolean;
28
28
  export declare function countValues(count: number): string;
29
- /** A call, or `await` of a call or of `go`: it may give several values. */
29
+ /** A call, `decode<T>(x)`, `try`, or `await` of a call or of `go`: it may give several values. */
30
30
  export declare function isCall(node: ast.Expression): boolean;
31
31
  /** How to name an expression in messages: `divide()`, `stats.minMax()`, or `this expression`. */
32
32
  export declare function callName(node: ast.Expression): string;
@@ -123,12 +123,15 @@ export function countValues(count) {
123
123
  return 'no values';
124
124
  return count === 1 ? '1 value' : `${count} values`;
125
125
  }
126
- /** A call, or `await` of a call or of `go`: it may give several values. */
126
+ /** A call, `decode<T>(x)`, `try`, or `await` of a call or of `go`: it may give several values. */
127
127
  export function isCall(node) {
128
128
  if (node.kind === 'UnaryExpression' && node.operator === 'await') {
129
129
  return node.argument.kind === 'CallExpression' || node.argument.kind === 'GoExpression';
130
130
  }
131
- return node.kind === 'CallExpression';
131
+ // `return decode<User>(body)` and `return try f()` pass on the value and the error.
132
+ return (node.kind === 'CallExpression' ||
133
+ node.kind === 'DecodeExpression' ||
134
+ node.kind === 'TryExpression');
132
135
  }
133
136
  /** How to name an expression in messages: `divide()`, `stats.minMax()`, or `this expression`. */
134
137
  export function callName(node) {
@@ -139,6 +142,10 @@ export function callName(node) {
139
142
  return callName(node.call);
140
143
  if (node.kind === 'StreamChanExpression')
141
144
  return 'chan(stream)';
145
+ if (node.kind === 'DecodeExpression')
146
+ return 'decode';
147
+ if (node.kind === 'TryExpression')
148
+ return 'try';
142
149
  if (node.kind !== 'CallExpression')
143
150
  return 'this expression';
144
151
  const { callee } = node;
@@ -1,5 +1,7 @@
1
- import type { ObjectType, Type } from './types.ts';
1
+ import { type ObjectType, type Type } from './types.ts';
2
2
  /** `Map<K, V>` or `ReadonlyMap<K, V>`: `for key, value in m`, and `for value in m` for the values. */
3
3
  export declare function isMap(type: Type): boolean;
4
4
  /** The key and the value of `for key, value in x`: of a Map, or of an array-like with positions. */
5
5
  export declare function loopEntry(type: ObjectType): [Type, Type] | null;
6
+ /** What `for v in x` waits for, of an async iterable that cannot be gone over at once, or null. */
7
+ export declare function asyncIterated(type: Type): Type | null;
@@ -1,5 +1,8 @@
1
+ import { bindParams, substitute } from "./types.js";
1
2
  // What `for ... in` goes over besides arrays, strings and channels: the iterables of the lib files,
2
3
  // whose values come from `[Symbol.iterator]()`. A Map gives its keys and values, as a map in Go.
4
+ // An async iterable, such as a stream of Node, gives the values that the Promises of
5
+ // `[Symbol.asyncIterator]()` give, and the loop waits for them, as `for await` of JS.
3
6
  /** `Map<K, V>` or `ReadonlyMap<K, V>`: `for key, value in m`, and `for value in m` for the values. */
4
7
  export function isMap(type) {
5
8
  const name = type.kind === 'object' ? (type.instanceOf?.template.name ?? type.name) : null;
@@ -15,3 +18,13 @@ export function loopEntry(type) {
15
18
  // A typed array or a NodeList has positions, and `entries()`, as an array has.
16
19
  return numberIndex && type.members.has('entries') ? [{ kind: 'number' }, iterates] : null;
17
20
  }
21
+ /** What `for v in x` waits for, of an async iterable that cannot be gone over at once, or null. */
22
+ export function asyncIterated(type) {
23
+ if (type.kind === 'object')
24
+ return type.iterates ? null : (type.asyncIterates ?? null);
25
+ if (type.kind !== 'class' || !type.info.asyncIterates)
26
+ return null;
27
+ // TypeScript lists inherited members with the type arguments of the base class already.
28
+ const { info } = type;
29
+ return substitute(info.asyncIterates, bindParams(info.typeParams, type.args ?? info.typeParams));
30
+ }
@@ -8,6 +8,9 @@ export declare abstract class IteratorChecker extends GoForChecker {
8
8
  /** The rules of iterators in the whole program; Checker.run calls it. */
9
9
  protected checkIterators(): void;
10
10
  private checkIterator;
11
- /** Where `yield` is used in the body of an iterator; `nested` inside a function in it. */
11
+ /**
12
+ * Where `yield` is used in the body of an iterator: `nested` inside a function in it, or in `try`,
13
+ * which compiles to a function that runs its expression (codegen/try.ts).
14
+ */
12
15
  private checkYields;
13
16
  }
@@ -68,14 +68,21 @@ export class IteratorChecker extends GoForChecker {
68
68
  if (!yieldsBool) {
69
69
  this.error('the yield of an iterator is func(T) bool or func(K, V) bool, e.g. func(yield func(number) bool)', type ?? param);
70
70
  }
71
- this.checkYields(node.body, false);
71
+ if (node.body)
72
+ this.checkYields(node.body, false);
72
73
  }
73
- /** Where `yield` is used in the body of an iterator; `nested` inside a function in it. */
74
+ /**
75
+ * Where `yield` is used in the body of an iterator: `nested` inside a function in it, or in `try`,
76
+ * which compiles to a function that runs its expression (codegen/try.ts).
77
+ */
74
78
  checkYields(node, nested) {
75
79
  if (isYieldCall(node)) {
76
- if (nested) {
80
+ if (nested === 'function') {
77
81
  this.error('yield is called in a function inside the iterator: a JS generator yields only in its own body, so loop there, e.g. for x in items { if !yield(x) { return } }', node.callee);
78
82
  }
83
+ else if (nested === 'try') {
84
+ this.error('yield cannot be in try, which compiles to a function: a JS generator yields only in its own body, so call yield outside of it', node.callee);
85
+ }
79
86
  for (const argument of node.arguments)
80
87
  this.checkYields(argument, nested);
81
88
  return;
@@ -89,7 +96,8 @@ export class IteratorChecker extends GoForChecker {
89
96
  isIterator(node.params)) {
90
97
  return;
91
98
  }
92
- const inner = nested || NESTED.has(node.kind);
99
+ const inner = nested ||
100
+ (NESTED.has(node.kind) ? 'function' : node.kind === 'TryExpression' ? 'try' : false);
93
101
  forEachChild(node, (child) => this.checkYields(child, inner));
94
102
  }
95
103
  }
@@ -0,0 +1,5 @@
1
+ import { type Type } from './types.ts';
2
+ /** Why a value of the type cannot come from JSON, or null if it can. */
3
+ export declare function notDecoded(type: Type, seen?: Set<Type>): string | null;
4
+ /** Whether a value of the type is always a string: then decode reads it as JSON text. */
5
+ export declare function isText(type: Type): boolean;