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