gofront 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/parser.js CHANGED
@@ -129,18 +129,23 @@ export class Parser {
129
129
 
130
130
  parseImport() {
131
131
  this.expect(T.IMPORT);
132
- const paths = [];
132
+ const imports = [];
133
+ const parseOne = () => {
134
+ const alias = this.check(T.IDENT) ? this.advance().value : null;
135
+ const path = this.expect(T.STRING).value;
136
+ return { path, alias };
137
+ };
133
138
  if (this.match(T.LPAREN)) {
134
139
  while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
135
- paths.push(this.expect(T.STRING).value);
140
+ imports.push(parseOne());
136
141
  this.semi();
137
142
  }
138
143
  this.expect(T.RPAREN);
139
144
  } else {
140
- paths.push(this.expect(T.STRING).value);
145
+ imports.push(parseOne());
141
146
  }
142
147
  this.semi();
143
- return { kind: "ImportDecl", paths };
148
+ return { kind: "ImportDecl", imports };
144
149
  }
145
150
 
146
151
  // ── Top-level declarations ───────────────────────────────────
@@ -155,7 +160,7 @@ export class Parser {
155
160
  case T.TYPE:
156
161
  return this.parseTypeDecl();
157
162
  case T.VAR:
158
- return this.parseVarDecl(true);
163
+ return this.parseVarDecl();
159
164
  case T.CONST:
160
165
  return this.parseConstDecl();
161
166
  default:
@@ -292,7 +297,12 @@ export class Parser {
292
297
 
293
298
  parseReturnType(inTypeExpr = false) {
294
299
  // No return type
295
- if (this.check(T.LBRACE) || this.check(T.SEMICOLON) || this.check(T.EOF))
300
+ if (
301
+ this.check(T.LBRACE) ||
302
+ this.check(T.RBRACE) ||
303
+ this.check(T.SEMICOLON) ||
304
+ this.check(T.EOF)
305
+ )
296
306
  return null;
297
307
  // Inside a type expression (e.g. func type in a tuple), `,` and `)` also end the type
298
308
  if (inTypeExpr && (this.check(T.COMMA) || this.check(T.RPAREN)))
@@ -330,12 +340,13 @@ export class Parser {
330
340
  parseTypeDecl() {
331
341
  this.expect(T.TYPE);
332
342
  const name = this.expect(T.IDENT).value;
343
+ const isAlias = this.match(T.ASSIGN);
333
344
  const type = this.parseType();
334
345
  this.semi();
335
- return { kind: "TypeDecl", name, type };
346
+ return { kind: "TypeDecl", name, type, isAlias };
336
347
  }
337
348
 
338
- parseVarDecl(_topLevel = false) {
349
+ parseVarDecl() {
339
350
  this.expect(T.VAR);
340
351
  const decls = [];
341
352
  if (this.match(T.LPAREN)) {
@@ -466,6 +477,11 @@ export class Parser {
466
477
  this.advance();
467
478
  return { kind: "SliceType", elem: this.parseType() };
468
479
  }
480
+ if (this.match(T.ELLIPSIS)) {
481
+ // [...]T — array with inferred length from composite literal
482
+ this.expect(T.RBRACKET);
483
+ return { kind: "ArrayType", inferLen: true, elem: this.parseType() };
484
+ }
469
485
  // fixed array: [n]T — treated as slice for JS purposes
470
486
  const size = this.parseExpr();
471
487
  this.expect(T.RBRACKET);
@@ -508,6 +524,8 @@ export class Parser {
508
524
  continue;
509
525
  }
510
526
  const type = this.parseType();
527
+ // Consume optional struct tag (backtick string), e.g. `json:"name"`
528
+ if (this.check(T.STRING)) this.advance();
511
529
  fields.push({ names, type });
512
530
  this.semi();
513
531
  }
@@ -519,15 +537,27 @@ export class Parser {
519
537
  this.expect(T.INTERFACE);
520
538
  this.expect(T.LBRACE);
521
539
  const methods = [];
540
+ const embeds = [];
522
541
  while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
523
542
  const name = this.expect(T.IDENT).value;
524
- const params = this.parseParamList();
525
- const returnType = this.parseReturnType();
526
- methods.push({ name, params, returnType });
543
+ if (this.check(T.LPAREN)) {
544
+ // Method signature: Name(params) returnType
545
+ const params = this.parseParamList();
546
+ const returnType = this.parseReturnType();
547
+ methods.push({ name, params, returnType });
548
+ } else {
549
+ // Embedded interface: TypeName or pkg.TypeName
550
+ let typeName = name;
551
+ if (this.check(T.DOT)) {
552
+ this.advance();
553
+ typeName += `.${this.expect(T.IDENT).value}`;
554
+ }
555
+ embeds.push({ kind: "TypeName", name: typeName });
556
+ }
527
557
  this.semi();
528
558
  }
529
559
  this.expect(T.RBRACE);
530
- return { kind: "InterfaceType", methods };
560
+ return { kind: "InterfaceType", methods, embeds };
531
561
  }
532
562
 
533
563
  // ── Statements ───────────────────────────────────────────────
@@ -564,10 +594,20 @@ export class Parser {
564
594
  return this.parseDefer();
565
595
  case T.BREAK:
566
596
  this.advance();
597
+ if (this.check(T.IDENT)) {
598
+ const label = this.advance().value;
599
+ this.semi();
600
+ return { kind: "BranchStmt", keyword: "break", label };
601
+ }
567
602
  this.semi();
568
603
  return { kind: "BranchStmt", keyword: "break" };
569
604
  case T.CONTINUE:
570
605
  this.advance();
606
+ if (this.check(T.IDENT)) {
607
+ const label = this.advance().value;
608
+ this.semi();
609
+ return { kind: "BranchStmt", keyword: "continue", label };
610
+ }
571
611
  this.semi();
572
612
  return { kind: "BranchStmt", keyword: "continue" };
573
613
  case T.FALLTHROUGH:
@@ -582,6 +622,14 @@ export class Parser {
582
622
  return this.parseSwitch();
583
623
  case T.LBRACE:
584
624
  return this.parseBlock();
625
+ case T.IDENT:
626
+ if (this.check2(T.COLON)) {
627
+ const label = this.advance().value; // consume IDENT
628
+ this.advance(); // consume COLON
629
+ const body = this.parseStmt();
630
+ return { kind: "LabeledStmt", label, body };
631
+ }
632
+ return this.parseSimpleStmt();
585
633
  default:
586
634
  return this.parseSimpleStmt();
587
635
  }
@@ -687,14 +735,27 @@ export class Parser {
687
735
  let init = null;
688
736
  let tag = null;
689
737
  if (!this.check(T.LBRACE)) {
690
- const expr = this.parseExpr(0, true);
738
+ const first = this.parseSimpleStmtRaw(true);
691
739
  if (this.match(T.SEMICOLON)) {
692
- init = this.exprToStmt(expr);
693
- if (!this.check(T.LBRACE)) tag = this.parseExpr(0, true);
740
+ init = first;
741
+ if (!this.check(T.LBRACE))
742
+ tag = this.stmtToExpr(this.parseSimpleStmtRaw(true));
694
743
  } else {
695
- tag = expr;
744
+ tag = this.stmtToExpr(first);
696
745
  }
697
746
  }
747
+
748
+ // Detect type switch: switch x.(type) or switch v := x.(type)
749
+ if (tag?.kind === "TypeSwitchExpr") {
750
+ return this._parseTypeSwitchBody(null, tag.expr);
751
+ }
752
+ if (tag?.kind === "DefineStmt" && tag.rhs[0]?.kind === "TypeSwitchExpr") {
753
+ return this._parseTypeSwitchBody(
754
+ tag.lhs[0]?.name ?? null,
755
+ tag.rhs[0].expr,
756
+ );
757
+ }
758
+
698
759
  this.expect(T.LBRACE);
699
760
  const cases = [];
700
761
  while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
@@ -702,29 +763,15 @@ export class Parser {
702
763
  this.advance();
703
764
  const list = this.parseExprList();
704
765
  this.expect(T.COLON);
705
- const stmts = [];
706
- while (
707
- !this.check(T.CASE) &&
708
- !this.check(T.DEFAULT) &&
709
- !this.check(T.RBRACE)
710
- ) {
711
- stmts.push(this.parseStmt());
712
- this.semi();
713
- }
714
- cases.push({ kind: "CaseClause", list, stmts });
766
+ cases.push({ kind: "CaseClause", list, stmts: this._parseCaseBody() });
715
767
  } else if (this.check(T.DEFAULT)) {
716
768
  this.advance();
717
769
  this.expect(T.COLON);
718
- const stmts = [];
719
- while (
720
- !this.check(T.CASE) &&
721
- !this.check(T.DEFAULT) &&
722
- !this.check(T.RBRACE)
723
- ) {
724
- stmts.push(this.parseStmt());
725
- this.semi();
726
- }
727
- cases.push({ kind: "CaseClause", list: null, stmts });
770
+ cases.push({
771
+ kind: "CaseClause",
772
+ list: null,
773
+ stmts: this._parseCaseBody(),
774
+ });
728
775
  } else {
729
776
  this.err("Expected case or default");
730
777
  }
@@ -733,6 +780,54 @@ export class Parser {
733
780
  return { kind: "SwitchStmt", init, tag, cases };
734
781
  }
735
782
 
783
+ _parseCaseBody() {
784
+ const stmts = [];
785
+ while (
786
+ !this.check(T.CASE) &&
787
+ !this.check(T.DEFAULT) &&
788
+ !this.check(T.RBRACE)
789
+ ) {
790
+ stmts.push(this.parseStmt());
791
+ this.semi();
792
+ }
793
+ return stmts;
794
+ }
795
+
796
+ _parseTypeSwitchBody(assign, expr) {
797
+ this.expect(T.LBRACE);
798
+ const cases = [];
799
+ while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
800
+ if (this.check(T.CASE)) {
801
+ this.advance();
802
+ const types = this._parseTypeSwitchTypeList();
803
+ this.expect(T.COLON);
804
+ cases.push({ types, stmts: this._parseCaseBody() });
805
+ } else if (this.check(T.DEFAULT)) {
806
+ this.advance();
807
+ this.expect(T.COLON);
808
+ cases.push({ types: null, stmts: this._parseCaseBody() });
809
+ } else {
810
+ this.err("Expected case or default");
811
+ }
812
+ }
813
+ this.expect(T.RBRACE);
814
+ return { kind: "TypeSwitchStmt", assign, expr, cases };
815
+ }
816
+
817
+ _parseTypeSwitchTypeList() {
818
+ const parseOne = () => {
819
+ // nil is a keyword, not a type name, so handle it explicitly
820
+ if (this.check(T.NIL)) {
821
+ this.advance();
822
+ return { kind: "TypeName", name: "nil" };
823
+ }
824
+ return this.parseType();
825
+ };
826
+ const types = [parseOne()];
827
+ while (this.match(T.COMMA)) types.push(parseOne());
828
+ return types;
829
+ }
830
+
736
831
  // Parses a "simple statement": assignment, define, inc/dec, or expression.
737
832
  // Returns a statement node.
738
833
  parseSimpleStmt() {
@@ -770,6 +865,11 @@ export class Parser {
770
865
  T.STAR_ASSIGN,
771
866
  T.SLASH_ASSIGN,
772
867
  T.PERCENT_ASSIGN,
868
+ T.AMP_ASSIGN,
869
+ T.PIPE_ASSIGN,
870
+ T.CARET_ASSIGN,
871
+ T.LSHIFT_ASSIGN,
872
+ T.RSHIFT_ASSIGN,
773
873
  ];
774
874
  if (assignOps.includes(t.type)) {
775
875
  const op = this.advance().value;
@@ -800,10 +900,6 @@ export class Parser {
800
900
  return stmt;
801
901
  }
802
902
 
803
- exprToStmt(expr) {
804
- return { kind: "ExprStmt", expr };
805
- }
806
-
807
903
  // ── Expressions ──────────────────────────────────────────────
808
904
 
809
905
  parseExprList() {
@@ -855,9 +951,16 @@ export class Parser {
855
951
  // type assertion: expr.(Type)
856
952
  if (this.check(T.LPAREN)) {
857
953
  this.advance();
858
- const type = this.parseType();
859
- this.expect(T.RPAREN);
860
- expr = { kind: "TypeAssertExpr", expr, type };
954
+ // x.(type) — type switch guard expression
955
+ if (this.check(T.TYPE)) {
956
+ this.advance();
957
+ this.expect(T.RPAREN);
958
+ expr = { kind: "TypeSwitchExpr", expr };
959
+ } else {
960
+ const type = this.parseType();
961
+ this.expect(T.RPAREN);
962
+ expr = { kind: "TypeAssertExpr", expr, type };
963
+ }
861
964
  } else {
862
965
  const field = this.advance().value;
863
966
  expr = { kind: "SelectorExpr", expr, field };
@@ -915,11 +1018,25 @@ export class Parser {
915
1018
  (t.type === T.MAP || isSliceOrArrayType) &&
916
1019
  !this.looksLikeCompositeLitArg()
917
1020
  ) {
918
- args.push({ kind: "TypeExpr", type: this.parseType() });
1021
+ const typeNode = this.parseType();
1022
+ // []byte(s) / []rune(s) used as a call argument — it's a type conversion
1023
+ if (this.check(T.LPAREN)) {
1024
+ this.advance();
1025
+ const convExpr = this.parseExpr();
1026
+ this.expect(T.RPAREN);
1027
+ args.push({
1028
+ kind: "TypeConversion",
1029
+ targetType: typeNode,
1030
+ expr: convExpr,
1031
+ });
1032
+ } else {
1033
+ args.push({ kind: "TypeExpr", type: typeNode });
1034
+ }
919
1035
  } else {
920
- args.push(this.parseExpr());
1036
+ const arg = this.parseExpr();
1037
+ if (this.match(T.ELLIPSIS)) arg._spread = true;
1038
+ args.push(arg);
921
1039
  }
922
- this.match(T.ELLIPSIS); // variadic spread, ignore for now
923
1040
  } while (this.match(T.COMMA));
924
1041
  }
925
1042
  this.expect(T.RPAREN);
@@ -1080,9 +1197,17 @@ export class Parser {
1080
1197
  return { kind: "Ident", name: t.value };
1081
1198
  }
1082
1199
 
1083
- // Slice/array/map composite literals: []int{1,2}, map[string]int{"a":1}
1200
+ // Slice/array/map composite literals or type conversions:
1201
+ // []int{1,2} → CompositeLit
1202
+ // []byte(s), []rune(s) → TypeConversion
1084
1203
  if (t.type === T.LBRACKET || t.type === T.MAP) {
1085
1204
  const typeExpr = this.parseType();
1205
+ if (this.check(T.LPAREN)) {
1206
+ this.advance();
1207
+ const expr = this.parseExpr();
1208
+ this.expect(T.RPAREN);
1209
+ return { kind: "TypeConversion", targetType: typeExpr, expr };
1210
+ }
1086
1211
  return this.parseCompositeLit(typeExpr);
1087
1212
  }
1088
1213
 
package/src/resolver.js CHANGED
@@ -82,7 +82,7 @@ export function resolveAll(imports, fromFile, parseDts) {
82
82
  const resolved = new Map();
83
83
 
84
84
  for (const imp of imports) {
85
- for (const path of imp.paths) {
85
+ for (const { path } of imp.imports) {
86
86
  if (path.startsWith("js:")) continue; // handled separately
87
87
  if (isLocalPath(path)) continue; // local .go packages — handled by compiler
88
88
 
@@ -34,6 +34,10 @@ export class TypeCheckError extends Error {
34
34
  }
35
35
  }
36
36
 
37
+ // ── Static operator sets (module-level for reuse) ────────────
38
+ const CMP_OPS = new Set(["==", "!=", "<", ">", "<=", ">="]);
39
+ const LOG_OPS = new Set(["&&", "||"]);
40
+
37
41
  // ── Built-in types ───────────────────────────────────────────
38
42
 
39
43
  const INT = { kind: "basic", name: "int" };
@@ -54,6 +58,18 @@ const BASIC_TYPES = {
54
58
  byte: INT,
55
59
  rune: INT,
56
60
  error: ERROR,
61
+ // Sized integer / float aliases — all map to int or float64 at runtime
62
+ uint: INT,
63
+ int8: INT,
64
+ int16: INT,
65
+ int32: INT,
66
+ int64: INT,
67
+ uint8: INT,
68
+ uint16: INT,
69
+ uint32: INT,
70
+ uint64: INT,
71
+ uintptr: INT,
72
+ float32: FLOAT64,
57
73
  };
58
74
 
59
75
  function isNumeric(t) {
@@ -188,7 +204,6 @@ export class TypeChecker {
188
204
  "Boolean",
189
205
  "Array",
190
206
  "Object",
191
- "JSON",
192
207
  "parseInt",
193
208
  "parseFloat",
194
209
  "isNaN",
@@ -219,6 +234,8 @@ export class TypeChecker {
219
234
  "FileReader",
220
235
  "ArrayBuffer",
221
236
  "Uint8Array",
237
+ "TextEncoder",
238
+ "TextDecoder",
222
239
  "WebGLRenderingContext",
223
240
  "WebGL2RenderingContext",
224
241
  "GPUDevice",
@@ -254,9 +271,13 @@ export class TypeChecker {
254
271
  this.globals.define("print", { kind: "builtin", name: "print" });
255
272
  this.globals.define("println", { kind: "builtin", name: "println" });
256
273
  this.globals.define("panic", { kind: "builtin", name: "panic" });
274
+ this.globals.define("recover", { kind: "builtin", name: "recover" });
257
275
  this.globals.define("new", { kind: "builtin", name: "new" });
258
276
  this.globals.define("copy", { kind: "builtin", name: "copy" });
259
277
  this.globals.define("error", { kind: "builtin", name: "error" });
278
+ this.globals.define("min", { kind: "builtin", name: "min" });
279
+ this.globals.define("max", { kind: "builtin", name: "max" });
280
+ this.globals.define("clear", { kind: "builtin", name: "clear" });
260
281
  }
261
282
 
262
283
  addDefinitions(types, values) {
@@ -412,6 +433,12 @@ export class TypeChecker {
412
433
 
413
434
  collectType(decl) {
414
435
  const underlying = this.resolveTypeNode(decl.type, this.globals);
436
+ if (decl.isAlias) {
437
+ // type A = B — transparent alias, A and B are identical types
438
+ this.types.set(decl.name, underlying);
439
+ this.globals.define(decl.name, underlying);
440
+ return;
441
+ }
415
442
  const named = { kind: "named", name: decl.name, underlying };
416
443
  if (underlying.kind === "struct") {
417
444
  underlying.name = decl.name;
@@ -685,9 +712,14 @@ export class TypeChecker {
685
712
  const inner = new Scope(scope);
686
713
  if (stmt.init) this.checkStmt(stmt.init, inner, returnType);
687
714
  if (stmt.cond) {
688
- const ct = this.checkExpr(stmt.cond, inner);
689
- if (!isBool(ct) && !isAny(ct))
690
- this.err("For condition must be bool", stmt);
715
+ // for range N { } — RangeExpr as condition is valid (Go 1.22 int range)
716
+ if (stmt.cond.kind !== "RangeExpr") {
717
+ const ct = this.checkExpr(stmt.cond, inner);
718
+ if (!isBool(ct) && !isAny(ct))
719
+ this.err("For condition must be bool", stmt);
720
+ } else {
721
+ this.checkExpr(stmt.cond.expr, inner);
722
+ }
691
723
  }
692
724
  if (stmt.post) this.checkStmt(stmt.post, inner, returnType);
693
725
  this._loopDepth++;
@@ -699,7 +731,7 @@ export class TypeChecker {
699
731
  case "SwitchStmt": {
700
732
  const inner = new Scope(scope);
701
733
  if (stmt.init) this.checkStmt(stmt.init, inner, returnType);
702
- const _tagType = stmt.tag ? this.checkExpr(stmt.tag, inner) : BOOL;
734
+ if (stmt.tag) this.checkExpr(stmt.tag, inner);
703
735
  this._switchDepth++;
704
736
  for (const c of stmt.cases) {
705
737
  const caseScope = new Scope(inner);
@@ -710,6 +742,26 @@ export class TypeChecker {
710
742
  break;
711
743
  }
712
744
 
745
+ case "TypeSwitchStmt": {
746
+ const inner = new Scope(scope);
747
+ this.checkExpr(stmt.expr, inner);
748
+ this._switchDepth++;
749
+ for (const c of stmt.cases) {
750
+ const caseScope = new Scope(inner);
751
+ if (stmt.assign) {
752
+ // Bind the variable to the single case type, or any for multi/default
753
+ const bindType =
754
+ c.types?.length === 1
755
+ ? this.resolveTypeNode(c.types[0], inner)
756
+ : ANY;
757
+ caseScope.define(stmt.assign, bindType);
758
+ }
759
+ for (const s of c.stmts) this.checkStmt(s, caseScope, returnType);
760
+ }
761
+ this._switchDepth--;
762
+ break;
763
+ }
764
+
713
765
  case "DeferStmt": {
714
766
  if (stmt.call.kind !== "CallExpr")
715
767
  this.err("defer requires a function call", stmt.call);
@@ -717,8 +769,13 @@ export class TypeChecker {
717
769
  break;
718
770
  }
719
771
 
772
+ case "LabeledStmt":
773
+ this.checkStmt(stmt.body, scope, returnType);
774
+ break;
775
+
720
776
  case "BranchStmt": {
721
777
  const kw = stmt.keyword;
778
+ if (stmt.label) break; // labeled branch — target checked by JS
722
779
  if (kw === "continue" && this._loopDepth === 0)
723
780
  this.err("continue statement outside for loop", stmt);
724
781
  else if (
@@ -879,13 +936,20 @@ export class TypeChecker {
879
936
 
880
937
  checkCall(expr, scope) {
881
938
  const fnType = this.checkExpr(expr.func, scope);
882
- const argTypes = expr.args.map((a) => this.checkExpr(a, scope));
883
939
 
884
- // Built-in handling
940
+ // Handle built-ins before evaluating args; some (e.g. new) take type names as
941
+ // arguments, which would otherwise produce false "Undefined" errors.
885
942
  if (fnType.kind === "builtin") {
943
+ const argTypes = expr.args.map((a, i) => {
944
+ // new(T) — first arg is a type name, not a value expression
945
+ if (fnType.name === "new" && i === 0) return ANY;
946
+ return this.checkExpr(a, scope);
947
+ });
886
948
  return this.checkBuiltin(fnType.name, expr, argTypes, scope);
887
949
  }
888
950
 
951
+ const argTypes = expr.args.map((a) => this.checkExpr(a, scope));
952
+
889
953
  if (isAny(fnType)) return ANY;
890
954
 
891
955
  if (fnType.kind !== "func") {
@@ -915,6 +979,8 @@ export class TypeChecker {
915
979
  fnType.variadic && i >= fnType.params.length - 1
916
980
  ? fnType.params.length - 1
917
981
  : i;
982
+ // Spread arg (f(slice...)) passes a whole slice into a variadic — skip per-element check
983
+ if (expr.args[i]?._spread) continue;
918
984
  this.assertAssignable(fnType.params[paramIdx], argTypes[i], expr.args[i]);
919
985
  }
920
986
 
@@ -950,8 +1016,15 @@ export class TypeChecker {
950
1016
  case "println":
951
1017
  case "panic":
952
1018
  return VOID;
1019
+ case "recover":
1020
+ return ANY;
953
1021
  case "error":
954
1022
  return ERROR;
1023
+ case "min":
1024
+ case "max":
1025
+ return argTypes[0] ?? ANY;
1026
+ case "clear":
1027
+ return VOID;
955
1028
  default:
956
1029
  return ANY;
957
1030
  }
@@ -993,7 +1066,7 @@ export class TypeChecker {
993
1066
  return t;
994
1067
  }
995
1068
 
996
- if (base?.kind === "slice") {
1069
+ if (base?.kind === "slice" || base?.kind === "array") {
997
1070
  for (const elem of expr.elems) {
998
1071
  if (elem.kind === "CompositeLit" && elem.typeExpr === null) {
999
1072
  const et = this.checkCompositeLit(elem, scope, base.elem);
@@ -1058,7 +1131,12 @@ export class TypeChecker {
1058
1131
  case "ArrayType":
1059
1132
  return {
1060
1133
  kind: "array",
1061
- size: node.size,
1134
+ // inferLen: true means [...]T — size is determined by composite literal
1135
+ size: node.inferLen
1136
+ ? null
1137
+ : node.size?.value !== undefined
1138
+ ? Number(node.size.value)
1139
+ : node.size,
1062
1140
  elem: this.resolveTypeNode(node.elem, scope),
1063
1141
  };
1064
1142
  case "MapType":
@@ -1116,6 +1194,24 @@ export class TypeChecker {
1116
1194
  : [VOID];
1117
1195
  methods.set(m.name, { kind: "func", params, returns });
1118
1196
  }
1197
+ // Flatten embedded interface methods
1198
+ if (node.embeds) {
1199
+ for (const embed of node.embeds) {
1200
+ const resolved = this.resolveTypeNode(embed, scope);
1201
+ const base =
1202
+ resolved?.kind === "named" ? resolved.underlying : resolved;
1203
+ if (base?.kind === "interface") {
1204
+ for (const [mName, mType] of base.methods) {
1205
+ if (!methods.has(mName)) methods.set(mName, mType);
1206
+ }
1207
+ } else if (!isAny(resolved)) {
1208
+ this.err(
1209
+ `cannot embed non-interface type ${typeStr(resolved)}`,
1210
+ embed,
1211
+ );
1212
+ }
1213
+ }
1214
+ }
1119
1215
  return { kind: "interface", methods };
1120
1216
  }
1121
1217
  case "TupleType": {
@@ -1189,10 +1285,8 @@ export class TypeChecker {
1189
1285
  }
1190
1286
 
1191
1287
  binaryResultType(op, lt, rt, node) {
1192
- const cmp = ["==", "!=", "<", ">", "<=", ">="];
1193
- const log = ["&&", "||"];
1194
- if (cmp.includes(op)) return BOOL;
1195
- if (log.includes(op)) return BOOL;
1288
+ if (CMP_OPS.has(op)) return BOOL;
1289
+ if (LOG_OPS.has(op)) return BOOL;
1196
1290
  if (isAny(lt) || isAny(rt)) return ANY;
1197
1291
  if (isNumeric(lt) && isNumeric(rt)) {
1198
1292
  // float64 is contagious