@nudojs/core 0.2.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.
Files changed (115) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +26 -0
  3. package/dist/index.d.ts +1601 -12
  4. package/dist/index.js +9657 -110
  5. package/package.json +18 -2
  6. package/src/__tests__/fn-sig-impl.test.ts +57 -0
  7. package/src/__tests__/ops-refined.test.ts +3 -1
  8. package/src/__tests__/ops.test.ts +8 -33
  9. package/src/__tests__/type-value.test.ts +106 -0
  10. package/src/algebra/__tests__/abs-native.test.ts +176 -0
  11. package/src/algebra/__tests__/arithmetic.test.ts +170 -0
  12. package/src/algebra/__tests__/ast-eval.test.ts +154 -0
  13. package/src/algebra/__tests__/bridge.test.ts +160 -0
  14. package/src/algebra/__tests__/call-budget.test.ts +117 -0
  15. package/src/algebra/__tests__/check-case-consistency.test.ts +69 -0
  16. package/src/algebra/__tests__/check-gold.test.ts +290 -0
  17. package/src/algebra/__tests__/check-json.test.ts +57 -0
  18. package/src/algebra/__tests__/check-mini-repo.test.ts +49 -0
  19. package/src/algebra/__tests__/check-real-commander.test.ts +43 -0
  20. package/src/algebra/__tests__/check-real-packages.test.ts +165 -0
  21. package/src/algebra/__tests__/check-recall-gold.test.ts +1024 -0
  22. package/src/algebra/__tests__/check-require-dep-memo.test.ts +45 -0
  23. package/src/algebra/__tests__/check-return-contract.test.ts +90 -0
  24. package/src/algebra/__tests__/check-shape-gold.test.ts +303 -0
  25. package/src/algebra/__tests__/check-source-memo.test.ts +204 -0
  26. package/src/algebra/__tests__/check.test.ts +69 -0
  27. package/src/algebra/__tests__/denote.test.ts +77 -0
  28. package/src/algebra/__tests__/diagnostics.test.ts +64 -0
  29. package/src/algebra/__tests__/differential.test.ts +124 -0
  30. package/src/algebra/__tests__/div-mod.test.ts +38 -0
  31. package/src/algebra/__tests__/example0.test.ts +103 -0
  32. package/src/algebra/__tests__/exec-analyze-throws.test.ts +60 -0
  33. package/src/algebra/__tests__/exec-async.test.ts +46 -0
  34. package/src/algebra/__tests__/exec-bpath.test.ts +193 -0
  35. package/src/algebra/__tests__/exec-class.test.ts +56 -0
  36. package/src/algebra/__tests__/exec-e2e.test.ts +97 -0
  37. package/src/algebra/__tests__/exec-generator.test.ts +53 -0
  38. package/src/algebra/__tests__/exec-inherit-default.test.ts +111 -0
  39. package/src/algebra/__tests__/exec-optional-nested.test.ts +87 -0
  40. package/src/algebra/__tests__/exec-rest.test.ts +55 -0
  41. package/src/algebra/__tests__/exec-spread-forof.test.ts +85 -0
  42. package/src/algebra/__tests__/exec-static-computed.test.ts +61 -0
  43. package/src/algebra/__tests__/exec-switch.test.ts +74 -0
  44. package/src/algebra/__tests__/exec-trycatch.test.ts +71 -0
  45. package/src/algebra/__tests__/fn-fp-memo.test.ts +166 -0
  46. package/src/algebra/__tests__/fn-fp-multidecl.test.ts +49 -0
  47. package/src/algebra/__tests__/format.test.ts +34 -0
  48. package/src/algebra/__tests__/generalize-memo.test.ts +285 -0
  49. package/src/algebra/__tests__/generalize.test.ts +113 -0
  50. package/src/algebra/__tests__/hash-source.test.ts +19 -0
  51. package/src/algebra/__tests__/hof.test.ts +80 -0
  52. package/src/algebra/__tests__/inlay.test.ts +30 -0
  53. package/src/algebra/__tests__/language.test.ts +77 -0
  54. package/src/algebra/__tests__/leq.test.ts +109 -0
  55. package/src/algebra/__tests__/load-deps-fp-memo.test.ts +132 -0
  56. package/src/algebra/__tests__/methods.test.ts +37 -0
  57. package/src/algebra/__tests__/node-types.test.ts +26 -0
  58. package/src/algebra/__tests__/nudo-constraints.ts +46 -0
  59. package/src/algebra/__tests__/objects.test.ts +117 -0
  60. package/src/algebra/__tests__/parse-source-cache.test.ts +42 -0
  61. package/src/algebra/__tests__/refine-import.test.ts +109 -0
  62. package/src/algebra/__tests__/stable-source-key-block.test.ts +23 -0
  63. package/src/algebra/__tests__/stable-source-key.test.ts +31 -0
  64. package/src/algebra/__tests__/surface.test.ts +41 -0
  65. package/src/algebra/__tests__/this-inherit.test.ts +130 -0
  66. package/src/algebra/abs-fn.ts +49 -0
  67. package/src/algebra/abs-modules.ts +139 -0
  68. package/src/algebra/abs.ts +206 -0
  69. package/src/algebra/arithmetic.ts +691 -0
  70. package/src/algebra/ast-eval.ts +1763 -0
  71. package/src/algebra/bridge.ts +350 -0
  72. package/src/algebra/builtins.ts +373 -0
  73. package/src/algebra/check-report.ts +150 -0
  74. package/src/algebra/check.ts +1867 -0
  75. package/src/algebra/constraint.ts +304 -0
  76. package/src/algebra/denote.ts +166 -0
  77. package/src/algebra/diagnostics.ts +210 -0
  78. package/src/algebra/exec/call.ts +29 -0
  79. package/src/algebra/exec/calls.ts +96 -0
  80. package/src/algebra/exec/class-registry.ts +29 -0
  81. package/src/algebra/exec/class.ts +329 -0
  82. package/src/algebra/exec/index.ts +6 -0
  83. package/src/algebra/exec/member-diag.ts +141 -0
  84. package/src/algebra/exec/run.ts +341 -0
  85. package/src/algebra/exec/runtime.ts +583 -0
  86. package/src/algebra/exec/transpile.ts +993 -0
  87. package/src/algebra/fn-fp.ts +294 -0
  88. package/src/algebra/format.ts +91 -0
  89. package/src/algebra/generalize.ts +776 -0
  90. package/src/algebra/hash-source.ts +27 -0
  91. package/src/algebra/index.ts +34 -0
  92. package/src/algebra/inlay.ts +198 -0
  93. package/src/algebra/language.ts +289 -0
  94. package/src/algebra/leak.ts +68 -0
  95. package/src/algebra/leq.ts +301 -0
  96. package/src/algebra/load-deps-fp.ts +97 -0
  97. package/src/algebra/methods.ts +128 -0
  98. package/src/algebra/objects.ts +251 -0
  99. package/src/algebra/parse-source.ts +64 -0
  100. package/src/algebra/phi.ts +42 -0
  101. package/src/algebra/pred.ts +395 -0
  102. package/src/algebra/refine.ts +227 -0
  103. package/src/algebra/stable-source-key.ts +81 -0
  104. package/src/algebra/surface.ts +206 -0
  105. package/src/algebra/template.ts +143 -0
  106. package/src/algebra/term.ts +119 -0
  107. package/src/environment.ts +5 -0
  108. package/src/index.ts +33 -0
  109. package/src/mock-helpers.ts +166 -0
  110. package/src/ops.ts +44 -63
  111. package/src/refinements/template-predicates.ts +11 -0
  112. package/src/refinements/template.ts +1 -3
  113. package/src/strip-types.ts +236 -0
  114. package/src/type-value.ts +153 -12
  115. package/src/refinements/index.ts +0 -2
