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.
- package/README.md +32 -7
- package/README.ru.md +32 -7
- package/dist/ast/declarations.d.ts +20 -1
- package/dist/ast/expressions.d.ts +31 -1
- package/dist/ast/markup.d.ts +6 -1
- package/dist/ast/statements.d.ts +25 -1
- package/dist/auto-import.d.ts +11 -0
- package/dist/auto-import.js +81 -0
- package/dist/breaks.d.ts +6 -0
- package/dist/breaks.js +36 -0
- package/dist/build.d.ts +7 -0
- package/dist/build.js +23 -2
- package/dist/checker/array-literals.d.ts +6 -0
- package/dist/checker/array-literals.js +59 -0
- package/dist/checker/assignments.d.ts +2 -0
- package/dist/checker/assignments.js +14 -0
- package/dist/checker/base.js +6 -6
- package/dist/checker/builtins.js +15 -5
- package/dist/checker/bytes.d.ts +2 -2
- package/dist/checker/bytes.js +2 -2
- package/dist/checker/calls.js +3 -0
- package/dist/checker/channel-objects.d.ts +3 -0
- package/dist/checker/channel-objects.js +40 -0
- package/dist/checker/channels.d.ts +1 -0
- package/dist/checker/channels.js +16 -2
- package/dist/checker/checker.d.ts +1 -1
- package/dist/checker/checker.js +19 -0
- package/dist/checker/class-members.d.ts +22 -0
- package/dist/checker/class-members.js +105 -0
- package/dist/checker/classes.d.ts +5 -4
- package/dist/checker/classes.js +41 -12
- package/dist/checker/closing.d.ts +21 -0
- package/dist/checker/closing.js +164 -0
- package/dist/checker/completion.d.ts +2 -2
- package/dist/checker/completion.js +2 -2
- package/dist/checker/context.d.ts +13 -0
- package/dist/checker/control-flow.d.ts +5 -0
- package/dist/checker/control-flow.js +17 -0
- package/dist/checker/declarations.js +1 -0
- package/dist/checker/decode.d.ts +8 -0
- package/dist/checker/decode.js +32 -0
- package/dist/checker/expressions.d.ts +5 -1
- package/dist/checker/expressions.js +13 -34
- package/dist/checker/flow.js +8 -4
- package/dist/checker/helpers.d.ts +1 -1
- package/dist/checker/helpers.js +9 -2
- package/dist/checker/iterables.d.ts +3 -1
- package/dist/checker/iterables.js +13 -0
- package/dist/checker/iterators.d.ts +4 -1
- package/dist/checker/iterators.js +12 -4
- package/dist/checker/json.d.ts +5 -0
- package/dist/checker/json.js +92 -0
- package/dist/checker/loops.js +23 -3
- package/dist/checker/members.js +7 -0
- package/dist/checker/operators.d.ts +2 -2
- package/dist/checker/operators.js +7 -5
- package/dist/checker/queues.d.ts +2 -2
- package/dist/checker/queues.js +2 -2
- package/dist/checker/resolution.js +5 -3
- package/dist/checker/signals.d.ts +8 -0
- package/dist/checker/signals.js +48 -0
- package/dist/checker/topics.d.ts +6 -0
- package/dist/checker/topics.js +17 -0
- package/dist/checker/transfer.d.ts +3 -1
- package/dist/checker/transfer.js +25 -5
- package/dist/checker/try.d.ts +6 -0
- package/dist/checker/try.js +19 -0
- package/dist/checker/types/constructors.d.ts +1 -1
- package/dist/checker/types/constructors.js +2 -2
- package/dist/checker/types/generics.js +1 -1
- package/dist/checker/types/iterables.d.ts +13 -0
- package/dist/checker/types/iterables.js +52 -0
- package/dist/checker/types/model.d.ts +25 -0
- package/dist/checker/types/params.js +22 -3
- package/dist/checker/types/printing.js +4 -3
- package/dist/checker/types/relations.js +19 -25
- package/dist/checker/types/topic.d.ts +6 -0
- package/dist/checker/types/topic.js +23 -0
- package/dist/checker/types.d.ts +2 -0
- package/dist/checker/types.js +2 -0
- package/dist/cli.js +18 -36
- package/dist/codegen/calls.d.ts +23 -0
- package/dist/codegen/calls.js +88 -7
- package/dist/codegen/control-statements.d.ts +10 -2
- package/dist/codegen/control-statements.js +56 -19
- package/dist/codegen/decode.d.ts +14 -0
- package/dist/codegen/decode.js +93 -0
- package/dist/codegen/emitter.d.ts +10 -41
- package/dist/codegen/emitter.js +9 -4
- package/dist/codegen/expressions.d.ts +9 -0
- package/dist/codegen/expressions.js +15 -24
- package/dist/codegen/js.d.ts +2 -2
- package/dist/codegen/js.js +5 -2
- package/dist/codegen/options.d.ts +40 -0
- package/dist/codegen/options.js +1 -0
- package/dist/codegen/runtime.d.ts +7 -3
- package/dist/codegen/runtime.js +27 -13
- package/dist/codegen/simple-statements.d.ts +10 -4
- package/dist/codegen/simple-statements.js +40 -15
- package/dist/codegen/statements.js +64 -4
- package/dist/codegen/try.d.ts +5 -0
- package/dist/codegen/try.js +40 -0
- package/dist/compile.d.ts +9 -0
- package/dist/compile.js +19 -6
- package/dist/completion.d.ts +4 -2
- package/dist/completion.js +10 -5
- package/dist/declarations.js +32 -6
- package/dist/decorator-copy.js +5 -2
- package/dist/goroutines.d.ts +1 -1
- package/dist/goroutines.js +5 -4
- package/dist/index.d.ts +6 -2
- package/dist/index.js +14 -5
- package/dist/lexer/base.d.ts +2 -0
- package/dist/lexer/base.js +6 -1
- package/dist/lexer/lexer.js +3 -0
- package/dist/lexer/literals.d.ts +5 -0
- package/dist/lexer/literals.js +51 -1
- package/dist/lexer/markup.js +3 -4
- package/dist/lexer/tables.js +2 -0
- package/dist/lexer/token.d.ts +4 -3
- package/dist/lexer/token.js +0 -2
- package/dist/loader.js +30 -8
- package/dist/parser/base.d.ts +4 -0
- package/dist/parser/base.js +2 -1
- package/dist/parser/classes.d.ts +2 -0
- package/dist/parser/classes.js +88 -23
- package/dist/parser/control-flow.d.ts +3 -1
- package/dist/parser/control-flow.js +21 -24
- package/dist/parser/declarations.d.ts +7 -4
- package/dist/parser/declarations.js +45 -6
- package/dist/parser/expressions.js +27 -3
- package/dist/parser/jumps.d.ts +11 -0
- package/dist/parser/jumps.js +77 -0
- package/dist/parser/literals.d.ts +9 -0
- package/dist/parser/literals.js +45 -6
- package/dist/parser/markup.js +1 -1
- package/dist/parser/simple-statements.d.ts +1 -1
- package/dist/parser/simple-statements.js +16 -9
- package/dist/parser/statements.d.ts +0 -3
- package/dist/parser/statements.js +22 -25
- package/dist/parser/syntax.d.ts +11 -1
- package/dist/parser/syntax.js +16 -2
- package/dist/parser/types.js +8 -3
- package/dist/project-modules.d.ts +12 -0
- package/dist/project-modules.js +106 -0
- package/dist/run-file.d.ts +6 -0
- package/dist/run-file.js +111 -0
- package/dist/runtime/sources.js +11 -5
- package/dist/source-map.d.ts +17 -0
- package/dist/source-map.js +75 -0
- package/dist/ts-types.js +4 -1
- package/dist/typescript/convert-objects.d.ts +2 -1
- package/dist/typescript/convert-objects.js +18 -9
- package/dist/vite.js +12 -4
- package/dist/walk-types.js +1 -0
- package/dist/walk.d.ts +0 -2
- package/dist/walk.js +11 -28
- package/package.json +1 -1
- package/src/runtime/chan.js +15 -5
- package/src/runtime/channel.js +28 -0
- package/src/runtime/classes.js +32 -0
- package/src/runtime/core.js +10 -1
- package/src/runtime/decode.js +190 -0
- package/src/runtime/go.js +3 -0
- package/src/runtime/index.js +6 -0
- package/src/runtime/select.js +14 -9
- package/src/runtime/signals.js +59 -0
- package/src/runtime/threads.js +32 -5
- package/src/runtime/topic.js +118 -0
- 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)
|
package/dist/checker/base.js
CHANGED
|
@@ -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 →
|
|
11
|
-
// completion → Checker (checker.ts). Statements, expressions and markup
|
|
12
|
-
// recursively, so methods of later layers that earlier ones call are declared
|
|
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;
|
package/dist/checker/builtins.js
CHANGED
|
@@ -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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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. */
|
package/dist/checker/bytes.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
|
-
import {
|
|
2
|
+
import { TryChecker } from './try.ts';
|
|
3
3
|
import { type Type } from './types.ts';
|
|
4
|
-
export declare abstract class BytesChecker extends
|
|
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. */
|
package/dist/checker/bytes.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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);
|
package/dist/checker/calls.js
CHANGED
|
@@ -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,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;
|
package/dist/checker/channels.js
CHANGED
|
@@ -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;
|
package/dist/checker/checker.js
CHANGED
|
@@ -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 {
|
|
5
|
-
import { type ClassInfo
|
|
6
|
-
export declare abstract class ClassChecker extends
|
|
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
|
-
|
|
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;
|
package/dist/checker/classes.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { inlinePackages } from "../inline-packages.js";
|
|
2
|
-
import {
|
|
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
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
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
|
-
|
|
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'
|
|
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
|
+
}
|