gofront 1.6.1 → 1.8.0
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/README.md +45 -49
- package/package.json +12 -8
- package/src/backend/js/expressions.js +1627 -148
- package/src/backend/js/fmt-runtime.js +573 -0
- package/src/backend/js/index.js +508 -102
- package/src/backend/js/runtime.js +1997 -106
- package/src/backend/js/statements.js +931 -87
- package/src/backend/js/stdlib/bits.js +87 -14
- package/src/backend/js/stdlib/builder.js +41 -6
- package/src/backend/js/stdlib/bytes.js +253 -7
- package/src/backend/js/stdlib/errors.js +7 -1
- package/src/backend/js/stdlib/fmt.js +349 -10
- package/src/backend/js/stdlib/index.js +19 -2
- package/src/backend/js/stdlib/json.js +1167 -0
- package/src/backend/js/stdlib/maps.js +0 -8
- package/src/backend/js/stdlib/math.js +17 -1
- package/src/backend/js/stdlib/simd.js +34 -0
- package/src/backend/js/stdlib/slices.js +36 -6
- package/src/backend/js/stdlib/strconv.js +10 -5
- package/src/backend/js/stdlib/strings.js +83 -4
- package/src/backend/js/stdlib/testing.js +7 -0
- package/src/backend/js/stdlib/time.js +79 -55
- package/src/backend/js/stdlib/unicode.js +1 -1
- package/src/backend/js/stdlib/utf8.js +1 -1
- package/src/backend/js/templ.js +38 -76
- package/src/backend/js/type-info.js +272 -0
- package/src/backend/shared/fmt-types.js +188 -0
- package/src/backend/wasm/boundary.js +446 -72
- package/src/backend/wasm/emit-builtins.js +320 -13
- package/src/backend/wasm/emit-exprs.js +1404 -199
- package/src/backend/wasm/emit-maps.js +61 -13
- package/src/backend/wasm/emit-stmts.js +1359 -164
- package/src/backend/wasm/emit.js +518 -74
- package/src/backend/wasm/encode.js +128 -13
- package/src/backend/wasm/globals.js +452 -0
- package/src/backend/wasm/index.js +194 -2489
- package/src/backend/wasm/method-expr.js +381 -0
- package/src/backend/wasm/module-emitter.js +2240 -0
- package/src/backend/wasm/monomorph.js +62 -4
- package/src/backend/wasm/optimize.js +4 -2
- package/src/backend/wasm/prescan.js +397 -0
- package/src/backend/wasm/stdlib/bytes.js +1804 -127
- package/src/backend/wasm/stdlib/core.js +1574 -199
- package/src/backend/wasm/stdlib/index.js +3 -5
- package/src/backend/wasm/stdlib/io.js +1567 -150
- package/src/backend/wasm/stdlib/packages.js +2 -0
- package/src/backend/wasm/stdlib/simd.js +499 -0
- package/src/backend/wasm/stdlib/time.js +1041 -12
- package/src/backend/wasm/types.js +221 -15
- package/src/backend/wasm/variadic.js +93 -0
- package/src/backend/wasm/wat.js +84 -20
- package/src/{cli-core.js → cli/cli-core.js} +236 -10
- package/src/{test-runner.js → cli/test-runner.js} +332 -39
- package/src/{vendor.js → cli/vendor.js} +89 -15
- package/src/compiler.js +107 -269
- package/src/dts-parser.js +71 -14
- package/src/index.js +15 -169
- package/src/{lexer.js → lexer/lexer.js} +102 -55
- package/src/{tokens.js → lexer/tokens.js} +1 -0
- package/src/link/sourcemap.js +97 -0
- package/src/link/stdlib.js +515 -0
- package/src/link/wasm.js +294 -0
- package/src/lower/bounds.js +394 -0
- package/src/lower/boxing.js +1 -6
- package/src/lower/captures.js +176 -32
- package/src/lower/embedding.js +2 -1
- package/src/lower/index.js +26 -18
- package/src/lower/json-codecs.js +1398 -0
- package/src/lower/ownership.js +22 -1
- package/src/lower/range.js +86 -1
- package/src/lower/reachability.js +132 -0
- package/src/parser/declarations.js +58 -13
- package/src/parser/expressions.js +29 -7
- package/src/parser/index.js +2 -1
- package/src/parser/statements.js +37 -22
- package/src/parser/struct-tag.js +144 -0
- package/src/{templ-parser.js → parser/templ.js} +138 -15
- package/src/parser/types.js +130 -13
- package/src/resolver.js +22 -1
- package/src/typechecker/assignability.js +215 -11
- package/src/typechecker/const-fold.js +356 -0
- package/src/typechecker/constants.js +256 -0
- package/src/typechecker/expressions.js +960 -99
- package/src/typechecker/index.js +491 -72
- package/src/typechecker/init-order.js +330 -0
- package/src/typechecker/resolve.js +596 -52
- package/src/typechecker/statements.js +157 -17
- package/src/typechecker/stdlib/core.js +333 -55
- package/src/typechecker/stdlib/excluded.js +898 -0
- package/src/typechecker/stdlib/extended.js +359 -26
- package/src/typechecker/{stdlib.js → stdlib/index.js} +6 -4
- package/src/typechecker/stdlib/simd.js +123 -0
- package/src/typechecker/termination.js +1 -1
- package/src/typechecker/types.js +165 -26
- package/stdlib/bytes/iter.go +192 -0
- package/stdlib/bytes/search.go +240 -0
- package/stdlib/bytes/split.go +218 -0
- package/stdlib/cmp/cmp.go +65 -0
- package/stdlib/encoding/json/errors.go +31 -0
- package/stdlib/encoding/json/indent.go +71 -0
- package/stdlib/encoding/json/number.go +12 -0
- package/stdlib/encoding/json/stream.go +173 -0
- package/stdlib/errors/overlay.go +12 -0
- package/stdlib/hash/hash.go +53 -0
- package/stdlib/internal/jsonrt/dec.go +738 -0
- package/stdlib/internal/jsonrt/enc.go +366 -0
- package/stdlib/internal/jsonrt/indent.go +135 -0
- package/stdlib/internal/jsonrt/number.go +27 -0
- package/stdlib/internal/jsonrt/reader.go +73 -0
- package/stdlib/internal/jsonrt/scanner.go +649 -0
- package/stdlib/io/io.go +346 -0
- package/stdlib/iter/iter.go +18 -0
- package/stdlib/maps/iter.go +65 -0
- package/stdlib/math/extra.go +270 -0
- package/stdlib/slices/extra.go +73 -0
- package/stdlib/slices/iter.go +113 -0
- package/stdlib/sort/sort.go +561 -0
- package/stdlib/strconv/quote.go +341 -0
- package/stdlib/strings/extra.go +35 -0
- package/stdlib/strings/iter.go +122 -0
- package/stdlib/sync/extra.go +105 -0
- package/stdlib/unicode/unicode.go +112 -0
- package/stdlib/unicode/utf8/bytes.go +239 -0
- package/src/backend/wasm/const-fold.js +0 -205
- package/src/backend/wasm/emit-stdlib.js +0 -13
- /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
- /package/src/{colors.js → cli/colors.js} +0 -0
- /package/src/{dev-server.js → cli/dev-server.js} +0 -0
- /package/src/{minifier.js → cli/minifier.js} +0 -0
- /package/src/{project-config.js → cli/project-config.js} +0 -0
- /package/src/{pwa.js → cli/pwa.js} +0 -0
- /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Assignability, binary result types, and interface satisfaction.
|
|
2
2
|
// Installed as a mixin on TypeChecker.prototype.
|
|
3
3
|
|
|
4
|
+
import { constIntValue } from "./const-fold.js";
|
|
4
5
|
import { sharedInfo } from "./stdlib/shared.js";
|
|
5
6
|
import {
|
|
6
7
|
ANY,
|
|
@@ -10,7 +11,9 @@ import {
|
|
|
10
11
|
FLOAT64,
|
|
11
12
|
isAny,
|
|
12
13
|
isComplex,
|
|
14
|
+
isComplex64,
|
|
13
15
|
isComplexOrNumeric,
|
|
16
|
+
isInteger,
|
|
14
17
|
isNil,
|
|
15
18
|
isNumeric,
|
|
16
19
|
isPointer,
|
|
@@ -27,6 +30,16 @@ import {
|
|
|
27
30
|
UNTYPED_INT,
|
|
28
31
|
} from "./types.js";
|
|
29
32
|
|
|
33
|
+
// Unnamed type literal kinds for Go's "identical underlying types" assignability rule.
|
|
34
|
+
const TYPE_LITERAL_KINDS = new Set([
|
|
35
|
+
"slice",
|
|
36
|
+
"map",
|
|
37
|
+
"func",
|
|
38
|
+
"pointer",
|
|
39
|
+
"array",
|
|
40
|
+
"struct",
|
|
41
|
+
]);
|
|
42
|
+
|
|
30
43
|
/** @typedef {import('./index.js').TypeChecker} TypeChecker */
|
|
31
44
|
|
|
32
45
|
// Untyped constant assignability: maps source.base → Set of compatible target.name values
|
|
@@ -69,6 +82,8 @@ export const assignabilityMethods = {
|
|
|
69
82
|
if (isAny(lt) || isAny(rt)) return ANY;
|
|
70
83
|
if (isComplex(lt) || isComplex(rt))
|
|
71
84
|
return this._binaryResultTypeComplex(op, lt, rt, node);
|
|
85
|
+
if ((op === "<<" || op === ">>") && isNumeric(lt) && isNumeric(rt))
|
|
86
|
+
return this._binaryResultTypeShift(op, lt, rt, node);
|
|
72
87
|
if (isNumeric(lt) && isNumeric(rt))
|
|
73
88
|
return this._binaryResultTypeNumeric(lt, rt);
|
|
74
89
|
if (isString(lt) && isString(rt) && op === "+")
|
|
@@ -78,6 +93,37 @@ export const assignabilityMethods = {
|
|
|
78
93
|
return ANY;
|
|
79
94
|
},
|
|
80
95
|
|
|
96
|
+
// A shift has the type of its left operand; the count may be any integer
|
|
97
|
+
// type or an untyped constant representable as uint.
|
|
98
|
+
_binaryResultTypeShift(_op, lt, rt, node) {
|
|
99
|
+
if (node) this.checkShiftCount(rt, node.right);
|
|
100
|
+
if (node && lt.kind !== "untyped" && !isInteger(lt))
|
|
101
|
+
this.err(`shifted operand ${typeStr(lt)} must be integer`, node.left);
|
|
102
|
+
return lt;
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
// Reports a shift count that is not an integer or is a negative constant.
|
|
106
|
+
checkShiftCount(rt, countExpr) {
|
|
107
|
+
const v = this._constValue(countExpr);
|
|
108
|
+
if (v) {
|
|
109
|
+
const n = constIntValue(v);
|
|
110
|
+
if (n === null) {
|
|
111
|
+
this.err(
|
|
112
|
+
`invalid shift count ${typeStr(rt)} constant: not an integer`,
|
|
113
|
+
countExpr,
|
|
114
|
+
);
|
|
115
|
+
} else if (n < 0n) {
|
|
116
|
+
this.err(`invalid shift count: ${n} is negative`, countExpr);
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (!isInteger(rt))
|
|
121
|
+
this.err(
|
|
122
|
+
`invalid shift count: ${typeStr(rt)} is not an integer type`,
|
|
123
|
+
countExpr,
|
|
124
|
+
);
|
|
125
|
+
},
|
|
126
|
+
|
|
81
127
|
_binaryResultTypeString(lt, rt) {
|
|
82
128
|
if (lt.kind === "untyped" && rt.kind === "untyped") return lt;
|
|
83
129
|
if (lt.kind === "untyped") return rt;
|
|
@@ -101,6 +147,9 @@ export const assignabilityMethods = {
|
|
|
101
147
|
return ANY;
|
|
102
148
|
}
|
|
103
149
|
if (lt.kind === "untyped" && rt.kind === "untyped") return UNTYPED_COMPLEX;
|
|
150
|
+
if (lt.kind === "untyped") return rt;
|
|
151
|
+
if (rt.kind === "untyped") return lt;
|
|
152
|
+
if (isComplex64(lt) && isComplex64(rt)) return lt;
|
|
104
153
|
return COMPLEX128;
|
|
105
154
|
},
|
|
106
155
|
|
|
@@ -155,8 +204,9 @@ export const assignabilityMethods = {
|
|
|
155
204
|
|
|
156
205
|
// ── Assignability ─────────────────────────────────────────────────
|
|
157
206
|
|
|
158
|
-
assertAssignable(target, source, node) {
|
|
207
|
+
assertAssignable(target, source, node, ctx = "assignment") {
|
|
159
208
|
if (!target || !source) return;
|
|
209
|
+
if (this._checkConstOverflow(target, source, node, ctx)) return;
|
|
160
210
|
target = this.resolveType(target);
|
|
161
211
|
source = this.resolveType(source);
|
|
162
212
|
this._markIfaceBox(target, source, node);
|
|
@@ -171,7 +221,14 @@ export const assignabilityMethods = {
|
|
|
171
221
|
return;
|
|
172
222
|
}
|
|
173
223
|
if (isUntyped(source) && this._isUntypedAssignable(target, source)) return;
|
|
174
|
-
if (this._isNumericCoercible(target, source))
|
|
224
|
+
if (this._isNumericCoercible(target, source)) {
|
|
225
|
+
if (!this._isGenericCallResult(node)) return;
|
|
226
|
+
this.err(
|
|
227
|
+
`mismatched types ${typeStr(source)} and ${typeStr(target)} in ${ctx}`,
|
|
228
|
+
node,
|
|
229
|
+
);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
175
232
|
if (this._checkArrayAssignable(target, source, node)) return;
|
|
176
233
|
if (this._checkTypedArrayAssignable(target, source, node)) return;
|
|
177
234
|
if (this._checkFuncAssignable(target, source, node)) return;
|
|
@@ -191,17 +248,62 @@ export const assignabilityMethods = {
|
|
|
191
248
|
)
|
|
192
249
|
return;
|
|
193
250
|
if (target.kind === "typeParam") return;
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
);
|
|
251
|
+
let tBase = this.resolveType(target);
|
|
252
|
+
if (tBase?.kind === "named") tBase = this.resolveType(tBase.underlying);
|
|
197
253
|
if (!isAny(target) && tBase?.kind !== "interface") return;
|
|
254
|
+
// The static type behind the interface value: codegen derives the
|
|
255
|
+
// fmt metadata (Stringer elements of slices and maps) from it.
|
|
256
|
+
node._ifaceSrc = source;
|
|
257
|
+
const meta = this._ifaceBoxMeta(source);
|
|
258
|
+
if (!meta) return;
|
|
259
|
+
node._ifaceBox = meta.box;
|
|
260
|
+
if (meta.type) node._ifaceNamedType = meta.type;
|
|
261
|
+
if (meta.ptrFields) node._ifacePtrFields = meta.ptrFields;
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
// How a value of static type `source` is boxed on entering an interface:
|
|
265
|
+
// { box, type? (named kinds), ptrFields? }, or null when it stays raw.
|
|
266
|
+
// Shared by _markIfaceBox and the JS backend's multi-value fmt operands.
|
|
267
|
+
_ifaceBoxMeta(source) {
|
|
268
|
+
const named = this._namedMethodBoxSource(source);
|
|
269
|
+
if (named)
|
|
270
|
+
return { box: named.ptr ? "named_ptr" : "named", type: named.type };
|
|
198
271
|
const sBase = this.resolveType(
|
|
199
272
|
source.kind === "named" ? source.underlying : source,
|
|
200
273
|
);
|
|
201
274
|
const info = this._classifyIfaceBox(sBase);
|
|
202
|
-
if (!info) return;
|
|
203
|
-
|
|
204
|
-
|
|
275
|
+
if (!info) return null;
|
|
276
|
+
return { box: info.box, ptrFields: info.ptrFields };
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
// A named non-struct type with methods (or a pointer to one) entering an
|
|
280
|
+
// interface: the JS backend keeps such values raw and boxes them here so
|
|
281
|
+
// dynamic method dispatch works.
|
|
282
|
+
_namedMethodBoxSource(source) {
|
|
283
|
+
const isPtr = source?.kind === "pointer";
|
|
284
|
+
let t = isPtr ? source.base : source;
|
|
285
|
+
if (t?.kind === "TypeName" || t?.kind === "Ident") t = this.resolveType(t);
|
|
286
|
+
if (t?.kind !== "named") return null;
|
|
287
|
+
const u = this.resolveType(t.underlying);
|
|
288
|
+
if (!u || u.kind === "struct" || u.kind === "interface") return null;
|
|
289
|
+
const methods = t.methods ?? t._generic?.methods;
|
|
290
|
+
// A user-declared named basic type without methods is boxed as well, so
|
|
291
|
+
// a type assertion to its underlying type fails like Go.
|
|
292
|
+
if (
|
|
293
|
+
u.kind === "basic" &&
|
|
294
|
+
!t._generic &&
|
|
295
|
+
t.methods instanceof Map &&
|
|
296
|
+
t.methods.size === 0 &&
|
|
297
|
+
t._target !== undefined &&
|
|
298
|
+
t._target !== "wasm"
|
|
299
|
+
)
|
|
300
|
+
return { type: t, ptr: isPtr };
|
|
301
|
+
if (!methods || methods.size === 0) return null;
|
|
302
|
+
// Only user-declared types (methods carry _ptrRecv); stdlib named types
|
|
303
|
+
// such as time.Duration have no box class.
|
|
304
|
+
if (![...methods.values()].some((m) => Object.hasOwn(m ?? {}, "_ptrRecv")))
|
|
305
|
+
return null;
|
|
306
|
+
return { type: t, ptr: isPtr };
|
|
205
307
|
},
|
|
206
308
|
|
|
207
309
|
_getStructPtrFields(st, prefix = "", seen = new Set()) {
|
|
@@ -316,7 +418,56 @@ export const assignabilityMethods = {
|
|
|
316
418
|
);
|
|
317
419
|
return;
|
|
318
420
|
}
|
|
319
|
-
this.
|
|
421
|
+
if (this._identicalUnderlyingLiteral(target, source, tBase, sBase)) return;
|
|
422
|
+
this.err(
|
|
423
|
+
`Cannot assign ${typeStr(source)} to ${typeStr(target)}`,
|
|
424
|
+
node,
|
|
425
|
+
this._seqToSliceHint(source, tBase, node),
|
|
426
|
+
);
|
|
427
|
+
},
|
|
428
|
+
|
|
429
|
+
// maps.Keys/Values (and every iter.Seq) no longer return a slice: point the
|
|
430
|
+
// slice-expecting caller at slices.Collect.
|
|
431
|
+
_seqToSliceHint(source, tBase, node) {
|
|
432
|
+
if (tBase?.kind !== "slice" || !this._isIterSeq(source)) return null;
|
|
433
|
+
return this._seqCollectHint(node);
|
|
434
|
+
},
|
|
435
|
+
|
|
436
|
+
_isIterSeq(t) {
|
|
437
|
+
return t?.kind === "named" && /^iter\.Seq\d*\[/.test(typeStr(t));
|
|
438
|
+
},
|
|
439
|
+
|
|
440
|
+
_seqCollectHint(node) {
|
|
441
|
+
const f = node?.kind === "CallExpr" ? node.func : null;
|
|
442
|
+
if (
|
|
443
|
+
f?.kind === "SelectorExpr" &&
|
|
444
|
+
f.expr?.kind === "Ident" &&
|
|
445
|
+
f.expr.name === "maps" &&
|
|
446
|
+
(f.field === "Keys" || f.field === "Values")
|
|
447
|
+
)
|
|
448
|
+
return `use slices.Collect(maps.${f.field}(m)) for a slice`;
|
|
449
|
+
return "use slices.Collect(seq) for a slice";
|
|
450
|
+
},
|
|
451
|
+
|
|
452
|
+
// len/cap, spread and slice conversion are lenient on func types, and an
|
|
453
|
+
// iter.Seq value is one: reject it there instead of miscompiling.
|
|
454
|
+
_rejectSeqAsSlice(type, node, use) {
|
|
455
|
+
if (!this._isIterSeq(type)) return;
|
|
456
|
+
this.err(
|
|
457
|
+
`cannot use ${typeStr(type)} in ${use}`,
|
|
458
|
+
node,
|
|
459
|
+
this._seqCollectHint(node),
|
|
460
|
+
);
|
|
461
|
+
},
|
|
462
|
+
|
|
463
|
+
// Identical underlying types are assignable when one side is an unnamed
|
|
464
|
+
// type literal (`var n Names = []string{...}`), as in Go.
|
|
465
|
+
_identicalUnderlyingLiteral(target, source, tBase, sBase) {
|
|
466
|
+
if ((target.kind === "named") === (source.kind === "named")) return false;
|
|
467
|
+
const unnamed = target.kind === "named" ? source : target;
|
|
468
|
+
return (
|
|
469
|
+
TYPE_LITERAL_KINDS.has(unnamed.kind) && typeStr(tBase) === typeStr(sBase)
|
|
470
|
+
);
|
|
320
471
|
},
|
|
321
472
|
|
|
322
473
|
_isUntypedAssignable(target, source) {
|
|
@@ -332,6 +483,20 @@ export const assignabilityMethods = {
|
|
|
332
483
|
);
|
|
333
484
|
},
|
|
334
485
|
|
|
486
|
+
// A value computed from a generic call (`3 + Sum[float64](1, 2)`) has the
|
|
487
|
+
// exact instantiated type, so the loose int/float64 promotion that plain
|
|
488
|
+
// code enjoys does not apply to it.
|
|
489
|
+
_isGenericCallResult(node) {
|
|
490
|
+
if (!node || typeof node !== "object") return false;
|
|
491
|
+
if (node.kind === "ParenExpr") return this._isGenericCallResult(node.expr);
|
|
492
|
+
if (node.kind === "BinaryExpr")
|
|
493
|
+
return (
|
|
494
|
+
this._isGenericCallResult(node.left) ||
|
|
495
|
+
this._isGenericCallResult(node.right)
|
|
496
|
+
);
|
|
497
|
+
return node.kind === "CallExpr" && Boolean(node.func?._genericFnType);
|
|
498
|
+
},
|
|
499
|
+
|
|
335
500
|
_isNumericCoercible(target, source) {
|
|
336
501
|
if (target.kind !== "basic" || source.kind !== "basic") return false;
|
|
337
502
|
const pair = `${target.name}:${source.name}`;
|
|
@@ -420,10 +585,47 @@ export const assignabilityMethods = {
|
|
|
420
585
|
|
|
421
586
|
// ── Interface satisfaction ────────────────────────────────────────
|
|
422
587
|
|
|
588
|
+
// `interface{}` and `any` are the same type, but print differently.
|
|
589
|
+
_sigTypeStr(t) {
|
|
590
|
+
switch (t?.kind) {
|
|
591
|
+
case "interface":
|
|
592
|
+
if (
|
|
593
|
+
!t.name &&
|
|
594
|
+
t.methods.size === 0 &&
|
|
595
|
+
!t.unionConstraint &&
|
|
596
|
+
!t._typeSetEmbeds
|
|
597
|
+
)
|
|
598
|
+
return "any";
|
|
599
|
+
return typeStr(t);
|
|
600
|
+
case "slice":
|
|
601
|
+
return `[]${this._sigTypeStr(t.elem)}`;
|
|
602
|
+
case "array":
|
|
603
|
+
return `[${t.size ?? "..."}]${this._sigTypeStr(t.elem)}`;
|
|
604
|
+
case "pointer":
|
|
605
|
+
return `*${this._sigTypeStr(t.base)}`;
|
|
606
|
+
case "map":
|
|
607
|
+
return `map[${this._sigTypeStr(t.key)}]${this._sigTypeStr(t.value)}`;
|
|
608
|
+
case "tuple":
|
|
609
|
+
return `(${t.types.map((x) => this._sigTypeStr(x)).join(", ")})`;
|
|
610
|
+
case "func": {
|
|
611
|
+
const ret =
|
|
612
|
+
t.returns.length > 1
|
|
613
|
+
? `(${t.returns.map((x) => this._sigTypeStr(x)).join(", ")})`
|
|
614
|
+
: t.returns[0]
|
|
615
|
+
? this._sigTypeStr(t.returns[0])
|
|
616
|
+
: "void";
|
|
617
|
+
return `func(${t.params.map((x) => this._sigTypeStr(x)).join(", ")}) ${ret}`;
|
|
618
|
+
}
|
|
619
|
+
default:
|
|
620
|
+
return typeStr(t);
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
|
|
423
624
|
_sigParamsMatch(reqList, actList) {
|
|
424
625
|
if (reqList.length !== actList.length) return false;
|
|
425
626
|
for (let i = 0; i < reqList.length; i++) {
|
|
426
|
-
if (
|
|
627
|
+
if (this._sigTypeStr(reqList[i]) !== this._sigTypeStr(actList[i]))
|
|
628
|
+
return false;
|
|
427
629
|
}
|
|
428
630
|
return true;
|
|
429
631
|
},
|
|
@@ -466,7 +668,9 @@ export const assignabilityMethods = {
|
|
|
466
668
|
}
|
|
467
669
|
const methodMap =
|
|
468
670
|
base?.kind === "struct"
|
|
469
|
-
? base.methods
|
|
671
|
+
? base.methods?.size
|
|
672
|
+
? base.methods
|
|
673
|
+
: srcType.methods || base.methods
|
|
470
674
|
: srcType.kind === "named"
|
|
471
675
|
? srcType.methods
|
|
472
676
|
: null;
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
// Backend-independent evaluation of Go constant expressions.
|
|
2
|
+
//
|
|
3
|
+
// A constant value is `{ k, v, t, i }`:
|
|
4
|
+
// k — "INT" | "FLOAT" | "STRING" | "BOOL"
|
|
5
|
+
// v — BigInt (INT), Number (FLOAT), string or boolean
|
|
6
|
+
// t — basic type name of a typed constant ("int8", "float64", …); absent
|
|
7
|
+
// for untyped constants, which are exact (BigInt) and may exceed every
|
|
8
|
+
// Go type until they are converted or assigned
|
|
9
|
+
// i — for an untyped integral FLOAT, its exact BigInt value, so that
|
|
10
|
+
// `1e18 + 1` stays exact when it later becomes an integer
|
|
11
|
+
//
|
|
12
|
+
// The checker evaluates constant expressions with these helpers and attaches
|
|
13
|
+
// the result to the AST node (`_const`); both backends emit that value.
|
|
14
|
+
// `foldConstExpr` folds an AST directly (the WASM backend uses it for
|
|
15
|
+
// package-level constants).
|
|
16
|
+
|
|
17
|
+
const INT = "INT";
|
|
18
|
+
const FLOAT = "FLOAT";
|
|
19
|
+
const STRING = "STRING";
|
|
20
|
+
const BOOL = "BOOL";
|
|
21
|
+
|
|
22
|
+
// Shift counts beyond this are left to the runtime (Go allows larger shifts
|
|
23
|
+
// of untyped constants, but no program needs a 4096-bit intermediate).
|
|
24
|
+
const MAX_SHIFT = 4096n;
|
|
25
|
+
|
|
26
|
+
// Value ranges of the integer types.
|
|
27
|
+
export const INT_RANGES = {
|
|
28
|
+
int8: [-(2n ** 7n), 2n ** 7n - 1n],
|
|
29
|
+
int16: [-(2n ** 15n), 2n ** 15n - 1n],
|
|
30
|
+
int32: [-(2n ** 31n), 2n ** 31n - 1n],
|
|
31
|
+
rune: [-(2n ** 31n), 2n ** 31n - 1n],
|
|
32
|
+
int64: [-(2n ** 63n), 2n ** 63n - 1n],
|
|
33
|
+
int: [-(2n ** 63n), 2n ** 63n - 1n],
|
|
34
|
+
uint8: [0n, 2n ** 8n - 1n],
|
|
35
|
+
byte: [0n, 2n ** 8n - 1n],
|
|
36
|
+
uint16: [0n, 2n ** 16n - 1n],
|
|
37
|
+
uint32: [0n, 2n ** 32n - 1n],
|
|
38
|
+
uint64: [0n, 2n ** 64n - 1n],
|
|
39
|
+
uint: [0n, 2n ** 64n - 1n],
|
|
40
|
+
uintptr: [0n, 2n ** 64n - 1n],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const FLOAT_TYPES = new Set(["float32", "float64"]);
|
|
44
|
+
|
|
45
|
+
// Untyped integer constants of package `math` (Go declares them untyped).
|
|
46
|
+
// `MaxInt`, `MinInt` and `MaxUint` are left out: the JS backend maps them to
|
|
47
|
+
// the safe-integer range on purpose.
|
|
48
|
+
export const MATH_INT_CONSTS = {
|
|
49
|
+
MaxInt8: 2n ** 7n - 1n,
|
|
50
|
+
MinInt8: -(2n ** 7n),
|
|
51
|
+
MaxInt16: 2n ** 15n - 1n,
|
|
52
|
+
MinInt16: -(2n ** 15n),
|
|
53
|
+
MaxInt32: 2n ** 31n - 1n,
|
|
54
|
+
MinInt32: -(2n ** 31n),
|
|
55
|
+
MaxInt64: 2n ** 63n - 1n,
|
|
56
|
+
MinInt64: -(2n ** 63n),
|
|
57
|
+
MaxUint8: 2n ** 8n - 1n,
|
|
58
|
+
MaxUint16: 2n ** 16n - 1n,
|
|
59
|
+
MaxUint32: 2n ** 32n - 1n,
|
|
60
|
+
MaxUint64: 2n ** 64n - 1n,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const intConst = (v) => ({ k: INT, v });
|
|
64
|
+
|
|
65
|
+
// Exact integer value of a decimal float literal (`1e18`, `2.50e1`), or null.
|
|
66
|
+
function exactIntOfFloatText(text) {
|
|
67
|
+
const m = /^(\d*)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$/.exec(text);
|
|
68
|
+
if (!m) return null;
|
|
69
|
+
const frac = m[2] ?? "";
|
|
70
|
+
const digits = `${m[1]}${frac}` || "0";
|
|
71
|
+
const exp = Number(m[3] ?? 0) - frac.length;
|
|
72
|
+
if (exp > 400) return null;
|
|
73
|
+
if (exp >= 0) return BigInt(digits) * 10n ** BigInt(exp);
|
|
74
|
+
const cut = digits.length + exp;
|
|
75
|
+
if (!/^0*$/.test(digits.slice(Math.max(cut, 0)))) return null;
|
|
76
|
+
return cut > 0 ? BigInt(digits.slice(0, cut)) : 0n;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function constFromLit(lit) {
|
|
80
|
+
switch (lit.litKind) {
|
|
81
|
+
case INT: {
|
|
82
|
+
const s = String(lit.value).replaceAll("_", "");
|
|
83
|
+
const clean = /^0[0-7]+$/.test(s) ? `0o${s.slice(1)}` : s;
|
|
84
|
+
return { k: INT, v: BigInt(clean) };
|
|
85
|
+
}
|
|
86
|
+
case FLOAT: {
|
|
87
|
+
const s = String(lit.value).replaceAll("_", "");
|
|
88
|
+
const n = Number(s);
|
|
89
|
+
if (!Number.isFinite(n)) return null;
|
|
90
|
+
const i = exactIntOfFloatText(s);
|
|
91
|
+
return i === null ? { k: FLOAT, v: n } : { k: FLOAT, v: n, i };
|
|
92
|
+
}
|
|
93
|
+
case STRING:
|
|
94
|
+
return { k: STRING, v: String(lit.value ?? "") };
|
|
95
|
+
case BOOL:
|
|
96
|
+
return { k: BOOL, v: lit.value === "true" || lit.value === true };
|
|
97
|
+
default:
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function constToLit(val) {
|
|
103
|
+
switch (val.k) {
|
|
104
|
+
case INT:
|
|
105
|
+
return { kind: "BasicLit", litKind: INT, value: val.v.toString() };
|
|
106
|
+
case FLOAT:
|
|
107
|
+
return {
|
|
108
|
+
kind: "BasicLit",
|
|
109
|
+
litKind: FLOAT,
|
|
110
|
+
value: Object.is(val.v, -0) ? "-0" : String(val.v),
|
|
111
|
+
};
|
|
112
|
+
case STRING:
|
|
113
|
+
return { kind: "BasicLit", litKind: STRING, value: val.v };
|
|
114
|
+
default:
|
|
115
|
+
return {
|
|
116
|
+
kind: "BasicLit",
|
|
117
|
+
litKind: BOOL,
|
|
118
|
+
value: val.v ? "true" : "false",
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Exact integer value of an INT or integral FLOAT, else null.
|
|
124
|
+
export function constIntValue(val) {
|
|
125
|
+
if (val?.k === INT) return val.v;
|
|
126
|
+
if (val?.k !== FLOAT) return null;
|
|
127
|
+
if (val.i !== undefined) return val.i;
|
|
128
|
+
return Number.isInteger(val.v) ? BigInt(val.v) : null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function asFloat(val) {
|
|
132
|
+
return val.k === INT ? Number(val.v) : val.v;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// An integer result typed `t` that does not fit is an overflow.
|
|
136
|
+
function typedInt(v, t) {
|
|
137
|
+
const range = t ? INT_RANGES[t] : null;
|
|
138
|
+
if (range && (v < range[0] || v > range[1]))
|
|
139
|
+
return { k: INT, v, t, overflow: true };
|
|
140
|
+
return t ? { k: INT, v, t } : { k: INT, v };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function typedFloat(v, t) {
|
|
144
|
+
if (!Number.isFinite(v)) return null;
|
|
145
|
+
const r = t === "float32" ? Math.fround(v) : v;
|
|
146
|
+
return t ? { k: FLOAT, v: r, t } : { k: FLOAT, v: r };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function foldUnary(op, x) {
|
|
150
|
+
if (op === "+" && (x.k === INT || x.k === FLOAT)) return x;
|
|
151
|
+
if (op === "!" && x.k === BOOL) return { ...x, v: !x.v };
|
|
152
|
+
if (op === "-" && x.k === INT) return typedInt(-x.v, x.t);
|
|
153
|
+
if (op === "-" && x.k === FLOAT) {
|
|
154
|
+
const r = typedFloat(-x.v, x.t);
|
|
155
|
+
return r && x.i !== undefined ? { ...r, i: -x.i } : r;
|
|
156
|
+
}
|
|
157
|
+
if (op === "^" && x.k === INT) {
|
|
158
|
+
// Unsigned: flip the bits of the type's width; otherwise -x-1.
|
|
159
|
+
const range = x.t ? INT_RANGES[x.t] : null;
|
|
160
|
+
if (range && range[0] === 0n) return { k: INT, v: range[1] ^ x.v, t: x.t };
|
|
161
|
+
return typedInt(~x.v, x.t);
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function compare(op, a, b) {
|
|
167
|
+
switch (op) {
|
|
168
|
+
case "==":
|
|
169
|
+
return a === b;
|
|
170
|
+
case "!=":
|
|
171
|
+
return a !== b;
|
|
172
|
+
case "<":
|
|
173
|
+
return a < b;
|
|
174
|
+
case "<=":
|
|
175
|
+
return a <= b;
|
|
176
|
+
case ">":
|
|
177
|
+
return a > b;
|
|
178
|
+
case ">=":
|
|
179
|
+
return a >= b;
|
|
180
|
+
default:
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function foldInts(op, a, b, t) {
|
|
186
|
+
switch (op) {
|
|
187
|
+
case "+":
|
|
188
|
+
return typedInt(a + b, t);
|
|
189
|
+
case "-":
|
|
190
|
+
return typedInt(a - b, t);
|
|
191
|
+
case "*":
|
|
192
|
+
return typedInt(a * b, t);
|
|
193
|
+
case "/":
|
|
194
|
+
if (b === 0n) throw new Error("invalid operation: division by zero");
|
|
195
|
+
return typedInt(a / b, t);
|
|
196
|
+
case "%":
|
|
197
|
+
if (b === 0n) throw new Error("invalid operation: division by zero");
|
|
198
|
+
return typedInt(a % b, t);
|
|
199
|
+
case "&":
|
|
200
|
+
return typedInt(a & b, t);
|
|
201
|
+
case "|":
|
|
202
|
+
return typedInt(a | b, t);
|
|
203
|
+
case "^":
|
|
204
|
+
return typedInt(a ^ b, t);
|
|
205
|
+
case "&^":
|
|
206
|
+
return typedInt(a & ~b, t);
|
|
207
|
+
default: {
|
|
208
|
+
const r = compare(op, a, b);
|
|
209
|
+
return r === null ? null : { k: BOOL, v: r };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function foldFloats(op, a, b, t) {
|
|
215
|
+
// Untyped integral operands stay exact for + - * (`1e18 + 1`).
|
|
216
|
+
const ai = t ? null : constIntValue(a);
|
|
217
|
+
const bi = t ? null : constIntValue(b);
|
|
218
|
+
if (ai !== null && bi !== null && (op === "+" || op === "-" || op === "*")) {
|
|
219
|
+
const i = op === "+" ? ai + bi : op === "-" ? ai - bi : ai * bi;
|
|
220
|
+
return { k: FLOAT, v: Number(i), i };
|
|
221
|
+
}
|
|
222
|
+
const x = asFloat(a);
|
|
223
|
+
const y = asFloat(b);
|
|
224
|
+
switch (op) {
|
|
225
|
+
case "+":
|
|
226
|
+
return typedFloat(x + y, t);
|
|
227
|
+
case "-":
|
|
228
|
+
return typedFloat(x - y, t);
|
|
229
|
+
case "*":
|
|
230
|
+
return typedFloat(x * y, t);
|
|
231
|
+
case "/":
|
|
232
|
+
if (y === 0) throw new Error("invalid operation: division by zero");
|
|
233
|
+
return typedFloat(x / y, t);
|
|
234
|
+
default: {
|
|
235
|
+
const r = compare(op, x, y);
|
|
236
|
+
return r === null ? null : { k: BOOL, v: r };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function foldShift(op, a, b) {
|
|
242
|
+
const n = constIntValue(b);
|
|
243
|
+
const x = constIntValue(a);
|
|
244
|
+
if (n === null || x === null) return null;
|
|
245
|
+
if (n < 0n) throw new Error("invalid shift count: negative shift count");
|
|
246
|
+
if (n > MAX_SHIFT) return null;
|
|
247
|
+
return typedInt(op === "<<" ? x << n : x >> n, a.t);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function foldBools(op, a, b) {
|
|
251
|
+
if (op === "&&") return { k: BOOL, v: a.v && b.v };
|
|
252
|
+
if (op === "||") return { k: BOOL, v: a.v || b.v };
|
|
253
|
+
const r = op === "==" || op === "!=" ? compare(op, a.v, b.v) : null;
|
|
254
|
+
return r === null ? null : { k: BOOL, v: r };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function foldStrings(op, a, b) {
|
|
258
|
+
if (op === "+") return { k: STRING, v: a.v + b.v, t: a.t ?? b.t };
|
|
259
|
+
const r = compare(op, a.v, b.v);
|
|
260
|
+
return r === null ? null : { k: BOOL, v: r };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function foldBinary(op, a, b) {
|
|
264
|
+
if (op === "<<" || op === ">>") return foldShift(op, a, b);
|
|
265
|
+
if (a.k === BOOL && b.k === BOOL) return foldBools(op, a, b);
|
|
266
|
+
if (a.k === STRING && b.k === STRING) return foldStrings(op, a, b);
|
|
267
|
+
const t = a.t ?? b.t;
|
|
268
|
+
const numeric = (x) => x.k === INT || x.k === FLOAT;
|
|
269
|
+
if (!numeric(a) || !numeric(b)) return null;
|
|
270
|
+
if (FLOAT_TYPES.has(t) || (!t && (a.k === FLOAT || b.k === FLOAT)))
|
|
271
|
+
return foldFloats(op, a, b, t);
|
|
272
|
+
const ai = constIntValue(a);
|
|
273
|
+
const bi = constIntValue(b);
|
|
274
|
+
if (ai === null || bi === null) return foldFloats(op, a, b, t);
|
|
275
|
+
return foldInts(op, ai, bi, t);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Converts `val` to the basic type `name`. Returns the typed value, a value
|
|
279
|
+
// with `overflow: true` when an integer does not fit, or null when the
|
|
280
|
+
// conversion is not a constant one this folder handles.
|
|
281
|
+
export function convertConst(val, name) {
|
|
282
|
+
if (INT_RANGES[name]) {
|
|
283
|
+
const i = constIntValue(val);
|
|
284
|
+
return i === null ? null : typedInt(i, name);
|
|
285
|
+
}
|
|
286
|
+
if (FLOAT_TYPES.has(name)) {
|
|
287
|
+
if (val.k !== INT && val.k !== FLOAT) return null;
|
|
288
|
+
return typedFloat(asFloat(val), name);
|
|
289
|
+
}
|
|
290
|
+
if (name === "string") {
|
|
291
|
+
if (val.k === STRING) return { k: STRING, v: val.v, t: name };
|
|
292
|
+
const i = constIntValue(val);
|
|
293
|
+
if (val.k !== INT || i === null) return null;
|
|
294
|
+
const valid = i >= 0n && i <= 0x10ffffn && !(i >= 0xd800n && i <= 0xdfffn);
|
|
295
|
+
return {
|
|
296
|
+
k: STRING,
|
|
297
|
+
v: valid ? String.fromCodePoint(Number(i)) : "�",
|
|
298
|
+
t: name,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
if (name === "bool" && val.k === BOOL) return { k: BOOL, v: val.v, t: name };
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// min/max over constants of one kind.
|
|
306
|
+
export function foldMinMax(isMin, vals) {
|
|
307
|
+
let best = vals[0];
|
|
308
|
+
for (const x of vals.slice(1)) {
|
|
309
|
+
const less = foldBinary("<", x, best);
|
|
310
|
+
if (!less) return null;
|
|
311
|
+
if (less.v === isMin) best = x;
|
|
312
|
+
}
|
|
313
|
+
return best;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function foldExpr(expr, resolve) {
|
|
317
|
+
switch (expr?.kind) {
|
|
318
|
+
case "BasicLit":
|
|
319
|
+
return constFromLit(expr);
|
|
320
|
+
case "Ident": {
|
|
321
|
+
if (expr.name === "true" || expr.name === "false")
|
|
322
|
+
return { k: BOOL, v: expr.name === "true" };
|
|
323
|
+
const lit = resolve(expr.name);
|
|
324
|
+
return lit ? constFromLit(lit) : null;
|
|
325
|
+
}
|
|
326
|
+
case "ParenExpr":
|
|
327
|
+
return foldExpr(expr.expr, resolve);
|
|
328
|
+
case "UnaryExpr": {
|
|
329
|
+
const x = foldExpr(expr.operand, resolve);
|
|
330
|
+
return x ? foldUnary(expr.op, x) : null;
|
|
331
|
+
}
|
|
332
|
+
case "BinaryExpr": {
|
|
333
|
+
const a = foldExpr(expr.left, resolve);
|
|
334
|
+
const b = a ? foldExpr(expr.right, resolve) : null;
|
|
335
|
+
return a && b ? foldBinary(expr.op, a, b) : null;
|
|
336
|
+
}
|
|
337
|
+
case "CallExpr": {
|
|
338
|
+
if (expr.func?.kind !== "Ident" || expr.func.name !== "len") return null;
|
|
339
|
+
if (expr.args?.length !== 1) return null;
|
|
340
|
+
const s = foldExpr(expr.args[0], resolve);
|
|
341
|
+
if (s?.k !== STRING) return null;
|
|
342
|
+
// Gofront strings across JS and WASM runtime use UTF-16 code units (s.length).
|
|
343
|
+
return { k: INT, v: BigInt(s.v.length) };
|
|
344
|
+
}
|
|
345
|
+
default:
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Folds `expr` into a BasicLit, or returns null when it needs the runtime.
|
|
351
|
+
// `resolve(name)` yields the BasicLit of an already-folded constant, if any.
|
|
352
|
+
// Throws on a constant division by zero or a negative shift count.
|
|
353
|
+
export function foldConstExpr(expr, resolve) {
|
|
354
|
+
const val = foldExpr(expr, resolve);
|
|
355
|
+
return val && !val.overflow ? constToLit(val) : null;
|
|
356
|
+
}
|