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,24 +1,32 @@
|
|
|
1
1
|
// TypeChecker expression-checking methods — installed as a mixin on TypeChecker.prototype.
|
|
2
2
|
|
|
3
|
+
import { EXCLUDED_STDLIB_MEMBERS } from "./stdlib/excluded.js";
|
|
3
4
|
import { sharedInfo } from "./stdlib/shared.js";
|
|
4
5
|
import {
|
|
5
6
|
ANY,
|
|
6
7
|
BOOL,
|
|
7
8
|
BROWSER_GLOBALS,
|
|
9
|
+
COMPLEX64,
|
|
8
10
|
COMPLEX128,
|
|
11
|
+
compositeLitMaxIndex,
|
|
12
|
+
displayTypeName,
|
|
9
13
|
ERROR,
|
|
14
|
+
FLOAT32,
|
|
10
15
|
FLOAT64,
|
|
11
16
|
INT,
|
|
12
17
|
isAny,
|
|
13
18
|
isArray,
|
|
14
19
|
isBool,
|
|
15
20
|
isComplex,
|
|
21
|
+
isComplex64,
|
|
16
22
|
isComplexOrNumeric,
|
|
23
|
+
isInteger,
|
|
17
24
|
isNumeric,
|
|
18
25
|
isString,
|
|
19
26
|
NIL,
|
|
20
27
|
RUNE,
|
|
21
28
|
Scope,
|
|
29
|
+
SIZED_INT_NAMES,
|
|
22
30
|
STRING,
|
|
23
31
|
TAINTED_ANY,
|
|
24
32
|
typeStr,
|
|
@@ -32,6 +40,14 @@ import {
|
|
|
32
40
|
|
|
33
41
|
/** @typedef {import('./index.js').TypeChecker} TypeChecker */
|
|
34
42
|
|
|
43
|
+
// min/max: the type of the first typed operand; with only untyped constants,
|
|
44
|
+
// an untyped float if any operand is one (Go's "largest kind" rule).
|
|
45
|
+
function minMaxType(at) {
|
|
46
|
+
const typed = at.find((t) => t && t.kind !== "untyped");
|
|
47
|
+
if (typed) return typed;
|
|
48
|
+
return at.find((t) => t?.base === "float64") ?? at[0] ?? ANY;
|
|
49
|
+
}
|
|
50
|
+
|
|
35
51
|
// Dispatch table for builtin function type checking.
|
|
36
52
|
// Each value is (self, name, expr, argTypes, scope) => returnType.
|
|
37
53
|
const BUILTIN_CHECK = {
|
|
@@ -53,6 +69,8 @@ const BUILTIN_CHECK = {
|
|
|
53
69
|
const sb = s.resolveType(st?.kind === "named" ? st.underlying : st);
|
|
54
70
|
if (sb?.kind === "slice") {
|
|
55
71
|
for (let i = 1; i < e.args.length; i++) {
|
|
72
|
+
if (e.args[i]._spread)
|
|
73
|
+
s._rejectSeqAsSlice(at[i], e.args[i], "a spread argument");
|
|
56
74
|
if (!e.args[i]._spread && at[i])
|
|
57
75
|
s._markIfaceBox(
|
|
58
76
|
s.resolveType(sb.elem),
|
|
@@ -64,7 +82,11 @@ const BUILTIN_CHECK = {
|
|
|
64
82
|
return st;
|
|
65
83
|
},
|
|
66
84
|
copy: (s, _n, e, at) => s._checkBuiltinCopy(e, at),
|
|
67
|
-
delete: () =>
|
|
85
|
+
delete: (s, _n, e, at) => {
|
|
86
|
+
const mt = s._deepUnderlying(at[0]);
|
|
87
|
+
if (mt?.kind === "map" && e.args[1]) s._markIfaceKey(mt, e.args[1], at[1]);
|
|
88
|
+
return VOID;
|
|
89
|
+
},
|
|
68
90
|
make: (s, _n, e, _at, sc) => {
|
|
69
91
|
if (e.args.length < 1) return ANY;
|
|
70
92
|
const ta = e.args[0];
|
|
@@ -73,16 +95,17 @@ const BUILTIN_CHECK = {
|
|
|
73
95
|
},
|
|
74
96
|
new: (s, _n, e, _at, sc) => {
|
|
75
97
|
if (e.args.length < 1) return ANY;
|
|
76
|
-
const
|
|
98
|
+
const ta = e.args[0];
|
|
99
|
+
const inner = s.resolveTypeNode(ta.kind === "TypeExpr" ? ta.type : ta, sc);
|
|
77
100
|
return { kind: "pointer", base: inner };
|
|
78
101
|
},
|
|
79
|
-
print: () =>
|
|
80
|
-
println: () =>
|
|
102
|
+
print: (s, n, e, at) => s._checkPrintArgs(n, e, at),
|
|
103
|
+
println: (s, n, e, at) => s._checkPrintArgs(n, e, at),
|
|
81
104
|
panic: () => VOID,
|
|
82
105
|
recover: () => ANY,
|
|
83
106
|
error: () => ERROR,
|
|
84
|
-
min: (_s, _n, _e, at) => at
|
|
85
|
-
max: (_s, _n, _e, at) => at
|
|
107
|
+
min: (_s, _n, _e, at) => minMaxType(at),
|
|
108
|
+
max: (_s, _n, _e, at) => minMaxType(at),
|
|
86
109
|
clear: () => VOID,
|
|
87
110
|
complex: (s, _n, e, at) => s._checkBuiltinComplex(e, at),
|
|
88
111
|
real: (s, n, e, at) => s._checkBuiltinRealImag(n, e, at),
|
|
@@ -109,6 +132,7 @@ export const expressionCheckMethods = {
|
|
|
109
132
|
checkExpr(expr, scope) {
|
|
110
133
|
const t = this._checkExpr(expr, scope);
|
|
111
134
|
expr._type = t;
|
|
135
|
+
this._noteConst(expr, scope);
|
|
112
136
|
return t;
|
|
113
137
|
},
|
|
114
138
|
|
|
@@ -134,11 +158,28 @@ export const expressionCheckMethods = {
|
|
|
134
158
|
case "Ident": {
|
|
135
159
|
const t = scope.lookup(expr.name);
|
|
136
160
|
if (!t) return this.err(`Undefined: '${expr.name}'`, expr);
|
|
137
|
-
if (this.types.has(expr.name)
|
|
161
|
+
if (this.types.has(expr.name) || t.kind === "typeParam")
|
|
162
|
+
expr._isTypeRef = true;
|
|
163
|
+
// A Go declaration that shadows a browser global: JS output renames
|
|
164
|
+
// it so generated code keeps reaching the real global. Package
|
|
165
|
+
// namespaces (`import console "./util"`) are never renamed: JS
|
|
166
|
+
// selects their members directly (bundledPackages).
|
|
167
|
+
if (BROWSER_GLOBALS.has(expr.name) && t.kind !== "namespace") {
|
|
168
|
+
const owner = scope.lookupScope(expr.name);
|
|
169
|
+
if (
|
|
170
|
+
owner !== this.universe &&
|
|
171
|
+
!(owner === this.globals && this._externalNames.has(expr.name))
|
|
172
|
+
)
|
|
173
|
+
expr._shadowsGlobal = true;
|
|
174
|
+
}
|
|
175
|
+
// Go-source stdlib loading: record package-scope references so the
|
|
176
|
+
// loader can mangle them (see compiler.js loadStdlibPackage).
|
|
177
|
+
if (this._pkgRefs && scope.lookupScope(expr.name) === this.globals)
|
|
178
|
+
this._pkgRefs.add(`${this._currentFile}:${expr._line}:${expr._col}`);
|
|
138
179
|
if (
|
|
139
180
|
(this.target === "wasm" || this.target === "both") &&
|
|
140
181
|
BROWSER_GLOBALS.has(expr.name) &&
|
|
141
|
-
scope.
|
|
182
|
+
scope.lookupScope(expr.name) === this.universe
|
|
142
183
|
) {
|
|
143
184
|
this.recordBlocker("browser global", expr.name);
|
|
144
185
|
this.err(
|
|
@@ -163,6 +204,7 @@ export const expressionCheckMethods = {
|
|
|
163
204
|
if (lt?._tainted || rt?._tainted) return TAINTED_ANY;
|
|
164
205
|
if (isAny(lt) || isAny(rt))
|
|
165
206
|
return this.binaryResultType(expr.op, ANY, ANY, expr);
|
|
207
|
+
this._checkConstOperands(expr.op, lt, rt, expr);
|
|
166
208
|
return this.binaryResultType(expr.op, lt, rt, expr);
|
|
167
209
|
}
|
|
168
210
|
case "AwaitExpr": {
|
|
@@ -177,6 +219,11 @@ export const expressionCheckMethods = {
|
|
|
177
219
|
}
|
|
178
220
|
case "InstantiationExpr":
|
|
179
221
|
return this._checkInstantiationExpr(expr, scope);
|
|
222
|
+
case "TypeName":
|
|
223
|
+
// A type in expression position: monomorphisation rewrites
|
|
224
|
+
// `Box[int]` in `(*Box[int]).Set` to the TypeName `Box$int`.
|
|
225
|
+
expr._isTypeRef = true;
|
|
226
|
+
return this._typeRefOperand(expr, this.resolveTypeNode(expr, scope));
|
|
180
227
|
default: {
|
|
181
228
|
const m = CHECK_EXPR_DELEGATE[expr.kind];
|
|
182
229
|
if (m) return this[m](expr, scope);
|
|
@@ -186,6 +233,13 @@ export const expressionCheckMethods = {
|
|
|
186
233
|
return ANY;
|
|
187
234
|
},
|
|
188
235
|
|
|
236
|
+
// A type ref (`*T`, `Box[int]`) is a value only as the base of a method
|
|
237
|
+
// expression (`(*T).M`), a conversion callee or the operand of `*`.
|
|
238
|
+
_typeRefOperand(expr, t) {
|
|
239
|
+
if (expr._typeRefOk) return t;
|
|
240
|
+
return this.err(`${typeStr(t)} (type) is not an expression`, expr);
|
|
241
|
+
},
|
|
242
|
+
|
|
189
243
|
_checkDeref(ot, expr) {
|
|
190
244
|
if (ot === ANY) return ANY;
|
|
191
245
|
if (ot.kind === "pointer") return ot.base;
|
|
@@ -201,12 +255,200 @@ export const expressionCheckMethods = {
|
|
|
201
255
|
},
|
|
202
256
|
|
|
203
257
|
_checkNumericUnaryOp(ot, expr) {
|
|
258
|
+
if (ot?.kind === "typeParam") {
|
|
259
|
+
if (!this._typeParamAllows(ot, isComplexOrNumeric))
|
|
260
|
+
this.err(`${expr.op} requires numeric`, expr);
|
|
261
|
+
return ot;
|
|
262
|
+
}
|
|
204
263
|
if (!isNumeric(ot) && !isComplex(ot) && !isAny(ot))
|
|
205
264
|
this.err(`${expr.op} requires numeric`, expr);
|
|
206
265
|
return ot;
|
|
207
266
|
},
|
|
208
267
|
|
|
268
|
+
// `^x` on a type parameter needs every type in its constraint to be an
|
|
269
|
+
// integer. Other operands keep the existing (unchecked) behaviour.
|
|
270
|
+
_checkComplementUnaryOp(ot, expr) {
|
|
271
|
+
if (ot?.kind === "typeParam" && !this._typeParamAllows(ot, isInteger))
|
|
272
|
+
this.err("^ requires integer", expr);
|
|
273
|
+
return ot;
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
// Whether every type in the type set of type parameter `tp`'s constraint
|
|
277
|
+
// satisfies `pred`. A constraint without a type set (any, comparable,
|
|
278
|
+
// method-only interfaces) allows nothing.
|
|
279
|
+
_typeParamAllows(tp, pred) {
|
|
280
|
+
const terms = this._constraintTerms(tp.constraint, new Set());
|
|
281
|
+
return terms !== null && terms.length > 0 && terms.every(pred);
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
_constraintTerms(c, seen) {
|
|
285
|
+
if (!c || seen.has(c)) return null;
|
|
286
|
+
seen.add(c);
|
|
287
|
+
// Receiver type parameters keep their constraint as an AST node.
|
|
288
|
+
if (c.kind === "TypeName" || c.kind === "InterfaceType")
|
|
289
|
+
return this._constraintTerms(this.resolveTypeNode(c, this.globals), seen);
|
|
290
|
+
if (c.kind === "named") return this._constraintTerms(c.underlying, seen);
|
|
291
|
+
if (c.kind === "interface") return this._interfaceTerms(c, seen);
|
|
292
|
+
if (c.kind === "typeParam")
|
|
293
|
+
return this._constraintTerms(c.constraint, seen);
|
|
294
|
+
if (c.kind !== "UnionConstraint") return isAny(c) ? null : [c];
|
|
295
|
+
const out = [];
|
|
296
|
+
for (const term of c.terms) {
|
|
297
|
+
const t = this.resolveTypeNode(term.type, this.globals);
|
|
298
|
+
const inner =
|
|
299
|
+
t?.kind === "named" && t.underlying?.kind === "interface"
|
|
300
|
+
? this._constraintTerms(t, seen)
|
|
301
|
+
: [t];
|
|
302
|
+
if (inner === null) return null;
|
|
303
|
+
out.push(...inner);
|
|
304
|
+
}
|
|
305
|
+
return out;
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
// An interface's type set is the intersection of its own union and the
|
|
309
|
+
// type sets of the interfaces it embeds; null when it has none.
|
|
310
|
+
_interfaceTerms(iface, seen) {
|
|
311
|
+
const sets = [iface.unionConstraint, ...(iface._typeSetEmbeds ?? [])]
|
|
312
|
+
.filter(Boolean)
|
|
313
|
+
.map((s) => this._constraintTerms(s, new Set(seen)));
|
|
314
|
+
if (sets.length === 0 || sets.includes(null)) return null;
|
|
315
|
+
const [first, ...rest] = sets;
|
|
316
|
+
return first.filter((t) =>
|
|
317
|
+
rest.every((set) => set.some((u) => typeStr(u) === typeStr(t))),
|
|
318
|
+
);
|
|
319
|
+
},
|
|
320
|
+
|
|
321
|
+
// Only structs and arrays are their own managed objects in wasm; every other
|
|
322
|
+
// value (numbers, strings, slices, maps, interfaces, pointers, funcs, SIMD
|
|
323
|
+
// vectors) lives inline in its parent struct, array or global, so wasm has
|
|
324
|
+
// no stable address for it. Type parameters are checked per instantiation.
|
|
325
|
+
_isScalarStorageType(t) {
|
|
326
|
+
const u = this.resolveType(t?.kind === "named" ? t.underlying : t) ?? t;
|
|
327
|
+
const k = u?.kind;
|
|
328
|
+
if (!k || u._tainted) return false;
|
|
329
|
+
return k !== "struct" && k !== "array" && k !== "typeParam";
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
// True when `operand` is a struct field, array/slice element or package-level
|
|
333
|
+
// variable holding a scalar, in a package compiled to wasm.
|
|
334
|
+
_isScalarInteriorOperand(operand, type, scope) {
|
|
335
|
+
if (!this._isWasmPackage()) return false;
|
|
336
|
+
let node = operand;
|
|
337
|
+
while (node?.kind === "ParenExpr") node = node.expr;
|
|
338
|
+
if (!node) return false;
|
|
339
|
+
if (node.kind === "Ident") {
|
|
340
|
+
if (scope.lookupScope(node.name) !== this.globals) return false;
|
|
341
|
+
if (scope.isConst?.(node.name)) return false;
|
|
342
|
+
const sym = this.globals.symbols.get(node.name);
|
|
343
|
+
if (sym?.kind === "func" || sym?.kind === "namespace") return false;
|
|
344
|
+
} else if (node.kind !== "SelectorExpr" && node.kind !== "IndexExpr") {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
if (node.kind === "SelectorExpr" && node.expr?._isTypeRef) return false;
|
|
348
|
+
if (node.kind === "SelectorExpr" && node.expr?._type?.kind === "namespace")
|
|
349
|
+
return false;
|
|
350
|
+
return this._isScalarStorageType(type);
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
// Postfix selectors and index expressions carry no position; report at
|
|
354
|
+
// their innermost positioned operand.
|
|
355
|
+
_positioned(node) {
|
|
356
|
+
let at = node;
|
|
357
|
+
while (at && !at._line && !at.line) at = at.operand ?? at.expr;
|
|
358
|
+
return at ?? node;
|
|
359
|
+
},
|
|
360
|
+
|
|
361
|
+
_errScalarInteriorAddress(node, type) {
|
|
362
|
+
return this.err(
|
|
363
|
+
`cannot take the address of a field, element or package-level variable of type ${typeStr(type)} in a ${this.target} package; only struct and array values have a stable address in WebAssembly. Pass the parent struct pointer or the index instead, or copy the value to a local variable`,
|
|
364
|
+
this._positioned(node),
|
|
365
|
+
);
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
// Go: map elements are not addressable, nor are fields and array elements
|
|
369
|
+
// reached from one by value (`&m[k]`, `&m[k].f`, `&m[k][i]` on an array).
|
|
370
|
+
_isMapElementOperand(operand) {
|
|
371
|
+
let node = operand;
|
|
372
|
+
while (node?.kind === "ParenExpr") node = node.expr;
|
|
373
|
+
if (node?.kind !== "SelectorExpr" && node?.kind !== "IndexExpr")
|
|
374
|
+
return false;
|
|
375
|
+
const bt = node.expr?._type;
|
|
376
|
+
const b = this.resolveType(bt?.kind === "named" ? bt.underlying : bt) ?? bt;
|
|
377
|
+
if (node.kind === "IndexExpr" && b?.kind === "map") return true;
|
|
378
|
+
if (b?.kind === "pointer" || b?.kind === "slice") return false;
|
|
379
|
+
if (node.kind === "SelectorExpr" && b?.kind !== "struct") return false;
|
|
380
|
+
if (
|
|
381
|
+
node.kind === "SelectorExpr" &&
|
|
382
|
+
this._promotedThroughPointer(bt, node.field)
|
|
383
|
+
)
|
|
384
|
+
return false;
|
|
385
|
+
return this._isMapElementOperand(node.expr);
|
|
386
|
+
},
|
|
387
|
+
|
|
388
|
+
// Whether field or method `name` of struct type `t` is promoted through an
|
|
389
|
+
// embedded pointer, so selecting it dereferences and is addressable.
|
|
390
|
+
_promotedThroughPointer(t, name) {
|
|
391
|
+
return this._selectorPath(t, name, 0) === "pointer";
|
|
392
|
+
},
|
|
393
|
+
|
|
394
|
+
// "pointer" when `name` is reached through an embedded pointer, "value"
|
|
395
|
+
// when it is found without one, null when `t` has no such member.
|
|
396
|
+
_selectorPath(t, name, depth) {
|
|
397
|
+
if (!t || depth > 8) return null;
|
|
398
|
+
if (t.kind === "named" && t.methods?.has(name)) return "value";
|
|
399
|
+
const st = this.resolveType(t.kind === "named" ? t.underlying : t) ?? t;
|
|
400
|
+
if (st?.kind !== "struct") return null;
|
|
401
|
+
for (const f of st._structFields ?? [])
|
|
402
|
+
if (!f.embedded && f.names?.includes(name)) return "value";
|
|
403
|
+
for (const e of st._embeds ?? []) {
|
|
404
|
+
const found = this._selectorPath(
|
|
405
|
+
e.kind === "pointer" ? e.base : e,
|
|
406
|
+
name,
|
|
407
|
+
depth + 1,
|
|
408
|
+
);
|
|
409
|
+
if (found) return e.kind === "pointer" ? "pointer" : found;
|
|
410
|
+
}
|
|
411
|
+
return null;
|
|
412
|
+
},
|
|
413
|
+
|
|
414
|
+
_errMapElementAddress(node) {
|
|
415
|
+
return this.err(
|
|
416
|
+
"cannot take the address of a map element; copy it to a local variable, or store pointers in the map",
|
|
417
|
+
this._positioned(node),
|
|
418
|
+
);
|
|
419
|
+
},
|
|
420
|
+
|
|
421
|
+
_isWasmPackage() {
|
|
422
|
+
return this.target === "wasm" || this.target === "both";
|
|
423
|
+
},
|
|
424
|
+
|
|
425
|
+
_isFuncType(t) {
|
|
426
|
+
const u = this.resolveType(t?.kind === "named" ? t.underlying : t) ?? t;
|
|
427
|
+
return u?.kind === "func";
|
|
428
|
+
},
|
|
429
|
+
|
|
430
|
+
// The WASM backend represents a func value by its closure reference and has
|
|
431
|
+
// no box for it, so a pointer to a func variable would lose writes.
|
|
432
|
+
_errFuncAddress(node, type) {
|
|
433
|
+
return this.err(
|
|
434
|
+
`cannot take the address of a variable of func type ${typeStr(type)} in a ${this.target} package; return the new func, or pass a pointer to a struct that holds it`,
|
|
435
|
+
this._positioned(node),
|
|
436
|
+
);
|
|
437
|
+
},
|
|
438
|
+
|
|
209
439
|
_checkAddressOfExpr(expr, scope, ot) {
|
|
440
|
+
if (this._isMapElementOperand(expr.operand)) {
|
|
441
|
+
this._errMapElementAddress(expr);
|
|
442
|
+
return { kind: "pointer", base: ot };
|
|
443
|
+
}
|
|
444
|
+
if (this._isScalarInteriorOperand(expr.operand, ot, scope)) {
|
|
445
|
+
this._errScalarInteriorAddress(expr, ot);
|
|
446
|
+
return { kind: "pointer", base: ot };
|
|
447
|
+
}
|
|
448
|
+
if (this._isWasmPackage() && this._isFuncType(ot)) {
|
|
449
|
+
this._errFuncAddress(expr, ot);
|
|
450
|
+
return { kind: "pointer", base: ot };
|
|
451
|
+
}
|
|
210
452
|
if (expr.operand.kind === "Ident") {
|
|
211
453
|
expr.operand._addressTaken = true;
|
|
212
454
|
const sym = scope.lookup(expr.operand.name);
|
|
@@ -215,19 +457,40 @@ export const expressionCheckMethods = {
|
|
|
215
457
|
return { kind: "pointer", base: ot };
|
|
216
458
|
},
|
|
217
459
|
|
|
460
|
+
// Go constants have no negative zero: `-0.0` is plain 0, so it is rewritten
|
|
461
|
+
// to a unary plus that both backends compile to +0.
|
|
462
|
+
_foldNegativeZeroLiteral(expr) {
|
|
463
|
+
const o = expr.operand;
|
|
464
|
+
if (expr.op !== "-" || o?.kind !== "BasicLit") return;
|
|
465
|
+
if (o.litKind !== "INT" && o.litKind !== "FLOAT") return;
|
|
466
|
+
if (Number(String(o.value).replaceAll("_", "")) === 0) expr.op = "+";
|
|
467
|
+
},
|
|
468
|
+
|
|
218
469
|
_checkUnaryExpr(expr, scope) {
|
|
470
|
+
this._foldNegativeZeroLiteral(expr);
|
|
471
|
+
if (expr.op === "*") expr.operand._typeRefOk = true;
|
|
219
472
|
const ot = this.checkExpr(expr.operand, scope);
|
|
220
473
|
if (expr.op === "!") return this._checkBoolUnaryOp(ot, expr);
|
|
221
474
|
if (expr.op === "-" || expr.op === "+")
|
|
222
475
|
return this._checkNumericUnaryOp(ot, expr);
|
|
476
|
+
if (expr.op === "^") return this._checkComplementUnaryOp(ot, expr);
|
|
223
477
|
if (expr.op === "&") return this._checkAddressOfExpr(expr, scope, ot);
|
|
224
|
-
if (expr.op === "*")
|
|
478
|
+
if (expr.op === "*") {
|
|
479
|
+
// `(*T).M`: the operand names a type, so `*T` is the pointer type
|
|
480
|
+
// of a method expression, not a dereference.
|
|
481
|
+
if (expr.operand._isTypeRef) {
|
|
482
|
+
expr._isTypeRef = true;
|
|
483
|
+
return this._typeRefOperand(expr, { kind: "pointer", base: ot });
|
|
484
|
+
}
|
|
485
|
+
return this._checkDeref(ot, expr);
|
|
486
|
+
}
|
|
225
487
|
return ot;
|
|
226
488
|
},
|
|
227
489
|
|
|
228
490
|
_isStructOrNamedMethod(baseType, base, field) {
|
|
229
491
|
return (
|
|
230
492
|
(base?.kind === "struct" && base.methods?.has(field)) ||
|
|
493
|
+
(base?.kind === "simd" && base.methods?.has(field)) ||
|
|
231
494
|
(baseType.kind === "named" && baseType.methods?.has(field))
|
|
232
495
|
);
|
|
233
496
|
},
|
|
@@ -237,51 +500,147 @@ export const expressionCheckMethods = {
|
|
|
237
500
|
return base?.methods?.get(field) ?? baseType.methods?.get(field);
|
|
238
501
|
},
|
|
239
502
|
|
|
503
|
+
// Instantiated generic methods are substituted copies without `_ptrRecv`,
|
|
504
|
+
// so read it from the generic declaration's method.
|
|
505
|
+
_methodExprPtrRecv(recvBase, methodType, field) {
|
|
506
|
+
if (!methodType) return false;
|
|
507
|
+
if (Object.hasOwn(methodType, "_ptrRecv")) return methodType._ptrRecv;
|
|
508
|
+
const generic = recvBase.typeArgs
|
|
509
|
+
? this.types.get(recvBase.name)?._generic
|
|
510
|
+
: null;
|
|
511
|
+
return Boolean(generic?.methods?.get(field)?._ptrRecv);
|
|
512
|
+
},
|
|
513
|
+
|
|
514
|
+
// `x.M` or `x.M()` with a pointer receiver on a named scalar or slice local takes
|
|
515
|
+
// `&x`, so `x` must be boxed like an explicit `&x`.
|
|
516
|
+
_markPtrRecvAddress(expr, base, ft, scope) {
|
|
517
|
+
if (!ft._ptrRecv) return;
|
|
518
|
+
if (
|
|
519
|
+
!this._promotedThroughPointer(expr.expr._type, expr.field) &&
|
|
520
|
+
this._isMapElementOperand(expr.expr)
|
|
521
|
+
) {
|
|
522
|
+
this._errMapElementAddress(expr.expr);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const k = base?.kind;
|
|
526
|
+
if (k !== "basic" && k !== "slice" && k !== "map" && k !== "func") return;
|
|
527
|
+
if (this._isScalarInteriorOperand(expr.expr, expr.expr._type, scope)) {
|
|
528
|
+
this._errScalarInteriorAddress(expr, expr.expr._type);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
if (k === "func" && this._isWasmPackage()) {
|
|
532
|
+
this._errFuncAddress(expr, expr.expr._type);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (expr.expr.kind !== "Ident") return;
|
|
536
|
+
expr.expr._addressTaken = true;
|
|
537
|
+
const sym = scope.lookup(expr.expr.name);
|
|
538
|
+
if (sym) sym._addressTaken = true;
|
|
539
|
+
},
|
|
540
|
+
|
|
541
|
+
// Resolved per file from the import path, so a local package sharing an
|
|
542
|
+
// identifier with a stdlib one (or a stdlib import in another file) never matches.
|
|
543
|
+
_checkExcludedNamespaceMember(expr, baseType) {
|
|
544
|
+
const pkgPath = this._fileImports
|
|
545
|
+
?.get(this._currentFile)
|
|
546
|
+
?.get(baseType.name);
|
|
547
|
+
if (!pkgPath || pkgPath.startsWith(".")) return null;
|
|
548
|
+
const key = `${pkgPath}.${expr.field}`;
|
|
549
|
+
if (!EXCLUDED_STDLIB_MEMBERS.has(key)) return null;
|
|
550
|
+
return this.err(
|
|
551
|
+
`${baseType.name}.${expr.field} is not available: ${EXCLUDED_STDLIB_MEMBERS.get(key)}`,
|
|
552
|
+
expr,
|
|
553
|
+
);
|
|
554
|
+
},
|
|
555
|
+
|
|
556
|
+
_checkExcludedSelectorExpr(expr, baseType) {
|
|
557
|
+
if (baseType?.kind === "namespace") {
|
|
558
|
+
return this._checkExcludedNamespaceMember(expr, baseType);
|
|
559
|
+
}
|
|
560
|
+
const recvCheck =
|
|
561
|
+
baseType?.kind === "pointer"
|
|
562
|
+
? (this.resolveType(baseType.base) ?? baseType.base)
|
|
563
|
+
: baseType;
|
|
564
|
+
const recvName = recvCheck?.name;
|
|
565
|
+
// Stdlib named types are qualified (`pkg.T` or mangled `T$$pkg`); a
|
|
566
|
+
// bare name is a user type and never matches the exclusion table.
|
|
567
|
+
if (!recvName || !/[.$]/.test(recvName)) return null;
|
|
568
|
+
const dName = displayTypeName(recvName);
|
|
569
|
+
const reason =
|
|
570
|
+
EXCLUDED_STDLIB_MEMBERS.get(`${dName}.${expr.field}`) ??
|
|
571
|
+
EXCLUDED_STDLIB_MEMBERS.get(`${recvName}.${expr.field}`);
|
|
572
|
+
if (!reason) return null;
|
|
573
|
+
return this.err(`${dName}.${expr.field} is not available: ${reason}`, expr);
|
|
574
|
+
},
|
|
575
|
+
|
|
576
|
+
_checkMethodExprSelector(expr, baseType) {
|
|
577
|
+
const recvBase = baseType.kind === "pointer" ? baseType.base : baseType;
|
|
578
|
+
const base = this.resolveType(
|
|
579
|
+
recvBase.kind === "named" ? recvBase.underlying : recvBase,
|
|
580
|
+
);
|
|
581
|
+
const methodType = this._resolveMethodExprType(recvBase, base, expr.field);
|
|
582
|
+
if (
|
|
583
|
+
this._methodExprPtrRecv(recvBase, methodType, expr.field) &&
|
|
584
|
+
baseType.kind !== "pointer"
|
|
585
|
+
) {
|
|
586
|
+
const t = typeStr(baseType);
|
|
587
|
+
return this.err(
|
|
588
|
+
`invalid method expression ${t}.${expr.field} (needs pointer receiver (*${t}).${expr.field})`,
|
|
589
|
+
expr,
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
if (!methodType) return null;
|
|
593
|
+
expr._isMethodExpr = true;
|
|
594
|
+
expr._methodExprRecv = baseType;
|
|
595
|
+
return {
|
|
596
|
+
kind: "func",
|
|
597
|
+
params: [baseType, ...(methodType.params ?? [])],
|
|
598
|
+
returns: methodType.returns ?? [],
|
|
599
|
+
variadic: methodType.variadic,
|
|
600
|
+
};
|
|
601
|
+
},
|
|
602
|
+
|
|
603
|
+
_markMethodValueSelector(expr, baseType, ft, scope) {
|
|
604
|
+
const recvT =
|
|
605
|
+
baseType.kind === "pointer"
|
|
606
|
+
? (this.resolveType(baseType.base) ?? baseType.base)
|
|
607
|
+
: baseType;
|
|
608
|
+
const base = this.resolveType(
|
|
609
|
+
recvT?.kind === "named" ? recvT.underlying : recvT,
|
|
610
|
+
);
|
|
611
|
+
if (!recvT || !this._isStructOrNamedMethod(recvT, base, expr.field)) return;
|
|
612
|
+
expr._isMethodValue = true;
|
|
613
|
+
if (baseType.kind === "pointer")
|
|
614
|
+
expr._derefRecv = !this._methodExprPtrRecv(recvT, ft, expr.field);
|
|
615
|
+
else this._markPtrRecvAddress(expr, base, ft, scope);
|
|
616
|
+
},
|
|
617
|
+
|
|
240
618
|
_checkSelectorExpr(expr, scope) {
|
|
619
|
+
expr.expr._typeRefOk = true;
|
|
241
620
|
const baseType = this.checkExpr(expr.expr, scope);
|
|
242
621
|
if (baseType?._tainted) return TAINTED_ANY;
|
|
622
|
+
const exclErr = this._checkExcludedSelectorExpr(expr, baseType);
|
|
623
|
+
if (exclErr) return exclErr;
|
|
243
624
|
if (expr.expr._isTypeRef) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const methodType = this._resolveMethodExprType(
|
|
247
|
-
baseType,
|
|
248
|
-
base,
|
|
249
|
-
expr.field,
|
|
250
|
-
);
|
|
251
|
-
if (methodType) {
|
|
252
|
-
expr._isMethodExpr = true;
|
|
253
|
-
expr._methodExprRecv = baseType;
|
|
254
|
-
return {
|
|
255
|
-
kind: "func",
|
|
256
|
-
params: [baseType, ...(methodType.params ?? [])],
|
|
257
|
-
returns: methodType.returns ?? [],
|
|
258
|
-
variadic: methodType.variadic,
|
|
259
|
-
};
|
|
260
|
-
}
|
|
625
|
+
const mExpr = this._checkMethodExprSelector(expr, baseType);
|
|
626
|
+
if (mExpr) return mExpr;
|
|
261
627
|
}
|
|
262
628
|
const ft = this.fieldType(baseType, expr.field, expr);
|
|
263
|
-
if (ft?.kind === "func")
|
|
264
|
-
|
|
265
|
-
base = this.resolveType(base);
|
|
266
|
-
if (this._isStructOrNamedMethod(baseType, base, expr.field))
|
|
267
|
-
expr._isMethodValue = true;
|
|
268
|
-
}
|
|
629
|
+
if (ft?.kind === "func")
|
|
630
|
+
this._markMethodValueSelector(expr, baseType, ft, scope);
|
|
269
631
|
if (
|
|
270
632
|
baseType?.kind === "namespace" &&
|
|
271
633
|
this.types.has(`${baseType.name}.${expr.field}`) &&
|
|
272
|
-
|
|
273
|
-
baseType.name === "binary" &&
|
|
274
|
-
(expr.field === "LittleEndian" || expr.field === "BigEndian")
|
|
275
|
-
)
|
|
634
|
+
baseType.name !== "binary"
|
|
276
635
|
) {
|
|
277
636
|
expr._isTypeRef = true;
|
|
278
637
|
}
|
|
279
638
|
return ft;
|
|
280
639
|
},
|
|
281
640
|
|
|
282
|
-
_checkArrayBoundsExpr(btu, bt, expr) {
|
|
641
|
+
_checkArrayBoundsExpr(btu, bt, expr, scope) {
|
|
283
642
|
if (btu.kind !== "array" || btu.size == null) return;
|
|
284
|
-
const idx = this._constIntValue(expr.index);
|
|
643
|
+
const idx = this._constIntValue(expr.index, scope);
|
|
285
644
|
if (idx === null) return;
|
|
286
645
|
if (idx < 0)
|
|
287
646
|
this.err(`invalid array index ${idx} (index must not be negative)`, expr);
|
|
@@ -292,26 +651,97 @@ export const expressionCheckMethods = {
|
|
|
292
651
|
);
|
|
293
652
|
},
|
|
294
653
|
|
|
654
|
+
// `Box[int]` in an expression (the receiver of a method expression such
|
|
655
|
+
// as `Box[int].Get` or `(*Box[int]).Set`) names the instantiated type.
|
|
656
|
+
// The node becomes an InstantiationExpr so monomorphisation rewrites it.
|
|
657
|
+
_genericTypeRef(expr, scope) {
|
|
658
|
+
const base = expr.expr;
|
|
659
|
+
let t;
|
|
660
|
+
if (base?.kind === "SelectorExpr" && base.expr?.kind === "Ident") {
|
|
661
|
+
// `iter.Seq[int]`: a generic type behind a package qualifier.
|
|
662
|
+
if (scope.lookup(base.expr.name)?.kind !== "namespace") return null;
|
|
663
|
+
t = this.types.get(`${base.expr.name}.${base.field}`);
|
|
664
|
+
if (!t?._generic || t._generic.isAlias) return null;
|
|
665
|
+
} else {
|
|
666
|
+
if (base?.kind !== "Ident") return null;
|
|
667
|
+
t = this.types.get(base.name);
|
|
668
|
+
if (!t?._generic || t._generic.isAlias || scope.lookup(base.name) !== t)
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
671
|
+
if (expr.kind === "IndexExpr") {
|
|
672
|
+
if (expr.index?.kind !== "Ident") return null;
|
|
673
|
+
expr.kind = "InstantiationExpr";
|
|
674
|
+
expr.typeArgs = [{ kind: "TypeName", name: expr.index.name }];
|
|
675
|
+
delete expr.index;
|
|
676
|
+
}
|
|
677
|
+
const resolved = this.resolveTypeNode(expr, scope);
|
|
678
|
+
expr._isTypeRef = true;
|
|
679
|
+
return this._typeRefOperand(expr, resolved);
|
|
680
|
+
},
|
|
681
|
+
|
|
682
|
+
// Underlying type with chains of defined types followed through
|
|
683
|
+
// (`type W V` where `V` is itself a named array).
|
|
684
|
+
_deepUnderlying(t) {
|
|
685
|
+
let u = t;
|
|
686
|
+
for (let i = 0; u?.kind === "named" && i < 32; i++)
|
|
687
|
+
u = this.resolveType(u.underlying) ?? u.underlying;
|
|
688
|
+
return u;
|
|
689
|
+
},
|
|
690
|
+
|
|
691
|
+
// A key of an interface-keyed map (`map[any]T`): the backend encodes it
|
|
692
|
+
// with its dynamic type, so `L("1")`, `N(1)`, `"1"` and `1` stay distinct.
|
|
693
|
+
// A named basic value is boxed first, exactly as in any interface
|
|
694
|
+
// conversion; other values are keyed as they are.
|
|
695
|
+
_markIfaceKey(mapType, node, srcType) {
|
|
696
|
+
const key = this.resolveType(mapType.key);
|
|
697
|
+
const kb = key?.kind === "named" ? this.resolveType(key.underlying) : key;
|
|
698
|
+
const src = srcType && this.resolveType(srcType);
|
|
699
|
+
if (node && src?.kind === "typeParam") {
|
|
700
|
+
// Generic code: whether the key is boxed depends on the type argument,
|
|
701
|
+
// so the backend decides at run time (`__ikt`).
|
|
702
|
+
if (key?.kind === "typeParam" || isAny(key) || kb?.kind === "interface") {
|
|
703
|
+
node._ifaceKeyDyn = key?.kind === "typeParam" ? "same" : "box";
|
|
704
|
+
node._ifaceKeyParam = src.name;
|
|
705
|
+
}
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
if (!node || !srcType || (!isAny(key) && kb?.kind !== "interface")) return;
|
|
709
|
+
node._ifaceKey = true;
|
|
710
|
+
this._markIfaceBox(key, this.resolveType(srcType), node);
|
|
711
|
+
if (node._ifaceBox === "named" || node._ifaceBox === "named_ptr") return;
|
|
712
|
+
node._ifaceBox = undefined;
|
|
713
|
+
node._ifaceSrc = undefined;
|
|
714
|
+
node._ifacePtrFields = undefined;
|
|
715
|
+
},
|
|
716
|
+
|
|
295
717
|
_checkIndexExpr(expr, scope) {
|
|
296
|
-
const
|
|
297
|
-
|
|
718
|
+
const typeRef = this._genericTypeRef(expr, scope);
|
|
719
|
+
if (typeRef) return typeRef;
|
|
720
|
+
const bt = this._derefArrayPtr(
|
|
721
|
+
expr,
|
|
722
|
+
"expr",
|
|
723
|
+
this.checkExpr(expr.expr, scope),
|
|
724
|
+
);
|
|
725
|
+
if (bt?.kind === "generic") {
|
|
726
|
+
const inst = this._genericFuncValueAsInstantiation(expr);
|
|
727
|
+
if (inst) return this._checkInstantiationExpr(inst, scope);
|
|
728
|
+
}
|
|
729
|
+
const indexType = this.checkExpr(expr.index, scope);
|
|
298
730
|
if (bt?._tainted) return TAINTED_ANY;
|
|
299
731
|
if (isAny(bt)) return ANY;
|
|
300
|
-
const btu =
|
|
301
|
-
bt.kind === "named"
|
|
302
|
-
? (this.resolveType(bt.underlying) ?? bt.underlying)
|
|
303
|
-
: bt;
|
|
732
|
+
const btu = this._deepUnderlying(bt);
|
|
304
733
|
if (btu.kind === "slice" || btu.kind === "array") {
|
|
305
|
-
this._checkArrayBoundsExpr(btu, bt, expr);
|
|
734
|
+
this._checkArrayBoundsExpr(btu, bt, expr, scope);
|
|
306
735
|
return btu.elem;
|
|
307
736
|
}
|
|
308
737
|
if (btu.kind === "map") {
|
|
309
738
|
expr._mapValueType = btu.value;
|
|
739
|
+
this._markIfaceKey(btu, expr.index, indexType);
|
|
310
740
|
return btu.value;
|
|
311
741
|
}
|
|
312
742
|
if (btu.kind === "shared") {
|
|
313
743
|
expr._sharedElem = btu.elem;
|
|
314
|
-
const idx = this._constIntValue(expr.index);
|
|
744
|
+
const idx = this._constIntValue(expr.index, scope);
|
|
315
745
|
if (idx !== null && idx < 0)
|
|
316
746
|
this.err(`invalid index ${idx} (index must not be negative)`, expr);
|
|
317
747
|
return btu.elem;
|
|
@@ -320,6 +750,30 @@ export const expressionCheckMethods = {
|
|
|
320
750
|
return this.err(`Cannot index type ${typeStr(bt)}`, expr);
|
|
321
751
|
},
|
|
322
752
|
|
|
753
|
+
// `Id[int]` used as a value reaches the typechecker as an IndexExpr (the
|
|
754
|
+
// parser only commits to type arguments before a call or literal). The
|
|
755
|
+
// node becomes an InstantiationExpr so both backends specialise it.
|
|
756
|
+
_genericFuncValueAsInstantiation(expr) {
|
|
757
|
+
const toTypeNode = (n) => {
|
|
758
|
+
if (n?.kind === "TypeExpr") return n.type;
|
|
759
|
+
const parts = [];
|
|
760
|
+
let cur = n;
|
|
761
|
+
while (cur?.kind === "SelectorExpr") {
|
|
762
|
+
parts.unshift(cur.field);
|
|
763
|
+
cur = cur.expr;
|
|
764
|
+
}
|
|
765
|
+
if (cur?.kind !== "Ident") return null;
|
|
766
|
+
parts.unshift(cur.name);
|
|
767
|
+
return { kind: "TypeName", name: parts.join("."), _line: n._line };
|
|
768
|
+
};
|
|
769
|
+
const typeArg = toTypeNode(expr.index);
|
|
770
|
+
if (!typeArg) return null;
|
|
771
|
+
expr.kind = "InstantiationExpr";
|
|
772
|
+
expr.typeArgs = [typeArg];
|
|
773
|
+
delete expr.index;
|
|
774
|
+
return expr;
|
|
775
|
+
},
|
|
776
|
+
|
|
323
777
|
// copy(dst, src): element types must be identical when either side is a
|
|
324
778
|
// shared buffer (the wasm emitter uses one element type for both sides).
|
|
325
779
|
_checkBuiltinCopy(e, at) {
|
|
@@ -345,15 +799,33 @@ export const expressionCheckMethods = {
|
|
|
345
799
|
return INT;
|
|
346
800
|
},
|
|
347
801
|
|
|
802
|
+
// Go's shorthand for pointers to arrays: `p[i]`, `p[lo:hi]`, `len(p)` and
|
|
803
|
+
// `range p` mean `(*p)[i]` and so on. The operand is rewritten to an
|
|
804
|
+
// explicit dereference so every backend sees the array.
|
|
805
|
+
_derefArrayPtr(holder, key, t) {
|
|
806
|
+
if (t?.kind !== "pointer") return t;
|
|
807
|
+
const base = t.base?.kind === "named" ? this.resolveType(t.base) : t.base;
|
|
808
|
+
const under = base?.kind === "named" ? base.underlying : base;
|
|
809
|
+
if (under?.kind !== "array") return t;
|
|
810
|
+
holder[key] = { kind: "UnaryExpr", op: "*", operand: holder[key] };
|
|
811
|
+
holder[key]._type = t.base;
|
|
812
|
+
return t.base;
|
|
813
|
+
},
|
|
814
|
+
|
|
348
815
|
_checkSliceExpr(expr, scope) {
|
|
349
|
-
const bt = this.
|
|
816
|
+
const bt = this._derefArrayPtr(
|
|
817
|
+
expr,
|
|
818
|
+
"expr",
|
|
819
|
+
this.checkExpr(expr.expr, scope),
|
|
820
|
+
);
|
|
350
821
|
if (expr.low) this.checkExpr(expr.low, scope);
|
|
351
822
|
if (expr.high) this.checkExpr(expr.high, scope);
|
|
352
823
|
if (expr.max) this.checkExpr(expr.max, scope);
|
|
353
824
|
if (bt?._tainted) return TAINTED_ANY;
|
|
354
825
|
if (isAny(bt)) return ANY;
|
|
355
|
-
|
|
356
|
-
|
|
826
|
+
const btu = this._deepUnderlying(bt);
|
|
827
|
+
if (btu.kind === "slice" || btu.kind === "array")
|
|
828
|
+
return { kind: "slice", elem: btu.elem };
|
|
357
829
|
if (isString(bt)) return STRING;
|
|
358
830
|
if (sharedInfo(bt))
|
|
359
831
|
return this.err(
|
|
@@ -386,7 +858,7 @@ export const expressionCheckMethods = {
|
|
|
386
858
|
}
|
|
387
859
|
const inner = new Scope(scope);
|
|
388
860
|
for (const p of expr.params)
|
|
389
|
-
inner.define(p.name, p.type ? this.
|
|
861
|
+
inner.define(p.name, p.type ? this._paramType(p, scope) : ANY);
|
|
390
862
|
const ret = expr.returnType
|
|
391
863
|
? this.resolveTypeNode(expr.returnType, scope)
|
|
392
864
|
: VOID;
|
|
@@ -403,6 +875,7 @@ export const expressionCheckMethods = {
|
|
|
403
875
|
kind: "func",
|
|
404
876
|
params: paramTypes,
|
|
405
877
|
returns: [ret],
|
|
878
|
+
variadic: expr.params.at(-1)?.variadic === true,
|
|
406
879
|
async: expr.async,
|
|
407
880
|
};
|
|
408
881
|
},
|
|
@@ -428,6 +901,19 @@ export const expressionCheckMethods = {
|
|
|
428
901
|
);
|
|
429
902
|
},
|
|
430
903
|
|
|
904
|
+
// Struct types with identical field names and types convert to each other.
|
|
905
|
+
_sameStructFields(a, b) {
|
|
906
|
+
if (a?.kind !== "struct" || b?.kind !== "struct") return false;
|
|
907
|
+
if (a.fields.size !== b.fields.size) return false;
|
|
908
|
+
const bf = [...b.fields];
|
|
909
|
+
let i = 0;
|
|
910
|
+
for (const [name, type] of a.fields) {
|
|
911
|
+
const [bn, bt] = bf[i++];
|
|
912
|
+
if (name !== bn || typeStr(type) !== typeStr(bt)) return false;
|
|
913
|
+
}
|
|
914
|
+
return true;
|
|
915
|
+
},
|
|
916
|
+
|
|
431
917
|
_isInvalidArrayConv(tgt, src, srcType) {
|
|
432
918
|
return tgt?.kind === "array" && src?.kind !== "array" && !isAny(srcType);
|
|
433
919
|
},
|
|
@@ -435,6 +921,8 @@ export const expressionCheckMethods = {
|
|
|
435
921
|
_checkTypeConversion(expr, scope) {
|
|
436
922
|
const srcType = this.checkExpr(expr.expr, scope);
|
|
437
923
|
const targetType = this.resolveTypeNode(expr.targetType, scope);
|
|
924
|
+
// `any(x)` / `error(x)`: box x as an implicit conversion would.
|
|
925
|
+
this._markIfaceBox(targetType, srcType, expr.expr);
|
|
438
926
|
const srcResolved =
|
|
439
927
|
srcType?.kind === "named" ? srcType.underlying : srcType;
|
|
440
928
|
const tgtResolved =
|
|
@@ -455,6 +943,8 @@ export const expressionCheckMethods = {
|
|
|
455
943
|
expr,
|
|
456
944
|
);
|
|
457
945
|
}
|
|
946
|
+
if (tgtResolved?.kind === "slice")
|
|
947
|
+
this._rejectSeqAsSlice(srcType, expr.expr, "a slice conversion");
|
|
458
948
|
if (this._isMutualArraySlice(tgtResolved, srcResolved)) {
|
|
459
949
|
this._checkElemTypeCompatible(
|
|
460
950
|
tgtResolved.elem,
|
|
@@ -494,6 +984,8 @@ export const expressionCheckMethods = {
|
|
|
494
984
|
);
|
|
495
985
|
return;
|
|
496
986
|
}
|
|
987
|
+
if (tgtResolved?.kind === "slice")
|
|
988
|
+
this._rejectSeqAsSlice(srcType, expr.args?.[0], "a slice conversion");
|
|
497
989
|
if (this._isMutualArraySlice(tgtResolved, srcResolved)) {
|
|
498
990
|
this._checkElemTypeCompatible(
|
|
499
991
|
tgtResolved.elem,
|
|
@@ -512,7 +1004,15 @@ export const expressionCheckMethods = {
|
|
|
512
1004
|
return;
|
|
513
1005
|
}
|
|
514
1006
|
if (typeStr(srcResolved) === typeStr(tgtResolved)) return;
|
|
1007
|
+
if (this._sameStructFields(srcResolved, tgtResolved)) return;
|
|
515
1008
|
if (isNumeric(srcResolved) && isNumeric(tgtResolved)) return;
|
|
1009
|
+
if (isString(srcResolved) && isString(tgtResolved)) return;
|
|
1010
|
+
if (isBool(srcResolved) && isBool(tgtResolved)) return;
|
|
1011
|
+
if (
|
|
1012
|
+
(isComplex(srcResolved) || isNumeric(srcResolved)) &&
|
|
1013
|
+
isComplex(tgtResolved)
|
|
1014
|
+
)
|
|
1015
|
+
return;
|
|
516
1016
|
if (
|
|
517
1017
|
isString(srcResolved) &&
|
|
518
1018
|
tgtResolved?.kind === "slice" &&
|
|
@@ -586,19 +1086,31 @@ export const expressionCheckMethods = {
|
|
|
586
1086
|
},
|
|
587
1087
|
|
|
588
1088
|
_checkRangeExpr(expr, scope) {
|
|
589
|
-
const collType = this.
|
|
1089
|
+
const collType = this._derefArrayPtr(
|
|
1090
|
+
expr,
|
|
1091
|
+
"expr",
|
|
1092
|
+
this.checkExpr(expr.expr, scope),
|
|
1093
|
+
);
|
|
590
1094
|
const resolved =
|
|
591
1095
|
collType?.kind === "named" ? collType.underlying : collType;
|
|
592
1096
|
return this._rangeCollTypeTuple(resolved, collType);
|
|
593
1097
|
},
|
|
594
1098
|
|
|
595
1099
|
_checkInstantiationExpr(expr, scope) {
|
|
1100
|
+
const typeRef = this._genericTypeRef(expr, scope);
|
|
1101
|
+
if (typeRef) return typeRef;
|
|
596
1102
|
// Generic instantiation: Foo[int] or Foo[int, string]
|
|
597
1103
|
const baseType = this.checkExpr(expr.expr, scope);
|
|
598
1104
|
if (baseType?.kind === "generic") {
|
|
599
1105
|
const typeArgs = expr.typeArgs.map((ta) =>
|
|
600
1106
|
this.resolveTypeNode(ta, scope),
|
|
601
1107
|
);
|
|
1108
|
+
if (typeArgs.length > baseType.typeParams.length) {
|
|
1109
|
+
return this.err(
|
|
1110
|
+
`got ${typeArgs.length} type arguments but ${baseType.name ?? "function"} has ${baseType.typeParams.length} type parameters`,
|
|
1111
|
+
expr,
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
602
1114
|
// Check constraints
|
|
603
1115
|
for (
|
|
604
1116
|
let i = 0;
|
|
@@ -622,9 +1134,28 @@ export const expressionCheckMethods = {
|
|
|
622
1134
|
// rewrite the node in place into an IndexExpr and check that instead.
|
|
623
1135
|
const indexExpr = this._instantiationAsIndex(expr, baseType);
|
|
624
1136
|
if (indexExpr) return this._checkIndexExpr(indexExpr, scope);
|
|
1137
|
+
if (baseType?.kind === "func" && !this._isPseudoGenericStdFunc(baseType))
|
|
1138
|
+
return this.err(
|
|
1139
|
+
`cannot instantiate ${this._calleeName(expr.expr)}: not a generic function`,
|
|
1140
|
+
expr,
|
|
1141
|
+
);
|
|
625
1142
|
return baseType;
|
|
626
1143
|
},
|
|
627
1144
|
|
|
1145
|
+
// Emitter-backed stdlib helpers (`slices.Sort`, `maps.Keys`) take `any`
|
|
1146
|
+
// parameters in place of type parameters and accept (and ignore) explicit
|
|
1147
|
+
// type arguments; a stdlib function with concrete parameters does not.
|
|
1148
|
+
_isPseudoGenericStdFunc(fnType) {
|
|
1149
|
+
return (fnType.params ?? []).some((p) => isAny(p));
|
|
1150
|
+
},
|
|
1151
|
+
|
|
1152
|
+
_calleeName(node) {
|
|
1153
|
+
if (node?.kind === "Ident") return node.name;
|
|
1154
|
+
if (node?.kind === "SelectorExpr")
|
|
1155
|
+
return `${this._calleeName(node.expr)}.${node.field}`;
|
|
1156
|
+
return "expression";
|
|
1157
|
+
},
|
|
1158
|
+
|
|
628
1159
|
_instantiationAsIndex(expr, baseType) {
|
|
629
1160
|
if (!baseType || expr.typeArgs.length !== 1) return null;
|
|
630
1161
|
const ta = expr.typeArgs[0];
|
|
@@ -649,18 +1180,97 @@ export const expressionCheckMethods = {
|
|
|
649
1180
|
return expr;
|
|
650
1181
|
},
|
|
651
1182
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
expr.
|
|
660
|
-
)
|
|
661
|
-
|
|
1183
|
+
// `int(K)`, `uint8(K)`, `MyInt(K)`: a conversion to an integer type.
|
|
1184
|
+
_isIntConversion(expr) {
|
|
1185
|
+
if (expr.args?.length !== 1) return false;
|
|
1186
|
+
const target = expr._conversionTargetType;
|
|
1187
|
+
if (expr._isTypeConversion && target)
|
|
1188
|
+
return isInteger(target.kind === "named" ? target.underlying : target);
|
|
1189
|
+
return (
|
|
1190
|
+
expr.func?.kind === "Ident" && SIZED_INT_NAMES.includes(expr.func.name)
|
|
1191
|
+
);
|
|
1192
|
+
},
|
|
1193
|
+
|
|
1194
|
+
// Keyed array literal indices (Go spec): each key must be a non-negative
|
|
1195
|
+
// integer constant, and no index may appear twice. The resolved index is
|
|
1196
|
+
// stored on the KeyValueExpr as `_index` for compositeLitIndices.
|
|
1197
|
+
_checkArrayLitKeys(expr, scope) {
|
|
1198
|
+
const seen = new Set();
|
|
1199
|
+
let next = 0;
|
|
1200
|
+
let unknown = false;
|
|
1201
|
+
for (const elem of expr.elems) {
|
|
1202
|
+
if (elem.kind === "KeyValueExpr") {
|
|
1203
|
+
this.checkExpr(elem.key, scope);
|
|
1204
|
+
const idx = this._constIntValue(elem.key, scope);
|
|
1205
|
+
const text = constKeyText(elem.key);
|
|
1206
|
+
if (idx === null && this._isConstExpr(elem.key, scope)) {
|
|
1207
|
+
// A constant key the folder cannot evaluate (`len(arr)`) keeps
|
|
1208
|
+
// the literal/positional index of compositeLitIndices; later
|
|
1209
|
+
// indices are not duplicate-checked.
|
|
1210
|
+
unknown = true;
|
|
1211
|
+
next++;
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
if (idx === null)
|
|
1215
|
+
this.err(`index ${text} must be integer constant`, elem.key);
|
|
1216
|
+
else if (idx < 0)
|
|
1217
|
+
this.err(
|
|
1218
|
+
`index ${text} must be non-negative integer constant`,
|
|
1219
|
+
elem.key,
|
|
1220
|
+
);
|
|
1221
|
+
else next = idx;
|
|
1222
|
+
elem._index = next;
|
|
1223
|
+
}
|
|
1224
|
+
if (!unknown && seen.has(next))
|
|
1225
|
+
this.err(`duplicate index ${next} in array or slice literal`, elem);
|
|
1226
|
+
seen.add(next);
|
|
1227
|
+
next++;
|
|
1228
|
+
}
|
|
1229
|
+
},
|
|
1230
|
+
|
|
1231
|
+
// Whether `e` is a constant expression (whose value the folder may not
|
|
1232
|
+
// know): literals, constants, operators, conversions and builtin calls.
|
|
1233
|
+
_isConstExpr(e, scope) {
|
|
1234
|
+
switch (e?.kind) {
|
|
1235
|
+
case "BasicLit":
|
|
1236
|
+
return true;
|
|
1237
|
+
case "ParenExpr":
|
|
1238
|
+
return this._isConstExpr(e.expr, scope);
|
|
1239
|
+
case "UnaryExpr":
|
|
1240
|
+
return (
|
|
1241
|
+
["-", "+", "^", "!"].includes(e.op) &&
|
|
1242
|
+
this._isConstExpr(e.operand, scope)
|
|
1243
|
+
);
|
|
1244
|
+
case "BinaryExpr":
|
|
1245
|
+
return (
|
|
1246
|
+
this._isConstExpr(e.left, scope) && this._isConstExpr(e.right, scope)
|
|
1247
|
+
);
|
|
1248
|
+
case "Ident":
|
|
1249
|
+
return !!(scope ?? this.globals)
|
|
1250
|
+
.lookupScope(e.name)
|
|
1251
|
+
?._consts.has(e.name);
|
|
1252
|
+
case "SelectorExpr": {
|
|
1253
|
+
// Package members: constant unless the package says otherwise.
|
|
1254
|
+
const ns =
|
|
1255
|
+
e.expr?.kind === "Ident"
|
|
1256
|
+
? (scope ?? this.globals).lookup(e.expr.name)
|
|
1257
|
+
: null;
|
|
1258
|
+
if (ns?.kind !== "namespace") return false;
|
|
1259
|
+
return ns._consts ? ns._consts.has(e.field) : true;
|
|
1260
|
+
}
|
|
1261
|
+
case "TypeConversion":
|
|
1262
|
+
return this._isConstExpr(e.expr, scope);
|
|
1263
|
+
case "CallExpr":
|
|
1264
|
+
if (e._isTypeConversion || this._isIntConversion(e))
|
|
1265
|
+
return this._isConstExpr(e.args?.[0], scope);
|
|
1266
|
+
return (
|
|
1267
|
+
e.func?.kind === "Ident" &&
|
|
1268
|
+
CONST_BUILTINS.has(e.func.name) &&
|
|
1269
|
+
(scope ?? this.globals).lookup(e.func.name)?.kind === "builtin"
|
|
1270
|
+
);
|
|
1271
|
+
default:
|
|
1272
|
+
return false;
|
|
662
1273
|
}
|
|
663
|
-
return null;
|
|
664
1274
|
},
|
|
665
1275
|
|
|
666
1276
|
_resolveCallArgTypes(expr, scope) {
|
|
@@ -688,11 +1298,88 @@ export const expressionCheckMethods = {
|
|
|
688
1298
|
if (!resolved) return null;
|
|
689
1299
|
fnType = resolved;
|
|
690
1300
|
}
|
|
691
|
-
return fnType;
|
|
1301
|
+
return this._completePartialInstantiation(expr, argTypes) ?? fnType;
|
|
1302
|
+
},
|
|
1303
|
+
|
|
1304
|
+
// `F[int](x)` with fewer explicit type arguments than type parameters:
|
|
1305
|
+
// infer the rest from the call arguments and record the full list.
|
|
1306
|
+
_completePartialInstantiation(expr, argTypes) {
|
|
1307
|
+
const inst = expr.func;
|
|
1308
|
+
const generic = inst?.kind === "InstantiationExpr" && inst._genericFnType;
|
|
1309
|
+
if (!generic || inst._typeArgs.length >= generic.typeParams.length)
|
|
1310
|
+
return null;
|
|
1311
|
+
const typeArgs = this.inferTypeArgs(
|
|
1312
|
+
generic,
|
|
1313
|
+
this._inferArgTypes(expr, argTypes),
|
|
1314
|
+
inst._typeArgs,
|
|
1315
|
+
);
|
|
1316
|
+
if (!typeArgs) {
|
|
1317
|
+
this.err(
|
|
1318
|
+
`Cannot infer type arguments for generic function '${generic.name}'`,
|
|
1319
|
+
expr,
|
|
1320
|
+
);
|
|
1321
|
+
return null;
|
|
1322
|
+
}
|
|
1323
|
+
inst._typeArgs = typeArgs;
|
|
1324
|
+
if (inst.expr) inst.expr._typeArgs = typeArgs;
|
|
1325
|
+
return this.instantiateGenericFunc(generic, typeArgs);
|
|
1326
|
+
},
|
|
1327
|
+
|
|
1328
|
+
// `T(x)` with T a type parameter: only a numeric x converts (the constraint
|
|
1329
|
+
// decides the target at instantiation).
|
|
1330
|
+
_checkTypeParamConversion(tp, argTypes, expr) {
|
|
1331
|
+
expr._isTypeConversion = true;
|
|
1332
|
+
expr._conversionTargetType = tp;
|
|
1333
|
+
if (expr.args.length !== 1) {
|
|
1334
|
+
return this.err(
|
|
1335
|
+
`cannot convert ${expr.args.length} arguments to type ${typeStr(tp)}`,
|
|
1336
|
+
expr,
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
if (isNumeric(argTypes[0]) && !this._constraintHasTypeSet(tp.constraint))
|
|
1340
|
+
return this.err(
|
|
1341
|
+
`cannot convert ${typeStr(argTypes[0])} to type ${typeStr(tp)}: ${typeStr(tp)} is not constrained to a set of convertible types`,
|
|
1342
|
+
expr,
|
|
1343
|
+
);
|
|
1344
|
+
if (
|
|
1345
|
+
!isNumeric(argTypes[0]) &&
|
|
1346
|
+
!isAny(argTypes[0]) &&
|
|
1347
|
+
argTypes[0]?.kind !== "typeParam"
|
|
1348
|
+
)
|
|
1349
|
+
this.err(
|
|
1350
|
+
`cannot convert ${typeStr(argTypes[0])} to ${typeStr(tp)}`,
|
|
1351
|
+
expr,
|
|
1352
|
+
);
|
|
1353
|
+
return tp;
|
|
1354
|
+
},
|
|
1355
|
+
|
|
1356
|
+
// A conversion to a type parameter needs a constraint that lists types
|
|
1357
|
+
// (`~int | ~float64`, or an interface embedding such a union); `any`,
|
|
1358
|
+
// `comparable` and method-only constraints allow every type argument.
|
|
1359
|
+
_constraintHasTypeSet(constraint) {
|
|
1360
|
+
if (!constraint) return true;
|
|
1361
|
+
if (constraint.kind === "UnionConstraint") return true;
|
|
1362
|
+
const c = this.resolveType(constraint);
|
|
1363
|
+
const base = c?.kind === "named" ? this.resolveType(c.underlying) : c;
|
|
1364
|
+
if (base?.kind !== "interface") return false;
|
|
1365
|
+
if (base.unionConstraint) return true;
|
|
1366
|
+
return (base._typeSetEmbeds ?? []).some((e) =>
|
|
1367
|
+
this._constraintHasTypeSet(e),
|
|
1368
|
+
);
|
|
692
1369
|
},
|
|
693
1370
|
|
|
694
1371
|
checkCall(expr, scope) {
|
|
1372
|
+
expr.func._typeRefOk = true;
|
|
695
1373
|
let fnType = this.checkExpr(expr.func, scope);
|
|
1374
|
+
// `slices.Sort[[]int](xs)` on an emitter-backed (non-generic) stdlib
|
|
1375
|
+
// function: the type arguments are inferred, so call it as `slices.Sort(xs)`.
|
|
1376
|
+
if (
|
|
1377
|
+
expr.func.kind === "InstantiationExpr" &&
|
|
1378
|
+
!expr.func._genericFnType &&
|
|
1379
|
+
expr.func.expr?.kind === "SelectorExpr" &&
|
|
1380
|
+
fnType?.kind === "func"
|
|
1381
|
+
)
|
|
1382
|
+
expr.func = expr.func.expr;
|
|
696
1383
|
this._checkSharedAllocSite(expr, scope);
|
|
697
1384
|
this._checkBoundaryCallInLoop(expr, scope);
|
|
698
1385
|
if (fnType.kind === "builtin") {
|
|
@@ -707,6 +1394,9 @@ export const expressionCheckMethods = {
|
|
|
707
1394
|
const argTypes = this._resolveCallArgTypes(expr, scope);
|
|
708
1395
|
|
|
709
1396
|
if (fnType?._tainted) return TAINTED_ANY;
|
|
1397
|
+
// `any(x)` / `error(x)` / `Iface(x)`: box x as an implicit conversion would.
|
|
1398
|
+
if (expr.func._isTypeRef && expr.args.length === 1 && argTypes[0])
|
|
1399
|
+
this._markIfaceBox(fnType, argTypes[0], expr.args[0]);
|
|
710
1400
|
if (isAny(fnType)) return ANY;
|
|
711
1401
|
|
|
712
1402
|
// Named type used as a constructor/conversion: Greeter(fn), NodeFunc(fn)
|
|
@@ -725,13 +1415,17 @@ export const expressionCheckMethods = {
|
|
|
725
1415
|
return fnType;
|
|
726
1416
|
}
|
|
727
1417
|
|
|
1418
|
+
if (fnType.kind === "typeParam" && expr.func._isTypeRef) {
|
|
1419
|
+
return this._checkTypeParamConversion(fnType, argTypes, expr);
|
|
1420
|
+
}
|
|
1421
|
+
|
|
728
1422
|
fnType = this._resolveCallFnType(fnType, argTypes, expr, scope);
|
|
729
1423
|
if (!fnType) return ANY;
|
|
730
1424
|
if (fnType.kind !== "func") {
|
|
731
1425
|
return this.err(`Cannot call non-function type ${typeStr(fnType)}`, expr);
|
|
732
1426
|
}
|
|
733
1427
|
|
|
734
|
-
this._checkCallArgs(fnType, argTypes, expr);
|
|
1428
|
+
this._checkCallArgs(fnType, argTypes, expr, scope);
|
|
735
1429
|
|
|
736
1430
|
// Pseudo-generic stdlib helpers (maps.Keys, slices.Clone, ...) declare
|
|
737
1431
|
// how their result derives from the first argument.
|
|
@@ -797,19 +1491,26 @@ export const expressionCheckMethods = {
|
|
|
797
1491
|
return under ? arg : null;
|
|
798
1492
|
case "elemOfArg0":
|
|
799
1493
|
return under?.elem ?? null;
|
|
800
|
-
case "keysOfArg0":
|
|
801
|
-
return under?.key ? { kind: "slice", elem: under.key } : null;
|
|
802
|
-
case "valuesOfArg0": {
|
|
803
|
-
const v = under?.value ?? under?.elem;
|
|
804
|
-
return v ? { kind: "slice", elem: v } : null;
|
|
805
|
-
}
|
|
806
1494
|
default:
|
|
807
1495
|
return null;
|
|
808
1496
|
}
|
|
809
1497
|
},
|
|
810
1498
|
|
|
1499
|
+
// Argument types for inference: a spread argument `xs...` stands for its
|
|
1500
|
+
// elements, which is what the variadic parameter type describes.
|
|
1501
|
+
_inferArgTypes(expr, argTypes) {
|
|
1502
|
+
return argTypes.map((t, i) => {
|
|
1503
|
+
if (!expr.args?.[i]?._spread) return t;
|
|
1504
|
+
const u = this.resolveType(t?.kind === "named" ? t.underlying : t);
|
|
1505
|
+
return u?.kind === "slice" ? u.elem : t;
|
|
1506
|
+
});
|
|
1507
|
+
},
|
|
1508
|
+
|
|
811
1509
|
_resolveGenericFnType(fnType, argTypes, expr, scope) {
|
|
812
|
-
const typeArgs = this.inferTypeArgs(
|
|
1510
|
+
const typeArgs = this.inferTypeArgs(
|
|
1511
|
+
fnType,
|
|
1512
|
+
this._inferArgTypes(expr, argTypes),
|
|
1513
|
+
);
|
|
813
1514
|
if (!typeArgs) {
|
|
814
1515
|
this.err(
|
|
815
1516
|
`Cannot infer type arguments for generic function '${fnType.name}'`,
|
|
@@ -848,8 +1549,28 @@ export const expressionCheckMethods = {
|
|
|
848
1549
|
);
|
|
849
1550
|
},
|
|
850
1551
|
|
|
851
|
-
|
|
1552
|
+
// WASM packs a variadic call's trailing arguments into a slice, which it
|
|
1553
|
+
// cannot do for `f(g())` yet. Emitter-backed stdlib calls (`fmt.X`) are
|
|
1554
|
+
// not packed; GoFront and Go-source package functions are.
|
|
1555
|
+
_checkMultiForwardVariadic(fnType, expr) {
|
|
1556
|
+
if (!expr._multiForward || !fnType.variadic) return;
|
|
1557
|
+
if (this.target !== "wasm" && this.target !== "both") return;
|
|
1558
|
+
const ns =
|
|
1559
|
+
expr.func?.kind === "SelectorExpr" ? expr.func.expr?._type : null;
|
|
1560
|
+
const emitterBacked =
|
|
1561
|
+
ns?.kind === "namespace" &&
|
|
1562
|
+
!ns._gofront &&
|
|
1563
|
+
!ns._mangle?.has(expr.func.field);
|
|
1564
|
+
if (emitterBacked) return;
|
|
1565
|
+
this.err(
|
|
1566
|
+
"passing multiple return values to a variadic function is not supported in wasm packages; assign them to variables first",
|
|
1567
|
+
expr,
|
|
1568
|
+
);
|
|
1569
|
+
},
|
|
1570
|
+
|
|
1571
|
+
_checkCallArgs(fnType, argTypes, expr, scope = null) {
|
|
852
1572
|
this._checkArgCount(fnType, argTypes, expr);
|
|
1573
|
+
this._checkMultiForwardVariadic(fnType, expr);
|
|
853
1574
|
const maxLen = fnType.variadic
|
|
854
1575
|
? argTypes.length
|
|
855
1576
|
: Math.min(fnType.params.length, argTypes.length);
|
|
@@ -858,11 +1579,61 @@ export const expressionCheckMethods = {
|
|
|
858
1579
|
fnType.variadic && i >= fnType.params.length - 1
|
|
859
1580
|
? fnType.params.length - 1
|
|
860
1581
|
: i;
|
|
861
|
-
if (expr.args[i]?._spread)
|
|
1582
|
+
if (expr.args[i]?._spread) {
|
|
1583
|
+
this._rejectSeqAsSlice(argTypes[i], expr.args[i], "a spread argument");
|
|
1584
|
+
continue;
|
|
1585
|
+
}
|
|
862
1586
|
const targetParam = fnType.params[paramIdx];
|
|
863
1587
|
const node = expr._multiForward ? null : expr.args[i];
|
|
864
|
-
this.assertAssignable(targetParam, argTypes[i], node);
|
|
1588
|
+
this.assertAssignable(targetParam, argTypes[i], node, "argument");
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
this._checkSimdLaneArgs(expr, scope);
|
|
1592
|
+
},
|
|
1593
|
+
|
|
1594
|
+
// Lane, WithLane, Swizzle and Shuffle take constant lane indices.
|
|
1595
|
+
_checkSimdLaneArgs(expr, scope) {
|
|
1596
|
+
if (expr.func?.kind !== "SelectorExpr") return;
|
|
1597
|
+
const recv = expr.func.expr?._type;
|
|
1598
|
+
const field = expr.func.field;
|
|
1599
|
+
let first = 0;
|
|
1600
|
+
let count = 0;
|
|
1601
|
+
let max = 3;
|
|
1602
|
+
if (recv?.kind === "namespace" && recv.members?.Mask32x4) {
|
|
1603
|
+
if (field !== "Shuffle") return;
|
|
1604
|
+
first = 2;
|
|
1605
|
+
count = 4;
|
|
1606
|
+
max = 7;
|
|
1607
|
+
} else if (this._isSimdType(recv)) {
|
|
1608
|
+
if (field === "Lane" || field === "WithLane") count = 1;
|
|
1609
|
+
else if (field === "Swizzle") count = 4;
|
|
1610
|
+
else return;
|
|
1611
|
+
} else {
|
|
1612
|
+
return;
|
|
865
1613
|
}
|
|
1614
|
+
for (let i = first; i < first + count; i++) {
|
|
1615
|
+
const arg = expr.args[i];
|
|
1616
|
+
if (!arg) continue;
|
|
1617
|
+
const idx = this._constIntValue(arg, scope);
|
|
1618
|
+
if (idx !== null) arg._constInt = idx;
|
|
1619
|
+
if (idx === null || idx < 0 || idx > max) {
|
|
1620
|
+
this.err(
|
|
1621
|
+
`lane index must be a constant integer in range [0, ${max}]`,
|
|
1622
|
+
arg,
|
|
1623
|
+
);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
},
|
|
1627
|
+
|
|
1628
|
+
_isSimdType(t) {
|
|
1629
|
+
return (
|
|
1630
|
+
t?.name === "simd.F32x4" ||
|
|
1631
|
+
t?.name === "F32x4" ||
|
|
1632
|
+
t?.name === "simd.Mask32x4" ||
|
|
1633
|
+
t?.name === "Mask32x4" ||
|
|
1634
|
+
t?.underlying?.kind === "simd" ||
|
|
1635
|
+
t?.kind === "simd"
|
|
1636
|
+
);
|
|
866
1637
|
},
|
|
867
1638
|
|
|
868
1639
|
checkBuiltin(name, expr, argTypes, scope) {
|
|
@@ -882,6 +1653,9 @@ export const expressionCheckMethods = {
|
|
|
882
1653
|
},
|
|
883
1654
|
|
|
884
1655
|
_checkBuiltinLen(name, expr, argTypes) {
|
|
1656
|
+
this._rejectSeqAsSlice(argTypes[0], expr.args?.[0], `${name}()`);
|
|
1657
|
+
if (expr.args?.length === 1)
|
|
1658
|
+
argTypes[0] = this._derefArrayPtr(expr.args, 0, argTypes[0]);
|
|
885
1659
|
if (name === "len") {
|
|
886
1660
|
const size = this._constLenFromType(argTypes[0]);
|
|
887
1661
|
if (size != null) expr._constLen = size;
|
|
@@ -911,6 +1685,17 @@ export const expressionCheckMethods = {
|
|
|
911
1685
|
}
|
|
912
1686
|
if (crt.kind === "untyped" && cit.kind === "untyped")
|
|
913
1687
|
return UNTYPED_COMPLEX;
|
|
1688
|
+
if (crt.name === "float32" && cit.name === "float32") return COMPLEX64;
|
|
1689
|
+
if (crt.name === "float64" && cit.name === "float64") return COMPLEX128;
|
|
1690
|
+
if (crt.name === "float32" || cit.name === "float32") {
|
|
1691
|
+
if (crt.name === "float64" || cit.name === "float64") {
|
|
1692
|
+
this.err(
|
|
1693
|
+
`invalid complex call: mismatched float types (${typeStr(crt)} and ${typeStr(cit)})`,
|
|
1694
|
+
expr,
|
|
1695
|
+
);
|
|
1696
|
+
}
|
|
1697
|
+
return COMPLEX64;
|
|
1698
|
+
}
|
|
914
1699
|
return COMPLEX128;
|
|
915
1700
|
},
|
|
916
1701
|
|
|
@@ -925,6 +1710,7 @@ export const expressionCheckMethods = {
|
|
|
925
1710
|
return ANY;
|
|
926
1711
|
}
|
|
927
1712
|
if (zt.kind === "untyped") return UNTYPED_FLOAT;
|
|
1713
|
+
if (isComplex64(zt)) return FLOAT32;
|
|
928
1714
|
return FLOAT64;
|
|
929
1715
|
},
|
|
930
1716
|
|
|
@@ -956,9 +1742,16 @@ export const expressionCheckMethods = {
|
|
|
956
1742
|
: this.resolveTypeNode(expr.typeExpr, scope);
|
|
957
1743
|
const base = t.kind === "named" ? t.underlying : t;
|
|
958
1744
|
const typeNode = expr.typeExpr;
|
|
1745
|
+
if (expr.typeExpr === null && hintType && base?.kind === "basic") {
|
|
1746
|
+
this.err(`invalid composite literal type ${typeStr(t)}`, expr);
|
|
1747
|
+
return t;
|
|
1748
|
+
}
|
|
1749
|
+
if (this._isSliceOrArray(base)) this._checkArrayLitKeys(expr, scope);
|
|
959
1750
|
this._inferArraySize(expr, typeNode, base);
|
|
960
1751
|
if (base?.kind === "struct")
|
|
961
1752
|
return this._checkStructCompositeLit(expr, scope, t, base);
|
|
1753
|
+
if (base?.kind === "simd")
|
|
1754
|
+
return this._checkSimdCompositeLit(expr, scope, t, base);
|
|
962
1755
|
if (this._isSliceOrArray(base))
|
|
963
1756
|
return this._checkSliceArrayCompositeLit(expr, scope, t, base, typeNode);
|
|
964
1757
|
if (base?.kind === "map")
|
|
@@ -966,8 +1759,38 @@ export const expressionCheckMethods = {
|
|
|
966
1759
|
return this._checkUnknownCompositeLit(expr, scope, t);
|
|
967
1760
|
},
|
|
968
1761
|
|
|
1762
|
+
_checkSimdCompositeLit(expr, scope, t, base) {
|
|
1763
|
+
if (base.simd === "Mask32x4") {
|
|
1764
|
+
if (expr.elems.length > 0)
|
|
1765
|
+
this.err("simd.Mask32x4 literal cannot have values", expr);
|
|
1766
|
+
return t;
|
|
1767
|
+
}
|
|
1768
|
+
if (expr.elems.length > 4) {
|
|
1769
|
+
this.err("too many values in simd.F32x4 literal", expr);
|
|
1770
|
+
}
|
|
1771
|
+
for (const elem of expr.elems) {
|
|
1772
|
+
const vt = this.checkExpr(elem, scope);
|
|
1773
|
+
this.assertAssignable(base.elem ?? FLOAT32, vt, elem);
|
|
1774
|
+
}
|
|
1775
|
+
return t;
|
|
1776
|
+
},
|
|
1777
|
+
|
|
969
1778
|
_checkStructPositionalElems(expr, scope, t, base) {
|
|
970
|
-
const fieldNames =
|
|
1779
|
+
const fieldNames = base._structFields
|
|
1780
|
+
? base._structFields.flatMap((f) =>
|
|
1781
|
+
f.embedded
|
|
1782
|
+
? [
|
|
1783
|
+
(
|
|
1784
|
+
(f.type?.kind === "pointer"
|
|
1785
|
+
? f.type.base?.name
|
|
1786
|
+
: f.type?.name) ?? ""
|
|
1787
|
+
)
|
|
1788
|
+
.split(".")
|
|
1789
|
+
.pop(),
|
|
1790
|
+
]
|
|
1791
|
+
: f.names,
|
|
1792
|
+
)
|
|
1793
|
+
: [...(base.fields?.keys() ?? [])];
|
|
971
1794
|
for (let i = 0; i < expr.elems.length; i++) {
|
|
972
1795
|
const elem = expr.elems[i];
|
|
973
1796
|
const fieldName = fieldNames[i];
|
|
@@ -987,21 +1810,19 @@ export const expressionCheckMethods = {
|
|
|
987
1810
|
for (const elem of expr.elems) {
|
|
988
1811
|
if (elem.kind === "KeyValueExpr") {
|
|
989
1812
|
const keyName = elem.key.name;
|
|
990
|
-
const
|
|
1813
|
+
const embed = base._embeds?.find((e) => {
|
|
1814
|
+
const inner = e.kind === "pointer" ? e.base : e;
|
|
1815
|
+
const name =
|
|
1816
|
+
inner.kind === "named" ? inner.name.split(".").pop() : null;
|
|
1817
|
+
return name === keyName;
|
|
1818
|
+
});
|
|
1819
|
+
if (embed) elem._isEmbedInit = true;
|
|
1820
|
+
const fieldType = base.fields?.get(keyName) ?? embed;
|
|
991
1821
|
if (fieldType) {
|
|
992
1822
|
const vt = this.checkExpr(elem.value, scope);
|
|
993
1823
|
this.assertAssignable(fieldType, vt, elem.value);
|
|
994
1824
|
} else {
|
|
995
|
-
|
|
996
|
-
(e) => (e.kind === "named" ? e.name : null) === keyName,
|
|
997
|
-
);
|
|
998
|
-
if (embed) {
|
|
999
|
-
const vt = this.checkExpr(elem.value, scope);
|
|
1000
|
-
this.assertAssignable(embed, vt, elem.value);
|
|
1001
|
-
elem._isEmbedInit = true;
|
|
1002
|
-
} else {
|
|
1003
|
-
this.err(`Unknown field '${keyName}'`, elem.key);
|
|
1004
|
-
}
|
|
1825
|
+
this.err(`Unknown field '${keyName}'`, elem.key);
|
|
1005
1826
|
}
|
|
1006
1827
|
} else {
|
|
1007
1828
|
this.checkExpr(elem, scope);
|
|
@@ -1026,8 +1847,19 @@ export const expressionCheckMethods = {
|
|
|
1026
1847
|
const et = this.checkCompositeLit(elem, scope, base.elem);
|
|
1027
1848
|
elem._type = et;
|
|
1028
1849
|
} else if (elem.kind === "KeyValueExpr") {
|
|
1029
|
-
|
|
1030
|
-
|
|
1850
|
+
if (
|
|
1851
|
+
elem.value.kind === "CompositeLit" &&
|
|
1852
|
+
elem.value.typeExpr === null
|
|
1853
|
+
) {
|
|
1854
|
+
elem.value._type = this.checkCompositeLit(
|
|
1855
|
+
elem.value,
|
|
1856
|
+
scope,
|
|
1857
|
+
base.elem,
|
|
1858
|
+
);
|
|
1859
|
+
} else {
|
|
1860
|
+
const vt = this.checkExpr(elem.value, scope);
|
|
1861
|
+
this.assertAssignable(base.elem, vt, elem.value);
|
|
1862
|
+
}
|
|
1031
1863
|
} else {
|
|
1032
1864
|
const et = this.checkExpr(elem, scope);
|
|
1033
1865
|
this.assertAssignable(base.elem, et, elem);
|
|
@@ -1051,7 +1883,13 @@ export const expressionCheckMethods = {
|
|
|
1051
1883
|
_checkMapCompositeLit(expr, scope, t, base) {
|
|
1052
1884
|
for (const elem of expr.elems) {
|
|
1053
1885
|
if (elem.kind === "KeyValueExpr") {
|
|
1054
|
-
|
|
1886
|
+
let kt;
|
|
1887
|
+
if (elem.key.kind === "CompositeLit" && elem.key.typeExpr === null) {
|
|
1888
|
+
kt = this.checkCompositeLit(elem.key, scope, base.key);
|
|
1889
|
+
elem.key._type = kt;
|
|
1890
|
+
} else {
|
|
1891
|
+
kt = this.checkExpr(elem.key, scope);
|
|
1892
|
+
}
|
|
1055
1893
|
// value may be an implicit composite lit
|
|
1056
1894
|
let vt;
|
|
1057
1895
|
if (
|
|
@@ -1064,6 +1902,7 @@ export const expressionCheckMethods = {
|
|
|
1064
1902
|
vt = this.checkExpr(elem.value, scope);
|
|
1065
1903
|
}
|
|
1066
1904
|
this.assertAssignable(base.key, kt, elem.key);
|
|
1905
|
+
this._markIfaceKey(base, elem.key, kt);
|
|
1067
1906
|
this.assertAssignable(base.value, vt, elem.value);
|
|
1068
1907
|
}
|
|
1069
1908
|
}
|
|
@@ -1071,15 +1910,37 @@ export const expressionCheckMethods = {
|
|
|
1071
1910
|
},
|
|
1072
1911
|
};
|
|
1073
1912
|
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1913
|
+
// Builtins whose calls may be constant expressions (`len("abc")`).
|
|
1914
|
+
const CONST_BUILTINS = new Set([
|
|
1915
|
+
"len",
|
|
1916
|
+
"cap",
|
|
1917
|
+
"real",
|
|
1918
|
+
"imag",
|
|
1919
|
+
"complex",
|
|
1920
|
+
"min",
|
|
1921
|
+
"max",
|
|
1922
|
+
]);
|
|
1923
|
+
|
|
1924
|
+
// Source-like text of a literal key for diagnostics (`k`, `-1`, `K + 1`).
|
|
1925
|
+
function constKeyText(e) {
|
|
1926
|
+
switch (e?.kind) {
|
|
1927
|
+
case "Ident":
|
|
1928
|
+
return e.name;
|
|
1929
|
+
case "BasicLit":
|
|
1930
|
+
return String(e.value);
|
|
1931
|
+
case "UnaryExpr":
|
|
1932
|
+
return `${e.op}${constKeyText(e.operand)}`;
|
|
1933
|
+
case "BinaryExpr":
|
|
1934
|
+
return `${constKeyText(e.left)} ${e.op} ${constKeyText(e.right)}`;
|
|
1935
|
+
case "ParenExpr":
|
|
1936
|
+
return `(${constKeyText(e.expr)})`;
|
|
1937
|
+
case "SelectorExpr":
|
|
1938
|
+
return `${constKeyText(e.expr)}.${e.field}`;
|
|
1939
|
+
default:
|
|
1940
|
+
return "expression";
|
|
1083
1941
|
}
|
|
1084
|
-
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
function computeMaxIndex(elems) {
|
|
1945
|
+
return compositeLitMaxIndex(elems);
|
|
1085
1946
|
}
|