gofront 1.8.0 → 1.9.1

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 (178) hide show
  1. package/README.md +3 -3
  2. package/package.json +6 -5
  3. package/src/backend/js/expressions.js +1157 -408
  4. package/src/backend/js/index.js +327 -153
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +559 -2552
  7. package/src/backend/js/statements.js +670 -158
  8. package/src/backend/js/stdlib/index.js +45 -93
  9. package/src/backend/js/templ.js +32 -45
  10. package/src/backend/js/type-info.js +298 -19
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +91 -170
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +490 -965
  15. package/src/backend/wasm/emit-builtins.js +884 -256
  16. package/src/backend/wasm/emit-exprs.js +185 -77
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-shared.js +4 -1
  19. package/src/backend/wasm/emit-stmts.js +524 -240
  20. package/src/backend/wasm/emit.js +176 -38
  21. package/src/backend/wasm/globals.js +48 -8
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +44 -530
  24. package/src/backend/wasm/method-expr.js +12 -1
  25. package/src/backend/wasm/module-emitter.js +299 -770
  26. package/src/backend/wasm/monomorph.js +139 -8
  27. package/src/backend/wasm/natives.js +600 -0
  28. package/src/backend/wasm/prescan.js +5 -153
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -32
  31. package/src/backend/wasm/types.js +97 -20
  32. package/src/backend/wasm/variadic.js +9 -4
  33. package/src/cli/cli-core.js +5 -1
  34. package/src/cli/test-runner.js +115 -342
  35. package/src/compiler.js +113 -130
  36. package/src/lexer/lexer.js +106 -11
  37. package/src/lexer/templ-lexer.js +61 -18
  38. package/src/lexer/tokens.js +1 -0
  39. package/src/link/stdlib.js +355 -132
  40. package/src/link/type-names.js +204 -0
  41. package/src/link/wasm.js +30 -6
  42. package/src/lower/bounds.js +153 -0
  43. package/src/lower/captures.js +92 -33
  44. package/src/lower/embedding.js +62 -5
  45. package/src/lower/errors-as.js +196 -0
  46. package/src/lower/fmt-formatters.js +929 -0
  47. package/src/lower/functions.js +225 -7
  48. package/src/lower/gen-types.js +293 -0
  49. package/src/lower/index.js +14 -2
  50. package/src/lower/json-codecs.js +334 -245
  51. package/src/lower/ownership.js +25 -7
  52. package/src/lower/reachability.js +69 -51
  53. package/src/lower/walk.js +12 -0
  54. package/src/parser/declarations.js +30 -5
  55. package/src/parser/expressions.js +111 -57
  56. package/src/parser/statements.js +108 -0
  57. package/src/parser/templ.js +44 -5
  58. package/src/parser/types.js +12 -0
  59. package/src/resolver.js +1 -2
  60. package/src/typechecker/assignability.js +214 -19
  61. package/src/typechecker/const-fold.js +70 -27
  62. package/src/typechecker/constants.js +156 -8
  63. package/src/typechecker/expressions.js +490 -108
  64. package/src/typechecker/index.js +346 -42
  65. package/src/typechecker/init-order.js +136 -40
  66. package/src/typechecker/operand-text.js +41 -0
  67. package/src/typechecker/resolve.js +490 -65
  68. package/src/typechecker/statements.js +288 -27
  69. package/src/typechecker/stdlib/excluded.js +9 -5
  70. package/src/typechecker/stdlib/index.js +25 -9
  71. package/src/typechecker/termination.js +10 -1
  72. package/src/typechecker/types.js +287 -575
  73. package/stdlib/bytes/buffer.go +479 -0
  74. package/stdlib/bytes/bytes.go +1010 -0
  75. package/stdlib/bytes/reader.go +161 -0
  76. package/stdlib/context/context.go +115 -0
  77. package/stdlib/crypto/rand/rand.go +34 -0
  78. package/stdlib/crypto/sha256/sha256.go +249 -0
  79. package/stdlib/encoding/base64/base64.go +331 -0
  80. package/stdlib/encoding/binary/binary.go +332 -0
  81. package/stdlib/encoding/hex/hex.go +140 -0
  82. package/stdlib/encoding/json/json.go +62 -0
  83. package/stdlib/errors/errors.go +174 -0
  84. package/stdlib/fmt/errors.go +110 -0
  85. package/stdlib/fmt/format.go +732 -0
  86. package/stdlib/fmt/print.go +1232 -0
  87. package/stdlib/gom/gom.go +293 -0
  88. package/stdlib/html/html.go +251 -0
  89. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  90. package/stdlib/internal/jsonrt/dec.go +171 -38
  91. package/stdlib/internal/jsonrt/enc.go +186 -36
  92. package/stdlib/internal/jsonrt/scanner.go +17 -8
  93. package/stdlib/io/io.go +157 -1
  94. package/stdlib/maps/maps.go +131 -0
  95. package/stdlib/math/bits/bits.go +507 -0
  96. package/stdlib/math/extra.go +16 -14
  97. package/stdlib/math/math.go +439 -0
  98. package/stdlib/math/rand/rand.go +297 -0
  99. package/stdlib/os/os.go +93 -0
  100. package/stdlib/path/filepath/filepath.go +47 -0
  101. package/stdlib/path/path.go +394 -0
  102. package/stdlib/regexp/regexp.go +231 -0
  103. package/stdlib/slices/slices.go +526 -0
  104. package/stdlib/slices/sort.go +209 -0
  105. package/stdlib/slices/zsort_cmp.go +480 -0
  106. package/stdlib/slices/zsort_ordered.go +482 -0
  107. package/stdlib/sort/sort.go +391 -452
  108. package/stdlib/sort/zsort_func.go +480 -0
  109. package/stdlib/sort/zsort_iface.go +480 -0
  110. package/stdlib/strconv/float.go +1699 -0
  111. package/stdlib/strconv/isprint.go +752 -0
  112. package/stdlib/strconv/quote.go +235 -50
  113. package/stdlib/strconv/strconv.go +616 -0
  114. package/stdlib/strings/builder.go +95 -0
  115. package/stdlib/strings/reader.go +163 -0
  116. package/stdlib/strings/replacer.go +102 -0
  117. package/stdlib/strings/strings.go +807 -0
  118. package/stdlib/sync/sync.go +268 -0
  119. package/stdlib/testing/async_js.go +61 -0
  120. package/stdlib/testing/benchmark.go +297 -0
  121. package/stdlib/testing/testing.go +523 -0
  122. package/stdlib/time/duration.go +233 -0
  123. package/stdlib/time/format.go +745 -0
  124. package/stdlib/time/parse.go +790 -0
  125. package/stdlib/time/sleep_js.go +63 -0
  126. package/stdlib/time/time.go +834 -0
  127. package/stdlib/unicode/unicode.go +141 -3
  128. package/stdlib/unicode/utf8/utf8.go +464 -0
  129. package/src/backend/js/fmt-runtime.js +0 -573
  130. package/src/backend/js/stdlib/base64.js +0 -12
  131. package/src/backend/js/stdlib/binary.js +0 -102
  132. package/src/backend/js/stdlib/bits.js +0 -119
  133. package/src/backend/js/stdlib/builder.js +0 -98
  134. package/src/backend/js/stdlib/bytes.js +0 -368
  135. package/src/backend/js/stdlib/errors.js +0 -45
  136. package/src/backend/js/stdlib/fmt.js +0 -517
  137. package/src/backend/js/stdlib/gom.js +0 -155
  138. package/src/backend/js/stdlib/hex.js +0 -17
  139. package/src/backend/js/stdlib/html.js +0 -20
  140. package/src/backend/js/stdlib/io.js +0 -137
  141. package/src/backend/js/stdlib/json.js +0 -1167
  142. package/src/backend/js/stdlib/maps.js +0 -37
  143. package/src/backend/js/stdlib/math.js +0 -85
  144. package/src/backend/js/stdlib/os.js +0 -13
  145. package/src/backend/js/stdlib/path.js +0 -48
  146. package/src/backend/js/stdlib/rand.js +0 -32
  147. package/src/backend/js/stdlib/regexp.js +0 -54
  148. package/src/backend/js/stdlib/sha256.js +0 -12
  149. package/src/backend/js/stdlib/shared.js +0 -17
  150. package/src/backend/js/stdlib/simd.js +0 -34
  151. package/src/backend/js/stdlib/slices.js +0 -107
  152. package/src/backend/js/stdlib/sort.js +0 -30
  153. package/src/backend/js/stdlib/strconv.js +0 -79
  154. package/src/backend/js/stdlib/strings.js +0 -301
  155. package/src/backend/js/stdlib/sync.js +0 -13
  156. package/src/backend/js/stdlib/testing.js +0 -41
  157. package/src/backend/js/stdlib/time.js +0 -152
  158. package/src/backend/js/stdlib/unicode.js +0 -25
  159. package/src/backend/js/stdlib/utf8.js +0 -26
  160. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  161. package/src/backend/wasm/stdlib/core.js +0 -10278
  162. package/src/backend/wasm/stdlib/io.js +0 -3578
  163. package/src/backend/wasm/stdlib/packages.js +0 -2402
  164. package/src/backend/wasm/stdlib/pure.js +0 -720
  165. package/src/backend/wasm/stdlib/time.js +0 -2229
  166. package/src/typechecker/stdlib/core.js +0 -1079
  167. package/src/typechecker/stdlib/extended.js +0 -1210
  168. package/stdlib/bytes/iter.go +0 -192
  169. package/stdlib/bytes/search.go +0 -240
  170. package/stdlib/bytes/split.go +0 -218
  171. package/stdlib/errors/overlay.go +0 -12
  172. package/stdlib/maps/iter.go +0 -65
  173. package/stdlib/slices/extra.go +0 -73
  174. package/stdlib/slices/iter.go +0 -113
  175. package/stdlib/strings/extra.go +0 -35
  176. package/stdlib/strings/iter.go +0 -122
  177. package/stdlib/sync/extra.go +0 -105
  178. package/stdlib/unicode/utf8/bytes.go +0 -239
