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/src/lexer/lexer.js
CHANGED
package/src/link/stdlib.js
CHANGED
package/src/link/type-names.js
CHANGED
|
@@ -37,10 +37,12 @@ export function mangleCollidingTypes(programs, pkgKey, pkgName, owners) {
|
|
|
37
37
|
// backends key types by bare name. A local type whose name another type of the
|
|
38
38
|
// package also uses (a package-level type or a local of another function)
|
|
39
39
|
// is renamed to `Name__L<n>` for its scope; `%T` and diagnostics drop the
|
|
40
|
-
// suffix (see `displayTypeName`).
|
|
40
|
+
// suffix (see `displayTypeName`). `reserved` holds type names other packages
|
|
41
|
+
// own: a local type spelled like one is renamed too, so it cannot clash with
|
|
42
|
+
// the imported type in the flat module. Returns whether anything was renamed.
|
|
41
43
|
let localTypeCounter = 0;
|
|
42
44
|
|
|
43
|
-
export function renameLocalTypes(programs) {
|
|
45
|
+
export function renameLocalTypes(programs, reserved = new Set()) {
|
|
44
46
|
const counts = new Map();
|
|
45
47
|
const count = (node) => {
|
|
46
48
|
if (!node || typeof node !== "object") return;
|
|
@@ -50,34 +52,88 @@ export function renameLocalTypes(programs) {
|
|
|
50
52
|
forEachChild(node, count);
|
|
51
53
|
};
|
|
52
54
|
for (const p of programs) count(p.decls);
|
|
53
|
-
const dup = new Set(
|
|
54
|
-
|
|
55
|
+
const dup = new Set(
|
|
56
|
+
[...counts].filter(([k, n]) => n > 1 || reserved.has(k)).map(([k]) => k),
|
|
57
|
+
);
|
|
55
58
|
let changed = false;
|
|
56
59
|
// Inner scopes first, so an outer rename never touches a nested
|
|
57
|
-
// declaration that already has its own name.
|
|
58
|
-
|
|
60
|
+
// declaration that already has its own name. `visible` holds the value
|
|
61
|
+
// names declared so far in the enclosing scopes.
|
|
62
|
+
const scan = (node, visible) => {
|
|
59
63
|
if (!node || typeof node !== "object") return;
|
|
60
64
|
if (Array.isArray(node)) {
|
|
61
|
-
|
|
65
|
+
const seen = new Set(visible);
|
|
62
66
|
for (let i = 0; i < node.length; i++) {
|
|
63
67
|
const d = node[i];
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
scan(d, seen);
|
|
69
|
+
if (d?.kind === "TypeDecl") {
|
|
70
|
+
// A value of the same name would clash with the type in JS.
|
|
71
|
+
const stop = node.findIndex(
|
|
72
|
+
(s, j) => j > i && declaresValue(s, d.name),
|
|
73
|
+
);
|
|
74
|
+
if (dup.has(d.name) || seen.has(d.name) || stop >= 0) {
|
|
75
|
+
const renamed = new Map([
|
|
76
|
+
[d.name, `${d.name}__L${++localTypeCounter}`],
|
|
77
|
+
]);
|
|
78
|
+
const end = stop >= 0 ? stop : node.length - 1;
|
|
79
|
+
for (let j = i; j <= end; j++) {
|
|
80
|
+
// The shadowing declaration's own right-hand side and type
|
|
81
|
+
// are evaluated before its new value is in scope.
|
|
82
|
+
const target =
|
|
83
|
+
j === stop && node[j].kind === "DefineStmt"
|
|
84
|
+
? node[j].rhs
|
|
85
|
+
: node[j];
|
|
86
|
+
renameTypeRefs(target, renamed, new Set(), new Set());
|
|
87
|
+
}
|
|
88
|
+
changed = true;
|
|
89
|
+
}
|
|
90
|
+
} else if (d) for (const n of valueNames(d)) seen.add(n);
|
|
71
91
|
}
|
|
72
92
|
return;
|
|
73
93
|
}
|
|
74
|
-
|
|
94
|
+
const here = FUNC_KINDS.has(node.kind)
|
|
95
|
+
? new Set([...visible, ...declaredParams(node)])
|
|
96
|
+
: visible;
|
|
97
|
+
forEachChild(node, (c) => scan(c, here));
|
|
75
98
|
};
|
|
76
99
|
for (const p of programs)
|
|
77
|
-
for (const d of p.decls ?? [])
|
|
100
|
+
for (const d of p.decls ?? [])
|
|
101
|
+
if (d.kind !== "TypeDecl") scan(d, new Set());
|
|
78
102
|
return changed;
|
|
79
103
|
}
|
|
80
104
|
|
|
105
|
+
const VALUE_DECL_KINDS = new Set(["DefineStmt", "VarDecl", "ConstDecl"]);
|
|
106
|
+
|
|
107
|
+
// Names a statement declares as values in its own scope.
|
|
108
|
+
function valueNames(stmt) {
|
|
109
|
+
return VALUE_DECL_KINDS.has(stmt.kind) ? declaredNames(stmt) : [];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function declaresValue(stmt, name) {
|
|
113
|
+
return valueNames(stmt).includes(name);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function declaredParams(fn) {
|
|
117
|
+
const names = (fn.params ?? []).map((p) => p.name).filter(Boolean);
|
|
118
|
+
if (fn.recvName) names.push(fn.recvName);
|
|
119
|
+
return names;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// A package compiled later that declares a type this package only declares
|
|
123
|
+
// inside a function is renamed by `mangleCollidingTypes`, so `Pt` of one package
|
|
124
|
+
// and a local `Pt` of another never meet in the flat module.
|
|
125
|
+
export function registerLocalTypeOwners(programs, pkgKey, owners) {
|
|
126
|
+
if (!owners) return;
|
|
127
|
+
const visit = (node) => {
|
|
128
|
+
if (!node || typeof node !== "object") return;
|
|
129
|
+
if (Array.isArray(node)) return node.forEach(visit);
|
|
130
|
+
if (node.kind === "TypeDecl" && !owners.has(node.name))
|
|
131
|
+
owners.set(node.name, pkgKey);
|
|
132
|
+
forEachChild(node, visit);
|
|
133
|
+
};
|
|
134
|
+
for (const p of programs) visit(p.decls);
|
|
135
|
+
}
|
|
136
|
+
|
|
81
137
|
// `tparams` are type parameters in scope; `values` are names a function
|
|
82
138
|
// declares as parameters or locals, which shadow a same-spelled bare `Ident`.
|
|
83
139
|
function renameTypeRefs(node, renamed, tparams, values) {
|
package/src/lower/embedding.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
// src/lower/embedding.js
|
|
2
2
|
// Computes promoted methods and delegation stubs for embedded structs.
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
ambiguousPromotions,
|
|
6
|
+
embeddedFieldName,
|
|
7
|
+
} from "../typechecker/types.js";
|
|
5
8
|
|
|
6
9
|
// A method promoted from an embedded struct value but declared on its
|
|
7
10
|
// pointer receiver is outside the method set of the outer struct's value.
|
|
@@ -10,36 +13,60 @@ function isPtrOnlyPromotion(rawEmbed, embed, mName, mType) {
|
|
|
10
13
|
return Boolean((mType ?? embed.methods?.get(mName))?._ptrRecv);
|
|
11
14
|
}
|
|
12
15
|
|
|
16
|
+
// A pointer-receiver method reached through an embedded pointer, which may
|
|
17
|
+
// be nil.
|
|
18
|
+
function isNilablePtrRecv(rawEmbed, mType) {
|
|
19
|
+
return rawEmbed.kind === "pointer" && Boolean(mType?._ptrRecv);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// An embedded interface (named, literal or `error`) forwards each method of
|
|
23
|
+
// its method set to the interface value in the field.
|
|
24
|
+
function pushInterfaceStubs(stubs, declared, iface, fieldName) {
|
|
25
|
+
for (const mName of iface.methods?.keys() ?? []) {
|
|
26
|
+
if (declared.has(mName)) continue;
|
|
27
|
+
stubs.push({ methodName: mName, fieldName, ifaceEmbed: true });
|
|
28
|
+
declared.add(mName);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function pushValueEmbedStubs(stubs, declared, embed, embedName, fieldName) {
|
|
33
|
+
for (const [mName, mType] of embed.methods?.entries() ?? []) {
|
|
34
|
+
if (declared.has(mName) || mType?._ptrRecv) continue;
|
|
35
|
+
stubs.push({ methodName: mName, embedName, fieldName, valueEmbed: true });
|
|
36
|
+
declared.add(mName);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
13
40
|
export function computeEmbeddedStubs(name, methodDecls, checker) {
|
|
14
41
|
if (!checker) return [];
|
|
15
42
|
const resolvedType = checker.types.get(name)?.underlying;
|
|
16
43
|
if (resolvedType?.kind !== "struct" || !resolvedType._embeds) return [];
|
|
17
44
|
|
|
18
|
-
|
|
45
|
+
// A method two embeds declare at the same depth is in neither method set:
|
|
46
|
+
// no stub, so the value does not satisfy an interface that needs it.
|
|
47
|
+
const declared = new Set([
|
|
48
|
+
...methodDecls.map((m) => m.name),
|
|
49
|
+
...ambiguousPromotions(resolvedType),
|
|
50
|
+
]);
|
|
19
51
|
const stubs = [];
|
|
20
52
|
|
|
21
53
|
const embedded = (resolvedType._structFields ?? []).filter((f) => f.embedded);
|
|
22
54
|
for (const [i, rawEmbed] of resolvedType._embeds.entries()) {
|
|
23
55
|
const fieldName = embedded[i] ? embeddedFieldName(embedded[i]) : null;
|
|
24
56
|
const embed = rawEmbed.kind === "pointer" ? rawEmbed.base : rawEmbed;
|
|
57
|
+
const embedBase = embed.kind === "named" ? embed.underlying : embed;
|
|
58
|
+
if (embedBase?.kind === "interface") {
|
|
59
|
+
if (rawEmbed.kind !== "pointer" && fieldName)
|
|
60
|
+
pushInterfaceStubs(stubs, declared, embedBase, fieldName);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
25
63
|
const embedName = embed.kind === "named" ? embed.name : null;
|
|
26
64
|
if (!embedName) continue;
|
|
27
|
-
const embedBase = embed.kind === "named" ? embed.underlying : embed;
|
|
28
65
|
if (embedBase?.kind !== "struct" || !embedBase.methods) {
|
|
29
66
|
// A named non-struct type (`time.Duration`) promotes its value
|
|
30
67
|
// methods; the stub passes the embedded value as the receiver.
|
|
31
|
-
if (rawEmbed.kind
|
|
32
|
-
|
|
33
|
-
for (const [mName, mType] of embed.methods?.entries() ?? []) {
|
|
34
|
-
if (declared.has(mName) || mType?._ptrRecv) continue;
|
|
35
|
-
stubs.push({
|
|
36
|
-
methodName: mName,
|
|
37
|
-
embedName,
|
|
38
|
-
fieldName,
|
|
39
|
-
valueEmbed: true,
|
|
40
|
-
});
|
|
41
|
-
declared.add(mName);
|
|
42
|
-
}
|
|
68
|
+
if (rawEmbed.kind !== "pointer")
|
|
69
|
+
pushValueEmbedStubs(stubs, declared, embed, embedName, fieldName);
|
|
43
70
|
continue;
|
|
44
71
|
}
|
|
45
72
|
|
|
@@ -50,6 +77,7 @@ export function computeEmbeddedStubs(name, methodDecls, checker) {
|
|
|
50
77
|
embedName,
|
|
51
78
|
fieldName,
|
|
52
79
|
ptrOnly: isPtrOnlyPromotion(rawEmbed, embed, mName, mType),
|
|
80
|
+
nilPtrRecv: isNilablePtrRecv(rawEmbed, mType),
|
|
53
81
|
});
|
|
54
82
|
declared.add(mName); // Prevent duplicate stubs for diamond embeds
|
|
55
83
|
}
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
under,
|
|
35
35
|
walkNodes,
|
|
36
36
|
} from "./gen-types.js";
|
|
37
|
-
import { holdsFunc
|
|
37
|
+
import { holdsFunc } from "./json-codecs.js";
|
|
38
38
|
|
|
39
39
|
const FMT_PATH = "fmt";
|
|
40
40
|
const RT_PATH = "internal/fmtrt";
|
|
@@ -137,6 +137,12 @@ const returnsString = (m) => {
|
|
|
137
137
|
);
|
|
138
138
|
};
|
|
139
139
|
|
|
140
|
+
// A map, slice or array type: one that generic code boxes into an interface
|
|
141
|
+
// is not spelled in the package, so the formatter lookup must describe it.
|
|
142
|
+
function isMapSliceOrArray(t) {
|
|
143
|
+
return t?.kind === "map" || t?.kind === "slice" || t?.kind === "array";
|
|
144
|
+
}
|
|
145
|
+
|
|
140
146
|
class FmtGen extends TypeSpeller {
|
|
141
147
|
constructor(checker, pkgName, qualifiers, programs, formatted = false) {
|
|
142
148
|
super(pkgName, qualifiers, programs);
|
|
@@ -559,12 +565,17 @@ class FmtGen extends TypeSpeller {
|
|
|
559
565
|
if (via === "declared" && !this.topLevel.has(type.name)) continue;
|
|
560
566
|
add(type);
|
|
561
567
|
}
|
|
562
|
-
// Only instances of generic types (and pointers to them)
|
|
563
|
-
//
|
|
564
|
-
//
|
|
565
|
-
|
|
568
|
+
// Only instances of generic types (and pointers to them), and maps, slices and
|
|
569
|
+
// arrays a generic body boxes into an interface: they are the types a package
|
|
570
|
+
// never spells, and any other type here would take the place of the
|
|
571
|
+
// dispatch a type parameter value already has.
|
|
572
|
+
for (const { type, boxed } of this.genericBodyTypes()) {
|
|
566
573
|
const base = type.kind === "pointer" ? type.base : type;
|
|
567
|
-
if (
|
|
574
|
+
if (
|
|
575
|
+
(boxed && isMapSliceOrArray(type)) ||
|
|
576
|
+
(base.kind === "named" && base.typeArgs?.length > 0)
|
|
577
|
+
)
|
|
578
|
+
add(type);
|
|
568
579
|
}
|
|
569
580
|
return [...found.values()];
|
|
570
581
|
}
|
|
@@ -593,16 +604,28 @@ class FmtGen extends TypeSpeller {
|
|
|
593
604
|
while (queue.length > 0) {
|
|
594
605
|
const { decl, map } = queue.shift();
|
|
595
606
|
walkNodes(decl.body, (node) => {
|
|
596
|
-
|
|
597
|
-
// package already.
|
|
598
|
-
if (node._type && mentionsTypeParam(node._type))
|
|
599
|
-
out.push(defaultType(this.checker.substituteType(node._type, map)));
|
|
607
|
+
out.push(...this.bodyNodeTypes(node, map));
|
|
600
608
|
request(node, map);
|
|
601
609
|
});
|
|
602
610
|
}
|
|
603
611
|
return out;
|
|
604
612
|
}
|
|
605
613
|
|
|
614
|
+
// The types a node of an instantiated generic body adds: the node's own
|
|
615
|
+
// type once substituted (a type without a type parameter is found by the
|
|
616
|
+
// walk of the package already), and a map it boxes into an interface,
|
|
617
|
+
// which is a type of its own (a type parameter's value keeps its dispatch).
|
|
618
|
+
bodyNodeTypes(node, map) {
|
|
619
|
+
const sub = (t) => defaultType(this.checker.substituteType(t, map));
|
|
620
|
+
const out = [];
|
|
621
|
+
if (node._type && mentionsTypeParam(node._type))
|
|
622
|
+
out.push({ type: sub(node._type) });
|
|
623
|
+
const src = node._ifaceSrc;
|
|
624
|
+
if (isMapSliceOrArray(src) && mentionsTypeParam(src))
|
|
625
|
+
out.push({ type: sub(src), boxed: true });
|
|
626
|
+
return out;
|
|
627
|
+
}
|
|
628
|
+
|
|
606
629
|
// The instantiation of a generic function that `call` makes, with its
|
|
607
630
|
// type arguments substituted through `map` (the instantiation of the
|
|
608
631
|
// function that contains the call), or null for any other node or one
|
|
@@ -626,11 +649,12 @@ class FmtGen extends TypeSpeller {
|
|
|
626
649
|
if (isInterface(t) || under(t)?.kind === "interface") return false;
|
|
627
650
|
if (t.kind === "basic") return false;
|
|
628
651
|
if (t.kind === "func") return true;
|
|
629
|
-
|
|
630
|
-
//
|
|
631
|
-
//
|
|
652
|
+
// An array, map or slice is boxed by its Go type, so one with small
|
|
653
|
+
// integer or boolean keys or elements no longer shadows its look-alikes in
|
|
654
|
+
// the type switch. One holding funcs is described too, so `%T` names it.
|
|
632
655
|
if (t.kind === "array" || t.kind === "map" || t.kind === "slice")
|
|
633
|
-
return
|
|
656
|
+
return true;
|
|
657
|
+
if (holdsFunc(t)) return false;
|
|
634
658
|
return true;
|
|
635
659
|
}
|
|
636
660
|
|
package/src/lower/functions.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/lower/functions.js
|
|
2
2
|
// Analyzes function signatures, named returns and defer structures.
|
|
3
3
|
|
|
4
|
-
import { forEachChild, someChild } from "./walk.js";
|
|
4
|
+
import { forEachChild, hasCallOrReceive, someChild } from "./walk.js";
|
|
5
5
|
|
|
6
6
|
export function extractNamedReturns(decl) {
|
|
7
7
|
const named = decl?.returnType?._namedReturns;
|
|
@@ -146,6 +146,80 @@ function deferCalleeMayRecover(funcExpr, ctx) {
|
|
|
146
146
|
return true;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
// A constant argument (`2.5`, `"a"`, `nil`, `k` for a constant `k`, `-1`)
|
|
150
|
+
// has no value to capture at the `defer` statement, so it stays in the
|
|
151
|
+
// deferred call, where it takes the callee's parameter type exactly as in a
|
|
152
|
+
// direct call. Hoisting it into a temporary would default its type (`float64`,
|
|
153
|
+
// `int`) and reject a defined-type, interface, pointer or generic parameter.
|
|
154
|
+
function isDeferConstant(arg) {
|
|
155
|
+
if (arg?.kind === "BasicLit" && arg.litKind === "NIL") return true;
|
|
156
|
+
if (arg?._type?.kind !== "untyped") return false;
|
|
157
|
+
if (arg._const) return true;
|
|
158
|
+
switch (arg.kind) {
|
|
159
|
+
case "BasicLit":
|
|
160
|
+
case "ImagLit":
|
|
161
|
+
return true;
|
|
162
|
+
case "ParenExpr":
|
|
163
|
+
return isDeferConstant(arg.expr);
|
|
164
|
+
case "UnaryExpr":
|
|
165
|
+
return isDeferConstant(arg.operand);
|
|
166
|
+
case "BinaryExpr":
|
|
167
|
+
return isDeferConstant(arg.left) && isDeferConstant(arg.right);
|
|
168
|
+
default:
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// A package-level basic or defined type a temporary can be declared with by
|
|
174
|
+
// its name; other parameter types (qualified, generic, composite) are not
|
|
175
|
+
// hoisted with a declared type.
|
|
176
|
+
function isNamedScalarParam(t) {
|
|
177
|
+
if (t?.kind === "basic")
|
|
178
|
+
return t.name !== "any" && /^[A-Za-z_]\w*$/.test(t.name ?? "");
|
|
179
|
+
return (
|
|
180
|
+
t?.kind === "named" &&
|
|
181
|
+
t.underlying?.kind === "basic" &&
|
|
182
|
+
!t.typeArgs?.length &&
|
|
183
|
+
/^[A-Za-z_]\w*$/.test(t.name ?? "")
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// A non-constant untyped expression (`1 << n`) the typechecker already gave
|
|
188
|
+
// its parameter's type: it is still untyped when the lowered code is checked
|
|
189
|
+
// again, so its temporary needs that type declared.
|
|
190
|
+
function isRetypedUntyped(e) {
|
|
191
|
+
if (!e || e._const) return false;
|
|
192
|
+
switch (e.kind) {
|
|
193
|
+
case "ParenExpr":
|
|
194
|
+
return isRetypedUntyped(e.expr);
|
|
195
|
+
case "UnaryExpr":
|
|
196
|
+
return isRetypedUntyped(e.operand);
|
|
197
|
+
case "BinaryExpr":
|
|
198
|
+
if (e.op === "<<" || e.op === ">>") return Boolean(e.left?._const);
|
|
199
|
+
return isRetypedUntyped(e.left) || isRetypedUntyped(e.right);
|
|
200
|
+
default:
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// The type of the temporary that holds deferred argument `a`: an untyped
|
|
206
|
+
// non-constant value (a shift, a comparison) takes its basic or defined
|
|
207
|
+
// scalar parameter's type, as it does in a direct call. A variadic call's
|
|
208
|
+
// trailing arguments take the element type.
|
|
209
|
+
function deferTempType(funcType, a, arg) {
|
|
210
|
+
const argType = arg._type;
|
|
211
|
+
if (argType?.kind !== "untyped" && !isRetypedUntyped(arg)) return argType;
|
|
212
|
+
const params = funcType?.params;
|
|
213
|
+
let param = params?.[a];
|
|
214
|
+
if (funcType?.variadic && a >= params.length - 1) {
|
|
215
|
+
// The last parameter's type is the element type (a slice only when
|
|
216
|
+
// a table spells it that way).
|
|
217
|
+
param = params[params.length - 1];
|
|
218
|
+
if (!isNamedScalarParam(param)) param = param?.elem;
|
|
219
|
+
}
|
|
220
|
+
return isNamedScalarParam(param) ? param : argType;
|
|
221
|
+
}
|
|
222
|
+
|
|
149
223
|
function normalizeDeferStmt(stmt, parentList = null, index = -1, ctx = null) {
|
|
150
224
|
if (stmt.call?.kind !== "CallExpr") return 0;
|
|
151
225
|
const call = stmt.call;
|
|
@@ -245,19 +319,40 @@ function normalizeDeferStmt(stmt, parentList = null, index = -1, ctx = null) {
|
|
|
245
319
|
lhs: tupleLhs,
|
|
246
320
|
rhs: [arg],
|
|
247
321
|
});
|
|
322
|
+
} else if (isDeferConstant(arg)) {
|
|
323
|
+
tempIdents.push(arg);
|
|
248
324
|
} else {
|
|
249
325
|
defargCounter++;
|
|
250
326
|
const tempName = `__defarg$${defargCounter}`;
|
|
327
|
+
const tempType = deferTempType(call.func?._type, a, arg);
|
|
251
328
|
tempIdents.push({
|
|
252
329
|
kind: "Ident",
|
|
253
330
|
name: tempName,
|
|
254
|
-
_type:
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
kind: "DefineStmt",
|
|
258
|
-
lhs: [{ kind: "Ident", name: tempName, _type: arg._type }],
|
|
259
|
-
rhs: [arg],
|
|
331
|
+
_type: tempType,
|
|
332
|
+
// A spread argument stays one: the call gets the slice itself.
|
|
333
|
+
...(arg._spread && { _spread: true }),
|
|
260
334
|
});
|
|
335
|
+
tempStmts.push(
|
|
336
|
+
tempType === arg._type &&
|
|
337
|
+
!(isRetypedUntyped(arg) && isNamedScalarParam(tempType))
|
|
338
|
+
? {
|
|
339
|
+
kind: "DefineStmt",
|
|
340
|
+
lhs: [{ kind: "Ident", name: tempName, _type: tempType }],
|
|
341
|
+
rhs: [arg],
|
|
342
|
+
}
|
|
343
|
+
: {
|
|
344
|
+
// Declared with the parameter's type so the lowered code
|
|
345
|
+
// type-checks again with the constant converted.
|
|
346
|
+
kind: "VarDecl",
|
|
347
|
+
decls: [
|
|
348
|
+
{
|
|
349
|
+
names: [tempName],
|
|
350
|
+
type: { kind: "TypeName", name: tempType.name },
|
|
351
|
+
value: [arg],
|
|
352
|
+
},
|
|
353
|
+
],
|
|
354
|
+
},
|
|
355
|
+
);
|
|
261
356
|
}
|
|
262
357
|
}
|
|
263
358
|
}
|
|
@@ -302,3 +397,124 @@ function normalizeDeferStmt(stmt, parentList = null, index = -1, ctx = null) {
|
|
|
302
397
|
|
|
303
398
|
return tempStmts.length;
|
|
304
399
|
}
|
|
400
|
+
|
|
401
|
+
let fwdCounter = 0;
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Splits a multi-value call forwarded into interfaces into temps:
|
|
405
|
+
* `return f()` where some result is an interface becomes
|
|
406
|
+
* `__fwd$1, __fwd$2 := f(); return __fwd$1, __fwd$2`, and `g(f())` where
|
|
407
|
+
* some parameter is an interface becomes `…; g(__fwd$1, __fwd$2)`. The
|
|
408
|
+
* typechecker records per-element interface-box marks in `call._fwdIface`;
|
|
409
|
+
* each temp takes its element's marks, so both backends box it through their
|
|
410
|
+
* ordinary per-value path. Forwards with no interface element are left alone.
|
|
411
|
+
*
|
|
412
|
+
* `g(f())` is split only as the operand of a statement (expression
|
|
413
|
+
* statement, single-value `:=`/`=`/`var`/`return`) whose callee evaluates
|
|
414
|
+
* no call, so hoisting `f()` keeps Go's evaluation order.
|
|
415
|
+
*/
|
|
416
|
+
export function splitForwardedTuples(programOrPrograms) {
|
|
417
|
+
fwdCounter = 0;
|
|
418
|
+
const programs = Array.isArray(programOrPrograms)
|
|
419
|
+
? programOrPrograms
|
|
420
|
+
: [programOrPrograms];
|
|
421
|
+
for (const program of programs)
|
|
422
|
+
for (const d of program?.decls ?? []) splitForwardsIn(d);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function splitForwardsIn(node) {
|
|
426
|
+
if (!node || typeof node !== "object") return;
|
|
427
|
+
if (Array.isArray(node)) {
|
|
428
|
+
for (let i = 0; i < node.length; i++) {
|
|
429
|
+
const item = node[i];
|
|
430
|
+
splitForwardsIn(item);
|
|
431
|
+
const defines = splitForwardsInStmt(item);
|
|
432
|
+
node.splice(i, 0, ...defines);
|
|
433
|
+
i += defines.length;
|
|
434
|
+
}
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
forEachChild(node, splitForwardsIn);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// The `:=` statements to insert before `stmt` (in order) after rewriting its
|
|
441
|
+
// forwarded calls to read the temps.
|
|
442
|
+
function splitForwardsInStmt(stmt) {
|
|
443
|
+
const defines = [];
|
|
444
|
+
const operand = statementOperand(stmt);
|
|
445
|
+
if (
|
|
446
|
+
operand?.kind === "CallExpr" &&
|
|
447
|
+
operand._multiForward &&
|
|
448
|
+
operand.args?.length === 1 &&
|
|
449
|
+
!hasCallOrReceive(operand.func)
|
|
450
|
+
) {
|
|
451
|
+
const split = forwardTemps(operand.args[0]);
|
|
452
|
+
if (split) {
|
|
453
|
+
operand.args = split.operands;
|
|
454
|
+
operand._multiForward = false;
|
|
455
|
+
defines.push(split.define);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (stmt?.kind === "ReturnStmt") {
|
|
459
|
+
const define = splitForwardedReturn(stmt);
|
|
460
|
+
if (define) defines.push(define);
|
|
461
|
+
}
|
|
462
|
+
return defines;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// The single expression a statement evaluates last, or null.
|
|
466
|
+
function statementOperand(stmt) {
|
|
467
|
+
switch (stmt?.kind) {
|
|
468
|
+
case "ExprStmt":
|
|
469
|
+
return stmt.expr;
|
|
470
|
+
case "DefineStmt":
|
|
471
|
+
case "AssignStmt":
|
|
472
|
+
return stmt.rhs?.length === 1 &&
|
|
473
|
+
stmt.lhs?.every((e) => e.kind === "Ident")
|
|
474
|
+
? stmt.rhs[0]
|
|
475
|
+
: null;
|
|
476
|
+
case "ReturnStmt":
|
|
477
|
+
return stmt.values?.length === 1 ? stmt.values[0] : null;
|
|
478
|
+
case "VarDecl":
|
|
479
|
+
return stmt.decls?.length === 1 && stmt.decls[0].value?.length === 1
|
|
480
|
+
? stmt.decls[0].value[0]
|
|
481
|
+
: null;
|
|
482
|
+
default:
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Splits one forwarded call into temps: returns the `:=` statement defining
|
|
488
|
+
// them and the operand idents (carrying each element's interface-box marks),
|
|
489
|
+
// or null when the call needs no split.
|
|
490
|
+
function forwardTemps(call) {
|
|
491
|
+
const holders = call?._fwdIface;
|
|
492
|
+
const types = call?._type?.types;
|
|
493
|
+
if (!holders || !Array.isArray(types) || types.length !== holders.length)
|
|
494
|
+
return null;
|
|
495
|
+
const names = types.map(() => `__fwd$${++fwdCounter}`);
|
|
496
|
+
const define = {
|
|
497
|
+
kind: "DefineStmt",
|
|
498
|
+
lhs: names.map((name, i) => ({ kind: "Ident", name, _type: types[i] })),
|
|
499
|
+
rhs: [call],
|
|
500
|
+
};
|
|
501
|
+
// The call is no longer a forward; a cached AST must not split it again.
|
|
502
|
+
call._fwdIface = undefined;
|
|
503
|
+
const operands = names.map((name, i) => ({
|
|
504
|
+
kind: "Ident",
|
|
505
|
+
name,
|
|
506
|
+
_type: types[i],
|
|
507
|
+
...holders[i],
|
|
508
|
+
}));
|
|
509
|
+
return { define, operands };
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function splitForwardedReturn(stmt) {
|
|
513
|
+
if (stmt.values?.length !== 1) return null;
|
|
514
|
+
const split = forwardTemps(stmt.values[0]);
|
|
515
|
+
if (!split) return null;
|
|
516
|
+
stmt.values = split.operands;
|
|
517
|
+
if (Array.isArray(stmt._types))
|
|
518
|
+
stmt._types = split.operands.map((o) => o._type);
|
|
519
|
+
return split.define;
|
|
520
|
+
}
|
package/src/lower/gen-types.js
CHANGED
|
@@ -59,8 +59,16 @@ export function walkNodes(root, fn) {
|
|
|
59
59
|
}
|
|
60
60
|
fn(root);
|
|
61
61
|
forEachChild(root, (c) => walkNodes(c, fn));
|
|
62
|
+
// templ bodies keep their Go expressions in non-enumerable properties
|
|
63
|
+
// (see parser/templ.js), which forEachChild does not visit.
|
|
64
|
+
for (const name of TEMPL_EXPR_PROPS) {
|
|
65
|
+
const d = Object.getOwnPropertyDescriptor(root, name);
|
|
66
|
+
if (d && !d.enumerable) walkNodes(d.value, fn);
|
|
67
|
+
}
|
|
62
68
|
}
|
|
63
69
|
|
|
70
|
+
const TEMPL_EXPR_PROPS = ["expr", "exprs", "forStmt"];
|
|
71
|
+
|
|
64
72
|
// True when `t` mentions a type parameter (a generic declaration, or a type
|
|
65
73
|
// built from one inside a generic function).
|
|
66
74
|
export function mentionsTypeParam(t, seen = new Set()) {
|
|
@@ -126,8 +134,9 @@ export function importsFor(qualifiers) {
|
|
|
126
134
|
|
|
127
135
|
// Every type a closed-world generator may need to describe: the entries
|
|
128
136
|
// { type, node, via } for the type arguments of generic calls (`typeArg`),
|
|
129
|
-
// the type of every expression (`expr`)
|
|
130
|
-
//
|
|
137
|
+
// the type of every expression (`expr`), the static type of every value
|
|
138
|
+
// converted to an interface (`boxed`) and the package's declared named types
|
|
139
|
+
// (`declared`, node null).
|
|
131
140
|
export function collectProgramTypes(programs, checker) {
|
|
132
141
|
const found = [];
|
|
133
142
|
for (const p of programs)
|
|
@@ -136,6 +145,14 @@ export function collectProgramTypes(programs, checker) {
|
|
|
136
145
|
found.push({ type: defaultType(a), node, via: "typeArg" });
|
|
137
146
|
if (node._type)
|
|
138
147
|
found.push({ type: defaultType(node._type), node, via: "expr" });
|
|
148
|
+
if (node._ifaceSrc)
|
|
149
|
+
found.push({ type: defaultType(node._ifaceSrc), node, via: "boxed" });
|
|
150
|
+
// A multi-value call forwarded into interfaces (`return f()`,
|
|
151
|
+
// `g(f())`) keeps one mark holder per element until lowering
|
|
152
|
+
// splits it.
|
|
153
|
+
for (const h of node._fwdIface ?? [])
|
|
154
|
+
if (h?._ifaceSrc)
|
|
155
|
+
found.push({ type: defaultType(h._ifaceSrc), node, via: "boxed" });
|
|
139
156
|
});
|
|
140
157
|
for (const [name, t] of checker.types) {
|
|
141
158
|
if (t?.kind !== "named" || t.name !== name) continue;
|
|
@@ -253,6 +270,8 @@ export class TypeSpeller {
|
|
|
253
270
|
const byKind = {
|
|
254
271
|
basic: () => t.name !== "void" && t.name !== "nil",
|
|
255
272
|
named: () => this.spellableNamed(t, ok, seen),
|
|
273
|
+
// A JS-interop (.d.ts) type has no Go source to write.
|
|
274
|
+
namespace: () => false,
|
|
256
275
|
pointer: () => ok(t.base),
|
|
257
276
|
slice: () => ok(t.elem),
|
|
258
277
|
array: () => ok(t.elem),
|
package/src/lower/index.js
CHANGED
|
@@ -16,7 +16,12 @@ import { scanAddressTaken } from "./boxing.js";
|
|
|
16
16
|
import { analyzeCaptures } from "./captures.js";
|
|
17
17
|
import { computeEmbeddedStubs } from "./embedding.js";
|
|
18
18
|
import { paramEscapes } from "./escape.js";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
extractNamedReturns,
|
|
21
|
+
hasDefer,
|
|
22
|
+
normalizeDefers,
|
|
23
|
+
splitForwardedTuples,
|
|
24
|
+
} from "./functions.js";
|
|
20
25
|
import { computeGlobalAnalysis } from "./globals.js";
|
|
21
26
|
import { OwnershipContext, takesElementAddress } from "./ownership.js";
|
|
22
27
|
|
|
@@ -83,6 +88,7 @@ export function lower(programOrPrograms, checker = null) {
|
|
|
83
88
|
? programOrPrograms
|
|
84
89
|
: [programOrPrograms];
|
|
85
90
|
|
|
91
|
+
splitForwardedTuples(programs);
|
|
86
92
|
normalizeDefers(programs);
|
|
87
93
|
|
|
88
94
|
res.elemAddrTaken = programs.some((p) => takesElementAddress(p?.decls));
|