gofront 0.0.6 → 0.0.7

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.
@@ -3,11 +3,13 @@
3
3
  //
4
4
  // Split into sub-modules under typechecker/:
5
5
  // types.js — shared type constants, predicates, Scope
6
+ // stdlib.js — browser globals + all built-in package registrations
6
7
  // statements.js — checkBlock, checkStmt
7
8
  // expressions.js — checkExpr, checkCall, checkBuiltin, checkCompositeLit
8
9
 
9
10
  import { expressionCheckMethods } from "./typechecker/expressions.js";
10
11
  import { statementCheckMethods } from "./typechecker/statements.js";
12
+ import { setupGlobals } from "./typechecker/stdlib.js";
11
13
  import {
12
14
  ANY,
13
15
  BASIC_TYPES,
@@ -59,603 +61,7 @@ export class TypeChecker {
59
61
  }
60
62
 
61
63
  _setupGlobals() {
62
- // Browser globals — typed as 'any' so any access/call is permitted
63
- const browserGlobals = [
64
- "console",
65
- "document",
66
- "window",
67
- "navigator",
68
- "location",
69
- "history",
70
- "screen",
71
- "performance",
72
- "crypto",
73
- "indexedDB",
74
- "fetch",
75
- "setTimeout",
76
- "setInterval",
77
- "clearTimeout",
78
- "clearInterval",
79
- "requestAnimationFrame",
80
- "cancelAnimationFrame",
81
- "Math",
82
- "JSON",
83
- "Date",
84
- "RegExp",
85
- "Promise",
86
- "Error",
87
- "Symbol",
88
- "String",
89
- "Number",
90
- "Boolean",
91
- "Array",
92
- "Object",
93
- "parseInt",
94
- "parseFloat",
95
- "isNaN",
96
- "isFinite",
97
- "encodeURIComponent",
98
- "decodeURIComponent",
99
- "atob",
100
- "btoa",
101
- "alert",
102
- "confirm",
103
- "prompt",
104
- "localStorage",
105
- "sessionStorage",
106
- "WebSocket",
107
- "Worker",
108
- "HTMLElement",
109
- "Element",
110
- "Event",
111
- "CustomEvent",
112
- "URL",
113
- "URLSearchParams",
114
- "FormData",
115
- "Headers",
116
- "Request",
117
- "Response",
118
- "Blob",
119
- "File",
120
- "FileReader",
121
- "ArrayBuffer",
122
- "Uint8Array",
123
- "TextEncoder",
124
- "TextDecoder",
125
- "WebGLRenderingContext",
126
- "WebGL2RenderingContext",
127
- "GPUDevice",
128
- "GPUAdapter",
129
- ];
130
- for (const g of browserGlobals) this.globals.define(g, ANY);
131
-
132
- // Built-in functions
133
- // fmt package — string formatting (variadic: format string + any args)
134
- const fmtVariadic = (ret) => ({
135
- kind: "func",
136
- params: [STRING, ANY],
137
- returns: [ret],
138
- variadic: true,
139
- });
140
- this.globals.define("fmt", {
141
- kind: "namespace",
142
- name: "fmt",
143
- members: {
144
- Sprintf: fmtVariadic(STRING),
145
- Errorf: fmtVariadic(ERROR),
146
- Printf: fmtVariadic(VOID),
147
- Println: fmtVariadic(VOID),
148
- Print: fmtVariadic(VOID),
149
- Fprintf: {
150
- kind: "func",
151
- params: [ANY, STRING],
152
- returns: [INT, ERROR],
153
- variadic: true,
154
- },
155
- Fprintln: {
156
- kind: "func",
157
- params: [ANY],
158
- returns: [INT, ERROR],
159
- variadic: true,
160
- },
161
- Fprint: {
162
- kind: "func",
163
- params: [ANY],
164
- returns: [INT, ERROR],
165
- variadic: true,
166
- },
167
- },
168
- });
169
-
170
- // strings package
171
- const strFn1 = (ret) => ({
172
- kind: "func",
173
- params: [STRING],
174
- returns: [ret],
175
- });
176
- const strFn2 = (p2, ret) => ({
177
- kind: "func",
178
- params: [STRING, p2],
179
- returns: [ret],
180
- });
181
- const strFn3 = (p2, p3, ret) => ({
182
- kind: "func",
183
- params: [STRING, p2, p3],
184
- returns: [ret],
185
- });
186
- this.globals.define("strings", {
187
- kind: "namespace",
188
- name: "strings",
189
- members: {
190
- Contains: strFn2(STRING, BOOL),
191
- HasPrefix: strFn2(STRING, BOOL),
192
- HasSuffix: strFn2(STRING, BOOL),
193
- Index: strFn2(STRING, INT),
194
- LastIndex: strFn2(STRING, INT),
195
- Count: strFn2(STRING, INT),
196
- Repeat: strFn2(INT, STRING),
197
- Replace: {
198
- kind: "func",
199
- params: [STRING, STRING, STRING, INT],
200
- returns: [STRING],
201
- },
202
- ReplaceAll: strFn3(STRING, STRING, STRING),
203
- ToUpper: strFn1(STRING),
204
- ToLower: strFn1(STRING),
205
- TrimSpace: strFn1(STRING),
206
- Trim: strFn2(STRING, STRING),
207
- TrimPrefix: strFn2(STRING, STRING),
208
- TrimSuffix: strFn2(STRING, STRING),
209
- TrimLeft: strFn2(STRING, STRING),
210
- TrimRight: strFn2(STRING, STRING),
211
- Split: strFn2(STRING, { kind: "slice", elem: STRING }),
212
- Join: {
213
- kind: "func",
214
- params: [{ kind: "slice", elem: STRING }, STRING],
215
- returns: [STRING],
216
- },
217
- EqualFold: strFn2(STRING, BOOL),
218
- },
219
- });
220
-
221
- // bytes package — operates on []byte (JS arrays of numbers)
222
- const BYTE_SLICE = { kind: "slice", elem: INT };
223
- const byFn1 = (ret) => ({
224
- kind: "func",
225
- params: [BYTE_SLICE],
226
- returns: [ret],
227
- });
228
- const byFn2 = (p2, ret) => ({
229
- kind: "func",
230
- params: [BYTE_SLICE, p2],
231
- returns: [ret],
232
- });
233
- this.globals.define("bytes", {
234
- kind: "namespace",
235
- name: "bytes",
236
- members: {
237
- Contains: byFn2(BYTE_SLICE, BOOL),
238
- HasPrefix: byFn2(BYTE_SLICE, BOOL),
239
- HasSuffix: byFn2(BYTE_SLICE, BOOL),
240
- Index: byFn2(BYTE_SLICE, INT),
241
- Count: byFn2(BYTE_SLICE, INT),
242
- Repeat: byFn2(INT, BYTE_SLICE),
243
- Replace: {
244
- kind: "func",
245
- params: [BYTE_SLICE, BYTE_SLICE, BYTE_SLICE, INT],
246
- returns: [BYTE_SLICE],
247
- },
248
- ToUpper: byFn1(BYTE_SLICE),
249
- ToLower: byFn1(BYTE_SLICE),
250
- TrimSpace: byFn1(BYTE_SLICE),
251
- Trim: byFn2(STRING, BYTE_SLICE),
252
- Equal: byFn2(BYTE_SLICE, BOOL),
253
- Split: byFn2(BYTE_SLICE, { kind: "slice", elem: BYTE_SLICE }),
254
- Join: {
255
- kind: "func",
256
- params: [{ kind: "slice", elem: BYTE_SLICE }, BYTE_SLICE],
257
- returns: [BYTE_SLICE],
258
- },
259
- },
260
- });
261
-
262
- // strconv package
263
- this.globals.define("strconv", {
264
- kind: "namespace",
265
- name: "strconv",
266
- members: {
267
- Itoa: { kind: "func", params: [INT], returns: [STRING] },
268
- Atoi: {
269
- kind: "func",
270
- params: [STRING],
271
- returns: [INT, ERROR],
272
- },
273
- FormatFloat: {
274
- kind: "func",
275
- params: [FLOAT64, INT, INT, INT],
276
- returns: [STRING],
277
- },
278
- FormatBool: { kind: "func", params: [BOOL], returns: [STRING] },
279
- FormatInt: { kind: "func", params: [INT, INT], returns: [STRING] },
280
- ParseFloat: {
281
- kind: "func",
282
- params: [STRING, INT],
283
- returns: [FLOAT64, ERROR],
284
- },
285
- ParseInt: {
286
- kind: "func",
287
- params: [STRING, INT, INT],
288
- returns: [INT, ERROR],
289
- },
290
- ParseBool: {
291
- kind: "func",
292
- params: [STRING],
293
- returns: [BOOL, ERROR],
294
- },
295
- },
296
- });
297
-
298
- // sort package
299
- this.globals.define("sort", {
300
- kind: "namespace",
301
- name: "sort",
302
- members: {
303
- Ints: {
304
- kind: "func",
305
- params: [{ kind: "slice", elem: INT }],
306
- returns: [VOID],
307
- },
308
- Float64s: {
309
- kind: "func",
310
- params: [{ kind: "slice", elem: FLOAT64 }],
311
- returns: [VOID],
312
- },
313
- Strings: {
314
- kind: "func",
315
- params: [{ kind: "slice", elem: STRING }],
316
- returns: [VOID],
317
- },
318
- Slice: {
319
- kind: "func",
320
- params: [ANY, ANY],
321
- returns: [VOID],
322
- },
323
- SliceStable: {
324
- kind: "func",
325
- params: [ANY, ANY],
326
- returns: [VOID],
327
- },
328
- SliceIsSorted: {
329
- kind: "func",
330
- params: [ANY, ANY],
331
- returns: [BOOL],
332
- },
333
- },
334
- });
335
-
336
- // math package
337
- this.globals.define("math", {
338
- kind: "namespace",
339
- name: "math",
340
- members: {
341
- Abs: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
342
- Floor: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
343
- Ceil: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
344
- Round: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
345
- Sqrt: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
346
- Cbrt: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
347
- Pow: {
348
- kind: "func",
349
- params: [FLOAT64, FLOAT64],
350
- returns: [FLOAT64],
351
- },
352
- Log: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
353
- Log2: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
354
- Log10: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
355
- Sin: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
356
- Cos: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
357
- Tan: { kind: "func", params: [FLOAT64], returns: [FLOAT64] },
358
- Min: {
359
- kind: "func",
360
- params: [FLOAT64, FLOAT64],
361
- returns: [FLOAT64],
362
- },
363
- Max: {
364
- kind: "func",
365
- params: [FLOAT64, FLOAT64],
366
- returns: [FLOAT64],
367
- },
368
- Mod: {
369
- kind: "func",
370
- params: [FLOAT64, FLOAT64],
371
- returns: [FLOAT64],
372
- },
373
- Inf: { kind: "func", params: [INT], returns: [FLOAT64] },
374
- IsNaN: { kind: "func", params: [FLOAT64], returns: [BOOL] },
375
- IsInf: {
376
- kind: "func",
377
- params: [FLOAT64, INT],
378
- returns: [BOOL],
379
- },
380
- NaN: { kind: "func", params: [], returns: [FLOAT64] },
381
- // Constants (typed as func with no params for namespace member access)
382
- Pi: FLOAT64,
383
- E: FLOAT64,
384
- MaxFloat64: FLOAT64,
385
- SmallestNonzeroFloat64: FLOAT64,
386
- MaxInt: INT,
387
- MinInt: INT,
388
- },
389
- });
390
-
391
- // errors package
392
- this.globals.define("errors", {
393
- kind: "namespace",
394
- name: "errors",
395
- members: {
396
- New: { kind: "func", params: [STRING], returns: [ERROR] },
397
- Is: { kind: "func", params: [ERROR, ERROR], returns: [BOOL] },
398
- Unwrap: { kind: "func", params: [ERROR], returns: [ERROR] },
399
- },
400
- });
401
-
402
- // time package (partial — JS-friendly subset)
403
- this.globals.define("time", {
404
- kind: "namespace",
405
- name: "time",
406
- members: {
407
- Now: { kind: "func", params: [], returns: [ANY] },
408
- Since: { kind: "func", params: [ANY], returns: [ANY] },
409
- Sleep: { kind: "func", params: [ANY], returns: [VOID], async: true },
410
- Millisecond: INT,
411
- Second: INT,
412
- Minute: INT,
413
- Hour: INT,
414
- },
415
- });
416
-
417
- // unicode package
418
- const runeToStr = { kind: "func", params: [INT], returns: [BOOL] };
419
- const runeTrans = { kind: "func", params: [INT], returns: [INT] };
420
- this.globals.define("unicode", {
421
- kind: "namespace",
422
- name: "unicode",
423
- members: {
424
- IsLetter: runeToStr,
425
- IsDigit: runeToStr,
426
- IsSpace: runeToStr,
427
- IsUpper: runeToStr,
428
- IsLower: runeToStr,
429
- IsPunct: runeToStr,
430
- IsControl: runeToStr,
431
- IsPrint: runeToStr,
432
- IsGraphic: runeToStr,
433
- ToUpper: runeTrans,
434
- ToLower: runeTrans,
435
- },
436
- });
437
-
438
- // os package (JS-friendly subset)
439
- this.globals.define("os", {
440
- kind: "namespace",
441
- name: "os",
442
- members: {
443
- Exit: { kind: "func", params: [INT], returns: [VOID] },
444
- Args: { kind: "slice", elem: STRING },
445
- Getenv: { kind: "func", params: [STRING], returns: [STRING] },
446
- Stdout: ANY,
447
- Stderr: ANY,
448
- Stdin: ANY,
449
- },
450
- });
451
-
452
- // slices package (Go 1.21)
453
- // Functions are generic — use ANY for slice params so any concrete slice type
454
- // is accepted without an assignability error (same pattern as sort.Slice).
455
- this.globals.define("slices", {
456
- kind: "namespace",
457
- name: "slices",
458
- members: {
459
- Contains: { kind: "func", params: [ANY, ANY], returns: [BOOL] },
460
- Index: { kind: "func", params: [ANY, ANY], returns: [INT] },
461
- Equal: { kind: "func", params: [ANY, ANY], returns: [BOOL] },
462
- Compare: { kind: "func", params: [ANY, ANY], returns: [INT] },
463
- Sort: { kind: "func", params: [ANY], returns: [VOID] },
464
- SortFunc: { kind: "func", params: [ANY, ANY], returns: [VOID] },
465
- SortStableFunc: { kind: "func", params: [ANY, ANY], returns: [VOID] },
466
- IsSorted: { kind: "func", params: [ANY], returns: [BOOL] },
467
- IsSortedFunc: { kind: "func", params: [ANY, ANY], returns: [BOOL] },
468
- Reverse: { kind: "func", params: [ANY], returns: [VOID] },
469
- Max: { kind: "func", params: [ANY], returns: [ANY] },
470
- Min: { kind: "func", params: [ANY], returns: [ANY] },
471
- MaxFunc: { kind: "func", params: [ANY, ANY], returns: [ANY] },
472
- MinFunc: { kind: "func", params: [ANY, ANY], returns: [ANY] },
473
- Clone: { kind: "func", params: [ANY], returns: [ANY] },
474
- Compact: { kind: "func", params: [ANY], returns: [ANY] },
475
- CompactFunc: { kind: "func", params: [ANY, ANY], returns: [ANY] },
476
- Concat: { kind: "func", params: [ANY], returns: [ANY], variadic: true },
477
- Delete: { kind: "func", params: [ANY, INT, INT], returns: [ANY] },
478
- DeleteFunc: { kind: "func", params: [ANY, ANY], returns: [ANY] },
479
- Insert: {
480
- kind: "func",
481
- params: [ANY, INT, ANY],
482
- returns: [ANY],
483
- variadic: true,
484
- },
485
- Replace: {
486
- kind: "func",
487
- params: [ANY, INT, INT, ANY],
488
- returns: [ANY],
489
- variadic: true,
490
- },
491
- Grow: { kind: "func", params: [ANY, INT], returns: [ANY] },
492
- Clip: { kind: "func", params: [ANY], returns: [ANY] },
493
- },
494
- });
495
-
496
- // html package
497
- this.globals.define("html", {
498
- kind: "namespace",
499
- name: "html",
500
- members: {
501
- EscapeString: { kind: "func", params: [STRING], returns: [STRING] },
502
- UnescapeString: { kind: "func", params: [STRING], returns: [STRING] },
503
- },
504
- });
505
-
506
- // maps package (Go 1.21)
507
- this.globals.define("maps", {
508
- kind: "namespace",
509
- name: "maps",
510
- members: {
511
- Keys: { kind: "func", params: [ANY], returns: [ANY] },
512
- Values: { kind: "func", params: [ANY], returns: [ANY] },
513
- Clone: { kind: "func", params: [ANY], returns: [ANY] },
514
- Copy: { kind: "func", params: [ANY, ANY], returns: [VOID] },
515
- Equal: { kind: "func", params: [ANY, ANY], returns: [BOOL] },
516
- EqualFunc: { kind: "func", params: [ANY, ANY, ANY], returns: [BOOL] },
517
- Delete: { kind: "func", params: [ANY, ANY], returns: [VOID] },
518
- DeleteFunc: { kind: "func", params: [ANY, ANY], returns: [VOID] },
519
- },
520
- });
521
-
522
- // regexp package
523
- const REGEXP_T = {
524
- kind: "named",
525
- name: "regexp.Regexp",
526
- underlying: { kind: "struct", fields: new Map(), methods: new Map() },
527
- };
528
- const REGEXP_PTR = { kind: "pointer", base: REGEXP_T };
529
- const STR_SLICE = { kind: "slice", elem: STRING };
530
- const INT_SLICE = { kind: "slice", elem: INT };
531
- const regexpMethods = new Map([
532
- ["MatchString", { kind: "func", params: [STRING], returns: [BOOL] }],
533
- ["FindString", { kind: "func", params: [STRING], returns: [STRING] }],
534
- [
535
- "FindStringIndex",
536
- { kind: "func", params: [STRING], returns: [INT_SLICE] },
537
- ],
538
- [
539
- "FindAllString",
540
- { kind: "func", params: [STRING, INT], returns: [STR_SLICE] },
541
- ],
542
- [
543
- "FindStringSubmatch",
544
- { kind: "func", params: [STRING], returns: [STR_SLICE] },
545
- ],
546
- [
547
- "FindAllStringSubmatch",
548
- {
549
- kind: "func",
550
- params: [STRING, INT],
551
- returns: [{ kind: "slice", elem: STR_SLICE }],
552
- },
553
- ],
554
- [
555
- "ReplaceAllString",
556
- { kind: "func", params: [STRING, STRING], returns: [STRING] },
557
- ],
558
- [
559
- "ReplaceAllLiteralString",
560
- { kind: "func", params: [STRING, STRING], returns: [STRING] },
561
- ],
562
- ["Split", { kind: "func", params: [STRING, INT], returns: [STR_SLICE] }],
563
- ["String", { kind: "func", params: [], returns: [STRING] }],
564
- ]);
565
- REGEXP_T.underlying.methods = regexpMethods;
566
- this.types.set("regexp.Regexp", REGEXP_T);
567
- this.globals.define("regexp", {
568
- kind: "namespace",
569
- name: "regexp",
570
- members: {
571
- MustCompile: { kind: "func", params: [STRING], returns: [REGEXP_PTR] },
572
- Compile: {
573
- kind: "func",
574
- params: [STRING],
575
- returns: [REGEXP_PTR, ERROR],
576
- },
577
- MatchString: {
578
- kind: "func",
579
- params: [STRING, STRING],
580
- returns: [BOOL, ERROR],
581
- },
582
- QuoteMeta: { kind: "func", params: [STRING], returns: [STRING] },
583
- },
584
- });
585
-
586
- // strings.Builder
587
- const BYTE_SLICE_T = { kind: "slice", elem: INT };
588
- const strBuilderMethods = new Map([
589
- [
590
- "WriteString",
591
- { kind: "func", params: [STRING], returns: [INT, ERROR] },
592
- ],
593
- ["WriteByte", { kind: "func", params: [INT], returns: [ERROR] }],
594
- ["WriteRune", { kind: "func", params: [INT], returns: [INT, ERROR] }],
595
- [
596
- "Write",
597
- { kind: "func", params: [BYTE_SLICE_T], returns: [INT, ERROR] },
598
- ],
599
- ["String", { kind: "func", params: [], returns: [STRING] }],
600
- ["Len", { kind: "func", params: [], returns: [INT] }],
601
- ["Reset", { kind: "func", params: [], returns: [VOID] }],
602
- ["Grow", { kind: "func", params: [INT], returns: [VOID] }],
603
- ]);
604
- this.types.set("strings.Builder", {
605
- kind: "named",
606
- name: "strings.Builder",
607
- underlying: {
608
- kind: "struct",
609
- fields: new Map(),
610
- methods: strBuilderMethods,
611
- },
612
- });
613
-
614
- // bytes.Buffer
615
- const bytesBufferMethods = new Map([
616
- [
617
- "WriteString",
618
- { kind: "func", params: [STRING], returns: [INT, ERROR] },
619
- ],
620
- ["WriteByte", { kind: "func", params: [INT], returns: [ERROR] }],
621
- [
622
- "Write",
623
- { kind: "func", params: [BYTE_SLICE_T], returns: [INT, ERROR] },
624
- ],
625
- ["String", { kind: "func", params: [], returns: [STRING] }],
626
- ["Bytes", { kind: "func", params: [], returns: [BYTE_SLICE_T] }],
627
- ["Len", { kind: "func", params: [], returns: [INT] }],
628
- ["Reset", { kind: "func", params: [], returns: [VOID] }],
629
- ["Grow", { kind: "func", params: [INT], returns: [VOID] }],
630
- ]);
631
- this.types.set("bytes.Buffer", {
632
- kind: "named",
633
- name: "bytes.Buffer",
634
- underlying: {
635
- kind: "struct",
636
- fields: new Map(),
637
- methods: bytesBufferMethods,
638
- },
639
- });
640
-
641
- this.globals.define("append", { kind: "builtin", name: "append" });
642
- this.globals.define("len", { kind: "builtin", name: "len" });
643
- this.globals.define("cap", { kind: "builtin", name: "cap" });
644
- this.globals.define("make", { kind: "builtin", name: "make" });
645
- this.globals.define("delete", { kind: "builtin", name: "delete" });
646
- this.globals.define("print", { kind: "builtin", name: "print" });
647
- this.globals.define("println", { kind: "builtin", name: "println" });
648
- this.globals.define("panic", { kind: "builtin", name: "panic" });
649
- this.globals.define("recover", { kind: "builtin", name: "recover" });
650
- this.globals.define("new", { kind: "builtin", name: "new" });
651
- this.globals.define("copy", { kind: "builtin", name: "copy" });
652
- this.globals.define("error", { kind: "builtin", name: "error" });
653
- this.globals.define("min", { kind: "builtin", name: "min" });
654
- this.globals.define("max", { kind: "builtin", name: "max" });
655
- this.globals.define("clear", { kind: "builtin", name: "clear" });
656
- this.globals.define("complex", { kind: "builtin", name: "complex" });
657
- this.globals.define("real", { kind: "builtin", name: "real" });
658
- this.globals.define("imag", { kind: "builtin", name: "imag" });
64
+ setupGlobals(this.globals, this.types);
659
65
  }
660
66
 
661
67
  addDefinitions(types, values) {
@@ -680,7 +86,10 @@ export class TypeChecker {
680
86
  members,
681
87
  _gofront: true,
682
88
  });
683
- for (const [name, type] of types) this.types.set(name, type);
89
+ for (const [name, type] of types) {
90
+ this.types.set(name, type);
91
+ this.types.set(`${pkgName}.${name}`, type);
92
+ }
684
93
  }
