gofront 1.9.0 → 1.9.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.
Files changed (43) hide show
  1. package/README.md +1 -1
  2. package/package.json +3 -3
  3. package/src/backend/js/expressions.js +329 -58
  4. package/src/backend/js/index.js +90 -10
  5. package/src/backend/js/runtime.js +51 -9
  6. package/src/backend/js/statements.js +193 -84
  7. package/src/backend/js/type-info.js +123 -8
  8. package/src/backend/shared/fmt-types.js +30 -1
  9. package/src/backend/wasm/emit-builtins.js +250 -9
  10. package/src/backend/wasm/emit-exprs.js +63 -22
  11. package/src/backend/wasm/emit-shared.js +4 -1
  12. package/src/backend/wasm/emit-stmts.js +239 -205
  13. package/src/backend/wasm/emit.js +73 -26
  14. package/src/backend/wasm/globals.js +1 -0
  15. package/src/backend/wasm/index.js +8 -0
  16. package/src/backend/wasm/module-emitter.js +211 -57
  17. package/src/backend/wasm/monomorph.js +36 -2
  18. package/src/backend/wasm/natives.js +4 -0
  19. package/src/backend/wasm/types.js +32 -3
  20. package/src/backend/wasm/variadic.js +6 -1
  21. package/src/cli/minifier.js +20 -6
  22. package/src/compiler.js +6 -1
  23. package/src/lexer/lexer.js +1 -0
  24. package/src/link/stdlib.js +1 -0
  25. package/src/link/type-names.js +72 -16
  26. package/src/lower/embedding.js +43 -15
  27. package/src/lower/fmt-formatters.js +38 -14
  28. package/src/lower/functions.js +223 -7
  29. package/src/lower/gen-types.js +21 -2
  30. package/src/lower/index.js +7 -1
  31. package/src/lower/json-codecs.js +114 -6
  32. package/src/parser/declarations.js +4 -2
  33. package/src/parser/expressions.js +25 -41
  34. package/src/parser/statements.js +107 -0
  35. package/src/parser/types.js +11 -0
  36. package/src/typechecker/assignability.js +94 -12
  37. package/src/typechecker/constants.js +124 -2
  38. package/src/typechecker/expressions.js +171 -17
  39. package/src/typechecker/index.js +158 -29
  40. package/src/typechecker/operand-text.js +1 -0
  41. package/src/typechecker/resolve.js +308 -39
  42. package/src/typechecker/statements.js +171 -21
  43. package/src/typechecker/types.js +84 -2
@@ -24,20 +24,27 @@ import {
24
24
  markRedundantNilChecks,
25
25
  normalizeDefers,
26
26
  scanAddressTaken,
27
+ splitForwardedTuples,
27
28
  takesElementAddress,
28
29
  } from "../../lower/index.js";
29
30
  import { orderPackageInits } from "../../typechecker/init-order.js";
30
31
  import {
31
32
  BROWSER_GLOBALS,
32
33
  isComplex,
34
+ isMethodPromotedOverField,
33
35
  isNumeric,
34
36
  } from "../../typechecker/types.js";
37
+ import { goTypeName } from "../shared/fmt-types.js";
35
38
  import {
36
39
  unicodeIsJs,
37
40
  unicodeToLowerJs,
38
41
  unicodeToUpperJs,
39
42
  } from "../shared/host-unicode.js";