@@ -15,19 +15,28 @@ const CHECK_STMT_DELEGATE = {
15
15
  BranchStmt: "_checkBranchStmt",
16
16
  };
17
17
 
18
+ import { exprText, operandKind } from "./operand-text.js";
18
19
  import {
19
20
  ANY,
20
21
  BOOL,
21
22
  defaultType,
23
+ is64BitInt,
22
24
  isAny,
23
25
  isBool,
26
+ isNil,
27
+ isString,
28
+ isUntyped,
24
29
  isVoid,
25
30
  iteratorYieldParams,
26
31
  Scope,
32
+ typeStr,
27
33
  } from "./types.js";
28
34
 
29
35
  /** @typedef {import('./index.js').TypeChecker} TypeChecker */
30
36
 
37
+ // Tag kinds with no nil value: `case nil` against them cannot compile.
38
+ const NON_NILABLE_TAG_KINDS = new Set(["basic", "struct", "array"]);
39
+
31
40
  /** @type {ThisType<TypeChecker>} */
32
41
  export const statementCheckMethods = {
33
42
  checkBlock(block, scope, returnType) {
@@ -67,18 +76,41 @@ export const statementCheckMethods = {
67
76
 
68
77
  _checkDefineCommaOkAssert(stmt, rhs, scope) {
69
78
  if (!stmt.rhs[0]?._commaOk) return false;
70
- const lhsNames = stmt.lhs.map((e) => e.name ?? e);
71
- if (lhsNames[0] !== "_") scope.defineLocal(lhsNames[0], rhs[0]);
72
- if (lhsNames[1] !== "_") scope.defineLocal(lhsNames[1], BOOL);
79
+ this._declareDefineVars(stmt, [rhs[0], BOOL], scope);
80
+ if (stmt.lhs.every((e) => e._redecl || (e.name ?? e) === "_"))
81
+ this.err("no new variables on left side of :=", stmt.lhs[0]);
73
82
  stmt._rhsTypes = [rhs[0], BOOL];
74
83
  return true;
75
84
  },
76
85
 
86
+ _assignOperand(stmt, i) {
87
+ if (stmt.rhs.length === stmt.lhs.length) return stmt.rhs[i];
88
+ return stmt.rhs.length === 1 ? stmt.rhs[0] : undefined;
89
+ },
90
+
91
+ _checkRedeclAssign(target, source, stmt, i) {
92
+ if (!target || !source || !this._needsAssignCheck(target, source)) return;
93
+ this._skipIfaceMark = true;
94
+ this.assertAssignable(
95
+ target,
96
+ source,
97
+ stmt.lhs[i],
98
+ "assignment",
99
+ this._assignOperand(stmt, i),
100
+ );
101
+ this._skipIfaceMark = false;
102
+ },
103
+
77
104
  _declareDefineVars(stmt, rhsFlat, scope) {
78
105
  for (let i = 0; i < stmt.lhs.length; i++) {
79
106
  const name = stmt.lhs[i].name ?? stmt.lhs[i];
80
107
  if (name === "_") continue;
81
- if (scope.symbols.has(name)) stmt.lhs[i]._redecl = true;
108
+ if (scope.symbols.has(name)) {
109
+ stmt.lhs[i]._redecl = true;
110
+ // Redeclared variables keep their type; the value must be assignable.
111
+ this._checkRedeclAssign(scope.symbols.get(name), rhsFlat[i], stmt, i);
112
+ continue;
113
+ }
82
114
  if (stmt.rhs.length === stmt.lhs.length)
83
115
  this._checkConstDefault(
84
116
  rhsFlat[i],
@@ -97,10 +129,10 @@ export const statementCheckMethods = {
97
129
  )
98
130
  return false;
99
131
  const mapBaseType = this.checkExpr(stmt.rhs[0].expr, scope);
100
- const resolvedBase =
101
- mapBaseType?.kind === "named" ? mapBaseType.underlying : mapBaseType;
132
+ const resolvedBase = this._underlyingOrCore(mapBaseType, scope);
102
133
  if (resolvedBase?.kind !== "map") return false;
103
134
  const keyType = this.checkExpr(stmt.rhs[0].index, scope);
135
+ this.assertAssignable(resolvedBase.key, keyType, stmt.rhs[0].index);
104
136
  this._markIfaceKey(resolvedBase, stmt.rhs[0].index, keyType);
105
137
  stmt._commaOkMap = true;
106
138
  stmt.rhs[0]._mapValueType = resolvedBase.value;
@@ -129,7 +161,8 @@ export const statementCheckMethods = {
129
161
  if (this._checkDefineCommaOkAssert(stmt, rhs, scope)) return;
130
162
  if (
131
163
  stmt.rhs[0]?.kind !== "RangeExpr" &&
132
- stmt.lhs.length !== rhsFlat.length
164
+ stmt.lhs.length !== rhsFlat.length &&
165
+ !rhsFlat.some((t) => t?._tainted)
133
166
  ) {
134
167
  const vars =
135
168
  stmt.lhs.length === 1 ? "1 variable" : `${stmt.lhs.length} variables`;
@@ -156,10 +189,10 @@ export const statementCheckMethods = {
156
189
  )
157
190
  return null;
158
191
  const mapBaseType = this.checkExpr(stmt.rhs[0].expr, scope);
159
- const resolvedBase =
160
- mapBaseType?.kind === "named" ? mapBaseType.underlying : mapBaseType;
192
+ const resolvedBase = this._underlyingOrCore(mapBaseType, scope);
161
193
  if (resolvedBase?.kind !== "map") return null;
162
194
  const keyType = this.checkExpr(stmt.rhs[0].index, scope);
195
+ this.assertAssignable(resolvedBase.key, keyType, stmt.rhs[0].index);
163
196
  this._markIfaceKey(resolvedBase, stmt.rhs[0].index, keyType);
164
197
  stmt._commaOkMap = true;
165
198
  stmt.rhs[0]._mapValueType = resolvedBase.value;
@@ -189,10 +222,37 @@ export const statementCheckMethods = {
189
222
  return this.checkExpr(e, scope);
190
223
  },
191
224
 
225
+ // A source-written `any` operand still goes through assertAssignable so a
226
+ // concrete target reports `need type assertion`; interop `any` stays exempt.
227
+ _needsAssignCheck(target, source) {
228
+ return !isAny(target) && (!isAny(source) || !!source._declared);
229
+ },
230
+
231
+ // A range assignment (`for _, s = range xs`) stores each key and element
232
+ // into an existing variable; one of interface type boxes the value as
233
+ // any other conversion does. The box is recorded on a holder, not on the
234
+ // target, which codegen also reads as an assignable expression.
235
+ _markRangeAssignBox(stmt, i, target, source) {
236
+ if (stmt.rhs[0]?.kind !== "RangeExpr") return;
237
+ this._markRangeTargetBox(stmt.lhs[i], target, source);
238
+ },
239
+
240
+ _markRangeTargetBox(node, target, source) {
241
+ if (!target || !source) return;
242
+ const holder = {};
243
+ this._markIfaceBox(
244
+ this.resolveType(target),
245
+ this.resolveType(source),
246
+ holder,
247
+ );
248
+ if (holder._ifaceBox) node._rangeBox = holder;
249
+ },
250
+
192
251
  _checkAssignTypes(stmt, rhsFlat, lhsTypes) {
193
252
  for (let i = 0; i < lhsTypes.length; i++) {
194
253
  if (stmt.lhs[i]?.kind === "Ident" && stmt.lhs[i]?.name === "_") continue;
195
254
  const r = rhsFlat[i] ?? ANY;
255
+ this._markRangeAssignBox(stmt, i, lhsTypes[i], r);
196
256
  if (stmt.rhs.length === lhsTypes.length && lhsTypes[i] && r)
197
257
  this._markIfaceBox(
198
258
  this.resolveType(lhsTypes[i]),
@@ -209,9 +269,15 @@ export const statementCheckMethods = {
209
269
  this._checkConstOverflow(lhsTypes[i], r, stmt.rhs[i], "assignment")
210
270
  )
211
271
  continue;
212
- if (!isAny(lhsTypes[i]) && !isAny(r)) {
272
+ if (this._needsAssignCheck(lhsTypes[i], r)) {
213
273
  this._skipIfaceMark = true;
214
- this.assertAssignable(lhsTypes[i], r, stmt.lhs[i]);
274
+ this.assertAssignable(
275
+ lhsTypes[i],
276
+ r,
277
+ stmt.lhs[i],
278
+ "assignment",
279
+ this._assignOperand(stmt, i),
280
+ );
215
281
  this._skipIfaceMark = false;
216
282
  }
217
283
  }
@@ -231,7 +297,8 @@ export const statementCheckMethods = {
231
297
  if (
232
298
  !stmt.rhs[0]?._commaOk &&
233
299
  stmt.rhs[0]?.kind !== "RangeExpr" &&
234
- stmt.lhs.length !== rhsFlat.length
300
+ stmt.lhs.length !== rhsFlat.length &&
301
+ !rhsFlat.some((t) => t?._tainted)
235
302
  ) {
236
303
  const vars =
237
304
  stmt.lhs.length === 1 ? "1 variable" : `${stmt.lhs.length} variables`;
@@ -247,9 +314,34 @@ export const statementCheckMethods = {
247
314
  }
248
315
  }
249
316
  const lhsTypes = stmt.lhs.map((e) => this._checkAssignLhsType(e, scope));
317
+ // A rejected range index is not checked again as an ordinary value.
318
+ if (this._checkRangeIndexAssign(stmt, lhsTypes))
319
+ rhsFlat = [ANY, ...rhsFlat.slice(1)];
250
320
  this._checkAssignTypes(stmt, rhsFlat, lhsTypes);
251
321
  },
252
322
 
323
+ // The index of a string, slice or array range is a typed `int`: Go assigns
324
+ // it only to `int` (or an interface), never to another integer type.
325
+ _checkRangeIndexAssign(stmt, lhsTypes) {
326
+ const range = stmt.rhs[0];
327
+ const lhs = stmt.lhs[0];
328
+ if (range?.kind !== "RangeExpr" || range._isIterator) return false;
329
+ if (!lhs || (lhs.kind === "Ident" && lhs.name === "_")) return false;
330
+ const target = lhsTypes[0];
331
+ if (!target || isAny(target) || typeStr(target) === "int") return false;
332
+ const coll = this.resolveType(range.expr._coreType ?? range.expr._type);
333
+ const cu =
334
+ coll?.kind === "named" ? this.resolveType(coll.underlying) : coll;
335
+ if (!isString(cu) && cu?.kind !== "slice" && cu?.kind !== "array")
336
+ return false;
337
+ const tu = this.resolveType(
338
+ target.kind === "named" ? target.underlying : target,
339
+ );
340
+ if (tu?.kind === "interface") return false;
341
+ this.err(`Cannot assign int to ${typeStr(target)}`, lhs);
342
+ return true;
343
+ },
344
+
253
345
  _checkReturnStmt(stmt, scope, returnType) {
254
346
  const types = stmt.values.map((v) => this.checkExpr(v, scope));
255
347
  stmt._types = types;
@@ -274,14 +366,12 @@ export const statementCheckMethods = {
274
366
  );
275
367
  return;
276
368
  }
277
- for (let i = 0; i < expected.length; i++) {
278
- this.assertAssignable(
279
- expected[i],
280
- forwarded[i],
281
- stmt.values[0],
282
- "return statement",
283
- );
284
- }
369
+ this._checkForwardedValues(
370
+ expected,
371
+ forwarded,
372
+ stmt.values[0],
373
+ "return statement",
374
+ );
285
375
  return;
286
376
  }
287
377
  for (let i = 0; i < expected.length; i++) {
@@ -296,6 +386,35 @@ export const statementCheckMethods = {
296
386
  }
297
387
  },
298
388
 
389
+ // Checks each result of a forwarded multi-value call (`return f()`,
390
+ // `g(f())`) against its target. Every element gets its own interface-box
391
+ // marks in `call._fwdIface` (null where nothing is boxed): tagging the
392
+ // shared call node would give every element the marks of the last one.
393
+ // Lowering splits such forwards into temps that carry these marks.
394
+ _checkForwardedValues(expected, forwarded, call, ctx) {
395
+ const skip = this._skipIfaceMark;
396
+ this._skipIfaceMark = true;
397
+ try {
398
+ for (let i = 0; i < expected.length; i++)
399
+ this.assertAssignable(expected[i], forwarded[i], call, ctx);
400
+ } finally {
401
+ this._skipIfaceMark = skip;
402
+ }
403
+ if (!skip) this._markForwardedElems(expected, forwarded, call);
404
+ },
405
+
406
+ // Records in `call._fwdIface` one interface-box mark holder per forwarded
407
+ // element (null where the element is not boxed), or clears it when no
408
+ // element enters an interface.
409
+ _markForwardedElems(expected, forwarded, call) {
410
+ const holders = expected.map((t, i) => {
411
+ const holder = {};
412
+ this._markIfaceBox(t, forwarded[i], holder);
413
+ return "_ifaceSrc" in holder ? holder : null;
414
+ });
415
+ call._fwdIface = holders.some(Boolean) ? holders : undefined;
416
+ },
417
+
299
418
  _checkIfStmt(stmt, scope, returnType) {
300
419
  const inner = new Scope(scope);
301
420
  if (stmt.init) this.checkStmt(stmt.init, inner, returnType);
@@ -321,6 +440,8 @@ export const statementCheckMethods = {
321
440
  this.err("For condition must be bool", stmt);
322
441
  } else {
323
442
  const ct = this.checkExpr(stmt.cond.expr, inner);
443
+ const core = ct?.kind === "typeParam" ? this._coreType(ct, inner) : null;
444
+ if (core) stmt.cond.expr._coreType = core;
324
445
  const info = iteratorYieldParams(ct);
325
446
  if (info) {
326
447
  stmt.cond._isIterator = true;
@@ -359,10 +480,18 @@ export const statementCheckMethods = {
359
480
  const inner = new Scope(scope);
360
481
  if (stmt.init) this.checkStmt(stmt.init, inner, returnType);
361
482
  if (stmt.tag) this.checkExpr(stmt.tag, inner);
483
+ const tagIs64 = stmt.tag && is64BitInt(stmt.tag._type);
362
484
  this._switchDepth++;
363
485
  for (const c of stmt.cases) {
364
486
  const caseScope = new Scope(inner);
365
- if (c.list) for (const e of c.list) this.checkExpr(e, caseScope);
487
+ if (c.list) {
488
+ for (const e of c.list) {
489
+ this.checkExpr(e, caseScope);
490
+ this._checkCaseValue(stmt.tag, e);
491
+ if (stmt.tag) this._tagFloat32Const(stmt.tag._type, e._type, e);
492
+ if (tagIs64 && e._type?.kind === "untyped") e._is64Bit = true;
493
+ }
494
+ }
366
495
  for (const s of c.stmts) this.checkStmt(s, caseScope, returnType);
367
496
  this._reportUnused(caseScope, stmt);
368
497
  }
@@ -370,6 +499,92 @@ export const statementCheckMethods = {
370
499
  this._reportUnused(inner, stmt);
371
500
  },
372
501
 
502
+ // `case e:` compares `tag == e`, so `e` must convert to the tag's type
503
+ // (or the other way round for typed values).
504
+ _checkCaseValue(tag, e) {
505
+ const caseType = this.resolveType(e._type);
506
+ if (!tag) {
507
+ this._checkTaglessCase(e, caseType);
508
+ return;
509
+ }
510
+ let tagType = this.resolveType(tag._type);
511
+ // `switch "foo" {` compares as the constant's default type.
512
+ if (isUntyped(tagType) && !isNil(tagType)) tagType = defaultType(tagType);
513
+ if (this._caseValueUnchecked(tagType, caseType)) return;
514
+ if (isNil(caseType)) {
515
+ this.err(`cannot convert nil to type ${typeStr(tagType)}`, e);
516
+ return;
517
+ }
518
+ if (isUntyped(caseType)) {
519
+ if (this._isUntypedAssignable(tagType, caseType)) return;
520
+ this.err(
521
+ `cannot convert ${exprText(e)} (${operandKind(e, caseType.base, true)}) to type ${typeStr(tagType)}`,
522
+ e,
523
+ );
524
+ return;
525
+ }
526
+ if (
527
+ this._assignableQuietly(tagType, caseType) ||
528
+ this._assignableQuietly(caseType, tagType)
529
+ )
530
+ return;
531
+ this.err(
532
+ `invalid case ${exprText(e)} in switch on ${exprText(tag)} (mismatched types ${typeStr(caseType)} and ${typeStr(tagType)})`,
533
+ e,
534
+ );
535
+ },
536
+
537
+ // A tagless `switch` is `switch true`, so every case must be boolean.
538
+ _checkTaglessCase(e, caseType) {
539
+ if (!caseType || caseType._tainted || caseType.kind === "typeParam") return;
540
+ if (isNil(caseType)) {
541
+ this.err(`cannot convert nil to type bool`, e);
542
+ return;
543
+ }
544
+ if (isAny(caseType) || isBool(caseType)) return;
545
+ if (isUntyped(caseType) && caseType.base === "bool") return;
546
+ const under = this.resolveType(caseType.underlying ?? caseType);
547
+ if (isBool(under)) return;
548
+ this.err(
549
+ `invalid case ${exprText(e)} in switch (mismatched types ${typeStr(caseType)} and bool)`,
550
+ e,
551
+ );
552
+ },
553
+
554
+ // Operands whose comparison the checker cannot (or need not) judge: unknown
555
+ // or recovered types, `any`/interface tags, nil cases, type parameters.
556
+ _caseValueUnchecked(tagType, caseType) {
557
+ if (!tagType || !caseType || tagType._tainted || caseType._tainted)
558
+ return true;
559
+ if (isAny(tagType) || isAny(caseType)) return true;
560
+ if (isUntyped(tagType) || tagType.kind === "typeParam") return true;
561
+ if (isNil(caseType)) return this._nilCaseOk(tagType);
562
+ return (
563
+ this.resolveType(tagType.underlying ?? tagType)?.kind === "interface"
564
+ );
565
+ },
566
+
567
+ // `case nil` needs a tag with a nil value (pointer, slice, map, func,
568
+ // chan, interface); a basic, struct or array tag makes the caller report
569
+ // `cannot convert nil`.
570
+ _nilCaseOk(tagType) {
571
+ const kind = this._deepUnderlying(tagType)?.kind;
572
+ return !NON_NILABLE_TAG_KINDS.has(kind);
573
+ },
574
+
575
+ // Whether assertAssignable(target, source) reports nothing.
576
+ _assignableQuietly(target, source) {
577
+ const saved = this.errors;
578
+ const savedSkip = this._skipIfaceMark;
579
+ this.errors = [];
580
+ this._skipIfaceMark = true;
581
+ this.assertAssignable(target, source, null);
582
+ const ok = this.errors.length === 0;
583
+ this.errors = saved;
584
+ this._skipIfaceMark = savedSkip;
585
+ return ok;
586
+ },
587
+
373
588
  // A case naming an interface (literal or declared) records the resolved
374
589
  // type so backends test its method set.
375
590
  _markInterfaceCaseType(t, scope) {
@@ -383,6 +598,23 @@ export const statementCheckMethods = {
383
598
  if (u?.kind === "interface") t._type = resolved;
384
599
  },
385
600
 
601
+ // Records the resolved type of a composite type node used as an assertion
602
+ // or type-switch target (`[]int`, `map[string]any`, `*int`, `iter.Seq[int]`)
603
+ // so the backend can match it against the dynamic type of an interface value.
604
+ _markDynamicType(node, resolved) {
605
+ const kind = node?.kind;
606
+ if (
607
+ kind === "SliceType" ||
608
+ kind === "ArrayType" ||
609
+ kind === "MapType" ||
610
+ kind === "PointerType" ||
611
+ kind === "FuncType" ||
612
+ kind === "StructType" ||
613
+ kind === "GenericTypeName"
614
+ )
615
+ node._assertType = resolved;
616
+ },
617
+
386
618
  _checkTypeSwitchStmt(stmt, scope, returnType) {
387
619
  const inner = new Scope(scope);
388
620
  this.checkExpr(stmt.expr, inner);
@@ -396,9 +628,12 @@ export const statementCheckMethods = {
396
628
  const base = t?.kind === "PointerType" ? t.base : t;
397
629
  if (base?.kind === "GenericTypeName") this.resolveTypeNode(t, inner);
398
630
  this._markInterfaceCaseType(t, inner);
631
+ if (t && t.kind !== "TypeName" && t.kind !== "InterfaceType")
632
+ this._markDynamicType(t, this.resolveTypeNode(t, inner));
399
633
  }
400
634
  if (stmt.assign) {
401
- // Bind the variable to the single case type, or any for multi/default.
635
+ // Bind the variable to the single case type, or the switch operand's type
636
+ // for multi-type and default clauses, as Go does.
402
637
  // `case nil` binds the switch expression's own type, as in Go.
403
638
  const only = c.types?.length === 1 ? c.types[0] : null;
404
639
  const isNilCase = only?.kind === "TypeName" && only.name === "nil";
@@ -406,7 +641,7 @@ export const statementCheckMethods = {
406
641
  ? (stmt.expr._type ?? ANY)
407
642
  : only
408
643
  ? this.resolveTypeNode(only, inner)
409
- : ANY;
644
+ : (stmt.expr._type ?? ANY);
410
645
  caseScope.defineLocal(stmt.assign, bindType);
411
646
  // The capture variable is always considered used — it's the whole
412
647
  // point of the switch-with-assign form. Each case body uses its own
@@ -453,9 +688,35 @@ export const statementCheckMethods = {
453
688
  }
454
689
  },
455
690
 
456
- _checkRangeIterAssignVars(lhs, scope) {
457
- for (const e of lhs) {
458
- if (e.kind === "Ident" && e.name !== "_") this.checkExpr(e, scope);
691
+ // Each yielded value is stored into an existing variable; one of
692
+ // interface type boxes the value as any other conversion does.
693
+ _checkRangeIterAssignVars(lhs, scope, yieldParams = []) {
694
+ lhs.forEach((e, i) => {
695
+ if (e.kind === "Ident" && e.name === "_") return;
696
+ const target = this.checkExpr(e, scope);
697
+ this._markRangeTargetBox(e, target, yieldParams[i]);
698
+ this._checkRangeIterAssignable(e, target, yieldParams[i]);
699
+ });
700
+ },
701
+
702
+ // The yielded value is assigned to the target, so it must be assignable
703
+ // to it (`for s = range seq` with `s string` and `seq` yielding `int`).
704
+ // The lenient `float64 = int` pair of plain assignments is reported here:
705
+ // the iterator loop stores the yielded value without converting it.
706
+ _checkRangeIterAssignable(node, target, source) {
707
+ if (!target || !source) return;
708
+ const t = this.resolveType(target);
709
+ const v = this.resolveType(source);
710
+ if (this._isNumericCoercible(t, v)) {
711
+ this.err(`Cannot assign ${typeStr(v)} to ${typeStr(t)}`, node);
712
+ return;
713
+ }
714
+ if (!this._needsAssignCheck(target, source)) return;
715
+ this._skipIfaceMark = true;
716
+ try {
717
+ this.assertAssignable(target, source, node, "assignment");
718
+ } finally {
719
+ this._skipIfaceMark = false;
459
720
  }
460
721
  },
461
722
 
@@ -481,7 +742,7 @@ export const statementCheckMethods = {
481
742
 
482
743
  if (initStmt.kind === "DefineStmt")
483
744
  this._bindRangeIterDefineVars(lhs, info, scope);
484
- else this._checkRangeIterAssignVars(lhs, scope);
745
+ else this._checkRangeIterAssignVars(lhs, scope, info.yieldParams);
485
746
  return info;
486
747
  },
487
748
  };
@@ -155,7 +155,7 @@ const TRIMMED_UNICODE_TABLES =
155
155
  const TRIMMED_UNICODE_CASE =
156
156
  "removed in v1.8.0; case ranges are not supported, use ToUpper, ToLower or ToTitle";
157
157
  const TRIMMED_FSCAN =
158
- "removed in v1.8.0; scanning from an io.Reader is not supported, use fmt.Sscan on the text";
158
+ "removed in v1.8.0; scanning from an io.Reader is not supported, parse the text with strings and strconv";
159
159
 
160
160
  // reason -> package path -> excluded member names
161
161
  const EXCLUDED_MEMBER_GROUPS = {
@@ -188,6 +188,14 @@ const EXCLUDED_MEMBER_GROUPS = {
188
188
  [TRIMMED_FSCAN]: {
189
189
  fmt: ["Fscan", "Fscanf", "Fscanln"],
190
190
  },
191
+ "removed in v1.9.0; reflection-based binary encoding is not supported, use binary.LittleEndian or binary.BigEndian methods (Uint32, PutUint32, AppendUint32, ...)":
192
+ {
193
+ "encoding/binary": ["Read", "Write", "Size"],
194
+ },
195
+ "removed in v1.9.0; format-string scanning is not supported, use strings.Fields with strconv.Atoi, ParseFloat or ParseBool":
196
+ {
197
+ fmt: ["Sscan", "Sscanf", "Sscanln"],
198
+ },
191
199
  "removed in v1.8.0; this function is not supported": {
192
200
  math: ["Gamma", "Erf"],
193
201
  },
@@ -252,7 +260,6 @@ const EXCLUDED_MEMBER_GROUPS = {
252
260
  "File.Chdir",
253
261
  "File.Chmod",
254
262
  "File.Chown",
255
- "File.Close",
256
263
  "File.Fd",
257
264
  "File.Name",
258
265
  "File.ReadAt",
@@ -393,7 +400,6 @@ const EXCLUDED_MEMBER_GROUPS = {
393
400
  "SEEK_CUR",
394
401
  "SEEK_END",
395
402
  "SEEK_SET",
396
- "Setenv",
397
403
  "Signal",
398
404
  "Signal.Signal",
399
405
  "Signal.String",
@@ -809,8 +815,6 @@ const EXCLUDED_MEMBER_GROUPS = {
809
815
  const REMOVED_PACKAGES = {
810
816
  bufio: "removed in v1.8.0; use strings and bytes readers",
811
817
  "net/url": "removed in v1.8.0; use the URL class from a js package",
812
- context:
813
- "removed in v1.8.0; use a cancel flag, or AbortController from a js package",
814
818
  "encoding/csv":
815
819
  "removed in v1.8.0; split lines and fields with strings.Split",
816
820
  "container/heap":
@@ -1,21 +1,37 @@
1
1
  // stdlib.js — built-in namespace and browser-global registration for the GoFront type
2
2
  // checker. Called once from TypeChecker._setupGlobals() during construction.
3
3
  //
4
- // Delegates to two focused sub-modules:
5
- // stdlib/core.js — browser globals, fmt, strings, bytes, strconv, sort,
6
- // math, errors, time, unicode, os, slices, html, io
7
- // stdlib/extended.js — gom, maps, regexp, rand, utf8, path, builder types,
8
- // built-in functions
4
+ // Registers browser globals and built-in functions, then delegates to the
5
+ // shared, simd and web sub-modules.
9
6
 
10
- import { setupCoreGlobals } from "./core.js";
11
- import { setupExtendedGlobals } from "./extended.js";
7
+ import { ANY, BROWSER_GLOBALS } from "../types.js";
12
8
  import { setupSharedGlobals } from "./shared.js";
13
9
  import { setupSimdGlobals } from "./simd.js";
14
10
  import { setupWebGlobals } from "./web.js";
15
11
 
16
12
  export function setupGlobals(globals, types) {
17
- setupCoreGlobals(globals, types);
18
- setupExtendedGlobals(globals, types);
13
+ for (const g of BROWSER_GLOBALS) globals.define(g, ANY);
14
+ for (const name of [
15
+ "append",
16
+ "len",
17
+ "cap",
18
+ "make",
19
+ "delete",
20
+ "print",
21
+ "println",
22
+ "panic",
23
+ "recover",
24
+ "new",
25
+ "copy",
26
+ "error",
27
+ "min",
28
+ "max",
29
+ "clear",
30
+ "complex",
31
+ "real",
32
+ "imag",
33
+ ])
34
+ globals.define(name, { kind: "builtin", name });
19
35
  setupSharedGlobals(globals, types);
20
36
  setupSimdGlobals(globals, types);
21
37
  setupWebGlobals(globals, types);
@@ -64,15 +64,24 @@ export const terminationMethods = {
64
64
 
65
65
  _isTerminatingSwitch(stmt) {
66
66
  let hasDefault = false;
67
- for (const c of stmt.cases ?? []) {
67
+ const cases = stmt.cases ?? [];
68
+ for (let i = 0; i < cases.length; i++) {
69
+ const c = cases[i];
68
70
  if (!c.list) hasDefault = true;
69
71
  if (!c.stmts?.length) return false;
70
72
  const last = c.stmts[c.stmts.length - 1];
73
+ // `fallthrough` hands control to the next clause, which is checked
74
+ // on its own; the final clause has none to fall into.
75
+ if (this._isFallthrough(last) && i < cases.length - 1) continue;
71
76
  if (!this._isTerminating(last)) return false;
72
77
  }
73
78
  return hasDefault;
74
79
  },
75
80
 
81
+ _isFallthrough(stmt) {
82
+ return stmt?.kind === "BranchStmt" && stmt.keyword === "fallthrough";
83
+ },
84
+
76
85
  _isTerminatingTypeSwitch(stmt) {
77
86
  let hasDefault = false;
78
87
  for (const c of stmt.cases ?? []) {