685
94
 
686
95
  // Register an import for unused-import detection.
@@ -884,6 +293,8 @@ export class TypeChecker {
884
293
  if (underlying.kind === "struct") {
885
294
  underlying.name = decl.name;
886
295
  underlying.methods = new Map();
296
+ } else if (underlying.kind !== "interface") {
297
+ named.methods = new Map();
887
298
  }
888
299
  if (underlying.kind === "interface") {
889
300
  underlying.name = decl.name;
@@ -952,7 +363,7 @@ export class TypeChecker {
952
363
  this.globals.define(decl.name, funcType);
953
364
  }
954
365
  } else {
955
- // Method: attach to struct type
366
+ // Method: attach to struct or named non-struct type
956
367
  const recvTypeName =
957
368
  decl.recvType.kind === "GenericTypeName"
958
369
  ? decl.recvType.name
@@ -972,6 +383,8 @@ export class TypeChecker {
972
383
  const base = recvType?.underlying ?? recvType;
973
384
  if (base?.kind === "struct") {
974
385
  base.methods.set(decl.name, funcType);
386
+ } else if (recvType?.kind === "named" && recvType.methods) {
387
+ recvType.methods.set(decl.name, funcType);
975
388
  }
976
389
  }
977
390
  }
@@ -1576,6 +989,10 @@ export class TypeChecker {
1576
989
  ) {
1577
990
  baseType = this.resolveType(baseType.base);
1578
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
+ }
1579
996
  let base = baseType.kind === "named" ? baseType.underlying : baseType;
1580
997
  base = this.resolveType(base);
1581
998
  if (base?.kind === "struct") {
@@ -1813,9 +1230,16 @@ export class TypeChecker {
1813
1230
  implements(srcType, iface, _node) {
1814
1231
  let base = srcType.kind === "named" ? srcType.underlying : srcType;
1815
1232
  base = this.resolveType(base);
1816
- if (base?.kind !== "struct") return false;
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;
1817
1241
  for (const [name, required] of iface.methods) {
1818
- const actual = base.methods?.get(name);
1242
+ const actual = methodMap.get(name);
1819
1243
  if (!actual) return false;
1820
1244
 
1821
1245
  // Check parameter count matches