rooklang 0.0.0-stage → 0.3.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/LICENSE +21 -0
- package/README.md +177 -2
- package/README.ru.md +178 -0
- package/dist/ast/declarations.d.ts +185 -0
- package/dist/ast/declarations.js +1 -0
- package/dist/ast/expressions.d.ts +201 -0
- package/dist/ast/expressions.js +1 -0
- package/dist/ast/markup.d.ts +141 -0
- package/dist/ast/markup.js +1 -0
- package/dist/ast/statements.d.ts +173 -0
- package/dist/ast/statements.js +1 -0
- package/dist/ast.d.ts +18 -0
- package/dist/ast.js +18 -0
- package/dist/attrs.d.ts +8 -0
- package/dist/attrs.js +23 -0
- package/dist/browser.d.ts +30 -0
- package/dist/browser.js +36 -0
- package/dist/build.d.ts +48 -0
- package/dist/build.js +112 -0
- package/dist/checker/assignments.d.ts +14 -0
- package/dist/checker/assignments.js +197 -0
- package/dist/checker/base.d.ts +124 -0
- package/dist/checker/base.js +180 -0
- package/dist/checker/bigint.d.ts +15 -0
- package/dist/checker/bigint.js +85 -0
- package/dist/checker/builtins.d.ts +9 -0
- package/dist/checker/builtins.js +307 -0
- package/dist/checker/bytes.d.ts +11 -0
- package/dist/checker/bytes.js +72 -0
- package/dist/checker/calls.d.ts +23 -0
- package/dist/checker/calls.js +210 -0
- package/dist/checker/channel.d.ts +14 -0
- package/dist/checker/channel.js +44 -0
- package/dist/checker/channels.d.ts +9 -0
- package/dist/checker/channels.js +95 -0
- package/dist/checker/checker.d.ts +4 -0
- package/dist/checker/checker.js +22 -0
- package/dist/checker/classes.d.ts +24 -0
- package/dist/checker/classes.js +223 -0
- package/dist/checker/completion-names.d.ts +11 -0
- package/dist/checker/completion-names.js +100 -0
- package/dist/checker/completion.d.ts +25 -0
- package/dist/checker/completion.js +222 -0
- package/dist/checker/component-values.d.ts +11 -0
- package/dist/checker/component-values.js +88 -0
- package/dist/checker/components.d.ts +37 -0
- package/dist/checker/components.js +287 -0
- package/dist/checker/context.d.ts +196 -0
- package/dist/checker/context.js +35 -0
- package/dist/checker/control-flow.d.ts +24 -0
- package/dist/checker/control-flow.js +237 -0
- package/dist/checker/declarations.d.ts +26 -0
- package/dist/checker/declarations.js +259 -0
- package/dist/checker/decorator-exports.d.ts +5 -0
- package/dist/checker/decorator-exports.js +40 -0
- package/dist/checker/decorator-members.d.ts +28 -0
- package/dist/checker/decorator-members.js +140 -0
- package/dist/checker/decorator-wrappers.d.ts +20 -0
- package/dist/checker/decorator-wrappers.js +132 -0
- package/dist/checker/decorators.d.ts +28 -0
- package/dist/checker/decorators.js +265 -0
- package/dist/checker/dom-svg.d.ts +7 -0
- package/dist/checker/dom-svg.js +19 -0
- package/dist/checker/dom.d.ts +22 -0
- package/dist/checker/dom.js +361 -0
- package/dist/checker/expressions.d.ts +32 -0
- package/dist/checker/expressions.js +244 -0
- package/dist/checker/flow.d.ts +18 -0
- package/dist/checker/flow.js +134 -0
- package/dist/checker/forwarding.d.ts +15 -0
- package/dist/checker/forwarding.js +78 -0
- package/dist/checker/functions.d.ts +24 -0
- package/dist/checker/functions.js +154 -0
- package/dist/checker/go-for.d.ts +12 -0
- package/dist/checker/go-for.js +93 -0
- package/dist/checker/goroutines.d.ts +19 -0
- package/dist/checker/goroutines.js +110 -0
- package/dist/checker/helpers.d.ts +35 -0
- package/dist/checker/helpers.js +162 -0
- package/dist/checker/imports.d.ts +7 -0
- package/dist/checker/imports.js +83 -0
- package/dist/checker/initializers.d.ts +22 -0
- package/dist/checker/initializers.js +104 -0
- package/dist/checker/iterables.d.ts +5 -0
- package/dist/checker/iterables.js +17 -0
- package/dist/checker/iterators.d.ts +13 -0
- package/dist/checker/iterators.js +95 -0
- package/dist/checker/loops.d.ts +10 -0
- package/dist/checker/loops.js +82 -0
- package/dist/checker/markup.d.ts +28 -0
- package/dist/checker/markup.js +220 -0
- package/dist/checker/members.d.ts +29 -0
- package/dist/checker/members.js +233 -0
- package/dist/checker/operators.d.ts +13 -0
- package/dist/checker/operators.js +177 -0
- package/dist/checker/promise.d.ts +17 -0
- package/dist/checker/promise.js +49 -0
- package/dist/checker/refs.d.ts +26 -0
- package/dist/checker/refs.js +136 -0
- package/dist/checker/resolution.d.ts +23 -0
- package/dist/checker/resolution.js +173 -0
- package/dist/checker/statements.d.ts +28 -0
- package/dist/checker/statements.js +212 -0
- package/dist/checker/styles.d.ts +14 -0
- package/dist/checker/styles.js +49 -0
- package/dist/checker/transfer.d.ts +5 -0
- package/dist/checker/transfer.js +83 -0
- package/dist/checker/types/bytes.d.ts +4 -0
- package/dist/checker/types/bytes.js +51 -0
- package/dist/checker/types/classes.d.ts +22 -0
- package/dist/checker/types/classes.js +93 -0
- package/dist/checker/types/constructors.d.ts +27 -0
- package/dist/checker/types/constructors.js +129 -0
- package/dist/checker/types/generics.d.ts +20 -0
- package/dist/checker/types/generics.js +75 -0
- package/dist/checker/types/lazy.d.ts +30 -0
- package/dist/checker/types/lazy.js +92 -0
- package/dist/checker/types/model.d.ts +149 -0
- package/dist/checker/types/model.js +15 -0
- package/dist/checker/types/params.d.ts +7 -0
- package/dist/checker/types/params.js +220 -0
- package/dist/checker/types/printing.d.ts +6 -0
- package/dist/checker/types/printing.js +109 -0
- package/dist/checker/types/relations.d.ts +16 -0
- package/dist/checker/types/relations.js +294 -0
- package/dist/checker/types.d.ts +9 -0
- package/dist/checker/types.js +10 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +193 -0
- package/dist/codegen/analysis.d.ts +18 -0
- package/dist/codegen/analysis.js +144 -0
- package/dist/codegen/attributes.d.ts +40 -0
- package/dist/codegen/attributes.js +227 -0
- package/dist/codegen/calls.d.ts +22 -0
- package/dist/codegen/calls.js +106 -0
- package/dist/codegen/chan-helpers.d.ts +2 -0
- package/dist/codegen/chan-helpers.js +253 -0
- package/dist/codegen/component-helpers.d.ts +33 -0
- package/dist/codegen/component-helpers.js +18 -0
- package/dist/codegen/component-props.d.ts +42 -0
- package/dist/codegen/component-props.js +173 -0
- package/dist/codegen/component-values.d.ts +27 -0
- package/dist/codegen/component-values.js +146 -0
- package/dist/codegen/components.d.ts +53 -0
- package/dist/codegen/components.js +268 -0
- package/dist/codegen/control-flow.d.ts +25 -0
- package/dist/codegen/control-flow.js +152 -0
- package/dist/codegen/control-statements.d.ts +28 -0
- package/dist/codegen/control-statements.js +226 -0
- package/dist/codegen/creation.d.ts +31 -0
- package/dist/codegen/creation.js +113 -0
- package/dist/codegen/early-returns.d.ts +59 -0
- package/dist/codegen/early-returns.js +244 -0
- package/dist/codegen/element-helpers.d.ts +12 -0
- package/dist/codegen/element-helpers.js +54 -0
- package/dist/codegen/elements.d.ts +71 -0
- package/dist/codegen/elements.js +293 -0
- package/dist/codegen/emitter.d.ts +135 -0
- package/dist/codegen/emitter.js +296 -0
- package/dist/codegen/expressions.d.ts +49 -0
- package/dist/codegen/expressions.js +262 -0
- package/dist/codegen/functions.d.ts +28 -0
- package/dist/codegen/functions.js +170 -0
- package/dist/codegen/go-helpers.d.ts +9 -0
- package/dist/codegen/go-helpers.js +307 -0
- package/dist/codegen/helpers.d.ts +10 -0
- package/dist/codegen/helpers.js +356 -0
- package/dist/codegen/imported.d.ts +38 -0
- package/dist/codegen/imported.js +71 -0
- package/dist/codegen/js.d.ts +20 -0
- package/dist/codegen/js.js +113 -0
- package/dist/codegen/mutations.d.ts +10 -0
- package/dist/codegen/mutations.js +102 -0
- package/dist/codegen/parallel.d.ts +7 -0
- package/dist/codegen/parallel.js +63 -0
- package/dist/codegen/reactive.d.ts +95 -0
- package/dist/codegen/reactive.js +280 -0
- package/dist/codegen/refs.d.ts +62 -0
- package/dist/codegen/refs.js +160 -0
- package/dist/codegen/select-helpers.d.ts +2 -0
- package/dist/codegen/select-helpers.js +135 -0
- package/dist/codegen/simple-statements.d.ts +42 -0
- package/dist/codegen/simple-statements.js +253 -0
- package/dist/codegen/statements.d.ts +22 -0
- package/dist/codegen/statements.js +142 -0
- package/dist/codegen/styles.d.ts +54 -0
- package/dist/codegen/styles.js +157 -0
- package/dist/codegen/syntax.d.ts +18 -0
- package/dist/codegen/syntax.js +70 -0
- package/dist/codegen/thread-helpers.d.ts +2 -0
- package/dist/codegen/thread-helpers.js +198 -0
- package/dist/codegen/updates.d.ts +51 -0
- package/dist/codegen/updates.js +201 -0
- package/dist/codegen/web-components.d.ts +20 -0
- package/dist/codegen/web-components.js +149 -0
- package/dist/compile-file.d.ts +11 -0
- package/dist/compile-file.js +24 -0
- package/dist/compile.d.ts +53 -0
- package/dist/compile.js +187 -0
- package/dist/completion.d.ts +10 -0
- package/dist/completion.js +134 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +44 -0
- package/dist/css/hash.d.ts +1 -0
- package/dist/css/hash.js +10 -0
- package/dist/css/parse.d.ts +42 -0
- package/dist/css/parse.js +138 -0
- package/dist/css/print.d.ts +9 -0
- package/dist/css/print.js +35 -0
- package/dist/css/selectors.d.ts +29 -0
- package/dist/css/selectors.js +168 -0
- package/dist/declarations.d.ts +3 -0
- package/dist/declarations.js +207 -0
- package/dist/decorator-copy.d.ts +18 -0
- package/dist/decorator-copy.js +158 -0
- package/dist/decorators.d.ts +61 -0
- package/dist/decorators.js +241 -0
- package/dist/diagnostics.d.ts +17 -0
- package/dist/diagnostics.js +22 -0
- package/dist/go-for.d.ts +26 -0
- package/dist/go-for.js +155 -0
- package/dist/goroutines.d.ts +37 -0
- package/dist/goroutines.js +253 -0
- package/dist/html-tag.d.ts +21 -0
- package/dist/html-tag.js +103 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +55 -0
- package/dist/inline-packages.d.ts +32 -0
- package/dist/inline-packages.js +129 -0
- package/dist/iterators.d.ts +10 -0
- package/dist/iterators.js +33 -0
- package/dist/lexer/base.d.ts +24 -0
- package/dist/lexer/base.js +87 -0
- package/dist/lexer/lexer.d.ts +8 -0
- package/dist/lexer/lexer.js +134 -0
- package/dist/lexer/literals.d.ts +18 -0
- package/dist/lexer/literals.js +203 -0
- package/dist/lexer/markup.d.ts +10 -0
- package/dist/lexer/markup.js +93 -0
- package/dist/lexer/style.d.ts +17 -0
- package/dist/lexer/style.js +97 -0
- package/dist/lexer/tables.d.ts +53 -0
- package/dist/lexer/tables.js +101 -0
- package/dist/lexer/token.d.ts +42 -0
- package/dist/lexer/token.js +108 -0
- package/dist/loader.d.ts +1 -0
- package/dist/loader.js +50 -0
- package/dist/methods.d.ts +5 -0
- package/dist/methods.js +81 -0
- package/dist/modules.d.ts +3 -0
- package/dist/modules.js +49 -0
- package/dist/names.d.ts +23 -0
- package/dist/names.js +267 -0
- package/dist/parser/base.d.ts +96 -0
- package/dist/parser/base.js +202 -0
- package/dist/parser/classes.d.ts +8 -0
- package/dist/parser/classes.js +164 -0
- package/dist/parser/control-flow.d.ts +29 -0
- package/dist/parser/control-flow.js +232 -0
- package/dist/parser/declarations.d.ts +27 -0
- package/dist/parser/declarations.js +238 -0
- package/dist/parser/decorators.d.ts +30 -0
- package/dist/parser/decorators.js +176 -0
- package/dist/parser/expressions.d.ts +15 -0
- package/dist/parser/expressions.js +255 -0
- package/dist/parser/literals.d.ts +27 -0
- package/dist/parser/literals.js +206 -0
- package/dist/parser/markup.d.ts +22 -0
- package/dist/parser/markup.js +193 -0
- package/dist/parser/parser.d.ts +14 -0
- package/dist/parser/parser.js +22 -0
- package/dist/parser/simple-statements.d.ts +22 -0
- package/dist/parser/simple-statements.js +156 -0
- package/dist/parser/statements.d.ts +37 -0
- package/dist/parser/statements.js +267 -0
- package/dist/parser/syntax.d.ts +51 -0
- package/dist/parser/syntax.js +173 -0
- package/dist/parser/types.d.ts +23 -0
- package/dist/parser/types.js +218 -0
- package/dist/recursion.d.ts +25 -0
- package/dist/recursion.js +170 -0
- package/dist/refs.d.ts +49 -0
- package/dist/refs.js +210 -0
- package/dist/run.d.ts +1 -0
- package/dist/run.js +21 -0
- package/dist/source.d.ts +15 -0
- package/dist/source.js +34 -0
- package/dist/style-modules.d.ts +17 -0
- package/dist/style-modules.js +49 -0
- package/dist/styles.d.ts +36 -0
- package/dist/styles.js +160 -0
- package/dist/svg.d.ts +8 -0
- package/dist/svg.js +77 -0
- package/dist/ts-types.d.ts +11 -0
- package/dist/ts-types.js +133 -0
- package/dist/typed-arrays.d.ts +8 -0
- package/dist/typed-arrays.js +43 -0
- package/dist/typescript/convert-base.d.ts +42 -0
- package/dist/typescript/convert-base.js +135 -0
- package/dist/typescript/convert-objects.d.ts +28 -0
- package/dist/typescript/convert-objects.js +245 -0
- package/dist/typescript/convert.d.ts +14 -0
- package/dist/typescript/convert.js +67 -0
- package/dist/typescript/importer.d.ts +37 -0
- package/dist/typescript/importer.js +152 -0
- package/dist/typescript/library.d.ts +23 -0
- package/dist/typescript/library.js +81 -0
- package/dist/vite.d.ts +11 -0
- package/dist/vite.js +86 -0
- package/dist/walk-types.d.ts +4 -0
- package/dist/walk-types.js +56 -0
- package/dist/walk.d.ts +5 -0
- package/dist/walk.js +315 -0
- package/package.json +104 -4
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type * as ast from '../ast.ts';
|
|
2
|
+
import { type FunctionType, type Type } from './types.ts';
|
|
3
|
+
import type { Scope } from './context.ts';
|
|
4
|
+
import { type Binding } from './context.ts';
|
|
5
|
+
/** A binding as seen from a scope and its parents. */
|
|
6
|
+
export declare function lookupIn(scope: Scope, name: string): Binding | undefined;
|
|
7
|
+
/** Return and defer statements of a body, not counting those of nested functions. */
|
|
8
|
+
export declare function ownStatements(body: ast.BlockStatement): ast.Statement[];
|
|
9
|
+
/**
|
|
10
|
+
* Values that can be element children: text, numbers (literal types included), nodes (`node` is
|
|
11
|
+
* the type of a DOM node), lists of them, or null.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isContent(given: Type, node: Type): boolean;
|
|
14
|
+
/** Values that setAttribute accepts: text, numbers, bools, or null to leave it out. */
|
|
15
|
+
export declare function isAttributeValue(type: Type): boolean;
|
|
16
|
+
export declare function isUntyped(type: Type): boolean;
|
|
17
|
+
export declare function isNumeric(type: Type): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* What `x instanceof C` makes `x`: an instance of a class, or of a constructor from a `.d.ts`,
|
|
20
|
+
* like `HTMLInputElement` (`declare var HTMLInputElement: { prototype: ...; new(): ... }`).
|
|
21
|
+
*/
|
|
22
|
+
export declare function instanceTypeOf(type: Type): Type | null;
|
|
23
|
+
/** A function and its overloads, in the order they are declared. */
|
|
24
|
+
export declare function signaturesOf(signature: FunctionType): FunctionType[];
|
|
25
|
+
export declare function callSignature(type: Type): FunctionType | null;
|
|
26
|
+
/** Whether the expected type has literal members: then a literal gets a literal type. */
|
|
27
|
+
export declare function expectsLiteral(expected: Type | null): boolean;
|
|
28
|
+
export declare function countValues(count: number): string;
|
|
29
|
+
/** A call, or `await` of a call or of `go`: it may give several values. */
|
|
30
|
+
export declare function isCall(node: ast.Expression): boolean;
|
|
31
|
+
/** How to name an expression in messages: `divide()`, `stats.minMax()`, or `this expression`. */
|
|
32
|
+
export declare function callName(node: ast.Expression): string;
|
|
33
|
+
export declare function isSuperCall(statement: ast.Statement): boolean;
|
|
34
|
+
/** `this.name = ...` at the top level of a constructor. */
|
|
35
|
+
export declare function assignsField(body: ast.BlockStatement, name: string): boolean;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { forEachChild } from "../walk.js";
|
|
2
|
+
import { CONTENT } from "./dom.js";
|
|
3
|
+
import { isAssignable, nonNull, unionMembers, widenLiterals, } from "./types.js";
|
|
4
|
+
import {} from "./context.js";
|
|
5
|
+
// Functions that the layers of the checker use and that need no state of their own.
|
|
6
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
/** A binding as seen from a scope and its parents. */
|
|
8
|
+
export function lookupIn(scope, name) {
|
|
9
|
+
for (let current = scope; current; current = current.parent) {
|
|
10
|
+
const binding = current.values.get(name);
|
|
11
|
+
if (binding)
|
|
12
|
+
return binding;
|
|
13
|
+
}
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
/** Return and defer statements of a body, not counting those of nested functions. */
|
|
17
|
+
export function ownStatements(body) {
|
|
18
|
+
const found = [];
|
|
19
|
+
const visit = (node) => {
|
|
20
|
+
if (node.kind === 'ReturnStatement' || node.kind === 'DeferStatement')
|
|
21
|
+
found.push(node);
|
|
22
|
+
if (node.kind === 'FuncDeclaration' ||
|
|
23
|
+
node.kind === 'FuncExpression' ||
|
|
24
|
+
node.kind === 'ArrowFunction' ||
|
|
25
|
+
node.kind === 'ClassDeclaration' ||
|
|
26
|
+
node.kind === 'MountStatement' ||
|
|
27
|
+
node.kind === 'EffectStatement')
|
|
28
|
+
return;
|
|
29
|
+
forEachChild(node, visit);
|
|
30
|
+
};
|
|
31
|
+
forEachChild(body, visit);
|
|
32
|
+
return found;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Values that can be element children: text, numbers (literal types included), nodes (`node` is
|
|
36
|
+
* the type of a DOM node), lists of them, or null.
|
|
37
|
+
*/
|
|
38
|
+
export function isContent(given, node) {
|
|
39
|
+
if (given === CONTENT)
|
|
40
|
+
return true;
|
|
41
|
+
const type = widenLiterals(given);
|
|
42
|
+
switch (type.kind) {
|
|
43
|
+
case 'string':
|
|
44
|
+
case 'number':
|
|
45
|
+
case 'bigint':
|
|
46
|
+
case 'any':
|
|
47
|
+
case 'unknown':
|
|
48
|
+
case 'never':
|
|
49
|
+
case 'null':
|
|
50
|
+
return true;
|
|
51
|
+
case 'nullable':
|
|
52
|
+
return isContent(type.type, node);
|
|
53
|
+
case 'array':
|
|
54
|
+
return isContent(type.element, node);
|
|
55
|
+
case 'object':
|
|
56
|
+
case 'class':
|
|
57
|
+
return isAssignable(type, node);
|
|
58
|
+
case 'union':
|
|
59
|
+
return type.types.every((member) => isContent(member, node));
|
|
60
|
+
default:
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Values that setAttribute accepts: text, numbers, bools, or null to leave it out. */
|
|
65
|
+
export function isAttributeValue(type) {
|
|
66
|
+
const value = nonNull(type);
|
|
67
|
+
return (isUntyped(value) ||
|
|
68
|
+
value.kind === 'string' ||
|
|
69
|
+
value.kind === 'number' ||
|
|
70
|
+
value.kind === 'bigint' ||
|
|
71
|
+
value.kind === 'bool' ||
|
|
72
|
+
value.kind === 'null' ||
|
|
73
|
+
value.kind === 'never');
|
|
74
|
+
}
|
|
75
|
+
export function isUntyped(type) {
|
|
76
|
+
return type.kind === 'any' || type.kind === 'unknown';
|
|
77
|
+
}
|
|
78
|
+
export function isNumeric(type) {
|
|
79
|
+
return type.kind === 'number' || isUntyped(type);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* What `x instanceof C` makes `x`: an instance of a class, or of a constructor from a `.d.ts`,
|
|
83
|
+
* like `HTMLInputElement` (`declare var HTMLInputElement: { prototype: ...; new(): ... }`).
|
|
84
|
+
*/
|
|
85
|
+
export function instanceTypeOf(type) {
|
|
86
|
+
if (type.kind === 'classValue')
|
|
87
|
+
return type.info.instance;
|
|
88
|
+
if (type.kind !== 'object')
|
|
89
|
+
return null;
|
|
90
|
+
const prototype = type.members.get('prototype');
|
|
91
|
+
if (prototype && !isUntyped(prototype.type))
|
|
92
|
+
return prototype.type;
|
|
93
|
+
return type.construct?.results[0] ?? null;
|
|
94
|
+
}
|
|
95
|
+
/** A function and its overloads, in the order they are declared. */
|
|
96
|
+
export function signaturesOf(signature) {
|
|
97
|
+
return [signature, ...(signature.overloads ?? [])];
|
|
98
|
+
}
|
|
99
|
+
export function callSignature(type) {
|
|
100
|
+
if (type.kind === 'function')
|
|
101
|
+
return type;
|
|
102
|
+
if (type.kind === 'object')
|
|
103
|
+
return type.call;
|
|
104
|
+
if (type.kind === 'union') {
|
|
105
|
+
// A union of functions can be called when they all accept and give the same: `x.toString()`.
|
|
106
|
+
const signatures = type.types.map(callSignature);
|
|
107
|
+
const [first] = signatures;
|
|
108
|
+
const same = signatures.every((each) => each !== null &&
|
|
109
|
+
first !== null &&
|
|
110
|
+
first !== undefined &&
|
|
111
|
+
isAssignable(each, first) &&
|
|
112
|
+
isAssignable(first, each));
|
|
113
|
+
return same ? (first ?? null) : null;
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
/** Whether the expected type has literal members: then a literal gets a literal type. */
|
|
118
|
+
export function expectsLiteral(expected) {
|
|
119
|
+
return (expected !== null && unionMembers(nonNull(expected)).some((type) => type.kind === 'literal'));
|
|
120
|
+
}
|
|
121
|
+
export function countValues(count) {
|
|
122
|
+
if (count === 0)
|
|
123
|
+
return 'no values';
|
|
124
|
+
return count === 1 ? '1 value' : `${count} values`;
|
|
125
|
+
}
|
|
126
|
+
/** A call, or `await` of a call or of `go`: it may give several values. */
|
|
127
|
+
export function isCall(node) {
|
|
128
|
+
if (node.kind === 'UnaryExpression' && node.operator === 'await') {
|
|
129
|
+
return node.argument.kind === 'CallExpression' || node.argument.kind === 'GoExpression';
|
|
130
|
+
}
|
|
131
|
+
return node.kind === 'CallExpression';
|
|
132
|
+
}
|
|
133
|
+
/** How to name an expression in messages: `divide()`, `stats.minMax()`, or `this expression`. */
|
|
134
|
+
export function callName(node) {
|
|
135
|
+
if (node.kind === 'UnaryExpression' && node.operator === 'await') {
|
|
136
|
+
return callName(node.argument);
|
|
137
|
+
}
|
|
138
|
+
if (node.kind === 'GoExpression')
|
|
139
|
+
return callName(node.call);
|
|
140
|
+
if (node.kind !== 'CallExpression')
|
|
141
|
+
return 'this expression';
|
|
142
|
+
const { callee } = node;
|
|
143
|
+
if (callee.kind === 'Identifier')
|
|
144
|
+
return `${callee.name}()`;
|
|
145
|
+
if (callee.kind === 'MemberExpression') {
|
|
146
|
+
const object = callee.object.kind === 'Identifier' ? `${callee.object.name}.` : '';
|
|
147
|
+
return `${object}${callee.property.name}()`;
|
|
148
|
+
}
|
|
149
|
+
return 'this call';
|
|
150
|
+
}
|
|
151
|
+
export function isSuperCall(statement) {
|
|
152
|
+
return (statement.kind === 'ExpressionStatement' &&
|
|
153
|
+
statement.expression.kind === 'CallExpression' &&
|
|
154
|
+
statement.expression.callee.kind === 'SuperExpression');
|
|
155
|
+
}
|
|
156
|
+
/** `this.name = ...` at the top level of a constructor. */
|
|
157
|
+
export function assignsField(body, name) {
|
|
158
|
+
return body.body.some((statement) => statement.kind === 'AssignmentStatement' &&
|
|
159
|
+
statement.targets.some((target) => target.kind === 'MemberExpression' &&
|
|
160
|
+
target.object.kind === 'ThisExpression' &&
|
|
161
|
+
target.property.name === name));
|
|
162
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type * as ast from '../ast.ts';
|
|
2
|
+
import { CheckerBase } from './base.ts';
|
|
3
|
+
export declare abstract class ImportChecker extends CheckerBase {
|
|
4
|
+
protected declareImport(node: ast.ImportDeclaration): void;
|
|
5
|
+
/** `import { visible } from "./visible.rk"`: decorators have a namespace of their own. */
|
|
6
|
+
private importDecorator;
|
|
7
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { CheckerBase } from "./base.js";
|
|
2
|
+
import { ANY, LazyMap, memberNames, UNKNOWN } from "./types.js";
|
|
3
|
+
// Imports: the names of other modules, typed by their exports (`importModule` for `.rk` modules,
|
|
4
|
+
// `importDeclarations` for the rest), or untyped when nothing is known about them.
|
|
5
|
+
export class ImportChecker extends CheckerBase {
|
|
6
|
+
declareImport(node) {
|
|
7
|
+
const specifier = node.source.value;
|
|
8
|
+
const isRook = specifier.endsWith('.rk');
|
|
9
|
+
const result = isRook
|
|
10
|
+
? this.options.importModule?.(specifier)
|
|
11
|
+
: this.options.importDeclarations?.(specifier);
|
|
12
|
+
if (result && 'error' in result)
|
|
13
|
+
this.error(result.error, node.source);
|
|
14
|
+
const exports = result && 'exports' in result ? result.exports : null;
|
|
15
|
+
if (node.defaultImport && exports && !isRook) {
|
|
16
|
+
// `import fs from "node:fs"`: the default export of a module with types.
|
|
17
|
+
const value = exports.values.get('default');
|
|
18
|
+
const type = exports.types.get('default');
|
|
19
|
+
if (value === undefined && type === undefined) {
|
|
20
|
+
this.error(`"${specifier}" has no default export`, node.defaultImport);
|
|
21
|
+
}
|
|
22
|
+
this.declareValue(node.defaultImport, 'import', value ?? UNKNOWN);
|
|
23
|
+
if (type !== undefined)
|
|
24
|
+
this.declareType(node.defaultImport, type);
|
|
25
|
+
}
|
|
26
|
+
else if (node.defaultImport) {
|
|
27
|
+
if (exports) {
|
|
28
|
+
this.error('Rook modules have no default export: use import { ... }', node.defaultImport);
|
|
29
|
+
}
|
|
30
|
+
this.declareValue(node.defaultImport, 'import', exports ? UNKNOWN : ANY);
|
|
31
|
+
this.scope.types.set(node.defaultImport.name, ANY);
|
|
32
|
+
}
|
|
33
|
+
if (node.namespaceImport) {
|
|
34
|
+
const values = exports?.values;
|
|
35
|
+
const members = new LazyMap(() => (values ? memberNames(values) : []), (name) => {
|
|
36
|
+
const type = values?.get(name);
|
|
37
|
+
return type && { type, method: false, visibility: 'public', owner: null };
|
|
38
|
+
});
|
|
39
|
+
const type = exports ? { kind: 'object', name: specifier, members, call: null } : ANY;
|
|
40
|
+
this.declareValue(node.namespaceImport, 'import', type);
|
|
41
|
+
}
|
|
42
|
+
for (const specifierNode of node.namedImports) {
|
|
43
|
+
const name = specifierNode.imported.name;
|
|
44
|
+
if (!exports) {
|
|
45
|
+
this.declareValue(specifierNode.local, 'import', ANY);
|
|
46
|
+
this.scope.types.set(specifierNode.local.name, ANY);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const value = exports.values.get(name);
|
|
50
|
+
const type = exports.types.get(name);
|
|
51
|
+
const decorator = exports.decorators?.get(name);
|
|
52
|
+
const component = exports.components?.get(name);
|
|
53
|
+
const found = [value, type, decorator, component].some((each) => each !== undefined);
|
|
54
|
+
if (!found) {
|
|
55
|
+
this.error(`"${name}" is not exported by "${specifier}"`, specifierNode.imported);
|
|
56
|
+
}
|
|
57
|
+
if (value !== undefined)
|
|
58
|
+
this.declareValue(specifierNode.local, 'import', value);
|
|
59
|
+
if (type !== undefined)
|
|
60
|
+
this.declareType(specifierNode.local, type);
|
|
61
|
+
if (decorator !== undefined)
|
|
62
|
+
this.importDecorator(specifierNode, decorator);
|
|
63
|
+
if (component !== undefined) {
|
|
64
|
+
this.declareValue(specifierNode.local, 'component', UNKNOWN).component = component;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** `import { visible } from "./visible.rk"`: decorators have a namespace of their own. */
|
|
69
|
+
importDecorator(node, info) {
|
|
70
|
+
const { imported, local } = node;
|
|
71
|
+
// After the error the decorator keeps its name, so its uses are not reported again.
|
|
72
|
+
if (local.name !== imported.name) {
|
|
73
|
+
this.error(`renaming decorators is not supported yet: import { ${imported.name} }`, local);
|
|
74
|
+
}
|
|
75
|
+
if (this.decorators.has(imported.name)) {
|
|
76
|
+
this.error(`the decorator @${imported.name} is already declared`, local);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
this.decorators.set(imported.name, info);
|
|
80
|
+
this.importedDecorators.add(info);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type * as ast from '../ast.ts';
|
|
2
|
+
import type { Binding } from './context.ts';
|
|
3
|
+
import { TypeResolver } from './resolution.ts';
|
|
4
|
+
export declare abstract class InitializerChecker extends TypeResolver {
|
|
5
|
+
/** Initializers being checked, the outermost first. */
|
|
6
|
+
private readonly initializers;
|
|
7
|
+
/**
|
|
8
|
+
* Checks the initializer of `bindings` with `check`, which gives them their types, and then the
|
|
9
|
+
* function bodies in it that waited for those types.
|
|
10
|
+
*/
|
|
11
|
+
protected checkInitializer(declaration: ast.VariableDeclaration, bindings: readonly Binding[], check: () => void): void;
|
|
12
|
+
/**
|
|
13
|
+
* Defers `check`, the check of a function body, if the body mentions a variable that is being
|
|
14
|
+
* initialized and has no type yet. Returns whether it was deferred.
|
|
15
|
+
*/
|
|
16
|
+
protected deferBody(body: ast.Node, check: () => void): boolean;
|
|
17
|
+
/** The error for `binding` used in its own initializer, or `null` if this use is fine. */
|
|
18
|
+
protected initializerError(binding: Binding): string | null;
|
|
19
|
+
private lookupFrom;
|
|
20
|
+
private saveState;
|
|
21
|
+
private restoreState;
|
|
22
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { mentionsName } from "../names.js";
|
|
2
|
+
import { TypeResolver } from "./resolution.js";
|
|
3
|
+
export class InitializerChecker extends TypeResolver {
|
|
4
|
+
/** Initializers being checked, the outermost first. */
|
|
5
|
+
initializers = [];
|
|
6
|
+
/**
|
|
7
|
+
* Checks the initializer of `bindings` with `check`, which gives them their types, and then the
|
|
8
|
+
* function bodies in it that waited for those types.
|
|
9
|
+
*/
|
|
10
|
+
checkInitializer(declaration, bindings, check) {
|
|
11
|
+
const initializer = {
|
|
12
|
+
declaration,
|
|
13
|
+
bindings: new Set(bindings),
|
|
14
|
+
scope: this.scope,
|
|
15
|
+
fn: this.fn,
|
|
16
|
+
deferred: [],
|
|
17
|
+
};
|
|
18
|
+
this.initializers.push(initializer);
|
|
19
|
+
try {
|
|
20
|
+
check();
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
this.initializers.pop();
|
|
24
|
+
}
|
|
25
|
+
for (const body of initializer.deferred)
|
|
26
|
+
body();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Defers `check`, the check of a function body, if the body mentions a variable that is being
|
|
30
|
+
* initialized and has no type yet. Returns whether it was deferred.
|
|
31
|
+
*/
|
|
32
|
+
deferBody(body, check) {
|
|
33
|
+
// The outermost initializer ends last: then every variable the body may use has its type.
|
|
34
|
+
const initializer = this.initializers.find((candidate) => {
|
|
35
|
+
const untyped = [...candidate.bindings].filter((binding) => binding.type === null);
|
|
36
|
+
return untyped.length > 0 && mentionsName(body, new Set(untyped.map(({ name }) => name)));
|
|
37
|
+
});
|
|
38
|
+
if (!initializer)
|
|
39
|
+
return false;
|
|
40
|
+
const state = this.saveState();
|
|
41
|
+
initializer.deferred.push(() => {
|
|
42
|
+
const current = this.saveState();
|
|
43
|
+
this.restoreState(state);
|
|
44
|
+
try {
|
|
45
|
+
check();
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
this.restoreState(current);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
/** The error for `binding` used in its own initializer, or `null` if this use is fine. */
|
|
54
|
+
initializerError(binding) {
|
|
55
|
+
const initializer = this.initializers.find((candidate) => candidate.bindings.has(binding));
|
|
56
|
+
if (!initializer)
|
|
57
|
+
return null;
|
|
58
|
+
const { name } = binding;
|
|
59
|
+
if (initializer.fn === this.fn) {
|
|
60
|
+
const outer = this.lookupFrom(initializer.scope.parent, name);
|
|
61
|
+
return outer
|
|
62
|
+
? `"${name}" here is the new variable, which has no value yet; give it another name to use the outer "${name}"`
|
|
63
|
+
: `"${name}" is used in its own initializer, before it has a value`;
|
|
64
|
+
}
|
|
65
|
+
if (binding.type !== null)
|
|
66
|
+
return null;
|
|
67
|
+
const { declaration } = initializer;
|
|
68
|
+
const value = declaration.values[declaration.names.findIndex((id) => id.name === name)];
|
|
69
|
+
const isFunction = value?.kind === 'ArrowFunction' || value?.kind === 'FuncExpression';
|
|
70
|
+
return isFunction
|
|
71
|
+
? `"${name}" is used in its own initializer: give it a type, e.g. ${declaration.keyword} ${name} func() = ...`
|
|
72
|
+
: `"${name}" is used in its own initializer: write its type after the name`;
|
|
73
|
+
}
|
|
74
|
+
lookupFrom(scope, name) {
|
|
75
|
+
for (; scope; scope = scope.parent) {
|
|
76
|
+
const binding = scope.values.get(name);
|
|
77
|
+
if (binding)
|
|
78
|
+
return binding;
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
saveState() {
|
|
83
|
+
return {
|
|
84
|
+
scope: this.scope,
|
|
85
|
+
flow: new Map(this.flow),
|
|
86
|
+
fn: this.fn,
|
|
87
|
+
cls: this.cls,
|
|
88
|
+
inferring: this.inferring,
|
|
89
|
+
component: this.component,
|
|
90
|
+
decorator: this.decorator,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
restoreState(state) {
|
|
94
|
+
({
|
|
95
|
+
scope: this.scope,
|
|
96
|
+
flow: this.flow,
|
|
97
|
+
fn: this.fn,
|
|
98
|
+
cls: this.cls,
|
|
99
|
+
inferring: this.inferring,
|
|
100
|
+
component: this.component,
|
|
101
|
+
decorator: this.decorator,
|
|
102
|
+
} = state);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ObjectType, Type } from './types.ts';
|
|
2
|
+
/** `Map<K, V>` or `ReadonlyMap<K, V>`: `for key, value in m`, and `for value in m` for the values. */
|
|
3
|
+
export declare function isMap(type: Type): boolean;
|
|
4
|
+
/** The key and the value of `for key, value in x`: of a Map, or of an array-like with positions. */
|
|
5
|
+
export declare function loopEntry(type: ObjectType): [Type, Type] | null;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// What `for ... in` goes over besides arrays, strings and channels: the iterables of the lib files,
|
|
2
|
+
// whose values come from `[Symbol.iterator]()`. A Map gives its keys and values, as a map in Go.
|
|
3
|
+
/** `Map<K, V>` or `ReadonlyMap<K, V>`: `for key, value in m`, and `for value in m` for the values. */
|
|
4
|
+
export function isMap(type) {
|
|
5
|
+
const name = type.kind === 'object' ? (type.instanceOf?.template.name ?? type.name) : null;
|
|
6
|
+
return name === 'Map' || name === 'ReadonlyMap';
|
|
7
|
+
}
|
|
8
|
+
/** The key and the value of `for key, value in x`: of a Map, or of an array-like with positions. */
|
|
9
|
+
export function loopEntry(type) {
|
|
10
|
+
const { iterates, numberIndex, instanceOf } = type;
|
|
11
|
+
if (!iterates)
|
|
12
|
+
return null;
|
|
13
|
+
if (isMap(type))
|
|
14
|
+
return instanceOf ? [instanceOf.args[0], instanceOf.args[1]] : null;
|
|
15
|
+
// A typed array or a NodeList has positions, and `entries()`, as an array has.
|
|
16
|
+
return numberIndex && type.members.has('entries') ? [{ kind: 'number' }, iterates] : null;
|
|
17
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type * as ast from '../ast.ts';
|
|
2
|
+
import { GoForChecker } from './go-for.ts';
|
|
3
|
+
import { type Type } from './types.ts';
|
|
4
|
+
export declare abstract class IteratorChecker extends GoForChecker {
|
|
5
|
+
protected checkCall(node: ast.CallExpression, callee: Type, expected?: Type | null): Type;
|
|
6
|
+
/** `Generator<T>` of the lib files, or `Generator<[K, V]>` for a yield of two values. */
|
|
7
|
+
private generatorOf;
|
|
8
|
+
/** The rules of iterators in the whole program; Checker.run calls it. */
|
|
9
|
+
protected checkIterators(): void;
|
|
10
|
+
private checkIterator;
|
|
11
|
+
/** Where `yield` is used in the body of an iterator; `nested` inside a function in it. */
|
|
12
|
+
private checkYields;
|
|
13
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { isIterator, isYieldCall } from "../iterators.js";
|
|
2
|
+
import { forEachChild } from "../walk.js";
|
|
3
|
+
import { GoForChecker } from "./go-for.js";
|
|
4
|
+
import { ANY, arrayOf, instantiate, typeToString, union, UNKNOWN, VOID, } from "./types.js";
|
|
5
|
+
// Iterators, `func(yield func(T) bool)`, as in Go 1.23 (src/iterators.ts). They compile to JS
|
|
6
|
+
// generators, so they keep to what a generator can do: `yield` is only called, in the body of the
|
|
7
|
+
// iterator itself and not in a function inside it; the iterator is not async and returns nothing;
|
|
8
|
+
// and it is not called with a function, as Go allows, but gone over with `for ... in`. Called
|
|
9
|
+
// without arguments, it gives what a generator function gives: a lazy JS iterator, with `take`,
|
|
10
|
+
// `map`, `filter` and the like.
|
|
11
|
+
const NESTED = new Set([
|
|
12
|
+
'FuncDeclaration',
|
|
13
|
+
'FuncExpression',
|
|
14
|
+
'ArrowFunction',
|
|
15
|
+
'ClassDeclaration',
|
|
16
|
+
]);
|
|
17
|
+
export class IteratorChecker extends GoForChecker {
|
|
18
|
+
checkCall(node, callee, expected = null) {
|
|
19
|
+
if (callee.kind !== 'function' || !callee.iterator) {
|
|
20
|
+
return super.checkCall(node, callee, expected);
|
|
21
|
+
}
|
|
22
|
+
if (node.arguments.length === 0)
|
|
23
|
+
return this.generatorOf(callee);
|
|
24
|
+
this.error(`${typeToString(callee)} is an iterator, gone over with "for": e.g. "for value in items"; it compiles to a JS generator, which takes no function, and seq() gives it`, node);
|
|
25
|
+
return UNKNOWN;
|
|
26
|
+
}
|
|
27
|
+
/** `Generator<T>` of the lib files, or `Generator<[K, V]>` for a yield of two values. */
|
|
28
|
+
generatorOf(iterator) {
|
|
29
|
+
const generator = this.options.library?.type('Generator');
|
|
30
|
+
const yieldType = iterator.params[0];
|
|
31
|
+
const taken = yieldType?.kind === 'function' ? yieldType.params : [];
|
|
32
|
+
const value = taken.length === 1 ? taken[0] : arrayOf(union(taken));
|
|
33
|
+
if (generator?.kind !== 'object' || generator.typeParams?.length !== 3)
|
|
34
|
+
return ANY;
|
|
35
|
+
return instantiate(generator, [value, VOID, ANY]);
|
|
36
|
+
}
|
|
37
|
+
/** The rules of iterators in the whole program; Checker.run calls it. */
|
|
38
|
+
checkIterators() {
|
|
39
|
+
const visit = (node) => {
|
|
40
|
+
if (node.kind === 'FuncDeclaration' ||
|
|
41
|
+
node.kind === 'FuncExpression' ||
|
|
42
|
+
node.kind === 'MethodDeclaration') {
|
|
43
|
+
if (isIterator(node.params))
|
|
44
|
+
this.checkIterator(node);
|
|
45
|
+
}
|
|
46
|
+
else if (node.kind === 'ArrowFunction' && isIterator(node.params)) {
|
|
47
|
+
this.error('an iterator is written with func, not as an arrow function: JS has no arrow generators, e.g. func(yield func(number) bool) { ... }', node);
|
|
48
|
+
}
|
|
49
|
+
forEachChild(node, visit);
|
|
50
|
+
};
|
|
51
|
+
visit(this.program);
|
|
52
|
+
}
|
|
53
|
+
checkIterator(node) {
|
|
54
|
+
const param = node.params[0];
|
|
55
|
+
const type = param.type;
|
|
56
|
+
if (node.async) {
|
|
57
|
+
this.error('an iterator cannot be async: it compiles to a JS generator, which yields at once; wait where it is gone over instead', node);
|
|
58
|
+
}
|
|
59
|
+
if (node.results.length > 0) {
|
|
60
|
+
this.error('an iterator returns nothing: it gives its values to yield', node.results[0]);
|
|
61
|
+
}
|
|
62
|
+
const result = type?.kind === 'FuncType' && type.results.length === 1 ? type.results[0] : null;
|
|
63
|
+
const yieldsBool = type?.kind === 'FuncType' &&
|
|
64
|
+
type.params.length >= 1 &&
|
|
65
|
+
type.params.length <= 2 &&
|
|
66
|
+
result?.kind === 'TypeReference' &&
|
|
67
|
+
result.name.name === 'bool';
|
|
68
|
+
if (!yieldsBool) {
|
|
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
|
+
}
|
|
71
|
+
this.checkYields(node.body, false);
|
|
72
|
+
}
|
|
73
|
+
/** Where `yield` is used in the body of an iterator; `nested` inside a function in it. */
|
|
74
|
+
checkYields(node, nested) {
|
|
75
|
+
if (isYieldCall(node)) {
|
|
76
|
+
if (nested) {
|
|
77
|
+
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
|
+
}
|
|
79
|
+
for (const argument of node.arguments)
|
|
80
|
+
this.checkYields(argument, nested);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (node.kind === 'Identifier' && node.name === 'yield') {
|
|
84
|
+
this.error('an iterator only calls yield, e.g. "if !yield(v) { return }": it compiles to a JS generator, where yield is not a value', node);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// An iterator inside has its own yield, checked on its own.
|
|
88
|
+
if ((node.kind === 'FuncExpression' || node.kind === 'FuncDeclaration') &&
|
|
89
|
+
isIterator(node.params)) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const inner = nested || NESTED.has(node.kind);
|
|
93
|
+
forEachChild(node, (child) => this.checkYields(child, inner));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type * as ast from '../ast.ts';
|
|
2
|
+
import { BytesChecker } from './bytes.ts';
|
|
3
|
+
export declare abstract class LoopChecker extends BytesChecker {
|
|
4
|
+
protected checkForIn(node: ast.ForInStatement): void;
|
|
5
|
+
/**
|
|
6
|
+
* The key and the value of a loop over an iterator: what its `yield` takes. Of two, one variable
|
|
7
|
+
* gets the second, as with a Map; of one, there is no key.
|
|
8
|
+
*/
|
|
9
|
+
private yielded;
|
|
10
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { assignedNames } from "../names.js";
|
|
2
|
+
import { BytesChecker } from "./bytes.js";
|
|
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";
|
|
6
|
+
// `for ... in`: over arrays, strings, the iterables of the lib files (a Map gives its keys and
|
|
7
|
+
// values), channels, which wait for their values, and iterators, `func(yield func(T) bool)`.
|
|
8
|
+
export class LoopChecker extends BytesChecker {
|
|
9
|
+
checkForIn(node) {
|
|
10
|
+
const iterable = this.checkValue(node.iterable);
|
|
11
|
+
let value = UNKNOWN;
|
|
12
|
+
let key = NUMBER;
|
|
13
|
+
if (isNullable(iterable)) {
|
|
14
|
+
this.nullError(node.iterable);
|
|
15
|
+
}
|
|
16
|
+
else if (iterable.kind === 'array') {
|
|
17
|
+
value = iterable.element;
|
|
18
|
+
}
|
|
19
|
+
else if (iterable.kind === 'object' && iterable.iterates) {
|
|
20
|
+
// A Map gives its keys and values; one variable gets the values, as with an array.
|
|
21
|
+
const entry = loopEntry(iterable);
|
|
22
|
+
value = isMap(iterable) && entry ? entry[1] : iterable.iterates;
|
|
23
|
+
if (entry)
|
|
24
|
+
key = entry[0];
|
|
25
|
+
else if (node.key) {
|
|
26
|
+
this.error(`${typeToString(iterable)} has no keys: loop over its values, e.g. "for value in items"`, node.key);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
else if (iterable.kind === 'function' && iterable.iterator) {
|
|
30
|
+
[key, value] = this.yielded(iterable, node);
|
|
31
|
+
}
|
|
32
|
+
else if (isChan(iterable)) {
|
|
33
|
+
// Each value waits for the channel, until it is closed.
|
|
34
|
+
value = elementOf(iterable);
|
|
35
|
+
if (chanDirection(iterable) === 'send') {
|
|
36
|
+
this.error(`"for ... in" cannot receive from ${typeToString(iterable)}: it only sends; receive from a "chan T" or a "<-chan T"`, node.iterable);
|
|
37
|
+
}
|
|
38
|
+
if (this.fn && !this.fn.isAsync) {
|
|
39
|
+
this.error('"for ... in" a channel waits for its values, so it is allowed only in async functions and at the top level of a module', node.iterable);
|
|
40
|
+
}
|
|
41
|
+
if (node.key)
|
|
42
|
+
this.error('a channel has no keys: write "for value in ch"', node.key);
|
|
43
|
+
}
|
|
44
|
+
else if (iterable.kind === 'string') {
|
|
45
|
+
value = STRING;
|
|
46
|
+
}
|
|
47
|
+
else if (iterable.kind === 'any') {
|
|
48
|
+
value = ANY;
|
|
49
|
+
key = ANY;
|
|
50
|
+
}
|
|
51
|
+
else if (iterable.kind === 'function') {
|
|
52
|
+
this.error(`cannot iterate over ${typeToString(iterable)}: an iterator names its parameter yield, e.g. func(yield func(number) bool)`, node.iterable);
|
|
53
|
+
}
|
|
54
|
+
else if (iterable.kind !== 'unknown') {
|
|
55
|
+
this.error(`cannot iterate over ${typeToString(iterable)}`, node.iterable);
|
|
56
|
+
}
|
|
57
|
+
this.withScope(() => {
|
|
58
|
+
if (node.key)
|
|
59
|
+
this.declareValue(node.key, 'loop', key);
|
|
60
|
+
this.declareValue(node.value, 'loop', value);
|
|
61
|
+
this.dropNarrowing(assignedNames(node.body));
|
|
62
|
+
const entry = this.flow;
|
|
63
|
+
this.flow = new Map(entry);
|
|
64
|
+
this.checkBlock(node.body.body);
|
|
65
|
+
this.flow = entry;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The key and the value of a loop over an iterator: what its `yield` takes. Of two, one variable
|
|
70
|
+
* gets the second, as with a Map; of one, there is no key.
|
|
71
|
+
*/
|
|
72
|
+
yielded(iterator, node) {
|
|
73
|
+
const yieldType = iterator.params[0];
|
|
74
|
+
const taken = yieldType?.kind === 'function' ? yieldType.params : [];
|
|
75
|
+
if (taken.length === 2)
|
|
76
|
+
return [taken[0], taken[1]];
|
|
77
|
+
if (node.key) {
|
|
78
|
+
this.error(`${typeToString(iterator)} yields one value, without a key: write "for value in ..."`, node.key);
|
|
79
|
+
}
|
|
80
|
+
return [UNKNOWN, taken[0] ?? UNKNOWN];
|
|
81
|
+
}
|
|
82
|
+
}
|