40
- import { comparesToNil, expressionGenMethods } from "./expressions.js";
43
+ import {
44
+ comparesToNil,
45
+ derefsReceiverFirst,
46
+ expressionGenMethods,
47
+ } from "./expressions.js";
41
48
  import {
42
49
  HELPER_APPEND,
43
50
  HELPER_BOUNDS,
@@ -367,7 +374,9 @@ export class CodeGen {
367
374
  this._precisionWarned = new Set();
368
375
  // Defer lowering (argument/receiver hoisting, recover-target marking) is
369
376
  // idempotent and owned by the backends: the wasm path runs it via
370
- // `lower()`, the JS path here.
377
+ // `lower()`, the JS path here. So is splitting multi-value calls
378
+ // forwarded into interfaces into temps.
379
+ splitForwardedTuples(program);
371
380
  normalizeDefers(program);
372
381
  // Strict mode: index checks the loop shape proves unnecessary are elided.
373
382
  if (this.strict) {
@@ -584,13 +593,35 @@ export class CodeGen {
584
593
  const name = d.recvType.name;
585
594
  if (!methods.has(name)) methods.set(name, []);
586
595
  methods.get(name).push(d);
587
- if (d.recvPointer && comparesToNil(d.body, d.recvName))
588
- this.nilSafeMethods.add(`${name}.${d.name}`);
589
596
  }
590
597
  }
598
+ this._markNilSafeMethods(methods);
591
599
  return methods;
592
600
  }
593
601
 
602
+ // Pointer-receiver methods that can run with a nil receiver: they compare
603
+ // it with nil, or do not read through it before anything could skip the
604
+ // read (a method that does panics on nil either way, so it keeps the
605
+ // faster direct call). Calls to these go through the prototype (see
606
+ // _nilSafeMethodClass).
607
+ _markNilSafeMethods(methods) {
608
+ for (const [name, decls] of methods) {
609
+ const safe = new Set();
610
+ const ptr = decls.filter((d) => d.recvPointer);
611
+ for (const d of ptr) safe.add(d.name);
612
+ for (let changed = true; changed; ) {
613
+ changed = false;
614
+ for (const d of ptr) {
615
+ if (!safe.has(d.name) || comparesToNil(d.body, d.recvName)) continue;
616
+ if (!derefsReceiverFirst(d.body?.stmts, d.recvName, safe)) continue;
617
+ safe.delete(d.name);
618
+ changed = true;
619
+ }
620
+ }
621
+ for (const m of safe) this.nilSafeMethods.add(`${name}.${m}`);
622
+ }
623
+ }
624
+
594
625
  _collectNamedWrappers(program, methods) {
595
626
  for (const d of program.decls) {
596
627
  if (
@@ -703,6 +734,30 @@ export class CodeGen {
703
734
  }
704
735
 
705
736
  genTypeDeclWithMethods(decl, methodDecls) {
737
+ this._genTypeDeclBody(decl, methodDecls);
738
+ this._genUnhashableMark(decl.name);
739
+ }
740
+
741
+ // Marks a defined type Go cannot hash so `__ik` can name it in the panic
742
+ // of an interface-keyed map insert: a struct class carries `__uh`, a
743
+ // defined slice or map type registers its interface tag.
744
+ _genUnhashableMark(name) {
745
+ const t = this.checker?.types.get(name);
746
+ if (t?.kind !== "named" || t.typeParams?.length || t.typeArgs?.length)
747
+ return;
748
+ if (!this._isUnhashableType(t)) return;
749
+ const goName = JSON.stringify(goTypeName(t));
750
+ if (this.structNames.has(name)) {
751
+ this.line(
752
+ `Object.defineProperty(${name}, "__uh", { value: ${goName} });`,
753
+ );
754
+ } else if (this._isNamedComposite(t)) {
755
+ this.useHelper("structKey");
756
+ this.line(`__uht.set(${JSON.stringify(name)}, ${goName});`);
757
+ }
758
+ }
759
+
760
+ _genTypeDeclBody(decl, methodDecls) {
706
761
  const definedStruct =
707
762
  decl.type.kind === "StructType" ? null : this._definedStructAst(decl);
708
763
  if (decl.type.kind === "StructType") {
@@ -816,8 +871,11 @@ export class CodeGen {
816
871
  if (this.checker) {
817
872
  const resolved = this.checker.types.get(name)?.underlying;
818
873
  if (resolved?.kind === "struct") {
819
- for (const [fName, fType] of resolved.fields.entries())
874
+ for (const [fName, fType] of resolved.fields.entries()) {
875
+ // A method promoted shallower shadows the deeper field.
876
+ if (isMethodPromotedOverField(resolved, fName)) continue;
820
877
  fields.push({ name: fName, zero: this.zeroValueForType(fType) });
878
+ }
821
879
  }
822
880
  } else {
823
881
  for (const f of structTypeAst.fields) {
@@ -837,8 +895,10 @@ export class CodeGen {
837
895
  const fields = [];
838
896
  // Cached per type name, so never bound to the current generic body.
839
897
  this._withTypeParamZeros(null, () => {
840
- for (const [name, type] of resolved.fields.entries())
898
+ for (const [name, type] of resolved.fields.entries()) {
899
+ if (isMethodPromotedOverField(resolved, name)) continue;
841
900
  fields.push({ name, zero: this.zeroValueForType(type) });
901
+ }
842
902
  });
843
903
  this.structFields.set(typeName, fields);
844
904
  return fields;
@@ -1038,7 +1098,17 @@ export class CodeGen {
1038
1098
  // A promoted method runs on the embedded value (`this.In`), so it and the
1039
1099
  // outer struct's promoted-field accesses see the same storage. Embedded
1040
1100
  // types that are not compiled Go structs keep the borrowed-`this` form.
1041
- _embedStub(method, embedName, valueEmbed = false, fieldName = null) {
1101
+ _embedStub(
1102
+ method,
1103
+ embedName,
1104
+ valueEmbed = false,
1105
+ fieldName = null,
1106
+ nilPtrRecv = false,
1107
+ ifaceEmbed = false,
1108
+ ) {
1109
+ // An embedded interface forwards to the interface value in its field.
1110
+ if (ifaceEmbed)
1111
+ return `${method}(...__a) { return this.${fieldName}.${method}(...__a); }`;
1042
1112
  // The field is named after the type without its package suffix
1043
1113
  // (`Mutex$$sync` is the field `Mutex`), or after the alias it embeds.
1044
1114
  const field = fieldName || embedName.replace(/\$\$.*$/, "");
@@ -1046,12 +1116,20 @@ export class CodeGen {
1046
1116
  // the value as the receiver.
1047
1117
  if (valueEmbed)
1048
1118
  return `${method}(...__a) { return ${embedName}.$${method}(this.${field}, ...__a); }`;
1049
- const call = this.structNames.has(embedName)
1050
- ? `this.${field}.${method}(...__a)`
1051
- : `${embedName}.prototype.${method}.call(this, ...__a)`;
1119
+ const call = this._embedStubCall(method, embedName, field, nilPtrRecv);
1052
1120
  return `${method}(...__a) { return ${call}; }`;
1053
1121
  }
1054
1122
 
1123
+ _embedStubCall(method, embedName, field, nilPtrRecv) {
1124
+ if (!this.structNames.has(embedName))
1125
+ return `${embedName}.prototype.${method}.call(this, ...__a)`;
1126
+ // A nil embedded pointer still reaches a pointer-receiver method, as in
1127
+ // Go: call it through the prototype instead of reading it off null.
1128
+ return nilPtrRecv
1129
+ ? `${embedName}.prototype.${method}.call(this.${field}, ...__a)`
1130
+ : `this.${field}.${method}(...__a)`;
1131
+ }
1132
+
1055
1133
  _genEmbeddedMethodStubs(name, methodDecls) {
1056
1134
  const stubs = computeEmbeddedStubs(name, methodDecls, this.checker);
1057
1135
  for (const stub of stubs) {
@@ -1062,6 +1140,8 @@ export class CodeGen {
1062
1140
  stub.embedName,
1063
1141
  stub.valueEmbed,
1064
1142
  stub.fieldName,
1143
+ stub.nilPtrRecv,
1144
+ stub.ifaceEmbed,
1065
1145
  ),
1066
1146
  );
1067
1147
  }
@@ -311,11 +311,21 @@ export const HELPER_VCLONE = `var __vclone = __vclone || function __vclone(z, x)
311
311
  if (x === null || typeof x !== "object") return x;
312
312
  const zero = z?.();
313
313
  if (zero === null || zero === undefined || typeof zero !== "object") return x;
314
+ // A copy keeps the hidden tags (__t, __map, ...) that name the value's type
315
+ // in an interface: the generic code boxes the value first and copies after.
316
+ const keep = (v, c) => {
317
+ for (const k of Object.getOwnPropertyNames(v)) {
318
+ const d = Object.getOwnPropertyDescriptor(v, k);
319
+ if (!d.enumerable && k !== "length") Object.defineProperty(c, k, d);
320
+ }
321
+ return c;
322
+ };
314
323
  const cp = (v) => {
315
324
  if (v === null || typeof v !== "object") return v;
316
- if (Array.isArray(v)) return v.map(cp);
317
- if (typeof v.__clone === "function") return v.__clone();
318
- return Object.assign(Object.create(Object.getPrototypeOf(v)), v);
325
+ if (v.__nilm === true) return v;
326
+ if (Array.isArray(v)) return keep(v, v.map(cp));
327
+ if (typeof v.__clone === "function") return keep(v, v.__clone());
328
+ return keep(v, Object.assign(Object.create(Object.getPrototypeOf(v)), v));
319
329
  };
320
330
  return cp(x);
321
331
  };`;
@@ -439,25 +449,41 @@ var __ifps = __ifps || function(x) {
439
449
  }
440
450
  return x;
441
451
  };
442
- var __ity = __ity || function(x, t, ptr) {
452
+ var __tfn = __tfn || function(f, t) {
453
+ // A defined func type tags a fresh wrapper, so the shared func keeps its own type.
454
+ if (typeof f !== "function" || f.__t === t) return f;
455
+ const w = function(...a) { return f.apply(this, a); };
456
+ Object.defineProperty(w, "__t", { value: t, configurable: true, writable: true });
457
+ return w;
458
+ };
459
+ var __tnm = __tnm || function(f) {
460
+ // The type name of a type argument's info factory, spelled as a static tag.
461
+ const id = f?.nm ?? f?.id;
462
+ return typeof id === "string" ? id.replace(/ void\\b/g, "").replace(/\\bbyte\\b/g, "uint8").replace(/\\brune\\b/g, "int32").replace(/\\bany\\b/g, "interface {}") : undefined;
463
+ };
464
+ var __ity = __ity || function(x, t, ptr, sl) {
465
+ if (t === undefined) return x;
443
466
  // The frozen nil map cannot carry a tag: a copy of it keeps its map type.
444
467
  if (x !== null && x !== undefined && x.__nilm === true && x.__t === undefined)
445
468
  return Object.freeze(Object.defineProperties({}, { __map: { value: true }, __nilm: { value: true }, __t: { value: t } }));
446
469
  if (x === null || x === undefined) {
447
470
  // A nil slice keeps its type as an empty tagged array (not a nil interface).
448
- if (!ptr && t.charCodeAt(0) !== 91) return x;
471
+ if (!ptr && !sl && t.charCodeAt(0) !== 91) return x;
449
472
  const n = ptr ? {} : [];
450
473
  if (!ptr) Object.defineProperty(n, "__nils", { value: true });
451
474
  Object.defineProperty(n, "__nilp", { value: true });
452
475
  Object.defineProperty(n, "__t", { value: t });
453
476
  return n;
454
477
  }
478
+ // A func already tagged with another type keeps it: tag a fresh wrapper.
479
+ if (typeof x === "function" && x.__t !== undefined && x.__t !== t) x = __tfn(x, undefined);
455
480
  if ((typeof x === "object" || typeof x === "function") && Object.isExtensible(x))
456
481
  Object.defineProperty(x, "__t", { value: t, configurable: true, writable: true });
457
482
  return x;
458
483
  };
459
484
  var __isty = __isty || function(v, t, k, c, n) {
460
485
  if (v === null || v === undefined || (typeof v !== "object" && typeof v !== "function")) return false;
486
+ if (k === "namedfunc") return typeof v === "function" && v.__t === t;
461
487
  if (k === "func") return typeof v === "function" && (v.__t === undefined || t === "" || v.__t === t);
462
488
  if (v.__t !== undefined && t !== "") return v.__t === t;
463
489
  if (v.__nilp === true) return false;
@@ -605,9 +631,9 @@ var __zu64 = __zu64 || Object.assign(() => 0n, { k: "uint64" });
605
631
  var __tbox = __tbox || function(f, x) {
606
632
  return f && f.b ? f.b(x) : x;
607
633
  };
608
- var __ti = __ti || function(f, k, id, is, u, b, fb) {
634
+ var __ti = __ti || function(f, k, id, is, u, b, fb, nm) {
609
635
  const g = () => f();
610
- g.k = k; g.id = id; g.is = is; g.u = u; g.b = b; g.fb = fb;
636
+ g.k = k; g.id = id; g.is = is; g.u = u; g.b = b; g.fb = fb; g.nm = nm;
611
637
  return g;
612
638
  };
613
639
  var __tar = __tar || function(f, op, a, b) {
@@ -707,6 +733,8 @@ var __isnm = __isnm || function(m) {
707
733
  // through `toString` to an injective encoding, and the key value is kept in
708
734
  // a table so ranging and fmt can recover it.
709
735
  export const HELPER_STRUCT_KEY = `var __kt = __kt || new Map();
736
+ // Tags of defined slice and map types Go cannot hash, with their Go names.
737
+ var __uht = __uht || new Map();
710
738
  var __kcl = __kcl || function(o) {
711
739
  if (o === null || typeof o !== "object") return o;
712
740
  if (typeof o.__clone === "function") return o.__clone();
@@ -739,10 +767,11 @@ var __pk = __pk || function(p) {
739
767
  };
740
768
  var __ik = __ik || function(k, tag) {
741
769
  const t = typeof k;
770
+ // A nil slice, map or func of a static type Go cannot hash panics too.
771
+ if (tag !== undefined && tag.charCodeAt(0) === 33) throw new Error("runtime error: hash of unhashable type " + tag.slice(1));
742
772
  if (k === null || k === undefined) return "\\u0001nil";
743
773
  if (tag !== undefined) {
744
774
  if (tag === "*") return __pk(k);
745
- if (tag.charCodeAt(0) === 33) throw new Error("runtime error: hash of unhashable type " + tag.slice(1));
746
775
  if (t === "number" || t === "bigint") {
747
776
  const s = "\\u0001" + tag + ":" + String(k);
748
777
  if (!__kt.has(s)) __kt.set(s, k);
@@ -752,7 +781,13 @@ var __ik = __ik || function(k, tag) {
752
781
  if (t === "number" || t === "boolean" || t === "bigint") return "\\u0001" + t + ":" + String(k);
753
782
  // A slice, map or func in an \`any\` carries its type tag: Go cannot hash it.
754
783
  if ((t === "object" || t === "function") && typeof k.__t === "string" && /^(\\[\\]|map\\[|func\\()/.test(k.__t))
755
- throw new Error("runtime error: hash of unhashable type " + k.__t.replace(/ void$/, ""));
784
+ throw new Error("runtime error: hash of unhashable type " + k.__t);
785
+ // A struct class or defined slice/map type that Go cannot hash is marked
786
+ // once at its declaration, so this is one lookup per insert.
787
+ if (t === "object") {
788
+ const uh = k.constructor?.__uh ?? __uht.get(k.__t);
789
+ if (uh !== undefined) throw new Error("runtime error: hash of unhashable type " + uh);
790
+ }
756
791
  if (k !== null && t === "object" && typeof k.__gfUnbox === "function" && !k.constructor.name.endsWith("$Ptr")) {
757
792
  const v = k.__gfUnbox(), vt = typeof v;
758
793
  if (vt === "string" || vt === "number" || vt === "boolean" || vt === "bigint") {
@@ -834,6 +869,13 @@ export const HELPER_IFEQ = `var __ifeq = __ifeq || function(a, b) {
834
869
  if (typeof a === "object" && typeof b === "object" && a.__v === true && b.__v === true && a.constructor === b.constructor) {
835
870
  return __equal(a, b);
836
871
  }
872
+ // Array values of the same type compare element by element; slices and
873
+ // defined slice types (registered in __uht) are not comparable.
874
+ if (typeof a === "object" && typeof b === "object" && typeof a.__t === "string" && a.__t === b.__t &&
875
+ (Array.isArray(a) || ArrayBuffer.isView(a)) && (Array.isArray(b) || ArrayBuffer.isView(b)) &&
876
+ !a.__t.startsWith("[]") && (typeof __uht === "undefined" || !__uht.has(a.__t))) {
877
+ return __equal(a, b);
878
+ }
837
879
  return false;
838
880
  };`;
839
881