@@ -0,0 +1,304 @@
1
+ /**
2
+ * 约束模板构建器(参数无关)。
3
+ *
4
+ * number().gt(0) → 占位项 self 上的 Pred
5
+ * number().int().ge(0) → 整数 + 下界
6
+ * string().min(1) → 非空串(长度)
7
+ * array(number().gt(0)) → 元素约束
8
+ * shape({ id: number().gt(0) }) → object 形状约束
9
+ * instantiate("ms") → Pred ms > 0 / u.id > 0 ∧ …
10
+ *
11
+ * 在 *.nudo.js 里执行;不是 zod 绑定,是我们自己的运行时 API。
12
+ * 不需要 interface/type 语法——契约用 JS 表达式声明。
13
+ */
14
+
15
+ import type { Pred, PrimName } from "./pred.ts";
16
+ import { and, gt, ge, lt, le, ptypeof } from "./pred.ts";
17
+ import { v as termVar, lit, app as termApp, type Term } from "./term.ts";
18
+ import type { Abs } from "./abs.ts";
19
+ import { abs } from "./abs.ts";
20
+
21
+ /** 模板占位项;instantiate 时换成真实参数名 */
22
+ export const SELF = "__nudo_self__";
23
+
24
+ function selfTerm(): Term {
25
+ return termVar(SELF);
26
+ }
27
+
28
+ /** 字段访问项:u.id */
29
+ export function getTerm(obj: Term, key: string): Term {
30
+ return termApp("get", [obj, lit(key)]);
31
+ }
32
+
33
+ /** 长度项:length(u) */
34
+ export function lenTerm(t: Term): Term {
35
+ return termApp("length", [t]);
36
+ }
37
+
38
+ export type NudoField = {
39
+ constraint: NudoConstraint;
40
+ optional?: boolean;
41
+ };
42
+
43
+ export type NudoConstraint = {
44
+ readonly __nudoConstraint: true;
45
+ readonly prim?: PrimName;
46
+ readonly preds: Pred[];
47
+ /** object 形状:字段名 → 嵌套约束 */
48
+ readonly fields?: Record<string, NudoField>;
49
+ /** array 元素约束 */
50
+ readonly element?: NudoConstraint;
51
+ /** 整数(number 链式 .int()) */
52
+ readonly int?: boolean;
53
+ /** 该约束整体可选(shape 字段用;不用 optional 以免与链式方法撞名) */
54
+ readonly isOptional?: boolean;
55
+ };
56
+
57
+ function isConstraint(x: unknown): x is NudoConstraint {
58
+ return (
59
+ !!x &&
60
+ typeof x === "object" &&
61
+ (x as NudoConstraint).__nudoConstraint === true
62
+ );
63
+ }
64
+
65
+ export function isNudoConstraint(x: unknown): x is NudoConstraint {
66
+ return isConstraint(x);
67
+ }
68
+
69
+ /** 链式约束:不可变,每次 .gt() 返回新对象 */
70
+ export type ConstraintBuilder = NudoConstraint & {
71
+ gt(n: number): ConstraintBuilder;
72
+ ge(n: number): ConstraintBuilder;
73
+ lt(n: number): ConstraintBuilder;
74
+ le(n: number): ConstraintBuilder;
75
+ /** number:要求整数 */
76
+ int(): ConstraintBuilder;
77
+ /** string:长度下界 */
78
+ min(n: number): ConstraintBuilder;
79
+ /** string:长度上界 */
80
+ max(n: number): ConstraintBuilder;
81
+ /** string:精确长度 */
82
+ length(n: number): ConstraintBuilder;
83
+ /** 字段可选(仅在 shape 内有意义) */
84
+ optional(): ConstraintBuilder;
85
+ };
86
+
87
+ function makeBuilder(
88
+ prim: PrimName | undefined,
89
+ preds: Pred[],
90
+ extra?: {
91
+ fields?: Record<string, NudoField>;
92
+ element?: NudoConstraint;
93
+ int?: boolean;
94
+ optional?: boolean;
95
+ },
96
+ ): ConstraintBuilder {
97
+ const fields = extra?.fields;
98
+ const element = extra?.element;
99
+ const isInt = extra?.int;
100
+ const optional = extra?.optional;
101
+ const base: NudoConstraint = {
102
+ __nudoConstraint: true,
103
+ ...(prim ? { prim } : {}),
104
+ preds: [...preds],
105
+ ...(fields ? { fields } : {}),
106
+ ...(element ? { element } : {}),
107
+ ...(isInt ? { int: true } : {}),
108
+ ...(optional ? { isOptional: true } : {}),
109
+ };
110
+ const add = (p: Pred): ConstraintBuilder =>
111
+ makeBuilder(prim, [...preds, p], extra);
112
+ return Object.assign(Object.create(null), base, {
113
+ gt: (n: number) => add(gt(selfTerm(), lit(n))),
114
+ ge: (n: number) => add(ge(selfTerm(), lit(n))),
115
+ lt: (n: number) => add(lt(selfTerm(), lit(n))),
116
+ le: (n: number) => add(le(selfTerm(), lit(n))),
117
+ int: () => makeBuilder(prim ?? "number", preds, { ...extra, int: true }),
118
+ min: (n: number) => add(ge(lenTerm(selfTerm()), lit(n))),
119
+ max: (n: number) => add(le(lenTerm(selfTerm()), lit(n))),
120
+ length: (n: number) =>
121
+ add(and(ge(lenTerm(selfTerm()), lit(n)), le(lenTerm(selfTerm()), lit(n)))),
122
+ optional: () => makeBuilder(prim, preds, { ...extra, optional: true }),
123
+ }) as ConstraintBuilder;
124
+ }
125
+
126
+ /** number() —— 约束 number 原语 + 后续链式界 */
127
+ export function number(): ConstraintBuilder {
128
+ return makeBuilder("number", []);
129
+ }
130
+
131
+ export function string(): ConstraintBuilder {
132
+ return makeBuilder("string", []);
133
+ }
134
+
135
+ export function boolean(): ConstraintBuilder {
136
+ return makeBuilder("boolean", []);
137
+ }
138
+
139
+ /** array(item) —— 数组,元素满足 item */
140
+ export function array(item: NudoConstraint | ConstraintBuilder): ConstraintBuilder {
141
+ if (!isConstraint(item)) return makeBuilder(undefined, []);
142
+ return makeBuilder(undefined, [], { element: item });
143
+ }
144
+
145
+ /**
146
+ * object 形状约束(契约规范形状,无需 interface):
147
+ *
148
+ * shape({ id: number().gt(0), name: string() })
149
+ */
150
+ export function shape(
151
+ fields: Record<string, NudoConstraint | ConstraintBuilder>,
152
+ ): ConstraintBuilder {
153
+ const mapped: Record<string, NudoField> = {};
154
+ for (const [k, v] of Object.entries(fields)) {
155
+ if (!isConstraint(v)) continue;
156
+ mapped[k] = {
157
+ constraint: v,
158
+ ...(v.isOptional ? { optional: true } : {}),
159
+ };
160
+ }
161
+ return makeBuilder(undefined, [], { fields: mapped });
162
+ }
163
+
164
+ function substTerm(t: Term, paramName: string): Term {
165
+ if (t.op === "var" && t.id === SELF) return termVar(paramName);
166
+ if (t.op === "app" && t.fn === "get" && t.args.length === 2) {
167
+ return getTerm(substTerm(t.args[0]!, paramName), String(
168
+ t.args[1]!.op === "lit" ? t.args[1]!.value : "",
169
+ ));
170
+ }
171
+ return t;
172
+ }
173
+
174
+ function substPred(p: Pred, paramName: string): Pred {
175
+ switch (p.op) {
176
+ case "gt":
177
+ case "ge":
178
+ case "lt":
179
+ case "le":
180
+ case "eq":
181
+ case "ne": {
182
+ return { op: p.op, a: substTerm(p.a, paramName), b: substTerm(p.b, paramName) };
183
+ }
184
+ case "typeof": {
185
+ return { op: "typeof", t: substTerm(p.t, paramName), type: p.type };
186
+ }
187
+ case "and":
188
+ return and(...p.args.map((x) => substPred(x, paramName)));
189
+ default:
190
+ return p;
191
+ }
192
+ }
193
+
194
+ /** 把模板绑定到参数名:self → paramName;shape 展开为字段访问 Pred */
195
+ export function instantiateConstraint(
196
+ c: NudoConstraint,
197
+ paramName: string,
198
+ ): Pred {
199
+ // shape:展开为 and(字段 preds)
200
+ if (c.fields) {
201
+ const parts: Pred[] = [];
202
+ for (const [key, field] of Object.entries(c.fields)) {
203
+ const fieldTerm = getTerm(termVar(paramName), key);
204
+ parts.push(instantiateOnTerm(field.constraint, fieldTerm));
205
+ }
206
+ if (c.prim) parts.push(ptypeof(termVar(paramName), c.prim));
207
+ if (parts.length === 0) return { op: "true" };
208
+ return parts.length === 1 ? parts[0]! : and(...parts);
209
+ }
210
+
211
+ const preds = c.preds.map((p) => substPred(p, paramName));
212
+ // prim 可作为 typeof 约束补上(optional)
213
+ if (c.prim && c.preds.length === 0) {
214
+ return ptypeof(termVar(paramName), c.prim);
215
+ }
216
+ return preds.length === 0 ? { op: "true" } : preds.length === 1 ? preds[0]! : and(...preds);
217
+ }
218
+
219
+ /** 在给定项上实例化约束(shape 字段递归用) */
220
+ function instantiateOnTerm(c: NudoConstraint, t: Term): Pred {
221
+ if (c.fields) {
222
+ const parts: Pred[] = [];
223
+ for (const [key, field] of Object.entries(c.fields)) {
224
+ parts.push(instantiateOnTerm(field.constraint, getTerm(t, key)));
225
+ }
226
+ if (parts.length === 0) return { op: "true" };
227
+ return parts.length === 1 ? parts[0]! : and(...parts);
228
+ }
229
+ const subst = (p: Pred): Pred => {
230
+ switch (p.op) {
231
+ case "gt":
232
+ case "ge":
233
+ case "lt":
234
+ case "le":
235
+ case "eq":
236
+ case "ne": {
237
+ const a = p.a.op === "var" && p.a.id === SELF ? t : p.a;
238
+ const b = p.b.op === "var" && p.b.id === SELF ? t : p.b;
239
+ return { op: p.op, a, b };
240
+ }
241
+ case "typeof": {
242
+ const tt = p.t.op === "var" && p.t.id === SELF ? t : p.t;
243
+ return { op: "typeof", t: tt, type: p.type };
244
+ }
245
+ case "and":
246
+ return and(...p.args.map(subst));
247
+ default:
248
+ return p;
249
+ }
250
+ };
251
+ const preds = c.preds.map(subst);
252
+ if (c.prim && c.preds.length === 0) return ptypeof(t, c.prim);
253
+ return preds.length === 0 ? { op: "true" } : preds.length === 1 ? preds[0]! : and(...preds);
254
+ }
255
+
256
+ /**
257
+ * 契约 → 函数入口 param Abs(infer/hover 用)。
258
+ * 标量:prim + pred;shape:obj slots 递归。
259
+ */
260
+ export function constraintToEntryAbs(
261
+ c: NudoConstraint,
262
+ paramName: string,
263
+ ): Abs {
264
+ const t = termVar(paramName);
265
+ if (c.fields) {
266
+ const slots: Record<string, { value: Abs; optional?: boolean }> = {};
267
+ for (const [key, field] of Object.entries(c.fields)) {
268
+ const fieldTerm = getTerm(t, key);
269
+ slots[key] = {
270
+ value: constraintOnTermAbs(field.constraint, fieldTerm),
271
+ ...(field.optional || field.constraint.isOptional
272
+ ? { optional: true }
273
+ : {}),
274
+ };
275
+ }
276
+ return abs({ k: "obj", slots }, t, undefined, "path");
277
+ }
278
+ return constraintOnTermAbs(c, t);
279
+ }
280
+
281
+ function constraintOnTermAbs(c: NudoConstraint, t: Term): Abs {
282
+ if (c.fields) {
283
+ const slots: Record<string, { value: Abs; optional?: boolean }> = {};
284
+ for (const [key, field] of Object.entries(c.fields)) {
285
+ slots[key] = {
286
+ value: constraintOnTermAbs(field.constraint, getTerm(t, key)),
287
+ ...(field.optional || field.constraint.isOptional
288
+ ? { optional: true }
289
+ : {}),
290
+ };
291
+ }
292
+ return abs({ k: "obj", slots }, t, undefined, "path");
293
+ }
294
+ const pred = instantiateOnTerm(c, t);
295
+ const predOut = pred.op === "true" ? undefined : pred;
296
+ if (c.prim) {
297
+ return abs({ k: "prim", type: c.prim }, t, predOut, "path");
298
+ }
299
+ // 有界但无 prim:按 number 处理(number().gt(0) 已带 prim)
300
+ if (c.preds.length > 0) {
301
+ return abs({ k: "prim", type: "number" }, t, predOut, "path");
302
+ }
303
+ return abs({ k: "any" }, t, undefined, "path");
304
+ }
@@ -0,0 +1,166 @@
1
+ /**
2
+ * 指称:Abs → 运行时谓词(设计 §2.7)。
3
+ * - 无 pred:按 shape 检查(typeof / 结构)
4
+ * - 有 pred:在 `term ↦ v` 下检查可判定的数值比较
5
+ * - sum:任一成员
6
+ */
7
+
8
+ import type { Abs, Shape } from "./abs.ts";
9
+ import { litValue } from "./abs.ts";
10
+ import type { Pred } from "./pred.ts";
11
+
12
+ /** Abs 上的运行时守卫表达式(JS boolean 布尔串) */
13
+ export function denoteGuard(a: Abs, v: string): string {
14
+ // 字面量:等值已蕴含 typeof,短路
15
+ const lv = litValue(a);
16
+ if (lv !== undefined && (!a.pred || a.pred.op === "true")) {
17
+ return `${v} === ${JSON.stringify(lv)}`;
18
+ }
19
+ const shape = denoteShape(a.shape, v);
20
+ const pred = denotePred(a, v);
21
+ if (shape === "true") return pred;
22
+ if (pred === "true") return shape;
23
+ return `(${shape}) && (${pred})`;
24
+ }
25
+
26
+ function denoteShape(s: Shape, v: string): string {
27
+ switch (s.k) {
28
+ case "never":
29
+ return "false";
30
+ case "unknown":
31
+ case "any":
32
+ return "true";
33
+ case "prim":
34
+ switch (s.type) {
35
+ case "number":
36
+ return `typeof ${v} === "number"`;
37
+ case "string":
38
+ return `typeof ${v} === "string"`;
39
+ case "boolean":
40
+ return `typeof ${v} === "boolean"`;
41
+ case "bigint":
42
+ return `typeof ${v} === "bigint"`;
43
+ case "symbol":
44
+ return `typeof ${v} === "symbol"`;
45
+ default:
46
+ return "true";
47
+ }
48
+ case "obj": {
49
+ const checks = [`typeof ${v} === "object"`, `${v} !== null`];
50
+ for (const [key, slot] of Object.entries(s.slots)) {
51
+ const access = `${v}.${key}`;
52
+ const inner = denoteGuard(slot.value, access);
53
+ if (slot.optional) {
54
+ if (inner !== "true") checks.push(`(${access} === undefined || ${inner})`);
55
+ } else {
56
+ if (inner !== "true") checks.push(inner);
57
+ }
58
+ }
59
+ return checks.join(" && ");
60
+ }
61
+ case "arr":
62
+ return `Array.isArray(${v}) && ${v}.every((item) => ${denoteGuard(s.element, "item")})`;
63
+ case "tuple": {
64
+ const checks = [`Array.isArray(${v})`];
65
+ const minLen = s.elements.length;
66
+ checks.push(s.rest ? `${v}.length >= ${minLen}` : `${v}.length === ${minLen}`);
67
+ s.elements.forEach((el, i) => {
68
+ const inner = denoteGuard(el, `${v}[${i}]`);
69
+ if (inner !== "true") checks.push(inner);
70
+ });
71
+ if (s.rest) {
72
+ // rest 槽:长度超出部分统一检查
73
+ const rest = denoteGuard(s.rest, "item");
74
+ if (rest !== "true") {
75
+ checks.push(`${v}.slice(${minLen}).every((item) => ${rest})`);
76
+ }
77
+ }
78
+ return checks.join(" && ");
79
+ }
80
+ case "fn":
81
+ return `typeof ${v} === "function"`;
82
+ case "eff":
83
+ if (s.eff === "promise") return `${v} instanceof Promise`;
84
+ return "true";
85
+ case "brand":
86
+ return denoteShape(s.shape.shape, v);
87
+ case "sum": {
88
+ if (s.members.length === 0) return "false";
89
+ const parts = s.members.map((m) => denoteGuard(m, v));
90
+ return `(${parts.join(" || ")})`;
91
+ }
92
+ }
93
+ }
94
+
95
+ /**
96
+ * 在 `term ↦ v` 下检查 pred。
97
+ * 仅编码可判定的数值比较与字面量等值;不可判定返回 "true"(保守、不撒谎)。
98
+ */
99
+ function denotePred(a: Abs, v: string): string {
100
+ const p = a.pred;
101
+ if (!p || p.op === "true") {
102
+ // 无 pred:字面量 term 直接等值
103
+ const lv = litValue(a);
104
+ if (lv !== undefined) return `${v} === ${JSON.stringify(lv)}`;
105
+ return "true";
106
+ }
107
+ return predAsJs(p, v, a);
108
+ }
109
+
110
+ function predAsJs(p: Pred, v: string, a: Abs): string {
111
+ switch (p.op) {
112
+ case "true":
113
+ return "true";
114
+ case "false":
115
+ return "false";
116
+ case "and":
117
+ return p.args.map((x) => predAsJs(x, v, a)).join(" && ");
118
+ case "or":
119
+ return `(${p.args.map((x) => predAsJs(x, v, a)).join(" || ")})`;
120
+ case "not": {
121
+ const inner = predAsJs(p.arg, v, a);
122
+ return inner === "true" ? "false" : `!(${inner})`;
123
+ }
124
+ case "gt":
125
+ case "ge":
126
+ case "lt":
127
+ case "le":
128
+ case "eq":
129
+ case "ne": {
130
+ // 仅支持 term 侧为值本身(var/lit 与 Abs.term 一致)或字面量比较
131
+ const op = { gt: ">", ge: ">=", lt: "<", le: "<=", eq: "===", ne: "!==" }[p.op];
132
+ const leftIsValue = termIsValue(p.a, a);
133
+ const rightLit = litOfTerm(p.b);
134
+ if (leftIsValue && rightLit !== undefined) {
135
+ return `${v} ${op} ${JSON.stringify(rightLit)}`;
136
+ }
137
+ const leftLit = litOfTerm(p.a);
138
+ const rightIsValue = termIsValue(p.b, a);
139
+ if (rightIsValue && leftLit !== undefined) {
140
+ return `${JSON.stringify(leftLit)} ${op} ${v}`;
141
+ }
142
+ // 双字面量:可判定
143
+ if (leftLit !== undefined && rightLit !== undefined) {
144
+ return `${JSON.stringify(leftLit)} ${op} ${JSON.stringify(rightLit)}`;
145
+ }
146
+ return "true";
147
+ }
148
+ default:
149
+ return "true";
150
+ }
151
+ }
152
+
153
+ function litOfTerm(t: import("./term.ts").Term): unknown {
154
+ return t.op === "lit" ? t.value : undefined;
155
+ }
156
+
157
+ /** term 是否可视为「当前检查值 v」本身 */
158
+ function termIsValue(t: import("./term.ts").Term, a: Abs): boolean {
159
+ if (t.op === "var") return true;
160
+ if (t.op === "lit") {
161
+ const lv = litValue(a);
162
+ return lv !== undefined && Object.is(lv, t.value);
163
+ }
164
+ // app 等复合项:保守不绑到 v
165
+ return false;
166
+ }
@@ -0,0 +1,210 @@
1
+ /**
2
+ * 约束诊断:检查调用点实参是否满足函数的前置约束 / 参数 shape。
3
+ * 输出可行动的建议,而不是 TS 式「类型不匹配」。
4
+ */
5
+
6
+ import type { Abs } from "./abs.ts";
7
+ import { litValue } from "./abs.ts";
8
+ import type { Pred, Phi } from "./pred.ts";
9
+ import { implies, predToString, pTrue, gt, ge, lt, le } from "./pred.ts";
10
+ import type { Term } from "./term.ts";
11
+ import { termToString, v as termVar, lit } from "./term.ts";
12
+ import { generalizeFromAst, type PolyFn } from "./generalize.ts";
13
+ import { analyzeFn } from "./ast-eval.ts";
14
+ import { formatShape } from "./format.ts";
15
+
16
+ export type Diagnostic = {
17
+ severity: "error" | "warning" | "info";
18
+ code: string;
19
+ message: string;
20
+ suggestion?: string;
21
+ fn?: string;
22
+ argIndex?: number;
23
+ };
24
+
25
+ /**
26
+ * 检查实参是否满足「期望约束」。
27
+ * expect: 相对参数 term 的 pred,例如 term > 0
28
+ * arg: 实际实参 Abs
29
+ */
30
+ export function checkArg(
31
+ arg: Abs,
32
+ expect: Pred | undefined,
33
+ phi: Phi = pTrue,
34
+ ): Diagnostic | undefined {
35
+ if (!expect || expect.op === "true") return undefined;
36
+ if (arg.shape.k === "unknown" && !arg.term) {
37
+ return {
38
+ severity: "warning",
39
+ code: "nudo:arg-opaque",
40
+ message: `实参类型 unknown,无法验证约束 ${predToString(expect)}`,
41
+ suggestion: "补充调用点、@nudo:case,或 --assume 提供前置条件",
42
+ };
43
+ }
44
+ // 若 Φ 与实参自身 pred 已蕴含 expect → OK
45
+ const combined: Phi =
46
+ arg.pred && arg.pred.op !== "true"
47
+ ? phi.op === "true"
48
+ ? arg.pred
49
+ : { op: "and", args: [phi, arg.pred] }
50
+ : phi;
51
+ if (implies(combined, expect)) return undefined;
52
+
53
+ // 字面量直接判定
54
+ const lv = litValue(arg);
55
+ if (
56
+ lv !== undefined &&
57
+ (expect.op === "gt" || expect.op === "ge" || expect.op === "lt" || expect.op === "le")
58
+ ) {
59
+ const ok = decideLitCmp(lv, expect);
60
+ if (ok === true) return undefined;
61
+ if (ok === false) {
62
+ return {
63
+ severity: "error",
64
+ code: "nudo:constraint-violated",
65
+ message: `实参 ${JSON.stringify(lv)} 不满足 ${predToString(expect)}`,
66
+ suggestion: `改用满足约束的值,或放宽函数前置条件`,
67
+ };
68
+ }
69
+ }
70
+
71
+ return {
72
+ severity: "warning",
73
+ code: "nudo:constraint-unproven",
74
+ message: `无法证明实参满足 ${predToString(expect)}`,
75
+ suggestion: `已知信息不足;可 --assume 或补 case`,
76
+ };
77
+ }
78
+
79
+ function decideLitCmp(
80
+ lv: string | number | boolean | null | undefined,
81
+ p: Extract<Pred, { op: "gt" | "ge" | "lt" | "le" }>,
82
+ ): boolean | undefined {
83
+ if (typeof lv !== "number" || p.b.op !== "lit" || typeof p.b.value !== "number") {
84
+ return undefined;
85
+ }
86
+ const n = p.b.value;
87
+ switch (p.op) {
88
+ case "gt":
89
+ return lv > n;
90
+ case "ge":
91
+ return lv >= n;
92
+ case "lt":
93
+ return lv < n;
94
+ case "le":
95
+ return lv <= n;
96
+ default:
97
+ return undefined;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * 对一次函数调用做诊断:实参 shape + 约束。
103
+ */
104
+ export function checkCall(
105
+ source: string,
106
+ fnName: string,
107
+ args: Abs[],
108
+ phi: Phi = pTrue,
109
+ ): Diagnostic[] {
110
+ const diags: Diagnostic[] = [];
111
+ const g = generalizeFromAst(fnName, source);
112
+ if (!g) {
113
+ diags.push({
114
+ severity: "error",
115
+ code: "nudo:fn-not-found",
116
+ message: `未找到函数 ${fnName}`,
117
+ });
118
+ return diags;
119
+ }
120
+
121
+ // 参数个数
122
+ if (args.length !== g.params.length) {
123
+ diags.push({
124
+ severity: "error",
125
+ code: "nudo:arg-count",
126
+ message: `${fnName} 期望 ${g.params.length} 个参数,实际 ${args.length}`,
127
+ fn: fnName,
128
+ });
129
+ return diags;
130
+ }
131
+
132
+ // 从符号执行结果提取「对参数的隐含要求」:
133
+ // 若 symbolic.pred 形如 (A1 > 0),则要求对应实参满足
134
+ const required = extractParamRequirements(g);
135
+ args.forEach((arg, i) => {
136
+ const req = required.get(i);
137
+ const d = checkArg(arg, req, phi);
138
+ if (d) diags.push({ ...d, fn: fnName, argIndex: i });
139
+ });
140
+
141
+ // 尝试实际求值,捕获 partial 结果
142
+ try {
143
+ const result = analyzeFn(source, fnName, args, phi);
144
+ if (result.conf === "partial" || result.conf === "opaque") {
145
+ diags.push({
146
+ severity: "info",
147
+ code: "nudo:partial-result",
148
+ message: `${fnName}(...) 结果置信度 ${result.conf}`,
149
+ suggestion: "补充调用点或约束可提高精度",
150
+ fn: fnName,
151
+ });
152
+ }
153
+ } catch (e) {
154
+ diags.push({
155
+ severity: "error",
156
+ code: "nudo:eval-error",
157
+ message: `求值失败: ${(e as Error).message}`,
158
+ fn: fnName,
159
+ });
160
+ }
161
+
162
+ return diags;
163
+ }
164
+
165
+ /**
166
+ * 从 generalize 的 symbolic.pred 提取对每个参数位置的要求。
167
+ * 例如 scale 的 pred 若是 (A1+1)>1,则不直接是 A1 的约束;
168
+ * 若 pred 是 A1>0,则要求实参 >0。
169
+ * Phase C 简化:只识别 pred 中直接比较 type-param 的形式。
170
+ */
171
+ function extractParamRequirements(g: PolyFn): Map<number, Pred> {
172
+ const map = new Map<number, Pred>();
173
+ const pred = g.symbolic.pred;
174
+ if (!pred || pred.op === "true") return map;
175
+
176
+ const paramIndex = new Map<string, number>();
177
+ g.typeParams.forEach((t, i) => paramIndex.set(t.id, i));
178
+
179
+ const consider = (p: Pred): void => {
180
+ if (p.op === "and") {
181
+ p.args.forEach(consider);
182
+ return;
183
+ }
184
+ if (
185
+ (p.op === "gt" || p.op === "ge" || p.op === "lt" || p.op === "le") &&
186
+ p.a.op === "var" &&
187
+ paramIndex.has(p.a.id) &&
188
+ p.b.op === "lit"
189
+ ) {
190
+ // 把 A1 换成 term 占位 —— checkArg 时用实参 term 比较
191
+ // 这里存的是「相对 type-param」的 pred;checkArg 期望的是相对实参 term
192
+ // 简化:存为对参数位置的数值界
193
+ map.set(paramIndex.get(p.a.id)!, p as Pred);
194
+ }
195
+ };
196
+ consider(pred);
197
+ return map;
198
+ }
199
+
200
+ export function formatDiagnostics(diags: Diagnostic[]): string {
201
+ if (diags.length === 0) return "No issues found.";
202
+ return diags
203
+ .map((d) => {
204
+ const loc = d.fn ? `${d.fn}${d.argIndex !== undefined ? `[${d.argIndex}]` : ""}: ` : "";
205
+ let s = `[${d.severity}] ${loc}${d.message} (${d.code})`;
206
+ if (d.suggestion) s += `\n → ${d.suggestion}`;
207
+ return s;
208
+ })
209
+ .join("\n");
210
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * 调用 Abs 一等函数(absFunction impl)或 mock apply。
3
+ * 供 B 路径 import 绑定包装:`(...args) => $call(absFn, args)`。
4
+ */
5
+
6
+ import type { Abs } from "../abs.ts";
7
+ import { abs, unknown } from "../abs.ts";
8
+ import { getFnImpl } from "../abs-fn.ts";
9
+ import { evalNode, emptyEnv, type AstEnv } from "../ast-eval.ts";
10
+ import { defaultLeakBudget } from "../leak.ts";
11
+ import { pTrue } from "../pred.ts";
12
+
13
+ export function $call(fn: Abs, args: Abs[]): Abs {
14
+ const impl = getFnImpl(fn);
15
+ if (!impl) {
16
+ if (fn?.shape?.k === "fn") return unknown;
17
+ return unknown;
18
+ }
19
+ if (impl.apply) return impl.apply(args);
20
+ const base = impl.env ?? emptyEnv();
21
+ let local: AstEnv = { vars: new Map(base.vars), fns: base.fns };
22
+ if (base.classes) local = { ...local, classes: base.classes };
23
+ impl.params.forEach((p, i) => {
24
+ local.vars.set(p, args[i] ?? unknown);
25
+ });
26
+ const r = evalNode(impl.body, local, pTrue, defaultLeakBudget);
27
+ if (r.threw) return abs({ k: "never" }, undefined, undefined, "exact");
28
+ return r.value;
29
+ }