@nudojs/cli 0.3.0 → 0.3.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nudojs/cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "CLI and evaluator API for the Nudo type inference engine",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -12,17 +12,23 @@
12
12
  "dts",
13
13
  "abstract-interpretation"
14
14
  ],
15
- "main": "src/index.ts",
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
16
17
  "exports": {
17
- ".": "./src/index.ts",
18
- "./evaluator": "./src/evaluator-api.ts"
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "default": "./dist/index.js"
21
+ },
22
+ "./evaluator": {
23
+ "types": "./dist/evaluator-api.d.ts",
24
+ "default": "./dist/evaluator-api.js"
25
+ }
19
26
  },
20
27
  "bin": {
21
- "nudo": "src/index.ts"
28
+ "nudo": "./dist/index.js"
22
29
  },
23
30
  "files": [
24
- "src",
25
- "!src/__tests__"
31
+ "dist"
26
32
  ],
27
33
  "repository": {
28
34
  "type": "git",
@@ -38,13 +44,14 @@
38
44
  },
39
45
  "dependencies": {
40
46
  "commander": "^13.0.0",
41
- "@nudojs/core": "0.3.0",
42
- "@nudojs/parser": "0.3.0",
43
- "@nudojs/service": "0.3.0",
44
- "@nudojs/env": "0.2.1",
45
- "@nudojs/harvester": "0.2.0"
47
+ "@nudojs/core": "0.3.1",
48
+ "@nudojs/parser": "0.3.1",
49
+ "@nudojs/env": "0.2.2",
50
+ "@nudojs/service": "0.3.1",
51
+ "@nudojs/harvester": "0.2.1"
46
52
  },
47
53
  "scripts": {
48
- "build": "tsup"
54
+ "build": "tsup",
55
+ "build:lib": "tsup src/evaluator-api.ts --format esm --dts"
49
56
  }
50
57
  }
