rooklang 0.4.2 → 0.5.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/CHANGELOG.md +235 -0
- package/CHANGELOG.ru.md +235 -0
- package/dist/aliases.d.ts +2 -0
- package/dist/aliases.js +83 -0
- package/dist/ast/declarations.d.ts +18 -0
- package/dist/ast/expressions.d.ts +14 -1
- package/dist/ast/markup.d.ts +2 -0
- package/dist/ast/statements.d.ts +2 -2
- package/dist/checker/assignments.js +2 -1
- package/dist/checker/base.d.ts +3 -0
- package/dist/checker/base.js +1 -1
- package/dist/checker/builtins.js +5 -1
- package/dist/checker/calls.d.ts +2 -0
- package/dist/checker/calls.js +18 -3
- package/dist/checker/classes.js +8 -0
- package/dist/checker/component-bindings.d.ts +16 -0
- package/dist/checker/component-bindings.js +65 -2
- package/dist/checker/context.d.ts +12 -0
- package/dist/checker/declarations.d.ts +2 -0
- package/dist/checker/declarations.js +38 -3
- package/dist/checker/expressions.d.ts +1 -0
- package/dist/checker/expressions.js +17 -1
- package/dist/checker/flow.d.ts +1 -1
- package/dist/checker/flow.js +10 -0
- package/dist/checker/functions.d.ts +2 -0
- package/dist/checker/functions.js +33 -7
- package/dist/checker/goroutines.d.ts +2 -2
- package/dist/checker/goroutines.js +2 -2
- package/dist/checker/imports.d.ts +10 -0
- package/dist/checker/imports.js +73 -6
- package/dist/checker/initializers.d.ts +12 -0
- package/dist/checker/initializers.js +80 -0
- package/dist/checker/loops.js +3 -1
- package/dist/checker/members.js +1 -0
- package/dist/checker/resolution.d.ts +2 -0
- package/dist/checker/resolution.js +13 -2
- package/dist/checker/statements.d.ts +5 -0
- package/dist/checker/statements.js +26 -3
- package/dist/checker/tagged-unions.d.ts +6 -0
- package/dist/checker/tagged-unions.js +31 -1
- package/dist/checker/tags.d.ts +6 -0
- package/dist/checker/tags.js +44 -0
- package/dist/checker/types/constructors.js +10 -1
- package/dist/checker/types/params.js +33 -7
- package/dist/checker/types/printing.js +5 -1
- package/dist/checker/types/relations.js +2 -1
- package/dist/codegen/calls.d.ts +2 -0
- package/dist/codegen/calls.js +23 -1
- package/dist/codegen/component-props.js +8 -1
- package/dist/codegen/control-flow.d.ts +3 -0
- package/dist/codegen/control-flow.js +17 -0
- package/dist/codegen/expressions.d.ts +4 -1
- package/dist/codegen/expressions.js +4 -15
- package/dist/codegen/functions.d.ts +2 -0
- package/dist/codegen/functions.js +13 -0
- package/dist/codegen/mutations.d.ts +6 -0
- package/dist/codegen/mutations.js +36 -0
- package/dist/codegen/options.d.ts +2 -0
- package/dist/codegen/reactive.js +9 -5
- package/dist/codegen/simple-statements.js +3 -1
- package/dist/codegen/statements.d.ts +4 -0
- package/dist/codegen/statements.js +37 -1
- package/dist/compile.js +2 -0
- package/dist/completion.js +2 -1
- package/dist/declarations.js +18 -0
- package/dist/lexer/base.d.ts +3 -2
- package/dist/lexer/base.js +14 -5
- package/dist/lexer/lexer.js +3 -0
- package/dist/lexer/token.d.ts +6 -1
- package/dist/lexer/token.js +0 -1
- package/dist/modules.js +4 -3
- package/dist/parser/base.d.ts +5 -0
- package/dist/parser/base.js +9 -1
- package/dist/parser/classes.js +4 -3
- package/dist/parser/declarations.d.ts +2 -0
- package/dist/parser/declarations.js +25 -2
- package/dist/parser/decorators.js +1 -1
- package/dist/parser/expressions.js +6 -0
- package/dist/parser/literals.d.ts +2 -4
- package/dist/parser/literals.js +12 -24
- package/dist/parser/objects.d.ts +8 -0
- package/dist/parser/objects.js +51 -0
- package/dist/parser/params.d.ts +37 -4
- package/dist/parser/params.js +143 -9
- package/dist/parser/postfix.d.ts +5 -0
- package/dist/parser/postfix.js +36 -0
- package/dist/parser/simple-statements.d.ts +2 -0
- package/dist/parser/simple-statements.js +50 -0
- package/dist/parser/statements.js +8 -6
- package/dist/parser/syntax.js +0 -1
- package/dist/parser/types.d.ts +0 -4
- package/dist/parser/types.js +17 -32
- package/dist/project-modules.js +21 -0
- package/dist/reexports.d.ts +4 -0
- package/dist/reexports.js +56 -0
- package/dist/typescript/importer.d.ts +4 -0
- package/dist/typescript/importer.js +27 -3
- package/dist/typescript/library.js +5 -2
- package/dist/walk.js +12 -0
- package/package.json +4 -2
|
@@ -77,6 +77,7 @@ export class AssignmentChecker extends InitializerChecker {
|
|
|
77
77
|
checkTarget(target) {
|
|
78
78
|
if (this.checkDecoratorWrite(target) || this.checkSharedWrite(target))
|
|
79
79
|
return UNKNOWN;
|
|
80
|
+
this.checkPropWrite(target);
|
|
80
81
|
switch (target.kind) {
|
|
81
82
|
case 'Identifier': {
|
|
82
83
|
if (target.name === '_')
|
|
@@ -192,7 +193,7 @@ export class AssignmentChecker extends InitializerChecker {
|
|
|
192
193
|
}
|
|
193
194
|
const expected = fn.results;
|
|
194
195
|
if (values.length === 0) {
|
|
195
|
-
if (expected.length > 0) {
|
|
196
|
+
if (expected.length > 0 && !fn.mayEnd) {
|
|
196
197
|
this.error(`missing return values: expected ${countValues(expected.length)}`, node);
|
|
197
198
|
}
|
|
198
199
|
return;
|
package/dist/checker/base.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export declare abstract class CheckerBase {
|
|
|
6
6
|
protected abstract checkClassBodies(node: ast.ClassDeclaration, info: ClassInfo): void;
|
|
7
7
|
protected abstract ensureClassResolved(info: ClassInfo): void;
|
|
8
8
|
protected abstract resolveClass(node: ast.ClassDeclaration, info: ClassInfo): void;
|
|
9
|
+
protected abstract typeEarly(node: ast.VariableDeclaration): void;
|
|
10
|
+
protected abstract resolveResults(results: readonly ast.TypeNode[]): Type[];
|
|
9
11
|
protected abstract inferredType(type: Type, node: ast.Expression): Type;
|
|
10
12
|
protected abstract resolveAlias(entry: AliasEntry): Type;
|
|
11
13
|
protected abstract resolveType(node: ast.TypeNode): Type;
|
|
@@ -52,6 +54,7 @@ export declare abstract class CheckerBase {
|
|
|
52
54
|
protected abstract wrappedType(info: ComponentInfo, own: Type, seen: Set<ComponentInfo>): Type;
|
|
53
55
|
protected abstract checkDecoratorMember(node: ast.DecoratorMember): Type;
|
|
54
56
|
protected abstract checkDecoratorWrite(target: ast.Expression): boolean;
|
|
57
|
+
protected abstract checkPropWrite(target: ast.Expression): void;
|
|
55
58
|
protected abstract checkComponentUse(node: ast.ElementExpression, tag: ast.Identifier): Type;
|
|
56
59
|
protected abstract checkRefDeclaration(node: ast.VariableDeclaration): void;
|
|
57
60
|
protected abstract checkStyle(node: ast.StyleStatement): void;
|
package/dist/checker/base.js
CHANGED
|
@@ -7,7 +7,7 @@ import { explainMismatch, isAssignable, LazyMap, memberNames, nonNull, property,
|
|
|
7
7
|
// shared state → declarations → class members → classes → resolution → initializers → assignments → statements → control flow →
|
|
8
8
|
// tagged unions → functions → expressions → array literals → operators → markup → forwarding → component bindings → components → component values →
|
|
9
9
|
// refs → styles → decorator wrappers → decorator exports → decorators → decorator includes → decorator members →
|
|
10
|
-
// members → calls → goroutines → channels → signals → queues → topics → decode → try → bytes → loops → go for →
|
|
10
|
+
// members → calls → tags → goroutines → channels → signals → queues → topics → decode → try → bytes → loops → go for →
|
|
11
11
|
// iterators → closing → completion → Checker (checker.ts). Statements, expressions and markup
|
|
12
12
|
// check each other recursively, so methods of later layers that earlier ones call are declared
|
|
13
13
|
// here as abstract. This layer has the state of a check, diagnostics, scopes and names, and the DOM types.
|
package/dist/checker/builtins.js
CHANGED
|
@@ -7,7 +7,7 @@ import { CHANNEL_OBJECT, TOPIC_OBJECT } from "./channel-objects.js";
|
|
|
7
7
|
import { PROMISE } from "./promise.js";
|
|
8
8
|
import { BUFFERS } from "./transfer.js";
|
|
9
9
|
import { UNTYPED_CLASSES, UNTYPED_GLOBALS } from "./untyped-globals.js";
|
|
10
|
-
import { ANY, arrayOf, BIGINT, BOOL, ERROR, ERROR_CLASS, func, method,
|
|
10
|
+
import { ANY, arrayOf, BIGINT, BOOL, ERROR, ERROR_CLASS, func, method, NEVER, nullable, NUMBER, property, STRING, topicOf, union, VOID, } from "./types.js";
|
|
11
11
|
// Types of the JS standard library that Rook programs use most. Anything not listed here
|
|
12
12
|
// can still be reached through the untyped globals below.
|
|
13
13
|
function object(name, members, call) {
|
|
@@ -280,6 +280,10 @@ export const GLOBAL_TYPES = new Map([
|
|
|
280
280
|
['string', STRING],
|
|
281
281
|
['bool', BOOL],
|
|
282
282
|
['any', ANY],
|
|
283
|
+
// A function whose result is `never` does not end: it throws or exits, as `process.exit`.
|
|
284
|
+
['never', NEVER],
|
|
285
|
+
// The value of `Promise<void>`; as the result of a function, `void` is no result.
|
|
286
|
+
['void', VOID],
|
|
283
287
|
['error', ERROR],
|
|
284
288
|
['Error', ERROR_CLASS.instance],
|
|
285
289
|
...['Date', 'RegExp', 'Map', 'Set', 'URL', ...UNTYPED_CLASSES, ...BUFFERS].map((name) => [name, ANY]),
|
package/dist/checker/calls.d.ts
CHANGED
|
@@ -21,3 +21,5 @@ export declare abstract class CallChecker extends MemberChecker {
|
|
|
21
21
|
protected checkArguments(signature: FunctionType, args: readonly (ast.Expression | ast.SpreadElement)[], node: ast.NodeBase, inferred?: Map<TypeParam, Type>, expected?: Type | null): Type[];
|
|
22
22
|
protected checkArgumentList(signature: FunctionType, args: readonly (ast.Expression | ast.SpreadElement)[], inferred: Map<TypeParam, Type>, needsContext: (arg: ast.Expression | ast.SpreadElement) => boolean, beforeContext?: () => void): void;
|
|
23
23
|
}
|
|
24
|
+
/** The inferred type parameters, and the default, or unknown, of each of the others. */
|
|
25
|
+
export declare function withDefaults(params: readonly TypeParam[], inferred: ReadonlyMap<TypeParam, Type>): Map<TypeParam, Type>;
|
package/dist/checker/calls.js
CHANGED
|
@@ -136,7 +136,10 @@ export class CallChecker extends MemberChecker {
|
|
|
136
136
|
}
|
|
137
137
|
// Arrow functions without parameter types are checked last: their parameter types may depend
|
|
138
138
|
// on type parameters inferred from the other arguments (e.g. `reduce(f, initial)`).
|
|
139
|
-
|
|
139
|
+
// So are object literals with such functions in their fields: `serve({ fetch: req => ... })`.
|
|
140
|
+
const needsContext = (arg) => (arg.kind === 'ArrowFunction' && arg.params.some((param) => param.type === null)) ||
|
|
141
|
+
(arg.kind === 'ObjectLiteral' &&
|
|
142
|
+
arg.properties.some((property) => property.kind === 'Property' && needsContext(property.value)));
|
|
140
143
|
const inferring = this.inferring;
|
|
141
144
|
this.inferring = new Set(signature.typeParams);
|
|
142
145
|
const errors = this.diagnostics.length;
|
|
@@ -200,8 +203,11 @@ export class CallChecker extends MemberChecker {
|
|
|
200
203
|
return;
|
|
201
204
|
}
|
|
202
205
|
// An expected type with parameters that are not inferred yet would reject the argument:
|
|
203
|
-
// `[1, 2]` for `[]T` is checked on its own, and T is inferred from it.
|
|
204
|
-
|
|
206
|
+
// `[1, 2]` for `[]T` is checked on its own, and T is inferred from it. An object literal
|
|
207
|
+
// checked last takes what is still open as its default, or unknown, as in TypeScript.
|
|
208
|
+
const expected = contextPass && arg.kind === 'ObjectLiteral'
|
|
209
|
+
? substitute(param, withDefaults(signature.typeParams, inferred))
|
|
210
|
+
: substitute(param, inferred);
|
|
205
211
|
const type = this.checkValue(arg, arg.kind === 'ArrowFunction' || !containsTypeParam(expected, this.inferring)
|
|
206
212
|
? expected
|
|
207
213
|
: null);
|
|
@@ -211,3 +217,12 @@ export class CallChecker extends MemberChecker {
|
|
|
211
217
|
}
|
|
212
218
|
}
|
|
213
219
|
}
|
|
220
|
+
/** The inferred type parameters, and the default, or unknown, of each of the others. */
|
|
221
|
+
export function withDefaults(params, inferred) {
|
|
222
|
+
const bindings = new Map(inferred);
|
|
223
|
+
for (const param of params) {
|
|
224
|
+
if (!bindings.has(param))
|
|
225
|
+
bindings.set(param, param.default ?? UNKNOWN);
|
|
226
|
+
}
|
|
227
|
+
return bindings;
|
|
228
|
+
}
|
package/dist/checker/classes.js
CHANGED
|
@@ -253,6 +253,14 @@ export class ClassChecker extends ClassMemberChecker {
|
|
|
253
253
|
break;
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
|
+
// `export { Rows } from "./rows.rk"`: the names of another module, as it exports them.
|
|
257
|
+
for (const kind of ['values', 'types', 'decorators', 'components']) {
|
|
258
|
+
for (const [name, value] of this.reexported[kind] ?? []) {
|
|
259
|
+
exports[kind].set(name, value);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
for (const name of this.reexported.shared ?? [])
|
|
263
|
+
exports.shared.add(name);
|
|
256
264
|
return { exports, declarations };
|
|
257
265
|
}
|
|
258
266
|
}
|
|
@@ -4,4 +4,20 @@ import { ForwardingChecker } from './forwarding.ts';
|
|
|
4
4
|
export declare abstract class ComponentBindingChecker extends ForwardingChecker {
|
|
5
5
|
/** `bind:active={tab}` on a component; adds the name of the property to `given`. */
|
|
6
6
|
protected checkPropBinding(attribute: ast.JsxAttribute, info: ComponentInfo, component: string, given: Set<string>): void;
|
|
7
|
+
/**
|
|
8
|
+
* The variable that `bind:` writes into. The item of `for row in rows` is one too: the component
|
|
9
|
+
* changes it in place, and a new value replaces it in `rows`, which must be a variable or a field.
|
|
10
|
+
*/
|
|
11
|
+
private bindTarget;
|
|
12
|
+
/**
|
|
13
|
+
* `row.name = "x"` or `bind:value={row.name}` where `row` is a property that is not bound: the
|
|
14
|
+
* object is the parent's, which would change without its markup knowing.
|
|
15
|
+
*/
|
|
16
|
+
protected checkPropWrite(target: ast.Expression): void;
|
|
17
|
+
/**
|
|
18
|
+
* `items.push(x)` or `seen.add(id)` where the property is not bound. Other methods are not
|
|
19
|
+
* reported: a method of a class or a function in a field may change nothing.
|
|
20
|
+
*/
|
|
21
|
+
protected checkPropMutation(node: ast.CallExpression): void;
|
|
22
|
+
private propChanged;
|
|
7
23
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { attrsParam } from "../attrs.js";
|
|
2
|
+
import { ARRAY_MUTATORS } from "../methods.js";
|
|
2
3
|
import { ForwardingChecker } from "./forwarding.js";
|
|
3
4
|
import { isUntyped } from "./helpers.js";
|
|
4
|
-
import { isAssignable, typeToString } from "./types.js";
|
|
5
|
+
import { isAssignable, nonNull, typeToString, UNKNOWN } from "./types.js";
|
|
5
6
|
// Bound properties: `comp Tabs(bind active string)` may change `active`, and
|
|
6
7
|
// `<Tabs bind:active={tab} />` writes those changes into the parent's `tab`, as `bind:value` of an
|
|
7
8
|
// input does. The value goes both ways, so the variable and the property have the same type.
|
|
@@ -33,7 +34,7 @@ export class ComponentBindingChecker extends ForwardingChecker {
|
|
|
33
34
|
this.error(`"bind:${name}" needs a variable or a field to write to`, value);
|
|
34
35
|
return;
|
|
35
36
|
}
|
|
36
|
-
const type = this.
|
|
37
|
+
const type = this.bindTarget(value, name);
|
|
37
38
|
this.checkedTypes.set(value, type);
|
|
38
39
|
if (!prop?.bind || isUntyped(type) || isUntyped(prop.type))
|
|
39
40
|
return;
|
|
@@ -42,4 +43,66 @@ export class ComponentBindingChecker extends ForwardingChecker {
|
|
|
42
43
|
this.error(`cannot bind ${typeToString(type)} to "${name}" of <${component}>, which is ${typeToString(prop.type)}`, value);
|
|
43
44
|
}
|
|
44
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* The variable that `bind:` writes into. The item of `for row in rows` is one too: the component
|
|
48
|
+
* changes it in place, and a new value replaces it in `rows`, which must be a variable or a field.
|
|
49
|
+
*/
|
|
50
|
+
bindTarget(value, name) {
|
|
51
|
+
const binding = value.kind === 'Identifier' ? this.lookupValue(value.name) : undefined;
|
|
52
|
+
if (binding?.kind !== 'loop' || value.kind !== 'Identifier')
|
|
53
|
+
return this.checkTarget(value);
|
|
54
|
+
const list = binding.iterable;
|
|
55
|
+
if (list?.kind !== 'Identifier' && list?.kind !== 'MemberExpression') {
|
|
56
|
+
this.error(`"bind:${name}" of "${value.name}" needs a loop over a list in a variable or a field, e.g. for ${value.name} in items`, value);
|
|
57
|
+
return this.checkValue(value);
|
|
58
|
+
}
|
|
59
|
+
// Written as `rows[i] = value`, so `rows` must be one that can be changed here.
|
|
60
|
+
const at = { start: value.start, end: value.end };
|
|
61
|
+
this.checkTarget({
|
|
62
|
+
kind: 'IndexExpression',
|
|
63
|
+
object: list,
|
|
64
|
+
index: { kind: 'NumberLiteral', value: 0, raw: '0', ...at },
|
|
65
|
+
optional: false,
|
|
66
|
+
start: value.start,
|
|
67
|
+
end: value.end,
|
|
68
|
+
});
|
|
69
|
+
return this.checkValue(value);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* `row.name = "x"` or `bind:value={row.name}` where `row` is a property that is not bound: the
|
|
73
|
+
* object is the parent's, which would change without its markup knowing.
|
|
74
|
+
*/
|
|
75
|
+
checkPropWrite(target) {
|
|
76
|
+
if (target.kind === 'MemberExpression' || target.kind === 'IndexExpression') {
|
|
77
|
+
this.propChanged(target.object, target);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* `items.push(x)` or `seen.add(id)` where the property is not bound. Other methods are not
|
|
82
|
+
* reported: a method of a class or a function in a field may change nothing.
|
|
83
|
+
*/
|
|
84
|
+
checkPropMutation(node) {
|
|
85
|
+
const { callee } = node;
|
|
86
|
+
if (callee.kind !== 'MemberExpression')
|
|
87
|
+
return;
|
|
88
|
+
const object = nonNull(this.typeOfChecked(callee.object) ?? UNKNOWN);
|
|
89
|
+
const name = callee.property.name;
|
|
90
|
+
const collection = object.kind === 'object' ? (object.instanceOf?.template.name ?? object.name) : null;
|
|
91
|
+
const changes = object.kind === 'array'
|
|
92
|
+
? ARRAY_MUTATORS.has(name)
|
|
93
|
+
: (collection === 'Map' || collection === 'Set') && COLLECTION_MUTATORS.has(name);
|
|
94
|
+
if (changes)
|
|
95
|
+
this.propChanged(callee.object, node);
|
|
96
|
+
}
|
|
97
|
+
propChanged(object, node) {
|
|
98
|
+
let root = object;
|
|
99
|
+
while (root.kind === 'MemberExpression' || root.kind === 'IndexExpression')
|
|
100
|
+
root = root.object;
|
|
101
|
+
if (root.kind !== 'Identifier' || !this.component || this.decorator)
|
|
102
|
+
return;
|
|
103
|
+
if (this.lookupValue(root.name)?.kind !== 'prop')
|
|
104
|
+
return;
|
|
105
|
+
this.error(`"${root.name}" is a property: changing it in place would not update the parent; declare it bound, e.g. comp ${this.component.node.name.name}(bind ${root.name} ...), and pass bind:${root.name}={...}`, node);
|
|
106
|
+
}
|
|
45
107
|
}
|
|
108
|
+
const COLLECTION_MUTATORS = new Set(['set', 'add', 'delete', 'clear']);
|
|
@@ -110,6 +110,13 @@ export interface Binding {
|
|
|
110
110
|
*/
|
|
111
111
|
guards?: Binding[];
|
|
112
112
|
guardedBy?: Binding;
|
|
113
|
+
/**
|
|
114
|
+
* A constant of the module with a written type or a literal value: its type, known before its
|
|
115
|
+
* declaration is checked. The fields of classes, which are resolved first, may use it.
|
|
116
|
+
*/
|
|
117
|
+
early?: Type;
|
|
118
|
+
/** The list that `for item in items` goes over, which `bind:item={item}` writes into. */
|
|
119
|
+
iterable?: ast.Expression;
|
|
113
120
|
}
|
|
114
121
|
export interface ComponentInfo {
|
|
115
122
|
node: ast.ComponentDeclaration;
|
|
@@ -212,6 +219,11 @@ export interface FunctionContext {
|
|
|
212
219
|
isComponent?: boolean;
|
|
213
220
|
/** An async function, which may wait: `await`, `<-ch`, `for v in ch`. */
|
|
214
221
|
isAsync?: boolean;
|
|
222
|
+
/**
|
|
223
|
+
* The function may end without a value, as in TypeScript: its result is `any`, or it is a
|
|
224
|
+
* callback whose result in a `.d.ts` is `void | T`, `onopen = () => { ... }`.
|
|
225
|
+
*/
|
|
226
|
+
mayEnd?: boolean;
|
|
215
227
|
}
|
|
216
228
|
export interface ClassContext {
|
|
217
229
|
info: ClassInfo;
|
|
@@ -21,6 +21,8 @@ export declare abstract class DeclarationChecker extends SharedStateChecker {
|
|
|
21
21
|
/** The type of a function; an async one returns a Promise of its results, and one whose
|
|
22
22
|
* parameter is `yield` is an iterator (iterators.ts). */
|
|
23
23
|
protected signature(params: readonly ast.Parameter[], results: readonly ast.TypeNode[], async?: boolean): FunctionType;
|
|
24
|
+
/** `...parts []any`: what each of the arguments it takes is, `any`. */
|
|
25
|
+
private restElement;
|
|
24
26
|
/** `Promise<...>` of the results of an async function, with the global `Promise` type. */
|
|
25
27
|
protected promiseOf(results: readonly Type[]): Type;
|
|
26
28
|
}
|
|
@@ -5,7 +5,7 @@ import { recursiveComponents } from "../recursion.js";
|
|
|
5
5
|
import { Scope } from "./context.js";
|
|
6
6
|
import { SharedStateChecker } from "./shared-state.js";
|
|
7
7
|
import { awaited, isPromise, promiseOf } from "./promise.js";
|
|
8
|
-
import { createClass, func, typeToString, UNKNOWN, } from "./types.js";
|
|
8
|
+
import { ANY, createClass, func, typeToString, UNKNOWN, } from "./types.js";
|
|
9
9
|
// Declarations of a list of statements: imports, types, functions, components, decorators and
|
|
10
10
|
// classes, declared before the statements are checked, so that they can refer to each other.
|
|
11
11
|
export class DeclarationChecker extends SharedStateChecker {
|
|
@@ -32,6 +32,9 @@ export class DeclarationChecker extends SharedStateChecker {
|
|
|
32
32
|
case 'ImportDeclaration':
|
|
33
33
|
this.declareImport(statement);
|
|
34
34
|
break;
|
|
35
|
+
case 'ExportFromDeclaration':
|
|
36
|
+
this.declareExportFrom(statement);
|
|
37
|
+
break;
|
|
35
38
|
case 'ClassDeclaration': {
|
|
36
39
|
const info = createClass(statement.name.name);
|
|
37
40
|
info.data = statement.data;
|
|
@@ -130,6 +133,12 @@ export class DeclarationChecker extends SharedStateChecker {
|
|
|
130
133
|
if (entry?.kind === 'alias')
|
|
131
134
|
this.resolveAlias(entry);
|
|
132
135
|
}
|
|
136
|
+
if (topLevel) {
|
|
137
|
+
for (const statement of statements) {
|
|
138
|
+
if (statement.kind === 'VariableDeclaration')
|
|
139
|
+
this.typeEarly(statement);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
133
142
|
for (const [node, binding] of functions) {
|
|
134
143
|
const typeParams = this.createTypeParams(node.typeParams);
|
|
135
144
|
binding.type = this.withTypeParams(typeParams, node.typeParams, () => ({
|
|
@@ -242,7 +251,7 @@ export class DeclarationChecker extends SharedStateChecker {
|
|
|
242
251
|
/** The type of a function; an async one returns a Promise of its results, and one whose
|
|
243
252
|
* parameter is `yield` is an iterator (iterators.ts). */
|
|
244
253
|
signature(params, results, async = false) {
|
|
245
|
-
const types =
|
|
254
|
+
const types = this.resolveResults(results);
|
|
246
255
|
if (async) {
|
|
247
256
|
types.forEach((type, i) => {
|
|
248
257
|
if (!isPromise(type))
|
|
@@ -251,7 +260,33 @@ export class DeclarationChecker extends SharedStateChecker {
|
|
|
251
260
|
types[i] = awaited(type);
|
|
252
261
|
});
|
|
253
262
|
}
|
|
254
|
-
|
|
263
|
+
const last = params.at(-1);
|
|
264
|
+
const rest = last?.rest ? this.restElement(last) : undefined;
|
|
265
|
+
const plain = rest === undefined ? params : params.slice(0, -1);
|
|
266
|
+
const firstDefault = plain.findIndex((param) => param.defaultValue !== null);
|
|
267
|
+
const required = firstDefault === -1 ? plain.length : firstDefault;
|
|
268
|
+
for (const param of plain.slice(required)) {
|
|
269
|
+
if (param.defaultValue === null) {
|
|
270
|
+
this.error(`"${param.name.name}" comes after a parameter with a default value, so it needs one too, e.g. ${param.name.name} = ...`, param);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return func(
|
|
274
|
+
// `times = 1` has the type of its value.
|
|
275
|
+
plain.map((param) => param.type
|
|
276
|
+
? this.resolveType(param.type)
|
|
277
|
+
: param.defaultValue
|
|
278
|
+
? this.inferredType(this.checkValue(param.defaultValue), param.defaultValue)
|
|
279
|
+
: UNKNOWN), async ? [this.promiseOf(types)] : types, { iterator: isIterator(params), required, ...(rest ? { rest } : {}) });
|
|
280
|
+
}
|
|
281
|
+
/** `...parts []any`: what each of the arguments it takes is, `any`. */
|
|
282
|
+
restElement(param) {
|
|
283
|
+
const type = param.type ? this.resolveType(param.type) : ANY;
|
|
284
|
+
if (type.kind === 'array')
|
|
285
|
+
return type.element;
|
|
286
|
+
if (type.kind !== 'any' && type.kind !== 'unknown') {
|
|
287
|
+
this.error(`...${param.name.name} takes the arguments in an array: write its type as one, e.g. ...${param.name.name} []${typeToString(type)}`, param.type ?? param);
|
|
288
|
+
}
|
|
289
|
+
return ANY;
|
|
255
290
|
}
|
|
256
291
|
/** `Promise<...>` of the results of an async function, with the global `Promise` type. */
|
|
257
292
|
promiseOf(results) {
|
|
@@ -16,6 +16,7 @@ export declare abstract class ExpressionChecker extends FunctionChecker {
|
|
|
16
16
|
/** `[1, 2]`; implemented by ArrayLiteralChecker. */
|
|
17
17
|
protected abstract checkArrayLiteral(node: ast.ArrayLiteral, expected: Type | null): Type;
|
|
18
18
|
protected abstract checkTryExpression(node: ast.TryExpression): Type;
|
|
19
|
+
protected abstract checkTaggedTemplate(node: ast.TaggedTemplateExpression): Type;
|
|
19
20
|
/** `[]byte("text")`; implemented by BytesChecker. */
|
|
20
21
|
protected abstract checkConversion(node: ast.ConversionExpression): Type;
|
|
21
22
|
protected abstract checkMake(node: ast.MakeExpression): Type;
|
|
@@ -89,6 +89,11 @@ export class ExpressionChecker extends FunctionChecker {
|
|
|
89
89
|
return this.checkNew(node, expected);
|
|
90
90
|
case 'ElementExpression':
|
|
91
91
|
return this.checkElement(node);
|
|
92
|
+
// `user!`: taken not to be null, as in TypeScript.
|
|
93
|
+
case 'NonNullExpression':
|
|
94
|
+
return nonNull(this.checkExpression(node.expression, expected));
|
|
95
|
+
case 'TaggedTemplateExpression':
|
|
96
|
+
return this.checkTaggedTemplate(node);
|
|
92
97
|
case 'DecoratorMember':
|
|
93
98
|
return this.checkDecoratorMember(node);
|
|
94
99
|
case 'MemberExpression':
|
|
@@ -125,6 +130,9 @@ export class ExpressionChecker extends FunctionChecker {
|
|
|
125
130
|
this.error(initializerError, node);
|
|
126
131
|
return UNKNOWN;
|
|
127
132
|
}
|
|
133
|
+
// The field of a class is set when an instance is made, after the constants of the module.
|
|
134
|
+
if (binding.type === null && binding.early && this.cls)
|
|
135
|
+
return binding.early;
|
|
128
136
|
if (binding.type === null) {
|
|
129
137
|
this.error(`"${node.name}" is used before its declaration`, node);
|
|
130
138
|
return UNKNOWN;
|
|
@@ -154,11 +162,15 @@ export class ExpressionChecker extends FunctionChecker {
|
|
|
154
162
|
}
|
|
155
163
|
checkObjectLiteral(node, expected) {
|
|
156
164
|
let target = expected ? nonNull(expected) : null;
|
|
165
|
+
// The objects of a union that no tag chooses from: fields take what they share from them.
|
|
166
|
+
let union = null;
|
|
157
167
|
// `?(ScrollIntoViewOptions | bool)`: an object literal can only be the object type in it, or
|
|
158
168
|
// the one that its tag chooses, `{ kind: "pickup" }` of `Pickup | Courier`.
|
|
159
169
|
if (target?.kind === 'union') {
|
|
160
170
|
const objects = target.types.filter((type) => type.kind === 'object' || type.kind === 'class');
|
|
161
171
|
target = objects.length === 1 ? objects[0] : this.taggedMember(node, objects, target);
|
|
172
|
+
if (target === null)
|
|
173
|
+
union = objects;
|
|
162
174
|
}
|
|
163
175
|
const index = target?.kind === 'object' ? target.index : undefined;
|
|
164
176
|
const targetMembers = target?.kind === 'object'
|
|
@@ -202,7 +214,11 @@ export class ExpressionChecker extends FunctionChecker {
|
|
|
202
214
|
if (targetMembers && !expectedMember && target) {
|
|
203
215
|
this.error(`${typeToString(target)} has no field "${name}"`, property.key);
|
|
204
216
|
}
|
|
205
|
-
|
|
217
|
+
const context = expectedMember?.type ?? (union && this.commonField(union, name));
|
|
218
|
+
// In JS `this` of a method is the object, not the class around it: it is not used.
|
|
219
|
+
let type = property.method
|
|
220
|
+
? this.withClass(null, () => this.checkValue(property.value, context))
|
|
221
|
+
: this.checkValue(property.value, context);
|
|
206
222
|
if (expectedMember) {
|
|
207
223
|
// Reported at the field; the literal then counts as having the expected field type.
|
|
208
224
|
this.expectAssignable(type, expectedMember.type, property.value, ` for field "${name}"`);
|
package/dist/checker/flow.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export declare function withoutAssigned(flow: Flow, names: ReadonlySet<string>,
|
|
|
34
34
|
export declare function withNarrowing(flow: Flow, narrowing: Narrowing): Flow;
|
|
35
35
|
/** Narrowing that holds after both branches: the places narrowed the same way in both. */
|
|
36
36
|
export declare function mergeFlows(a: Flow, b: Flow): Flow;
|
|
37
|
-
|
|
37
|
+
export declare function markNeverEnding(node: ast.Statement): void;
|
|
38
38
|
export declare function isTerminating(node: ast.Statement): boolean;
|
|
39
39
|
/** Whether execution never continues after the statement, counting `break` and `continue`. */
|
|
40
40
|
export declare function leaves(node: ast.Statement): boolean;
|
package/dist/checker/flow.js
CHANGED
|
@@ -124,11 +124,21 @@ export function mergeFlows(a, b) {
|
|
|
124
124
|
return result;
|
|
125
125
|
}
|
|
126
126
|
/** Go's terminating statements: execution never continues after them. */
|
|
127
|
+
/**
|
|
128
|
+
* Statements that call a function whose result is `never`, `process.exit(1)`: the checker marks
|
|
129
|
+
* them, and they end a block as `throw` does.
|
|
130
|
+
*/
|
|
131
|
+
const neverEnding = new WeakSet();
|
|
132
|
+
export function markNeverEnding(node) {
|
|
133
|
+
neverEnding.add(node);
|
|
134
|
+
}
|
|
127
135
|
export function isTerminating(node) {
|
|
128
136
|
switch (node.kind) {
|
|
129
137
|
case 'ReturnStatement':
|
|
130
138
|
case 'ThrowStatement':
|
|
131
139
|
return true;
|
|
140
|
+
case 'ExpressionStatement':
|
|
141
|
+
return neverEnding.has(node);
|
|
132
142
|
case 'BlockStatement': {
|
|
133
143
|
const last = node.body.at(-1);
|
|
134
144
|
return last !== undefined && isTerminating(last);
|
|
@@ -13,6 +13,8 @@ export declare abstract class FunctionChecker extends TaggedUnionChecker {
|
|
|
13
13
|
closure?: boolean;
|
|
14
14
|
paramKind?: BindingKind;
|
|
15
15
|
async?: boolean;
|
|
16
|
+
/** A callback whose result in a `.d.ts` is `void | T`: it may end without a value. */
|
|
17
|
+
mayEnd?: boolean;
|
|
16
18
|
inspect?: (scope: Scope) => void;
|
|
17
19
|
}): Type[];
|
|
18
20
|
/** The body of `x => x * 2`. */
|
|
@@ -3,7 +3,7 @@ import { isTerminating } from "./flow.js";
|
|
|
3
3
|
import { isUntyped, callSignature } from "./helpers.js";
|
|
4
4
|
import { awaitedResults, isPromise } from "./promise.js";
|
|
5
5
|
import { TaggedUnionChecker } from "./tagged-unions.js";
|
|
6
|
-
import { commonType, containsTypeParam, func, nonNull, UNKNOWN, } from "./types.js";
|
|
6
|
+
import { ANY, arrayOf, commonType, containsTypeParam, func, isNullable, nonNull, UNKNOWN, } from "./types.js";
|
|
7
7
|
// Bodies of functions, arrow functions and func literals, and the results they return.
|
|
8
8
|
export class FunctionChecker extends TaggedUnionChecker {
|
|
9
9
|
/**
|
|
@@ -25,18 +25,28 @@ export class FunctionChecker extends TaggedUnionChecker {
|
|
|
25
25
|
isConstructor: options.isConstructor ?? false,
|
|
26
26
|
isComponent: options.isComponent ?? false,
|
|
27
27
|
isAsync: async,
|
|
28
|
+
mayEnd: options.mayEnd === true || (declared?.length === 1 && declared[0].kind === 'any'),
|
|
28
29
|
};
|
|
29
30
|
try {
|
|
30
31
|
// A generic function: its type parameters are types in its body.
|
|
31
32
|
for (const typeParam of type.typeParams)
|
|
32
33
|
this.scope.types.set(typeParam.name, typeParam);
|
|
33
|
-
params.forEach((param, i) =>
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
params.forEach((param, i) => {
|
|
35
|
+
// `...parts []any` is the array of the arguments; `times = 1` sees the parameters before it.
|
|
36
|
+
const paramType = param.rest && type.rest ? arrayOf(type.rest) : (type.params[i] ?? UNKNOWN);
|
|
37
|
+
if (param.defaultValue && !options.isComponent) {
|
|
38
|
+
this.expectAssignable(this.checkValue(param.defaultValue, paramType), paramType, param.defaultValue);
|
|
39
|
+
}
|
|
40
|
+
// A bound property of a component, `bind active string`, changes as its state does.
|
|
41
|
+
this.declareValue(param.name, param.bind ? 'state' : (options.paramKind ?? 'param'), paramType);
|
|
42
|
+
});
|
|
36
43
|
if (body.kind === 'BlockStatement') {
|
|
37
44
|
this.checkStatementList(body.body, false);
|
|
38
45
|
options.inspect?.(this.scope);
|
|
39
|
-
if (declared && declared.
|
|
46
|
+
if (declared?.length === 1 && declared[0].kind === 'never' && !isTerminating(body)) {
|
|
47
|
+
this.error('a function whose result is never must not reach its end: throw, or call one that never returns, e.g. process.exit(1)', { start: body.end - 1, end: body.end });
|
|
48
|
+
}
|
|
49
|
+
else if (declared && declared.length > 0 && !this.fn.mayEnd && !isTerminating(body)) {
|
|
40
50
|
this.error('missing return at the end of the function', {
|
|
41
51
|
start: body.end - 1,
|
|
42
52
|
end: body.end,
|
|
@@ -101,7 +111,9 @@ export class FunctionChecker extends TaggedUnionChecker {
|
|
|
101
111
|
const context = expected ? callSignature(nonNull(expected)) : null;
|
|
102
112
|
// Callbacks passed to untyped JS functions get untyped parameters.
|
|
103
113
|
const untypedContext = expected !== null && isUntyped(nonNull(expected));
|
|
104
|
-
const
|
|
114
|
+
const last = node.params.at(-1);
|
|
115
|
+
const plain = last?.rest ? node.params.slice(0, -1) : node.params;
|
|
116
|
+
const params = plain.map((param, i) => {
|
|
105
117
|
if (param.type)
|
|
106
118
|
return this.resolveType(param.type);
|
|
107
119
|
const fromContext = context ? (context.params[i] ?? context.rest) : null;
|
|
@@ -109,9 +121,19 @@ export class FunctionChecker extends TaggedUnionChecker {
|
|
|
109
121
|
return fromContext;
|
|
110
122
|
if (untypedContext)
|
|
111
123
|
return nonNull(expected);
|
|
124
|
+
if (param.defaultValue)
|
|
125
|
+
return this.inferredType(this.checkValue(param.defaultValue), param.defaultValue);
|
|
112
126
|
this.error(`cannot infer the type of parameter "${param.name.name}": add a type`, param);
|
|
113
127
|
return UNKNOWN;
|
|
114
128
|
});
|
|
129
|
+
// `(...parts) => ...` takes the rest of the context, or anything.
|
|
130
|
+
const restType = last?.rest
|
|
131
|
+
? last.type
|
|
132
|
+
? this.resolveType(last.type)
|
|
133
|
+
: arrayOf(context?.rest ?? ANY)
|
|
134
|
+
: null;
|
|
135
|
+
const rest = restType?.kind === 'array' ? restType.element : restType ? ANY : null;
|
|
136
|
+
const firstDefault = plain.findIndex((param) => param.defaultValue !== null);
|
|
115
137
|
// Known result types from the context are checked; otherwise they are inferred. Those of an
|
|
116
138
|
// async function must be a Promise.
|
|
117
139
|
const contextResults = context &&
|
|
@@ -120,7 +142,10 @@ export class FunctionChecker extends TaggedUnionChecker {
|
|
|
120
142
|
(!node.async || (context.results.length === 1 && isPromise(context.results[0])))
|
|
121
143
|
? context.results
|
|
122
144
|
: null;
|
|
123
|
-
const type = func(params, []
|
|
145
|
+
const type = func(params, [], {
|
|
146
|
+
required: firstDefault === -1 ? params.length : firstDefault,
|
|
147
|
+
...(rest ? { rest } : {}),
|
|
148
|
+
});
|
|
124
149
|
const checkBody = () => {
|
|
125
150
|
const inferring = this.inferring;
|
|
126
151
|
this.inferring = new Set();
|
|
@@ -128,6 +153,7 @@ export class FunctionChecker extends TaggedUnionChecker {
|
|
|
128
153
|
return this.checkFunction(node.params, type, contextResults, node.body, {
|
|
129
154
|
closure: true,
|
|
130
155
|
async: node.async,
|
|
156
|
+
mayEnd: contextResults?.length === 1 && isNullable(contextResults[0]),
|
|
131
157
|
});
|
|
132
158
|
}
|
|
133
159
|
finally {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
|
-
import {
|
|
2
|
+
import { TagChecker } from './tags.ts';
|
|
3
3
|
import { type Type } from './types.ts';
|
|
4
|
-
export declare abstract class GoroutineChecker extends
|
|
4
|
+
export declare abstract class GoroutineChecker extends TagChecker {
|
|
5
5
|
/** The functions already checked for what their code reaches, so each is reported once. */
|
|
6
6
|
private readonly reached;
|
|
7
7
|
protected checkGo(node: ast.GoExpression): Type;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { reach, recordCaptures } from "../goroutines.js";
|
|
2
2
|
import { assignedNames, freeNames } from "../names.js";
|
|
3
3
|
import { forEachChild } from "../walk.js";
|
|
4
|
-
import {
|
|
4
|
+
import { TagChecker } from "./tags.js";
|
|
5
5
|
import { awaited } from "./promise.js";
|
|
6
6
|
import { notCopied } from "./transfer.js";
|
|
7
7
|
import { UNKNOWN } from "./types.js";
|
|
@@ -10,7 +10,7 @@ import { UNKNOWN } from "./types.js";
|
|
|
10
10
|
// arguments and gives back a copy of the result, so both must be data that can be copied; and the
|
|
11
11
|
// code of the function must not need what a worker does not have: the variables of the module and
|
|
12
12
|
// a document.
|
|
13
|
-
export class GoroutineChecker extends
|
|
13
|
+
export class GoroutineChecker extends TagChecker {
|
|
14
14
|
/** The functions already checked for what their code reaches, so each is reported once. */
|
|
15
15
|
reached = new Set();
|
|
16
16
|
checkGo(node) {
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
2
|
import { CheckerBase } from './base.ts';
|
|
3
|
+
import type { ModuleExports } from './context.ts';
|
|
3
4
|
export declare abstract class ImportChecker extends CheckerBase {
|
|
5
|
+
/** `export { Rows } from "./rows.rk"`: what this module exports of another, by name here. */
|
|
6
|
+
protected readonly reexported: ModuleExports;
|
|
7
|
+
/** The exports of the module that an import or an `export ... from` names. */
|
|
8
|
+
private exportsOf;
|
|
9
|
+
/**
|
|
10
|
+
* `export { Row, Rows } from "./rows.rk"`: the names go into the exports of this module, with
|
|
11
|
+
* what a component or a decorator brings. Without types of the module, nothing is known of them.
|
|
12
|
+
*/
|
|
13
|
+
protected declareExportFrom(node: ast.ExportFromDeclaration): void;
|
|
4
14
|
protected declareImport(node: ast.ImportDeclaration): void;
|
|
5
15
|
/** `import { visible } from "./visible.rk"`: decorators have a namespace of their own. */
|
|
6
16
|
private importDecorator;
|