gofront 0.0.8 → 1.0.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 (51) hide show
  1. package/README.md +88 -12
  2. package/package.json +8 -4
  3. package/src/codegen/expressions.js +520 -1717
  4. package/src/{codegen.js → codegen/index.js} +155 -184
  5. package/src/codegen/runtime.js +151 -0
  6. package/src/codegen/statements.js +305 -296
  7. package/src/codegen/stdlib/builder.js +36 -0
  8. package/src/codegen/stdlib/bytes.js +62 -0
  9. package/src/codegen/stdlib/errors.js +17 -0
  10. package/src/codegen/stdlib/fmt.js +101 -0
  11. package/src/codegen/stdlib/gom.js +152 -0
  12. package/src/codegen/stdlib/html.js +17 -0
  13. package/src/codegen/stdlib/index.js +95 -0
  14. package/src/codegen/stdlib/io.js +33 -0
  15. package/src/codegen/stdlib/maps.js +42 -0
  16. package/src/codegen/stdlib/math.js +53 -0
  17. package/src/codegen/stdlib/os.js +10 -0
  18. package/src/codegen/stdlib/path.js +31 -0
  19. package/src/codegen/stdlib/rand.js +24 -0
  20. package/src/codegen/stdlib/regexp.js +51 -0
  21. package/src/codegen/stdlib/slices.js +48 -0
  22. package/src/codegen/stdlib/sort.js +28 -0
  23. package/src/codegen/stdlib/strconv.js +30 -0
  24. package/src/codegen/stdlib/strings.js +83 -0
  25. package/src/codegen/stdlib/time.js +54 -0
  26. package/src/codegen/stdlib/unicode.js +22 -0
  27. package/src/codegen/stdlib/utf8.js +23 -0
  28. package/src/codegen/templ.js +281 -0
  29. package/src/compiler.js +102 -62
  30. package/src/dts-parser.js +355 -371
  31. package/src/index.js +22 -54
  32. package/src/lexer.js +200 -286
  33. package/src/minifier.js +305 -318
  34. package/src/parser/expressions.js +54 -36
  35. package/src/{parser.js → parser/index.js} +12 -5
  36. package/src/parser/statements.js +29 -35
  37. package/src/parser/types.js +31 -29
  38. package/src/templ-lexer.js +737 -0
  39. package/src/templ-parser.js +235 -0
  40. package/src/tokens.js +109 -0
  41. package/src/typechecker/assignability.js +258 -0
  42. package/src/typechecker/expressions.js +559 -549
  43. package/src/typechecker/index.js +555 -0
  44. package/src/typechecker/resolve.js +421 -0
  45. package/src/typechecker/statements.js +267 -280
  46. package/src/typechecker/stdlib/core.js +592 -0
  47. package/src/typechecker/stdlib/extended.js +332 -0
  48. package/src/typechecker/stdlib.js +9 -1060
  49. package/src/typechecker/termination.js +83 -0
  50. package/src/typechecker/types.js +6 -3
  51. package/src/typechecker.js +0 -1271