package/src/abs-route.ts DELETED
@@ -1,273 +0,0 @@
1
- /**
2
- * TypeValue IR ⇄ Abs 代数的路由。
3
- *
4
- * 唯一计算路径:算术/比较/相等/一元语言表面先走 Abs;
5
- * 仅 refined 宿主扩展(/ % 自定义 ops、方法表)与代数无法建模的操作数才回落。
6
- */
7
-
8
- import type { TypeValue } from "@nudojs/core";
9
- import {
10
- typeValueToAbs,
11
- absToTypeValue,
12
- simplifyUnion,
13
- add as kAdd,
14
- sub as kSub,
15
- mul as kMul,
16
- div as kDiv,
17
- mod as kMod,
18
- cmp as kCmp,
19
- typeofAbs as kTypeof,
20
- negAbs as kNeg,
21
- notAbs as kNot,
22
- strictEqAbs as kStrictEq,
23
- type Abs,
24
- type Pred,
25
- pTrue,
26
- and as predAnd,
27
- ge as predGe,
28
- le as predLe,
29
- lit as termLit,
30
- getRangeMeta,
31
- spread as kSpread,
32
- joinAbs as kJoinAbs,
33
- abs as makeAbs,
34
- boolLit,
35
- currentPhi,
36
- pushPhi,
37
- popPhi,
38
- resetPhi,
39
- withPhiConstraint,
40
- describePhi,
41
- } from "@nudojs/core";
42
- import { getTerm, getPred } from "./term-registry.ts";
43
-
44
- export { currentPhi, pushPhi, popPhi, resetPhi, withPhiConstraint, describePhi };
45
-
46
- const ARITH_OPS = new Set(["+", "-", "*", "/", "%"]);
47
- const CMP_OPS = new Set(["<", "<=", ">", ">="]);
48
- const EQ_OPS = new Set(["===", "!=="]);
49
- const UNARY_OPS = new Set(["typeof", "!", "-"]);
50
-
51
- function isNumericOperand(tv: TypeValue): boolean {
52
- if (!tv) return false;
53
- if (tv.kind === "literal") return typeof tv.value === "number";
54
- if (tv.kind === "primitive") return tv.type === "number";
55
- if (tv.kind === "refined") return isNumericOperand(tv.base);
56
- return false;
57
- }
58
-
59
- function isStringOperand(tv: TypeValue): boolean {
60
- if (!tv) return false;
61
- if (tv.kind === "literal") return typeof tv.value === "string";
62
- if (tv.kind === "primitive") return tv.type === "string";
63
- if (tv.kind === "refined") return isStringOperand(tv.base);
64
- return false;
65
- }
66
-
67
- /** 比较:数值或字符串字面量 */
68
- function isComparableOperand(tv: TypeValue): boolean {
69
- if (tv.kind === "literal") {
70
- return typeof tv.value === "number" || typeof tv.value === "string";
71
- }
72
- if (tv.kind === "primitive") return tv.type === "number" || tv.type === "string";
73
- if (tv.kind === "refined") return isComparableOperand(tv.base);
74
- return false;
75
- }
76
-
77
- /**
78
- * 宿主 refinement 自定义了该运算 → 让位。
79
- * 仅限代数未覆盖的运算(/ %):template 的 + 等仍走代数(concatString)。
80
- */
81
- function hasCustomRefinementOp(tv: TypeValue, op: string): boolean {
82
- if (op !== "/" && op !== "%") return false;
83
- let cur: TypeValue | undefined = tv;
84
- while (cur && cur.kind === "refined") {
85
- if (cur.refinement.ops?.[op]) return true;
86
- cur = cur.base;
87
- }
88
- return false;
89
- }
90
-
91
- /** `+` 可接受:双数值;或任一侧 string/template(拼接) */
92
- function canAdd(left: TypeValue, right: TypeValue): boolean {
93
- if (isNumericOperand(left) && isNumericOperand(right)) return true;
94
- if (isStringOperand(left) || isStringOperand(right)) return true;
95
- if (left.kind === "refined" && isStringOperand(left.base)) return true;
96
- if (right.kind === "refined" && isStringOperand(right.base)) return true;
97
- return false;
98
- }
99
-
100
- /**
101
- * 带 term 旁路的 TypeValue → Abs。
102
- * range refined 编码为 term 上的 Pred(唯一约束通道),不再靠 refinement.ops。
103
- */
104
- function toAbsWithTerms(tv: TypeValue): Abs {
105
- const base = typeValueToAbs(tv);
106
- const term = getTerm(tv);
107
- const pred = getPred(tv);
108
- let abs: Abs =
109
- !term && !pred && !getRangeMeta(tv)
110
- ? base
111
- : makeAbs(base.shape, term ?? base.term, pred ?? base.pred, base.conf);
112
-
113
- const range = getRangeMeta(tv);
114
- if (range && abs.term) {
115
- const facts: Pred[] = [];
116
- if (range.min != null) facts.push(predGe(abs.term, termLit(range.min)));
117
- if (range.max != null) facts.push(predLe(abs.term, termLit(range.max)));
118
- if (facts.length > 0) {
119
- const existing = abs.pred && abs.pred.op !== "true" ? abs.pred : undefined;
120
- abs = makeAbs(
121
- abs.shape,
122
- abs.term,
123
- existing ? predAnd(existing, ...facts) : predAnd(...facts),
124
- abs.conf,
125
- );
126
- }
127
- }
128
- return abs;
129
- }
130
-
131
- /**
132
- * 代数计算二元算术/比较/相等。
133
- * - union:逐成员代数后 simplifyUnion(约束不丢)
134
- * - undefined = 不可处理 → 调用方走 refined 扩展 / 形状兜底
135
- */
136
- export function tryAbsBinary(
137
- op: string,
138
- left: TypeValue,
139
- right: TypeValue,
140
- ): TypeValue | undefined {
141
- if (!ARITH_OPS.has(op) && !CMP_OPS.has(op) && !EQ_OPS.has(op)) return undefined;
142
-
143
- if (left.kind === "union" || right.kind === "union") {
144
- const ls = left.kind === "union" ? left.members : [left];
145
- const rs = right.kind === "union" ? right.members : [right];
146
- const results: TypeValue[] = [];
147
- for (const l of ls) {
148
- for (const r of rs) {
149
- const one = tryAbsBinary(op, l, r);
150
- if (one === undefined) return undefined;
151
- results.push(one);
152
- }
153
- }
154
- return simplifyUnion(results);
155
- }
156
-
157
- if (hasCustomRefinementOp(left, op) || hasCustomRefinementOp(right, op)) {
158
- return undefined;
159
- }
160
-
161
- if (op === "+") {
162
- if (!canAdd(left, right)) return undefined;
163
- } else if (op === "-" || op === "*" || op === "/" || op === "%") {
164
- if (!isNumericOperand(left) || !isNumericOperand(right)) return undefined;
165
- } else if (op === "===" || op === "!==") {
166
- // 任意操作数:先 strictEqAbs(字面量 / nullish / 同 var)
167
- } else {
168
- // 比较:数值或字符串
169
- if (!isComparableOperand(left) || !isComparableOperand(right)) return undefined;
170
- }
171
-
172
- try {
173
- const la = toAbsWithTerms(left);
174
- const ra = toAbsWithTerms(right);
175
- const phi = currentPhi();
176
-
177
- if (op === "===" || op === "!==") {
178
- const eq = kStrictEq(la, ra);
179
- if (eq !== undefined) {
180
- return absToTypeValue(op === "===" ? boolLit(eq) : boolLit(!eq));
181
- }
182
- // 无法判定:数值同型走 cmp 挂 pred;否则交还调用方
183
- if (isNumericOperand(left) && isNumericOperand(right)) {
184
- return absToTypeValue(kCmp(op === "===" ? "eq" : "ne", la, ra, phi));
185
- }
186
- return undefined;
187
- }
188
-
189
- let result: Abs;
190
- if (op === "+") result = kAdd(la, ra, phi);
191
- else if (op === "-") result = kSub(la, ra, phi);
192
- else if (op === "*") result = kMul(la, ra, phi);
193
- else if (op === "/") result = kDiv(la, ra, phi);
194
- else if (op === "%") result = kMod(la, ra, phi);
195
- else if (op === "<") result = kCmp("lt", la, ra, phi);
196
- else if (op === "<=") result = kCmp("le", la, ra, phi);
197
- else if (op === ">") result = kCmp("gt", la, ra, phi);
198
- else if (op === ">=") result = kCmp("ge", la, ra, phi);
199
- else return undefined;
200
-
201
- return absToTypeValue(result);
202
- } catch {
203
- return undefined;
204
- }
205
- }
206
-
207
- /**
208
- * 一元语言表面经代数:typeof / ! / -。
209
- * 返回 undefined 表示调用方自行处理(union 分发等)。
210
- */
211
- export function tryAbsUnary(
212
- op: string,
213
- operand: TypeValue,
214
- ): TypeValue | undefined {
215
- if (!UNARY_OPS.has(op)) return undefined;
216
- if (operand.kind === "union") {
217
- const outs: TypeValue[] = [];
218
- for (const m of operand.members) {
219
- const one = tryAbsUnary(op, m);
220
- if (one === undefined) return undefined;
221
- outs.push(one);
222
- }
223
- return simplifyUnion(outs);
224
- }
225
- try {
226
- const a = toAbsWithTerms(operand);
227
- if (op === "typeof") return absToTypeValue(kTypeof(a));
228
- if (op === "!") return absToTypeValue(kNot(a));
229
- if (op === "-") {
230
- if (!isNumericOperand(operand) && operand.kind !== "unknown") return undefined;
231
- return absToTypeValue(kNeg(a, currentPhi()));
232
- }
233
- return undefined;
234
- } catch {
235
- return undefined;
236
- }
237
- }
238
-
239
- /**
240
- * 对象 spread 经代数(右侧覆盖,保留字面量)。
241
- * 返回 undefined 表示走 Object.assign 路径。
242
- */
243
- export function tryAbsObjectSpread(
244
- base: TypeValue,
245
- over: TypeValue,
246
- ): TypeValue | undefined {
247
- if (base.kind !== "object" && base.kind !== "union") return undefined;
248
- if (over.kind !== "object" && over.kind !== "union") return undefined;
249
- try {
250
- const ba = toAbsWithTerms(base);
251
- const oa = toAbsWithTerms(over);
252
- const r = kSpread(ba, oa);
253
- return absToTypeValue(r);
254
- } catch {
255
- return undefined;
256
- }
257
- }
258
-
259
- /**
260
- * 对象 join(异 key 保持 sum,不折 optional)。
261
- */
262
- export function tryAbsJoinObjects(
263
- a: TypeValue,
264
- b: TypeValue,
265
- ): TypeValue | undefined {
266
- if (a.kind !== "object" || b.kind !== "object") return undefined;
267
- try {
268
- const r = kJoinAbs(toAbsWithTerms(a), toAbsWithTerms(b));
269
- return absToTypeValue(r);
270
- } catch {
271
- return undefined;
272
- }
273
- }
@@ -1,17 +0,0 @@
1
- import { type TypeValue, T } from "@nudojs/core";
2
-
3
- export const INTL_DATETIMEFORMAT_METHODS: Record<string, (...args: TypeValue[]) => TypeValue> = {
4
- format: () => T.string,
5
- };
6
-
7
- export const INTL_NUMBERFORMAT_METHODS: Record<string, (...args: TypeValue[]) => TypeValue> = {
8
- format: () => T.string,
9
- };
10
-
11
- export function createDateTimeFormatType(): TypeValue {
12
- return T.instanceOf("DateTimeFormat", {});
13
- }
14
-
15
- export function createNumberFormatType(): TypeValue {
16
- return T.instanceOf("NumberFormat", {});
17
- }
@@ -1,174 +0,0 @@
1
- import { type TypeValue, T, simplifyUnion, typeValueEquals } from "@nudojs/core";
2
-
3
- // Literal/instance keys recorded at construction (`new Map([['k', v]])`) or
4
- // via set() — precise enough for typeValueEquals lookup. Symbolic keys are
5
- // skipped: they cannot be definitely matched, so get() falls back to the
6
- // V | undefined approximation.
7
- export type MapEntry = { key: TypeValue; value: TypeValue };
8
-
9
- function entryKeyTrackable(key: TypeValue | undefined): boolean {
10
- return key?.kind === "literal" || key?.kind === "instance";
11
- }
12
-
13
- // Union keys distribute: every member hitting → union of values, every
14
- // member missing → undefined, mixed → values | undefined.
15
- function lookupEntries(entries: MapEntry[], key: TypeValue): TypeValue | null {
16
- if (key.kind === "union") {
17
- const results = key.members.map((m) => lookupEntries(entries, m));
18
- if (results.some((r) => r === null)) return null;
19
- return simplifyUnion(results as TypeValue[]);
20
- }
21
- if (!entryKeyTrackable(key)) return null;
22
- const hit = entries.find((e) => typeValueEquals(e.key, key));
23
- return hit ? hit.value : T.undefined;
24
- }
25
-
26
- export const MAP_INSTANCE_METHODS: Record<string, (...args: TypeValue[]) => TypeValue> = {
27
- get: (key: TypeValue, map?: TypeValue) => {
28
- const entries = exactMapEntries(map);
29
- if (entries) {
30
- const looked = lookupEntries(entries, key);
31
- if (looked !== null) return looked;
32
- }
33
- const typeArgs = (map as any)?._typeArgs;
34
- if (typeArgs?.V) return T.union(typeArgs.V, T.undefined);
35
- // Untyped receiver (unknown / non-Map value reaching the impl): a
36
- // definite undefined would be a false claim and poison downstream
37
- // guards (hoek isDeepEqual's `ref.name` path) into error-severity
38
- // misses. Unknown keeps it honest.
39
- return T.unknown;
40
- },
41
- set: (key: TypeValue, value: TypeValue, map?: TypeValue) => {
42
- if (map) {
43
- const typeArgs = (map as any)._typeArgs || { K: T.unknown, V: T.unknown };
44
- (map as any)._typeArgs = {
45
- K: typeArgs.K.kind === "unknown" ? key : simplifyUnion([typeArgs.K, key]),
46
- V: typeArgs.V.kind === "unknown" ? value : simplifyUnion([typeArgs.V, value]),
47
- };
48
- if (entryKeyTrackable(key)) {
49
- // An exact entry list can start on the first trackable set()
50
- // (nothing untracked came before); once partial it stays partial.
51
- if ((map as any)._entries === undefined && (map as any)._entriesExact !== false) {
52
- (map as any)._entries = [];
53
- (map as any)._entriesExact = true;
54
- }
55
- if ((map as any)._entriesExact) {
56
- const entries = (map as any)._entries as MapEntry[];
57
- const hit = entries.find((e) => typeValueEquals(e.key, key));
58
- if (hit) hit.value = value;
59
- else entries.push({ key, value });
60
- }
61
- } else {
62
- (map as any)._entriesExact = false;
63
- }
64
- }
65
- return map ?? T.unknown;
66
- },
67
- has: (key: TypeValue, map?: TypeValue) => {
68
- const entries = exactMapEntries(map);
69
- if (entries && entryKeyTrackable(key)) {
70
- return T.literal(entries.some((e) => typeValueEquals(e.key, key)));
71
- }
72
- return T.boolean;
73
- },
74
- delete: (key: TypeValue, map?: TypeValue) => {
75
- const entries = exactMapEntries(map);
76
- if (entries && entryKeyTrackable(key)) {
77
- const idx = entries.findIndex((e) => typeValueEquals(e.key, key));
78
- if (idx >= 0) {
79
- entries.splice(idx, 1);
80
- return T.literal(true);
81
- }
82
- return T.literal(false);
83
- }
84
- return T.boolean;
85
- },
86
- clear: (value?: TypeValue, map?: TypeValue) => {
87
- // m.clear() arrives as clear(receiver): the map sits in the first
88
- // param slot when the call has no arguments.
89
- const target = map ?? value;
90
- if (target && (target as any)._entriesExact) {
91
- (target as any)._entries = [];
92
- }
93
- return T.undefined;
94
- },
95
- forEach: () => T.undefined,
96
- keys: (map?: TypeValue) => {
97
- const entries = exactMapEntries(map);
98
- if (entries) return T.tuple(entries.map((e) => e.key));
99
- const typeArgs = (map as any)?._typeArgs;
100
- if (typeArgs?.K) return T.array(typeArgs.K);
101
- return T.array(T.unknown);
102
- },
103
- values: (map?: TypeValue) => {
104
- const entries = exactMapEntries(map);
105
- if (entries) return T.tuple(entries.map((e) => e.value));
106
- const typeArgs = (map as any)?._typeArgs;
107
- if (typeArgs?.V) return T.array(typeArgs.V);
108
- return T.array(T.unknown);
109
- },
110
- entries: (map?: TypeValue) => mapEntriesIterable(map),
111
- };
112
-
113
- // Exact entry side table: present only when every key seen so far
114
- // (construction pairs + set() calls) was trackable. Partial tables must
115
- // not decide get()/has()/delete()/iteration — an untracked key could map
116
- // to anything.
117
- export function exactMapEntries(map: TypeValue | undefined): MapEntry[] | undefined {
118
- if (!map || (map as any)._entriesExact !== true) return undefined;
119
- return (map as any)._entries as MapEntry[] | undefined;
120
- }
121
-
122
- // entries() keeps feeding for-of destructuring (`for (const [k, v] of m)`)
123
- // and `new Map(...)` consumers: a tuple of [key, value] pairs when the
124
- // side table is complete, else Array<[K, V]>.
125
- export function mapEntriesIterable(map: TypeValue | undefined): TypeValue {
126
- const entries = exactMapEntries(map);
127
- if (entries) return T.tuple(entries.map((e) => T.tuple([e.key, e.value])));
128
- const typeArgs = (map as any)?._typeArgs;
129
- if (typeArgs?.K && typeArgs?.V) return T.array(T.tuple([typeArgs.K, typeArgs.V]));
130
- return T.array(T.tuple([T.unknown, T.unknown]));
131
- }
132
-
133
- export function createMapType(args?: TypeValue[]): TypeValue {
134
- const obj = T.instanceOf("Map", {});
135
-
136
- if (args && args.length > 0) {
137
- const arg = args[0];
138
- if (arg.kind === "tuple" || arg.kind === "array") {
139
- const elements = arg.kind === "tuple" ? arg.elements : [arg.element];
140
- const keys: TypeValue[] = [];
141
- const values: TypeValue[] = [];
142
- const entries: MapEntry[] = [];
143
- let exact = true;
144
- for (const el of elements) {
145
- if (el.kind === "tuple" && el.elements.length >= 2) {
146
- keys.push(el.elements[0]);
147
- values.push(el.elements[1]);
148
- if (entryKeyTrackable(el.elements[0])) {
149
- entries.push({ key: el.elements[0], value: el.elements[1] });
150
- } else {
151
- exact = false;
152
- }
153
- }
154
- }
155
- if (keys.length > 0) {
156
- (obj as any)._typeArgs = { K: simplifyUnion(keys), V: simplifyUnion(values) };
157
- }
158
- // Only a fully trackable key list is exact: symbolic keys mean the
159
- // recorded entries are a subset of the real ones.
160
- if (exact && entries.length > 0) {
161
- (obj as any)._entries = entries;
162
- (obj as any)._entriesExact = true;
163
- } else if (!exact) {
164
- (obj as any)._entriesExact = false;
165
- }
166
- }
167
- }
168
-
169
- if (!(obj as any)._typeArgs) {
170
- (obj as any)._typeArgs = { K: T.unknown, V: T.unknown };
171
- }
172
-
173
- return obj;
174
- }
@@ -1,116 +0,0 @@
1
- import { type TypeValue, T, simplifyUnion } from "@nudojs/core";
2
-
3
- export const PROMISE_STATIC_METHODS: Record<string, TypeValue> = {
4
- resolve: T.unknown,
5
- reject: T.never,
6
- all: T.unknown,
7
- race: T.unknown,
8
- allSettled: T.unknown,
9
- any: T.unknown,
10
- };
11
-
12
- export function evaluatePromiseStaticMethod(
13
- methodName: string,
14
- args: TypeValue[],
15
- ): TypeValue | null {
16
- if (methodName === "resolve") {
17
- if (args.length > 0) return T.promise(args[0]);
18
- return T.promise(T.undefined);
19
- }
20
-
21
- if (methodName === "reject") {
22
- return T.promise(T.never);
23
- }
24
-
25
- if (methodName === "all") {
26
- if (args.length > 0 && args[0].kind === "tuple") {
27
- const elemTypes = args[0].elements.map((e: TypeValue) => {
28
- if (e.kind === "promise") return e.value;
29
- return T.unknown;
30
- });
31
- return T.promise(T.tuple(elemTypes));
32
- }
33
- if (args.length > 0 && args[0].kind === "array") {
34
- return T.promise(T.array(args[0].element));
35
- }
36
- return T.promise(T.array(T.unknown));
37
- }
38
-
39
- if (methodName === "race") {
40
- if (args.length > 0 && args[0].kind === "tuple") {
41
- const elemTypes = args[0].elements.map((e: TypeValue) => {
42
- if (e.kind === "promise") return e.value;
43
- return T.unknown;
44
- });
45
- return T.promise(simplifyUnion(elemTypes));
46
- }
47
- if (args.length > 0 && args[0].kind === "array") {
48
- const el = args[0].element;
49
- return T.promise(el.kind === "promise" ? el.value : T.unknown);
50
- }
51
- return T.promise(T.unknown);
52
- }
53
-
54
- if (methodName === "allSettled") {
55
- // Best effort: each slot settles to { status, value } — the fulfilled
56
- // value is the element's promise payload (unknown for non-promises).
57
- const settledOf = (e: TypeValue): TypeValue =>
58
- T.object({
59
- status: T.union(T.literal("fulfilled"), T.literal("rejected")),
60
- value: e.kind === "promise" ? e.value : T.unknown,
61
- });
62
- if (args.length > 0 && args[0].kind === "tuple") {
63
- return T.promise(T.tuple(args[0].elements.map(settledOf)));
64
- }
65
- if (args.length > 0 && args[0].kind === "array") {
66
- return T.promise(T.array(settledOf(args[0].element)));
67
- }
68
- return T.promise(T.array(T.unknown));
69
- }
70
-
71
- if (methodName === "any") {
72
- if (args.length > 0 && args[0].kind === "tuple") {
73
- const elemTypes = args[0].elements.map((e: TypeValue) => {
74
- if (e.kind === "promise") return e.value;
75
- return T.unknown;
76
- });
77
- return T.promise(simplifyUnion(elemTypes));
78
- }
79
- if (args.length > 0 && args[0].kind === "array") {
80
- const el = args[0].element;
81
- return T.promise(el.kind === "promise" ? el.value : T.unknown);
82
- }
83
- return T.promise(T.unknown);
84
- }
85
-
86
- return null;
87
- }
88
-
89
- export function evaluatePromiseInstanceMethod(
90
- promiseValue: TypeValue,
91
- methodName: string,
92
- args: TypeValue[],
93
- ): TypeValue | null {
94
- if (methodName === "then") {
95
- if (promiseValue.kind === "promise") {
96
- // A real function callback is executed by the evaluator's main call
97
- // path (applyPromiseThenCallback) before reaching here; only
98
- // non-callable handlers fall through to an unresolved value type.
99
- return T.promise(T.unknown);
100
- }
101
- }
102
-
103
- if (methodName === "catch") {
104
- if (promiseValue.kind === "promise") {
105
- return promiseValue; // catch returns same promise type
106
- }
107
- }
108
-
109
- if (methodName === "finally") {
110
- if (promiseValue.kind === "promise") {
111
- return promiseValue; // finally returns same promise type
112
- }
113
- }
114
-
115
- return null;
116
- }