@nudojs/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/index.d.ts +104 -0
- package/dist/index.js +440 -0
- package/package.json +12 -0
- package/src/__tests__/environment.test.ts +48 -0
- package/src/__tests__/ops.test.ts +109 -0
- package/src/__tests__/type-value-phase2.test.ts +117 -0
- package/src/__tests__/type-value-phase3.test.ts +94 -0
- package/src/__tests__/type-value.test.ts +213 -0
- package/src/environment.ts +69 -0
- package/src/index.ts +22 -0
- package/src/ops.ts +147 -0
- package/src/type-value.ts +332 -0
- package/tsconfig.json +8 -0
package/src/ops.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { type TypeValue, T, isSubtypeOf } from "./type-value.ts";
|
|
2
|
+
|
|
3
|
+
function bothLiteral(
|
|
4
|
+
l: TypeValue,
|
|
5
|
+
r: TypeValue,
|
|
6
|
+
): { lv: string | number | boolean | null | undefined; rv: string | number | boolean | null | undefined } | null {
|
|
7
|
+
if (l.kind === "literal" && r.kind === "literal") {
|
|
8
|
+
return { lv: l.value, rv: r.value };
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const Ops = {
|
|
14
|
+
add(left: TypeValue, right: TypeValue): TypeValue {
|
|
15
|
+
const lit = bothLiteral(left, right);
|
|
16
|
+
if (lit) {
|
|
17
|
+
return T.literal((lit.lv as any) + (lit.rv as any));
|
|
18
|
+
}
|
|
19
|
+
if (isSubtypeOf(left, T.string) || isSubtypeOf(right, T.string)) {
|
|
20
|
+
return T.string;
|
|
21
|
+
}
|
|
22
|
+
if (isSubtypeOf(left, T.number) && isSubtypeOf(right, T.number)) {
|
|
23
|
+
return T.number;
|
|
24
|
+
}
|
|
25
|
+
return T.union(T.number, T.string);
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
sub(left: TypeValue, right: TypeValue): TypeValue {
|
|
29
|
+
const lit = bothLiteral(left, right);
|
|
30
|
+
if (lit && typeof lit.lv === "number" && typeof lit.rv === "number") {
|
|
31
|
+
return T.literal(lit.lv - lit.rv);
|
|
32
|
+
}
|
|
33
|
+
return T.number;
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
mul(left: TypeValue, right: TypeValue): TypeValue {
|
|
37
|
+
const lit = bothLiteral(left, right);
|
|
38
|
+
if (lit && typeof lit.lv === "number" && typeof lit.rv === "number") {
|
|
39
|
+
return T.literal(lit.lv * lit.rv);
|
|
40
|
+
}
|
|
41
|
+
return T.number;
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
div(left: TypeValue, right: TypeValue): TypeValue {
|
|
45
|
+
const lit = bothLiteral(left, right);
|
|
46
|
+
if (lit && typeof lit.lv === "number" && typeof lit.rv === "number") {
|
|
47
|
+
return T.literal(lit.lv / lit.rv);
|
|
48
|
+
}
|
|
49
|
+
return T.number;
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
mod(left: TypeValue, right: TypeValue): TypeValue {
|
|
53
|
+
const lit = bothLiteral(left, right);
|
|
54
|
+
if (lit && typeof lit.lv === "number" && typeof lit.rv === "number") {
|
|
55
|
+
return T.literal(lit.lv % lit.rv);
|
|
56
|
+
}
|
|
57
|
+
return T.number;
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
strictEq(left: TypeValue, right: TypeValue): TypeValue {
|
|
61
|
+
const lit = bothLiteral(left, right);
|
|
62
|
+
if (lit) return T.literal(lit.lv === lit.rv);
|
|
63
|
+
return T.boolean;
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
strictNeq(left: TypeValue, right: TypeValue): TypeValue {
|
|
67
|
+
const lit = bothLiteral(left, right);
|
|
68
|
+
if (lit) return T.literal(lit.lv !== lit.rv);
|
|
69
|
+
return T.boolean;
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
gt(left: TypeValue, right: TypeValue): TypeValue {
|
|
73
|
+
const lit = bothLiteral(left, right);
|
|
74
|
+
if (lit) return T.literal((lit.lv as any) > (lit.rv as any));
|
|
75
|
+
return T.boolean;
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
lt(left: TypeValue, right: TypeValue): TypeValue {
|
|
79
|
+
const lit = bothLiteral(left, right);
|
|
80
|
+
if (lit) return T.literal((lit.lv as any) < (lit.rv as any));
|
|
81
|
+
return T.boolean;
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
gte(left: TypeValue, right: TypeValue): TypeValue {
|
|
85
|
+
const lit = bothLiteral(left, right);
|
|
86
|
+
if (lit) return T.literal((lit.lv as any) >= (lit.rv as any));
|
|
87
|
+
return T.boolean;
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
lte(left: TypeValue, right: TypeValue): TypeValue {
|
|
91
|
+
const lit = bothLiteral(left, right);
|
|
92
|
+
if (lit) return T.literal((lit.lv as any) <= (lit.rv as any));
|
|
93
|
+
return T.boolean;
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
typeof_(operand: TypeValue): TypeValue {
|
|
97
|
+
if (operand.kind === "literal") {
|
|
98
|
+
const v = operand.value;
|
|
99
|
+
if (v === null) return T.literal("object");
|
|
100
|
+
return T.literal(typeof v);
|
|
101
|
+
}
|
|
102
|
+
if (operand.kind === "primitive") return T.literal(operand.type);
|
|
103
|
+
if (operand.kind === "object") return T.literal("object");
|
|
104
|
+
if (operand.kind === "array" || operand.kind === "tuple")
|
|
105
|
+
return T.literal("object");
|
|
106
|
+
if (operand.kind === "function") return T.literal("function");
|
|
107
|
+
if (operand.kind === "promise") return T.literal("object");
|
|
108
|
+
if (operand.kind === "instance") return T.literal("object");
|
|
109
|
+
return T.string;
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
not(operand: TypeValue): TypeValue {
|
|
113
|
+
if (operand.kind === "literal") return T.literal(!operand.value);
|
|
114
|
+
return T.boolean;
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
neg(operand: TypeValue): TypeValue {
|
|
118
|
+
if (operand.kind === "literal" && typeof operand.value === "number") {
|
|
119
|
+
return T.literal(-operand.value);
|
|
120
|
+
}
|
|
121
|
+
return T.number;
|
|
122
|
+
},
|
|
123
|
+
} as const;
|
|
124
|
+
|
|
125
|
+
const binaryOpMap: Record<string, (l: TypeValue, r: TypeValue) => TypeValue> = {
|
|
126
|
+
"+": Ops.add,
|
|
127
|
+
"-": Ops.sub,
|
|
128
|
+
"*": Ops.mul,
|
|
129
|
+
"/": Ops.div,
|
|
130
|
+
"%": Ops.mod,
|
|
131
|
+
"===": Ops.strictEq,
|
|
132
|
+
"!==": Ops.strictNeq,
|
|
133
|
+
">": Ops.gt,
|
|
134
|
+
"<": Ops.lt,
|
|
135
|
+
">=": Ops.gte,
|
|
136
|
+
"<=": Ops.lte,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export function applyBinaryOp(
|
|
140
|
+
op: string,
|
|
141
|
+
left: TypeValue,
|
|
142
|
+
right: TypeValue,
|
|
143
|
+
): TypeValue {
|
|
144
|
+
const fn = binaryOpMap[op];
|
|
145
|
+
if (!fn) return T.unknown;
|
|
146
|
+
return fn(left, right);
|
|
147
|
+
}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import type { Node } from "@babel/types";
|
|
2
|
+
import type { Environment } from "./environment.ts";
|
|
3
|
+
|
|
4
|
+
// --- TypeValue discriminated union ---
|
|
5
|
+
|
|
6
|
+
export type LiteralValue = string | number | boolean | null | undefined;
|
|
7
|
+
|
|
8
|
+
export type TypeValue =
|
|
9
|
+
| { kind: "literal"; value: LiteralValue }
|
|
10
|
+
| {
|
|
11
|
+
kind: "primitive";
|
|
12
|
+
type: "number" | "string" | "boolean" | "bigint" | "symbol";
|
|
13
|
+
}
|
|
14
|
+
| { kind: "object"; properties: Record<string, TypeValue>; id: symbol }
|
|
15
|
+
| { kind: "array"; element: TypeValue }
|
|
16
|
+
| { kind: "tuple"; elements: TypeValue[] }
|
|
17
|
+
| {
|
|
18
|
+
kind: "function";
|
|
19
|
+
params: string[];
|
|
20
|
+
body: Node;
|
|
21
|
+
closure: Environment;
|
|
22
|
+
}
|
|
23
|
+
| { kind: "promise"; value: TypeValue }
|
|
24
|
+
| { kind: "instance"; className: string; properties: Record<string, TypeValue> }
|
|
25
|
+
| { kind: "union"; members: TypeValue[] }
|
|
26
|
+
| { kind: "never" }
|
|
27
|
+
| { kind: "unknown" };
|
|
28
|
+
|
|
29
|
+
// --- T: static factory ---
|
|
30
|
+
|
|
31
|
+
function createUnion(...members: TypeValue[]): TypeValue {
|
|
32
|
+
return simplifyUnion(members);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const T = {
|
|
36
|
+
literal: (value: LiteralValue): TypeValue => ({ kind: "literal", value }),
|
|
37
|
+
number: { kind: "primitive", type: "number" } as TypeValue,
|
|
38
|
+
string: { kind: "primitive", type: "string" } as TypeValue,
|
|
39
|
+
boolean: { kind: "primitive", type: "boolean" } as TypeValue,
|
|
40
|
+
bigint: { kind: "primitive", type: "bigint" } as TypeValue,
|
|
41
|
+
symbol: { kind: "primitive", type: "symbol" } as TypeValue,
|
|
42
|
+
null: { kind: "literal", value: null } as TypeValue,
|
|
43
|
+
undefined: { kind: "literal", value: undefined } as TypeValue,
|
|
44
|
+
unknown: { kind: "unknown" } as TypeValue,
|
|
45
|
+
never: { kind: "never" } as TypeValue,
|
|
46
|
+
|
|
47
|
+
object: (properties: Record<string, TypeValue>): TypeValue => ({
|
|
48
|
+
kind: "object",
|
|
49
|
+
properties,
|
|
50
|
+
id: Symbol("object"),
|
|
51
|
+
}),
|
|
52
|
+
array: (element: TypeValue): TypeValue => ({ kind: "array", element }),
|
|
53
|
+
tuple: (elements: TypeValue[]): TypeValue => ({ kind: "tuple", elements }),
|
|
54
|
+
promise: (value: TypeValue): TypeValue => ({ kind: "promise", value }),
|
|
55
|
+
instanceOf: (className: string, properties: Record<string, TypeValue> = {}): TypeValue => ({
|
|
56
|
+
kind: "instance",
|
|
57
|
+
className,
|
|
58
|
+
properties,
|
|
59
|
+
}),
|
|
60
|
+
union: createUnion,
|
|
61
|
+
fn: (
|
|
62
|
+
params: string[],
|
|
63
|
+
body: Node,
|
|
64
|
+
closure: Environment,
|
|
65
|
+
): TypeValue => ({
|
|
66
|
+
kind: "function",
|
|
67
|
+
params,
|
|
68
|
+
body,
|
|
69
|
+
closure,
|
|
70
|
+
}),
|
|
71
|
+
} as const;
|
|
72
|
+
|
|
73
|
+
// --- Helpers ---
|
|
74
|
+
|
|
75
|
+
export function typeValueEquals(a: TypeValue, b: TypeValue): boolean {
|
|
76
|
+
if (a.kind !== b.kind) return false;
|
|
77
|
+
if (a.kind === "literal" && b.kind === "literal") return a.value === b.value;
|
|
78
|
+
if (a.kind === "primitive" && b.kind === "primitive") return a.type === b.type;
|
|
79
|
+
if (a.kind === "never" && b.kind === "never") return true;
|
|
80
|
+
if (a.kind === "unknown" && b.kind === "unknown") return true;
|
|
81
|
+
if (a.kind === "promise" && b.kind === "promise") {
|
|
82
|
+
return typeValueEquals(a.value, b.value);
|
|
83
|
+
}
|
|
84
|
+
if (a.kind === "instance" && b.kind === "instance") {
|
|
85
|
+
return a.className === b.className;
|
|
86
|
+
}
|
|
87
|
+
if (a.kind === "union" && b.kind === "union") {
|
|
88
|
+
return (
|
|
89
|
+
a.members.length === b.members.length &&
|
|
90
|
+
a.members.every((m, i) => typeValueEquals(m, b.members[i]))
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
return a === b;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function simplifyUnion(members: TypeValue[]): TypeValue {
|
|
97
|
+
const flat: TypeValue[] = [];
|
|
98
|
+
for (const m of members) {
|
|
99
|
+
if (m.kind === "never") continue;
|
|
100
|
+
if (m.kind === "union") {
|
|
101
|
+
flat.push(...m.members);
|
|
102
|
+
} else {
|
|
103
|
+
flat.push(m);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const deduped: TypeValue[] = [];
|
|
108
|
+
for (const m of flat) {
|
|
109
|
+
if (!deduped.some((d) => typeValueEquals(d, m))) {
|
|
110
|
+
deduped.push(m);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (deduped.length === 0) return T.never;
|
|
115
|
+
if (deduped.length === 1) return deduped[0];
|
|
116
|
+
if (deduped.some((m) => m.kind === "unknown")) return T.unknown;
|
|
117
|
+
return { kind: "union", members: deduped };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function widenLiteral(tv: TypeValue): TypeValue {
|
|
121
|
+
if (tv.kind !== "literal") return tv;
|
|
122
|
+
const v = tv.value;
|
|
123
|
+
if (typeof v === "number") return T.number;
|
|
124
|
+
if (typeof v === "string") return T.string;
|
|
125
|
+
if (typeof v === "boolean") return T.boolean;
|
|
126
|
+
if (v === null) return T.null;
|
|
127
|
+
if (v === undefined) return T.undefined;
|
|
128
|
+
return T.unknown;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function isSubtypeOf(a: TypeValue, b: TypeValue): boolean {
|
|
132
|
+
if (b.kind === "unknown") return true;
|
|
133
|
+
if (a.kind === "never") return true;
|
|
134
|
+
if (typeValueEquals(a, b)) return true;
|
|
135
|
+
|
|
136
|
+
if (a.kind === "literal" && b.kind === "primitive") {
|
|
137
|
+
const v = a.value;
|
|
138
|
+
if (b.type === "number" && typeof v === "number") return true;
|
|
139
|
+
if (b.type === "string" && typeof v === "string") return true;
|
|
140
|
+
if (b.type === "boolean" && typeof v === "boolean") return true;
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (a.kind === "object" && b.kind === "object") {
|
|
145
|
+
return Object.entries(b.properties).every(
|
|
146
|
+
([k, bv]) => k in a.properties && isSubtypeOf(a.properties[k], bv),
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (a.kind === "array" && b.kind === "array") {
|
|
151
|
+
return isSubtypeOf(a.element, b.element);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (a.kind === "tuple" && b.kind === "tuple") {
|
|
155
|
+
return (
|
|
156
|
+
a.elements.length === b.elements.length &&
|
|
157
|
+
a.elements.every((el, i) => isSubtypeOf(el, b.elements[i]))
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (a.kind === "tuple" && b.kind === "array") {
|
|
162
|
+
return a.elements.every((el) => isSubtypeOf(el, b.element));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (a.kind === "promise" && b.kind === "promise") {
|
|
166
|
+
return isSubtypeOf(a.value, b.value);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (a.kind === "instance" && b.kind === "instance") {
|
|
170
|
+
return a.className === b.className || isErrorSubclass(a.className, b.className);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (a.kind === "union") {
|
|
174
|
+
return a.members.every((m) => isSubtypeOf(m, b));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (b.kind === "union") {
|
|
178
|
+
return b.members.some((m) => isSubtypeOf(a, m));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const errorHierarchy: Record<string, string> = {
|
|
185
|
+
TypeError: "Error",
|
|
186
|
+
SyntaxError: "Error",
|
|
187
|
+
RangeError: "Error",
|
|
188
|
+
ReferenceError: "Error",
|
|
189
|
+
URIError: "Error",
|
|
190
|
+
EvalError: "Error",
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
function isErrorSubclass(child: string, parent: string): boolean {
|
|
194
|
+
if (child === parent) return true;
|
|
195
|
+
const sup = errorHierarchy[child];
|
|
196
|
+
return sup ? isErrorSubclass(sup, parent) : false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function deepCloneTypeValue(tv: TypeValue, idMap?: Map<symbol, symbol>): TypeValue {
|
|
200
|
+
const map = idMap ?? new Map<symbol, symbol>();
|
|
201
|
+
if (tv.kind === "object") {
|
|
202
|
+
let newId = map.get(tv.id);
|
|
203
|
+
if (!newId) {
|
|
204
|
+
newId = Symbol("object");
|
|
205
|
+
map.set(tv.id, newId);
|
|
206
|
+
}
|
|
207
|
+
const newProps: Record<string, TypeValue> = {};
|
|
208
|
+
for (const [k, v] of Object.entries(tv.properties)) {
|
|
209
|
+
newProps[k] = deepCloneTypeValue(v, map);
|
|
210
|
+
}
|
|
211
|
+
return { kind: "object", properties: newProps, id: newId };
|
|
212
|
+
}
|
|
213
|
+
if (tv.kind === "array") {
|
|
214
|
+
return { kind: "array", element: deepCloneTypeValue(tv.element, map) };
|
|
215
|
+
}
|
|
216
|
+
if (tv.kind === "tuple") {
|
|
217
|
+
return { kind: "tuple", elements: tv.elements.map((e) => deepCloneTypeValue(e, map)) };
|
|
218
|
+
}
|
|
219
|
+
if (tv.kind === "promise") {
|
|
220
|
+
return { kind: "promise", value: deepCloneTypeValue(tv.value, map) };
|
|
221
|
+
}
|
|
222
|
+
if (tv.kind === "instance") {
|
|
223
|
+
const newProps: Record<string, TypeValue> = {};
|
|
224
|
+
for (const [k, v] of Object.entries(tv.properties)) {
|
|
225
|
+
newProps[k] = deepCloneTypeValue(v, map);
|
|
226
|
+
}
|
|
227
|
+
return { kind: "instance", className: tv.className, properties: newProps };
|
|
228
|
+
}
|
|
229
|
+
if (tv.kind === "union") {
|
|
230
|
+
return simplifyUnion(tv.members.map((m) => deepCloneTypeValue(m, map)));
|
|
231
|
+
}
|
|
232
|
+
return tv;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function mergeObjectProperties(
|
|
236
|
+
a: TypeValue & { kind: "object" },
|
|
237
|
+
b: TypeValue & { kind: "object" },
|
|
238
|
+
): TypeValue {
|
|
239
|
+
const allKeys = new Set([...Object.keys(a.properties), ...Object.keys(b.properties)]);
|
|
240
|
+
const merged: Record<string, TypeValue> = {};
|
|
241
|
+
for (const k of allKeys) {
|
|
242
|
+
const av = a.properties[k];
|
|
243
|
+
const bv = b.properties[k];
|
|
244
|
+
if (av && bv) {
|
|
245
|
+
merged[k] = simplifyUnion([av, bv]);
|
|
246
|
+
} else {
|
|
247
|
+
merged[k] = av ?? bv;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return { kind: "object", properties: merged, id: a.id };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function typeValueToString(tv: TypeValue): string {
|
|
254
|
+
switch (tv.kind) {
|
|
255
|
+
case "literal": {
|
|
256
|
+
const v = tv.value;
|
|
257
|
+
if (v === null) return "null";
|
|
258
|
+
if (v === undefined) return "undefined";
|
|
259
|
+
if (typeof v === "string") return JSON.stringify(v);
|
|
260
|
+
return String(v);
|
|
261
|
+
}
|
|
262
|
+
case "primitive":
|
|
263
|
+
return tv.type;
|
|
264
|
+
case "object": {
|
|
265
|
+
const entries = Object.entries(tv.properties);
|
|
266
|
+
if (entries.length === 0) return "{}";
|
|
267
|
+
const inner = entries
|
|
268
|
+
.map(([k, v]) => `${k}: ${typeValueToString(v)}`)
|
|
269
|
+
.join(", ");
|
|
270
|
+
return `{ ${inner} }`;
|
|
271
|
+
}
|
|
272
|
+
case "array":
|
|
273
|
+
return `${typeValueToString(tv.element)}[]`;
|
|
274
|
+
case "tuple": {
|
|
275
|
+
const inner = tv.elements.map(typeValueToString).join(", ");
|
|
276
|
+
return `[${inner}]`;
|
|
277
|
+
}
|
|
278
|
+
case "function": {
|
|
279
|
+
const params = tv.params.join(", ");
|
|
280
|
+
return `(${params}) => ...`;
|
|
281
|
+
}
|
|
282
|
+
case "promise":
|
|
283
|
+
return `Promise<${typeValueToString(tv.value)}>`;
|
|
284
|
+
case "instance": {
|
|
285
|
+
const entries = Object.entries(tv.properties);
|
|
286
|
+
if (entries.length === 0) return tv.className;
|
|
287
|
+
const inner = entries
|
|
288
|
+
.map(([k, v]) => `${k}: ${typeValueToString(v)}`)
|
|
289
|
+
.join(", ");
|
|
290
|
+
return `${tv.className} { ${inner} }`;
|
|
291
|
+
}
|
|
292
|
+
case "union": {
|
|
293
|
+
return tv.members.map(typeValueToString).join(" | ");
|
|
294
|
+
}
|
|
295
|
+
case "never":
|
|
296
|
+
return "never";
|
|
297
|
+
case "unknown":
|
|
298
|
+
return "unknown";
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function narrowType(
|
|
303
|
+
tv: TypeValue,
|
|
304
|
+
predicate: (member: TypeValue) => boolean,
|
|
305
|
+
): TypeValue {
|
|
306
|
+
if (tv.kind === "union") {
|
|
307
|
+
return simplifyUnion(tv.members.filter(predicate));
|
|
308
|
+
}
|
|
309
|
+
return predicate(tv) ? tv : T.never;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function subtractType(
|
|
313
|
+
tv: TypeValue,
|
|
314
|
+
predicate: (member: TypeValue) => boolean,
|
|
315
|
+
): TypeValue {
|
|
316
|
+
return narrowType(tv, (m) => !predicate(m));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export function getPrimitiveTypeOf(tv: TypeValue): string | undefined {
|
|
320
|
+
if (tv.kind === "literal") {
|
|
321
|
+
const v = tv.value;
|
|
322
|
+
if (v === null) return "object";
|
|
323
|
+
return typeof v;
|
|
324
|
+
}
|
|
325
|
+
if (tv.kind === "primitive") return tv.type;
|
|
326
|
+
if (tv.kind === "object") return "object";
|
|
327
|
+
if (tv.kind === "array" || tv.kind === "tuple") return "object";
|
|
328
|
+
if (tv.kind === "function") return "function";
|
|
329
|
+
if (tv.kind === "promise") return "object";
|
|
330
|
+
if (tv.kind === "instance") return "object";
|
|
331
|
+
return undefined;
|
|
332
|
+
}
|