@@ -1,1271 +0,0 @@
1
- // Type checker: walks the AST, resolves types for all expressions,
2
- // and reports type errors at compile time.
3
- //
4
- // Split into sub-modules under typechecker/:
5
- // types.js — shared type constants, predicates, Scope
6
- // stdlib.js — browser globals + all built-in package registrations
7
- // statements.js — checkBlock, checkStmt
8
- // expressions.js — checkExpr, checkCall, checkBuiltin, checkCompositeLit
9
-
10
- import { expressionCheckMethods } from "./typechecker/expressions.js";
11
- import { statementCheckMethods } from "./typechecker/statements.js";
12
- import { setupGlobals } from "./typechecker/stdlib.js";
13
- import {
14
- ANY,
15
- BASIC_TYPES,
16
- BOOL,
17
- CMP_OPS,
18
- COMPLEX128,
19
- defaultType,
20
- ERROR,
21
- FLOAT64,
22
- INT,
23
- isAny,
24
- isComplex,
25
- isComplexOrNumeric,
26
- isNil,
27
- isNumeric,
28
- isPointer,
29
- isString,
30
- isUntyped,
31
- isVoid,
32
- LOG_OPS,
33
- Scope,
34
- STRING,
35
- TypeCheckError,
36
- typeStr,
37
- UNTYPED_COMPLEX,
38
- UNTYPED_FLOAT,
39
- UNTYPED_INT,
40
- VOID,
41
- } from "./typechecker/types.js";
42
-
43
- // Re-export for consumers that import from typechecker.js
44
- export { TypeCheckError, typeStr };
45
-
46
- export class TypeChecker {
47
- constructor() {
48
- this.types = new Map(); // named types
49
- this.globals = new Scope();
50
- this.errors = [];
51
- this._currentFile = null; // filename of file currently being checked
52
- this._currentSource = null;
53
- this._loopDepth = 0; // for break/continue validation
54
- this._switchDepth = 0; // for break/fallthrough validation
55
- this._typeSwitchDepth = 0; // for rejecting fallthrough in type switch
56
- this._deferCount = 0; // tracks defer usage in current function body
57
- this._imports = []; // tracked imports for unused-import detection
58
- this._setupGlobals();
59
- // Register error as a named type (interface)
60
- this.types.set("error", ERROR);
61
- }
62
-
63
- _setupGlobals() {
64
- setupGlobals(this.globals, this.types);
65
- }
66
-
67
- addDefinitions(types, values) {
68
- for (const [name, type] of types) {
69
- this.types.set(name, type);
70
- }
71
- for (const [name, type] of values) {
72
- this.globals.define(name, type);
73
- }
74
- }
75
-
76
- // Add an imported GoFront package as a qualified namespace.
77
- // e.g. addPackageNamespace('utils', symbolsMap, typesMap)
78
- // lets callers type-check `utils.Foo` via SelectorExpr.
79
- addPackageNamespace(pkgName, symbols, types) {
80
- const members = {};
81
- for (const [name, type] of symbols) members[name] = type;
82
- // _gofront: true marks this as a GoFront package — exported identifier rules apply
83
- this.globals.define(pkgName, {
84
- kind: "namespace",
85
- name: pkgName,
86
- members,
87
- _gofront: true,
88
- });
89
- for (const [name, type] of types) {
90
- this.types.set(name, type);
91
- this.types.set(`${pkgName}.${name}`, type);
92
- }
93
- }
94
-
95
- // Register an import for unused-import detection.
96
- // `name` is the package name or alias used in code.
97
- // `node` carries _line for error reporting.
98
- // `filename` / `source` identify the file containing the import.
99
- trackImport(name, node, filename, source) {
100
- this._imports.push({ name, node, filename, source });
101
- }
102
-
103
- // Report any tracked imports whose package name was never referenced.
104
- reportUnusedImports() {
105
- for (const { name, node, filename, source } of this._imports) {
106
- if (!this.globals._used.has(name)) {
107
- const saved = [this._currentFile, this._currentSource];
108
- this._currentFile = filename;
109
- this._currentSource = source;
110
- this.err(`'${name}' imported and not used`, node);
111
- [this._currentFile, this._currentSource] = saved;
112
- }
113
- }
114
- }
115
-
116
- // Return a snapshot of all user-defined globals (excludes built-ins).
117
- // Used by the compiler to expose a package's exports to importers.
118
- getExportedSymbols() {
119
- const out = new Map();
120
- for (const [name, type] of this.globals.symbols) {
121
- if (type?.kind === "builtin") continue; // skip built-ins
122
- if (type === ANY && !name[0].match(/[A-Z]/)) continue; // skip browser globals
123
- out.set(name, type);
124
- }
125
- return out;
126
- }
127
-
128
- getExportedTypes() {
129
- return new Map(this.types);
130
- }
131
-
132
- err(msg, node) {
133
- const e = new TypeCheckError(
134
- msg,
135
- node,
136
- this._currentFile,
137
- this._currentSource,
138
- );
139
- this.errors.push(e);
140
- return ANY; // recovery type
141
- }
142
-
143
- _reportUnused(scope, node) {
144
- for (const name of scope.unusedLocals()) {
145
- this.err(`'${name}' declared and not used`, node);
146
- }
147
- }
148
-
149
- check(program) {
150
- this._currentFile = program._filename ?? null;
151
- this._currentSource = program._source ?? null;
152
- // Pass 1: collect type declarations
153
- for (const decl of program.decls) {
154
- if (decl.kind === "TypeDecl") this.collectType(decl);
155
- }
156
- // Pass 2: collect function / method signatures
157
- for (const decl of program.decls) {
158
- if (decl.kind === "FuncDecl" || decl.kind === "MethodDecl") {
159
- this.collectFunc(decl);
160
- }
161
- }
162
- // Pass 2.1: Promote embedded methods
163
- this._promoteEmbeddedMethods();
164
- // Pass 3: check bodies
165
- for (const decl of program.decls) {
166
- this.checkTopDecl(decl, this.globals);
167
- }
168
- return this.errors;
169
- }
170
-
171
- // Pre-declare a package-level var so other files can reference its name
172
- // before we've type-checked its initializer. Called in pass 2.5.
173
- collectVar(decl) {
174
- for (const spec of decl.decls) {
175
- const type = spec.type
176
- ? this.resolveTypeNode(spec.type, this.globals)
177
- : ANY;
178
- for (const name of spec.names) this.globals.define(name, type);
179
- }
180
- }
181
-
182
- collectConst(decl) {
183
- for (const spec of decl.decls) {
184
- const type = spec.type
185
- ? this.resolveTypeNode(spec.type, this.globals)
186
- : ANY;
187
- for (const name of spec.names) this.globals.defineConst(name, type);
188
- }
189
- }
190
-
191
- // Like check() but operates over multiple programs (same-package multi-file).
192
- // All passes run across all files before moving to the next pass,
193
- // so every file sees every other file's declarations.
194
- checkAll(programs) {
195
- // Pass 1: collect type declarations
196
- for (const p of programs) {
197
- this._currentFile = p._filename ?? null;
198
- this._currentSource = p._source ?? null;
199
- for (const d of p.decls) if (d.kind === "TypeDecl") this.collectType(d);
200
- }
201
-
202
- // Pass 2: collect function / method signatures
203
- for (const p of programs) {
204
- this._currentFile = p._filename ?? null;
205
- this._currentSource = p._source ?? null;
206
- for (const d of p.decls)
207
- if (d.kind === "FuncDecl" || d.kind === "MethodDecl")
208
- this.collectFunc(d);
209
- }
210
-
211
- // Pass 2.1: Promote embedded methods
212
- this._promoteEmbeddedMethods();
213
-
214
- // Pass 2.5: pre-declare package-level vars and consts
215
- // This allows any file's function body to reference vars from other files.
216
- for (const p of programs) {
217
- this._currentFile = p._filename ?? null;
218
- this._currentSource = p._source ?? null;
219
- for (const d of p.decls) {
220
- if (d.kind === "VarDecl") this.collectVar(d);
221
- if (d.kind === "ConstDecl") this.collectConst(d);
222
- }
223
- }
224
-
225
- // Pass 3: check bodies (re-checks var/const decls with proper types)
226
- for (const p of programs) {
227
- this._currentFile = p._filename ?? null;
228
- this._currentSource = p._source ?? null;
229
- for (const d of p.decls) this.checkTopDecl(d, this.globals);
230
- }
231
-
232
- return this.errors;
233
- }
234
-
235
- // ── Shared pass helpers ──────────────────────────────────────
236
-
237
- _promoteEmbeddedMethods() {
238
- for (const type of this.types.values()) {
239
- const struct = type.underlying;
240
- if (struct?.kind === "struct" && struct._embeds) {
241
- for (const embed of struct._embeds) {
242
- const base = embed.kind === "named" ? embed.underlying : embed;
243
- if (base?.kind !== "struct" || !base.methods) continue;
244
- for (const [mName, mType] of base.methods.entries()) {
245
- if (!struct.methods.has(mName)) struct.methods.set(mName, mType);
246
- }
247
- }
248
- }
249
- }
250
- }
251
-
252
- // ── Type collection ──────────────────────────────────────────
253
-
254
- collectType(decl) {
255
- if (decl.typeParams) {
256
- // Generic type — resolve underlying with type params as ANY for struct fields
257
- const typeScope = new Scope(this.globals);
258
- const typeParamTypes = decl.typeParams.map((tp) => {
259
- const t = {
260
- kind: "typeParam",
261
- name: tp.name,
262
- constraint: tp.constraint,
263
- };
264
- typeScope.define(tp.name, t);
265
- return t;
266
- });
267
- const underlying = this.resolveTypeNode(decl.type, typeScope);
268
- const named = {
269
- kind: "named",
270
- name: decl.name,
271
- underlying,
272
- _generic: {
273
- typeParams: typeParamTypes,
274
- declNode: decl,
275
- },
276
- };
277
- if (underlying.kind === "struct") {
278
- underlying.name = decl.name;
279
- underlying.methods = new Map();
280
- }
281
- this.types.set(decl.name, named);
282
- this.globals.define(decl.name, named);
283
- return;
284
- }
285
- const underlying = this.resolveTypeNode(decl.type, this.globals);
286
- if (decl.isAlias) {
287
- // type A = B — transparent alias, A and B are identical types
288
- this.types.set(decl.name, underlying);
289
- this.globals.define(decl.name, underlying);
290
- return;
291
- }
292
- const named = { kind: "named", name: decl.name, underlying };
293
- if (underlying.kind === "struct") {
294
- underlying.name = decl.name;
295
- underlying.methods = new Map();
296
- } else if (underlying.kind !== "interface") {
297
- named.methods = new Map();
298
- }
299
- if (underlying.kind === "interface") {
300
- underlying.name = decl.name;
301
- }
302
- this.types.set(decl.name, named);
303
- this.globals.define(decl.name, named);
304
- }
305
-
306
- collectFunc(decl) {
307
- // For generic functions, create type param scope for resolving param/return types
308
- let resolveScope = this.globals;
309
- let typeParamTypes = null;
310
- if (decl.typeParams) {
311
- resolveScope = new Scope(this.globals);
312
- typeParamTypes = decl.typeParams.map((tp) => {
313
- const t = {
314
- kind: "typeParam",
315
- name: tp.name,
316
- constraint: tp.constraint,
317
- };
318
- resolveScope.define(tp.name, t);
319
- return t;
320
- });
321
- }
322
- // For methods on generic types, inject the receiver's type params
323
- if (decl.kind === "MethodDecl") {
324
- const recvTypeName =
325
- decl.recvType.kind === "GenericTypeName"
326
- ? decl.recvType.name
327
- : decl.recvType.name;
328
- const recvNamedType = this.types.get(recvTypeName);
329
- if (recvNamedType?._generic) {
330
- resolveScope = new Scope(this.globals);
331
- for (const tp of recvNamedType._generic.typeParams) {
332
- resolveScope.define(tp.name, tp);
333
- }
334
- }
335
- }
336
- const paramTypes = decl.params.map((p) =>
337
- this.resolveTypeNode(p.type, resolveScope),
338
- );
339
- const returnType = decl.returnType
340
- ? this.resolveTypeNode(decl.returnType, resolveScope)
341
- : VOID;
342
- const isVariadic =
343
- decl.params.length > 0 && decl.params[decl.params.length - 1].variadic;
344
- const funcType = {
345
- kind: "func",
346
- params: paramTypes,
347
- returns: [returnType],
348
- variadic: isVariadic,
349
- async: decl.async ?? false,
350
- };
351
- if (decl.kind === "FuncDecl") {
352
- if (this.globals.symbols.has(decl.name) && decl.name !== "init") {
353
- this.err(`${decl.name} redeclared in this block`, decl);
354
- }
355
- if (typeParamTypes) {
356
- this.globals.define(decl.name, {
357
- kind: "generic",
358
- name: decl.name,
359
- typeParams: typeParamTypes,
360
- underlying: funcType,
361
- });
362
- } else {
363
- this.globals.define(decl.name, funcType);
364
- }
365
- } else {
366
- // Method: attach to struct or named non-struct type
367
- const recvTypeName =
368
- decl.recvType.kind === "GenericTypeName"
369
- ? decl.recvType.name
370
- : decl.recvType.name;
371
- const recvNamedType = this.types.get(recvTypeName);
372
- if (recvNamedType?._generic) {
373
- // Store method on the generic metadata and on the underlying struct
374
- if (!recvNamedType._generic.methods)
375
- recvNamedType._generic.methods = new Map();
376
- recvNamedType._generic.methods.set(decl.name, funcType);
377
- const base = recvNamedType.underlying;
378
- if (base?.kind === "struct") {
379
- base.methods.set(decl.name, funcType);
380
- }
381
- } else {
382
- const recvType = this.resolveTypeNodeName(decl.recvType, this.globals);
383
- const base = recvType?.underlying ?? recvType;
384
- if (base?.kind === "struct") {
385
- base.methods.set(decl.name, funcType);
386
- } else if (recvType?.kind === "named" && recvType.methods) {
387
- recvType.methods.set(decl.name, funcType);
388
- }
389
- }
390
- }
391
- }
392
-
393
- // ── Top-level checker ────────────────────────────────────────
394
-
395
- checkTopDecl(decl, scope) {
396
- switch (decl.kind) {
397
- case "FuncDecl":
398
- this.checkFuncDecl(decl, scope);
399
- break;
400
- case "MethodDecl":
401
- this.checkMethodDecl(decl, scope);
402
- break;
403
- case "VarDecl":
404
- this.checkVarDecl(decl, scope);
405
- break;
406
- case "ConstDecl":
407
- this.checkConstDecl(decl, scope);
408
- break;
409
- case "TypeDecl":
410
- break; // already collected
411
- }
412
- }
413
-
414
- checkFuncDecl(decl, outer) {
415
- const inner = new Scope(outer);
416
- // Inject type params into scope for generic functions
417
- if (decl.typeParams) {
418
- for (const tp of decl.typeParams) {
419
- const constraint = tp.constraint
420
- ? this.resolveTypeNode(tp.constraint, outer)
421
- : ANY;
422
- inner.define(tp.name, { kind: "typeParam", name: tp.name, constraint });
423
- }
424
- }
425
- for (const p of decl.params) {
426
- inner.define(p.name, this.resolveTypeNode(p.type, inner));
427
- }
428
- let returnType = decl.returnType
429
- ? this.resolveTypeNode(decl.returnType, inner)
430
- : VOID;
431
- const hasNamedReturns = this._injectNamedReturns(
432
- decl.returnType,
433
- inner,
434
- outer,
435
- );
436
- // Wrap the return type to avoid mutating the shared singleton
437
- if (hasNamedReturns && returnType)
438
- returnType = { ...returnType, _hasNamedReturns: true };
439
- const savedDefer = this._deferCount;
440
- this._deferCount = 0;
441
- this.checkBlock(decl.body, inner, returnType);
442
- this._reportUnused(inner, decl);
443
- if (this._deferCount > 0) decl.body._hasDefer = true;
444
- this._deferCount = savedDefer;
445
- decl._returnType = returnType;
446
- // Missing return check: non-void functions must terminate on all paths
447
- if (!isVoid(returnType) && !returnType._hasNamedReturns) {
448
- if (!this._isTerminating(decl.body)) {
449
- this.err(`missing return at end of function '${decl.name}'`, decl);
450
- }
451
- }
452
- }
453
-
454
- checkMethodDecl(decl, outer) {
455
- const inner = new Scope(outer);
456
- // For generic receiver types (e.g. Stack[T]), inject type params
457
- const recvTypeName =
458
- decl.recvType.kind === "GenericTypeName"
459
- ? decl.recvType.name
460
- : decl.recvType.name;
461
- const recvNamedType = this.types.get(recvTypeName);
462
- if (recvNamedType?._generic) {
463
- for (const tp of recvNamedType._generic.typeParams) {
464
- inner.define(tp.name, tp);
465
- }
466
- }
467
- const recvType = this.resolveTypeNodeName(decl.recvType, outer);
468
- inner.define(decl.recvName, recvType);
469
- for (const p of decl.params) {
470
- inner.define(p.name, this.resolveTypeNode(p.type, inner));
471
- }
472
- let returnType = decl.returnType
473
- ? this.resolveTypeNode(decl.returnType, inner)
474
- : VOID;
475
- const hasNamedReturns = this._injectNamedReturns(
476
- decl.returnType,
477
- inner,
478
- inner,
479
- );
480
- if (hasNamedReturns && returnType)
481
- returnType = { ...returnType, _hasNamedReturns: true };
482
- const savedDefer = this._deferCount;
483
- this._deferCount = 0;
484
- this.checkBlock(decl.body, inner, returnType);
485
- this._reportUnused(inner, decl);
486
- if (this._deferCount > 0) decl.body._hasDefer = true;
487
- this._deferCount = savedDefer;
488
- decl._returnType = returnType;
489
- // Missing return check
490
- if (!isVoid(returnType) && !returnType._hasNamedReturns) {
491
- if (!this._isTerminating(decl.body)) {
492
- this.err(`missing return at end of method '${decl.name}'`, decl);
493
- }
494
- }
495
- }
496
-
497
- // Go spec §Terminating statements — returns true if the statement always terminates.
498
- _isTerminating(stmt) {
499
- if (!stmt) return false;
500
- switch (stmt.kind) {
501
- case "ReturnStmt":
502
- return true;
503
- case "BranchStmt":
504
- // panic() is in ExprStmt, not BranchStmt — but "goto" could be here
505
- return false;
506
- case "ExprStmt":
507
- // panic(...) is a terminating call
508
- if (
509
- stmt.expr?.kind === "CallExpr" &&
510
- stmt.expr?.func?.kind === "Ident" &&
511
- stmt.expr?.func?.name === "panic"
512
- )
513
- return true;
514
- return false;
515
- case "Block":
516
- return this._isTerminatingBlock(stmt);
517
- case "IfStmt":
518
- // Terminating if: has else, and both branches terminate
519
- if (!stmt.elseBody) return false;
520
- return (
521
- this._isTerminatingBlock(stmt.body) &&
522
- (stmt.elseBody.kind === "Block"
523
- ? this._isTerminatingBlock(stmt.elseBody)
524
- : this._isTerminating(stmt.elseBody))
525
- );
526
- case "ForStmt":
527
- // Infinite loop (no condition) with no break — terminating
528
- if (!stmt.cond && !this._blockHasBreak(stmt.body)) return true;
529
- return false;
530
- case "SwitchStmt":
531
- // Terminating switch: has default and every case terminates
532
- return this._isTerminatingSwitch(stmt);
533
- case "TypeSwitchStmt":
534
- return this._isTerminatingTypeSwitch(stmt);
535
- case "LabeledStmt":
536
- return this._isTerminating(stmt.body);
537
- default:
538
- return false;
539
- }
540
- }
541
-
542
- _isTerminatingBlock(block) {
543
- if (!block?.stmts?.length) return false;
544
- return this._isTerminating(block.stmts[block.stmts.length - 1]);
545
- }
546
-
547
- _blockHasBreak(block) {
548
- if (!block?.stmts) return false;
549
- for (const s of block.stmts) {
550
- if (s.kind === "BranchStmt" && s.keyword === "break") return true;
551
- }
552
- return false;
553
- }
554
-
555
- _isTerminatingSwitch(stmt) {
556
- let hasDefault = false;
557
- for (const c of stmt.cases ?? []) {
558
- if (!c.list) hasDefault = true;
559
- if (!c.stmts?.length) return false;
560
- const last = c.stmts[c.stmts.length - 1];
561
- if (!this._isTerminating(last)) return false;
562
- }
563
- return hasDefault;
564
- }
565
-
566
- _isTerminatingTypeSwitch(stmt) {
567
- let hasDefault = false;
568
- for (const c of stmt.cases ?? []) {
569
- if (!c.types) hasDefault = true;
570
- if (!c.stmts?.length) return false;
571
- const last = c.stmts[c.stmts.length - 1];
572
- if (!this._isTerminating(last)) return false;
573
- }
574
- return hasDefault;
575
- }
576
-
577
- _injectNamedReturns(returnTypeNode, scope, outer) {
578
- if (!returnTypeNode?._namedReturns) return false;
579
- for (const { name, type } of returnTypeNode._namedReturns) {
580
- if (name) scope.define(name, this.resolveTypeNode(type, outer));
581
- }
582
- return true;
583
- }
584
-
585
- checkVarDecl(decl, scope) {
586
- for (const spec of decl.decls) {
587
- let type = spec.type ? this.resolveTypeNode(spec.type, scope) : null;
588
- if (spec.value) {
589
- const valTypes = spec.value.map((v) => this.checkExpr(v, scope));
590
- if (!type) type = defaultType(valTypes[0]) ?? ANY;
591
- for (let i = 0; i < spec.names.length; i++) {
592
- const vt = valTypes[i] ?? ANY;
593
- this.assertAssignable(type, vt, spec.value[i]);
594
- }
595
- }
596
- if (!type) type = ANY;
597
- for (const name of spec.names) scope.defineLocal(name, type);
598
- }
599
- }
600
-
601
- checkConstDecl(decl, scope) {
602
- for (const spec of decl.decls) {
603
- const valTypes = spec.value.map((v) => this.checkExpr(v, scope));
604
- // Explicit type annotation → typed constant; otherwise preserve untyped
605
- const type = spec.type
606
- ? this.resolveTypeNode(spec.type, scope)
607
- : (valTypes[0] ?? ANY);
608
- for (const name of spec.names) scope.defineConst(name, type);
609
- }
610
- }
611
-
612
- // ── Statement checking ───────────────────────────────────────
613
-
614
- // ── Type resolution ──────────────────────────────────────────
615
-
616
- resolveTypeNode(node, scope) {
617
- if (!node) return ANY;
618
- switch (node.kind) {
619
- case "TypeName": {
620
- if (BASIC_TYPES[node.name]) return BASIC_TYPES[node.name];
621
- if (node.name === "comparable")
622
- return { kind: "basic", name: "comparable" };
623
- // Check scope for type params before checking this.types
624
- if (scope) {
625
- const fromScope = scope.lookup(node.name);
626
- if (fromScope?.kind === "typeParam") return fromScope;
627
- }
628
- const named = this.types.get(node.name);
629
- if (named) return named;
630
- return this.err(`Unknown type '${node.name}'`, node);
631
- }
632
- case "GenericTypeName": {
633
- const base = this.types.get(node.name);
634
- if (!base) return this.err(`Unknown type '${node.name}'`, node);
635
- if (base.kind === "named" && base._generic) {
636
- return this.instantiateGenericType(
637
- base._generic,
638
- node.typeArgs,
639
- scope,
640
- );
641
- }
642
- return this.err(`Type '${node.name}' is not generic`, node);
643
- }
644
- case "TypeParam": {
645
- const constraint = node.constraint
646
- ? this.resolveTypeNode(node.constraint, scope)
647
- : ANY;
648
- return { kind: "typeParam", name: node.name, constraint };
649
- }
650
- case "UnionConstraint":
651
- return node;
652
- case "SliceType":
653
- return {
654
- kind: "slice",
655
- elem: this.resolveTypeNode(node.elem, scope),
656
- };
657
- case "ArrayType":
658
- return {
659
- kind: "array",
660
- // inferLen: true means [...]T — size is determined by composite literal
661
- size: node.inferLen
662
- ? null
663
- : node.size?.value !== undefined
664
- ? Number(node.size.value)
665
- : node.size,
666
- elem: this.resolveTypeNode(node.elem, scope),
667
- };
668
- case "MapType":
669
- return {
670
- kind: "map",
671
- key: this.resolveTypeNode(node.key, scope),
672
- value: this.resolveTypeNode(node.value, scope),
673
- };
674
- case "PointerType":
675
- return {
676
- kind: "pointer",
677
- base: this.resolveTypeNode(node.base, scope),
678
- };
679
- case "FuncType": {
680
- const params = node.params.map((p) =>
681
- this.resolveTypeNode(p.type, scope),
682
- );
683
- const returns = node.returnType
684
- ? [this.resolveTypeNode(node.returnType, scope)]
685
- : [VOID];
686
- return { kind: "func", params, returns };
687
- }
688
- case "StructType": {
689
- const fields = new Map();
690
- const embeds = []; // track embedded types to merge methods later
691
- for (const f of node.fields) {
692
- const ft = this.resolveTypeNode(f.type, scope);
693
- if (f.embedded) {
694
- // Flatten embedded struct fields
695
- const base = ft.kind === "named" ? ft.underlying : ft;
696
- if (base?.kind === "struct") {
697
- for (const [k, v] of base.fields.entries()) fields.set(k, v);
698
- }
699
- embeds.push(ft);
700
- } else {
701
- for (const n of f.names) fields.set(n, ft);
702
- }
703
- }
704
- const structType = {
705
- kind: "struct",
706
- fields,
707
- methods: new Map(),
708
- _embeds: embeds,
709
- };
710
- return structType;
711
- }
712
- case "InterfaceType": {
713
- const methods = new Map();
714
- for (const m of node.methods) {
715
- const params = m.params.map((p) =>
716
- this.resolveTypeNode(p.type, scope),
717
- );
718
- const returns = m.returnType
719
- ? [this.resolveTypeNode(m.returnType, scope)]
720
- : [VOID];
721
- const isVariadic =
722
- m.params.length > 0 && m.params[m.params.length - 1].variadic;
723
- const mType = { kind: "func", params, returns };
724
- if (isVariadic) mType.variadic = true;
725
- methods.set(m.name, mType);
726
- }
727
- // Flatten embedded interface methods
728
- if (node.embeds) {
729
- for (const embed of node.embeds) {
730
- const resolved = this.resolveTypeNode(embed, scope);
731
- const base =
732
- resolved?.kind === "named" ? resolved.underlying : resolved;
733
- if (base?.kind === "interface") {
734
- for (const [mName, mType] of base.methods) {
735
- if (!methods.has(mName)) methods.set(mName, mType);
736
- }
737
- } else if (!isAny(resolved)) {
738
- this.err(
739
- `cannot embed non-interface type ${typeStr(resolved)}`,
740
- embed,
741
- );
742
- }
743
- }
744
- }
745
- return { kind: "interface", methods };
746
- }
747
- case "TupleType": {
748
- return {
749
- kind: "tuple",
750
- types: node.types.map((t) => this.resolveTypeNode(t, scope)),
751
- };
752
- }
753
- // Expression used as type (e.g. in make/new calls)
754
- case "Ident":
755
- return this.resolveTypeNode(
756
- { kind: "TypeName", name: node.name },
757
- scope,
758
- );
759
- default:
760
- return ANY;
761
- }
762
- }
763
-
764
- resolveTypeNodeName(node, scope) {
765
- if (!node) return ANY;
766
- if (node.kind === "TypeName") {
767
- const named = this.types.get(node.name);
768
- if (named) return named;
769
- }
770
- if (node.kind === "GenericTypeName") {
771
- const named = this.types.get(node.name);
772
- if (named) return named;
773
- }
774
- return this.resolveTypeNode(node, scope);
775
- }
776
-
777
- // ── Helpers ───────────────────────────────────────────────────
778
-
779
- // ── Generics — instantiation, inference, substitution ────────
780
-
781
- substituteType(type, map) {
782
- if (!type) return type;
783
- if (type.kind === "typeParam") {
784
- const bound = map.get(type.name);
785
- return bound ?? type;
786
- }
787
- switch (type.kind) {
788
- case "basic":
789
- case "untyped":
790
- return type;
791
- case "slice":
792
- return { kind: "slice", elem: this.substituteType(type.elem, map) };
793
- case "array":
794
- return {
795
- kind: "array",
796
- size: type.size,
797
- elem: this.substituteType(type.elem, map),
798
- };
799
- case "map":
800
- return {
801
- kind: "map",
802
- key: this.substituteType(type.key, map),
803
- value: this.substituteType(type.value, map),
804
- };
805
- case "func": {
806
- return {
807
- kind: "func",
808
- params: type.params.map((p) => this.substituteType(p, map)),
809
- returns: type.returns.map((r) => this.substituteType(r, map)),
810
- variadic: type.variadic,
811
- async: type.async,
812
- };
813
- }
814
- case "struct": {
815
- const fields = new Map();
816
- for (const [k, v] of type.fields)
817
- fields.set(k, this.substituteType(v, map));
818
- const methods = new Map();
819
- for (const [k, v] of type.methods)
820
- methods.set(k, this.substituteType(v, map));
821
- return {
822
- kind: "struct",
823
- name: type.name,
824
- fields,
825
- methods,
826
- _embeds: type._embeds,
827
- };
828
- }
829
- case "interface": {
830
- const methods = new Map();
831
- for (const [k, v] of type.methods)
832
- methods.set(k, this.substituteType(v, map));
833
- return { kind: "interface", name: type.name, methods };
834
- }
835
- case "named":
836
- return {
837
- kind: "named",
838
- name: type.name,
839
- underlying: this.substituteType(type.underlying, map),
840
- };
841
- case "pointer":
842
- return { kind: "pointer", base: this.substituteType(type.base, map) };
843
- case "tuple":
844
- return {
845
- kind: "tuple",
846
- types: type.types.map((t) => this.substituteType(t, map)),
847
- };
848
- default:
849
- return type;
850
- }
851
- }
852
-
853
- instantiateGenericFunc(genericType, typeArgs) {
854
- const map = new Map();
855
- for (let i = 0; i < genericType.typeParams.length; i++) {
856
- map.set(genericType.typeParams[i].name, typeArgs[i] ?? ANY);
857
- }
858
- return this.substituteType(genericType.underlying, map);
859
- }
860
-
861
- instantiateGenericType(generic, typeArgNodes, scope) {
862
- const map = new Map();
863
- const typeArgs = typeArgNodes.map((n) => this.resolveTypeNode(n, scope));
864
- for (let i = 0; i < generic.typeParams.length; i++) {
865
- map.set(generic.typeParams[i].name, typeArgs[i] ?? ANY);
866
- }
867
- // Resolve the underlying type from the decl node using a scope with type params bound
868
- const typeScope = new Scope(this.globals);
869
- for (const [name, type] of map) typeScope.define(name, type);
870
- const underlying = this.resolveTypeNode(generic.declNode.type, typeScope);
871
- const instantiated = this.substituteType(underlying, map);
872
- if (instantiated.kind === "struct") {
873
- instantiated.name = generic.declNode.name;
874
- if (!instantiated.methods) instantiated.methods = new Map();
875
- // Copy and substitute methods from the generic
876
- if (generic.methods) {
877
- for (const [mName, mType] of generic.methods) {
878
- instantiated.methods.set(mName, this.substituteType(mType, map));
879
- }
880
- }
881
- }
882
- return {
883
- kind: "named",
884
- name: generic.declNode.name,
885
- underlying: instantiated,
886
- };
887
- }
888
-
889
- inferTypeArgs(genericType, argTypes) {
890
- const map = new Map();
891
- const params = genericType.underlying.params;
892
- for (let i = 0; i < params.length && i < argTypes.length; i++) {
893
- this._inferFromPair(params[i], argTypes[i], map);
894
- }
895
- // Check all type params are resolved
896
- for (const tp of genericType.typeParams) {
897
- if (!map.has(tp.name)) return null;
898
- }
899
- // Apply defaultType to resolve untyped constants
900
- const result = [];
901
- for (const tp of genericType.typeParams) {
902
- result.push(defaultType(map.get(tp.name)));
903
- }
904
- return result;
905
- }
906
-
907
- _inferFromPair(paramType, argType, map) {
908
- if (!paramType || !argType) return;
909
- if (paramType.kind === "typeParam") {
910
- if (!map.has(paramType.name)) {
911
- map.set(paramType.name, argType);
912
- }
913
- return;
914
- }
915
- if (paramType.kind === "slice" && argType.kind === "slice") {
916
- this._inferFromPair(paramType.elem, argType.elem, map);
917
- }
918
- if (paramType.kind === "map" && argType.kind === "map") {
919
- this._inferFromPair(paramType.key, argType.key, map);
920
- this._inferFromPair(paramType.value, argType.value, map);
921
- }
922
- if (paramType.kind === "func" && argType.kind === "func") {
923
- for (
924
- let i = 0;
925
- i < paramType.params.length && i < argType.params.length;
926
- i++
927
- ) {
928
- this._inferFromPair(paramType.params[i], argType.params[i], map);
929
- }
930
- for (
931
- let i = 0;
932
- i < paramType.returns.length && i < argType.returns.length;
933
- i++
934
- ) {
935
- this._inferFromPair(paramType.returns[i], argType.returns[i], map);
936
- }
937
- }
938
- }
939
-
940
- checkConstraint(typeArg, constraint, node) {
941
- if (!constraint) return;
942
- if (isAny(constraint)) return;
943
- if (constraint.kind === "basic" && constraint.name === "comparable") return;
944
- if (constraint.kind === "UnionConstraint") {
945
- // Check if typeArg matches any term; ~terms always pass (v1)
946
- for (const term of constraint.terms) {
947
- if (term.approx) return; // ~ means approximate, always passes
948
- const termType = this.resolveTypeNode(term.type, this.globals);
949
- if (typeStr(typeArg) === typeStr(termType)) return;
950
- }
951
- this.err(`type ${typeStr(typeArg)} does not satisfy constraint`, node);
952
- return;
953
- }
954
- // Interface constraint — unwrap named types
955
- const base =
956
- constraint.kind === "named" ? constraint.underlying : constraint;
957
- if (base?.kind === "interface") {
958
- if (base.methods.size === 0 && !base.unionConstraint) return;
959
- if (base.unionConstraint) {
960
- return this.checkConstraint(typeArg, base.unionConstraint, node);
961
- }
962
- if (!this.implements(typeArg, base, node)) {
963
- this.err(
964
- `type ${typeStr(typeArg)} does not satisfy constraint ${typeStr(constraint)}`,
965
- node,
966
- );
967
- }
968
- }
969
- }
970
-
971
- resolveType(t) {
972
- if (!t) return ANY;
973
- if (t.kind === "basic" && t.alias) {
974
- const found = this.types.get(t.alias);
975
- if (found) return found;
976
- }
977
- return t;
978
- }
979
-
980
- fieldType(baseType, field, node) {
981
- baseType = this.resolveType(baseType);
982
- if (!baseType || isAny(baseType)) return ANY;
983
- // Transparent pointer dereference for named-type method calls: (*T).Method works like T.Method
984
- // (only unwrap when the pointer's base is a named type that has methods)
985
- if (
986
- baseType.kind === "pointer" &&
987
- field !== "value" &&
988
- baseType.base?.kind === "named"
989
- ) {
990
- baseType = this.resolveType(baseType.base);
991
- }
992
- // Check methods on named type itself (for non-struct named types)
993
- if (baseType.kind === "named" && baseType.methods?.has(field)) {
994
- return baseType.methods.get(field);
995
- }
996
- let base = baseType.kind === "named" ? baseType.underlying : baseType;
997
- base = this.resolveType(base);
998
- if (base?.kind === "struct") {
999
- if (base.fields?.has(field)) return base.fields.get(field);
1000
- if (base.methods?.has(field)) return base.methods.get(field);
1001
- return this.err(`No field '${field}' on ${typeStr(baseType)}`, node);
1002
- }
1003
- if (base?.kind === "interface") {
1004
- if (base.methods?.has(field)) return base.methods.get(field);
1005
- return this.err(`No method '${field}' on ${typeStr(baseType)}`, node);
1006
- }
1007
- if (base?.kind === "namespace") {
1008
- // Enforce exported identifier rule for GoFront packages only
1009
- if (
1010
- base._gofront &&
1011
- field.length > 0 &&
1012
- field[0] >= "a" &&
1013
- field[0] <= "z"
1014
- ) {
1015
- return this.err(
1016
- `cannot refer to unexported name ${base.name}.${field}`,
1017
- node,
1018
- );
1019
- }
1020
- if (field in base.members) return base.members[field];
1021
- return this.err(`No member '${field}' in namespace ${base.name}`, node);
1022
- }
1023
- // For explicitly typed non-any values, report the bad access rather than
1024
- // silently returning any — this catches typos on structs and primitive misuse.
1025
- if (base && !isAny(base)) {
1026
- // pointer.value is the GoFront new(T) pattern
1027
- if (base.kind === "pointer" && field === "value") return base.base ?? ANY;
1028
- const badKinds = ["basic", "slice", "array", "map", "func", "tuple"];
1029
- if (badKinds.includes(base.kind)) {
1030
- return this.err(
1031
- `${typeStr(baseType ?? base)} has no field or method '${field}'`,
1032
- node,
1033
- );
1034
- }
1035
- }
1036
- return ANY; // unknown types (e.g. external .d.ts) — allow any access
1037
- }
1038
-
1039
- binaryResultType(op, lt, rt, node) {
1040
- // Type parameters are permissive — allow operations and return the typeParam
1041
- if (lt?.kind === "typeParam" || rt?.kind === "typeParam") {
1042
- if (CMP_OPS.has(op)) return BOOL;
1043
- if (LOG_OPS.has(op)) return BOOL;
1044
- return lt?.kind === "typeParam" ? lt : rt;
1045
- }
1046
- if (CMP_OPS.has(op)) {
1047
- // Complex only supports == and !=
1048
- if (isComplex(lt) || isComplex(rt)) {
1049
- if (op !== "==" && op !== "!=") {
1050
- this.err(
1051
- `invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`,
1052
- node,
1053
- );
1054
- return ANY;
1055
- }
1056
- }
1057
- // Reject slice/map/func comparisons unless one side is nil (Go spec)
1058
- if (op === "==" || op === "!=") {
1059
- const lNil = isNil(lt);
1060
- const rNil = isNil(rt);
1061
- if (!lNil && !rNil) {
1062
- for (const t of [lt, rt]) {
1063
- const base = t?.kind === "named" ? t.underlying : t;
1064
- if (
1065
- base?.kind === "slice" ||
1066
- base?.kind === "map" ||
1067
- base?.kind === "func"
1068
- ) {
1069
- this.err(`operator ${op} not defined on ${typeStr(t)}`, node);
1070
- }
1071
- }
1072
- }
1073
- }
1074
- return BOOL;
1075
- }
1076
- if (LOG_OPS.has(op)) return BOOL;
1077
- if (isAny(lt) || isAny(rt)) return ANY;
1078
- // Complex arithmetic: +, -, *, /
1079
- if (isComplex(lt) || isComplex(rt)) {
1080
- if (!isComplexOrNumeric(lt) || !isComplexOrNumeric(rt)) {
1081
- this.err(
1082
- `invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`,
1083
- node,
1084
- );
1085
- return ANY;
1086
- }
1087
- if (op !== "+" && op !== "-" && op !== "*" && op !== "/") {
1088
- this.err(
1089
- `invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`,
1090
- node,
1091
- );
1092
- return ANY;
1093
- }
1094
- if (lt.kind === "untyped" && rt.kind === "untyped")
1095
- return UNTYPED_COMPLEX;
1096
- return COMPLEX128;
1097
- }
1098
- if (isNumeric(lt) && isNumeric(rt)) {
1099
- const lFloat = (lt.kind === "untyped" ? lt.base : lt.name) === "float64";
1100
- const rFloat = (rt.kind === "untyped" ? rt.base : rt.name) === "float64";
1101
- const isFloat = lFloat || rFloat;
1102
- // Both untyped → result is untyped
1103
- if (lt.kind === "untyped" && rt.kind === "untyped")
1104
- return isFloat ? UNTYPED_FLOAT : UNTYPED_INT;
1105
- // One typed, one untyped → typed wins
1106
- if (lt.kind === "untyped")
1107
- return isFloat && rt.name !== "float64" ? FLOAT64 : rt;
1108
- if (rt.kind === "untyped")
1109
- return isFloat && lt.name !== "float64" ? FLOAT64 : lt;
1110
- // Both typed
1111
- return isFloat ? FLOAT64 : INT;
1112
- }
1113
- if (isString(lt) && isString(rt) && op === "+") {
1114
- if (lt.kind === "untyped" && rt.kind === "untyped") return lt;
1115
- if (lt.kind === "untyped") return rt;
1116
- if (rt.kind === "untyped") return lt;
1117
- return STRING;
1118
- }
1119
- if (node)
1120
- this.err(`Invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`, node);
1121
- return ANY;
1122
- }
1123
-
1124
- assertAssignable(target, source, node) {
1125
- if (!target || !source) return;
1126
- target = this.resolveType(target);
1127
- source = this.resolveType(source);
1128
- // Type params are permissive — allow any assignment
1129
- if (target?.kind === "typeParam" || source?.kind === "typeParam") return;
1130
- if (isAny(target) || isAny(source)) return;
1131
- if (isNil(source)) return; // nil is assignable to anything nullable
1132
- // Pointer type: nil → *T is allowed (handled above); *T → *T check base types
1133
- if (isPointer(target) && isPointer(source)) {
1134
- this.assertAssignable(
1135
- target.base ?? target.underlying?.base,
1136
- source.base ?? source.underlying?.base,
1137
- node,
1138
- );
1139
- return;
1140
- }
1141
-
1142
- // Untyped constants coerce to any compatible typed target
1143
- if (isUntyped(source)) {
1144
- if (isUntyped(target)) return; // untyped → untyped always OK
1145
- if (target.kind === "basic") {
1146
- // untyped int → int or float64
1147
- if (
1148
- source.base === "int" &&
1149
- (target.name === "int" || target.name === "float64")
1150
- )
1151
- return;
1152
- // untyped float → float64 (and int for compatibility)
1153
- if (
1154
- source.base === "float64" &&
1155
- (target.name === "float64" || target.name === "int")
1156
- )
1157
- return;
1158
- // untyped string → string
1159
- if (source.base === "string" && target.name === "string") return;
1160
- // untyped bool → bool
1161
- if (source.base === "bool" && target.name === "bool") return;
1162
- // untyped complex → complex128 or complex64
1163
- if (
1164
- source.base === "complex128" &&
1165
- (target.name === "complex128" || target.name === "complex64")
1166
- )
1167
- return;
1168
- // untyped int/float → complex (Go allows: var z complex128 = 5)
1169
- if (
1170
- isComplex(target) &&
1171
- (source.base === "int" || source.base === "float64")
1172
- )
1173
- return;
1174
- }
1175
- }
1176
-
1177
- // Go untyped constant promotion: int literals are assignable to float64
1178
- if (target.kind === "basic" && source.kind === "basic") {
1179
- if (target.name === "float64" && source.name === "int") return;
1180
- if (target.name === "int" && source.name === "float64") return; // truncating
1181
- }
1182
-
1183
- // Array-specific checks: size matching and array/slice incompatibility
1184
- if (target.kind === "array" && source.kind === "array") {
1185
- if (
1186
- target.size != null &&
1187
- source.size != null &&
1188
- target.size !== source.size
1189
- ) {
1190
- this.err(
1191
- `Cannot assign ${typeStr(source)} to ${typeStr(target)} (different array lengths)`,
1192
- node,
1193
- );
1194
- return;
1195
- }
1196
- // Check element type compatibility
1197
- this.assertAssignable(target.elem, source.elem, node);
1198
- return;
1199
- }
1200
- if (target.kind === "array" && source.kind === "slice") {
1201
- this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
1202
- return;
1203
- }
1204
- if (target.kind === "slice" && source.kind === "array") {
1205
- this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
1206
- return;
1207
- }
1208
-
1209
- if (typeStr(target) !== typeStr(source)) {
1210
- // Interface satisfaction check
1211
- let tBase = target.kind === "named" ? target.underlying : target;
1212
- let sBase = source.kind === "named" ? source.underlying : source;
1213
- tBase = this.resolveType(tBase);
1214
- sBase = this.resolveType(sBase);
1215
- if (tBase?.kind === "interface") {
1216
- // Empty interface (interface{}) accepts any type
1217
- if (tBase.methods.size === 0) return;
1218
- if (!this.implements(source, tBase, node)) {
1219
- this.err(
1220
- `${typeStr(source)} does not implement ${typeStr(target)}`,
1221
- node,
1222
- );
1223
- }
1224
- return;
1225
- }
1226
- this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
1227
- }
1228
- }
1229
-
1230
- implements(srcType, iface, _node) {
1231
- let base = srcType.kind === "named" ? srcType.underlying : srcType;
1232
- base = this.resolveType(base);
1233
- // For non-struct named types, methods live on the named type itself
1234
- const methodMap =
1235
- base?.kind === "struct"
1236
- ? base.methods
1237
- : srcType.kind === "named"
1238
- ? srcType.methods
1239
- : null;
1240
- if (!methodMap) return false;
1241
- for (const [name, required] of iface.methods) {
1242
- const actual = methodMap.get(name);
1243
- if (!actual) return false;
1244
-
1245
- // Check parameter count matches
1246
- const reqParams = required.params ?? [];
1247
- const actParams = actual.params ?? [];
1248
- if (reqParams.length !== actParams.length) return false;
1249
-
1250
- // Check each parameter type matches
1251
- for (let i = 0; i < reqParams.length; i++) {
1252
- if (typeStr(reqParams[i]) !== typeStr(actParams[i])) return false;
1253
- }
1254
-
1255
- // Check variadic flag matches
1256
- if (!!required.variadic !== !!actual.variadic) return false;
1257
-
1258
- // Check return types match (count and each type)
1259
- const reqRets = required.returns ?? [];
1260
- const actRets = actual.returns ?? [];
1261
- if (reqRets.length !== actRets.length) return false;
1262
- for (let i = 0; i < reqRets.length; i++) {
1263
- if (typeStr(reqRets[i]) !== typeStr(actRets[i])) return false;
1264
- }
1265
- }
1266
- return true;
1267
- }
1268
- }
1269
-
1270
- Object.assign(TypeChecker.prototype, statementCheckMethods);
1271
- Object.assign(TypeChecker.prototype, expressionCheckMethods);