@venn-lang/core 0.1.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 +303 -0
- package/dist/index.d.ts +3654 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13137 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
- package/src/ast/call-args.ts +15 -0
- package/src/ast/dotted-path.ts +22 -0
- package/src/ast/index.ts +7 -0
- package/src/ast/is-statement.ts +40 -0
- package/src/ast/split-call.ts +28 -0
- package/src/ast/walk.ts +6 -0
- package/src/codes/build-problem.ts +24 -0
- package/src/codes/catalog.ts +35 -0
- package/src/codes/code.types.ts +7 -0
- package/src/codes/index.ts +3 -0
- package/src/compile/compile.ts +169 -0
- package/src/compile/compile.types.ts +40 -0
- package/src/compile/index.ts +3 -0
- package/src/compile/lex-scope.ts +78 -0
- package/src/compile/nodes/binary.ts +56 -0
- package/src/compile/nodes/call.ts +50 -0
- package/src/compile/nodes/collection.ts +83 -0
- package/src/compile/nodes/const-lit.ts +30 -0
- package/src/compile/nodes/fast-binary.ts +71 -0
- package/src/compile/nodes/fn.ts +87 -0
- package/src/compile/nodes/index.ts +8 -0
- package/src/compile/nodes/literal.ts +86 -0
- package/src/compile/nodes/map-fields.ts +79 -0
- package/src/compile/nodes/member.ts +32 -0
- package/src/events/envelope.types.ts +17 -0
- package/src/events/event-data.types.ts +25 -0
- package/src/events/ids.types.ts +10 -0
- package/src/events/index.ts +5 -0
- package/src/events/run-plan.types.ts +18 -0
- package/src/events/status.types.ts +2 -0
- package/src/expand/accepted-kinds.ts +23 -0
- package/src/expand/deco/deco-decorator.ts +77 -0
- package/src/expand/deco/deco-env.ts +24 -0
- package/src/expand/deco/deco.types.ts +43 -0
- package/src/expand/deco/document-decos.ts +64 -0
- package/src/expand/deco/index.ts +7 -0
- package/src/expand/deco/read-signature.ts +57 -0
- package/src/expand/deco/run-body.ts +119 -0
- package/src/expand/decorate-callable.ts +49 -0
- package/src/expand/decorations.ts +41 -0
- package/src/expand/expand.ts +108 -0
- package/src/expand/expand.types.ts +78 -0
- package/src/expand/handles/around-verbs.ts +35 -0
- package/src/expand/handles/binding-verbs.ts +27 -0
- package/src/expand/handles/common-verbs.ts +40 -0
- package/src/expand/handles/fn-verbs.ts +58 -0
- package/src/expand/handles/handle.types.ts +33 -0
- package/src/expand/handles/index.ts +4 -0
- package/src/expand/handles/kind-of.ts +36 -0
- package/src/expand/handles/make-handle.ts +100 -0
- package/src/expand/handles/missing-verb.ts +27 -0
- package/src/expand/handles/runnable-verbs.ts +13 -0
- package/src/expand/handles/type-verbs.ts +56 -0
- package/src/expand/index.ts +40 -0
- package/src/expand/make-context.ts +69 -0
- package/src/expand/node-meta.ts +30 -0
- package/src/expand/node-span.ts +17 -0
- package/src/expand/swap-node.ts +28 -0
- package/src/expand/wrong-kind.ts +102 -0
- package/src/expr/cell.types.ts +25 -0
- package/src/expr/closure.ts +53 -0
- package/src/expr/closure.types.ts +31 -0
- package/src/expr/eval-env.types.ts +9 -0
- package/src/expr/evaluate.ts +18 -0
- package/src/expr/frame.ts +60 -0
- package/src/expr/index.ts +14 -0
- package/src/expr/invoke.ts +140 -0
- package/src/expr/member-value.ts +91 -0
- package/src/expr/methods/index.ts +65 -0
- package/src/expr/methods/list-grouping.ts +107 -0
- package/src/expr/methods/list-methods.ts +57 -0
- package/src/expr/methods/list-selection.ts +142 -0
- package/src/expr/methods/map-extras.ts +87 -0
- package/src/expr/methods/map-methods.ts +17 -0
- package/src/expr/methods/number-methods.ts +29 -0
- package/src/expr/methods/string-extras.ts +66 -0
- package/src/expr/methods/string-methods.ts +32 -0
- package/src/expr/methods/unit-methods.ts +54 -0
- package/src/expr/namespace.ts +20 -0
- package/src/expr/native.types.ts +40 -0
- package/src/expr/operators.ts +91 -0
- package/src/expr/pending.ts +47 -0
- package/src/expr/prelude.ts +75 -0
- package/src/expr/task.ts +71 -0
- package/src/format/format-text.ts +22 -0
- package/src/format/format.types.ts +18 -0
- package/src/format/from-settings.ts +40 -0
- package/src/format/index.ts +5 -0
- package/src/format/organize-header.ts +66 -0
- package/src/format/reindent.ts +35 -0
- package/src/generated/ast.ts +2383 -0
- package/src/generated/grammar.ts +5236 -0
- package/src/generated/module.ts +25 -0
- package/src/grammar/venn.langium +291 -0
- package/src/graph/graph.types.ts +20 -0
- package/src/graph/index.ts +2 -0
- package/src/graph/to-graph.ts +87 -0
- package/src/index.ts +93 -0
- package/src/interpolation/compile-template.ts +40 -0
- package/src/interpolation/index.ts +4 -0
- package/src/interpolation/interpolation.types.ts +18 -0
- package/src/interpolation/scan-interpolations.ts +47 -0
- package/src/interpolation/template.types.ts +19 -0
- package/src/lang/default-services.ts +14 -0
- package/src/lang/index.ts +3 -0
- package/src/lang/venn-lexer.ts +34 -0
- package/src/lang/venn-module.ts +51 -0
- package/src/module/index.ts +5 -0
- package/src/module/specifier.ts +21 -0
- package/src/parse/error-to-problem.ts +43 -0
- package/src/parse/index.ts +3 -0
- package/src/parse/parse-expression.ts +27 -0
- package/src/parse/parse-output.types.ts +8 -0
- package/src/parse/parse.ts +26 -0
- package/src/problem/build-diff.ts +116 -0
- package/src/problem/diff.types.ts +20 -0
- package/src/problem/format-value.ts +40 -0
- package/src/problem/index.ts +8 -0
- package/src/problem/problem-error.ts +15 -0
- package/src/problem/problem.types.ts +21 -0
- package/src/problem/related.types.ts +7 -0
- package/src/problem/severity.types.ts +2 -0
- package/src/problem/span.types.ts +8 -0
- package/src/typecheck/action-signature.ts +53 -0
- package/src/typecheck/builtin-types.ts +63 -0
- package/src/typecheck/builtins.ts +267 -0
- package/src/typecheck/catalog.types.ts +16 -0
- package/src/typecheck/check-deco.ts +174 -0
- package/src/typecheck/check-stmts.ts +151 -0
- package/src/typecheck/check-types.ts +232 -0
- package/src/typecheck/context.ts +31 -0
- package/src/typecheck/imported-types.ts +127 -0
- package/src/typecheck/index.ts +26 -0
- package/src/typecheck/infer.ts +416 -0
- package/src/typecheck/kind-types.ts +79 -0
- package/src/typecheck/member-docs.ts +211 -0
- package/src/typecheck/named-types.ts +57 -0
- package/src/typecheck/prelude-types.ts +152 -0
- package/src/typecheck/reshaped-fns.ts +58 -0
- package/src/typecheck/scheme.ts +75 -0
- package/src/typecheck/seed-params.ts +72 -0
- package/src/typecheck/seed-values.ts +64 -0
- package/src/typecheck/show.ts +64 -0
- package/src/typecheck/solidify.ts +60 -0
- package/src/typecheck/spec-to-type.ts +88 -0
- package/src/typecheck/type-env.ts +46 -0
- package/src/typecheck/type-ref.ts +62 -0
- package/src/typecheck/type.types.ts +168 -0
- package/src/typecheck/unify.ts +192 -0
- package/src/typecheck/unit-types.ts +49 -0
- package/src/units/combine.ts +97 -0
- package/src/units/index.ts +6 -0
- package/src/units/parse-instant.ts +12 -0
- package/src/units/parse-number.ts +48 -0
- package/src/units/unit-guards.ts +16 -0
- package/src/units/unit.types.ts +38 -0
- package/src/value/equals.ts +4 -0
- package/src/value/index.ts +4 -0
- package/src/value/is-numeric.ts +6 -0
- package/src/value/truthiness.ts +9 -0
- package/src/value/value.types.ts +12 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import type { AstNode } from "langium";
|
|
2
|
+
import { buildProblem, CODES } from "../codes/index.js";
|
|
3
|
+
import type { ImportedDeco } from "../expand/index.js";
|
|
4
|
+
import type { Declaration, Document, Expr, FnDecl } from "../generated/ast.js";
|
|
5
|
+
import * as ast from "../generated/ast.js";
|
|
6
|
+
import type { Problem, Span } from "../problem/index.js";
|
|
7
|
+
import type { TypeCatalog } from "./catalog.types.js";
|
|
8
|
+
import { checkDecoBody, checkDecos, decosInReach } from "./check-deco.js";
|
|
9
|
+
import { checkBlock, checkFragment, checkStatement } from "./check-stmts.js";
|
|
10
|
+
import { createContext, type TypeContext, type TypeMismatch } from "./context.js";
|
|
11
|
+
import { type Infer, inferFn, type Slot } from "./infer.js";
|
|
12
|
+
import { collectNamedTypes } from "./named-types.js";
|
|
13
|
+
import { PRELUDE_SPECS } from "./prelude-types.js";
|
|
14
|
+
import { reshapedFns } from "./reshaped-fns.js";
|
|
15
|
+
import { generalize, mono } from "./scheme.js";
|
|
16
|
+
import { seedParams } from "./seed-params.js";
|
|
17
|
+
import { seedValues } from "./seed-values.js";
|
|
18
|
+
import { showType } from "./show.js";
|
|
19
|
+
import { DYNAMIC, type Type } from "./type.types.js";
|
|
20
|
+
import { emptyEnv, type TypeEnv } from "./type-env.js";
|
|
21
|
+
import { prune, unify } from "./unify.js";
|
|
22
|
+
|
|
23
|
+
/** What one check of a document produced. */
|
|
24
|
+
export interface CheckTypesResult {
|
|
25
|
+
problems: Problem[];
|
|
26
|
+
/** Every expression's inferred type, keyed by node, for hover. */
|
|
27
|
+
types: Map<object, Type>;
|
|
28
|
+
/** Per string literal, the expression parsed from each of its ${…} slots. */
|
|
29
|
+
slots: Map<object, (Expr | undefined)[]>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** What the checker may be told about the world outside the file. */
|
|
33
|
+
export interface CheckTypesOptions {
|
|
34
|
+
uri?: string;
|
|
35
|
+
/** The types and signatures the loaded plugins contribute. */
|
|
36
|
+
catalog?: TypeCatalog;
|
|
37
|
+
/** The `pub deco`s this file's imports reach. Without them an imported
|
|
38
|
+
* `@name` is a name the checker knows nothing about, and says nothing about. */
|
|
39
|
+
decos?: ReadonlyMap<string, ImportedDeco>;
|
|
40
|
+
/** What the names this file imports turned out to be, from the files it names. */
|
|
41
|
+
imports?: ReadonlyMap<string, Type>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Infer and check the types across a document.
|
|
46
|
+
*
|
|
47
|
+
* Errors are raised only where a type is actually known. Anything touching
|
|
48
|
+
* `dynamic`, such as a plugin that published no signature or an HTTP response,
|
|
49
|
+
* is left alone, so the checker helps without ever blocking.
|
|
50
|
+
*
|
|
51
|
+
* @returns the problems found, plus the inferred type of every expression and
|
|
52
|
+
* the expressions parsed out of each `${…}`, both of which the editor reads.
|
|
53
|
+
*/
|
|
54
|
+
export function checkTypes(document: Document, options: CheckTypesOptions = {}): CheckTypesResult {
|
|
55
|
+
const uri = options.uri ?? "memory://inline.vn";
|
|
56
|
+
const ctx = createContext();
|
|
57
|
+
// Shared with the seeding pass: parsing every `${…}` twice is the one part of
|
|
58
|
+
// a second walk that would actually cost something.
|
|
59
|
+
const parsed = new Map<AstNode, Slot[]>();
|
|
60
|
+
const decos = decosInReach({ document, imported: options.decos });
|
|
61
|
+
const shared = { document, catalog: options.catalog, decos, parsed, run: pass };
|
|
62
|
+
const values = seedValues(shared);
|
|
63
|
+
const seeds = seedParams(shared);
|
|
64
|
+
const infer: Infer = {
|
|
65
|
+
ctx,
|
|
66
|
+
named: collectNamedTypes(document, ctx, options.catalog),
|
|
67
|
+
catalog: options.catalog,
|
|
68
|
+
decos,
|
|
69
|
+
seeds,
|
|
70
|
+
values,
|
|
71
|
+
parsed,
|
|
72
|
+
imports: options.imports,
|
|
73
|
+
types: new Map(),
|
|
74
|
+
slots: new Map(),
|
|
75
|
+
};
|
|
76
|
+
pass(document, infer);
|
|
77
|
+
// After the pass, so a decorator's arguments are read once, and before the
|
|
78
|
+
// mismatches are turned into problems, since that is where they land.
|
|
79
|
+
const deco = checkDecos({ document, infer, uri });
|
|
80
|
+
return {
|
|
81
|
+
problems: [...ctx.mismatches.map((m) => problem(m, uri)), ...deco],
|
|
82
|
+
types: infer.types ?? new Map(),
|
|
83
|
+
slots: infer.slots ?? new Map(),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** One walk of the whole document: top-level bindings, then everything that runs. */
|
|
88
|
+
function pass(document: Document, infer: Infer): void {
|
|
89
|
+
walk(document, topLevelEnv(document, infer), infer);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Hoist functions and bind top-level values, so the whole file sees them.
|
|
94
|
+
*
|
|
95
|
+
* Generalising is what makes a helper reusable at more than one type, and also
|
|
96
|
+
* what stops a call site from ever reaching the declaration, since each use gets
|
|
97
|
+
* its own copy. The seeding pass therefore skips it: that pass exists precisely
|
|
98
|
+
* to let the callers speak.
|
|
99
|
+
*/
|
|
100
|
+
function topLevelEnv(document: Document, infer: Infer): TypeEnv {
|
|
101
|
+
const fns = document.decls.filter(ast.isFnDecl);
|
|
102
|
+
// A decorator may hand this function a shape nobody here can read; its body is
|
|
103
|
+
// still checked, but its signature is not the one callers will meet.
|
|
104
|
+
const reshaped = reshapedFns({ document, decos: infer.decos ?? new Map() });
|
|
105
|
+
const outer = withImports(preludeEnv(), infer);
|
|
106
|
+
let env = withSeededValues(hoist({ fns, reshaped, ctx: infer.ctx, env: outer }), infer);
|
|
107
|
+
for (const decl of fns) {
|
|
108
|
+
const inferred = inferFn(decl, env, infer);
|
|
109
|
+
if (!reshaped.has(decl)) unify(env.lookup(decl.name)?.type ?? placeholder(infer), inferred);
|
|
110
|
+
}
|
|
111
|
+
if (!infer.seeding) env = generalizeFns(fns, env, infer);
|
|
112
|
+
return bindValues(document, env, infer);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* What the file's values hold, put in reach of the function bodies about to be
|
|
117
|
+
* checked. The real binding still happens below, in source order and with the
|
|
118
|
+
* annotations read; this only stops a body from meeting a name it cannot know.
|
|
119
|
+
*/
|
|
120
|
+
function withSeededValues(env: TypeEnv, infer: Infer): TypeEnv {
|
|
121
|
+
let next = env;
|
|
122
|
+
for (const [name, type] of infer.values ?? []) next = next.with(name, mono(type));
|
|
123
|
+
return next;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The names this file imported, each with the type its own module gave it.
|
|
128
|
+
*
|
|
129
|
+
* Generalised over everything free in them, for two reasons: a generic helper
|
|
130
|
+
* stays generic across the file boundary, and the variables belonging to the
|
|
131
|
+
* pass that inferred them are replaced with fresh ones here rather than being
|
|
132
|
+
* unified with anything in this file.
|
|
133
|
+
*/
|
|
134
|
+
function withImports(env: TypeEnv, infer: Infer): TypeEnv {
|
|
135
|
+
let next = env;
|
|
136
|
+
for (const [name, type] of infer.imports ?? []) next = next.with(name, generalize(type, EMPTY));
|
|
137
|
+
return next;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const EMPTY: ReadonlySet<number> = new Set();
|
|
141
|
+
|
|
142
|
+
/** The prelude is in scope everywhere, so inference knows what its verbs return. */
|
|
143
|
+
function preludeEnv(): TypeEnv {
|
|
144
|
+
let env = emptyEnv();
|
|
145
|
+
for (const [name, spec] of Object.entries(PRELUDE_SPECS)) env = env.with(name, mono(spec.type));
|
|
146
|
+
return env;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function hoist(args: {
|
|
150
|
+
fns: readonly FnDecl[];
|
|
151
|
+
reshaped: ReadonlySet<FnDecl>;
|
|
152
|
+
ctx: TypeContext;
|
|
153
|
+
env: TypeEnv;
|
|
154
|
+
}): TypeEnv {
|
|
155
|
+
let next = args.env;
|
|
156
|
+
for (const decl of args.fns) {
|
|
157
|
+
next = next.with(decl.name, mono(args.reshaped.has(decl) ? DYNAMIC : args.ctx.fresh()));
|
|
158
|
+
}
|
|
159
|
+
return next;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function generalizeFns(fns: readonly FnDecl[], env: TypeEnv, infer: Infer): TypeEnv {
|
|
163
|
+
let next = env;
|
|
164
|
+
for (const decl of fns) {
|
|
165
|
+
const type = env.lookup(decl.name)?.type;
|
|
166
|
+
if (!type) continue;
|
|
167
|
+
// The declaration carries its own type, so hovering the name shows it.
|
|
168
|
+
infer.types?.set(decl, prune(type));
|
|
169
|
+
next = next.with(decl.name, generalize(prune(type), new Set()));
|
|
170
|
+
}
|
|
171
|
+
return next;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function bindValues(document: Document, env: TypeEnv, infer: Infer): TypeEnv {
|
|
175
|
+
let next = env;
|
|
176
|
+
for (const decl of document.decls) {
|
|
177
|
+
if (ast.isLetStmt(decl)) next = checkStatement(decl, next, infer);
|
|
178
|
+
}
|
|
179
|
+
return next;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Check the executable parts: flows, fragments, and top-level statements. */
|
|
183
|
+
function walk(document: Document, env: TypeEnv, infer: Infer): void {
|
|
184
|
+
for (const decl of document.decls) checkDeclaration(decl, env, infer);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function checkDeclaration(decl: Declaration, env: TypeEnv, infer: Infer): void {
|
|
188
|
+
if (ast.isFlowDecl(decl)) checkBlock(decl.body, env, infer);
|
|
189
|
+
else if (ast.isFragmentDecl(decl)) checkFragment(decl, env, infer);
|
|
190
|
+
else if (ast.isDecoDecl(decl)) checkDecoBody(decl, env, infer);
|
|
191
|
+
else if (!ast.isFnDecl(decl) && !ast.isLetStmt(decl) && isExecutable(decl)) {
|
|
192
|
+
checkStatement(decl as never, env, infer);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function isExecutable(decl: Declaration): boolean {
|
|
197
|
+
return !(ast.isTypeDecl(decl) || ast.isConfigDecl(decl) || ast.isMatrixDecl(decl));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function placeholder(infer: Infer): Type {
|
|
201
|
+
return infer.ctx.fresh();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function problem(mismatch: TypeMismatch, uri: string): Problem {
|
|
205
|
+
const title = titleOf(mismatch);
|
|
206
|
+
return buildProblem({
|
|
207
|
+
spec: mismatch.unit ? CODES.VN3012_UNIT_MISMATCH : CODES.VN3010_TYPE_MISMATCH,
|
|
208
|
+
span: spanOf(mismatch.node, uri),
|
|
209
|
+
title,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** A unit clash already reads as a sentence; anything else is a type mismatch. */
|
|
214
|
+
function titleOf(mismatch: TypeMismatch): string {
|
|
215
|
+
if (mismatch.unit) return mismatch.note ?? "These values cannot be combined.";
|
|
216
|
+
if (mismatch.note) return `Type ${showType(mismatch.expected)} ${mismatch.note}.`;
|
|
217
|
+
return `Type mismatch: expected ${showType(mismatch.expected)}, found ${showType(mismatch.actual)}.`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function spanOf(node: { $cstNode?: unknown }, uri: string): Span {
|
|
221
|
+
const cst = node.$cstNode as
|
|
222
|
+
| { offset: number; length: number; range?: { start: { line: number; character: number } } }
|
|
223
|
+
| undefined;
|
|
224
|
+
const start = cst?.range?.start;
|
|
225
|
+
return {
|
|
226
|
+
uri,
|
|
227
|
+
offset: cst?.offset ?? 0,
|
|
228
|
+
length: cst?.length ?? 0,
|
|
229
|
+
line: (start?.line ?? 0) + 1,
|
|
230
|
+
column: (start?.character ?? 0) + 1,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { AstNode } from "langium";
|
|
2
|
+
import type { Type, TypeVar } from "./type.types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Per-run inference state: a fresh-variable source and the list of type
|
|
6
|
+
* mismatches found. Held in a context (not a module global) so two checks never
|
|
7
|
+
* share variable ids or leak diagnostics into each other.
|
|
8
|
+
*/
|
|
9
|
+
export interface TypeContext {
|
|
10
|
+
fresh(): TypeVar;
|
|
11
|
+
mismatches: TypeMismatch[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** One place where two types could not be made equal, at a source node. */
|
|
15
|
+
export interface TypeMismatch {
|
|
16
|
+
node: AstNode;
|
|
17
|
+
expected: Type;
|
|
18
|
+
actual: Type;
|
|
19
|
+
note?: string;
|
|
20
|
+
/** A unit clash rather than a plain type clash: VN3012 instead of VN3010. */
|
|
21
|
+
unit?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** A fresh inference context: variable ids from zero, no mismatches recorded. */
|
|
25
|
+
export function createContext(): TypeContext {
|
|
26
|
+
let next = 0;
|
|
27
|
+
return {
|
|
28
|
+
fresh: () => ({ kind: "var", id: next++, ref: undefined }),
|
|
29
|
+
mismatches: [],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { TypeSpec } from "@venn-lang/types";
|
|
2
|
+
import type { Document } from "../generated/ast.js";
|
|
3
|
+
import * as ast from "../generated/ast.js";
|
|
4
|
+
import { isPackageSpecifier } from "../module/index.js";
|
|
5
|
+
import type { TypeCatalog } from "./catalog.types.js";
|
|
6
|
+
import { checkTypes } from "./check-types.js";
|
|
7
|
+
import { type ResolveRef, specToType } from "./spec-to-type.js";
|
|
8
|
+
import { record, type Type } from "./type.types.js";
|
|
9
|
+
|
|
10
|
+
/** What the names a file imported turned out to be, ready to bind in its env. */
|
|
11
|
+
export type ImportedTypes = ReadonlyMap<string, Type>;
|
|
12
|
+
|
|
13
|
+
export interface ImportedTypesArgs {
|
|
14
|
+
document: Document;
|
|
15
|
+
uri: string;
|
|
16
|
+
/** Every module the import graph reached, already parsed, by resolved URI. */
|
|
17
|
+
modules: ReadonlyMap<string, Document>;
|
|
18
|
+
/** How a specifier written in one file names another. */
|
|
19
|
+
resolve: (from: string, spec: string) => string;
|
|
20
|
+
catalog?: TypeCatalog;
|
|
21
|
+
/**
|
|
22
|
+
* What each installed package publishes, derived from its `.d.ts`.
|
|
23
|
+
*
|
|
24
|
+
* Keyed by the specifier as written, because that is what the import says.
|
|
25
|
+
* Absent for a host that has not derived any, which is every run in an editor
|
|
26
|
+
* that has not installed anything yet.
|
|
27
|
+
*/
|
|
28
|
+
packages?: ReadonlyMap<string, Record<string, TypeSpec>>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The types of the names a document imports, worked out from the files it names.
|
|
33
|
+
*
|
|
34
|
+
* Each module is checked on its own, with *its* imports resolved first, so a
|
|
35
|
+
* `pub fn` that calls another file has the signature it really has. That is what
|
|
36
|
+
* lets `triplo("texto")` be refused against a `fn(number) -> number` declared
|
|
37
|
+
* one file away.
|
|
38
|
+
*
|
|
39
|
+
* @returns a type per imported name. A name whose module publishes nothing for
|
|
40
|
+
* it is simply absent, and the checker treats it as `dynamic`.
|
|
41
|
+
*/
|
|
42
|
+
export function importedTypes(args: ImportedTypesArgs): ImportedTypes {
|
|
43
|
+
const state: State = {
|
|
44
|
+
modules: args.modules,
|
|
45
|
+
resolve: args.resolve,
|
|
46
|
+
catalog: args.catalog,
|
|
47
|
+
packages: args.packages,
|
|
48
|
+
done: new Map(),
|
|
49
|
+
busy: new Set([args.uri]),
|
|
50
|
+
};
|
|
51
|
+
return bindingsOf({ document: args.document, uri: args.uri, state });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface State {
|
|
55
|
+
modules: ReadonlyMap<string, Document>;
|
|
56
|
+
resolve: (from: string, spec: string) => string;
|
|
57
|
+
catalog?: TypeCatalog;
|
|
58
|
+
packages?: ReadonlyMap<string, Record<string, TypeSpec>>;
|
|
59
|
+
done: Map<string, Map<string, Type>>;
|
|
60
|
+
busy: Set<string>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** What each name a document imports binds to, following `import { … }` and `* as`. */
|
|
64
|
+
function bindingsOf(args: { document: Document; uri: string; state: State }): Map<string, Type> {
|
|
65
|
+
const out = new Map<string, Type>();
|
|
66
|
+
for (const decl of args.document.imports) {
|
|
67
|
+
if (!ast.isValueImport(decl)) continue;
|
|
68
|
+
const published = isPackageSpecifier(decl.path)
|
|
69
|
+
? packageTypes(decl.path, args.state)
|
|
70
|
+
: publishedBy(args.state.resolve(args.uri, decl.path), args.state);
|
|
71
|
+
if (decl.wildcard) out.set(decl.wildcard, record(published));
|
|
72
|
+
else for (const name of decl.names) take(out, published, name);
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function take(out: Map<string, Type>, published: ReadonlyMap<string, Type>, name: string): void {
|
|
78
|
+
const found = published.get(name);
|
|
79
|
+
if (found) out.set(name, found);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* What an installed package publishes, as this language's types.
|
|
84
|
+
*
|
|
85
|
+
* Derived elsewhere, by whoever can read a `.d.ts`, and handed over already
|
|
86
|
+
* converted, so the checker stays a checker and never learns what npm is.
|
|
87
|
+
*/
|
|
88
|
+
function packageTypes(spec: string, state: State): ReadonlyMap<string, Type> {
|
|
89
|
+
const found = state.packages?.get(spec);
|
|
90
|
+
if (!found) return new Map();
|
|
91
|
+
// A package's types name nothing of this language's, so a reference in one is
|
|
92
|
+
// a name from a `.d.ts` that did not survive the conversion: `dynamic`.
|
|
93
|
+
const unknown: ResolveRef = () => undefined;
|
|
94
|
+
return new Map(Object.entries(found).map(([name, one]) => [name, specToType(one, unknown)]));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The types a module publishes, checked once.
|
|
99
|
+
*
|
|
100
|
+
* A module already being checked answers with nothing rather than looping: two
|
|
101
|
+
* files that call each other cannot both be inferred from the other, and the
|
|
102
|
+
* honest answer for the second is that its signature is not yet known. Whatever
|
|
103
|
+
* it declared outright is still read on its own next time round.
|
|
104
|
+
*/
|
|
105
|
+
function publishedBy(uri: string, state: State): ReadonlyMap<string, Type> {
|
|
106
|
+
const cached = state.done.get(uri);
|
|
107
|
+
if (cached) return cached;
|
|
108
|
+
const module = state.modules.get(uri);
|
|
109
|
+
if (!module || state.busy.has(uri)) return new Map();
|
|
110
|
+
state.busy.add(uri);
|
|
111
|
+
const out = exportedTypes(module, uri, state);
|
|
112
|
+
state.busy.delete(uri);
|
|
113
|
+
state.done.set(uri, out);
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function exportedTypes(module: Document, uri: string, state: State): Map<string, Type> {
|
|
118
|
+
const imports = bindingsOf({ document: module, uri, state });
|
|
119
|
+
const checked = checkTypes(module, { uri, catalog: state.catalog, imports });
|
|
120
|
+
const out = new Map<string, Type>();
|
|
121
|
+
for (const decl of module.decls) {
|
|
122
|
+
if (!ast.isFnDecl(decl) || !decl.export) continue;
|
|
123
|
+
const found = checked.types.get(decl);
|
|
124
|
+
if (found) out.set(decl.name, found);
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type { BuiltinType } from "./builtin-types.js";
|
|
2
|
+
export { BUILTIN_TYPES, isBuiltinType } from "./builtin-types.js";
|
|
3
|
+
export { memberType, resolveMember } from "./builtins.js";
|
|
4
|
+
export type { TypeCatalog } from "./catalog.types.js";
|
|
5
|
+
export type { CheckTypesOptions, CheckTypesResult } from "./check-types.js";
|
|
6
|
+
export { checkTypes } from "./check-types.js";
|
|
7
|
+
export { createContext } from "./context.js";
|
|
8
|
+
export { type ImportedTypes, importedTypes } from "./imported-types.js";
|
|
9
|
+
export { KIND_SPECS, KIND_TYPES } from "./kind-types.js";
|
|
10
|
+
export type { MemberDoc } from "./member-docs.js";
|
|
11
|
+
export { MEMBER_DOCS, memberKind } from "./member-docs.js";
|
|
12
|
+
export type { PreludeArg, PreludeSpec } from "./prelude-types.js";
|
|
13
|
+
export { isPrelude, PRELUDE_SPECS } from "./prelude-types.js";
|
|
14
|
+
export { showType, showTypes } from "./show.js";
|
|
15
|
+
export type { ResolveRef } from "./spec-to-type.js";
|
|
16
|
+
export { specToType } from "./spec-to-type.js";
|
|
17
|
+
export type {
|
|
18
|
+
FnType,
|
|
19
|
+
LiteralType,
|
|
20
|
+
OpaqueType,
|
|
21
|
+
RecordType,
|
|
22
|
+
Type,
|
|
23
|
+
UnionType,
|
|
24
|
+
} from "./type.types.js";
|
|
25
|
+
export { DYNAMIC, literal, opaque, union } from "./type.types.js";
|
|
26
|
+
export { prune } from "./unify.js";
|