gofront 1.7.0 → 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 +13 -13
- package/package.json +8 -37
- package/src/backend/js/expressions.js +1341 -148
- package/src/backend/js/fmt-runtime.js +573 -0
- package/src/backend/js/index.js +375 -57
- package/src/backend/js/runtime.js +1460 -133
- package/src/backend/js/statements.js +900 -86
- 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 +2 -1
- package/src/backend/js/stdlib/fmt.js +349 -10
- package/src/backend/js/stdlib/index.js +8 -3
- package/src/backend/js/stdlib/json.js +247 -52
- 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 +10 -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 +436 -80
- package/src/backend/wasm/emit-builtins.js +303 -13
- package/src/backend/wasm/emit-exprs.js +1397 -203
- package/src/backend/wasm/emit-maps.js +61 -13
- package/src/backend/wasm/emit-stmts.js +1359 -164
- package/src/backend/wasm/emit.js +512 -77
- package/src/backend/wasm/encode.js +43 -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/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 +0 -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 +293 -28
- package/src/backend/wasm/stdlib/time.js +1041 -12
- package/src/backend/wasm/types.js +200 -15
- package/src/backend/wasm/variadic.js +93 -0
- package/src/backend/wasm/wat.js +47 -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} +3 -4
- package/src/compiler.js +107 -269
- 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 -1
- package/src/lower/captures.js +176 -32
- 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/{templ-parser.js → parser/templ.js} +138 -15
- package/src/parser/types.js +69 -9
- package/src/resolver.js +22 -1
- package/src/typechecker/assignability.js +176 -17
- package/src/typechecker/const-fold.js +356 -0
- package/src/typechecker/constants.js +256 -0
- package/src/typechecker/expressions.js +931 -122
- package/src/typechecker/index.js +459 -67
- package/src/typechecker/init-order.js +330 -0
- package/src/typechecker/resolve.js +451 -56
- package/src/typechecker/statements.js +90 -9
- package/src/typechecker/stdlib/core.js +333 -55
- package/src/typechecker/stdlib/excluded.js +898 -0
- package/src/typechecker/stdlib/extended.js +322 -26
- package/src/typechecker/{stdlib.js → stdlib/index.js} +5 -5
- package/src/typechecker/stdlib/simd.js +65 -15
- package/src/typechecker/termination.js +1 -1
- package/src/typechecker/types.js +149 -25
- 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,13 +457,33 @@ 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
|
|
|
@@ -238,51 +500,147 @@ export const expressionCheckMethods = {
|
|
|
238
500
|
return base?.methods?.get(field) ?? baseType.methods?.get(field);
|
|
239
501
|
},
|
|
240
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
|
+
|
|
241
618
|
_checkSelectorExpr(expr, scope) {
|
|
619
|
+
expr.expr._typeRefOk = true;
|
|
242
620
|
const baseType = this.checkExpr(expr.expr, scope);
|
|
243
621
|
if (baseType?._tainted) return TAINTED_ANY;
|
|
622
|
+
const exclErr = this._checkExcludedSelectorExpr(expr, baseType);
|
|
623
|
+
if (exclErr) return exclErr;
|
|
244
624
|
if (expr.expr._isTypeRef) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const methodType = this._resolveMethodExprType(
|
|
248
|
-
baseType,
|
|
249
|
-
base,
|
|
250
|
-
expr.field,
|
|
251
|
-
);
|
|
252
|
-
if (methodType) {
|
|
253
|
-
expr._isMethodExpr = true;
|
|
254
|
-
expr._methodExprRecv = baseType;
|
|
255
|
-
return {
|
|
256
|
-
kind: "func",
|
|
257
|
-
params: [baseType, ...(methodType.params ?? [])],
|
|
258
|
-
returns: methodType.returns ?? [],
|
|
259
|
-
variadic: methodType.variadic,
|
|
260
|
-
};
|
|
261
|
-
}
|
|
625
|
+
const mExpr = this._checkMethodExprSelector(expr, baseType);
|
|
626
|
+
if (mExpr) return mExpr;
|
|
262
627
|
}
|
|
263
628
|
const ft = this.fieldType(baseType, expr.field, expr);
|
|
264
|
-
if (ft?.kind === "func")
|
|
265
|
-
|
|
266
|
-
base = this.resolveType(base);
|
|
267
|
-
if (this._isStructOrNamedMethod(baseType, base, expr.field))
|
|
268
|
-
expr._isMethodValue = true;
|
|
269
|
-
}
|
|
629
|
+
if (ft?.kind === "func")
|
|
630
|
+
this._markMethodValueSelector(expr, baseType, ft, scope);
|
|
270
631
|
if (
|
|
271
632
|
baseType?.kind === "namespace" &&
|
|
272
633
|
this.types.has(`${baseType.name}.${expr.field}`) &&
|
|
273
|
-
|
|
274
|
-
baseType.name === "binary" &&
|
|
275
|
-
(expr.field === "LittleEndian" || expr.field === "BigEndian")
|
|
276
|
-
)
|
|
634
|
+
baseType.name !== "binary"
|
|
277
635
|
) {
|
|
278
636
|
expr._isTypeRef = true;
|
|
279
637
|
}
|
|
280
638
|
return ft;
|
|
281
639
|
},
|
|
282
640
|
|
|
283
|
-
_checkArrayBoundsExpr(btu, bt, expr) {
|
|
641
|
+
_checkArrayBoundsExpr(btu, bt, expr, scope) {
|
|
284
642
|
if (btu.kind !== "array" || btu.size == null) return;
|
|
285
|
-
const idx = this._constIntValue(expr.index);
|
|
643
|
+
const idx = this._constIntValue(expr.index, scope);
|
|
286
644
|
if (idx === null) return;
|
|
287
645
|
if (idx < 0)
|
|
288
646
|
this.err(`invalid array index ${idx} (index must not be negative)`, expr);
|
|
@@ -293,26 +651,97 @@ export const expressionCheckMethods = {
|
|
|
293
651
|
);
|
|
294
652
|
},
|
|
295
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
|
+
|
|
296
717
|
_checkIndexExpr(expr, scope) {
|
|
297
|
-
const
|
|
298
|
-
|
|
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);
|
|
299
730
|
if (bt?._tainted) return TAINTED_ANY;
|
|
300
731
|
if (isAny(bt)) return ANY;
|
|
301
|
-
const btu =
|
|
302
|
-
bt.kind === "named"
|
|
303
|
-
? (this.resolveType(bt.underlying) ?? bt.underlying)
|
|
304
|
-
: bt;
|
|
732
|
+
const btu = this._deepUnderlying(bt);
|
|
305
733
|
if (btu.kind === "slice" || btu.kind === "array") {
|
|
306
|
-
this._checkArrayBoundsExpr(btu, bt, expr);
|
|
734
|
+
this._checkArrayBoundsExpr(btu, bt, expr, scope);
|
|
307
735
|
return btu.elem;
|
|
308
736
|
}
|
|
309
737
|
if (btu.kind === "map") {
|
|
310
738
|
expr._mapValueType = btu.value;
|
|
739
|
+
this._markIfaceKey(btu, expr.index, indexType);
|
|
311
740
|
return btu.value;
|
|
312
741
|
}
|
|
313
742
|
if (btu.kind === "shared") {
|
|
314
743
|
expr._sharedElem = btu.elem;
|
|
315
|
-
const idx = this._constIntValue(expr.index);
|
|
744
|
+
const idx = this._constIntValue(expr.index, scope);
|
|
316
745
|
if (idx !== null && idx < 0)
|
|
317
746
|
this.err(`invalid index ${idx} (index must not be negative)`, expr);
|
|
318
747
|
return btu.elem;
|
|
@@ -321,6 +750,30 @@ export const expressionCheckMethods = {
|
|
|
321
750
|
return this.err(`Cannot index type ${typeStr(bt)}`, expr);
|
|
322
751
|
},
|
|
323
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
|
+
|
|
324
777
|
// copy(dst, src): element types must be identical when either side is a
|
|
325
778
|
// shared buffer (the wasm emitter uses one element type for both sides).
|
|
326
779
|
_checkBuiltinCopy(e, at) {
|
|
@@ -346,15 +799,33 @@ export const expressionCheckMethods = {
|
|
|
346
799
|
return INT;
|
|
347
800
|
},
|
|
348
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
|
+
|
|
349
815
|
_checkSliceExpr(expr, scope) {
|
|
350
|
-
const bt = this.
|
|
816
|
+
const bt = this._derefArrayPtr(
|
|
817
|
+
expr,
|
|
818
|
+
"expr",
|
|
819
|
+
this.checkExpr(expr.expr, scope),
|
|
820
|
+
);
|
|
351
821
|
if (expr.low) this.checkExpr(expr.low, scope);
|
|
352
822
|
if (expr.high) this.checkExpr(expr.high, scope);
|
|
353
823
|
if (expr.max) this.checkExpr(expr.max, scope);
|
|
354
824
|
if (bt?._tainted) return TAINTED_ANY;
|
|
355
825
|
if (isAny(bt)) return ANY;
|
|
356
|
-
|
|
357
|
-
|
|
826
|
+
const btu = this._deepUnderlying(bt);
|
|
827
|
+
if (btu.kind === "slice" || btu.kind === "array")
|
|
828
|
+
return { kind: "slice", elem: btu.elem };
|
|
358
829
|
if (isString(bt)) return STRING;
|
|
359
830
|
if (sharedInfo(bt))
|
|
360
831
|
return this.err(
|
|
@@ -387,7 +858,7 @@ export const expressionCheckMethods = {
|
|
|
387
858
|
}
|
|
388
859
|
const inner = new Scope(scope);
|
|
389
860
|
for (const p of expr.params)
|
|
390
|
-
inner.define(p.name, p.type ? this.
|
|
861
|
+
inner.define(p.name, p.type ? this._paramType(p, scope) : ANY);
|
|
391
862
|
const ret = expr.returnType
|
|
392
863
|
? this.resolveTypeNode(expr.returnType, scope)
|
|
393
864
|
: VOID;
|
|
@@ -404,6 +875,7 @@ export const expressionCheckMethods = {
|
|
|
404
875
|
kind: "func",
|
|
405
876
|
params: paramTypes,
|
|
406
877
|
returns: [ret],
|
|
878
|
+
variadic: expr.params.at(-1)?.variadic === true,
|
|
407
879
|
async: expr.async,
|
|
408
880
|
};
|
|
409
881
|
},
|
|
@@ -429,6 +901,19 @@ export const expressionCheckMethods = {
|
|
|
429
901
|
);
|
|
430
902
|
},
|
|
431
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
|
+
|
|
432
917
|
_isInvalidArrayConv(tgt, src, srcType) {
|
|
433
918
|
return tgt?.kind === "array" && src?.kind !== "array" && !isAny(srcType);
|
|
434
919
|
},
|
|
@@ -436,6 +921,8 @@ export const expressionCheckMethods = {
|
|
|
436
921
|
_checkTypeConversion(expr, scope) {
|
|
437
922
|
const srcType = this.checkExpr(expr.expr, scope);
|
|
438
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);
|
|
439
926
|
const srcResolved =
|
|
440
927
|
srcType?.kind === "named" ? srcType.underlying : srcType;
|
|
441
928
|
const tgtResolved =
|
|
@@ -456,6 +943,8 @@ export const expressionCheckMethods = {
|
|
|
456
943
|
expr,
|
|
457
944
|
);
|
|
458
945
|
}
|
|
946
|
+
if (tgtResolved?.kind === "slice")
|
|
947
|
+
this._rejectSeqAsSlice(srcType, expr.expr, "a slice conversion");
|
|
459
948
|
if (this._isMutualArraySlice(tgtResolved, srcResolved)) {
|
|
460
949
|
this._checkElemTypeCompatible(
|
|
461
950
|
tgtResolved.elem,
|
|
@@ -495,6 +984,8 @@ export const expressionCheckMethods = {
|
|
|
495
984
|
);
|
|
496
985
|
return;
|
|
497
986
|
}
|
|
987
|
+
if (tgtResolved?.kind === "slice")
|
|
988
|
+
this._rejectSeqAsSlice(srcType, expr.args?.[0], "a slice conversion");
|
|
498
989
|
if (this._isMutualArraySlice(tgtResolved, srcResolved)) {
|
|
499
990
|
this._checkElemTypeCompatible(
|
|
500
991
|
tgtResolved.elem,
|
|
@@ -513,9 +1004,15 @@ export const expressionCheckMethods = {
|
|
|
513
1004
|
return;
|
|
514
1005
|
}
|
|
515
1006
|
if (typeStr(srcResolved) === typeStr(tgtResolved)) return;
|
|
1007
|
+
if (this._sameStructFields(srcResolved, tgtResolved)) return;
|
|
516
1008
|
if (isNumeric(srcResolved) && isNumeric(tgtResolved)) return;
|
|
517
1009
|
if (isString(srcResolved) && isString(tgtResolved)) return;
|
|
518
1010
|
if (isBool(srcResolved) && isBool(tgtResolved)) return;
|
|
1011
|
+
if (
|
|
1012
|
+
(isComplex(srcResolved) || isNumeric(srcResolved)) &&
|
|
1013
|
+
isComplex(tgtResolved)
|
|
1014
|
+
)
|
|
1015
|
+
return;
|
|
519
1016
|
if (
|
|
520
1017
|
isString(srcResolved) &&
|
|
521
1018
|
tgtResolved?.kind === "slice" &&
|
|
@@ -589,19 +1086,31 @@ export const expressionCheckMethods = {
|
|
|
589
1086
|
},
|
|
590
1087
|
|
|
591
1088
|
_checkRangeExpr(expr, scope) {
|
|
592
|
-
const collType = this.
|
|
1089
|
+
const collType = this._derefArrayPtr(
|
|
1090
|
+
expr,
|
|
1091
|
+
"expr",
|
|
1092
|
+
this.checkExpr(expr.expr, scope),
|
|
1093
|
+
);
|
|
593
1094
|
const resolved =
|
|
594
1095
|
collType?.kind === "named" ? collType.underlying : collType;
|
|
595
1096
|
return this._rangeCollTypeTuple(resolved, collType);
|
|
596
1097
|
},
|
|
597
1098
|
|
|
598
1099
|
_checkInstantiationExpr(expr, scope) {
|
|
1100
|
+
const typeRef = this._genericTypeRef(expr, scope);
|
|
1101
|
+
if (typeRef) return typeRef;
|
|
599
1102
|
// Generic instantiation: Foo[int] or Foo[int, string]
|
|
600
1103
|
const baseType = this.checkExpr(expr.expr, scope);
|
|
601
1104
|
if (baseType?.kind === "generic") {
|
|
602
1105
|
const typeArgs = expr.typeArgs.map((ta) =>
|
|
603
1106
|
this.resolveTypeNode(ta, scope),
|
|
604
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
|
+
}
|
|
605
1114
|
// Check constraints
|
|
606
1115
|
for (
|
|
607
1116
|
let i = 0;
|
|
@@ -625,9 +1134,28 @@ export const expressionCheckMethods = {
|
|
|
625
1134
|
// rewrite the node in place into an IndexExpr and check that instead.
|
|
626
1135
|
const indexExpr = this._instantiationAsIndex(expr, baseType);
|
|
627
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
|
+
);
|
|
628
1142
|
return baseType;
|
|
629
1143
|
},
|
|
630
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
|
+
|
|
631
1159
|
_instantiationAsIndex(expr, baseType) {
|
|
632
1160
|
if (!baseType || expr.typeArgs.length !== 1) return null;
|
|
633
1161
|
const ta = expr.typeArgs[0];
|
|
@@ -652,32 +1180,97 @@ export const expressionCheckMethods = {
|
|
|
652
1180
|
return expr;
|
|
653
1181
|
},
|
|
654
1182
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
if (expr.
|
|
658
|
-
|
|
659
|
-
if (
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
expr.
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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++;
|
|
676
1228
|
}
|
|
677
|
-
|
|
678
|
-
|
|
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;
|
|
679
1273
|
}
|
|
680
|
-
return null;
|
|
681
1274
|
},
|
|
682
1275
|
|
|
683
1276
|
_resolveCallArgTypes(expr, scope) {
|
|
@@ -705,11 +1298,88 @@ export const expressionCheckMethods = {
|
|
|
705
1298
|
if (!resolved) return null;
|
|
706
1299
|
fnType = resolved;
|
|
707
1300
|
}
|
|
708
|
-
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
|
+
);
|
|
709
1369
|
},
|
|
710
1370
|
|
|
711
1371
|
checkCall(expr, scope) {
|
|
1372
|
+
expr.func._typeRefOk = true;
|
|
712
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;
|
|
713
1383
|
this._checkSharedAllocSite(expr, scope);
|
|
714
1384
|
this._checkBoundaryCallInLoop(expr, scope);
|
|
715
1385
|
if (fnType.kind === "builtin") {
|
|
@@ -724,6 +1394,9 @@ export const expressionCheckMethods = {
|
|
|
724
1394
|
const argTypes = this._resolveCallArgTypes(expr, scope);
|
|
725
1395
|
|
|
726
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]);
|
|
727
1400
|
if (isAny(fnType)) return ANY;
|
|
728
1401
|
|
|
729
1402
|
// Named type used as a constructor/conversion: Greeter(fn), NodeFunc(fn)
|
|
@@ -742,6 +1415,10 @@ export const expressionCheckMethods = {
|
|
|
742
1415
|
return fnType;
|
|
743
1416
|
}
|
|
744
1417
|
|
|
1418
|
+
if (fnType.kind === "typeParam" && expr.func._isTypeRef) {
|
|
1419
|
+
return this._checkTypeParamConversion(fnType, argTypes, expr);
|
|
1420
|
+
}
|
|
1421
|
+
|
|
745
1422
|
fnType = this._resolveCallFnType(fnType, argTypes, expr, scope);
|
|
746
1423
|
if (!fnType) return ANY;
|
|
747
1424
|
if (fnType.kind !== "func") {
|
|
@@ -814,19 +1491,26 @@ export const expressionCheckMethods = {
|
|
|
814
1491
|
return under ? arg : null;
|
|
815
1492
|
case "elemOfArg0":
|
|
816
1493
|
return under?.elem ?? null;
|
|
817
|
-
case "keysOfArg0":
|
|
818
|
-
return under?.key ? { kind: "slice", elem: under.key } : null;
|
|
819
|
-
case "valuesOfArg0": {
|
|
820
|
-
const v = under?.value ?? under?.elem;
|
|
821
|
-
return v ? { kind: "slice", elem: v } : null;
|
|
822
|
-
}
|
|
823
1494
|
default:
|
|
824
1495
|
return null;
|
|
825
1496
|
}
|
|
826
1497
|
},
|
|
827
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
|
+
|
|
828
1509
|
_resolveGenericFnType(fnType, argTypes, expr, scope) {
|
|
829
|
-
const typeArgs = this.inferTypeArgs(
|
|
1510
|
+
const typeArgs = this.inferTypeArgs(
|
|
1511
|
+
fnType,
|
|
1512
|
+
this._inferArgTypes(expr, argTypes),
|
|
1513
|
+
);
|
|
830
1514
|
if (!typeArgs) {
|
|
831
1515
|
this.err(
|
|
832
1516
|
`Cannot infer type arguments for generic function '${fnType.name}'`,
|
|
@@ -865,8 +1549,28 @@ export const expressionCheckMethods = {
|
|
|
865
1549
|
);
|
|
866
1550
|
},
|
|
867
1551
|
|
|
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
|
+
|
|
868
1571
|
_checkCallArgs(fnType, argTypes, expr, scope = null) {
|
|
869
1572
|
this._checkArgCount(fnType, argTypes, expr);
|
|
1573
|
+
this._checkMultiForwardVariadic(fnType, expr);
|
|
870
1574
|
const maxLen = fnType.variadic
|
|
871
1575
|
? argTypes.length
|
|
872
1576
|
: Math.min(fnType.params.length, argTypes.length);
|
|
@@ -875,37 +1579,63 @@ export const expressionCheckMethods = {
|
|
|
875
1579
|
fnType.variadic && i >= fnType.params.length - 1
|
|
876
1580
|
? fnType.params.length - 1
|
|
877
1581
|
: i;
|
|
878
|
-
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
|
+
}
|
|
879
1586
|
const targetParam = fnType.params[paramIdx];
|
|
880
1587
|
const node = expr._multiForward ? null : expr.args[i];
|
|
881
|
-
this.assertAssignable(targetParam, argTypes[i], node);
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
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;
|
|
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
|
+
);
|
|
905
1624
|
}
|
|
906
1625
|
}
|
|
907
1626
|
},
|
|
908
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
|
+
);
|
|
1637
|
+
},
|
|
1638
|
+
|
|
909
1639
|
checkBuiltin(name, expr, argTypes, scope) {
|
|
910
1640
|
const gen = BUILTIN_CHECK[name];
|
|
911
1641
|
return gen ? gen(this, name, expr, argTypes, scope) : ANY;
|
|
@@ -923,6 +1653,9 @@ export const expressionCheckMethods = {
|
|
|
923
1653
|
},
|
|
924
1654
|
|
|
925
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]);
|
|
926
1659
|
if (name === "len") {
|
|
927
1660
|
const size = this._constLenFromType(argTypes[0]);
|
|
928
1661
|
if (size != null) expr._constLen = size;
|
|
@@ -952,6 +1685,17 @@ export const expressionCheckMethods = {
|
|
|
952
1685
|
}
|
|
953
1686
|
if (crt.kind === "untyped" && cit.kind === "untyped")
|
|
954
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
|
+
}
|
|
955
1699
|
return COMPLEX128;
|
|
956
1700
|
},
|
|
957
1701
|
|
|
@@ -966,6 +1710,7 @@ export const expressionCheckMethods = {
|
|
|
966
1710
|
return ANY;
|
|
967
1711
|
}
|
|
968
1712
|
if (zt.kind === "untyped") return UNTYPED_FLOAT;
|
|
1713
|
+
if (isComplex64(zt)) return FLOAT32;
|
|
969
1714
|
return FLOAT64;
|
|
970
1715
|
},
|
|
971
1716
|
|
|
@@ -997,6 +1742,11 @@ export const expressionCheckMethods = {
|
|
|
997
1742
|
: this.resolveTypeNode(expr.typeExpr, scope);
|
|
998
1743
|
const base = t.kind === "named" ? t.underlying : t;
|
|
999
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);
|
|
1000
1750
|
this._inferArraySize(expr, typeNode, base);
|
|
1001
1751
|
if (base?.kind === "struct")
|
|
1002
1752
|
return this._checkStructCompositeLit(expr, scope, t, base);
|
|
@@ -1010,6 +1760,11 @@ export const expressionCheckMethods = {
|
|
|
1010
1760
|
},
|
|
1011
1761
|
|
|
1012
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
|
+
}
|
|
1013
1768
|
if (expr.elems.length > 4) {
|
|
1014
1769
|
this.err("too many values in simd.F32x4 literal", expr);
|
|
1015
1770
|
}
|
|
@@ -1021,7 +1776,21 @@ export const expressionCheckMethods = {
|
|
|
1021
1776
|
},
|
|
1022
1777
|
|
|
1023
1778
|
_checkStructPositionalElems(expr, scope, t, base) {
|
|
1024
|
-
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() ?? [])];
|
|
1025
1794
|
for (let i = 0; i < expr.elems.length; i++) {
|
|
1026
1795
|
const elem = expr.elems[i];
|
|
1027
1796
|
const fieldName = fieldNames[i];
|
|
@@ -1078,8 +1847,19 @@ export const expressionCheckMethods = {
|
|
|
1078
1847
|
const et = this.checkCompositeLit(elem, scope, base.elem);
|
|
1079
1848
|
elem._type = et;
|
|
1080
1849
|
} else if (elem.kind === "KeyValueExpr") {
|
|
1081
|
-
|
|
1082
|
-
|
|
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
|
+
}
|
|
1083
1863
|
} else {
|
|
1084
1864
|
const et = this.checkExpr(elem, scope);
|
|
1085
1865
|
this.assertAssignable(base.elem, et, elem);
|
|
@@ -1103,7 +1883,13 @@ export const expressionCheckMethods = {
|
|
|
1103
1883
|
_checkMapCompositeLit(expr, scope, t, base) {
|
|
1104
1884
|
for (const elem of expr.elems) {
|
|
1105
1885
|
if (elem.kind === "KeyValueExpr") {
|
|
1106
|
-
|
|
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
|
+
}
|
|
1107
1893
|
// value may be an implicit composite lit
|
|
1108
1894
|
let vt;
|
|
1109
1895
|
if (
|
|
@@ -1116,6 +1902,7 @@ export const expressionCheckMethods = {
|
|
|
1116
1902
|
vt = this.checkExpr(elem.value, scope);
|
|
1117
1903
|
}
|
|
1118
1904
|
this.assertAssignable(base.key, kt, elem.key);
|
|
1905
|
+
this._markIfaceKey(base, elem.key, kt);
|
|
1119
1906
|
this.assertAssignable(base.value, vt, elem.value);
|
|
1120
1907
|
}
|
|
1121
1908
|
}
|
|
@@ -1123,15 +1910,37 @@ export const expressionCheckMethods = {
|
|
|
1123
1910
|
},
|
|
1124
1911
|
};
|
|
1125
1912
|
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
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";
|
|
1135
1941
|
}
|
|
1136
|
-
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
function computeMaxIndex(elems) {
|
|
1945
|
+
return compositeLitMaxIndex(elems);
|
|
1137
1946
|
}
|