@nudojs/core 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/LICENSE +21 -0
- package/README.md +26 -0
- package/dist/index.d.ts +1633 -11
- package/dist/index.js +9965 -107
- package/package.json +28 -2
- package/src/__tests__/fn-sig-impl.test.ts +57 -0
- package/src/__tests__/ops-refined.test.ts +196 -0
- package/src/__tests__/ops.test.ts +8 -33
- package/src/__tests__/type-value-refined.test.ts +210 -0
- package/src/__tests__/type-value.test.ts +106 -0
- package/src/algebra/__tests__/abs-native.test.ts +176 -0
- package/src/algebra/__tests__/arithmetic.test.ts +170 -0
- package/src/algebra/__tests__/ast-eval.test.ts +154 -0
- package/src/algebra/__tests__/bridge.test.ts +160 -0
- package/src/algebra/__tests__/call-budget.test.ts +117 -0
- package/src/algebra/__tests__/check-case-consistency.test.ts +69 -0
- package/src/algebra/__tests__/check-gold.test.ts +290 -0
- package/src/algebra/__tests__/check-json.test.ts +57 -0
- package/src/algebra/__tests__/check-mini-repo.test.ts +49 -0
- package/src/algebra/__tests__/check-real-commander.test.ts +43 -0
- package/src/algebra/__tests__/check-real-packages.test.ts +165 -0
- package/src/algebra/__tests__/check-recall-gold.test.ts +1024 -0
- package/src/algebra/__tests__/check-require-dep-memo.test.ts +45 -0
- package/src/algebra/__tests__/check-return-contract.test.ts +90 -0
- package/src/algebra/__tests__/check-shape-gold.test.ts +303 -0
- package/src/algebra/__tests__/check-source-memo.test.ts +204 -0
- package/src/algebra/__tests__/check.test.ts +69 -0
- package/src/algebra/__tests__/denote.test.ts +77 -0
- package/src/algebra/__tests__/diagnostics.test.ts +64 -0
- package/src/algebra/__tests__/differential.test.ts +124 -0
- package/src/algebra/__tests__/div-mod.test.ts +38 -0
- package/src/algebra/__tests__/example0.test.ts +103 -0
- package/src/algebra/__tests__/exec-analyze-throws.test.ts +60 -0
- package/src/algebra/__tests__/exec-async.test.ts +46 -0
- package/src/algebra/__tests__/exec-bpath.test.ts +193 -0
- package/src/algebra/__tests__/exec-class.test.ts +56 -0
- package/src/algebra/__tests__/exec-e2e.test.ts +97 -0
- package/src/algebra/__tests__/exec-generator.test.ts +53 -0
- package/src/algebra/__tests__/exec-inherit-default.test.ts +111 -0
- package/src/algebra/__tests__/exec-optional-nested.test.ts +87 -0
- package/src/algebra/__tests__/exec-rest.test.ts +55 -0
- package/src/algebra/__tests__/exec-spread-forof.test.ts +85 -0
- package/src/algebra/__tests__/exec-static-computed.test.ts +61 -0
- package/src/algebra/__tests__/exec-switch.test.ts +74 -0
- package/src/algebra/__tests__/exec-trycatch.test.ts +71 -0
- package/src/algebra/__tests__/fn-fp-memo.test.ts +166 -0
- package/src/algebra/__tests__/fn-fp-multidecl.test.ts +49 -0
- package/src/algebra/__tests__/format.test.ts +34 -0
- package/src/algebra/__tests__/generalize-memo.test.ts +285 -0
- package/src/algebra/__tests__/generalize.test.ts +113 -0
- package/src/algebra/__tests__/hash-source.test.ts +19 -0
- package/src/algebra/__tests__/hof.test.ts +80 -0
- package/src/algebra/__tests__/inlay.test.ts +30 -0
- package/src/algebra/__tests__/language.test.ts +77 -0
- package/src/algebra/__tests__/leq.test.ts +109 -0
- package/src/algebra/__tests__/load-deps-fp-memo.test.ts +132 -0
- package/src/algebra/__tests__/methods.test.ts +37 -0
- package/src/algebra/__tests__/node-types.test.ts +26 -0
- package/src/algebra/__tests__/nudo-constraints.ts +46 -0
- package/src/algebra/__tests__/objects.test.ts +117 -0
- package/src/algebra/__tests__/parse-source-cache.test.ts +42 -0
- package/src/algebra/__tests__/refine-import.test.ts +109 -0
- package/src/algebra/__tests__/stable-source-key-block.test.ts +23 -0
- package/src/algebra/__tests__/stable-source-key.test.ts +31 -0
- package/src/algebra/__tests__/surface.test.ts +41 -0
- package/src/algebra/__tests__/this-inherit.test.ts +130 -0
- package/src/algebra/abs-fn.ts +49 -0
- package/src/algebra/abs-modules.ts +139 -0
- package/src/algebra/abs.ts +206 -0
- package/src/algebra/arithmetic.ts +691 -0
- package/src/algebra/ast-eval.ts +1763 -0
- package/src/algebra/bridge.ts +350 -0
- package/src/algebra/builtins.ts +373 -0
- package/src/algebra/check-report.ts +150 -0
- package/src/algebra/check.ts +1867 -0
- package/src/algebra/constraint.ts +304 -0
- package/src/algebra/denote.ts +166 -0
- package/src/algebra/diagnostics.ts +210 -0
- package/src/algebra/exec/call.ts +29 -0
- package/src/algebra/exec/calls.ts +96 -0
- package/src/algebra/exec/class-registry.ts +29 -0
- package/src/algebra/exec/class.ts +329 -0
- package/src/algebra/exec/index.ts +6 -0
- package/src/algebra/exec/member-diag.ts +141 -0
- package/src/algebra/exec/run.ts +341 -0
- package/src/algebra/exec/runtime.ts +583 -0
- package/src/algebra/exec/transpile.ts +993 -0
- package/src/algebra/fn-fp.ts +294 -0
- package/src/algebra/format.ts +91 -0
- package/src/algebra/generalize.ts +776 -0
- package/src/algebra/hash-source.ts +27 -0
- package/src/algebra/index.ts +34 -0
- package/src/algebra/inlay.ts +198 -0
- package/src/algebra/language.ts +289 -0
- package/src/algebra/leak.ts +68 -0
- package/src/algebra/leq.ts +301 -0
- package/src/algebra/load-deps-fp.ts +97 -0
- package/src/algebra/methods.ts +128 -0
- package/src/algebra/objects.ts +251 -0
- package/src/algebra/parse-source.ts +64 -0
- package/src/algebra/phi.ts +42 -0
- package/src/algebra/pred.ts +395 -0
- package/src/algebra/refine.ts +227 -0
- package/src/algebra/stable-source-key.ts +81 -0
- package/src/algebra/surface.ts +206 -0
- package/src/algebra/template.ts +143 -0
- package/src/algebra/term.ts +119 -0
- package/src/environment.ts +5 -0
- package/src/index.ts +52 -1
- package/src/mock-helpers.ts +166 -0
- package/src/ops.ts +106 -62
- package/src/refinements/range.ts +86 -0
- package/src/refinements/template-predicates.ts +11 -0
- package/src/refinements/template.ts +176 -0
- package/src/strip-types.ts +236 -0
- package/src/type-value.ts +193 -12
package/package.json
CHANGED
|
@@ -1,10 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nudojs/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Nudo type system: type-as-computation (Abs) with TypeValue evaluation IR",
|
|
4
5
|
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"nudo",
|
|
9
|
+
"type-inference",
|
|
10
|
+
"javascript",
|
|
11
|
+
"abstract-interpretation",
|
|
12
|
+
"type-system"
|
|
13
|
+
],
|
|
5
14
|
"main": "src/index.ts",
|
|
6
15
|
"exports": {
|
|
7
|
-
".": "./src/index.ts"
|
|
16
|
+
".": "./src/index.ts",
|
|
17
|
+
"./exec": "./src/algebra/exec/index.ts"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/nudojs/nudo.git",
|
|
22
|
+
"directory": "packages/core"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/nudojs/nudo/tree/main/packages/core",
|
|
25
|
+
"bugs": "https://github.com/nudojs/nudo/issues",
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public",
|
|
28
|
+
"provenance": true,
|
|
29
|
+
"registry": "https://registry.npmjs.org"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@babel/parser": "^7.29.0",
|
|
33
|
+
"@babel/types": "^7.29.0"
|
|
8
34
|
},
|
|
9
35
|
"scripts": {
|
|
10
36
|
"build": "tsup src/index.ts --format esm --dts"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { T, getFnSig, isFnSig, typeValueEquals, typeValueToString } from "../index.ts";
|
|
3
|
+
|
|
4
|
+
describe("T.fnSig with impl", () => {
|
|
5
|
+
it("creates fnSig without impl (backward compatible)", () => {
|
|
6
|
+
const fn = T.fnSig([T.number], T.string);
|
|
7
|
+
expect(isFnSig(fn)).toBe(true);
|
|
8
|
+
const sig = getFnSig(fn)!;
|
|
9
|
+
expect(sig.impl).toBeUndefined();
|
|
10
|
+
expect(typeValueEquals(sig.returnType, T.string)).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("creates fnSig with impl", () => {
|
|
14
|
+
const fn = T.fnSig([T.number], T.number, T.never, (args) => {
|
|
15
|
+
if (args[0].kind === "literal" && typeof args[0].value === "number") {
|
|
16
|
+
return T.literal(args[0].value * 2);
|
|
17
|
+
}
|
|
18
|
+
return undefined;
|
|
19
|
+
});
|
|
20
|
+
expect(isFnSig(fn)).toBe(true);
|
|
21
|
+
const sig = getFnSig(fn)!;
|
|
22
|
+
expect(sig.impl).toBeDefined();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("impl returns concrete value for literal args", () => {
|
|
26
|
+
const fn = T.fnSig([T.number], T.number, T.never, (args) => {
|
|
27
|
+
if (args[0].kind === "literal" && typeof args[0].value === "number") {
|
|
28
|
+
return T.literal(args[0].value * 2);
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
});
|
|
32
|
+
const sig = getFnSig(fn)!;
|
|
33
|
+
const result = sig.impl!([T.literal(5)]);
|
|
34
|
+
expect(result).toBeDefined();
|
|
35
|
+
expect(typeValueEquals(result!, T.literal(10))).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("impl returns undefined for symbolic args (fallback to returnType)", () => {
|
|
39
|
+
const fn = T.fnSig([T.number], T.number, T.never, (args) => {
|
|
40
|
+
if (args[0].kind === "literal" && typeof args[0].value === "number") {
|
|
41
|
+
return T.literal(args[0].value * 2);
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
44
|
+
});
|
|
45
|
+
const sig = getFnSig(fn)!;
|
|
46
|
+
const result = sig.impl!([T.number]);
|
|
47
|
+
expect(result).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("typeValueToString still works for fnSig with impl", () => {
|
|
51
|
+
const fn = T.fnSig([T.number, T.string], T.boolean, T.never, () => T.literal(true));
|
|
52
|
+
const str = typeValueToString(fn);
|
|
53
|
+
expect(str).toContain("number");
|
|
54
|
+
expect(str).toContain("string");
|
|
55
|
+
expect(str).toContain("boolean");
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { T, typeValueEquals } from "../type-value.ts";
|
|
3
|
+
import { dispatchBinaryOp, dispatchMethod, dispatchProperty } from "../ops.ts";
|
|
4
|
+
import { createTemplate, isTemplate } from "../refinements/template.ts";
|
|
5
|
+
import { createRange } from "../refinements/range.ts";
|
|
6
|
+
|
|
7
|
+
describe("dispatchBinaryOp", () => {
|
|
8
|
+
describe("refined fallback", () => {
|
|
9
|
+
it("falls back to base type when no ops defined", () => {
|
|
10
|
+
const odd = T.refine(T.number, { name: "odd", meta: {} });
|
|
11
|
+
const result = dispatchBinaryOp("+", odd, T.literal(1));
|
|
12
|
+
expect(result.kind).toBe("primitive");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("uses custom ops when defined", () => {
|
|
16
|
+
const odd = T.refine(T.number, {
|
|
17
|
+
name: "odd",
|
|
18
|
+
meta: {},
|
|
19
|
+
ops: {
|
|
20
|
+
"%"(self, other) {
|
|
21
|
+
if (other.kind === "literal" && other.value === 2) return T.literal(1);
|
|
22
|
+
return undefined;
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
const result = dispatchBinaryOp("%", odd, T.literal(2));
|
|
27
|
+
expect(typeValueEquals(result, T.literal(1))).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("falls back when custom op returns undefined", () => {
|
|
31
|
+
const odd = T.refine(T.number, {
|
|
32
|
+
name: "odd",
|
|
33
|
+
meta: {},
|
|
34
|
+
ops: {
|
|
35
|
+
"%"(self, other) {
|
|
36
|
+
if (other.kind === "literal" && other.value === 2) return T.literal(1);
|
|
37
|
+
return undefined;
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
// Ops.mod 已删:dispatchBinaryOp 的 % 最终落到 unknown;
|
|
42
|
+
// evaluator 侧由 evalBinaryValue 的 numberish 兜底补 number。
|
|
43
|
+
const result = dispatchBinaryOp("%", odd, T.literal(3));
|
|
44
|
+
expect(result.kind).toBe("unknown");
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("template string via add", () => {
|
|
49
|
+
it("literal string + T.string produces template", () => {
|
|
50
|
+
const result = dispatchBinaryOp("+", T.literal("xy"), T.string);
|
|
51
|
+
expect(isTemplate(result)).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("T.string + literal string produces template", () => {
|
|
55
|
+
const result = dispatchBinaryOp("+", T.string, T.literal("!"));
|
|
56
|
+
expect(isTemplate(result)).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("template + template concatenates", () => {
|
|
60
|
+
const left = createTemplate([T.literal("a"), T.string]);
|
|
61
|
+
const right = createTemplate([T.literal("b"), T.string]);
|
|
62
|
+
const result = dispatchBinaryOp("+", left, right);
|
|
63
|
+
expect(isTemplate(result)).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("two literal strings still produce literal", () => {
|
|
67
|
+
const result = dispatchBinaryOp("+", T.literal("a"), T.literal("b"));
|
|
68
|
+
expect(typeValueEquals(result, T.literal("ab"))).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("T.string + T.string stays T.string", () => {
|
|
72
|
+
const result = dispatchBinaryOp("+", T.string, T.string);
|
|
73
|
+
expect(typeValueEquals(result, T.string)).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("range comparison ops", () => {
|
|
78
|
+
it("range(min:0) >= 0 is true", () => {
|
|
79
|
+
const r = createRange({ min: 0 });
|
|
80
|
+
const result = dispatchBinaryOp(">=", r, T.literal(0));
|
|
81
|
+
expect(typeValueEquals(result, T.literal(true))).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("range(min:0) >= -1 is true", () => {
|
|
85
|
+
const r = createRange({ min: 0 });
|
|
86
|
+
const result = dispatchBinaryOp(">=", r, T.literal(-1));
|
|
87
|
+
expect(typeValueEquals(result, T.literal(true))).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("range(max:100) > 200 is false", () => {
|
|
91
|
+
const r = createRange({ max: 100 });
|
|
92
|
+
const result = dispatchBinaryOp(">", r, T.literal(200));
|
|
93
|
+
expect(typeValueEquals(result, T.literal(false))).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("range(min:0, max:100) > 50 is uncertain (boolean)", () => {
|
|
97
|
+
const r = createRange({ min: 0, max: 100 });
|
|
98
|
+
const result = dispatchBinaryOp(">", r, T.literal(50));
|
|
99
|
+
expect(typeValueEquals(result, T.boolean)).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("range(max:10) <= 10 is true", () => {
|
|
103
|
+
const r = createRange({ max: 10 });
|
|
104
|
+
const result = dispatchBinaryOp("<=", r, T.literal(10));
|
|
105
|
+
expect(typeValueEquals(result, T.literal(true))).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("range(min:5) < 3 is false", () => {
|
|
109
|
+
const r = createRange({ min: 5 });
|
|
110
|
+
const result = dispatchBinaryOp("<", r, T.literal(3));
|
|
111
|
+
expect(typeValueEquals(result, T.literal(false))).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe("dispatchMethod", () => {
|
|
117
|
+
describe("template startsWith", () => {
|
|
118
|
+
it("known prefix matches", () => {
|
|
119
|
+
const tmpl = createTemplate([T.literal("xy"), T.string]);
|
|
120
|
+
const result = dispatchMethod(tmpl, "startsWith", [T.literal("x")]);
|
|
121
|
+
expect(result).toBeDefined();
|
|
122
|
+
expect(typeValueEquals(result!, T.literal(true))).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("known prefix full match", () => {
|
|
126
|
+
const tmpl = createTemplate([T.literal("xy"), T.string]);
|
|
127
|
+
const result = dispatchMethod(tmpl, "startsWith", [T.literal("xy")]);
|
|
128
|
+
expect(typeValueEquals(result!, T.literal(true))).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("known prefix mismatch", () => {
|
|
132
|
+
const tmpl = createTemplate([T.literal("xy"), T.string]);
|
|
133
|
+
const result = dispatchMethod(tmpl, "startsWith", [T.literal("a")]);
|
|
134
|
+
expect(typeValueEquals(result!, T.literal(false))).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("uncertain when search extends beyond prefix", () => {
|
|
138
|
+
const tmpl = createTemplate([T.literal("xy"), T.string]);
|
|
139
|
+
const result = dispatchMethod(tmpl, "startsWith", [T.literal("xyz")]);
|
|
140
|
+
expect(result).toBeUndefined();
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("template endsWith", () => {
|
|
145
|
+
it("known suffix matches", () => {
|
|
146
|
+
const tmpl = createTemplate([T.string, T.literal("!")]);
|
|
147
|
+
const result = dispatchMethod(tmpl, "endsWith", [T.literal("!")]);
|
|
148
|
+
expect(typeValueEquals(result!, T.literal(true))).toBe(true);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("known suffix mismatch", () => {
|
|
152
|
+
const tmpl = createTemplate([T.string, T.literal("!")]);
|
|
153
|
+
const result = dispatchMethod(tmpl, "endsWith", [T.literal("?")]);
|
|
154
|
+
expect(typeValueEquals(result!, T.literal(false))).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe("template includes", () => {
|
|
159
|
+
it("known fixed text includes substring", () => {
|
|
160
|
+
const tmpl = createTemplate([T.literal("hello"), T.string, T.literal("world")]);
|
|
161
|
+
const result = dispatchMethod(tmpl, "includes", [T.literal("hello")]);
|
|
162
|
+
expect(typeValueEquals(result!, T.literal(true))).toBe(true);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("uncertain when not in fixed text", () => {
|
|
166
|
+
const tmpl = createTemplate([T.literal("hello"), T.string]);
|
|
167
|
+
const result = dispatchMethod(tmpl, "includes", [T.literal("xyz")]);
|
|
168
|
+
expect(result).toBeUndefined();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("returns undefined for unknown methods", () => {
|
|
173
|
+
const tmpl = createTemplate([T.literal("x"), T.string]);
|
|
174
|
+
const result = dispatchMethod(tmpl, "unknownMethod", []);
|
|
175
|
+
expect(result).toBeUndefined();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("returns undefined for non-refined types", () => {
|
|
179
|
+
const result = dispatchMethod(T.string, "startsWith", [T.literal("x")]);
|
|
180
|
+
expect(result).toBeUndefined();
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe("dispatchProperty", () => {
|
|
185
|
+
it("template length returns range", () => {
|
|
186
|
+
const tmpl = createTemplate([T.literal("xy"), T.string]);
|
|
187
|
+
const result = dispatchProperty(tmpl, "length");
|
|
188
|
+
expect(result).toBeDefined();
|
|
189
|
+
expect(result!.kind).toBe("refined");
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("returns undefined for non-refined types", () => {
|
|
193
|
+
const result = dispatchProperty(T.string, "length");
|
|
194
|
+
expect(result).toBeUndefined();
|
|
195
|
+
});
|
|
196
|
+
});
|
|
@@ -21,19 +21,7 @@ describe("Ops.add", () => {
|
|
|
21
21
|
});
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
it("computes literal arithmetic", () => {
|
|
26
|
-
expect(typeValueEquals(Ops.sub(T.literal(5), T.literal(3)), T.literal(2))).toBe(true);
|
|
27
|
-
expect(typeValueEquals(Ops.mul(T.literal(3), T.literal(4)), T.literal(12))).toBe(true);
|
|
28
|
-
expect(typeValueEquals(Ops.div(T.literal(10), T.literal(2)), T.literal(5))).toBe(true);
|
|
29
|
-
expect(typeValueEquals(Ops.mod(T.literal(7), T.literal(3)), T.literal(1))).toBe(true);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("returns number for abstract operands", () => {
|
|
33
|
-
expect(typeValueEquals(Ops.sub(T.number, T.number), T.number)).toBe(true);
|
|
34
|
-
expect(typeValueEquals(Ops.mul(T.number, T.literal(2)), T.number)).toBe(true);
|
|
35
|
-
});
|
|
36
|
-
});
|
|
24
|
+
// sub/mul/div/mod 已删除:数值路径唯一真理在 algebra;非数应得 unknown。
|
|
37
25
|
|
|
38
26
|
describe("Ops.strictEq / strictNeq", () => {
|
|
39
27
|
it("compares literals", () => {
|
|
@@ -60,16 +48,6 @@ describe("Ops.gt / lt / gte / lte", () => {
|
|
|
60
48
|
});
|
|
61
49
|
});
|
|
62
50
|
|
|
63
|
-
describe("Ops.typeof_", () => {
|
|
64
|
-
it("returns literal typeof for known types", () => {
|
|
65
|
-
expect(typeValueEquals(Ops.typeof_(T.literal(1)), T.literal("number"))).toBe(true);
|
|
66
|
-
expect(typeValueEquals(Ops.typeof_(T.literal("hi")), T.literal("string"))).toBe(true);
|
|
67
|
-
expect(typeValueEquals(Ops.typeof_(T.literal(null)), T.literal("object"))).toBe(true);
|
|
68
|
-
expect(typeValueEquals(Ops.typeof_(T.number), T.literal("number"))).toBe(true);
|
|
69
|
-
expect(typeValueEquals(Ops.typeof_(T.object({})), T.literal("object"))).toBe(true);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
51
|
describe("Ops.not", () => {
|
|
74
52
|
it("negates literal booleans", () => {
|
|
75
53
|
expect(typeValueEquals(Ops.not(T.literal(true)), T.literal(false))).toBe(true);
|
|
@@ -87,15 +65,7 @@ describe("Ops.not", () => {
|
|
|
87
65
|
});
|
|
88
66
|
});
|
|
89
67
|
|
|
90
|
-
|
|
91
|
-
it("negates literal number", () => {
|
|
92
|
-
expect(typeValueEquals(Ops.neg(T.literal(5)), T.literal(-5))).toBe(true);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("returns number for abstract", () => {
|
|
96
|
-
expect(typeValueEquals(Ops.neg(T.number), T.number)).toBe(true);
|
|
97
|
-
});
|
|
98
|
-
});
|
|
68
|
+
// typeof_ / neg 已迁入 algebra surface(tryAbsUnary)
|
|
99
69
|
|
|
100
70
|
describe("applyBinaryOp", () => {
|
|
101
71
|
it("dispatches to correct op", () => {
|
|
@@ -103,7 +73,12 @@ describe("applyBinaryOp", () => {
|
|
|
103
73
|
expect(typeValueEquals(applyBinaryOp("===", T.literal(1), T.literal(1)), T.literal(true))).toBe(true);
|
|
104
74
|
});
|
|
105
75
|
|
|
106
|
-
it("returns unknown for unsupported
|
|
76
|
+
it("returns unknown for deleted / unsupported ops", () => {
|
|
107
77
|
expect(applyBinaryOp("**", T.number, T.number)).toEqual(T.unknown);
|
|
78
|
+
// sub/mul/div/mod 已从 Ops 删除,代数是唯一路径
|
|
79
|
+
expect(applyBinaryOp("-", T.literal(5), T.literal(3))).toEqual(T.unknown);
|
|
80
|
+
expect(applyBinaryOp("*", T.number, T.number)).toEqual(T.unknown);
|
|
81
|
+
expect(applyBinaryOp("/", T.literal(10), T.literal(2))).toEqual(T.unknown);
|
|
82
|
+
expect(applyBinaryOp("%", T.literal(7), T.literal(3))).toEqual(T.unknown);
|
|
108
83
|
});
|
|
109
84
|
});
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
T,
|
|
4
|
+
typeValueEquals,
|
|
5
|
+
isSubtypeOf,
|
|
6
|
+
typeValueToString,
|
|
7
|
+
deepCloneTypeValue,
|
|
8
|
+
getPrimitiveTypeOf,
|
|
9
|
+
getRefinedBase,
|
|
10
|
+
} from "../type-value.ts";
|
|
11
|
+
import { createTemplate, isTemplate, getTemplateParts, getKnownPrefix, getKnownSuffix } from "../refinements/template.ts";
|
|
12
|
+
import { createRange, isRange, getRangeMeta } from "../refinements/range.ts";
|
|
13
|
+
|
|
14
|
+
describe("Refined TypeValue", () => {
|
|
15
|
+
describe("T.refine", () => {
|
|
16
|
+
it("creates a refined type", () => {
|
|
17
|
+
const odd = T.refine(T.number, { name: "odd", meta: {} });
|
|
18
|
+
expect(odd.kind).toBe("refined");
|
|
19
|
+
if (odd.kind === "refined") {
|
|
20
|
+
expect(odd.base).toBe(T.number);
|
|
21
|
+
expect(odd.refinement.name).toBe("odd");
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("typeValueEquals", () => {
|
|
27
|
+
it("equal refined types with same name and base", () => {
|
|
28
|
+
const a = T.refine(T.number, { name: "odd", meta: {} });
|
|
29
|
+
const b = T.refine(T.number, { name: "odd", meta: {} });
|
|
30
|
+
expect(typeValueEquals(a, b)).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("different name means not equal", () => {
|
|
34
|
+
const a = T.refine(T.number, { name: "odd", meta: {} });
|
|
35
|
+
const b = T.refine(T.number, { name: "even", meta: {} });
|
|
36
|
+
expect(typeValueEquals(a, b)).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("different base means not equal", () => {
|
|
40
|
+
const a = T.refine(T.number, { name: "x", meta: {} });
|
|
41
|
+
const b = T.refine(T.string, { name: "x", meta: {} });
|
|
42
|
+
expect(typeValueEquals(a, b)).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("isSubtypeOf", () => {
|
|
47
|
+
it("refined is subtype of its base", () => {
|
|
48
|
+
const odd = T.refine(T.number, { name: "odd", meta: {} });
|
|
49
|
+
expect(isSubtypeOf(odd, T.number)).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("refined is subtype of unknown", () => {
|
|
53
|
+
const odd = T.refine(T.number, { name: "odd", meta: {} });
|
|
54
|
+
expect(isSubtypeOf(odd, T.unknown)).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("literal is subtype of refined when check passes", () => {
|
|
58
|
+
const odd = T.refine(T.number, {
|
|
59
|
+
name: "odd",
|
|
60
|
+
meta: {},
|
|
61
|
+
check: (v) => typeof v === "number" && v % 2 !== 0,
|
|
62
|
+
});
|
|
63
|
+
expect(isSubtypeOf(T.literal(3), odd)).toBe(true);
|
|
64
|
+
expect(isSubtypeOf(T.literal(4), odd)).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("nested refined is subtype of outer base", () => {
|
|
68
|
+
const integer = T.refine(T.number, { name: "integer", meta: {} });
|
|
69
|
+
const positiveInt = T.refine(integer, { name: "positive integer", meta: {} });
|
|
70
|
+
expect(isSubtypeOf(positiveInt, T.number)).toBe(true);
|
|
71
|
+
expect(isSubtypeOf(positiveInt, integer)).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("typeValueToString", () => {
|
|
76
|
+
it("uses refinement name", () => {
|
|
77
|
+
const odd = T.refine(T.number, { name: "odd", meta: {} });
|
|
78
|
+
expect(typeValueToString(odd)).toBe("odd");
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("getPrimitiveTypeOf", () => {
|
|
83
|
+
it("delegates to base", () => {
|
|
84
|
+
const odd = T.refine(T.number, { name: "odd", meta: {} });
|
|
85
|
+
expect(getPrimitiveTypeOf(odd)).toBe("number");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("handles nested refined", () => {
|
|
89
|
+
const inner = T.refine(T.string, { name: "inner", meta: {} });
|
|
90
|
+
const outer = T.refine(inner, { name: "outer", meta: {} });
|
|
91
|
+
expect(getPrimitiveTypeOf(outer)).toBe("string");
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("getRefinedBase", () => {
|
|
96
|
+
it("unwraps to primitive", () => {
|
|
97
|
+
const inner = T.refine(T.number, { name: "a", meta: {} });
|
|
98
|
+
const outer = T.refine(inner, { name: "b", meta: {} });
|
|
99
|
+
expect(getRefinedBase(outer)).toBe(T.number);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("returns non-refined as-is", () => {
|
|
103
|
+
expect(getRefinedBase(T.string)).toBe(T.string);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("deepCloneTypeValue", () => {
|
|
108
|
+
it("clones refined type", () => {
|
|
109
|
+
const odd = T.refine(T.number, { name: "odd", meta: { x: 1 } });
|
|
110
|
+
const cloned = deepCloneTypeValue(odd);
|
|
111
|
+
expect(typeValueEquals(cloned, odd)).toBe(true);
|
|
112
|
+
expect(cloned).not.toBe(odd);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("Template String", () => {
|
|
118
|
+
it("creates template from parts", () => {
|
|
119
|
+
const tmpl = createTemplate([T.literal("xy"), T.string]);
|
|
120
|
+
expect(isTemplate(tmpl)).toBe(true);
|
|
121
|
+
expect(tmpl.kind).toBe("refined");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("collapses to literal when all parts are literal", () => {
|
|
125
|
+
const tmpl = createTemplate([T.literal("hello"), T.literal(" world")]);
|
|
126
|
+
expect(tmpl.kind).toBe("literal");
|
|
127
|
+
if (tmpl.kind === "literal") expect(tmpl.value).toBe("hello world");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("collapses to T.string when only T.string", () => {
|
|
131
|
+
const tmpl = createTemplate([T.string]);
|
|
132
|
+
expect(tmpl.kind).toBe("primitive");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("getKnownPrefix extracts leading literals", () => {
|
|
136
|
+
expect(getKnownPrefix([T.literal("xy"), T.string])).toBe("xy");
|
|
137
|
+
expect(getKnownPrefix([T.string, T.literal("z")])).toBe("");
|
|
138
|
+
expect(getKnownPrefix([T.literal("a"), T.literal("b"), T.string])).toBe("ab");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("getKnownSuffix extracts trailing literals", () => {
|
|
142
|
+
expect(getKnownSuffix([T.string, T.literal("!")])).toBe("!");
|
|
143
|
+
expect(getKnownSuffix([T.literal("x"), T.string])).toBe("");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("getTemplateParts returns parts", () => {
|
|
147
|
+
const tmpl = createTemplate([T.literal("x"), T.string]);
|
|
148
|
+
const parts = getTemplateParts(tmpl);
|
|
149
|
+
expect(parts).toHaveLength(2);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("toString formats as template literal", () => {
|
|
153
|
+
const tmpl = createTemplate([T.literal("xy"), T.string]);
|
|
154
|
+
expect(typeValueToString(tmpl)).toBe("`xy${string}`");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("is subtype of T.string", () => {
|
|
158
|
+
const tmpl = createTemplate([T.literal("x"), T.string]);
|
|
159
|
+
expect(isSubtypeOf(tmpl, T.string)).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("check validates concrete strings", () => {
|
|
163
|
+
const tmpl = createTemplate([T.literal("xy"), T.string]);
|
|
164
|
+
if (tmpl.kind === "refined" && tmpl.refinement.check) {
|
|
165
|
+
expect(tmpl.refinement.check("xyhello")).toBe(true);
|
|
166
|
+
expect(tmpl.refinement.check("xy")).toBe(true);
|
|
167
|
+
expect(tmpl.refinement.check("ahello")).toBe(false);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe("Range", () => {
|
|
173
|
+
it("creates range type", () => {
|
|
174
|
+
const r = createRange({ min: 0 });
|
|
175
|
+
expect(isRange(r)).toBe(true);
|
|
176
|
+
expect(r.kind).toBe("refined");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("collapses to literal when min === max", () => {
|
|
180
|
+
const r = createRange({ min: 5, max: 5 });
|
|
181
|
+
expect(r.kind).toBe("literal");
|
|
182
|
+
if (r.kind === "literal") expect(r.value).toBe(5);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("getRangeMeta returns metadata", () => {
|
|
186
|
+
const r = createRange({ min: 0, max: 100, integer: true });
|
|
187
|
+
const meta = getRangeMeta(r);
|
|
188
|
+
expect(meta).toEqual({ min: 0, max: 100, integer: true });
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("is subtype of T.number", () => {
|
|
192
|
+
const r = createRange({ min: 0 });
|
|
193
|
+
expect(isSubtypeOf(r, T.number)).toBe(true);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("toString formats with constraints", () => {
|
|
197
|
+
expect(typeValueToString(createRange({ min: 0 }))).toBe("number (>= 0)");
|
|
198
|
+
expect(typeValueToString(createRange({ min: 0, max: 100 }))).toBe("number (>= 0, <= 100)");
|
|
199
|
+
expect(typeValueToString(createRange({ integer: true }))).toBe("integer");
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("check validates concrete numbers", () => {
|
|
203
|
+
const r = createRange({ min: 0, max: 10 });
|
|
204
|
+
if (r.kind === "refined" && r.refinement.check) {
|
|
205
|
+
expect(r.refinement.check(5)).toBe(true);
|
|
206
|
+
expect(r.refinement.check(-1)).toBe(false);
|
|
207
|
+
expect(r.refinement.check(11)).toBe(false);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
});
|