rooklang 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +7 -0
- package/dist/checker/assignments.js +48 -8
- package/dist/checker/base.d.ts +2 -1
- package/dist/checker/base.js +6 -12
- 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 +19 -0
- package/dist/checker/control-flow.d.ts +6 -0
- package/dist/checker/control-flow.js +34 -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/error-results.d.ts +6 -0
- package/dist/checker/error-results.js +31 -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/statements.d.ts +5 -0
- package/dist/checker/statements.js +19 -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 +19 -3
- package/dist/codegen/control-statements.js +105 -20
- 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 -5
- package/dist/codegen/runtime.js +28 -20
- 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 +40 -13
- 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/runtime-module.d.ts +1 -0
- package/dist/runtime-module.js +3 -0
- package/dist/source-map.d.ts +17 -0
- package/dist/source-map.js +75 -0
- package/dist/ts-types.d.ts +4 -0
- package/dist/ts-types.js +20 -3
- package/dist/typescript/convert-objects.d.ts +2 -1
- package/dist/typescript/convert-objects.js +18 -9
- package/dist/vite.js +13 -5
- 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 +14 -5
- 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
package/dist/checker/flow.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { containsBreak } from "../
|
|
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 &&
|
|
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 &&
|
|
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;
|
package/dist/checker/helpers.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
|
|
71
|
+
if (node.body)
|
|
72
|
+
this.checkYields(node.body, false);
|
|
72
73
|
}
|
|
73
|
-
/**
|
|
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 ||
|
|
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;
|
|
@@ -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
|
+
}
|
package/dist/checker/loops.js
CHANGED
|
@@ -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,
|
|
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
|
}
|
package/dist/checker/members.js
CHANGED
|
@@ -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 {
|
|
2
|
+
import { ArrayLiteralChecker } from './array-literals.ts';
|
|
3
3
|
import { type Type } from './types.ts';
|
|
4
|
-
export declare abstract class OperatorChecker extends
|
|
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 {
|
|
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
|
|
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(
|
|
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
|
}
|
package/dist/checker/queues.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
|
-
import {
|
|
2
|
+
import { SignalChecker } from './signals.ts';
|
|
3
3
|
import { type Type } from './types.ts';
|
|
4
|
-
export declare abstract class QueueChecker extends
|
|
4
|
+
export declare abstract class QueueChecker extends SignalChecker {
|
|
5
5
|
protected checkQueue(node: ast.QueueExpression): Type;
|
|
6
6
|
}
|
package/dist/checker/queues.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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
|
-
?
|
|
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
|
+
}
|
|
@@ -23,6 +23,11 @@ export declare abstract class StatementChecker extends AssignmentChecker {
|
|
|
23
23
|
private checkDelete;
|
|
24
24
|
protected checkBlock(statements: readonly ast.Statement[]): void;
|
|
25
25
|
protected checkVariableDeclaration(node: ast.VariableDeclaration): void;
|
|
26
|
+
/**
|
|
27
|
+
* `const user, err = loadUser(id)` of `(User, error)`: `user` is not null once `err` is found to
|
|
28
|
+
* be null. Variables of `let` may be assigned apart, so only constants are linked.
|
|
29
|
+
*/
|
|
30
|
+
private linkError;
|
|
26
31
|
/** The types of the variables of a declaration: the written one, or those of their values. */
|
|
27
32
|
private initializerTypes;
|
|
28
33
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { guardedByError } from "./error-results.js";
|
|
1
2
|
import { AssignmentChecker } from "./assignments.js";
|
|
2
3
|
import { isUntyped, isNumeric, ownStatements } from "./helpers.js";
|
|
3
4
|
import { func, hasZeroValue, isNullable, nonNull, nullable, typeToString, UNKNOWN, } from "./types.js";
|
|
@@ -182,8 +183,26 @@ export class StatementChecker extends AssignmentChecker {
|
|
|
182
183
|
this.flow.set(binding, nonNull(type));
|
|
183
184
|
}
|
|
184
185
|
});
|
|
186
|
+
if (node.keyword === 'const' && !declared)
|
|
187
|
+
this.linkError(node, bindings);
|
|
185
188
|
});
|
|
186
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* `const user, err = loadUser(id)` of `(User, error)`: `user` is not null once `err` is found to
|
|
192
|
+
* be null. Variables of `let` may be assigned apart, so only constants are linked.
|
|
193
|
+
*/
|
|
194
|
+
linkError(node, bindings) {
|
|
195
|
+
const results = node.values.length === 1 ? this.typeOfChecked(node.values[0]) : null;
|
|
196
|
+
const error = bindings.at(-1);
|
|
197
|
+
if (results?.kind !== 'tuple' || results.types.length !== bindings.length || !error)
|
|
198
|
+
return;
|
|
199
|
+
const guarded = guardedByError(results.types).flatMap((i) => bindings[i] ?? []);
|
|
200
|
+
if (guarded.length === 0)
|
|
201
|
+
return;
|
|
202
|
+
error.guards = guarded;
|
|
203
|
+
for (const binding of guarded)
|
|
204
|
+
binding.guardedBy = error;
|
|
205
|
+
}
|
|
187
206
|
/** The types of the variables of a declaration: the written one, or those of their values. */
|
|
188
207
|
initializerTypes(node, declared) {
|
|
189
208
|
const { names, values } = node;
|
|
@@ -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
|
|
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;
|
package/dist/checker/transfer.js
CHANGED
|
@@ -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
|
-
|
|
66
|
-
|
|
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
|
+
}
|