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
@@ -12,6 +12,7 @@ import {
12
12
  FLOAT64,
13
13
  is64BitInt,
14
14
  isAny,
15
+ isBool,
15
16
  isComplex,
16
17
  isComplex64,
17
18
  isComplexOrNumeric,
@@ -27,9 +28,11 @@ import {
27
28
  SIZED_INT_NAMES,
28
29
  STRING,
29
30
  typeStr,
31
+ UNTYPED_BOOL,
30
32
  UNTYPED_COMPLEX,
31
33
  UNTYPED_FLOAT,
32
34
  UNTYPED_INT,
35
+ UNTYPED_RUNE,
33
36
  } from "./types.js";
34
37
 
35
38
  // Unnamed type literal kinds for Go's "identical underlying types" assignability rule.
@@ -63,6 +66,23 @@ const UNTYPED_COMPAT = {
63
66
  complex128: new Set(["complex128", "complex64"]),
64
67
  };
65
68
 
69
+ // `&&` and `||` of two untyped bools stay untyped; a typed operand (including
70
+ // a defined bool type) gives its type, as in Go.
71
+ // Two typed operands must have the same type, and the result has it.
72
+ function logicalResultType(lt, rt) {
73
+ if (lt?.kind === "untyped" && rt?.kind === "untyped") return UNTYPED_BOOL;
74
+ if (rt?.kind === "untyped" && isBool(lt)) return lt;
75
+ if (lt?.kind === "untyped" && isBool(rt)) return rt;
76
+ if (isBool(lt) && isBool(rt) && typeStr(lt) === typeStr(rt)) return lt;
77
+ return BOOL;
78
+ }
79
+
80
+ // Two untyped numeric operands combine to the later of int, rune, float.
81
+ function untypedIntegerKind(isFloat, lt, rt) {
82
+ if (isFloat) return UNTYPED_FLOAT;
83
+ return lt.rune || rt.rune ? UNTYPED_RUNE : UNTYPED_INT;
84
+ }
85
+
66
86
  /** @type {ThisType<TypeChecker>} */
67
87
  export const assignabilityMethods = {
68
88
  // ── Binary result types ───────────────────────────────────────────
@@ -71,12 +91,26 @@ export const assignabilityMethods = {
71
91
  if (lt?.kind === "typeParam" || rt?.kind === "typeParam")
72
92
  return this._binaryResultTypeTypeParam(op, lt, rt);
73
93
  if (CMP_OPS.has(op)) return this._binaryResultTypeCmp(op, lt, rt, node);
74
- if (LOG_OPS.has(op)) return BOOL;
94
+ if (LOG_OPS.has(op)) {
95
+ this._checkLogicalOperands(lt, rt, node);
96
+ return logicalResultType(lt, rt);
97
+ }
75
98
  return this._binaryResultTypeNonComplex(op, lt, rt, node);
76
99
  },
77
100
 
101
+ // `B && bool` mixes two distinct bool types, which Go rejects.
102
+ _checkLogicalOperands(lt, rt, node) {
103
+ if (!node || lt?.kind === "untyped" || rt?.kind === "untyped") return;
104
+ if (!isBool(lt) || !isBool(rt) || typeStr(lt) === typeStr(rt)) return;
105
+ this.err(
106
+ `invalid operation: ${exprText(node)} (mismatched types ${typeStr(lt)} and ${typeStr(rt)})`,
107
+ node,
108
+ );
109
+ },
110
+
78
111
  _binaryResultTypeTypeParam(op, lt, rt) {
79
- if (CMP_OPS.has(op) || LOG_OPS.has(op)) return BOOL;
112
+ if (CMP_OPS.has(op)) return UNTYPED_BOOL;
113
+ if (LOG_OPS.has(op)) return BOOL;
80
114
  return lt?.kind === "typeParam" ? lt : rt;
81
115
  },
82
116
 
@@ -137,7 +171,8 @@ export const assignabilityMethods = {
137
171
  if (this._checkComplexCmpOp(op, lt, rt, node)) return ANY;
138
172
  this._checkIntegerCmpKinds(op, lt, rt, node);
139
173
  this._checkEqualityComparable(op, lt, rt, node);
140
- return BOOL;
174
+ // A comparison is an untyped bool: it also fits a defined bool type.
175
+ return UNTYPED_BOOL;
141
176
  },
142
177
 
143
178
  _binaryResultTypeComplex(op, lt, rt, node) {
@@ -163,7 +198,7 @@ export const assignabilityMethods = {
163
198
  const rFloat = rName === "float32" || rName === "float64";
164
199
  const isFloat = lFloat || rFloat;
165
200
  if (lt.kind === "untyped" && rt.kind === "untyped")
166
- return isFloat ? UNTYPED_FLOAT : UNTYPED_INT;
201
+ return untypedIntegerKind(isFloat, lt, rt);
167
202
  if (lt.kind === "untyped")
168
203
  return isFloat && rt.name !== "float64" && rt.name !== "float32"
169
204
  ? FLOAT64
@@ -229,6 +264,19 @@ export const assignabilityMethods = {
229
264
  }
230
265
  },
231
266
 
267
+ // An untyped numeric constant that takes type float32 is rounded to
268
+ // float32 in JS output (`var c float32 = 0.01`, `v == 0.01`).
269
+ _tagFloat32Const(target, source, node) {
270
+ if (!node || typeof node !== "object" || !node._const) return;
271
+ if (!isUntyped(source) || this._float32Base(target) !== "float32") return;
272
+ node._f32Const = true;
273
+ },
274
+
275
+ _float32Base(t) {
276
+ const r = t?.kind === "named" ? t.underlying : t;
277
+ return r?.kind === "basic" || r?.kind === "untyped" ? r.name : null;
278
+ },
279
+
232
280
  // `var m uint64 = (1 << k) - 1`: a non-constant shift whose left operand is
233
281
  // an untyped constant takes the type its context gives it, so codegen
234
282
  // computes it in that type (BigInt for 64-bit integers).
@@ -265,6 +313,17 @@ export const assignabilityMethods = {
265
313
  !isComplex(source)
266
314
  )
267
315
  valueNode._asComplex = true;
316
+ this._tagComplex64Const(target, source, valueNode);
317
+ },
318
+
319
+ // An untyped constant that takes type complex64 is rounded to float32 in
320
+ // JS output: a real constant through `_f32Const`, a complex constant
321
+ // (`1 + 0.01i`) through `_c64Const`.
322
+ _tagComplex64Const(target, source, node) {
323
+ if (!node || typeof node !== "object") return;
324
+ if (!isComplex64(target) || !isUntyped(source)) return;
325
+ if (isComplex(source)) node._c64Const = true;
326
+ else if (node._const) node._f32Const = true;
268
327
  },
269
328
 
270
329
  // `operand` is the expression whose value flows in, when `node` (the
@@ -275,6 +334,7 @@ export const assignabilityMethods = {
275
334
  target = this.resolveType(target);
276
335
  source = this.resolveType(source);
277
336
  this._tag64BitNode(target, node);
337
+ this._tagFloat32Const(target, source, operand ?? node);
278
338
  this._retypeUntypedShift(target, source, operand ?? node);
279
339
  this._tagComplexConst(target, source, operand ?? node);
280
340
  this._markIfaceBox(target, source, node);
@@ -442,11 +502,13 @@ export const assignabilityMethods = {
442
502
  },
443
503
 
444
504
  // An interface-typed operand needs an explicit type assertion before it can
445
- // flow into a concrete (non-interface) type.
505
+ // flow into a concrete (non-interface) type, or into an interface with
506
+ // methods (`var e error = b` with `b any`).
446
507
  _checkIfaceToConcrete(target, source, node, ctx, operand) {
447
508
  if (!source?._declared || !operand || typeof operand !== "object")
448
509
  return false;
449
- if (!this._isConcreteTarget(target)) return false;
510
+ if (!this._isConcreteTarget(target) && !this._isMethodIfaceTarget(target))
511
+ return false;
450
512
  this.err(
451
513
  `cannot use ${exprText(operand)} (${operandKind(operand, typeStr(source))}) as ${typeStr(target)} value in ${ctx}: need type assertion`,
452
514
  node,
@@ -454,6 +516,31 @@ export const assignabilityMethods = {
454
516
  return true;
455
517
  },
456
518
 
519
+ // `int(x)`, `(*T)(x)` and `I(x)` (I with methods) with a source-written
520
+ // `any` operand need an assertion.
521
+ _checkIfaceConversion(srcType, targetType, expr, operand) {
522
+ if (!srcType?._declared || !operand) return false;
523
+ if (
524
+ !this._isConcreteTarget(targetType) &&
525
+ !this._isMethodIfaceTarget(targetType)
526
+ )
527
+ return false;
528
+ this.err(
529
+ `cannot convert ${exprText(operand)} (${operandKind(operand, typeStr(srcType))}) to type ${typeStr(targetType)}: need type assertion`,
530
+ expr,
531
+ );
532
+ return true;
533
+ },
534
+
535
+ // An interface type (named or literal) with at least one method.
536
+ _isMethodIfaceTarget(target) {
537
+ if (!target || isAny(target)) return false;
538
+ const base = this.resolveType(
539
+ target.kind === "named" ? target.underlying : target,
540
+ );
541
+ return base?.kind === "interface" && base.methods?.size > 0;
542
+ },
543
+
457
544
  _isConcreteTarget(target) {
458
545
  if (!target || target.kind === "typeParam" || isAny(target)) return false;
459
546
  if (isNil(target) || isUntyped(target)) return false;
@@ -584,12 +671,7 @@ export const assignabilityMethods = {
584
671
  _isNumericCoercible(target, source) {
585
672
  if (target.kind !== "basic" || source.kind !== "basic") return false;
586
673
  const pair = `${target.name}:${source.name}`;
587
- return (
588
- pair === "float64:int" ||
589
- // rune was historically int in GoFront; keep rune/int interchangeable.
590
- pair === "int:int32" ||
591
- pair === "int32:int"
592
- );
674
+ return pair === "float64:int";
593
675
  },
594
676
 
595
677
  // The lenient numeric pairs: accepted, except that a generic call result
@@ -15,7 +15,28 @@ import {
15
15
  INT_RANGES,
16
16
  intConst,
17
17
  } from "./const-fold.js";
18
- import { defaultType, isAny, typeStr, VOID } from "./types.js";
18
+ import {
19
+ compositeLitMaxIndex,
20
+ defaultType,
21
+ isAny,
22
+ isComplex,
23
+ isComplex64,
24
+ isUntyped,
25
+ typeStr,
26
+ VOID,
27
+ } from "./types.js";
28
+
29
+ // True when evaluating `node` runs a call or a channel receive (conversions
30
+ // excluded): such an operand makes `len` of an array a non-constant.
31
+ function hasCallOrReceive(node) {
32
+ if (!node || typeof node !== "object") return false;
33
+ if (Array.isArray(node)) return node.some(hasCallOrReceive);
34
+ if (node.kind === "CallExpr" && !node._isTypeConversion) return true;
35
+ if (node.kind === "UnaryExpr" && node.op === "<-") return true;
36
+ return Object.keys(node).some(
37
+ (key) => key.charCodeAt(0) !== 95 && hasCallOrReceive(node[key]),
38
+ );
39
+ }
19
40
 
20
41
  // Basic type names usable as conversion functions (`int32(x)`).
21
42
  const BASIC_CONVERSIONS = new Set([
@@ -38,6 +59,17 @@ const CONST_KINDS = new Set([
38
59
  "SelectorExpr",
39
60
  ]);
40
61
 
62
+ // Describes a constant that is not integer-valued (`float64 constant`), or
63
+ // null for an integer, an untyped float with an integer value, or a rune.
64
+ function nonIntegerConstKind(v) {
65
+ if (v.k === "INT") return null;
66
+ if (v.k === "FLOAT") {
67
+ if (v.t) return `${v.t} constant`;
68
+ return constIntValue(v) === null ? "untyped float constant" : null;
69
+ }
70
+ return `${v.k.toLowerCase()} constant`;
71
+ }
72
+
41
73
  const NEGATIVE_SHIFT_ERROR = "invalid shift count: negative shift count";
42
74
 
43
75
  /** @type {ThisType<import('./index.js').TypeChecker>} */
@@ -49,6 +81,23 @@ export const constantCheckMethods = {
49
81
  return raw && !raw.overflow && !raw.error ? raw : null;
50
82
  },
51
83
 
84
+ // What makes `expr` a constant that cannot be an array length, index or
85
+ // size (`untyped bool constant`, `untyped nil`), or null when it is an
86
+ // integer constant or not a constant at all.
87
+ _nonIntegerConst(expr, scope = null) {
88
+ let e = expr;
89
+ while (e?.kind === "ParenExpr") e = e.expr;
90
+ if (e?.kind === "ImagLit") return "untyped complex constant";
91
+ if (e?.kind === "BasicLit" && e.litKind === "NIL") return "untyped nil";
92
+ let v = null;
93
+ try {
94
+ v = this._constValue(expr, scope);
95
+ } catch {
96
+ // not a constant the evaluator understands
97
+ }
98
+ return v ? nonIntegerConstKind(v) : null;
99
+ },
100
+
52
101
  // Integer value of an integer (or untyped integral float) constant as a
53
102
  // Number, or null.
54
103
  _constIntValue(expr, scope = null) {
@@ -58,6 +107,23 @@ export const constantCheckMethods = {
58
107
  return i === null ? null : Number(i);
59
108
  },
60
109
 
110
+ // A named untyped complex constant has no folded value (`_const`): its
111
+ // initializer goes on the identifier as `_complexInit`, so the backends
112
+ // can fold it where it takes type complex64.
113
+ // A named `complex64` constant is tagged `_c64Const` as well, so a use
114
+ // takes float32 components as in Go.
115
+ _noteComplexConstInit(expr, scope) {
116
+ if (expr.kind !== "Ident") return;
117
+ const typed64 = !isUntyped(expr._type) && isComplex64(expr._type);
118
+ if (!typed64 && !(isUntyped(expr._type) && isComplex(expr._type))) return;
119
+ const owner = scope.lookupScope(expr.name);
120
+ if (!owner?._consts.has(expr.name)) return;
121
+ const init = owner.lookupConstExpr(expr.name);
122
+ if (!init) return;
123
+ expr._complexInit = init;
124
+ if (typed64) expr._c64Const = true;
125
+ },
126
+
61
127
  // Raw evaluation result, memoized on checked nodes: a value, a value with
62
128
  // `overflow`, `{ error }`, or null.
63
129
  _constRaw(expr, scope) {
@@ -83,6 +149,7 @@ export const constantCheckMethods = {
83
149
  // overflow / division by zero once per node.
84
150
  _noteConst(expr, scope) {
85
151
  if (!CONST_KINDS.has(expr.kind)) return;
152
+ this._noteComplexConstInit(expr, scope ?? this.globals);
86
153
  const raw = this._constRaw(expr, scope);
87
154
  expr._const = raw && !raw.overflow && !raw.error ? raw : null;
88
155
  if (!raw || expr._constReported) return;
@@ -128,7 +195,13 @@ export const constantCheckMethods = {
128
195
 
129
196
  _evalConstIdent(expr, scope) {
130
197
  const owner = scope.lookupScope(expr.name);
131
- if (!owner?._consts.has(expr.name)) return null;
198
+ // A constant declared after the type that uses it (`type B [k]int`) has
199
+ // no symbol yet, but its initializer is registered.
200
+ if (!owner) {
201
+ const early = scope.lookupConstExpr(expr.name);
202
+ return early ? this._constValue(early, scope) : null;
203
+ }
204
+ if (!owner._consts.has(expr.name)) return null;
132
205
  const init = owner.lookupConstExpr(expr.name);
133
206
  const v = init
134
207
  ? this._constValue(init, owner)
@@ -159,6 +232,8 @@ export const constantCheckMethods = {
159
232
  const fn = expr.func;
160
233
  if (fn?.kind !== "Ident" || scope.lookup(fn.name)?.kind !== "builtin")
161
234
  return null;
235
+ const arrayLen = this._constArrayLen(fn.name, expr.args, scope);
236
+ if (arrayLen !== null) return intConst(BigInt(arrayLen));
162
237
  const vals = (expr.args ?? []).map((a) => this._constValue(a, scope));
163
238
  if (vals.length === 0 || vals.some((v) => !v)) return null;
164
239
  if (fn.name === "min" || fn.name === "max")
@@ -168,6 +243,53 @@ export const constantCheckMethods = {
168
243
  return isLen ? intConst(BigInt(vals[0].v.length)) : null;
169
244
  },
170
245
 
246
+ // A package variable named in an array length may be declared after the
247
+ // type (or variable) that uses it, so it is not defined yet: its declared
248
+ // type, or the type of its composite literal, is resolved from the spec.
249
+ _earlyPackageVarType(arg, scope) {
250
+ let e = arg;
251
+ while (e?.kind === "ParenExpr") e = e.expr;
252
+ if (e?.kind !== "Ident") return null;
253
+ // An untyped package variable is `any` until its initializer is typed.
254
+ const known = scope.lookup(e.name);
255
+ if (known && !isAny(known)) return null;
256
+ const entry = this._pkgVarSpecs?.get(e.name);
257
+ if (!entry || entry.busy) return null;
258
+ const { spec, index } = entry;
259
+ const lit = spec.value?.[index];
260
+ const typeNode =
261
+ spec.type ?? (lit?.kind === "CompositeLit" ? lit.typeExpr : null);
262
+ if (!typeNode) return null;
263
+ entry.busy = true;
264
+ try {
265
+ const t = this.resolveTypeNode(typeNode, this.globals);
266
+ // `[...]T{...}`: the length is the literal's highest index + 1.
267
+ return t?.kind === "array" && t.size === null && lit?.elems
268
+ ? { ...t, size: compositeLitMaxIndex(lit.elems) + 1 }
269
+ : t;
270
+ } finally {
271
+ entry.busy = false;
272
+ }
273
+ },
274
+
275
+ // `len(a)` and `cap(a)` of an array (or pointer to an array) are constants
276
+ // unless `a` contains a function call or channel receive; `a` itself is not
277
+ // evaluated. Returns the length, or null.
278
+ _constArrayLen(name, args, scope) {
279
+ if ((name !== "len" && name !== "cap") || args?.length !== 1) return null;
280
+ const arg = args[0];
281
+ if (hasCallOrReceive(arg)) return null;
282
+ let t = this._earlyPackageVarType(arg, scope);
283
+ if (!t) {
284
+ if (arg._type === undefined) this.checkExpr(arg, scope);
285
+ t = arg._type;
286
+ }
287
+ t = this.resolveType(t);
288
+ if (t?.kind === "pointer") t = this.resolveType(t.base);
289
+ if (t?.kind === "named") t = this.resolveType(t.underlying);
290
+ return t?.kind === "array" && Number.isInteger(t.size) ? t.size : null;
291
+ },
292
+
171
293
  // Target type of a conversion call (`MyInt(x)`, `int32(x)`), or null.
172
294
  _constConversionTarget(expr, scope) {
173
295
  if (expr.args?.length !== 1) return null;