gofront 1.9.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.
- package/README.md +1 -1
- package/package.json +2 -2
- package/src/backend/js/expressions.js +329 -58
- package/src/backend/js/index.js +90 -10
- package/src/backend/js/runtime.js +51 -9
- package/src/backend/js/statements.js +193 -84
- package/src/backend/js/type-info.js +123 -8
- package/src/backend/shared/fmt-types.js +30 -1
- package/src/backend/wasm/emit-builtins.js +250 -9
- package/src/backend/wasm/emit-exprs.js +63 -22
- package/src/backend/wasm/emit-shared.js +4 -1
- package/src/backend/wasm/emit-stmts.js +239 -205
- package/src/backend/wasm/emit.js +73 -26
- package/src/backend/wasm/globals.js +1 -0
- package/src/backend/wasm/index.js +8 -0
- package/src/backend/wasm/module-emitter.js +211 -57
- package/src/backend/wasm/monomorph.js +36 -2
- package/src/backend/wasm/natives.js +4 -0
- package/src/backend/wasm/types.js +32 -3
- package/src/backend/wasm/variadic.js +6 -1
- package/src/compiler.js +6 -1
- package/src/lexer/lexer.js +1 -0
- package/src/link/stdlib.js +1 -0
- package/src/link/type-names.js +72 -16
- package/src/lower/embedding.js +43 -15
- package/src/lower/fmt-formatters.js +38 -14
- package/src/lower/functions.js +223 -7
- package/src/lower/gen-types.js +21 -2
- package/src/lower/index.js +7 -1
- package/src/lower/json-codecs.js +114 -6
- package/src/parser/declarations.js +4 -2
- package/src/parser/expressions.js +25 -41
- package/src/parser/statements.js +107 -0
- package/src/parser/types.js +11 -0
- package/src/typechecker/assignability.js +94 -12
- package/src/typechecker/constants.js +124 -2
- package/src/typechecker/expressions.js +171 -17
- package/src/typechecker/index.js +158 -29
- package/src/typechecker/operand-text.js +1 -0
- package/src/typechecker/resolve.js +308 -39
- package/src/typechecker/statements.js +171 -21
- package/src/typechecker/types.js +84 -2
package/README.md
CHANGED
|
@@ -461,7 +461,7 @@ For detailed command flags, project configuration (`gofront.json`), multi-file p
|
|
|
461
461
|
## Tests
|
|
462
462
|
|
|
463
463
|
```sh
|
|
464
|
-
npm test # unit, zero-alloc perf, and example tests (~
|
|
464
|
+
npm test # unit, zero-alloc perf, and example tests (~4,700 tests, no browser required)
|
|
465
465
|
npm run test:e2e # E2E browser tests (Playwright, headless Chromium)
|
|
466
466
|
npm run bench # Raycast, JSON, and SIMD particle benchmarks (pure JS vs hybrid WASM)
|
|
467
467
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gofront",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"author": "Luuk van Venrooij",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"prepare": "lefthook install"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@biomejs/biome": "^2.
|
|
50
|
+
"@biomejs/biome": "^2.6.6",
|
|
51
51
|
"@playwright/test": "^1.64.1",
|
|
52
52
|
"c8": "^12.0.0",
|
|
53
53
|
"esbuild": "^0.28.2",
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
is64BitUnsigned,
|
|
17
17
|
isAny,
|
|
18
18
|
isComplex,
|
|
19
|
+
isComplex64,
|
|
19
20
|
isTypedArraySlice,
|
|
20
21
|
typedArrayConstructorForElem,
|
|
21
22
|
typeStr,
|
|
@@ -42,8 +43,92 @@ export function comparesToNil(node, name) {
|
|
|
42
43
|
return someChild(node, (c) => comparesToNil(c, name));
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
// True when evaluating `node` reads through the receiver `name` (`r.f`,
|
|
47
|
+
// `r[i]`, `*r`), which panics on a nil receiver anyway. Calling a method of
|
|
48
|
+
// `safe` on it is not a read: that method accepts a nil receiver. A func
|
|
49
|
+
// literal's body is not evaluated where it appears, so it is not searched.
|
|
50
|
+
export function derefsReceiver(node, name, safe) {
|
|
51
|
+
if (Array.isArray(node))
|
|
52
|
+
return node.some((n) => derefsReceiver(n, name, safe));
|
|
53
|
+
if (!node || typeof node !== "object" || node.kind === "FuncLit")
|
|
54
|
+
return false;
|
|
55
|
+
const isRecv = (e) => e?.kind === "Ident" && e.name === name;
|
|
56
|
+
if (node.kind === "SelectorExpr" && isRecv(node.expr))
|
|
57
|
+
return !safe.has(node.field);
|
|
58
|
+
if (
|
|
59
|
+
(node.kind === "IndexExpr" || node.kind === "SliceExpr") &&
|
|
60
|
+
isRecv(node.expr)
|
|
61
|
+
)
|
|
62
|
+
return true;
|
|
63
|
+
if (node.kind === "UnaryExpr" && node.op === "*" && isRecv(node.operand))
|
|
64
|
+
return true;
|
|
65
|
+
return someChild(node, (c) => derefsReceiver(c, name, safe));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// True when `stmts` (a method body) reads through the receiver `name` before
|
|
69
|
+
// anything could skip the read: only the statements that always run first
|
|
70
|
+
// count, up to the first branch, loop, return or rebinding of `name`. A
|
|
71
|
+
// method that does so panics on a nil receiver either way.
|
|
72
|
+
export function derefsReceiverFirst(stmts, name, safe) {
|
|
73
|
+
return receiverPrefix(stmts, name, safe) === "deref";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// What one statement of a method body's unconditional prefix does with the
|
|
77
|
+
// receiver: "deref" reads through it, "stop" may skip the rest (a branch,
|
|
78
|
+
// loop, return, panic or rebinding of the name), "next" runs on.
|
|
79
|
+
const binds = (lhs, name) =>
|
|
80
|
+
lhs.some((e) => e?.kind === "Ident" && e.name === name);
|
|
81
|
+
const branches = (reads, ...parts) => (parts.some(reads) ? "deref" : "stop");
|
|
82
|
+
const RECEIVER_STEP = {
|
|
83
|
+
Block: (s, _reads, name, safe) => receiverPrefix(s.stmts, name, safe),
|
|
84
|
+
IfStmt: (s, reads) => branches(reads, s.init, s.cond),
|
|
85
|
+
ForStmt: (s, reads) => branches(reads, s.init, s.cond),
|
|
86
|
+
SwitchStmt: (s, reads) => branches(reads, s.init, s.tag),
|
|
87
|
+
TypeSwitchStmt: (s, reads) => branches(reads, s.expr),
|
|
88
|
+
ReturnStmt: (s, reads) => branches(reads, s.values),
|
|
89
|
+
ExprStmt: (s, reads) => {
|
|
90
|
+
if (reads(s.expr)) return "deref";
|
|
91
|
+
const panics = s.expr?.kind === "CallExpr" && s.expr.func?.name === "panic";
|
|
92
|
+
return panics ? "stop" : "next";
|
|
93
|
+
},
|
|
94
|
+
DefineStmt: (s, reads, name) => {
|
|
95
|
+
if (reads(s.rhs) || reads(s.lhs.filter((e) => e?.kind !== "Ident")))
|
|
96
|
+
return "deref";
|
|
97
|
+
return binds(s.lhs, name) ? "stop" : "next";
|
|
98
|
+
},
|
|
99
|
+
IncDecStmt: (s, reads) => (reads(s.expr) ? "deref" : "next"),
|
|
100
|
+
VarDecl: (s, reads, name) => {
|
|
101
|
+
if (s.decls.some((d) => reads(d.value))) return "deref";
|
|
102
|
+
const named = (d) => d.names.some((n) => (n?.name ?? n) === name);
|
|
103
|
+
return s.decls.some(named) ? "stop" : "next";
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
RECEIVER_STEP.AssignStmt = RECEIVER_STEP.DefineStmt;
|
|
107
|
+
|
|
108
|
+
// "deref" when the unconditional prefix of `stmts` reads through the
|
|
109
|
+
// receiver, "stop" when something may skip the rest, else "next".
|
|
110
|
+
function receiverPrefix(stmts, name, safe) {
|
|
111
|
+
const reads = (n) => derefsReceiver(n, name, safe);
|
|
112
|
+
for (const s of stmts ?? []) {
|
|
113
|
+
const step = RECEIVER_STEP[s?.kind];
|
|
114
|
+
const r = step ? step(s, reads, name, safe) : "stop";
|
|
115
|
+
if (r !== "next") return r;
|
|
116
|
+
}
|
|
117
|
+
return "next";
|
|
118
|
+
}
|
|
119
|
+
|
|
45
120
|
/** @typedef {import('./index.js').CodeGen} CodeGen */
|
|
46
121
|
|
|
122
|
+
// True for a map, slice or array type that mentions a type parameter: one
|
|
123
|
+
// that generic code boxes into an interface is tagged by its instantiated
|
|
124
|
+
// type.
|
|
125
|
+
export function isGenericComposite(t) {
|
|
126
|
+
return (
|
|
127
|
+
(t?.kind === "map" || t?.kind === "slice" || t?.kind === "array") &&
|
|
128
|
+
mentionsTypeParam(t)
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
47
132
|
// True when type `t` mentions a type parameter anywhere.
|
|
48
133
|
export function mentionsTypeParam(t, depth = 0) {
|
|
49
134
|
if (!t || depth > 16) return false;
|
|
@@ -130,21 +215,6 @@ const STRICT_OPS = new Set([
|
|
|
130
215
|
">>",
|
|
131
216
|
]);
|
|
132
217
|
|
|
133
|
-
function collectFunctionLabels(node, labels = new Set()) {
|
|
134
|
-
if (!node || typeof node !== "object") return labels;
|
|
135
|
-
if (Array.isArray(node)) {
|
|
136
|
-
for (const item of node) collectFunctionLabels(item, labels);
|
|
137
|
-
return labels;
|
|
138
|
-
}
|
|
139
|
-
if (node.kind === "LabeledStmt" && node.label) labels.add(node.label);
|
|
140
|
-
if (node.kind === "ForStmt" && node.label) labels.add(node.label);
|
|
141
|
-
for (const k of Object.keys(node)) {
|
|
142
|
-
if (k.startsWith("_")) continue;
|
|
143
|
-
collectFunctionLabels(node[k], labels);
|
|
144
|
-
}
|
|
145
|
-
return labels;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
218
|
const NAMED_ZERO_TABLE = {
|
|
149
219
|
"simd.F32x4": [null, "Object.freeze([0, 0, 0, 0])"],
|
|
150
220
|
F32x4: [null, "Object.freeze([0, 0, 0, 0])"],
|
|
@@ -522,6 +592,41 @@ function hasTypeParamType(t) {
|
|
|
522
592
|
).includes('"typeParam"');
|
|
523
593
|
}
|
|
524
594
|
|
|
595
|
+
// Complex constant arithmetic on [re, im] pairs.
|
|
596
|
+
const COMPLEX_CONST_OPS = {
|
|
597
|
+
"+": (a, b) => [a[0] + b[0], a[1] + b[1]],
|
|
598
|
+
"-": (a, b) => [a[0] - b[0], a[1] - b[1]],
|
|
599
|
+
"*": (a, b) => [a[0] * b[0] - a[1] * b[1], a[0] * b[1] + a[1] * b[0]],
|
|
600
|
+
"/": (a, b) => {
|
|
601
|
+
const d = b[0] * b[0] + b[1] * b[1];
|
|
602
|
+
if (d === 0) return null;
|
|
603
|
+
return [(a[0] * b[0] + a[1] * b[1]) / d, (a[1] * b[0] - a[0] * b[1]) / d];
|
|
604
|
+
},
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
// The [re, im] value of an untyped complex constant expression built from
|
|
608
|
+
// literals and named constants, or null.
|
|
609
|
+
function complexConstParts(e, depth = 0) {
|
|
610
|
+
if (!e || typeof e !== "object" || depth > 32) return null;
|
|
611
|
+
if (e.kind === "ParenExpr") return complexConstParts(e.expr, depth + 1);
|
|
612
|
+
// A named complex constant has no folded value: fold its initializer.
|
|
613
|
+
if (e._complexInit) return complexConstParts(e._complexInit, depth + 1);
|
|
614
|
+
if (e.kind === "ImagLit") return [0, Number(e.value)];
|
|
615
|
+
const val = e._const;
|
|
616
|
+
if (val && (val.k === "FLOAT" || val.k === "INT")) return [Number(val.v), 0];
|
|
617
|
+
if (e.kind === "UnaryExpr") return complexConstNeg(e, depth);
|
|
618
|
+
const op = e.kind === "BinaryExpr" ? COMPLEX_CONST_OPS[e.op] : null;
|
|
619
|
+
const a = op && complexConstParts(e.left, depth + 1);
|
|
620
|
+
const b = a && complexConstParts(e.right, depth + 1);
|
|
621
|
+
return b ? op(a, b) : null;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function complexConstNeg(e, depth) {
|
|
625
|
+
if (e.op !== "-" && e.op !== "+") return null;
|
|
626
|
+
const c = complexConstParts(e.operand, depth + 1);
|
|
627
|
+
return c && e.op === "-" ? [-c[0], -c[1]] : c;
|
|
628
|
+
}
|
|
629
|
+
|
|
525
630
|
export const expressionGenMethods = {
|
|
526
631
|
genExpr(expr) {
|
|
527
632
|
if (expr._asComplex && !expr._complexWrapping) {
|
|
@@ -605,14 +710,44 @@ export const expressionGenMethods = {
|
|
|
605
710
|
}
|
|
606
711
|
}
|
|
607
712
|
if (this._isNamedIfaceBox(expr)) return this._genNamedIfaceBoxExpr(expr);
|
|
608
|
-
if (
|
|
609
|
-
|
|
713
|
+
if (
|
|
714
|
+
expr._ifaceSrc &&
|
|
715
|
+
!expr._tfnTagging &&
|
|
716
|
+
this._isTaggedFuncType(expr._ifaceSrc)
|
|
717
|
+
) {
|
|
718
|
+
// A value of a defined func type keeps that type in an interface.
|
|
719
|
+
expr._tfnTagging = true;
|
|
720
|
+
try {
|
|
721
|
+
this.useHelper("ifaceBox");
|
|
722
|
+
return `__tfn(${this.genExpr(expr)}, ${JSON.stringify(expr._ifaceSrc.name)})`;
|
|
723
|
+
} finally {
|
|
724
|
+
expr._tfnTagging = false;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (isGenericComposite(expr._ifaceSrc) && !expr._gmapTagging) {
|
|
728
|
+
const tag = this._genericMapTag(expr._ifaceSrc);
|
|
729
|
+
if (tag) {
|
|
730
|
+
this.useHelper("ifaceBox");
|
|
731
|
+
expr._gmapTagging = true;
|
|
732
|
+
try {
|
|
733
|
+
// An array is copied before it is tagged: a copy made after
|
|
734
|
+
// `__ity` (`.map`) would drop the tag.
|
|
735
|
+
return `__ity(${this.genValueExpr(expr)}, ${tag})`;
|
|
736
|
+
} finally {
|
|
737
|
+
expr._gmapTagging = false;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
{
|
|
742
|
+
const tag = this._ityTag(expr);
|
|
610
743
|
if (tag) {
|
|
611
744
|
this.useHelper("ifaceBox");
|
|
612
745
|
expr._ityTagging = true;
|
|
613
746
|
try {
|
|
614
|
-
const ptr = expr._ifaceSrc
|
|
615
|
-
|
|
747
|
+
const ptr = this._dynTagFlags(expr._ifaceSrc);
|
|
748
|
+
// An array is copied before it is tagged: the copy is the value
|
|
749
|
+
// the interface holds, and it must keep the tag.
|
|
750
|
+
return `__ity(${this.genValueExpr(expr)}, ${JSON.stringify(tag)}${ptr})`;
|
|
616
751
|
} finally {
|
|
617
752
|
expr._ityTagging = false;
|
|
618
753
|
}
|
|
@@ -633,6 +768,10 @@ export const expressionGenMethods = {
|
|
|
633
768
|
expr._ifaceBoxing = false;
|
|
634
769
|
}
|
|
635
770
|
}
|
|
771
|
+
const f32 = this._genFloat32Const(expr);
|
|
772
|
+
if (f32 !== null) return f32;
|
|
773
|
+
const c64 = this._genComplex64Const(expr);
|
|
774
|
+
if (c64 !== null) return c64;
|
|
636
775
|
const folded = this._genFoldedConst(expr);
|
|
637
776
|
if (folded !== null) return folded;
|
|
638
777
|
switch (expr.kind) {
|
|
@@ -687,6 +826,31 @@ export const expressionGenMethods = {
|
|
|
687
826
|
return null;
|
|
688
827
|
},
|
|
689
828
|
|
|
829
|
+
// An untyped constant converted to float32 (`var c float32 = 0.01`) is
|
|
830
|
+
// emitted as its float32 value, or null when that changes nothing.
|
|
831
|
+
_genFloat32Const(expr) {
|
|
832
|
+
const val = expr._const;
|
|
833
|
+
if (!expr._f32Const || !val || (val.k !== "FLOAT" && val.k !== "INT"))
|
|
834
|
+
return null;
|
|
835
|
+
const n = Number(val.v);
|
|
836
|
+
if (!Number.isFinite(n)) return null;
|
|
837
|
+
const r = Math.fround(n);
|
|
838
|
+
if (r === n) return null;
|
|
839
|
+
return CONST_LITS.FLOAT(r);
|
|
840
|
+
},
|
|
841
|
+
|
|
842
|
+
// An untyped complex constant that takes type complex64
|
|
843
|
+
// (`var c complex64 = 1 + 0.01i`) is emitted as its folded value with
|
|
844
|
+
// float32 components, or null when it cannot be folded here.
|
|
845
|
+
_genComplex64Const(expr) {
|
|
846
|
+
if (!expr._c64Const) return null;
|
|
847
|
+
const c = complexConstParts(expr);
|
|
848
|
+
if (!c) return null;
|
|
849
|
+
const re = CONST_LITS.FLOAT(Math.fround(c[0]));
|
|
850
|
+
const im = CONST_LITS.FLOAT(Math.fround(c[1]));
|
|
851
|
+
return re === null || im === null ? null : `{ re: ${re}, im: ${im} }`;
|
|
852
|
+
},
|
|
853
|
+
|
|
690
854
|
// The literal for a folded constant expression (`1 << 40` →
|
|
691
855
|
// `1099511627776`), or null. Integers outside ±2⁵³ emit the nearest
|
|
692
856
|
// Number and a precision warning.
|
|
@@ -906,10 +1070,22 @@ export const expressionGenMethods = {
|
|
|
906
1070
|
}
|
|
907
1071
|
},
|
|
908
1072
|
|
|
1073
|
+
// The type tag `expr` takes on entering an interface, or null.
|
|
1074
|
+
_ityTag(expr) {
|
|
1075
|
+
if (!expr._ifaceSrc || expr._ityTagging || this._isNamedBoxKind(expr))
|
|
1076
|
+
return null;
|
|
1077
|
+
return this._dynTypeTag(expr._ifaceSrc);
|
|
1078
|
+
},
|
|
1079
|
+
|
|
909
1080
|
// Emits `expr` as a Go value: struct/array operands that alias existing storage are copied.
|
|
910
1081
|
genValueExpr(expr) {
|
|
1082
|
+
const tagged =
|
|
1083
|
+
Boolean(this._ityTag(expr)) ||
|
|
1084
|
+
(isGenericComposite(expr._ifaceSrc) &&
|
|
1085
|
+
!expr._gmapTagging &&
|
|
1086
|
+
Boolean(this._genericMapTag(expr._ifaceSrc)));
|
|
911
1087
|
const js = this.genExpr(expr);
|
|
912
|
-
if (expr._ifaceBox) return js;
|
|
1088
|
+
if (expr._ifaceBox || tagged) return js;
|
|
913
1089
|
if (
|
|
914
1090
|
!(
|
|
915
1091
|
this._aggregateBase(expr._type) || this._typeParamClone(expr._type, js)
|
|
@@ -935,12 +1111,10 @@ export const expressionGenMethods = {
|
|
|
935
1111
|
|
|
936
1112
|
_withFnCtx(body, fn) {
|
|
937
1113
|
const prev = this._fnCtx;
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
);
|
|
943
|
-
this._functionLabels = Array.from(labels);
|
|
1114
|
+
// A function starts with no JS labels in scope (see
|
|
1115
|
+
// `_emitIterTargetDispatch`).
|
|
1116
|
+
const prevLabels = this._jsLabels;
|
|
1117
|
+
this._jsLabels = [];
|
|
944
1118
|
this._fnCtx = {
|
|
945
1119
|
body,
|
|
946
1120
|
mut: new Map(),
|
|
@@ -954,7 +1128,7 @@ export const expressionGenMethods = {
|
|
|
954
1128
|
fn();
|
|
955
1129
|
} finally {
|
|
956
1130
|
this._fnCtx = prev;
|
|
957
|
-
this.
|
|
1131
|
+
this._jsLabels = prevLabels;
|
|
958
1132
|
}
|
|
959
1133
|
},
|
|
960
1134
|
|
|
@@ -1121,9 +1295,24 @@ export const expressionGenMethods = {
|
|
|
1121
1295
|
const namedExpr = this._namedMethodExprInfo(expr);
|
|
1122
1296
|
if (namedExpr) return `${namedExpr.cls}.$${expr.field}`;
|
|
1123
1297
|
const rt = expr._methodExprRecv;
|
|
1298
|
+
// `(*MyInt).M` for a pointer-receiver method of a named non-struct
|
|
1299
|
+
// type: the static method takes the pointer, nil included.
|
|
1300
|
+
if (rt?.kind === "pointer") {
|
|
1301
|
+
const info = this._namedBoxInfo(rt.base);
|
|
1302
|
+
const decl = rt.base?.methods?.get(expr.field)?._methodDecl;
|
|
1303
|
+
if (info?.methods.has(expr.field) && decl?.recvPointer)
|
|
1304
|
+
return `${info.cls}.$${expr.field}`;
|
|
1305
|
+
}
|
|
1124
1306
|
const protoCls = this._ptrMethodProtoClass(rt, expr.field);
|
|
1125
|
-
if (protoCls)
|
|
1126
|
-
|
|
1307
|
+
if (protoCls) {
|
|
1308
|
+
// A value-receiver method reads `*recv`, which panics on nil.
|
|
1309
|
+
let recv = "recv";
|
|
1310
|
+
if (rt.base.methods.get(expr.field)?._ptrRecv === false) {
|
|
1311
|
+
this.useHelper("nilCall");
|
|
1312
|
+
recv = "__nilcall(recv)";
|
|
1313
|
+
}
|
|
1314
|
+
return `((recv, ...args) => ${protoCls}.prototype.${expr.field}.call(${recv}, ...args))`;
|
|
1315
|
+
}
|
|
1127
1316
|
return `((recv, ...args) => recv.${expr.field}(...args))`;
|
|
1128
1317
|
}
|
|
1129
1318
|
const namedValue = this._genNamedMethodValue(expr);
|
|
@@ -1466,7 +1655,7 @@ export const expressionGenMethods = {
|
|
|
1466
1655
|
if (!expr._lvalue) return this._genMapIndexExpr(expr, base, idx);
|
|
1467
1656
|
return `${this._genMapWriteBase(base)}[${idx}]`;
|
|
1468
1657
|
}
|
|
1469
|
-
|
|
1658
|
+
idx = this._asIndexNumber(idx, expr.index?._type);
|
|
1470
1659
|
const checked = this._genStrictIndex(expr, base, idx);
|
|
1471
1660
|
if (checked) return checked;
|
|
1472
1661
|
if (this._isStringExprType(exprType) && !expr._lvalue)
|
|
@@ -1523,14 +1712,10 @@ export const expressionGenMethods = {
|
|
|
1523
1712
|
_genSliceExpr(expr) {
|
|
1524
1713
|
const base = this.genExpr(expr.expr);
|
|
1525
1714
|
const lo = expr.low
|
|
1526
|
-
? this.
|
|
1527
|
-
? `Number(${this.genExpr(expr.low)})`
|
|
1528
|
-
: this.genExpr(expr.low)
|
|
1715
|
+
? this._asIndexNumber(this.genExpr(expr.low), expr.low._type)
|
|
1529
1716
|
: "0";
|
|
1530
1717
|
const hi = expr.high
|
|
1531
|
-
? this.
|
|
1532
|
-
? `Number(${this.genExpr(expr.high)})`
|
|
1533
|
-
: this.genExpr(expr.high)
|
|
1718
|
+
? this._asIndexNumber(this.genExpr(expr.high), expr.high._type)
|
|
1534
1719
|
: "undefined";
|
|
1535
1720
|
if (this._isStringExprType(expr.expr._type))
|
|
1536
1721
|
return expr.high ? `${base}.slice(${lo}, ${hi})` : `${base}.slice(${lo})`;
|
|
@@ -1545,7 +1730,7 @@ export const expressionGenMethods = {
|
|
|
1545
1730
|
if (expr._selfReslice && !isTypedArraySlice(expr.expr._type))
|
|
1546
1731
|
return `__reslice(${base}, ${lo}, ${hi})`;
|
|
1547
1732
|
const max = expr.max
|
|
1548
|
-
? `, ${this.
|
|
1733
|
+
? `, ${this._asIndexNumber(this.genExpr(expr.max), expr.max._type)}`
|
|
1549
1734
|
: "";
|
|
1550
1735
|
return `__slice(${base}, ${lo}, ${hi}${max})`;
|
|
1551
1736
|
},
|
|
@@ -1667,6 +1852,10 @@ export const expressionGenMethods = {
|
|
|
1667
1852
|
[...named.underlying.methods.keys()],
|
|
1668
1853
|
this._unwrapIsMulti(named.underlying.methods.get("Unwrap")),
|
|
1669
1854
|
);
|
|
1855
|
+
if (this._isTaggedFuncType(named)) {
|
|
1856
|
+
this.useHelper("ifaceBox");
|
|
1857
|
+
return `__isty(${val}, ${JSON.stringify(name)}, "namedfunc")`;
|
|
1858
|
+
}
|
|
1670
1859
|
if (this._isNamedComposite(named)) {
|
|
1671
1860
|
this.useHelper("ifaceBox");
|
|
1672
1861
|
return `__isty(${val}, ${JSON.stringify(name)}, "named")`;
|
|
@@ -1727,7 +1916,10 @@ export const expressionGenMethods = {
|
|
|
1727
1916
|
this.useHelper("ifaceBox");
|
|
1728
1917
|
return `__unnil(${val})`;
|
|
1729
1918
|
}
|
|
1730
|
-
if (
|
|
1919
|
+
if (
|
|
1920
|
+
typeNode?._assertType?.kind === "slice" ||
|
|
1921
|
+
this._isNamedSliceTypeNode(typeNode)
|
|
1922
|
+
) {
|
|
1731
1923
|
// A nil slice in an interface is a tagged empty array.
|
|
1732
1924
|
this.useHelper("ifaceBox");
|
|
1733
1925
|
return `__unnil(${val})`;
|
|
@@ -1739,6 +1931,16 @@ export const expressionGenMethods = {
|
|
|
1739
1931
|
return factory ? `(${factory}?.u ? ${val}.__gfUnbox() : ${val})` : val;
|
|
1740
1932
|
},
|
|
1741
1933
|
|
|
1934
|
+
// A type name that denotes a defined slice type (`type S []int`).
|
|
1935
|
+
_isNamedSliceTypeNode(typeNode) {
|
|
1936
|
+
if (typeNode?.kind !== "TypeName") return false;
|
|
1937
|
+
const named = this.checker?.types.get(typeNode.name);
|
|
1938
|
+
return (
|
|
1939
|
+
this._isNamedComposite(named) &&
|
|
1940
|
+
this._underlyingOf(named)?.kind === "slice"
|
|
1941
|
+
);
|
|
1942
|
+
},
|
|
1943
|
+
|
|
1742
1944
|
_isStructPtrTypeNode(typeNode) {
|
|
1743
1945
|
const base = typeNode?.kind === "PointerType" ? typeNode.base : null;
|
|
1744
1946
|
const name =
|
|
@@ -1793,6 +1995,10 @@ export const expressionGenMethods = {
|
|
|
1793
1995
|
targetType?.kind === "named" ? targetType.underlying : targetType;
|
|
1794
1996
|
if (base?.kind === "map" && this._isNilLiteral(expr.args[0]))
|
|
1795
1997
|
return this._nilMapZero();
|
|
1998
|
+
if (this._isTaggedFuncType(targetType)) {
|
|
1999
|
+
this.useHelper("ifaceBox");
|
|
2000
|
+
return `__tfn(${arg}, ${JSON.stringify(targetType.name)})`;
|
|
2001
|
+
}
|
|
1796
2002
|
if (
|
|
1797
2003
|
targetType?.kind === "named" &&
|
|
1798
2004
|
base?.kind === "basic" &&
|
|
@@ -1856,7 +2062,13 @@ export const expressionGenMethods = {
|
|
|
1856
2062
|
// prototype so a nil `this` reaches it instead of a TypeError.
|
|
1857
2063
|
const recv = this.genExpr(expr.func.expr);
|
|
1858
2064
|
const rest = this._genCallArgs(expr);
|
|
1859
|
-
|
|
2065
|
+
const m = expr.func.field;
|
|
2066
|
+
const viaProto = (r) =>
|
|
2067
|
+
`${nilSafe}.prototype.${m}.call(${rest ? `${r}, ${rest}` : r})`;
|
|
2068
|
+
// A non-nil variable keeps the direct (faster) call.
|
|
2069
|
+
if (/^[\w$]+$/.test(recv))
|
|
2070
|
+
return `(${recv} != null ? ${recv}.${m}(${rest}) : ${viaProto(recv)})`;
|
|
2071
|
+
return viaProto(recv);
|
|
1860
2072
|
}
|
|
1861
2073
|
|
|
1862
2074
|
expr.func._callee = true;
|
|
@@ -1875,11 +2087,27 @@ export const expressionGenMethods = {
|
|
|
1875
2087
|
const factory = this._typeParamZeroFactory(expr.func.expr._type.name);
|
|
1876
2088
|
if (!factory) return null;
|
|
1877
2089
|
this.useHelper("generics");
|
|
1878
|
-
|
|
2090
|
+
let recv = `__tbox(${factory}, ${this.genExpr(expr.func.expr)})`;
|
|
2091
|
+
// `P(&v).M()` with `P interface{ *T; M() }`: a pointer to a boxed
|
|
2092
|
+
// type-parameter variable is the box, so reach the value it holds.
|
|
2093
|
+
if (this._hasPointerTypeSet(expr.func.expr._type))
|
|
2094
|
+
recv = `((__r) => typeof __r?.${expr.func.field} === "function" ? __r : __r?.value)(${recv})`;
|
|
1879
2095
|
const args = this._genCallArgs(expr);
|
|
1880
2096
|
return `${recv}.${expr.func.field}(${args})`;
|
|
1881
2097
|
},
|
|
1882
2098
|
|
|
2099
|
+
// Whether type parameter `tp` is constrained to a single `*T` type-set term.
|
|
2100
|
+
_hasPointerTypeSet(tp) {
|
|
2101
|
+
const c = tp.constraint;
|
|
2102
|
+
const iface = c?.kind === "named" ? c.underlying : c;
|
|
2103
|
+
const terms = iface?.unionConstraint?.terms;
|
|
2104
|
+
return (
|
|
2105
|
+
terms?.length === 1 &&
|
|
2106
|
+
(terms[0].resolved?.kind === "pointer" ||
|
|
2107
|
+
terms[0].type?.kind === "PointerType")
|
|
2108
|
+
);
|
|
2109
|
+
},
|
|
2110
|
+
|
|
1883
2111
|
// `(*S)(p).M()` where `p` points at another struct type: the JS object keeps
|
|
1884
2112
|
// its own class, so call S's prototype method on it.
|
|
1885
2113
|
_convertedPointerMethodCall(expr) {
|
|
@@ -1918,9 +2146,17 @@ export const expressionGenMethods = {
|
|
|
1918
2146
|
if (func?.kind !== "SelectorExpr" || func._isMethodExpr) return null;
|
|
1919
2147
|
const t = func.expr?._type;
|
|
1920
2148
|
if (t?.kind !== "pointer" || t.base?.kind !== "named") return null;
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
2149
|
+
// An instance of a generic struct (`Gen[int]`) shares its class.
|
|
2150
|
+
const cls = t.base.typeArgs?.length
|
|
2151
|
+
? this._structClassName(t.base)
|
|
2152
|
+
: t.base.name;
|
|
2153
|
+
if (!cls) return null;
|
|
2154
|
+
if (
|
|
2155
|
+
t.base.underlying?.kind === "struct" &&
|
|
2156
|
+
!this._promotedPath(func) &&
|
|
2157
|
+
this.nilSafeMethods?.has(`${cls}.${func.field}`)
|
|
2158
|
+
)
|
|
2159
|
+
return cls;
|
|
1924
2160
|
// A Go-source stdlib type (`Buffer$$bytes`) is declared outside this
|
|
1925
2161
|
// package: its method decl tells whether it tests the receiver for nil.
|
|
1926
2162
|
const decl = cls.includes("$$")
|
|
@@ -2066,9 +2302,7 @@ export const expressionGenMethods = {
|
|
|
2066
2302
|
// TypeError with a different message.
|
|
2067
2303
|
this.useHelper("nilCall");
|
|
2068
2304
|
const recv = this.genExpr(expr.func.expr);
|
|
2069
|
-
const args = expr
|
|
2070
|
-
.map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genExpr(a)))
|
|
2071
|
-
.join(", ");
|
|
2305
|
+
const args = this._genCallArgs(expr);
|
|
2072
2306
|
return `__nilcall(${recv}).${expr.func.field}(${args})`;
|
|
2073
2307
|
}
|
|
2074
2308
|
return undefined;
|
|
@@ -2223,8 +2457,7 @@ export const expressionGenMethods = {
|
|
|
2223
2457
|
const resolvedKind = madeType?.kind;
|
|
2224
2458
|
if (typeNode.kind === "SliceType" || resolvedKind === "slice") {
|
|
2225
2459
|
let n = expr.args[1] ? this.genExpr(expr.args[1]) : "0";
|
|
2226
|
-
if (expr.args[1]
|
|
2227
|
-
n = `Number(${n})`;
|
|
2460
|
+
if (expr.args[1]) n = this._asIndexNumber(n, expr.args[1]._type);
|
|
2228
2461
|
// A negative length or capacity panics with Go's message.
|
|
2229
2462
|
n = this._guardMakeSize(n, "__mkn");
|
|
2230
2463
|
const elemNode = typeNode.kind === "SliceType" ? typeNode.elem : null;
|
|
@@ -2240,8 +2473,8 @@ export const expressionGenMethods = {
|
|
|
2240
2473
|
// make([]T, n, cap) allocates cap elements and returns a view of
|
|
2241
2474
|
// the first n (`__mkslice`), so append within cap shares them.
|
|
2242
2475
|
let capArg = expr.args[2] ? this.genExpr(expr.args[2]) : null;
|
|
2243
|
-
if (capArg && expr.args[2]
|
|
2244
|
-
capArg =
|
|
2476
|
+
if (capArg && expr.args[2])
|
|
2477
|
+
capArg = this._asIndexNumber(capArg, expr.args[2]._type);
|
|
2245
2478
|
if (capArg) {
|
|
2246
2479
|
this.useHelper("slice");
|
|
2247
2480
|
capArg = this._guardMakeSize(capArg, "__mkc");
|
|
@@ -3025,22 +3258,47 @@ export const expressionGenMethods = {
|
|
|
3025
3258
|
const r = this._genComplexOperand(expr.right);
|
|
3026
3259
|
switch (expr.op) {
|
|
3027
3260
|
case "+":
|
|
3028
|
-
return `{ re: ${l}.re + ${r}.re, im: ${l}.im + ${r}.im }`;
|
|
3029
3261
|
case "-":
|
|
3030
|
-
return `{ re: ${l}.re - ${r}.re, im: ${l}.im - ${r}.im }`;
|
|
3031
3262
|
case "*":
|
|
3032
|
-
this.useHelper("cmul");
|
|
3033
|
-
return `__cmul(${l}, ${r})`;
|
|
3034
3263
|
case "/":
|
|
3035
|
-
this.
|
|
3036
|
-
return `__cdiv(${l}, ${r})`;
|
|
3264
|
+
return this._genComplexArith(expr.op, l, r, isComplex64(expr._type));
|
|
3037
3265
|
case "==":
|
|
3038
|
-
return
|
|
3266
|
+
return this._genComplexEquality("===", "&&", l, r);
|
|
3039
3267
|
case "!=":
|
|
3040
|
-
return
|
|
3268
|
+
return this._genComplexEquality("!==", "||", l, r);
|
|
3041
3269
|
}
|
|
3042
3270
|
},
|
|
3043
3271
|
|
|
3272
|
+
// `l == r` / `l != r` on complex operands: each component reads both
|
|
3273
|
+
// operands, so an operand that calls a function is bound once first.
|
|
3274
|
+
_genComplexEquality(cmp, join, l, r) {
|
|
3275
|
+
const pure = (js) => !js.includes("(");
|
|
3276
|
+
if (pure(l) && pure(r))
|
|
3277
|
+
return `(${l}.re ${cmp} ${r}.re ${join} ${l}.im ${cmp} ${r}.im)`;
|
|
3278
|
+
return `((__a, __b) => __a.re ${cmp} __b.re ${join} __a.im ${cmp} __b.im)(${l}, ${r})`;
|
|
3279
|
+
},
|
|
3280
|
+
|
|
3281
|
+
// `l op r` for complex operands. A complex64 result has float32
|
|
3282
|
+
// components, as in Go (where the arithmetic runs in float64 and the
|
|
3283
|
+
// result is narrowed).
|
|
3284
|
+
_genComplexArith(op, l, r, is64) {
|
|
3285
|
+
let re;
|
|
3286
|
+
let im;
|
|
3287
|
+
let call = null;
|
|
3288
|
+
if (op === "+" || op === "-") {
|
|
3289
|
+
re = `${l}.re ${op} ${r}.re`;
|
|
3290
|
+
im = `${l}.im ${op} ${r}.im`;
|
|
3291
|
+
} else {
|
|
3292
|
+
const helper = op === "*" ? "cmul" : "cdiv";
|
|
3293
|
+
this.useHelper(helper);
|
|
3294
|
+
call = `__${helper}(${l}, ${r})`;
|
|
3295
|
+
}
|
|
3296
|
+
if (!is64) return call ?? `{ re: ${re}, im: ${im} }`;
|
|
3297
|
+
if (call)
|
|
3298
|
+
return `((__z) => ({ re: Math.fround(__z.re), im: Math.fround(__z.im) }))(${call})`;
|
|
3299
|
+
return `{ re: Math.fround(${re}), im: Math.fround(${im}) }`;
|
|
3300
|
+
},
|
|
3301
|
+
|
|
3044
3302
|
_genBinaryExpr(expr) {
|
|
3045
3303
|
if (
|
|
3046
3304
|
isComplex(expr._type) ||
|
|
@@ -3507,6 +3765,19 @@ export const expressionGenMethods = {
|
|
|
3507
3765
|
return is64BitInt(t);
|
|
3508
3766
|
},
|
|
3509
3767
|
|
|
3768
|
+
// Whether a value of type `t` can be a BigInt at run time: a 64-bit
|
|
3769
|
+
// integer, or a type parameter whose type set holds one (`~int | ~int64`).
|
|
3770
|
+
_mayBeBigInt(t) {
|
|
3771
|
+
if (t?.kind !== "typeParam") return is64BitInt(t);
|
|
3772
|
+
const terms = this.checker?._constraintTerms(t.constraint, new Set());
|
|
3773
|
+
return terms?.some(is64BitInt) === true;
|
|
3774
|
+
},
|
|
3775
|
+
|
|
3776
|
+
// `code` as a JS number for an index, slice bound or `make` size.
|
|
3777
|
+
_asIndexNumber(code, t) {
|
|
3778
|
+
return this._mayBeBigInt(t) ? `Number(${code})` : code;
|
|
3779
|
+
},
|
|
3780
|
+
|
|
3510
3781
|
// float32 arithmetic rounds through Math.fround and arithmetic on a type
|
|
3511
3782
|
// parameter wraps for the instantiated type (`__tar`) in every package kind.
|
|
3512
3783
|
_isFloat32(t) {
|