rooklang 0.3.3 → 0.4.0
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 +34 -7
- package/README.ru.md +34 -7
- package/dist/ast/declarations.d.ts +39 -4
- package/dist/ast/expressions.d.ts +34 -8
- package/dist/ast/markup.d.ts +6 -1
- package/dist/ast/statements.d.ts +28 -2
- 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 +4 -0
- package/dist/checker/assignments.js +23 -3
- package/dist/checker/base.d.ts +8 -2
- package/dist/checker/base.js +7 -7
- package/dist/checker/builtins.js +22 -8
- 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 +17 -4
- 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 +48 -13
- package/dist/checker/closing.d.ts +21 -0
- package/dist/checker/closing.js +164 -0
- package/dist/checker/completion.d.ts +3 -3
- package/dist/checker/completion.js +4 -3
- package/dist/checker/component-bindings.d.ts +7 -0
- package/dist/checker/component-bindings.js +45 -0
- package/dist/checker/component-values.js +10 -7
- package/dist/checker/components.d.ts +2 -2
- package/dist/checker/components.js +8 -3
- package/dist/checker/context.d.ts +41 -5
- package/dist/checker/context.js +23 -0
- package/dist/checker/control-flow.d.ts +11 -5
- package/dist/checker/control-flow.js +65 -40
- package/dist/checker/declarations.d.ts +2 -2
- package/dist/checker/declarations.js +4 -2
- package/dist/checker/decode.d.ts +8 -0
- package/dist/checker/decode.js +32 -0
- package/dist/checker/decorator-exports.js +4 -0
- package/dist/checker/decorator-includes.d.ts +9 -0
- package/dist/checker/decorator-includes.js +71 -0
- package/dist/checker/decorator-members.d.ts +8 -10
- package/dist/checker/decorator-members.js +28 -31
- package/dist/checker/decorator-wrappers.d.ts +1 -1
- package/dist/checker/decorator-wrappers.js +3 -2
- package/dist/checker/decorators.d.ts +14 -5
- package/dist/checker/decorators.js +52 -25
- package/dist/checker/expressions.d.ts +6 -2
- package/dist/checker/expressions.js +20 -40
- package/dist/checker/flow.d.ts +25 -3
- package/dist/checker/flow.js +56 -12
- package/dist/checker/functions.d.ts +2 -2
- package/dist/checker/functions.js +9 -5
- package/dist/checker/helpers.d.ts +5 -2
- package/dist/checker/helpers.js +16 -4
- package/dist/checker/imports.js +5 -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.d.ts +2 -0
- package/dist/checker/loops.js +50 -5
- package/dist/checker/markup.js +14 -2
- package/dist/checker/members.js +10 -1
- 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/shared-state.d.ts +11 -0
- package/dist/checker/shared-state.js +37 -0
- package/dist/checker/signals.d.ts +8 -0
- package/dist/checker/signals.js +48 -0
- package/dist/checker/statements.d.ts +5 -2
- package/dist/checker/statements.js +51 -22
- package/dist/checker/tagged-unions.d.ts +37 -0
- package/dist/checker/tagged-unions.js +132 -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/checker/untyped-globals.d.ts +1 -0
- package/dist/checker/untyped-globals.js +3 -1
- package/dist/cli.js +18 -36
- package/dist/codegen/attributes.d.ts +4 -0
- package/dist/codegen/attributes.js +24 -2
- package/dist/codegen/calls.d.ts +23 -0
- package/dist/codegen/calls.js +88 -7
- package/dist/codegen/component-helpers.d.ts +6 -1
- package/dist/codegen/component-helpers.js +6 -1
- package/dist/codegen/component-props.d.ts +11 -2
- package/dist/codegen/component-props.js +50 -5
- package/dist/codegen/components.d.ts +6 -5
- package/dist/codegen/components.js +44 -21
- package/dist/codegen/control-flow.d.ts +2 -0
- package/dist/codegen/control-flow.js +23 -9
- package/dist/codegen/control-statements.d.ts +10 -2
- package/dist/codegen/control-statements.js +56 -19
- package/dist/codegen/creation.js +5 -2
- package/dist/codegen/decode.d.ts +14 -0
- package/dist/codegen/decode.js +93 -0
- package/dist/codegen/early-returns.d.ts +4 -3
- package/dist/codegen/early-returns.js +17 -5
- package/dist/codegen/emitter.d.ts +13 -41
- package/dist/codegen/emitter.js +27 -8
- package/dist/codegen/expressions.d.ts +9 -0
- package/dist/codegen/expressions.js +15 -26
- package/dist/codegen/functions.d.ts +11 -1
- package/dist/codegen/functions.js +31 -3
- package/dist/codegen/js.d.ts +2 -2
- package/dist/codegen/js.js +5 -2
- package/dist/codegen/live.d.ts +60 -0
- package/dist/codegen/live.js +29 -0
- package/dist/codegen/mutations.d.ts +7 -0
- package/dist/codegen/mutations.js +43 -2
- package/dist/codegen/options.d.ts +42 -0
- package/dist/codegen/options.js +1 -0
- package/dist/codegen/reactive.d.ts +4 -59
- package/dist/codegen/reactive.js +34 -72
- package/dist/codegen/runtime.d.ts +7 -3
- package/dist/codegen/runtime.js +33 -14
- package/dist/codegen/shared.d.ts +22 -0
- package/dist/codegen/shared.js +93 -0
- package/dist/codegen/simple-statements.d.ts +11 -5
- package/dist/codegen/simple-statements.js +40 -15
- package/dist/codegen/statements.d.ts +5 -0
- package/dist/codegen/statements.js +99 -4
- package/dist/codegen/try.d.ts +5 -0
- package/dist/codegen/try.js +40 -0
- package/dist/codegen/updates.d.ts +11 -3
- package/dist/codegen/updates.js +52 -4
- package/dist/codegen/web-components.d.ts +2 -2
- package/dist/codegen/web-components.js +2 -2
- package/dist/compile.d.ts +9 -0
- package/dist/compile.js +25 -8
- package/dist/completion.d.ts +4 -2
- package/dist/completion.js +10 -5
- package/dist/declarations.js +32 -6
- package/dist/decorator-copy.d.ts +1 -1
- package/dist/decorator-copy.js +6 -2
- package/dist/decorator-names.d.ts +14 -0
- package/dist/decorator-names.js +56 -0
- package/dist/decorators.d.ts +2 -3
- package/dist/decorators.js +36 -14
- 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/inline-packages.d.ts +2 -1
- package/dist/inline-packages.js +9 -4
- 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/names.d.ts +4 -0
- package/dist/names.js +29 -0
- package/dist/parser/base.d.ts +5 -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 +8 -1
- package/dist/parser/control-flow.js +49 -26
- package/dist/parser/declarations.d.ts +5 -12
- package/dist/parser/declarations.js +14 -79
- package/dist/parser/decorators.d.ts +6 -2
- package/dist/parser/decorators.js +43 -19
- 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/params.d.ts +21 -0
- package/dist/parser/params.js +143 -0
- package/dist/parser/simple-statements.d.ts +2 -1
- package/dist/parser/simple-statements.js +38 -15
- package/dist/parser/statements.d.ts +5 -5
- package/dist/parser/statements.js +39 -35
- 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 +13 -6
- 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 +14 -31
- package/package.json +2 -2
- 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 +221 -0
- package/src/runtime/go.js +3 -0
- package/src/runtime/index.js +7 -0
- package/src/runtime/markup.js +27 -17
- package/src/runtime/select.js +14 -9
- package/src/runtime/shared.js +17 -0
- 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
|
@@ -1,27 +1,14 @@
|
|
|
1
1
|
import { ARRAY_MUTATORS } from "../methods.js";
|
|
2
|
-
import {
|
|
2
|
+
import { withIncluded } from "./context.js";
|
|
3
|
+
import { DecoratorIncludeChecker } from "./decorator-includes.js";
|
|
3
4
|
import { nonNull, UNKNOWN } from "./types.js";
|
|
4
5
|
// The public members of decorators, in the components they are applied to: `@visible.show()` calls
|
|
5
|
-
// a function,
|
|
6
|
-
//
|
|
7
|
-
export class DecoratorMemberChecker extends
|
|
8
|
-
/** `@visible.show`:
|
|
6
|
+
// a function, `@visible.shown` reads state or a constant, and none of them can be changed outside
|
|
7
|
+
// the decorator.
|
|
8
|
+
export class DecoratorMemberChecker extends DecoratorIncludeChecker {
|
|
9
|
+
/** `@visible.shown` or `@visible.show`: public state, a constant or a function. */
|
|
9
10
|
checkDecoratorMember(node) {
|
|
10
|
-
|
|
11
|
-
if (member && member.kind !== 'func') {
|
|
12
|
-
const { decorator, member: name } = node;
|
|
13
|
-
this.error(`read "${name.name}" of @${decorator.name} with get(@${decorator.name}.${name.name})`, node);
|
|
14
|
-
}
|
|
15
|
-
return member?.type ?? UNKNOWN;
|
|
16
|
-
}
|
|
17
|
-
/** `get(@visible.shown)`: public state or a constant. */
|
|
18
|
-
checkDecoratorGet(node) {
|
|
19
|
-
const member = this.findDecoratorMember(node.target);
|
|
20
|
-
if (member?.kind === 'func') {
|
|
21
|
-
const { decorator, member: name } = node.target;
|
|
22
|
-
this.error(`"${name.name}" of @${decorator.name} is a function: call it, @${decorator.name}.${name.name}()`, node);
|
|
23
|
-
}
|
|
24
|
-
return member?.type ?? UNKNOWN;
|
|
11
|
+
return this.findDecoratorMember(node)?.type ?? UNKNOWN;
|
|
25
12
|
}
|
|
26
13
|
/**
|
|
27
14
|
* A public member of a decorator applied to the component being checked, or of one that the
|
|
@@ -38,6 +25,13 @@ export class DecoratorMemberChecker extends DecoratorChecker {
|
|
|
38
25
|
return null;
|
|
39
26
|
}
|
|
40
27
|
const info = component.decorators.find((decorator) => decorator.node.name.name === name);
|
|
28
|
+
// `@cart.count` of the `cart` that `@shop` includes is `@shop.count`.
|
|
29
|
+
const via = component.decorators.find((decorator) => withIncluded(decorator.included).some((each) => each.node.name.name === name));
|
|
30
|
+
if (!info && via) {
|
|
31
|
+
const combined = via.node.name.name;
|
|
32
|
+
this.error(`"${member}" of @${name} comes through @${combined}: write @${combined}.${member}`, node);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
41
35
|
if (!info) {
|
|
42
36
|
this.error(this.decorators.has(name)
|
|
43
37
|
? `@${name} is not applied to ${component.node.name.name}: write @${name} before comp`
|
|
@@ -46,7 +40,7 @@ export class DecoratorMemberChecker extends DecoratorChecker {
|
|
|
46
40
|
}
|
|
47
41
|
return this.publicMember(node, info);
|
|
48
42
|
}
|
|
49
|
-
/**
|
|
43
|
+
/** `@cart.count` in `dec cartBadge() needs cart`. */
|
|
50
44
|
neededMember(node, decorator) {
|
|
51
45
|
const name = node.decorator.name;
|
|
52
46
|
const current = decorator.name.name;
|
|
@@ -54,8 +48,9 @@ export class DecoratorMemberChecker extends DecoratorChecker {
|
|
|
54
48
|
this.error(`inside @${name} its members are used by their names: ${node.member.name}`, node);
|
|
55
49
|
return null;
|
|
56
50
|
}
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
const uses = [...decorator.needs, ...decorator.includes.map((use) => use.name)];
|
|
52
|
+
if (!uses.some((used) => used.name === name)) {
|
|
53
|
+
this.error(`@${current} can use @${name} only if it needs or includes it: write "needs ${name}" after its parameters`, node.decorator);
|
|
59
54
|
return null;
|
|
60
55
|
}
|
|
61
56
|
const info = this.decorators.get(name);
|
|
@@ -74,18 +69,22 @@ export class DecoratorMemberChecker extends DecoratorChecker {
|
|
|
74
69
|
return found ?? null;
|
|
75
70
|
}
|
|
76
71
|
/**
|
|
77
|
-
* Members of a decorator are read-only outside it:
|
|
78
|
-
*
|
|
72
|
+
* Members of a decorator are read-only outside it: `@cart.count = 0`, `@cart.items[0] = x`.
|
|
73
|
+
* Returns whether `target` is one of them.
|
|
79
74
|
*/
|
|
80
75
|
checkDecoratorWrite(target) {
|
|
81
76
|
const root = decoratorRoot(target);
|
|
82
77
|
if (!root)
|
|
83
78
|
return false;
|
|
84
|
-
|
|
79
|
+
const member = this.findDecoratorMember(root);
|
|
80
|
+
// A ref always points to its element, but the element is the page's: `@framed.box.title = x`.
|
|
81
|
+
if (member?.kind === 'ref' && target !== root)
|
|
82
|
+
return false;
|
|
83
|
+
if (member)
|
|
85
84
|
this.readOnlyError(root, target);
|
|
86
85
|
return true;
|
|
87
86
|
}
|
|
88
|
-
/**
|
|
87
|
+
/** `@cart.items.push(x)`: a method that changes the array. */
|
|
89
88
|
checkDecoratorMutation(node) {
|
|
90
89
|
const { callee } = node;
|
|
91
90
|
if (callee.kind !== 'MemberExpression')
|
|
@@ -118,17 +117,15 @@ export class DecoratorMemberChecker extends DecoratorChecker {
|
|
|
118
117
|
const member = node.property.name;
|
|
119
118
|
this.error(info.members.get(member)?.kind === 'func'
|
|
120
119
|
? `"${name}" is not defined; to call a function of @${name} write @${name}.${member}()`
|
|
121
|
-
: `"${name}" is not defined; to read public state of @${name} write
|
|
120
|
+
: `"${name}" is not defined; to read public state of @${name} write @${name}.${member}`, object);
|
|
122
121
|
return true;
|
|
123
122
|
}
|
|
124
123
|
}
|
|
125
|
-
/** The member of a decorator that
|
|
124
|
+
/** The member of a decorator that `@cart.items[0].done` starts with, if any. */
|
|
126
125
|
function decoratorRoot(node) {
|
|
127
126
|
switch (node.kind) {
|
|
128
127
|
case 'DecoratorMember':
|
|
129
128
|
return node;
|
|
130
|
-
case 'DecoratorGet':
|
|
131
|
-
return node.target;
|
|
132
129
|
case 'MemberExpression':
|
|
133
130
|
case 'IndexExpression':
|
|
134
131
|
return decoratorRoot(node.object);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ComponentInfo, type DecoratorInfo } from './context.ts';
|
|
2
2
|
import { StyleChecker } from './styles.ts';
|
|
3
3
|
import { type Type } from './types.ts';
|
|
4
4
|
type WrapperInfo = NonNullable<DecoratorInfo['wrapper']>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { decoratorNames, wrapperBody, wrapperOf } from "../decorators.js";
|
|
2
|
+
import { withIncluded } from "./context.js";
|
|
2
3
|
import { isUntyped, ownStatements } from "./helpers.js";
|
|
3
4
|
import { StyleChecker } from "./styles.js";
|
|
4
5
|
import { isAssignable, typeToString, UNKNOWN } from "./types.js";
|
|
@@ -78,7 +79,7 @@ export class DecoratorWrapperChecker extends StyleChecker {
|
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
wrappedType(info, own, seen) {
|
|
81
|
-
return info.decorators.reduceRight((type, decorator) => (decorator.wrapper ? this.wrapperResult(decorator.wrapper, seen) : type), own);
|
|
82
|
+
return withIncluded(info.decorators).reduceRight((type, decorator) => (decorator.wrapper ? this.wrapperResult(decorator.wrapper, seen) : type), own);
|
|
82
83
|
}
|
|
83
84
|
/** What a wrapper gives, found without checking it again, like the markup of a component. */
|
|
84
85
|
wrapperResult(wrapper, seen) {
|
|
@@ -96,7 +97,7 @@ export class DecoratorWrapperChecker extends StyleChecker {
|
|
|
96
97
|
checkWrappedTypes(info) {
|
|
97
98
|
let type = this.ownResult(info, new Set());
|
|
98
99
|
let source = null;
|
|
99
|
-
for (const decorator of
|
|
100
|
+
for (const decorator of withIncluded(info.decorators).reverse()) {
|
|
100
101
|
const { wrapper } = decorator;
|
|
101
102
|
if (!wrapper)
|
|
102
103
|
continue;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
|
-
import type
|
|
2
|
+
import { type ComponentInfo, type DecoratorInfo, type Scope } from './context.ts';
|
|
3
3
|
import { DecoratorExportChecker } from './decorator-exports.ts';
|
|
4
|
+
import { type Type } from './types.ts';
|
|
4
5
|
export declare abstract class DecoratorChecker extends DecoratorExportChecker {
|
|
5
6
|
protected resolveDecoratorParams(info: DecoratorInfo): void;
|
|
6
7
|
/** `needs cart, auth`: other decorators, each once. */
|
|
7
8
|
protected checkNeedsDeclaration(info: DecoratorInfo): void;
|
|
8
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Decorators after the ones they need or include. A cycle is an error: none can be first.
|
|
11
|
+
*/
|
|
9
12
|
protected orderByNeeds(decorators: readonly DecoratorInfo[]): DecoratorInfo[];
|
|
10
13
|
/**
|
|
11
14
|
* The body is checked once, by itself: it does not see the components it is applied to. Its
|
|
@@ -15,11 +18,17 @@ export declare abstract class DecoratorChecker extends DecoratorExportChecker {
|
|
|
15
18
|
protected collectMembers(info: DecoratorInfo, scope: Scope): void;
|
|
16
19
|
/**
|
|
17
20
|
* The decorators before `comp`: they exist, are applied once and below the ones they need, get
|
|
18
|
-
* valid arguments, and their wrappers get what they can take.
|
|
21
|
+
* valid arguments, and their wrappers get what they can take. Those that a decorator includes
|
|
22
|
+
* are applied with it, above it.
|
|
19
23
|
*/
|
|
20
24
|
protected checkDecoratorUses(info: ComponentInfo): void;
|
|
21
|
-
/**
|
|
22
|
-
|
|
25
|
+
/** Applied once, below the ones it needs; `via` is the decorator that includes it. */
|
|
26
|
+
private checkApplied;
|
|
27
|
+
/**
|
|
28
|
+
* Arguments are positional and see `params`: the properties of the component, as its body does,
|
|
29
|
+
* or the parameters of the decorator that includes this one.
|
|
30
|
+
*/
|
|
31
|
+
protected checkDecoratorArguments(use: ast.DecoratorUse, decorator: DecoratorInfo, scope: readonly [ast.Identifier, Type][]): void;
|
|
23
32
|
/**
|
|
24
33
|
* The code of the decorator and its arguments go into the component's body, so the names of the
|
|
25
34
|
* module they use must not be hidden there by the component's declarations.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { freeNames } from "../names.js";
|
|
2
2
|
import { forEachChild } from "../walk.js";
|
|
3
|
+
import { withIncluded, } from "./context.js";
|
|
3
4
|
import { DecoratorExportChecker } from "./decorator-exports.js";
|
|
4
5
|
import { lookupIn, ownStatements } from "./helpers.js";
|
|
5
6
|
import { func, UNKNOWN } from "./types.js";
|
|
@@ -42,7 +43,9 @@ export class DecoratorChecker extends DecoratorExportChecker {
|
|
|
42
43
|
needed.add(name);
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* Decorators after the ones they need or include. A cycle is an error: none can be first.
|
|
48
|
+
*/
|
|
46
49
|
orderByNeeds(decorators) {
|
|
47
50
|
const order = [];
|
|
48
51
|
const path = [];
|
|
@@ -51,13 +54,17 @@ export class DecoratorChecker extends DecoratorExportChecker {
|
|
|
51
54
|
return;
|
|
52
55
|
const at = path.indexOf(info);
|
|
53
56
|
if (at >= 0) {
|
|
54
|
-
const cycle = path.slice(at)
|
|
55
|
-
const steps = cycle.map((
|
|
56
|
-
|
|
57
|
+
const cycle = path.slice(at);
|
|
58
|
+
const steps = cycle.map((each, i) => {
|
|
59
|
+
const next = cycle[(i + 1) % cycle.length];
|
|
60
|
+
const how = each.included.includes(next) ? 'includes' : 'needs';
|
|
61
|
+
return `@${each.node.name.name} ${how} @${next.node.name.name}`;
|
|
62
|
+
});
|
|
63
|
+
this.error(`decorators cannot need or include each other: ${steps.join(', ')}; remove one of them`, info.node.name);
|
|
57
64
|
return;
|
|
58
65
|
}
|
|
59
66
|
path.push(info);
|
|
60
|
-
for (const needed of info.needed)
|
|
67
|
+
for (const needed of [...info.needed, ...info.included])
|
|
61
68
|
visit(needed);
|
|
62
69
|
path.pop();
|
|
63
70
|
order.push(info);
|
|
@@ -112,8 +119,7 @@ export class DecoratorChecker extends DecoratorExportChecker {
|
|
|
112
119
|
}
|
|
113
120
|
else if (statement.kind === 'VariableDeclaration' &&
|
|
114
121
|
statement.isPublic &&
|
|
115
|
-
statement.keyword !== 'let'
|
|
116
|
-
statement.keyword !== 'ref') {
|
|
122
|
+
statement.keyword !== 'let') {
|
|
117
123
|
for (const name of statement.names)
|
|
118
124
|
add(name, statement.keyword);
|
|
119
125
|
}
|
|
@@ -121,10 +127,16 @@ export class DecoratorChecker extends DecoratorExportChecker {
|
|
|
121
127
|
}
|
|
122
128
|
/**
|
|
123
129
|
* The decorators before `comp`: they exist, are applied once and below the ones they need, get
|
|
124
|
-
* valid arguments, and their wrappers get what they can take.
|
|
130
|
+
* valid arguments, and their wrappers get what they can take. Those that a decorator includes
|
|
131
|
+
* are applied with it, above it.
|
|
125
132
|
*/
|
|
126
133
|
checkDecoratorUses(info) {
|
|
127
|
-
|
|
134
|
+
// The decorator that each one is applied with, for those that another includes.
|
|
135
|
+
const applied = new Map();
|
|
136
|
+
const props = info.node.params.map((param) => [
|
|
137
|
+
param.name,
|
|
138
|
+
info.props.get(param.name.name)?.type ?? UNKNOWN,
|
|
139
|
+
]);
|
|
128
140
|
for (const use of info.node.decorators) {
|
|
129
141
|
const name = use.name.name;
|
|
130
142
|
const decorator = this.decorators.get(name);
|
|
@@ -132,23 +144,39 @@ export class DecoratorChecker extends DecoratorExportChecker {
|
|
|
132
144
|
this.error(`unknown decorator @${name}`, use.name);
|
|
133
145
|
continue;
|
|
134
146
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
continue;
|
|
141
|
-
const other = needed.node.name.name;
|
|
142
|
-
this.error(`@${name} needs @${other} above it: @${other} @${name} comp ...`, use);
|
|
147
|
+
for (const each of withIncluded([decorator])) {
|
|
148
|
+
const via = each === decorator ? null : name;
|
|
149
|
+
this.checkApplied(each, via, applied, use);
|
|
150
|
+
applied.set(each, via);
|
|
151
|
+
this.checkInlinedNames(use, each, info);
|
|
143
152
|
}
|
|
144
|
-
|
|
145
|
-
this.checkDecoratorArguments(use, decorator, info);
|
|
146
|
-
this.checkInlinedNames(use, decorator, info);
|
|
153
|
+
this.checkDecoratorArguments(use, decorator, props);
|
|
147
154
|
}
|
|
148
155
|
this.checkWrappedTypes(info);
|
|
149
156
|
}
|
|
150
|
-
/**
|
|
151
|
-
|
|
157
|
+
/** Applied once, below the ones it needs; `via` is the decorator that includes it. */
|
|
158
|
+
checkApplied(decorator, via, applied, use) {
|
|
159
|
+
const name = decorator.node.name.name;
|
|
160
|
+
const where = (other) => (other === null ? '' : ` (it is in @${other})`);
|
|
161
|
+
if (applied.has(decorator)) {
|
|
162
|
+
const before = applied.get(decorator) ?? null;
|
|
163
|
+
this.error(before === null && via === null
|
|
164
|
+
? `@${name} is applied twice: apply it once`
|
|
165
|
+
: `@${name} is applied twice${where(via ?? before)}: apply it once`, use);
|
|
166
|
+
}
|
|
167
|
+
// The body of a decorator may read the state of those it needs right away.
|
|
168
|
+
for (const needed of decorator.needed) {
|
|
169
|
+
if (applied.has(needed))
|
|
170
|
+
continue;
|
|
171
|
+
const other = needed.node.name.name;
|
|
172
|
+
this.error(`@${name}${where(via)} needs @${other} above it: @${other} @${via ?? name} comp ...`, use);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Arguments are positional and see `params`: the properties of the component, as its body does,
|
|
177
|
+
* or the parameters of the decorator that includes this one.
|
|
178
|
+
*/
|
|
179
|
+
checkDecoratorArguments(use, decorator, scope) {
|
|
152
180
|
const name = use.name.name;
|
|
153
181
|
const { params } = decorator.node;
|
|
154
182
|
const extra = use.arguments[params.length];
|
|
@@ -157,9 +185,8 @@ export class DecoratorChecker extends DecoratorExportChecker {
|
|
|
157
185
|
this.error(params.length === 0 ? `@${name} takes no arguments` : `@${name} takes ${count}`, extra);
|
|
158
186
|
}
|
|
159
187
|
this.withScope(() => {
|
|
160
|
-
for (const param of
|
|
161
|
-
this.declareValue(param
|
|
162
|
-
}
|
|
188
|
+
for (const [param, type] of scope)
|
|
189
|
+
this.declareValue(param, 'prop', type);
|
|
163
190
|
params.forEach((param, i) => {
|
|
164
191
|
const type = decorator.params[i] ?? UNKNOWN;
|
|
165
192
|
const arg = use.arguments[i];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
|
-
import type
|
|
2
|
+
import { type ComponentInfo } from './context.ts';
|
|
3
3
|
import { FunctionChecker } from './functions.ts';
|
|
4
4
|
import { type ComponentType, type Type } from './types.ts';
|
|
5
5
|
export declare abstract class ExpressionChecker extends FunctionChecker {
|
|
@@ -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,7 @@
|
|
|
1
|
+
import { placeOf } from "./context.js";
|
|
1
2
|
import { FunctionChecker } from "./functions.js";
|
|
2
3
|
import { expectsLiteral, callName, isUntyped } from "./helpers.js";
|
|
3
|
-
import { ANY, BIGINT,
|
|
4
|
+
import { ANY, BIGINT, BOOL, hasUntypedBase, instanceMembers, isAssignable, literal, nonNull, NULL, nullable, NUMBER, STRING, typeToString, UNKNOWN, } from "./types.js";
|
|
4
5
|
// Expressions: names, `this`, literals of arrays and objects; the operators are in operators.ts.
|
|
5
6
|
export class ExpressionChecker extends FunctionChecker {
|
|
6
7
|
/** The type of an expression that must be a single value (not `void` or several results). */
|
|
@@ -35,6 +36,9 @@ export class ExpressionChecker extends FunctionChecker {
|
|
|
35
36
|
return BIGINT;
|
|
36
37
|
case 'StringLiteral':
|
|
37
38
|
return expectsLiteral(expected) ? literal(node.value) : STRING;
|
|
39
|
+
// A RegExp of lib.es5.d.ts; the playground, which has no lib files, does not know its members.
|
|
40
|
+
case 'RegExpLiteral':
|
|
41
|
+
return this.options.library?.type('RegExp') ?? ANY;
|
|
38
42
|
case 'TemplateLiteral':
|
|
39
43
|
for (const expression of node.expressions)
|
|
40
44
|
this.checkValue(expression);
|
|
@@ -71,6 +75,12 @@ export class ExpressionChecker extends FunctionChecker {
|
|
|
71
75
|
return this.checkStreamChan(node);
|
|
72
76
|
case 'QueueExpression':
|
|
73
77
|
return this.checkQueue(node);
|
|
78
|
+
case 'TopicExpression':
|
|
79
|
+
return this.checkTopic(node);
|
|
80
|
+
case 'DecodeExpression':
|
|
81
|
+
return this.checkDecode(node);
|
|
82
|
+
case 'TryExpression':
|
|
83
|
+
return this.checkTryExpression(node);
|
|
74
84
|
case 'ConversionExpression':
|
|
75
85
|
return this.checkConversion(node);
|
|
76
86
|
case 'MakeExpression':
|
|
@@ -81,8 +91,6 @@ export class ExpressionChecker extends FunctionChecker {
|
|
|
81
91
|
return this.checkElement(node);
|
|
82
92
|
case 'DecoratorMember':
|
|
83
93
|
return this.checkDecoratorMember(node);
|
|
84
|
-
case 'DecoratorGet':
|
|
85
|
-
return this.checkDecoratorGet(node);
|
|
86
94
|
case 'MemberExpression':
|
|
87
95
|
case 'IndexExpression':
|
|
88
96
|
case 'CallExpression': {
|
|
@@ -104,7 +112,9 @@ export class ExpressionChecker extends FunctionChecker {
|
|
|
104
112
|
? `"${node.name}" is a type, not a value`
|
|
105
113
|
: node.name === 'make'
|
|
106
114
|
? 'make takes the type of a typed array first, e.g. make([]float64, n)'
|
|
107
|
-
:
|
|
115
|
+
: node.name === 'decode'
|
|
116
|
+
? 'decode takes the type first, e.g. decode<User>(body)'
|
|
117
|
+
: `"${node.name}" is not defined`, node);
|
|
108
118
|
return UNKNOWN;
|
|
109
119
|
}
|
|
110
120
|
// A component as a value: a tag can take it from there, `<section.content />`.
|
|
@@ -119,7 +129,7 @@ export class ExpressionChecker extends FunctionChecker {
|
|
|
119
129
|
this.error(`"${node.name}" is used before its declaration`, node);
|
|
120
130
|
return UNKNOWN;
|
|
121
131
|
}
|
|
122
|
-
return this.flow.get(binding) ?? binding.type;
|
|
132
|
+
return this.flow.get(placeOf(binding)) ?? binding.type;
|
|
123
133
|
}
|
|
124
134
|
thisType(node) {
|
|
125
135
|
if (!this.cls) {
|
|
@@ -142,44 +152,13 @@ export class ExpressionChecker extends FunctionChecker {
|
|
|
142
152
|
this.error(`class "${info.name}" has no base class`, node);
|
|
143
153
|
return UNKNOWN;
|
|
144
154
|
}
|
|
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
155
|
checkObjectLiteral(node, expected) {
|
|
178
156
|
let target = expected ? nonNull(expected) : null;
|
|
179
|
-
// `?(ScrollIntoViewOptions | bool)`: an object literal can only be the object type in it
|
|
157
|
+
// `?(ScrollIntoViewOptions | bool)`: an object literal can only be the object type in it, or
|
|
158
|
+
// the one that its tag chooses, `{ kind: "pickup" }` of `Pickup | Courier`.
|
|
180
159
|
if (target?.kind === 'union') {
|
|
181
160
|
const objects = target.types.filter((type) => type.kind === 'object' || type.kind === 'class');
|
|
182
|
-
target = objects.length === 1 ? objects[0] :
|
|
161
|
+
target = objects.length === 1 ? objects[0] : this.taggedMember(node, objects, target);
|
|
183
162
|
}
|
|
184
163
|
const index = target?.kind === 'object' ? target.index : undefined;
|
|
185
164
|
const targetMembers = target?.kind === 'object'
|
|
@@ -189,7 +168,8 @@ export class ExpressionChecker extends FunctionChecker {
|
|
|
189
168
|
: null;
|
|
190
169
|
const members = new Map();
|
|
191
170
|
const written = new Set();
|
|
192
|
-
|
|
171
|
+
// A tag of no member is reported once, not again where the literal is used.
|
|
172
|
+
let untyped = target?.kind === 'unknown';
|
|
193
173
|
for (const property of node.properties) {
|
|
194
174
|
if (property.kind === 'SpreadElement') {
|
|
195
175
|
const spread = this.checkValue(property.argument);
|
package/dist/checker/flow.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
|
-
import type { Flow, Narrowing } from './context.ts';
|
|
3
|
-
import { type Type } from './types.ts';
|
|
2
|
+
import type { Binding, Flow, Narrowing, Place } from './context.ts';
|
|
3
|
+
import { type LiteralType, type Type } from './types.ts';
|
|
4
4
|
/** What `typeof` gives for every value of a type, or `null` when it may give several things. */
|
|
5
5
|
export declare function typeofTag(type: Type): string | null;
|
|
6
6
|
/** The type after `typeof x == tag` is found true (`matches`) or false. */
|
|
@@ -9,8 +9,30 @@ export declare function narrowByTypeof(type: Type, tag: string, matches: boolean
|
|
|
9
9
|
export declare function literalValue(node: ast.Expression): string | number | boolean | undefined;
|
|
10
10
|
/** A type without one literal value: what is left after `x != "all"`. */
|
|
11
11
|
export declare function withoutLiteral(type: Type, value: string | number | boolean): Type;
|
|
12
|
+
/**
|
|
13
|
+
* A union after a comparison of its tag, a field with literal types in each member: `tags` has the
|
|
14
|
+
* values the tag may have in each member, `values` those it is compared with. When `equal`, the
|
|
15
|
+
* members whose tag may be one of the values remain; otherwise those whose tag may be another one.
|
|
16
|
+
*/
|
|
17
|
+
export declare function narrowByTag(type: Type, tags: readonly (readonly LiteralType['value'][])[], values: readonly LiteralType['value'][], equal: boolean): Type;
|
|
18
|
+
/**
|
|
19
|
+
* Whether `field` is a tag of what `place` was declared as: then an assignment to it may change
|
|
20
|
+
* the member of the union that the place was narrowed to.
|
|
21
|
+
*/
|
|
22
|
+
export type IsTag = (place: Place, field: string) => boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Whether an assignment to the fields `written` of a variable ends the narrowing of `place`, a
|
|
25
|
+
* path of the same variable or the variable itself (the empty path): it writes the path, its
|
|
26
|
+
* start or a tag of it. `"delivery.kind"` ends the narrowing of `"delivery"`, which may come from
|
|
27
|
+
* that tag; `"delivery.floor"` does not.
|
|
28
|
+
*/
|
|
29
|
+
export declare function overwrites(written: string, place: Place, isTag: IsTag): boolean;
|
|
30
|
+
/** Ends the narrowing that an assignment to `binding` or to its fields `written` changes. */
|
|
31
|
+
export declare function forgetWritten(flow: Flow, binding: Binding, written: string, isTag: IsTag): void;
|
|
32
|
+
/** The flow without what assignments to the variables `names` and the paths `paths` change. */
|
|
33
|
+
export declare function withoutAssigned(flow: Flow, names: ReadonlySet<string>, paths: ReadonlySet<string>, isTag: IsTag): Flow;
|
|
12
34
|
export declare function withNarrowing(flow: Flow, narrowing: Narrowing): Flow;
|
|
13
|
-
/** Narrowing that holds after both branches: the
|
|
35
|
+
/** Narrowing that holds after both branches: the places narrowed the same way in both. */
|
|
14
36
|
export declare function mergeFlows(a: Flow, b: Flow): Flow;
|
|
15
37
|
/** Go's terminating statements: execution never continues after them. */
|
|
16
38
|
export declare function isTerminating(node: ast.Statement): boolean;
|
package/dist/checker/flow.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
|
-
// Control flow for the checker: how conditions narrow the types of variables, and which
|
|
4
|
-
// execution never continues after.
|
|
3
|
+
// Control flow for the checker: how conditions narrow the types of variables and paths, and which
|
|
4
|
+
// statements execution never continues after.
|
|
5
5
|
/** What a value of unknown type is after `typeof x == tag`. */
|
|
6
6
|
const PRIMITIVE_TAGS = new Map([
|
|
7
7
|
['string', STRING],
|
|
@@ -67,19 +67,59 @@ export function withoutLiteral(type, value) {
|
|
|
67
67
|
const kept = unionMembers(nonNull(type)).filter((member) => !(member.kind === 'literal' && member.value === value));
|
|
68
68
|
return union(isNullable(type) ? [...kept, NULL] : kept);
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* A union after a comparison of its tag, a field with literal types in each member: `tags` has the
|
|
72
|
+
* values the tag may have in each member, `values` those it is compared with. When `equal`, the
|
|
73
|
+
* members whose tag may be one of the values remain; otherwise those whose tag may be another one.
|
|
74
|
+
*/
|
|
75
|
+
export function narrowByTag(type, tags, values, equal) {
|
|
76
|
+
const kept = unionMembers(nonNull(type)).filter((_, i) => {
|
|
77
|
+
const tag = tags[i] ?? [];
|
|
78
|
+
return equal
|
|
79
|
+
? tag.some((value) => values.includes(value))
|
|
80
|
+
: !tag.every((value) => values.includes(value));
|
|
81
|
+
});
|
|
82
|
+
return union(!equal && isNullable(type) ? [...kept, NULL] : kept);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Whether an assignment to the fields `written` of a variable ends the narrowing of `place`, a
|
|
86
|
+
* path of the same variable or the variable itself (the empty path): it writes the path, its
|
|
87
|
+
* start or a tag of it. `"delivery.kind"` ends the narrowing of `"delivery"`, which may come from
|
|
88
|
+
* that tag; `"delivery.floor"` does not.
|
|
89
|
+
*/
|
|
90
|
+
export function overwrites(written, place, isTag) {
|
|
91
|
+
const { path } = place;
|
|
92
|
+
if (written === '' || written === path || path.startsWith(`${written}.`))
|
|
93
|
+
return true;
|
|
94
|
+
const field = path === '' ? written : written.startsWith(`${path}.`) ? written.slice(path.length + 1) : null;
|
|
95
|
+
return field !== null && !field.includes('.') && isTag(place, field);
|
|
96
|
+
}
|
|
97
|
+
/** Ends the narrowing that an assignment to `binding` or to its fields `written` changes. */
|
|
98
|
+
export function forgetWritten(flow, binding, written, isTag) {
|
|
99
|
+
for (const place of flow.keys()) {
|
|
100
|
+
if (place.binding === binding && overwrites(written, place, isTag))
|
|
101
|
+
flow.delete(place);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** The flow without what assignments to the variables `names` and the paths `paths` change. */
|
|
105
|
+
export function withoutAssigned(flow, names, paths, isTag) {
|
|
106
|
+
const written = [...paths].map((path) => path.split('.'));
|
|
107
|
+
return new Map([...flow].filter(([place]) => !names.has(place.binding.name) &&
|
|
108
|
+
!written.some(([root, ...fields]) => root === place.binding.name && overwrites(fields.join('.'), place, isTag))));
|
|
109
|
+
}
|
|
70
110
|
export function withNarrowing(flow, narrowing) {
|
|
71
111
|
const result = new Map(flow);
|
|
72
|
-
for (const [
|
|
73
|
-
result.set(
|
|
112
|
+
for (const [place, type] of narrowing)
|
|
113
|
+
result.set(place, type);
|
|
74
114
|
return result;
|
|
75
115
|
}
|
|
76
|
-
/** Narrowing that holds after both branches: the
|
|
116
|
+
/** Narrowing that holds after both branches: the places narrowed the same way in both. */
|
|
77
117
|
export function mergeFlows(a, b) {
|
|
78
118
|
const result = new Map();
|
|
79
|
-
for (const [
|
|
80
|
-
const other = b.get(
|
|
119
|
+
for (const [place, type] of a) {
|
|
120
|
+
const other = b.get(place);
|
|
81
121
|
if (other && typesEqual(type, other))
|
|
82
|
-
result.set(
|
|
122
|
+
result.set(place, type);
|
|
83
123
|
}
|
|
84
124
|
return result;
|
|
85
125
|
}
|
|
@@ -96,18 +136,22 @@ export function isTerminating(node) {
|
|
|
96
136
|
case 'IfStatement':
|
|
97
137
|
return (node.alternate !== null && isTerminating(node.consequent) && isTerminating(node.alternate));
|
|
98
138
|
case 'ForStatement':
|
|
99
|
-
return node.condition === null && !containsBreak(node.body.body);
|
|
139
|
+
return node.condition === null && !containsBreak(node.body.body, node.label ?? null);
|
|
100
140
|
case 'SwitchStatement':
|
|
101
141
|
return (node.cases.some((switchCase) => switchCase.tests.length === 0) &&
|
|
102
142
|
node.cases.every((switchCase) => {
|
|
103
143
|
const last = switchCase.body.at(-1);
|
|
104
|
-
return last !== undefined &&
|
|
144
|
+
return (last !== undefined &&
|
|
145
|
+
isTerminating(last) &&
|
|
146
|
+
!containsBreak(switchCase.body, node.label ?? null));
|
|
105
147
|
}));
|
|
106
148
|
// A select runs one of its cases, waiting for one if there is no default.
|
|
107
149
|
case 'SelectStatement':
|
|
108
150
|
return node.cases.every((selectCase) => {
|
|
109
151
|
const last = selectCase.body.at(-1);
|
|
110
|
-
return last !== undefined &&
|
|
152
|
+
return (last !== undefined &&
|
|
153
|
+
isTerminating(last) &&
|
|
154
|
+
!containsBreak(selectCase.body, node.label ?? null));
|
|
111
155
|
});
|
|
112
156
|
case 'TryStatement':
|
|
113
157
|
return ((isTerminating(node.block) && (!node.handler || isTerminating(node.handler.body))) ||
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
2
|
import { type BindingKind, Scope, type Flow } from './context.ts';
|
|
3
|
-
import {
|
|
3
|
+
import { TaggedUnionChecker } from './tagged-unions.ts';
|
|
4
4
|
import { type FunctionType, type Type } from './types.ts';
|
|
5
|
-
export declare abstract class FunctionChecker extends
|
|
5
|
+
export declare abstract class FunctionChecker extends TaggedUnionChecker {
|
|
6
6
|
/**
|
|
7
7
|
* Checks a function body and returns its result types: the declared ones, or those inferred from
|
|
8
8
|
* the `return` statements when `results` is `null`.
|