edict-lang 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 +202 -0
- package/dist/ast/nodes.d.ts +248 -0
- package/dist/ast/nodes.d.ts.map +1 -0
- package/dist/ast/nodes.js +95 -0
- package/dist/ast/nodes.js.map +1 -0
- package/dist/ast/types.d.ts +78 -0
- package/dist/ast/types.d.ts.map +1 -0
- package/dist/ast/types.js +7 -0
- package/dist/ast/types.js.map +1 -0
- package/dist/check.d.ts +19 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +49 -0
- package/dist/check.js.map +1 -0
- package/dist/checker/check.d.ts +7 -0
- package/dist/checker/check.d.ts.map +1 -0
- package/dist/checker/check.js +548 -0
- package/dist/checker/check.js.map +1 -0
- package/dist/checker/type-env.d.ts +24 -0
- package/dist/checker/type-env.d.ts.map +1 -0
- package/dist/checker/type-env.js +67 -0
- package/dist/checker/type-env.js.map +1 -0
- package/dist/checker/types-equal.d.ts +18 -0
- package/dist/checker/types-equal.d.ts.map +1 -0
- package/dist/checker/types-equal.js +79 -0
- package/dist/checker/types-equal.js.map +1 -0
- package/dist/codegen/builtins.d.ts +27 -0
- package/dist/codegen/builtins.d.ts.map +1 -0
- package/dist/codegen/builtins.js +54 -0
- package/dist/codegen/builtins.js.map +1 -0
- package/dist/codegen/codegen.d.ts +32 -0
- package/dist/codegen/codegen.d.ts.map +1 -0
- package/dist/codegen/codegen.js +1192 -0
- package/dist/codegen/codegen.js.map +1 -0
- package/dist/codegen/runner.d.ts +16 -0
- package/dist/codegen/runner.d.ts.map +1 -0
- package/dist/codegen/runner.js +82 -0
- package/dist/codegen/runner.js.map +1 -0
- package/dist/codegen/string-table.d.ts +35 -0
- package/dist/codegen/string-table.d.ts.map +1 -0
- package/dist/codegen/string-table.js +62 -0
- package/dist/codegen/string-table.js.map +1 -0
- package/dist/compile.d.ts +18 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +40 -0
- package/dist/compile.js.map +1 -0
- package/dist/contracts/translate.d.ts +39 -0
- package/dist/contracts/translate.d.ts.map +1 -0
- package/dist/contracts/translate.js +372 -0
- package/dist/contracts/translate.js.map +1 -0
- package/dist/contracts/verify.d.ts +8 -0
- package/dist/contracts/verify.d.ts.map +1 -0
- package/dist/contracts/verify.js +400 -0
- package/dist/contracts/verify.js.map +1 -0
- package/dist/contracts/z3-context.d.ts +14 -0
- package/dist/contracts/z3-context.d.ts.map +1 -0
- package/dist/contracts/z3-context.js +24 -0
- package/dist/contracts/z3-context.js.map +1 -0
- package/dist/effects/call-graph.d.ts +21 -0
- package/dist/effects/call-graph.d.ts.map +1 -0
- package/dist/effects/call-graph.js +135 -0
- package/dist/effects/call-graph.js.map +1 -0
- package/dist/effects/effect-check.d.ts +13 -0
- package/dist/effects/effect-check.d.ts.map +1 -0
- package/dist/effects/effect-check.js +48 -0
- package/dist/effects/effect-check.js.map +1 -0
- package/dist/errors/structured-errors.d.ts +193 -0
- package/dist/errors/structured-errors.d.ts.map +1 -0
- package/dist/errors/structured-errors.js +96 -0
- package/dist/errors/structured-errors.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/create-server.d.ts +3 -0
- package/dist/mcp/create-server.d.ts.map +1 -0
- package/dist/mcp/create-server.js +157 -0
- package/dist/mcp/create-server.js.map +1 -0
- package/dist/mcp/handlers.d.ts +44 -0
- package/dist/mcp/handlers.d.ts.map +1 -0
- package/dist/mcp/handlers.js +112 -0
- package/dist/mcp/handlers.js.map +1 -0
- package/dist/mcp/server.d.ts +3 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +129 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/resolver/levenshtein.d.ts +12 -0
- package/dist/resolver/levenshtein.d.ts.map +1 -0
- package/dist/resolver/levenshtein.js +54 -0
- package/dist/resolver/levenshtein.js.map +1 -0
- package/dist/resolver/resolve.d.ts +7 -0
- package/dist/resolver/resolve.d.ts.map +1 -0
- package/dist/resolver/resolve.js +393 -0
- package/dist/resolver/resolve.js.map +1 -0
- package/dist/resolver/scope.d.ts +34 -0
- package/dist/resolver/scope.d.ts.map +1 -0
- package/dist/resolver/scope.js +51 -0
- package/dist/resolver/scope.js.map +1 -0
- package/dist/validator/id-tracker.d.ts +14 -0
- package/dist/validator/id-tracker.d.ts.map +1 -0
- package/dist/validator/id-tracker.js +28 -0
- package/dist/validator/id-tracker.js.map +1 -0
- package/dist/validator/node-validators.d.ts +6 -0
- package/dist/validator/node-validators.d.ts.map +1 -0
- package/dist/validator/node-validators.js +808 -0
- package/dist/validator/node-validators.js.map +1 -0
- package/dist/validator/validate.d.ts +17 -0
- package/dist/validator/validate.d.ts.map +1 -0
- package/dist/validator/validate.js +27 -0
- package/dist/validator/validate.js.map +1 -0
- package/package.json +75 -0
package/dist/check.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { StructuredError } from "./errors/structured-errors.js";
|
|
2
|
+
import type { EdictModule } from "./ast/nodes.js";
|
|
3
|
+
export interface CheckResult {
|
|
4
|
+
ok: boolean;
|
|
5
|
+
errors: StructuredError[];
|
|
6
|
+
/** The validated module AST (only present when ok === true) */
|
|
7
|
+
module?: EdictModule;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Full pipeline: validate → resolve → typeCheck → effectCheck → contractVerify.
|
|
11
|
+
*
|
|
12
|
+
* If validation fails, returns validation errors (later phases skipped).
|
|
13
|
+
* If resolution fails, returns resolution errors (later phases skipped).
|
|
14
|
+
* If type checking fails, returns type errors (effect checking skipped).
|
|
15
|
+
* If effect checking fails, returns effect errors (contract verification skipped).
|
|
16
|
+
* If all passes succeed, returns `{ ok: true, errors: [] }`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function check(ast: unknown): Promise<CheckResult>;
|
|
19
|
+
//# sourceMappingURL=check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAMrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,+DAA+D;IAC/D,MAAM,CAAC,EAAE,WAAW,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC,CAiC9D"}
|
package/dist/check.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Edict Pipeline — check(ast) → Promise<CheckResult>
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Runs the full pipeline: validate → resolve → typeCheck → effectCheck → contractVerify
|
|
5
|
+
// Stops early if an earlier phase fails.
|
|
6
|
+
import { validate } from "./validator/validate.js";
|
|
7
|
+
import { resolve } from "./resolver/resolve.js";
|
|
8
|
+
import { typeCheck } from "./checker/check.js";
|
|
9
|
+
import { effectCheck } from "./effects/effect-check.js";
|
|
10
|
+
import { contractVerify } from "./contracts/verify.js";
|
|
11
|
+
/**
|
|
12
|
+
* Full pipeline: validate → resolve → typeCheck → effectCheck → contractVerify.
|
|
13
|
+
*
|
|
14
|
+
* If validation fails, returns validation errors (later phases skipped).
|
|
15
|
+
* If resolution fails, returns resolution errors (later phases skipped).
|
|
16
|
+
* If type checking fails, returns type errors (effect checking skipped).
|
|
17
|
+
* If effect checking fails, returns effect errors (contract verification skipped).
|
|
18
|
+
* If all passes succeed, returns `{ ok: true, errors: [] }`.
|
|
19
|
+
*/
|
|
20
|
+
export async function check(ast) {
|
|
21
|
+
// Phase 1 — Structural validation
|
|
22
|
+
const validation = validate(ast);
|
|
23
|
+
if (!validation.ok) {
|
|
24
|
+
return { ok: false, errors: validation.errors };
|
|
25
|
+
}
|
|
26
|
+
const module = ast;
|
|
27
|
+
// Phase 2a — Name resolution
|
|
28
|
+
const resolveErrors = resolve(module);
|
|
29
|
+
if (resolveErrors.length > 0) {
|
|
30
|
+
return { ok: false, errors: resolveErrors };
|
|
31
|
+
}
|
|
32
|
+
// Phase 2b — Type checking
|
|
33
|
+
const typeErrors = typeCheck(module);
|
|
34
|
+
if (typeErrors.length > 0) {
|
|
35
|
+
return { ok: false, errors: typeErrors };
|
|
36
|
+
}
|
|
37
|
+
// Phase 3 — Effect checking
|
|
38
|
+
const effectErrors = effectCheck(module);
|
|
39
|
+
if (effectErrors.length > 0) {
|
|
40
|
+
return { ok: false, errors: effectErrors };
|
|
41
|
+
}
|
|
42
|
+
// Phase 4 — Contract verification
|
|
43
|
+
const contractErrors = await contractVerify(module);
|
|
44
|
+
if (contractErrors.length > 0) {
|
|
45
|
+
return { ok: false, errors: contractErrors };
|
|
46
|
+
}
|
|
47
|
+
return { ok: true, errors: [], module };
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.js","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,qDAAqD;AACrD,gFAAgF;AAChF,wFAAwF;AACxF,yCAAyC;AAGzC,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAUvD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,GAAY;IACpC,kCAAkC;IAClC,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC;QACjB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC;IACpD,CAAC;IAED,MAAM,MAAM,GAAG,GAAkB,CAAC;IAElC,6BAA6B;IAC7B,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;IAChD,CAAC;IAED,2BAA2B;IAC3B,MAAM,UAAU,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IACrC,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAC7C,CAAC;IAED,4BAA4B;IAC5B,MAAM,YAAY,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;IAC/C,CAAC;IAED,kCAAkC;IAClC,MAAM,cAAc,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,CAAC;IACpD,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IACjD,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC;AAC5C,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { EdictModule } from "../ast/nodes.js";
|
|
2
|
+
import type { StructuredError } from "../errors/structured-errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* Entry point: type-check a validated + resolved Edict module.
|
|
5
|
+
*/
|
|
6
|
+
export declare function typeCheck(module: EdictModule): StructuredError[];
|
|
7
|
+
//# sourceMappingURL=check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/checker/check.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACR,WAAW,EAOd,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAqBtE;;GAEG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,eAAe,EAAE,CAwChE"}
|
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Type Checker — typeCheck(module) → StructuredError[]
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Assumes name resolution has already passed. Infers types for expressions
|
|
5
|
+
// and reports type mismatches, arity errors, etc.
|
|
6
|
+
import { typeMismatch, arityMismatch, notAFunction, unknownField, unknownRecord, unknownEnum, unknownVariant, missingRecordFields, } from "../errors/structured-errors.js";
|
|
7
|
+
import { TypeEnv } from "./type-env.js";
|
|
8
|
+
import { typesEqual, isUnknown, resolveType } from "./types-equal.js";
|
|
9
|
+
import { BUILTIN_FUNCTIONS } from "../codegen/builtins.js";
|
|
10
|
+
const UNKNOWN_TYPE = { kind: "named", name: "unknown" };
|
|
11
|
+
const INT_TYPE = { kind: "basic", name: "Int" };
|
|
12
|
+
const FLOAT_TYPE = { kind: "basic", name: "Float" };
|
|
13
|
+
const STRING_TYPE = { kind: "basic", name: "String" };
|
|
14
|
+
const BOOL_TYPE = { kind: "basic", name: "Bool" };
|
|
15
|
+
/**
|
|
16
|
+
* Entry point: type-check a validated + resolved Edict module.
|
|
17
|
+
*/
|
|
18
|
+
export function typeCheck(module) {
|
|
19
|
+
const errors = [];
|
|
20
|
+
const rootEnv = new TypeEnv();
|
|
21
|
+
// Register built-in function signatures
|
|
22
|
+
for (const [name, builtin] of BUILTIN_FUNCTIONS) {
|
|
23
|
+
rootEnv.bind(name, builtin.type);
|
|
24
|
+
}
|
|
25
|
+
// Register type definitions (records, enums, type aliases)
|
|
26
|
+
for (const def of module.definitions) {
|
|
27
|
+
registerTypeDef(def, rootEnv);
|
|
28
|
+
}
|
|
29
|
+
// Register function signatures and const types
|
|
30
|
+
for (const def of module.definitions) {
|
|
31
|
+
registerValueDef(def, rootEnv);
|
|
32
|
+
}
|
|
33
|
+
// Register imports as unknown
|
|
34
|
+
for (const imp of module.imports) {
|
|
35
|
+
for (const name of imp.names) {
|
|
36
|
+
rootEnv.bind(name, UNKNOWN_TYPE);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Type-check each definition
|
|
40
|
+
for (const def of module.definitions) {
|
|
41
|
+
switch (def.kind) {
|
|
42
|
+
case "fn":
|
|
43
|
+
checkFunction(def, rootEnv, errors);
|
|
44
|
+
break;
|
|
45
|
+
case "const":
|
|
46
|
+
checkConst(def, rootEnv, errors);
|
|
47
|
+
break;
|
|
48
|
+
// record, enum, type — no body to type-check
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return errors;
|
|
52
|
+
}
|
|
53
|
+
// =============================================================================
|
|
54
|
+
// Registration
|
|
55
|
+
// =============================================================================
|
|
56
|
+
function registerTypeDef(def, env) {
|
|
57
|
+
switch (def.kind) {
|
|
58
|
+
case "type":
|
|
59
|
+
case "record":
|
|
60
|
+
case "enum":
|
|
61
|
+
env.registerTypeDef(def.name, def);
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function registerValueDef(def, env) {
|
|
66
|
+
switch (def.kind) {
|
|
67
|
+
case "fn": {
|
|
68
|
+
const fnType = {
|
|
69
|
+
kind: "fn_type",
|
|
70
|
+
params: def.params.map((p) => p.type),
|
|
71
|
+
effects: [...def.effects],
|
|
72
|
+
returnType: def.returnType,
|
|
73
|
+
};
|
|
74
|
+
env.bind(def.name, fnType);
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case "const":
|
|
78
|
+
env.bind(def.name, def.type);
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// =============================================================================
|
|
83
|
+
// Function checking
|
|
84
|
+
// =============================================================================
|
|
85
|
+
function checkFunction(fn, rootEnv, errors) {
|
|
86
|
+
const fnEnv = rootEnv.child();
|
|
87
|
+
// Bind params
|
|
88
|
+
for (const param of fn.params) {
|
|
89
|
+
fnEnv.bind(param.name, param.type);
|
|
90
|
+
}
|
|
91
|
+
// Check contracts
|
|
92
|
+
for (const contract of fn.contracts) {
|
|
93
|
+
if (contract.kind === "post") {
|
|
94
|
+
const postEnv = fnEnv.child();
|
|
95
|
+
postEnv.bind("result", fn.returnType);
|
|
96
|
+
const condType = inferExpr(contract.condition, postEnv, errors);
|
|
97
|
+
checkExpectedType(condType, BOOL_TYPE, contract.id, fnEnv, errors, "post-contract condition must be Bool");
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const condType = inferExpr(contract.condition, fnEnv, errors);
|
|
101
|
+
checkExpectedType(condType, BOOL_TYPE, contract.id, fnEnv, errors, "pre-contract condition must be Bool");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Infer body type
|
|
105
|
+
const bodyType = inferExprList(fn.body, fnEnv, errors);
|
|
106
|
+
// Check return type
|
|
107
|
+
if (bodyType && !isUnknown(bodyType) && !isUnknown(resolveType(fn.returnType, fnEnv))) {
|
|
108
|
+
checkExpectedType(bodyType, fn.returnType, fn.id, fnEnv, errors, "function body type must match return type");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function checkConst(def, env, errors) {
|
|
112
|
+
const valType = inferExpr(def.value, env, errors);
|
|
113
|
+
checkExpectedType(valType, def.type, def.id, env, errors, "const value must match declared type");
|
|
114
|
+
}
|
|
115
|
+
// =============================================================================
|
|
116
|
+
// Type inference
|
|
117
|
+
// =============================================================================
|
|
118
|
+
function inferExprList(exprs, env, errors) {
|
|
119
|
+
if (exprs.length === 0)
|
|
120
|
+
return UNKNOWN_TYPE;
|
|
121
|
+
let currentEnv = env;
|
|
122
|
+
let lastType = UNKNOWN_TYPE;
|
|
123
|
+
for (const expr of exprs) {
|
|
124
|
+
lastType = inferExpr(expr, currentEnv, errors);
|
|
125
|
+
if (expr.kind === "let") {
|
|
126
|
+
currentEnv = currentEnv.child();
|
|
127
|
+
// Bind the let name in child env
|
|
128
|
+
const bindType = expr.type ?? lastType;
|
|
129
|
+
currentEnv.bind(expr.name, bindType);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return lastType;
|
|
133
|
+
}
|
|
134
|
+
function inferExpr(expr, env, errors) {
|
|
135
|
+
switch (expr.kind) {
|
|
136
|
+
case "literal":
|
|
137
|
+
return inferLiteral(expr);
|
|
138
|
+
case "ident":
|
|
139
|
+
return env.getType(expr.name) ?? UNKNOWN_TYPE;
|
|
140
|
+
case "binop":
|
|
141
|
+
return inferBinop(expr, env, errors);
|
|
142
|
+
case "unop":
|
|
143
|
+
return inferUnop(expr, env, errors);
|
|
144
|
+
case "call":
|
|
145
|
+
return inferCall(expr, env, errors);
|
|
146
|
+
case "if":
|
|
147
|
+
return inferIf(expr, env, errors);
|
|
148
|
+
case "let":
|
|
149
|
+
return inferLet(expr, env, errors);
|
|
150
|
+
case "match":
|
|
151
|
+
return inferMatch(expr, env, errors);
|
|
152
|
+
case "array":
|
|
153
|
+
return inferArray(expr, env, errors);
|
|
154
|
+
case "tuple_expr":
|
|
155
|
+
return inferTuple(expr, env, errors);
|
|
156
|
+
case "record_expr":
|
|
157
|
+
return inferRecordExpr(expr, env, errors);
|
|
158
|
+
case "enum_constructor":
|
|
159
|
+
return inferEnumConstructor(expr, env, errors);
|
|
160
|
+
case "access":
|
|
161
|
+
return inferAccess(expr, env, errors);
|
|
162
|
+
case "lambda":
|
|
163
|
+
return inferLambda(expr, env, errors);
|
|
164
|
+
case "block":
|
|
165
|
+
return inferExprList(expr.body, env.child(), errors);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// =============================================================================
|
|
169
|
+
// Inference helpers
|
|
170
|
+
// =============================================================================
|
|
171
|
+
function inferLiteral(expr) {
|
|
172
|
+
// If annotated, use that type
|
|
173
|
+
if (expr.type)
|
|
174
|
+
return expr.type;
|
|
175
|
+
if (typeof expr.value === "boolean")
|
|
176
|
+
return BOOL_TYPE;
|
|
177
|
+
if (typeof expr.value === "string")
|
|
178
|
+
return STRING_TYPE;
|
|
179
|
+
if (typeof expr.value === "number") {
|
|
180
|
+
return Number.isInteger(expr.value) ? INT_TYPE : FLOAT_TYPE;
|
|
181
|
+
}
|
|
182
|
+
return UNKNOWN_TYPE;
|
|
183
|
+
}
|
|
184
|
+
function inferBinop(expr, env, errors) {
|
|
185
|
+
const leftType = inferExpr(expr.left, env, errors);
|
|
186
|
+
const rightType = inferExpr(expr.right, env, errors);
|
|
187
|
+
// unknown propagation
|
|
188
|
+
if (isUnknown(leftType) || isUnknown(rightType))
|
|
189
|
+
return UNKNOWN_TYPE;
|
|
190
|
+
const op = expr.op;
|
|
191
|
+
// Logical operators: and, or, implies
|
|
192
|
+
if (op === "and" || op === "or" || op === "implies") {
|
|
193
|
+
if (!isBool(leftType)) {
|
|
194
|
+
errors.push(typeMismatch(expr.id, BOOL_TYPE, leftType));
|
|
195
|
+
}
|
|
196
|
+
if (!isBool(rightType)) {
|
|
197
|
+
errors.push(typeMismatch(expr.id, BOOL_TYPE, rightType));
|
|
198
|
+
}
|
|
199
|
+
return BOOL_TYPE;
|
|
200
|
+
}
|
|
201
|
+
// Comparison operators: ==, !=, <, >, <=, >=
|
|
202
|
+
if (op === "==" || op === "!=" || op === "<" || op === ">" || op === "<=" || op === ">=") {
|
|
203
|
+
if (!typesEqual(leftType, rightType, env)) {
|
|
204
|
+
errors.push(typeMismatch(expr.id, leftType, rightType));
|
|
205
|
+
}
|
|
206
|
+
return BOOL_TYPE;
|
|
207
|
+
}
|
|
208
|
+
// Arithmetic: +, -, *, /, %
|
|
209
|
+
if (op === "+") {
|
|
210
|
+
// + works on numeric types AND strings
|
|
211
|
+
if (isString(leftType) && isString(rightType))
|
|
212
|
+
return STRING_TYPE;
|
|
213
|
+
if (isNumeric(leftType, env) && isNumeric(rightType, env)) {
|
|
214
|
+
if (!typesEqual(leftType, rightType, env)) {
|
|
215
|
+
errors.push(typeMismatch(expr.id, leftType, rightType));
|
|
216
|
+
return UNKNOWN_TYPE;
|
|
217
|
+
}
|
|
218
|
+
return leftType;
|
|
219
|
+
}
|
|
220
|
+
errors.push(typeMismatch(expr.id, UNKNOWN_TYPE, leftType)); // Unknown since both numeric/String are valid expected
|
|
221
|
+
return UNKNOWN_TYPE;
|
|
222
|
+
}
|
|
223
|
+
// -, *, /, % — numeric only
|
|
224
|
+
if (isNumeric(leftType, env) && isNumeric(rightType, env)) {
|
|
225
|
+
if (!typesEqual(leftType, rightType, env)) {
|
|
226
|
+
errors.push(typeMismatch(expr.id, leftType, rightType));
|
|
227
|
+
return UNKNOWN_TYPE;
|
|
228
|
+
}
|
|
229
|
+
return leftType;
|
|
230
|
+
}
|
|
231
|
+
errors.push(typeMismatch(expr.id, UNKNOWN_TYPE, leftType)); // Expected numeric
|
|
232
|
+
return UNKNOWN_TYPE;
|
|
233
|
+
}
|
|
234
|
+
function inferUnop(expr, env, errors) {
|
|
235
|
+
const operandType = inferExpr(expr.operand, env, errors);
|
|
236
|
+
if (isUnknown(operandType))
|
|
237
|
+
return UNKNOWN_TYPE;
|
|
238
|
+
if (expr.op === "not") {
|
|
239
|
+
if (!isBool(operandType)) {
|
|
240
|
+
errors.push(typeMismatch(expr.id, BOOL_TYPE, operandType));
|
|
241
|
+
}
|
|
242
|
+
return BOOL_TYPE;
|
|
243
|
+
}
|
|
244
|
+
// Unary -
|
|
245
|
+
if (!isNumeric(operandType, env)) {
|
|
246
|
+
errors.push(typeMismatch(expr.id, UNKNOWN_TYPE, operandType)); // expected numeric
|
|
247
|
+
return UNKNOWN_TYPE;
|
|
248
|
+
}
|
|
249
|
+
return operandType;
|
|
250
|
+
}
|
|
251
|
+
function inferCall(expr, env, errors) {
|
|
252
|
+
const fnType = inferExpr(expr.fn, env, errors);
|
|
253
|
+
if (isUnknown(fnType)) {
|
|
254
|
+
// If fn is unknown, we can't type-check args. Infer them for side effects (let bindings)
|
|
255
|
+
for (const arg of expr.args)
|
|
256
|
+
inferExpr(arg, env, errors);
|
|
257
|
+
return UNKNOWN_TYPE;
|
|
258
|
+
}
|
|
259
|
+
const resolved = resolveType(fnType, env);
|
|
260
|
+
if (resolved.kind !== "fn_type") {
|
|
261
|
+
errors.push(notAFunction(expr.id, fnType));
|
|
262
|
+
// Still infer arg types for error propagation
|
|
263
|
+
for (const arg of expr.args)
|
|
264
|
+
inferExpr(arg, env, errors);
|
|
265
|
+
return UNKNOWN_TYPE;
|
|
266
|
+
}
|
|
267
|
+
// Check arity
|
|
268
|
+
if (expr.args.length !== resolved.params.length) {
|
|
269
|
+
errors.push(arityMismatch(expr.id, resolved.params.length, expr.args.length));
|
|
270
|
+
}
|
|
271
|
+
// Check arg types (up to the minimum of args/params)
|
|
272
|
+
const checkCount = Math.min(expr.args.length, resolved.params.length);
|
|
273
|
+
for (let i = 0; i < checkCount; i++) {
|
|
274
|
+
const argType = inferExpr(expr.args[i], env, errors);
|
|
275
|
+
checkExpectedType(argType, resolved.params[i], expr.args[i].id, env, errors, `argument ${i + 1}`);
|
|
276
|
+
}
|
|
277
|
+
// Infer remaining surplus args
|
|
278
|
+
for (let i = checkCount; i < expr.args.length; i++) {
|
|
279
|
+
inferExpr(expr.args[i], env, errors);
|
|
280
|
+
}
|
|
281
|
+
return resolved.returnType;
|
|
282
|
+
}
|
|
283
|
+
function inferIf(expr, env, errors) {
|
|
284
|
+
const condType = inferExpr(expr.condition, env, errors);
|
|
285
|
+
checkExpectedType(condType, BOOL_TYPE, expr.id, env, errors, "if condition must be Bool");
|
|
286
|
+
const thenType = inferExprList(expr.then, env.child(), errors);
|
|
287
|
+
if (expr.else) {
|
|
288
|
+
const elseType = inferExprList(expr.else, env.child(), errors);
|
|
289
|
+
if (!isUnknown(thenType) && !isUnknown(elseType)) {
|
|
290
|
+
if (!typesEqual(thenType, elseType, env)) {
|
|
291
|
+
errors.push(typeMismatch(expr.id, thenType, elseType));
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return thenType;
|
|
295
|
+
}
|
|
296
|
+
// No else → Option<thenType>
|
|
297
|
+
return { kind: "option", inner: thenType };
|
|
298
|
+
}
|
|
299
|
+
function inferLet(expr, env, errors) {
|
|
300
|
+
const valType = inferExpr(expr.value, env, errors);
|
|
301
|
+
if (expr.type) {
|
|
302
|
+
checkExpectedType(valType, expr.type, expr.id, env, errors, "let value must match type annotation");
|
|
303
|
+
return expr.type;
|
|
304
|
+
}
|
|
305
|
+
return valType;
|
|
306
|
+
}
|
|
307
|
+
function inferMatch(expr, env, errors) {
|
|
308
|
+
const targetType = inferExpr(expr.target, env, errors);
|
|
309
|
+
let resultType = null;
|
|
310
|
+
for (const arm of expr.arms) {
|
|
311
|
+
const armEnv = env.child();
|
|
312
|
+
inferPattern(arm.pattern, targetType, armEnv, env, errors);
|
|
313
|
+
const bodyType = inferExprList(arm.body, armEnv, errors);
|
|
314
|
+
if (resultType === null) {
|
|
315
|
+
resultType = bodyType;
|
|
316
|
+
}
|
|
317
|
+
else if (!isUnknown(resultType) && !isUnknown(bodyType)) {
|
|
318
|
+
if (!typesEqual(resultType, bodyType, env)) {
|
|
319
|
+
errors.push(typeMismatch(arm.id, resultType, bodyType));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return resultType ?? UNKNOWN_TYPE;
|
|
324
|
+
}
|
|
325
|
+
function inferPattern(pattern, targetType, armEnv, rootEnv, errors) {
|
|
326
|
+
switch (pattern.kind) {
|
|
327
|
+
case "binding":
|
|
328
|
+
armEnv.bind(pattern.name, targetType);
|
|
329
|
+
break;
|
|
330
|
+
case "wildcard":
|
|
331
|
+
break;
|
|
332
|
+
case "literal_pattern": {
|
|
333
|
+
const litType = inferLiteralPatternType(pattern.value);
|
|
334
|
+
if (!isUnknown(targetType) && !isUnknown(litType)) {
|
|
335
|
+
if (!typesEqual(litType, targetType, rootEnv)) {
|
|
336
|
+
errors.push(typeMismatch(null, targetType, litType));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
case "constructor": {
|
|
342
|
+
// Resolve the enum from target type
|
|
343
|
+
if (isUnknown(targetType)) {
|
|
344
|
+
// If target is unknown, bind sub-patterns as unknown
|
|
345
|
+
for (const sub of pattern.fields) {
|
|
346
|
+
inferPattern(sub, UNKNOWN_TYPE, armEnv, rootEnv, errors);
|
|
347
|
+
}
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
const resolvedTarget = resolveType(targetType, rootEnv);
|
|
351
|
+
if (resolvedTarget.kind !== "named") {
|
|
352
|
+
// Can't destructure non-named types
|
|
353
|
+
for (const sub of pattern.fields) {
|
|
354
|
+
inferPattern(sub, UNKNOWN_TYPE, armEnv, rootEnv, errors);
|
|
355
|
+
}
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
const enumDef = rootEnv.lookupTypeDef(resolvedTarget.name);
|
|
359
|
+
if (!enumDef || enumDef.kind !== "enum") {
|
|
360
|
+
for (const sub of pattern.fields) {
|
|
361
|
+
inferPattern(sub, UNKNOWN_TYPE, armEnv, rootEnv, errors);
|
|
362
|
+
}
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
const variant = enumDef.variants.find((v) => v.name === pattern.name);
|
|
366
|
+
if (!variant) {
|
|
367
|
+
// Already caught by resolver, but bind sub-patterns
|
|
368
|
+
for (const sub of pattern.fields) {
|
|
369
|
+
inferPattern(sub, UNKNOWN_TYPE, armEnv, rootEnv, errors);
|
|
370
|
+
}
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
// Bind positional fields
|
|
374
|
+
for (let i = 0; i < pattern.fields.length; i++) {
|
|
375
|
+
const fieldType = i < variant.fields.length ? variant.fields[i].type : UNKNOWN_TYPE;
|
|
376
|
+
inferPattern(pattern.fields[i], fieldType, armEnv, rootEnv, errors);
|
|
377
|
+
}
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
function inferArray(expr, env, errors) {
|
|
383
|
+
if (expr.elements.length === 0) {
|
|
384
|
+
return { kind: "array", element: UNKNOWN_TYPE };
|
|
385
|
+
}
|
|
386
|
+
const firstType = inferExpr(expr.elements[0], env, errors);
|
|
387
|
+
for (let i = 1; i < expr.elements.length; i++) {
|
|
388
|
+
const elType = inferExpr(expr.elements[i], env, errors);
|
|
389
|
+
if (!isUnknown(firstType) && !isUnknown(elType)) {
|
|
390
|
+
if (!typesEqual(firstType, elType, env)) {
|
|
391
|
+
errors.push(typeMismatch(expr.elements[i].id, firstType, elType));
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return { kind: "array", element: firstType };
|
|
396
|
+
}
|
|
397
|
+
function inferTuple(expr, env, errors) {
|
|
398
|
+
const elementTypes = expr.elements.map((el) => inferExpr(el, env, errors));
|
|
399
|
+
return { kind: "tuple", elements: elementTypes };
|
|
400
|
+
}
|
|
401
|
+
function inferRecordExpr(expr, env, errors) {
|
|
402
|
+
const def = env.lookupTypeDef(expr.name);
|
|
403
|
+
if (!def) {
|
|
404
|
+
errors.push(unknownRecord(expr.id, expr.name, collectTypeDefNames(env, "record")));
|
|
405
|
+
// Still infer field value types
|
|
406
|
+
for (const f of expr.fields)
|
|
407
|
+
inferExpr(f.value, env, errors);
|
|
408
|
+
return UNKNOWN_TYPE;
|
|
409
|
+
}
|
|
410
|
+
if (def.kind !== "record") {
|
|
411
|
+
errors.push(unknownRecord(expr.id, expr.name, collectTypeDefNames(env, "record")));
|
|
412
|
+
for (const f of expr.fields)
|
|
413
|
+
inferExpr(f.value, env, errors);
|
|
414
|
+
return UNKNOWN_TYPE;
|
|
415
|
+
}
|
|
416
|
+
const recordDef = def;
|
|
417
|
+
const providedFields = new Set(expr.fields.map((f) => f.name));
|
|
418
|
+
// Check required fields (no defaultValue)
|
|
419
|
+
const requiredMissing = recordDef.fields
|
|
420
|
+
.filter((f) => !f.defaultValue && !providedFields.has(f.name))
|
|
421
|
+
.map((f) => f.name);
|
|
422
|
+
if (requiredMissing.length > 0) {
|
|
423
|
+
errors.push(missingRecordFields(expr.id, expr.name, requiredMissing));
|
|
424
|
+
}
|
|
425
|
+
// Check each provided field
|
|
426
|
+
for (const fieldInit of expr.fields) {
|
|
427
|
+
const fieldDef = recordDef.fields.find((f) => f.name === fieldInit.name);
|
|
428
|
+
if (!fieldDef) {
|
|
429
|
+
errors.push(unknownField(expr.id, expr.name, fieldInit.name, recordDef.fields.map((f) => f.name)));
|
|
430
|
+
inferExpr(fieldInit.value, env, errors);
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
const valType = inferExpr(fieldInit.value, env, errors);
|
|
434
|
+
checkExpectedType(valType, fieldDef.type, expr.id, env, errors, `field '${fieldInit.name}'`);
|
|
435
|
+
}
|
|
436
|
+
return { kind: "named", name: expr.name };
|
|
437
|
+
}
|
|
438
|
+
function inferEnumConstructor(expr, env, errors) {
|
|
439
|
+
const def = env.lookupTypeDef(expr.enumName);
|
|
440
|
+
if (!def) {
|
|
441
|
+
errors.push(unknownEnum(expr.id, expr.enumName, collectTypeDefNames(env, "enum")));
|
|
442
|
+
for (const f of expr.fields)
|
|
443
|
+
inferExpr(f.value, env, errors);
|
|
444
|
+
return UNKNOWN_TYPE;
|
|
445
|
+
}
|
|
446
|
+
if (def.kind !== "enum") {
|
|
447
|
+
errors.push(unknownEnum(expr.id, expr.enumName, collectTypeDefNames(env, "enum")));
|
|
448
|
+
for (const f of expr.fields)
|
|
449
|
+
inferExpr(f.value, env, errors);
|
|
450
|
+
return UNKNOWN_TYPE;
|
|
451
|
+
}
|
|
452
|
+
const enumDef = def;
|
|
453
|
+
const variant = enumDef.variants.find((v) => v.name === expr.variant);
|
|
454
|
+
if (!variant) {
|
|
455
|
+
errors.push(unknownVariant(expr.id, expr.enumName, expr.variant, enumDef.variants.map((v) => v.name)));
|
|
456
|
+
for (const f of expr.fields)
|
|
457
|
+
inferExpr(f.value, env, errors);
|
|
458
|
+
return UNKNOWN_TYPE;
|
|
459
|
+
}
|
|
460
|
+
// Check field count and types
|
|
461
|
+
for (const fieldInit of expr.fields) {
|
|
462
|
+
const fieldDef = variant.fields.find((f) => f.name === fieldInit.name);
|
|
463
|
+
if (!fieldDef) {
|
|
464
|
+
errors.push(unknownField(expr.id, `${expr.enumName}.${expr.variant}`, fieldInit.name, variant.fields.map((f) => f.name)));
|
|
465
|
+
inferExpr(fieldInit.value, env, errors);
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
const valType = inferExpr(fieldInit.value, env, errors);
|
|
469
|
+
checkExpectedType(valType, fieldDef.type, expr.id, env, errors, `field '${fieldInit.name}'`);
|
|
470
|
+
}
|
|
471
|
+
return { kind: "named", name: expr.enumName };
|
|
472
|
+
}
|
|
473
|
+
function inferAccess(expr, env, errors) {
|
|
474
|
+
const targetType = inferExpr(expr.target, env, errors);
|
|
475
|
+
if (isUnknown(targetType))
|
|
476
|
+
return UNKNOWN_TYPE;
|
|
477
|
+
const resolved = resolveType(targetType, env);
|
|
478
|
+
if (resolved.kind !== "named") {
|
|
479
|
+
errors.push(typeMismatch(expr.id, UNKNOWN_TYPE, targetType)); // expected record type
|
|
480
|
+
return UNKNOWN_TYPE;
|
|
481
|
+
}
|
|
482
|
+
const def = env.lookupTypeDef(resolved.name);
|
|
483
|
+
if (!def || def.kind !== "record") {
|
|
484
|
+
errors.push(typeMismatch(expr.id, UNKNOWN_TYPE, targetType)); // expected record type
|
|
485
|
+
return UNKNOWN_TYPE;
|
|
486
|
+
}
|
|
487
|
+
const field = def.fields.find((f) => f.name === expr.field);
|
|
488
|
+
if (!field) {
|
|
489
|
+
errors.push(unknownField(expr.id, resolved.name, expr.field, def.fields.map((f) => f.name)));
|
|
490
|
+
return UNKNOWN_TYPE;
|
|
491
|
+
}
|
|
492
|
+
return field.type;
|
|
493
|
+
}
|
|
494
|
+
function inferLambda(expr, env, errors) {
|
|
495
|
+
const lamEnv = env.child();
|
|
496
|
+
for (const param of expr.params) {
|
|
497
|
+
lamEnv.bind(param.name, param.type);
|
|
498
|
+
}
|
|
499
|
+
const bodyType = inferExprList(expr.body, lamEnv, errors);
|
|
500
|
+
return {
|
|
501
|
+
kind: "fn_type",
|
|
502
|
+
params: expr.params.map((p) => p.type),
|
|
503
|
+
effects: [],
|
|
504
|
+
returnType: bodyType,
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
// =============================================================================
|
|
508
|
+
// Utility functions
|
|
509
|
+
// =============================================================================
|
|
510
|
+
function checkExpectedType(actual, expected, nodeId, env, errors, _hint) {
|
|
511
|
+
if (isUnknown(actual) || isUnknown(expected))
|
|
512
|
+
return;
|
|
513
|
+
if (!typesEqual(actual, expected, env)) {
|
|
514
|
+
errors.push(typeMismatch(nodeId, expected, actual));
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function isBool(type) {
|
|
518
|
+
return type.kind === "basic" && type.name === "Bool";
|
|
519
|
+
}
|
|
520
|
+
function isString(type) {
|
|
521
|
+
return type.kind === "basic" && type.name === "String";
|
|
522
|
+
}
|
|
523
|
+
function isNumeric(type, env) {
|
|
524
|
+
const resolved = resolveType(type, env);
|
|
525
|
+
if (resolved.kind === "basic") {
|
|
526
|
+
return resolved.name === "Int" || resolved.name === "Float";
|
|
527
|
+
}
|
|
528
|
+
if (resolved.kind === "unit_type")
|
|
529
|
+
return true;
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
function inferLiteralPatternType(value) {
|
|
533
|
+
if (typeof value === "boolean")
|
|
534
|
+
return BOOL_TYPE;
|
|
535
|
+
if (typeof value === "string")
|
|
536
|
+
return STRING_TYPE;
|
|
537
|
+
if (typeof value === "number") {
|
|
538
|
+
return Number.isInteger(value) ? INT_TYPE : FLOAT_TYPE;
|
|
539
|
+
}
|
|
540
|
+
return UNKNOWN_TYPE;
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Collect known type definition names of a specific kind (for error candidates).
|
|
544
|
+
*/
|
|
545
|
+
function collectTypeDefNames(env, kind) {
|
|
546
|
+
return env.allTypeDefNames(kind);
|
|
547
|
+
}
|
|
548
|
+
//# sourceMappingURL=check.js.map
|