@xnoxs/flux-lang 3.1.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/CHANGELOG.md +103 -0
- package/README.md +1089 -0
- package/bin/flux.js +1397 -0
- package/dist/flux.cjs.js +6664 -0
- package/dist/flux.esm.js +6674 -0
- package/dist/flux.min.js +263 -0
- package/index.d.ts +202 -0
- package/index.js +26 -0
- package/package.json +77 -0
- package/scripts/build.js +76 -0
- package/src/bundler.js +216 -0
- package/src/checker.js +322 -0
- package/src/codegen.js +785 -0
- package/src/css-preprocessor.js +399 -0
- package/src/formatter.js +140 -0
- package/src/jsx.js +480 -0
- package/src/lexer.js +518 -0
- package/src/linter.js +758 -0
- package/src/mangler.js +280 -0
- package/src/parser.js +1671 -0
- package/src/self/bundler.flux +167 -0
- package/src/self/bundler.js +187 -0
- package/src/self/checker.flux +249 -0
- package/src/self/checker.js +338 -0
- package/src/self/codegen.flux +555 -0
- package/src/self/codegen.js +784 -0
- package/src/self/css-preprocessor.flux +373 -0
- package/src/self/css-preprocessor.js +387 -0
- package/src/self/formatter.flux +93 -0
- package/src/self/formatter.js +114 -0
- package/src/self/jsx.flux +430 -0
- package/src/self/jsx.js +396 -0
- package/src/self/lexer.flux +529 -0
- package/src/self/lexer.js +709 -0
- package/src/self/lexer.stage2.js +700 -0
- package/src/self/linter.flux +515 -0
- package/src/self/linter.js +804 -0
- package/src/self/mangler.flux +253 -0
- package/src/self/mangler.js +348 -0
- package/src/self/parser.flux +1146 -0
- package/src/self/parser.js +1571 -0
- package/src/self/sourcemap.flux +66 -0
- package/src/self/sourcemap.js +72 -0
- package/src/self/stdlib.flux +356 -0
- package/src/self/stdlib.js +396 -0
- package/src/self/test-runner.flux +201 -0
- package/src/self/test-runner.js +132 -0
- package/src/self/transpiler.flux +123 -0
- package/src/self/transpiler.js +83 -0
- package/src/self/type-checker.flux +821 -0
- package/src/self/type-checker.js +1106 -0
- package/src/sourcemap.js +82 -0
- package/src/stdlib.js +436 -0
- package/src/test-runner.js +239 -0
- package/src/transpiler.js +172 -0
- package/src/type-checker.js +1206 -0
|
@@ -0,0 +1,821 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Flux Self-Hosted Type Checker
|
|
3
|
+
// src/self/type-checker.flux — written in Flux, compiled by stage-0
|
|
4
|
+
//
|
|
5
|
+
// Full TypeScript-level type checking engine.
|
|
6
|
+
// Features: structural typing, union/intersection, nullable,
|
|
7
|
+
// generics, utility types, type narrowing, tuples,
|
|
8
|
+
// conditional types, function types, interface enforcement
|
|
9
|
+
// ============================================================
|
|
10
|
+
|
|
11
|
+
// ── Primitive singletons ──────────────────────────────────────────────────────
|
|
12
|
+
export val T_ANY = Object.freeze({ kind: "any" })
|
|
13
|
+
export val T_UNKNOWN = Object.freeze({ kind: "unknown" })
|
|
14
|
+
export val T_NEVER = Object.freeze({ kind: "never" })
|
|
15
|
+
export val T_VOID = Object.freeze({ kind: "void" })
|
|
16
|
+
export val T_NULL = Object.freeze({ kind: "null" })
|
|
17
|
+
export val T_STRING = Object.freeze({ kind: "primitive", name: "String" })
|
|
18
|
+
export val T_INT = Object.freeze({ kind: "primitive", name: "Int" })
|
|
19
|
+
export val T_FLOAT = Object.freeze({ kind: "primitive", name: "Float" })
|
|
20
|
+
export val T_NUMBER = Object.freeze({ kind: "primitive", name: "Number" })
|
|
21
|
+
export val T_BOOL = Object.freeze({ kind: "primitive", name: "Bool" })
|
|
22
|
+
|
|
23
|
+
// ── Type constructors ─────────────────────────────────────────────────────────
|
|
24
|
+
export fn T_UNION(...members):
|
|
25
|
+
val flat = []
|
|
26
|
+
for m in members:
|
|
27
|
+
if m and m.kind == "union":
|
|
28
|
+
for sub in m.members: flat.push(sub)
|
|
29
|
+
else if m:
|
|
30
|
+
flat.push(m)
|
|
31
|
+
val seen = new Set()
|
|
32
|
+
val deduped = []
|
|
33
|
+
for m in flat:
|
|
34
|
+
val k = typeStr(m)
|
|
35
|
+
if not seen.has(k):
|
|
36
|
+
seen.add(k)
|
|
37
|
+
deduped.push(m)
|
|
38
|
+
if deduped.length == 0: return T_NEVER
|
|
39
|
+
if deduped.length == 1: return deduped[0]
|
|
40
|
+
return { kind: "union", members: deduped }
|
|
41
|
+
|
|
42
|
+
export fn T_INTERSECTION(...members):
|
|
43
|
+
val flat = []
|
|
44
|
+
for m in members:
|
|
45
|
+
if m and m.kind == "intersection":
|
|
46
|
+
for sub in m.members: flat.push(sub)
|
|
47
|
+
else if m:
|
|
48
|
+
flat.push(m)
|
|
49
|
+
if flat.length == 1: return flat[0]
|
|
50
|
+
return { kind: "intersection", members: flat }
|
|
51
|
+
|
|
52
|
+
export fn T_ARRAY(elem):
|
|
53
|
+
return { kind: "generic", name: "Array", args: [elem ?? T_UNKNOWN] }
|
|
54
|
+
|
|
55
|
+
export fn T_TUPLE(types):
|
|
56
|
+
return { kind: "tuple", types: types ?? [] }
|
|
57
|
+
|
|
58
|
+
export fn T_NULLABLE(t):
|
|
59
|
+
return T_UNION(t, T_NULL)
|
|
60
|
+
|
|
61
|
+
export fn T_NAMED(name):
|
|
62
|
+
return { kind: "named", name }
|
|
63
|
+
|
|
64
|
+
export fn T_FN(params, ret):
|
|
65
|
+
return { kind: "fn", params: params ?? [], ret: ret ?? T_VOID }
|
|
66
|
+
|
|
67
|
+
export fn T_OBJECT(shape):
|
|
68
|
+
return { kind: "object", shape: shape ?? new Map() }
|
|
69
|
+
|
|
70
|
+
export fn T_RECORD(key, val_):
|
|
71
|
+
return { kind: "record", key: key ?? T_STRING, val: val_ ?? T_UNKNOWN }
|
|
72
|
+
|
|
73
|
+
export fn T_LITERAL(value, kind):
|
|
74
|
+
return { kind: "literal", value, prim: kind }
|
|
75
|
+
|
|
76
|
+
// ── Type → display string ─────────────────────────────────────────────────────
|
|
77
|
+
export fn typeStr(t):
|
|
78
|
+
if not t: return "unknown"
|
|
79
|
+
if t.kind == "any": return "Any"
|
|
80
|
+
if t.kind == "unknown": return "Unknown"
|
|
81
|
+
if t.kind == "never": return "Never"
|
|
82
|
+
if t.kind == "void": return "Void"
|
|
83
|
+
if t.kind == "null": return "Null"
|
|
84
|
+
if t.kind == "primitive": return t.name
|
|
85
|
+
if t.kind == "literal": return JSON.stringify(t.value)
|
|
86
|
+
if t.kind == "union": return t.members.map(typeStr).join(" | ")
|
|
87
|
+
if t.kind == "intersection": return t.members.map(typeStr).join(" & ")
|
|
88
|
+
if t.kind == "tuple": return "[" + t.types.map(typeStr).join(", ") + "]"
|
|
89
|
+
if t.kind == "object":
|
|
90
|
+
val entries = []
|
|
91
|
+
for pair in (t.shape ?? new Map()).entries():
|
|
92
|
+
entries.push(pair[0] + ': ' + typeStr(pair[1]))
|
|
93
|
+
return '{' + entries.join(', ') + '}'
|
|
94
|
+
if t.kind == "record": return "Record<" + typeStr(t.key) + ", " + typeStr(t.val) + ">"
|
|
95
|
+
if t.kind == "generic":
|
|
96
|
+
if t.args and t.args.length > 0:
|
|
97
|
+
return t.name + "<" + t.args.map(typeStr).join(", ") + ">"
|
|
98
|
+
return t.name
|
|
99
|
+
if t.kind == "named": return t.name
|
|
100
|
+
if t.kind == "fn":
|
|
101
|
+
return "(" + (t.params ?? []).map(typeStr).join(", ") + ") -> " + typeStr(t.ret)
|
|
102
|
+
if t.kind == "keyof": return "keyof " + typeStr(t.inner)
|
|
103
|
+
if t.kind == "conditional":
|
|
104
|
+
return typeStr(t.check) + " extends " + typeStr(t.extends) + " ? " + typeStr(t.then) + " : " + typeStr(t.else)
|
|
105
|
+
return "unknown"
|
|
106
|
+
|
|
107
|
+
// ── Split at top-level separator ──────────────────────────────────────────────
|
|
108
|
+
fn splitAtTopLevel(str, sep):
|
|
109
|
+
val parts = []
|
|
110
|
+
var depth = 0
|
|
111
|
+
var start = 0
|
|
112
|
+
var i = 0
|
|
113
|
+
while i < str.length:
|
|
114
|
+
val c = str[i]
|
|
115
|
+
if '<([{'.includes(c): depth = depth + 1
|
|
116
|
+
else if '>)]}'.includes(c): depth = depth - 1
|
|
117
|
+
else if depth == 0 and str.slice(i, i + sep.length) == sep:
|
|
118
|
+
parts.push(str.slice(start, i))
|
|
119
|
+
start = i + sep.length
|
|
120
|
+
i = i + sep.length - 1
|
|
121
|
+
i = i + 1
|
|
122
|
+
parts.push(str.slice(start))
|
|
123
|
+
return parts.filter(p -> p.trim().length > 0)
|
|
124
|
+
|
|
125
|
+
// ── Primitive type map ─────────────────────────────────────────────────────────
|
|
126
|
+
val PRIMITIVE_MAP = {
|
|
127
|
+
String: T_STRING, string: T_STRING,
|
|
128
|
+
Int: T_INT, int: T_INT,
|
|
129
|
+
Float: T_FLOAT, float: T_FLOAT,
|
|
130
|
+
Number: T_NUMBER, number: T_NUMBER,
|
|
131
|
+
Bool: T_BOOL, boolean: T_BOOL, Boolean: T_BOOL, bool: T_BOOL,
|
|
132
|
+
Void: T_VOID, void: T_VOID,
|
|
133
|
+
Never: T_NEVER, never: T_NEVER,
|
|
134
|
+
Any: T_ANY, any: T_ANY,
|
|
135
|
+
Unknown: T_UNKNOWN, unknown: T_UNKNOWN,
|
|
136
|
+
Null: T_NULL, null: T_NULL, undefined: T_VOID,
|
|
137
|
+
Object: T_NAMED("Object"), object: T_NAMED("Object"),
|
|
138
|
+
Symbol: T_NAMED("Symbol"), symbol: T_NAMED("Symbol"),
|
|
139
|
+
BigInt: T_NAMED("BigInt"), bigint: T_NAMED("BigInt"),
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ── Parse annotation string → Type ────────────────────────────────────────────
|
|
143
|
+
export fn parseAnnotation(str):
|
|
144
|
+
if not str: return null
|
|
145
|
+
var s = str.trim()
|
|
146
|
+
|
|
147
|
+
// Strip outer parens
|
|
148
|
+
if s.startsWith("(") and s.endsWith(")"):
|
|
149
|
+
s = s.slice(1, -1).trim()
|
|
150
|
+
|
|
151
|
+
// Union
|
|
152
|
+
val unionParts = splitAtTopLevel(s, " | ")
|
|
153
|
+
if unionParts.length > 1:
|
|
154
|
+
return T_UNION(...unionParts.map(parseAnnotation))
|
|
155
|
+
|
|
156
|
+
// Intersection
|
|
157
|
+
val interParts = splitAtTopLevel(s, " & ")
|
|
158
|
+
if interParts.length > 1:
|
|
159
|
+
return T_INTERSECTION(...interParts.map(parseAnnotation))
|
|
160
|
+
|
|
161
|
+
// Conditional type: T extends U ? A : B
|
|
162
|
+
val condMatch = s.match(/^(\w+)\s+extends\s+(.+?)\s*\?\s*(.+?)\s*:\s*(.+)$/)
|
|
163
|
+
if condMatch:
|
|
164
|
+
return {
|
|
165
|
+
kind: "conditional",
|
|
166
|
+
check: parseAnnotation(condMatch[1]),
|
|
167
|
+
extends: parseAnnotation(condMatch[2]),
|
|
168
|
+
then: parseAnnotation(condMatch[3]),
|
|
169
|
+
else: parseAnnotation(condMatch[4]),
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// keyof T
|
|
173
|
+
if s.startsWith("keyof "):
|
|
174
|
+
return { kind: "keyof", inner: parseAnnotation(s.slice(6)) }
|
|
175
|
+
|
|
176
|
+
// typeof x
|
|
177
|
+
if s.startsWith("typeof "):
|
|
178
|
+
return { kind: "typeof", name: s.slice(7) }
|
|
179
|
+
|
|
180
|
+
// readonly T
|
|
181
|
+
if s.startsWith("readonly "):
|
|
182
|
+
return parseAnnotation(s.slice(9))
|
|
183
|
+
|
|
184
|
+
// infer T → Any
|
|
185
|
+
if s.startsWith("infer "):
|
|
186
|
+
return T_ANY
|
|
187
|
+
|
|
188
|
+
// Function type: fn(T1, T2) -> Ret OR (T1, T2) -> Ret
|
|
189
|
+
val fnMatch = s.match(/^fn\(([^)]*)\)\s*->\s*(.+)$/) ?? s.match(/^\(([^)]*)\)\s*->\s*(.+)$/)
|
|
190
|
+
if fnMatch:
|
|
191
|
+
val paramStr = fnMatch[1].trim()
|
|
192
|
+
val retStr = fnMatch[2].trim()
|
|
193
|
+
val params = paramStr ? splitAtTopLevel(paramStr, ", ").map(p -> parseAnnotation(p.slice(p.indexOf(":") >= 0 ? p.indexOf(":") + 1 : 0).trim())) : []
|
|
194
|
+
return T_FN(params, parseAnnotation(retStr))
|
|
195
|
+
|
|
196
|
+
// Nullable shorthand: String?
|
|
197
|
+
if s.endsWith("?"):
|
|
198
|
+
return T_NULLABLE(parseAnnotation(s.slice(0, -1)))
|
|
199
|
+
|
|
200
|
+
// Array shorthand: String[]
|
|
201
|
+
if s.endsWith("[]"):
|
|
202
|
+
return T_ARRAY(parseAnnotation(s.slice(0, -2)))
|
|
203
|
+
|
|
204
|
+
// Tuple: [String, Int, Bool]
|
|
205
|
+
if s.startsWith("[") and s.endsWith("]"):
|
|
206
|
+
val inner = s.slice(1, -1)
|
|
207
|
+
if not inner.trim(): return T_TUPLE([])
|
|
208
|
+
val parts = splitAtTopLevel(inner, ", ")
|
|
209
|
+
return T_TUPLE(parts.map(p -> parseAnnotation(p.replace(/^\.\.\./, "").trim())))
|
|
210
|
+
|
|
211
|
+
// Inline object type: { key: Type, key2?: Type }
|
|
212
|
+
if s.startsWith("{") and s.endsWith("}"):
|
|
213
|
+
val inner = s.slice(1, -1).trim()
|
|
214
|
+
val shape = new Map()
|
|
215
|
+
if inner:
|
|
216
|
+
val pairs = splitAtTopLevel(inner, ", ")
|
|
217
|
+
for pair in pairs:
|
|
218
|
+
val idxMatch = pair.match(/^\[(\w+):\s*\w+\]:\s*(.+)$/)
|
|
219
|
+
if idxMatch:
|
|
220
|
+
shape.set("[index]", parseAnnotation(idxMatch[2]))
|
|
221
|
+
continue
|
|
222
|
+
val colonIdx = pair.indexOf(":")
|
|
223
|
+
if colonIdx >= 0:
|
|
224
|
+
val rawKey = pair.slice(0, colonIdx).trim().replace(/^readonly\s+/, "")
|
|
225
|
+
val key = rawKey.replace(/\?$/, "")
|
|
226
|
+
val opt = rawKey.endsWith("?")
|
|
227
|
+
val valType = parseAnnotation(pair.slice(colonIdx + 1).trim())
|
|
228
|
+
shape.set(key, opt ? T_UNION(valType, T_VOID) : valType)
|
|
229
|
+
return T_OBJECT(shape)
|
|
230
|
+
|
|
231
|
+
// Generic / Utility types: Array<T>, Record<K,V>, Partial<T> etc.
|
|
232
|
+
val genMatch = s.match(/^(\w+)<(.+)>$/s)
|
|
233
|
+
if genMatch:
|
|
234
|
+
val gname = genMatch[1]
|
|
235
|
+
val rawArgs = splitAtTopLevel(genMatch[2], ", ")
|
|
236
|
+
val args = rawArgs.map(a -> parseAnnotation(a.trim()))
|
|
237
|
+
if gname == "Array" or gname == "List" or gname == "ReadonlyArray":
|
|
238
|
+
return T_ARRAY(args[0] ?? T_UNKNOWN)
|
|
239
|
+
if gname == "NonNullable":
|
|
240
|
+
return resolveNonNullable(args[0])
|
|
241
|
+
if gname == "Partial": return { kind: "utility", util: "Partial", inner: args[0] }
|
|
242
|
+
if gname == "Required": return { kind: "utility", util: "Required", inner: args[0] }
|
|
243
|
+
if gname == "Readonly": return args[0]
|
|
244
|
+
if gname == "Record": return T_RECORD(args[0] ?? T_STRING, args[1] ?? T_UNKNOWN)
|
|
245
|
+
if gname == "Pick": return { kind: "utility", util: "Pick", inner: args[0], keys: args[1] }
|
|
246
|
+
if gname == "Omit": return { kind: "utility", util: "Omit", inner: args[0], keys: args[1] }
|
|
247
|
+
if gname == "Exclude": return { kind: "utility", util: "Exclude", inner: args[0], keys: args[1] }
|
|
248
|
+
if gname == "Extract": return { kind: "utility", util: "Extract", inner: args[0], keys: args[1] }
|
|
249
|
+
if gname == "ReturnType":return { kind: "utility", util: "ReturnType", inner: args[0] }
|
|
250
|
+
if gname == "Parameters":return { kind: "utility", util: "Parameters", inner: args[0] }
|
|
251
|
+
if gname == "Awaited": return { kind: "utility", util: "Awaited", inner: args[0] }
|
|
252
|
+
return { kind: "generic", name: gname, args }
|
|
253
|
+
|
|
254
|
+
// Primitive or named
|
|
255
|
+
if PRIMITIVE_MAP[s]: return PRIMITIVE_MAP[s]
|
|
256
|
+
return T_NAMED(s)
|
|
257
|
+
|
|
258
|
+
fn resolveNonNullable(t):
|
|
259
|
+
if not t: return T_UNKNOWN
|
|
260
|
+
if t.kind == "union":
|
|
261
|
+
return T_UNION(...t.members.filter(m -> m.kind != "null" and m.kind != "void"))
|
|
262
|
+
if t.kind == "null" or t.kind == "void": return T_NEVER
|
|
263
|
+
return t
|
|
264
|
+
|
|
265
|
+
// ── Basic assignability ───────────────────────────────────────────────────────
|
|
266
|
+
fn basicAssignable(from_, to_):
|
|
267
|
+
if not from_ or not to_: return true
|
|
268
|
+
if to_.kind == "any": return true
|
|
269
|
+
if from_.kind == "any": return true
|
|
270
|
+
if from_.kind == "never": return true
|
|
271
|
+
if to_.kind == "unknown": return true
|
|
272
|
+
if from_.kind == "void" and to_.kind == "void": return true
|
|
273
|
+
if from_.kind == "null" and to_.kind == "null": return true
|
|
274
|
+
|
|
275
|
+
// Null assignable to nullable union
|
|
276
|
+
if from_.kind == "null" and to_.kind == "union":
|
|
277
|
+
return to_.members.some(m -> m.kind == "null" or m.kind == "any" or m.kind == "void")
|
|
278
|
+
|
|
279
|
+
// From union: all members assignable to target
|
|
280
|
+
if from_.kind == "union":
|
|
281
|
+
return from_.members.every(m -> basicAssignable(m, to_))
|
|
282
|
+
// To union: from assignable to at least one
|
|
283
|
+
if to_.kind == "union":
|
|
284
|
+
return to_.members.some(m -> basicAssignable(from_, m))
|
|
285
|
+
|
|
286
|
+
// Intersection
|
|
287
|
+
if from_.kind == "intersection":
|
|
288
|
+
return from_.members.some(m -> basicAssignable(m, to_))
|
|
289
|
+
if to_.kind == "intersection":
|
|
290
|
+
return to_.members.every(m -> basicAssignable(from_, m))
|
|
291
|
+
|
|
292
|
+
// Primitives
|
|
293
|
+
if from_.kind == "primitive" and to_.kind == "primitive":
|
|
294
|
+
if from_.name == to_.name: return true
|
|
295
|
+
if to_.name == "Number" and (from_.name == "Int" or from_.name == "Float" or from_.name == "Number"): return true
|
|
296
|
+
if to_.name == "Float" and from_.name == "Int": return true
|
|
297
|
+
if from_.name == "Bool" and to_.name == "Boolean": return true
|
|
298
|
+
if from_.name == "Boolean" and to_.name == "Bool": return true
|
|
299
|
+
return false
|
|
300
|
+
|
|
301
|
+
// Literal to primitive
|
|
302
|
+
if from_.kind == "literal" and to_.kind == "primitive":
|
|
303
|
+
val lmap = { string: "String", number: "Number", boolean: "Bool" }
|
|
304
|
+
return lmap[from_.prim] == to_.name or (from_.prim == "number" and (to_.name == "Int" or to_.name == "Float" or to_.name == "Number"))
|
|
305
|
+
|
|
306
|
+
// Named types
|
|
307
|
+
if from_.kind == "named" and to_.kind == "named":
|
|
308
|
+
return from_.name == to_.name or to_.name == "Object"
|
|
309
|
+
|
|
310
|
+
// Generic types (covariant args)
|
|
311
|
+
if from_.kind == "generic" and to_.kind == "generic":
|
|
312
|
+
val arrayNames = new Set(["Array", "List", "ReadonlyArray"])
|
|
313
|
+
if from_.name != to_.name:
|
|
314
|
+
if not (arrayNames.has(from_.name) and arrayNames.has(to_.name)): return false
|
|
315
|
+
if from_.args.length != to_.args.length: return false
|
|
316
|
+
return from_.args.every((a, i) -> basicAssignable(a, to_.args[i]))
|
|
317
|
+
|
|
318
|
+
// Tuple types
|
|
319
|
+
if from_.kind == "tuple" and to_.kind == "tuple":
|
|
320
|
+
if from_.types.length != to_.types.length: return false
|
|
321
|
+
return from_.types.every((t, i) -> basicAssignable(t, to_.types[i]))
|
|
322
|
+
// Array to tuple
|
|
323
|
+
if from_.kind == "generic" and from_.name == "Array" and to_.kind == "tuple":
|
|
324
|
+
val elemType = from_.args[0] ?? T_UNKNOWN
|
|
325
|
+
if elemType.kind == "unknown": return true
|
|
326
|
+
return to_.types.every(t -> basicAssignable(elemType, t))
|
|
327
|
+
|
|
328
|
+
// Object structural
|
|
329
|
+
if from_.kind == "object" and to_.kind == "object":
|
|
330
|
+
for pair in to_.shape.entries():
|
|
331
|
+
val key = pair[0]
|
|
332
|
+
val toType = pair[1]
|
|
333
|
+
if not from_.shape.has(key): return false
|
|
334
|
+
if not basicAssignable(from_.shape.get(key), toType): return false
|
|
335
|
+
return true
|
|
336
|
+
|
|
337
|
+
// Object to named (structural checked in class)
|
|
338
|
+
if from_.kind == "object" and to_.kind == "named": return true
|
|
339
|
+
|
|
340
|
+
// Function type
|
|
341
|
+
if from_.kind == "fn" and to_.kind == "fn":
|
|
342
|
+
if to_.ret and from_.ret: return basicAssignable(from_.ret, to_.ret)
|
|
343
|
+
return true
|
|
344
|
+
|
|
345
|
+
// Record
|
|
346
|
+
if from_.kind == "record" and to_.kind == "record":
|
|
347
|
+
return basicAssignable(from_.val, to_.val)
|
|
348
|
+
|
|
349
|
+
// Utility types
|
|
350
|
+
if to_.kind == "utility": return true
|
|
351
|
+
if from_.kind == "utility": return true
|
|
352
|
+
|
|
353
|
+
return false
|
|
354
|
+
|
|
355
|
+
// ── Type merging ──────────────────────────────────────────────────────────────
|
|
356
|
+
fn mergeTypes(a, b):
|
|
357
|
+
if not a or a.kind == "unknown": return b ?? T_UNKNOWN
|
|
358
|
+
if not b or b.kind == "unknown": return a
|
|
359
|
+
if typeStr(a) == typeStr(b): return a
|
|
360
|
+
if a.kind == "any" or b.kind == "any": return T_ANY
|
|
361
|
+
return T_UNION(a, b)
|
|
362
|
+
|
|
363
|
+
// ── Type Environment ──────────────────────────────────────────────────────────
|
|
364
|
+
export class TypeEnv:
|
|
365
|
+
parent: any
|
|
366
|
+
vars: any
|
|
367
|
+
retType: any
|
|
368
|
+
isAsync: bool
|
|
369
|
+
|
|
370
|
+
fn set(name, type_):
|
|
371
|
+
self.vars.set(name, type_)
|
|
372
|
+
|
|
373
|
+
fn has(name):
|
|
374
|
+
var e = self
|
|
375
|
+
while e:
|
|
376
|
+
if e.vars.has(name): return true
|
|
377
|
+
e = e.parent
|
|
378
|
+
return false
|
|
379
|
+
|
|
380
|
+
fn get(name):
|
|
381
|
+
var env = self
|
|
382
|
+
while env:
|
|
383
|
+
if env.vars.has(name): return env.vars.get(name)
|
|
384
|
+
env = env.parent
|
|
385
|
+
return null
|
|
386
|
+
|
|
387
|
+
fn child():
|
|
388
|
+
return new TypeEnv(self, new Map(), self.retType, self.isAsync)
|
|
389
|
+
|
|
390
|
+
fn childFn(retType, isAsync_):
|
|
391
|
+
return new TypeEnv(self, new Map(), retType, isAsync_ ?? false)
|
|
392
|
+
|
|
393
|
+
fn narrow(name, type_):
|
|
394
|
+
val c = new TypeEnv(self, new Map(), self.retType, self.isAsync)
|
|
395
|
+
if name and type_: c.vars.set(name, type_)
|
|
396
|
+
return c
|
|
397
|
+
|
|
398
|
+
// ── Type Check Error ──────────────────────────────────────────────────────────
|
|
399
|
+
class TypeCheckError:
|
|
400
|
+
message: any
|
|
401
|
+
name: any
|
|
402
|
+
hint: any
|
|
403
|
+
line: any
|
|
404
|
+
col: any
|
|
405
|
+
|
|
406
|
+
// ── Main Type Checker ─────────────────────────────────────────────────────────
|
|
407
|
+
export class FluxTypeChecker:
|
|
408
|
+
errors: any[]
|
|
409
|
+
warnings: any[]
|
|
410
|
+
interfaces: any
|
|
411
|
+
types: any
|
|
412
|
+
classes: any
|
|
413
|
+
enums: any
|
|
414
|
+
|
|
415
|
+
fn _err(msg, loc, hint):
|
|
416
|
+
val e = new TypeCheckError()
|
|
417
|
+
e.message = msg
|
|
418
|
+
e.name = "TypeError"
|
|
419
|
+
e.hint = hint ?? null
|
|
420
|
+
if loc:
|
|
421
|
+
e.line = loc.line
|
|
422
|
+
e.col = loc.col
|
|
423
|
+
self.errors.push(e)
|
|
424
|
+
|
|
425
|
+
fn _warn(msg, loc, hint):
|
|
426
|
+
val w = new TypeCheckError()
|
|
427
|
+
w.message = msg
|
|
428
|
+
w.name = "TypeError"
|
|
429
|
+
w.hint = hint ?? null
|
|
430
|
+
if loc:
|
|
431
|
+
w.line = loc.line
|
|
432
|
+
w.col = loc.col
|
|
433
|
+
self.warnings.push(w)
|
|
434
|
+
|
|
435
|
+
fn check(ast):
|
|
436
|
+
self.errors = []
|
|
437
|
+
self.warnings = []
|
|
438
|
+
self.interfaces = new Map()
|
|
439
|
+
self.types = new Map()
|
|
440
|
+
self.classes = new Map()
|
|
441
|
+
self.enums = new Map()
|
|
442
|
+
|
|
443
|
+
self._collectDeclarations(ast.body)
|
|
444
|
+
self._validateImplementations()
|
|
445
|
+
|
|
446
|
+
val env = new TypeEnv(null, new Map(), null, false)
|
|
447
|
+
self._registerBuiltins(env)
|
|
448
|
+
self._checkStmts(ast.body, env)
|
|
449
|
+
|
|
450
|
+
return { errors: self.errors, warnings: self.warnings }
|
|
451
|
+
|
|
452
|
+
fn _collectDeclarations(stmts):
|
|
453
|
+
for node in stmts:
|
|
454
|
+
val n = node.type == "ExportDecl" ? node.decl : node
|
|
455
|
+
if not n: continue
|
|
456
|
+
if n.type == "InterfaceDecl": self.interfaces.set(n.name, n)
|
|
457
|
+
else if n.type == "TypeDecl": self.types.set(n.name, n)
|
|
458
|
+
else if n.type == "ClassDecl": self.classes.set(n.name, n)
|
|
459
|
+
else if n.type == "EnumDecl": self.enums.set(n.name, n)
|
|
460
|
+
|
|
461
|
+
fn _validateImplementations():
|
|
462
|
+
for clsPair in self.classes.entries():
|
|
463
|
+
val cls = clsPair[1]
|
|
464
|
+
for ifaceName in (cls.interfaces ?? []):
|
|
465
|
+
self._checkInterfaceImpl(cls, ifaceName)
|
|
466
|
+
|
|
467
|
+
fn _checkInterfaceImpl(cls, ifaceName):
|
|
468
|
+
val iface = self.interfaces.get(ifaceName)
|
|
469
|
+
if not iface:
|
|
470
|
+
self._warn(
|
|
471
|
+
"Class '" + cls.name + "' implements unknown interface '" + ifaceName + "'",
|
|
472
|
+
cls.loc,
|
|
473
|
+
"Define 'interface " + ifaceName + "' before use"
|
|
474
|
+
)
|
|
475
|
+
return
|
|
476
|
+
for member in iface.members:
|
|
477
|
+
if member.kind == "method":
|
|
478
|
+
val impl = cls.methods.find(m -> m.name == member.name)
|
|
479
|
+
if not impl:
|
|
480
|
+
self._err(
|
|
481
|
+
"Class '" + cls.name + "' does not implement method '" + member.name + "()' required by '" + ifaceName + "'",
|
|
482
|
+
cls.loc,
|
|
483
|
+
"Add 'fn " + member.name + "(...)' to the class"
|
|
484
|
+
)
|
|
485
|
+
else if member.kind == "field" and not member.optional:
|
|
486
|
+
val hasField = cls.fields.find(f -> f.name == member.name)
|
|
487
|
+
val hasMethod = cls.methods.find(m -> m.name == member.name)
|
|
488
|
+
if not hasField and not hasMethod:
|
|
489
|
+
self._err(
|
|
490
|
+
"Class '" + cls.name + "' is missing field '" + member.name + "' required by '" + ifaceName + "'",
|
|
491
|
+
cls.loc,
|
|
492
|
+
"Add '" + member.name + ": " + (member.typeAnn ?? "Any") + "' to the class"
|
|
493
|
+
)
|
|
494
|
+
for superIface in (iface.superInterfaces ?? []):
|
|
495
|
+
self._checkInterfaceImpl(cls, superIface)
|
|
496
|
+
|
|
497
|
+
fn _isStructurallyCompatible(objShape, typeName):
|
|
498
|
+
val iface = self.interfaces.get(typeName)
|
|
499
|
+
if not iface:
|
|
500
|
+
val cls = self.classes.get(typeName)
|
|
501
|
+
if not cls: return false
|
|
502
|
+
for field in cls.fields:
|
|
503
|
+
if not objShape.has(field.name) and not field.optional: return false
|
|
504
|
+
return true
|
|
505
|
+
for member in iface.members:
|
|
506
|
+
if member.kind == "field" and not member.optional:
|
|
507
|
+
if not objShape.has(member.name): return false
|
|
508
|
+
return true
|
|
509
|
+
|
|
510
|
+
fn _isAssignable(from_, to_):
|
|
511
|
+
if not from_ or not to_: return true
|
|
512
|
+
if to_.kind == "any": return true
|
|
513
|
+
if from_.kind == "any": return true
|
|
514
|
+
if from_.kind == "never": return true
|
|
515
|
+
if to_.kind == "unknown": return true
|
|
516
|
+
|
|
517
|
+
// Structural: object literal vs interface/class
|
|
518
|
+
if from_.kind == "object" and to_.kind == "named":
|
|
519
|
+
return self._isStructurallyCompatible(from_.shape, to_.name)
|
|
520
|
+
|
|
521
|
+
// Named satisfies interface (structural check)
|
|
522
|
+
if from_.kind == "named" and to_.kind == "named":
|
|
523
|
+
if from_.name == to_.name: return true
|
|
524
|
+
if to_.name == "Object": return true
|
|
525
|
+
val cls = self.classes.get(from_.name)
|
|
526
|
+
if cls and (cls.interfaces ?? []).includes(to_.name): return true
|
|
527
|
+
if cls and cls.superClass == to_.name: return true
|
|
528
|
+
val iface = self.interfaces.get(from_.name)
|
|
529
|
+
if iface and (iface.superInterfaces ?? []).includes(to_.name): return true
|
|
530
|
+
return false
|
|
531
|
+
|
|
532
|
+
// Utility types
|
|
533
|
+
if to_.kind == "utility":
|
|
534
|
+
if to_.util == "Partial": return true
|
|
535
|
+
if to_.util == "Required": return self._isAssignable(from_, to_.inner)
|
|
536
|
+
if to_.util == "NonNullable":return from_.kind != "null" and from_.kind != "void"
|
|
537
|
+
return true
|
|
538
|
+
if from_.kind == "utility": return true
|
|
539
|
+
|
|
540
|
+
return basicAssignable(from_, to_)
|
|
541
|
+
|
|
542
|
+
fn _analyzeNarrowingCondition(condExpr, env):
|
|
543
|
+
if not condExpr: return null
|
|
544
|
+
|
|
545
|
+
// x != null / x !== null
|
|
546
|
+
if condExpr.type == "BinaryExpr" and (condExpr.op == "!=" or condExpr.op == "!==") and condExpr.left.type == "Identifier" and condExpr.right.type == "NullLit":
|
|
547
|
+
val name = condExpr.left.name
|
|
548
|
+
val t = env.get(name)
|
|
549
|
+
if t and t.kind == "union":
|
|
550
|
+
val narrowed = T_UNION(...t.members.filter(m -> m.kind != "null" and m.kind != "void"))
|
|
551
|
+
return { varName: name, trueType: narrowed, falseType: T_UNION(T_NULL, T_VOID) }
|
|
552
|
+
|
|
553
|
+
// typeof x === "string"
|
|
554
|
+
if condExpr.type == "BinaryExpr" and (condExpr.op == "===" or condExpr.op == "==") and condExpr.left.type == "TypeofExpr" and condExpr.left.operand.type == "Identifier" and condExpr.right.type == "StringLit":
|
|
555
|
+
val name = condExpr.left.operand.name
|
|
556
|
+
val typeMap = { string: T_STRING, number: T_NUMBER, boolean: T_BOOL, undefined: T_VOID }
|
|
557
|
+
val narrowedTrue = typeMap[condExpr.right.value] ?? T_UNKNOWN
|
|
558
|
+
return { varName: name, trueType: narrowedTrue, falseType: env.get(name) ?? T_UNKNOWN }
|
|
559
|
+
|
|
560
|
+
// x instanceof Class
|
|
561
|
+
if condExpr.type == "BinaryExpr" and condExpr.op == "instanceof" and condExpr.left.type == "Identifier":
|
|
562
|
+
val name = condExpr.left.name
|
|
563
|
+
val cls = condExpr.right.type == "Identifier" ? condExpr.right.name : null
|
|
564
|
+
if cls: return { varName: name, trueType: T_NAMED(cls), falseType: env.get(name) ?? T_UNKNOWN }
|
|
565
|
+
|
|
566
|
+
// Boolean narrowing: x (truthy)
|
|
567
|
+
if condExpr.type == "Identifier":
|
|
568
|
+
val name = condExpr.name
|
|
569
|
+
val t = env.get(name)
|
|
570
|
+
if t and t.kind == "union":
|
|
571
|
+
val narrowed = T_UNION(...t.members.filter(m -> m.kind != "null" and m.kind != "void"))
|
|
572
|
+
return { varName: name, trueType: narrowed, falseType: T_UNION(T_NULL, T_VOID) }
|
|
573
|
+
|
|
574
|
+
return null
|
|
575
|
+
|
|
576
|
+
fn _registerBuiltins(env):
|
|
577
|
+
env.set("console", T_NAMED("Console"))
|
|
578
|
+
env.set("process", T_NAMED("Process"))
|
|
579
|
+
env.set("Math", T_NAMED("Math"))
|
|
580
|
+
env.set("JSON", T_NAMED("JSON"))
|
|
581
|
+
env.set("Date", T_NAMED("Date"))
|
|
582
|
+
env.set("Promise", T_NAMED("Promise"))
|
|
583
|
+
env.set("Error", T_NAMED("Error"))
|
|
584
|
+
env.set("Buffer", T_NAMED("Buffer"))
|
|
585
|
+
env.set("RegExp", T_NAMED("RegExp"))
|
|
586
|
+
env.set("Map", T_NAMED("Map"))
|
|
587
|
+
env.set("Set", T_NAMED("Set"))
|
|
588
|
+
env.set("WeakMap", T_NAMED("WeakMap"))
|
|
589
|
+
env.set("WeakSet", T_NAMED("WeakSet"))
|
|
590
|
+
env.set("Symbol", T_NAMED("Symbol"))
|
|
591
|
+
env.set("Object", T_NAMED("Object"))
|
|
592
|
+
env.set("Array", T_NAMED("Array"))
|
|
593
|
+
env.set("String", T_NAMED("String"))
|
|
594
|
+
env.set("Number", T_NAMED("Number"))
|
|
595
|
+
env.set("Boolean", T_NAMED("Boolean"))
|
|
596
|
+
env.set("Function", T_NAMED("Function"))
|
|
597
|
+
env.set("parseInt", T_FN([T_STRING, T_INT], T_INT))
|
|
598
|
+
env.set("parseFloat", T_FN([T_STRING], T_FLOAT))
|
|
599
|
+
env.set("isNaN", T_FN([T_NUMBER], T_BOOL))
|
|
600
|
+
env.set("isFinite", T_FN([T_NUMBER], T_BOOL))
|
|
601
|
+
env.set("require", T_FN([T_STRING], T_ANY))
|
|
602
|
+
env.set("fetch", T_FN([T_STRING], T_ANY))
|
|
603
|
+
env.set("print", T_FN([], T_VOID))
|
|
604
|
+
env.set("undefined", T_VOID)
|
|
605
|
+
env.set("Infinity", T_NUMBER)
|
|
606
|
+
env.set("NaN", T_NUMBER)
|
|
607
|
+
env.set("globalThis", T_NAMED("Object"))
|
|
608
|
+
|
|
609
|
+
fn _inferType(node, env):
|
|
610
|
+
if not node: return T_UNKNOWN
|
|
611
|
+
if node.type == "NumberLit":
|
|
612
|
+
if String(node.value).includes("."): return T_FLOAT
|
|
613
|
+
return T_INT
|
|
614
|
+
if node.type == "StringLit": return T_STRING
|
|
615
|
+
if node.type == "BoolLit": return T_BOOL
|
|
616
|
+
if node.type == "NullLit": return T_NULL
|
|
617
|
+
if node.type == "Identifier":
|
|
618
|
+
val t = env.get(node.name)
|
|
619
|
+
return t ?? T_UNKNOWN
|
|
620
|
+
if node.type == "ArrayExpr":
|
|
621
|
+
if node.items.length == 0: return T_ARRAY(T_UNKNOWN)
|
|
622
|
+
val elemTypes = node.items.map(i -> i ? self._inferType(i, env) : T_UNKNOWN)
|
|
623
|
+
val merged = elemTypes.reduce((a, b) -> mergeTypes(a, b), T_UNKNOWN)
|
|
624
|
+
return T_ARRAY(merged)
|
|
625
|
+
if node.type == "ObjectExpr":
|
|
626
|
+
val shape = new Map()
|
|
627
|
+
for pair in node.pairs:
|
|
628
|
+
if not pair.spread and pair.key:
|
|
629
|
+
shape.set(pair.key, pair.value ? self._inferType(pair.value, env) : T_UNKNOWN)
|
|
630
|
+
return T_OBJECT(shape)
|
|
631
|
+
if node.type == "LambdaExpr":
|
|
632
|
+
val paramTypes = node.params.map(p -> p.typeAnn ? parseAnnotation(p.typeAnn) : T_UNKNOWN)
|
|
633
|
+
val retType = node.retType ? parseAnnotation(node.retType) : T_UNKNOWN
|
|
634
|
+
return T_FN(paramTypes, retType)
|
|
635
|
+
if node.type == "CallExpr" or node.type == "OptCallExpr":
|
|
636
|
+
val calleeType = self._inferType(node.callee, env)
|
|
637
|
+
if calleeType.kind == "fn": return calleeType.ret ?? T_UNKNOWN
|
|
638
|
+
return T_UNKNOWN
|
|
639
|
+
if node.type == "MemberExpr" or node.type == "OptMemberExpr":
|
|
640
|
+
return T_UNKNOWN
|
|
641
|
+
if node.type == "TernaryExpr":
|
|
642
|
+
val thenType = self._inferType(node.then, env)
|
|
643
|
+
val elseType = self._inferType(node.else_, env)
|
|
644
|
+
return mergeTypes(thenType, elseType)
|
|
645
|
+
if node.type == "BinaryExpr":
|
|
646
|
+
if node.op == "+" or node.op == "-" or node.op == "*" or node.op == "/" or node.op == "%":
|
|
647
|
+
return T_NUMBER
|
|
648
|
+
if node.op == "==" or node.op == "!=" or node.op == "===" or node.op == "!==" or node.op == "<" or node.op == ">" or node.op == "<=" or node.op == ">=":
|
|
649
|
+
return T_BOOL
|
|
650
|
+
if node.op == "and" or node.op == "or" or node.op == "&&" or node.op == "||":
|
|
651
|
+
return T_BOOL
|
|
652
|
+
return T_UNKNOWN
|
|
653
|
+
if node.type == "AwaitExpr":
|
|
654
|
+
val inner = self._inferType(node.operand, env)
|
|
655
|
+
return inner
|
|
656
|
+
if node.type == "NewExpr":
|
|
657
|
+
if node.callee and node.callee.type == "Identifier":
|
|
658
|
+
return T_NAMED(node.callee.name)
|
|
659
|
+
return T_UNKNOWN
|
|
660
|
+
if node.type == "TemplateLit": return T_STRING
|
|
661
|
+
if node.type == "CastExpr" or node.type == "AsConstExpr":
|
|
662
|
+
if node.typeAnn: return parseAnnotation(node.typeAnn)
|
|
663
|
+
return self._inferType(node.expr, env)
|
|
664
|
+
return T_UNKNOWN
|
|
665
|
+
|
|
666
|
+
fn _checkStmts(stmts, env):
|
|
667
|
+
for node in stmts: self._checkStmt(node, env)
|
|
668
|
+
|
|
669
|
+
fn _checkStmt(node, env):
|
|
670
|
+
if not node: return
|
|
671
|
+
|
|
672
|
+
if node.type == "VarDecl":
|
|
673
|
+
val declared = node.typeAnn ? parseAnnotation(node.typeAnn) : null
|
|
674
|
+
var actual = null
|
|
675
|
+
if node.init: actual = self._inferType(node.init, env)
|
|
676
|
+
if declared and actual and actual.kind != "unknown":
|
|
677
|
+
if not self._isAssignable(actual, declared):
|
|
678
|
+
self._err(
|
|
679
|
+
"Type '" + typeStr(actual) + "' is not assignable to '" + node.name + ": " + typeStr(declared) + "'",
|
|
680
|
+
node.loc,
|
|
681
|
+
"Change the value to match '" + typeStr(declared) + "' or update the annotation"
|
|
682
|
+
)
|
|
683
|
+
env.set(node.name, declared ?? actual ?? T_UNKNOWN)
|
|
684
|
+
|
|
685
|
+
else if node.type == "DestructureDecl":
|
|
686
|
+
val srcType = node.init ? self._inferType(node.init, env) : T_UNKNOWN
|
|
687
|
+
if node.patternType == "object":
|
|
688
|
+
for p in node.pattern:
|
|
689
|
+
var memberType = T_UNKNOWN
|
|
690
|
+
if srcType.kind == "object" and srcType.shape.has(p.key):
|
|
691
|
+
memberType = srcType.shape.get(p.key)
|
|
692
|
+
env.set(p.alias, memberType)
|
|
693
|
+
else:
|
|
694
|
+
var idx = 0
|
|
695
|
+
for p in node.pattern:
|
|
696
|
+
if p:
|
|
697
|
+
var elemType = T_UNKNOWN
|
|
698
|
+
if srcType.kind == "tuple" and srcType.types[idx]:
|
|
699
|
+
elemType = srcType.types[idx]
|
|
700
|
+
else if srcType.kind == "generic" and srcType.name == "Array":
|
|
701
|
+
elemType = srcType.args[0] ?? T_UNKNOWN
|
|
702
|
+
env.set(p.name, elemType)
|
|
703
|
+
idx = idx + 1
|
|
704
|
+
|
|
705
|
+
else if node.type == "FnDecl":
|
|
706
|
+
val retType = node.retType ? parseAnnotation(node.retType) : null
|
|
707
|
+
val paramTypes = node.params.map(p -> p.typeAnn ? parseAnnotation(p.typeAnn) : T_UNKNOWN)
|
|
708
|
+
if node.name: env.set(node.name, T_FN(paramTypes, retType ?? T_UNKNOWN))
|
|
709
|
+
val fnEnv = env.childFn(retType, node.async)
|
|
710
|
+
var i = 0
|
|
711
|
+
for p in node.params:
|
|
712
|
+
fnEnv.set(p.name, paramTypes[i])
|
|
713
|
+
i = i + 1
|
|
714
|
+
if node.inline:
|
|
715
|
+
val bodyType = self._inferType(node.body, fnEnv)
|
|
716
|
+
if retType and bodyType and bodyType.kind != "unknown":
|
|
717
|
+
if not self._isAssignable(bodyType, retType):
|
|
718
|
+
self._err(
|
|
719
|
+
"Function '" + (node.name ?? "(anon)") + "' returns '" + typeStr(bodyType) + "' but declared '" + typeStr(retType) + "'",
|
|
720
|
+
node.loc,
|
|
721
|
+
"Fix return type or update the annotation"
|
|
722
|
+
)
|
|
723
|
+
else:
|
|
724
|
+
self._checkStmts(node.body, fnEnv)
|
|
725
|
+
|
|
726
|
+
else if node.type == "ClassDecl":
|
|
727
|
+
env.set(node.name, T_NAMED(node.name))
|
|
728
|
+
val clsEnv = env.child()
|
|
729
|
+
val shape = new Map()
|
|
730
|
+
for f in node.fields:
|
|
731
|
+
val ft = f.typeAnn ? parseAnnotation(f.typeAnn) : T_UNKNOWN
|
|
732
|
+
clsEnv.set(f.name, ft)
|
|
733
|
+
shape.set(f.name, ft)
|
|
734
|
+
clsEnv.set("self", T_OBJECT(shape))
|
|
735
|
+
for m in node.methods: self._checkStmt(m, clsEnv)
|
|
736
|
+
|
|
737
|
+
else if node.type == "TypeDecl":
|
|
738
|
+
for v in node.variants:
|
|
739
|
+
if v.fields.length == 0:
|
|
740
|
+
env.set(v.name, T_NAMED(node.name))
|
|
741
|
+
else:
|
|
742
|
+
env.set(v.name, T_FN(v.fields.map(f_ -> T_ANY), T_NAMED(node.name)))
|
|
743
|
+
|
|
744
|
+
else if node.type == "InterfaceDecl":
|
|
745
|
+
env.set(node.name, T_NAMED(node.name))
|
|
746
|
+
|
|
747
|
+
else if node.type == "EnumDecl":
|
|
748
|
+
env.set(node.name, T_NAMED(node.name))
|
|
749
|
+
|
|
750
|
+
else if node.type == "IfStmt":
|
|
751
|
+
self._inferType(node.cond, env)
|
|
752
|
+
val narrowing = self._analyzeNarrowingCondition(node.cond, env)
|
|
753
|
+
val thenEnv = narrowing ? env.narrow(narrowing.varName, narrowing.trueType) : env.child()
|
|
754
|
+
val elseEnv = narrowing ? env.narrow(narrowing.varName, narrowing.falseType) : env.child()
|
|
755
|
+
self._checkStmts(node.then, thenEnv)
|
|
756
|
+
for ei in node.elseifs:
|
|
757
|
+
self._inferType(ei.cond, env)
|
|
758
|
+
self._checkStmts(ei.body, env.child())
|
|
759
|
+
if node.else_: self._checkStmts(node.else_, elseEnv)
|
|
760
|
+
|
|
761
|
+
else if node.type == "ForInStmt":
|
|
762
|
+
val iterType = self._inferType(node.iter, env)
|
|
763
|
+
val inner = env.child()
|
|
764
|
+
if iterType.kind == "generic" and iterType.name == "Array":
|
|
765
|
+
inner.set(node.var, iterType.args[0] ?? T_UNKNOWN)
|
|
766
|
+
else if iterType.kind == "tuple":
|
|
767
|
+
inner.set(node.var, iterType.types[0] ?? T_UNKNOWN)
|
|
768
|
+
else:
|
|
769
|
+
inner.set(node.var, T_UNKNOWN)
|
|
770
|
+
self._checkStmts(node.body, inner)
|
|
771
|
+
|
|
772
|
+
else if node.type == "WhileStmt" or node.type == "DoWhileStmt":
|
|
773
|
+
self._inferType(node.cond, env)
|
|
774
|
+
self._checkStmts(node.body ?? [], env.child())
|
|
775
|
+
|
|
776
|
+
else if node.type == "MatchStmt":
|
|
777
|
+
self._inferType(node.subject, env)
|
|
778
|
+
for arm in node.arms:
|
|
779
|
+
val inner = env.child()
|
|
780
|
+
if arm.pattern.type == "VariantPat":
|
|
781
|
+
for b in arm.pattern.bindings: inner.set(b, T_UNKNOWN)
|
|
782
|
+
self._checkStmts(arm.body, inner)
|
|
783
|
+
|
|
784
|
+
else if node.type == "ReturnStmt":
|
|
785
|
+
if node.value:
|
|
786
|
+
val retType = env.retType
|
|
787
|
+
val retActual = self._inferType(node.value, env)
|
|
788
|
+
if retType and retActual and retActual.kind != "unknown":
|
|
789
|
+
if not self._isAssignable(retActual, retType):
|
|
790
|
+
self._err(
|
|
791
|
+
"Return type '" + typeStr(retActual) + "' is not assignable to declared '" + typeStr(retType) + "'",
|
|
792
|
+
node.loc,
|
|
793
|
+
"Fix the return value or update the return type annotation"
|
|
794
|
+
)
|
|
795
|
+
|
|
796
|
+
else if node.type == "TryCatchStmt":
|
|
797
|
+
self._checkStmts(node.tryBody, env.child())
|
|
798
|
+
if node.catchBody:
|
|
799
|
+
val inner = env.child()
|
|
800
|
+
if node.catchParam: inner.set(node.catchParam, T_ANY)
|
|
801
|
+
self._checkStmts(node.catchBody, inner)
|
|
802
|
+
if node.finallyBody: self._checkStmts(node.finallyBody, env.child())
|
|
803
|
+
|
|
804
|
+
else if node.type == "ThrowStmt":
|
|
805
|
+
if node.value: self._inferType(node.value, env)
|
|
806
|
+
|
|
807
|
+
else if node.type == "ImportDecl":
|
|
808
|
+
if node.defaultName: env.set(node.defaultName, T_ANY)
|
|
809
|
+
if node.namespaceName: env.set(node.namespaceName, T_ANY)
|
|
810
|
+
for n in node.names:
|
|
811
|
+
val nm = typeof n == "string" ? n : n.alias
|
|
812
|
+
env.set(nm, T_ANY)
|
|
813
|
+
|
|
814
|
+
else if node.type == "ExportDecl":
|
|
815
|
+
if node.isDefault:
|
|
816
|
+
self._inferType(node.decl, env)
|
|
817
|
+
else:
|
|
818
|
+
self._checkStmt(node.decl, env)
|
|
819
|
+
|
|
820
|
+
else if node.type == "ExprStmt":
|
|
821
|
+
self._inferType(node.expr, env)
|