gofront 1.7.0 → 1.9.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 +14 -14
- package/package.json +10 -38
- package/src/backend/js/expressions.js +2060 -389
- package/src/backend/js/index.js +586 -174
- package/src/backend/js/natives.js +383 -0
- package/src/backend/js/runtime.js +936 -1644
- package/src/backend/js/statements.js +1349 -132
- package/src/backend/js/stdlib/index.js +51 -94
- package/src/backend/js/templ.js +67 -118
- package/src/backend/js/type-info.js +436 -0
- package/src/backend/shared/fixed-float.js +20 -0
- package/src/backend/shared/fmt-types.js +80 -0
- package/src/backend/shared/host-unicode.js +36 -0
- package/src/backend/wasm/boundary.js +753 -872
- package/src/backend/wasm/emit-builtins.js +914 -237
- package/src/backend/wasm/emit-exprs.js +1456 -195
- package/src/backend/wasm/emit-maps.js +78 -380
- package/src/backend/wasm/emit-stmts.js +1659 -214
- package/src/backend/wasm/emit.js +614 -88
- package/src/backend/wasm/encode.js +43 -13
- package/src/backend/wasm/globals.js +491 -0
- package/src/backend/wasm/glue.js +2 -4
- package/src/backend/wasm/index.js +406 -3195
- package/src/backend/wasm/method-expr.js +392 -0
- package/src/backend/wasm/module-emitter.js +1615 -0
- package/src/backend/wasm/monomorph.js +164 -9
- package/src/backend/wasm/natives.js +596 -0
- package/src/backend/wasm/prescan.js +249 -0
- package/src/backend/wasm/prune-funcs.js +89 -0
- package/src/backend/wasm/stdlib/index.js +2 -37
- package/src/backend/wasm/stdlib/simd.js +293 -28
- package/src/backend/wasm/types.js +260 -27
- 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} +241 -11
- package/src/{test-runner.js → cli/test-runner.js} +188 -122
- package/src/{vendor.js → cli/vendor.js} +3 -4
- package/src/compiler.js +202 -386
- package/src/index.js +15 -169
- package/src/{lexer.js → lexer/lexer.js} +203 -62
- package/src/{templ-lexer.js → lexer/templ-lexer.js} +61 -18
- package/src/{tokens.js → lexer/tokens.js} +2 -0
- package/src/link/sourcemap.js +97 -0
- package/src/link/stdlib.js +737 -0
- package/src/link/type-names.js +148 -0
- package/src/link/wasm.js +318 -0
- package/src/lower/bounds.js +547 -0
- package/src/lower/boxing.js +1 -1
- package/src/lower/captures.js +267 -64
- package/src/lower/embedding.js +32 -3
- package/src/lower/errors-as.js +196 -0
- package/src/lower/fmt-formatters.js +905 -0
- package/src/lower/functions.js +2 -0
- package/src/lower/gen-types.js +274 -0
- package/src/lower/index.js +33 -19
- package/src/lower/json-codecs.js +1379 -0
- package/src/lower/ownership.js +47 -8
- package/src/lower/range.js +86 -1
- package/src/lower/reachability.js +150 -0
- package/src/lower/walk.js +12 -0
- package/src/parser/declarations.js +83 -15
- package/src/parser/expressions.js +110 -18
- package/src/parser/index.js +2 -1
- package/src/parser/statements.js +38 -22
- package/src/{templ-parser.js → parser/templ.js} +179 -17
- package/src/parser/types.js +70 -9
- package/src/resolver.js +21 -1
- package/src/typechecker/assignability.js +291 -19
- package/src/typechecker/const-fold.js +399 -0
- package/src/typechecker/constants.js +282 -0
- package/src/typechecker/expressions.js +1220 -183
- package/src/typechecker/index.js +659 -92
- package/src/typechecker/init-order.js +426 -0
- package/src/typechecker/operand-text.js +40 -0
- package/src/typechecker/resolve.js +613 -62
- package/src/typechecker/statements.js +212 -20
- package/src/typechecker/stdlib/excluded.js +902 -0
- package/src/typechecker/stdlib/index.js +38 -0
- package/src/typechecker/stdlib/simd.js +65 -15
- package/src/typechecker/termination.js +11 -2
- package/src/typechecker/types.js +268 -514
- package/stdlib/bytes/buffer.go +479 -0
- package/stdlib/bytes/bytes.go +1010 -0
- package/stdlib/bytes/reader.go +161 -0
- package/stdlib/cmp/cmp.go +65 -0
- package/stdlib/context/context.go +115 -0
- package/stdlib/crypto/rand/rand.go +34 -0
- package/stdlib/crypto/sha256/sha256.go +249 -0
- package/stdlib/encoding/base64/base64.go +331 -0
- package/stdlib/encoding/binary/binary.go +332 -0
- package/stdlib/encoding/hex/hex.go +140 -0
- package/stdlib/encoding/json/errors.go +31 -0
- package/stdlib/encoding/json/indent.go +71 -0
- package/stdlib/encoding/json/json.go +62 -0
- package/stdlib/encoding/json/number.go +12 -0
- package/stdlib/encoding/json/stream.go +173 -0
- package/stdlib/errors/errors.go +174 -0
- package/stdlib/fmt/errors.go +110 -0
- package/stdlib/fmt/format.go +732 -0
- package/stdlib/fmt/print.go +1232 -0
- package/stdlib/gom/gom.go +293 -0
- package/stdlib/hash/hash.go +53 -0
- package/stdlib/html/html.go +251 -0
- package/stdlib/internal/fmtrt/fmtrt.go +163 -0
- package/stdlib/internal/jsonrt/dec.go +871 -0
- package/stdlib/internal/jsonrt/enc.go +516 -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 +658 -0
- package/stdlib/io/io.go +502 -0
- package/stdlib/iter/iter.go +18 -0
- package/stdlib/maps/maps.go +131 -0
- package/stdlib/math/bits/bits.go +507 -0
- package/stdlib/math/extra.go +272 -0
- package/stdlib/math/math.go +439 -0
- package/stdlib/math/rand/rand.go +297 -0
- package/stdlib/os/os.go +93 -0
- package/stdlib/path/filepath/filepath.go +47 -0
- package/stdlib/path/path.go +394 -0
- package/stdlib/regexp/regexp.go +231 -0
- package/stdlib/slices/slices.go +526 -0
- package/stdlib/slices/sort.go +209 -0
- package/stdlib/slices/zsort_cmp.go +480 -0
- package/stdlib/slices/zsort_ordered.go +482 -0
- package/stdlib/sort/sort.go +500 -0
- package/stdlib/sort/zsort_func.go +480 -0
- package/stdlib/sort/zsort_iface.go +480 -0
- package/stdlib/strconv/float.go +1699 -0
- package/stdlib/strconv/isprint.go +752 -0
- package/stdlib/strconv/quote.go +526 -0
- package/stdlib/strconv/strconv.go +616 -0
- package/stdlib/strings/builder.go +95 -0
- package/stdlib/strings/reader.go +163 -0
- package/stdlib/strings/replacer.go +102 -0
- package/stdlib/strings/strings.go +807 -0
- package/stdlib/sync/sync.go +268 -0
- package/stdlib/testing/async_js.go +61 -0
- package/stdlib/testing/benchmark.go +297 -0
- package/stdlib/testing/testing.go +523 -0
- package/stdlib/time/duration.go +233 -0
- package/stdlib/time/format.go +745 -0
- package/stdlib/time/parse.go +790 -0
- package/stdlib/time/sleep_js.go +63 -0
- package/stdlib/time/time.go +834 -0
- package/stdlib/unicode/unicode.go +250 -0
- package/stdlib/unicode/utf8/utf8.go +464 -0
- package/src/backend/js/stdlib/base64.js +0 -12
- package/src/backend/js/stdlib/binary.js +0 -102
- package/src/backend/js/stdlib/bits.js +0 -46
- package/src/backend/js/stdlib/builder.js +0 -63
- package/src/backend/js/stdlib/bytes.js +0 -122
- package/src/backend/js/stdlib/errors.js +0 -44
- package/src/backend/js/stdlib/fmt.js +0 -178
- package/src/backend/js/stdlib/gom.js +0 -155
- package/src/backend/js/stdlib/hex.js +0 -17
- package/src/backend/js/stdlib/html.js +0 -20
- package/src/backend/js/stdlib/io.js +0 -137
- package/src/backend/js/stdlib/json.js +0 -972
- package/src/backend/js/stdlib/maps.js +0 -45
- package/src/backend/js/stdlib/math.js +0 -69
- package/src/backend/js/stdlib/os.js +0 -13
- package/src/backend/js/stdlib/path.js +0 -48
- package/src/backend/js/stdlib/rand.js +0 -32
- package/src/backend/js/stdlib/regexp.js +0 -54
- package/src/backend/js/stdlib/sha256.js +0 -12
- package/src/backend/js/stdlib/shared.js +0 -17
- package/src/backend/js/stdlib/simd.js +0 -24
- package/src/backend/js/stdlib/slices.js +0 -77
- package/src/backend/js/stdlib/sort.js +0 -30
- package/src/backend/js/stdlib/strconv.js +0 -74
- package/src/backend/js/stdlib/strings.js +0 -222
- package/src/backend/js/stdlib/sync.js +0 -13
- package/src/backend/js/stdlib/testing.js +0 -34
- package/src/backend/js/stdlib/time.js +0 -128
- package/src/backend/js/stdlib/unicode.js +0 -25
- package/src/backend/js/stdlib/utf8.js +0 -26
- package/src/backend/wasm/const-fold.js +0 -205
- package/src/backend/wasm/emit-stdlib.js +0 -13
- package/src/backend/wasm/stdlib/bytes.js +0 -5039
- package/src/backend/wasm/stdlib/core.js +0 -8903
- package/src/backend/wasm/stdlib/io.js +0 -2161
- package/src/backend/wasm/stdlib/packages.js +0 -2400
- package/src/backend/wasm/stdlib/pure.js +0 -720
- package/src/backend/wasm/stdlib/time.js +0 -1200
- package/src/typechecker/stdlib/core.js +0 -801
- package/src/typechecker/stdlib/extended.js +0 -914
- package/src/typechecker/stdlib.js +0 -22
- /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
|
@@ -1,27 +1,45 @@
|
|
|
1
1
|
// TypeChecker expression-checking methods — installed as a mixin on TypeChecker.prototype.
|
|
2
2
|
|
|
3
|
+
import { exprText, operandKind } from "./operand-text.js";
|
|
4
|
+
import {
|
|
5
|
+
EXCLUDED_STDLIB_MEMBERS,
|
|
6
|
+
EXCLUDED_STDLIB_PACKAGES,
|
|
7
|
+
} from "./stdlib/excluded.js";
|
|
3
8
|
import { sharedInfo } from "./stdlib/shared.js";
|
|
4
9
|
import {
|
|
5
10
|
ANY,
|
|
11
|
+
BASIC_TYPES,
|
|
6
12
|
BOOL,
|
|
7
13
|
BROWSER_GLOBALS,
|
|
14
|
+
COMPLEX64,
|
|
8
15
|
COMPLEX128,
|
|
16
|
+
compositeLitMaxIndex,
|
|
17
|
+
displayTypeName,
|
|
9
18
|
ERROR,
|
|
19
|
+
embeddedFieldName,
|
|
20
|
+
FLOAT32,
|
|
10
21
|
FLOAT64,
|
|
22
|
+
FMT_PRINT_FUNCS,
|
|
11
23
|
INT,
|
|
24
|
+
is64BitInt,
|
|
12
25
|
isAny,
|
|
13
26
|
isArray,
|
|
14
27
|
isBool,
|
|
15
28
|
isComplex,
|
|
29
|
+
isComplex64,
|
|
16
30
|
isComplexOrNumeric,
|
|
31
|
+
isInteger,
|
|
32
|
+
isNil,
|
|
17
33
|
isNumeric,
|
|
18
34
|
isString,
|
|
19
35
|
NIL,
|
|
20
36
|
RUNE,
|
|
21
37
|
Scope,
|
|
38
|
+
SIZED_INT_NAMES,
|
|
22
39
|
STRING,
|
|
23
40
|
TAINTED_ANY,
|
|
24
41
|
typeStr,
|
|
42
|
+
UINT8,
|
|
25
43
|
UNTYPED_BOOL,
|
|
26
44
|
UNTYPED_COMPLEX,
|
|
27
45
|
UNTYPED_FLOAT,
|
|
@@ -30,8 +48,25 @@ import {
|
|
|
30
48
|
VOID,
|
|
31
49
|
} from "./types.js";
|
|
32
50
|
|
|
51
|
+
const NILABLE_KINDS = new Set([
|
|
52
|
+
"slice",
|
|
53
|
+
"map",
|
|
54
|
+
"pointer",
|
|
55
|
+
"func",
|
|
56
|
+
"interface",
|
|
57
|
+
"chan",
|
|
58
|
+
]);
|
|
59
|
+
|
|
33
60
|
/** @typedef {import('./index.js').TypeChecker} TypeChecker */
|
|
34
61
|
|
|
62
|
+
// min/max: the type of the first typed operand; with only untyped constants,
|
|
63
|
+
// an untyped float if any operand is one (Go's "largest kind" rule).
|
|
64
|
+
function minMaxType(at) {
|
|
65
|
+
const typed = at.find((t) => t && t.kind !== "untyped");
|
|
66
|
+
if (typed) return typed;
|
|
67
|
+
return at.find((t) => t?.base === "float64") ?? at[0] ?? ANY;
|
|
68
|
+
}
|
|
69
|
+
|
|
35
70
|
// Dispatch table for builtin function type checking.
|
|
36
71
|
// Each value is (self, name, expr, argTypes, scope) => returnType.
|
|
37
72
|
const BUILTIN_CHECK = {
|
|
@@ -52,37 +87,55 @@ const BUILTIN_CHECK = {
|
|
|
52
87
|
}
|
|
53
88
|
const sb = s.resolveType(st?.kind === "named" ? st.underlying : st);
|
|
54
89
|
if (sb?.kind === "slice") {
|
|
90
|
+
const is64 = is64BitInt(sb.elem);
|
|
55
91
|
for (let i = 1; i < e.args.length; i++) {
|
|
56
|
-
if (
|
|
92
|
+
if (e.args[i]._spread)
|
|
93
|
+
s._rejectSeqAsSlice(at[i], e.args[i], "a spread argument");
|
|
94
|
+
if (!e.args[i]._spread && at[i]) {
|
|
95
|
+
if (is64) e.args[i]._is64Bit = true;
|
|
57
96
|
s._markIfaceBox(
|
|
58
97
|
s.resolveType(sb.elem),
|
|
59
98
|
s.resolveType(at[i]),
|
|
60
99
|
e.args[i],
|
|
61
100
|
);
|
|
101
|
+
}
|
|
62
102
|
}
|
|
63
103
|
}
|
|
64
104
|
return st;
|
|
65
105
|
},
|
|
66
106
|
copy: (s, _n, e, at) => s._checkBuiltinCopy(e, at),
|
|
67
|
-
delete: () =>
|
|
107
|
+
delete: (s, _n, e, at) => {
|
|
108
|
+
const mt = s._deepUnderlying(at[0]);
|
|
109
|
+
if (mt?.kind === "map" && e.args[1]) s._markIfaceKey(mt, e.args[1], at[1]);
|
|
110
|
+
return VOID;
|
|
111
|
+
},
|
|
68
112
|
make: (s, _n, e, _at, sc) => {
|
|
69
113
|
if (e.args.length < 1) return ANY;
|
|
70
114
|
const ta = e.args[0];
|
|
71
115
|
const tn = ta.kind === "TypeExpr" ? ta.type : ta;
|
|
72
|
-
|
|
116
|
+
const made = s.resolveTypeNode(tn, sc) ?? ANY;
|
|
117
|
+
// `make(S, n)` with S a type parameter makes its core type.
|
|
118
|
+
const core = made.kind === "typeParam" ? s._coreType(made, sc) : null;
|
|
119
|
+
if (core) ta._coreType = core;
|
|
120
|
+
return made;
|
|
73
121
|
},
|
|
74
122
|
new: (s, _n, e, _at, sc) => {
|
|
75
123
|
if (e.args.length < 1) return ANY;
|
|
76
|
-
const
|
|
124
|
+
const ta = e.args[0];
|
|
125
|
+
const inner = s.resolveTypeNode(ta.kind === "TypeExpr" ? ta.type : ta, sc);
|
|
77
126
|
return { kind: "pointer", base: inner };
|
|
78
127
|
},
|
|
79
|
-
print: () =>
|
|
80
|
-
println: () =>
|
|
81
|
-
panic: () =>
|
|
128
|
+
print: (s, n, e, at) => s._checkPrintArgs(n, e, at),
|
|
129
|
+
println: (s, n, e, at) => s._checkPrintArgs(n, e, at),
|
|
130
|
+
panic: (s, _n, e, at) => {
|
|
131
|
+
// The operand converts to `any`, so a defined type keeps its identity.
|
|
132
|
+
if (e.args[0] && at?.[0]) s._markIfaceBox(ANY, at[0], e.args[0]);
|
|
133
|
+
return VOID;
|
|
134
|
+
},
|
|
82
135
|
recover: () => ANY,
|
|
83
136
|
error: () => ERROR,
|
|
84
|
-
min: (_s, _n, _e, at) => at
|
|
85
|
-
max: (_s, _n, _e, at) => at
|
|
137
|
+
min: (_s, _n, _e, at) => minMaxType(at),
|
|
138
|
+
max: (_s, _n, _e, at) => minMaxType(at),
|
|
86
139
|
clear: () => VOID,
|
|
87
140
|
complex: (s, _n, e, at) => s._checkBuiltinComplex(e, at),
|
|
88
141
|
real: (s, n, e, at) => s._checkBuiltinRealImag(n, e, at),
|
|
@@ -109,6 +162,7 @@ export const expressionCheckMethods = {
|
|
|
109
162
|
checkExpr(expr, scope) {
|
|
110
163
|
const t = this._checkExpr(expr, scope);
|
|
111
164
|
expr._type = t;
|
|
165
|
+
this._noteConst(expr, scope);
|
|
112
166
|
return t;
|
|
113
167
|
},
|
|
114
168
|
|
|
@@ -133,12 +187,56 @@ export const expressionCheckMethods = {
|
|
|
133
187
|
return UNTYPED_COMPLEX;
|
|
134
188
|
case "Ident": {
|
|
135
189
|
const t = scope.lookup(expr.name);
|
|
136
|
-
|
|
137
|
-
if (
|
|
190
|
+
// `(*int)(nil)`: a predeclared type named where a type may stand.
|
|
191
|
+
if (!t && expr._typeRefOk && Object.hasOwn(BASIC_TYPES, expr.name)) {
|
|
192
|
+
expr._isTypeRef = true;
|
|
193
|
+
return BASIC_TYPES[expr.name];
|
|
194
|
+
}
|
|
195
|
+
if (!t) {
|
|
196
|
+
// The import of an excluded package already reported; its
|
|
197
|
+
// uses are not a second root cause.
|
|
198
|
+
const imported = this._fileImports
|
|
199
|
+
?.get(this._currentFile)
|
|
200
|
+
?.get(expr.name);
|
|
201
|
+
if (imported && EXCLUDED_STDLIB_PACKAGES.has(imported))
|
|
202
|
+
return TAINTED_ANY;
|
|
203
|
+
if (this._unavailableImports?.has(expr.name)) return TAINTED_ANY;
|
|
204
|
+
// `gom` needs no import in a js package; wasm packages get
|
|
205
|
+
// the same answer as an `import "gom"`.
|
|
206
|
+
if (
|
|
207
|
+
expr.name === "gom" &&
|
|
208
|
+
(this.target === "wasm" || this.target === "both")
|
|
209
|
+
) {
|
|
210
|
+
this.recordBlocker("gom usage", "gom");
|
|
211
|
+
return this.err(
|
|
212
|
+
"package 'gom' is not available in wasm packages",
|
|
213
|
+
expr,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
return this.err(`Undefined: '${expr.name}'`, expr);
|
|
217
|
+
}
|
|
218
|
+
if (this._namesType(expr.name, t, scope) || t.kind === "typeParam")
|
|
219
|
+
expr._isTypeRef = true;
|
|
220
|
+
// A Go declaration that shadows a browser global: JS output renames
|
|
221
|
+
// it so generated code keeps reaching the real global. Package
|
|
222
|
+
// namespaces (`import console "./util"`) are never renamed: JS
|
|
223
|
+
// selects their members directly (bundledPackages).
|
|
224
|
+
if (BROWSER_GLOBALS.has(expr.name) && t.kind !== "namespace") {
|
|
225
|
+
const owner = scope.lookupScope(expr.name);
|
|
226
|
+
if (
|
|
227
|
+
owner !== this.universe &&
|
|
228
|
+
!(owner === this.globals && this._externalNames.has(expr.name))
|
|
229
|
+
)
|
|
230
|
+
expr._shadowsGlobal = true;
|
|
231
|
+
}
|
|
232
|
+
// Go-source stdlib loading: record package-scope references so the
|
|
233
|
+
// loader can mangle them (see compiler.js loadStdlibPackage).
|
|
234
|
+
if (this._pkgRefs && scope.lookupScope(expr.name) === this.globals)
|
|
235
|
+
this._pkgRefs.add(`${this._currentFile}:${expr._line}:${expr._col}`);
|
|
138
236
|
if (
|
|
139
237
|
(this.target === "wasm" || this.target === "both") &&
|
|
140
238
|
BROWSER_GLOBALS.has(expr.name) &&
|
|
141
|
-
scope.
|
|
239
|
+
scope.lookupScope(expr.name) === this.universe
|
|
142
240
|
) {
|
|
143
241
|
this.recordBlocker("browser global", expr.name);
|
|
144
242
|
this.err(
|
|
@@ -147,14 +245,6 @@ export const expressionCheckMethods = {
|
|
|
147
245
|
`(package '${this.pkgName}' is //gofront:target ${this.target} — browser globals are only available in js packages)`,
|
|
148
246
|
);
|
|
149
247
|
}
|
|
150
|
-
if (
|
|
151
|
-
(this.target === "wasm" || this.target === "both") &&
|
|
152
|
-
expr.name === "gom" &&
|
|
153
|
-
scope.lookup(expr.name) === this.globals.lookup(expr.name)
|
|
154
|
-
) {
|
|
155
|
-
this.recordBlocker("gom usage", "gom");
|
|
156
|
-
this.err("package 'gom' is not available in wasm packages", expr);
|
|
157
|
-
}
|
|
158
248
|
return t;
|
|
159
249
|
}
|
|
160
250
|
case "BinaryExpr": {
|
|
@@ -163,7 +253,18 @@ export const expressionCheckMethods = {
|
|
|
163
253
|
if (lt?._tainted || rt?._tainted) return TAINTED_ANY;
|
|
164
254
|
if (isAny(lt) || isAny(rt))
|
|
165
255
|
return this.binaryResultType(expr.op, ANY, ANY, expr);
|
|
166
|
-
|
|
256
|
+
this._checkConstOperands(expr.op, lt, rt, expr);
|
|
257
|
+
this._markTypeParamMapNil(expr, lt, rt, scope);
|
|
258
|
+
const res = this.binaryResultType(expr.op, lt, rt, expr);
|
|
259
|
+
if (expr.op !== "<<" && expr.op !== ">>") {
|
|
260
|
+
this._retypeUntypedShift(lt, rt, expr.right);
|
|
261
|
+
this._retypeUntypedShift(rt, lt, expr.left);
|
|
262
|
+
}
|
|
263
|
+
if (is64BitInt(lt) && rt?.kind === "untyped" && expr.right)
|
|
264
|
+
expr.right._is64Bit = true;
|
|
265
|
+
if (is64BitInt(rt) && lt?.kind === "untyped" && expr.left)
|
|
266
|
+
expr.left._is64Bit = true;
|
|
267
|
+
return res;
|
|
167
268
|
}
|
|
168
269
|
case "AwaitExpr": {
|
|
169
270
|
if (this.target === "wasm" || this.target === "both") {
|
|
@@ -177,6 +278,11 @@ export const expressionCheckMethods = {
|
|
|
177
278
|
}
|
|
178
279
|
case "InstantiationExpr":
|
|
179
280
|
return this._checkInstantiationExpr(expr, scope);
|
|
281
|
+
case "TypeName":
|
|
282
|
+
// A type in expression position: monomorphisation rewrites
|
|
283
|
+
// `Box[int]` in `(*Box[int]).Set` to the TypeName `Box$int`.
|
|
284
|
+
expr._isTypeRef = true;
|
|
285
|
+
return this._typeRefOperand(expr, this.resolveTypeNode(expr, scope));
|
|
180
286
|
default: {
|
|
181
287
|
const m = CHECK_EXPR_DELEGATE[expr.kind];
|
|
182
288
|
if (m) return this[m](expr, scope);
|
|
@@ -186,6 +292,13 @@ export const expressionCheckMethods = {
|
|
|
186
292
|
return ANY;
|
|
187
293
|
},
|
|
188
294
|
|
|
295
|
+
// A type ref (`*T`, `Box[int]`) is a value only as the base of a method
|
|
296
|
+
// expression (`(*T).M`), a conversion callee or the operand of `*`.
|
|
297
|
+
_typeRefOperand(expr, t) {
|
|
298
|
+
if (expr._typeRefOk) return t;
|
|
299
|
+
return this.err(`${typeStr(t)} (type) is not an expression`, expr);
|
|
300
|
+
},
|
|
301
|
+
|
|
189
302
|
_checkDeref(ot, expr) {
|
|
190
303
|
if (ot === ANY) return ANY;
|
|
191
304
|
if (ot.kind === "pointer") return ot.base;
|
|
@@ -201,12 +314,209 @@ export const expressionCheckMethods = {
|
|
|
201
314
|
},
|
|
202
315
|
|
|
203
316
|
_checkNumericUnaryOp(ot, expr) {
|
|
317
|
+
if (ot?.kind === "typeParam") {
|
|
318
|
+
if (!this._typeParamAllows(ot, isComplexOrNumeric))
|
|
319
|
+
this.err(`${expr.op} requires numeric`, expr);
|
|
320
|
+
return ot;
|
|
321
|
+
}
|
|
204
322
|
if (!isNumeric(ot) && !isComplex(ot) && !isAny(ot))
|
|
205
323
|
this.err(`${expr.op} requires numeric`, expr);
|
|
206
324
|
return ot;
|
|
207
325
|
},
|
|
208
326
|
|
|
327
|
+
// `^x` on a type parameter needs every type in its constraint to be an
|
|
328
|
+
// integer. Other operands keep the existing (unchecked) behaviour.
|
|
329
|
+
_checkComplementUnaryOp(ot, expr) {
|
|
330
|
+
if (ot?.kind === "typeParam" && !this._typeParamAllows(ot, isInteger))
|
|
331
|
+
this.err("^ requires integer", expr);
|
|
332
|
+
return ot;
|
|
333
|
+
},
|
|
334
|
+
|
|
335
|
+
// Whether every type in the type set of type parameter `tp`'s constraint
|
|
336
|
+
// satisfies `pred`. A constraint without a type set (any, comparable,
|
|
337
|
+
// method-only interfaces) allows nothing.
|
|
338
|
+
_typeParamAllows(tp, pred) {
|
|
339
|
+
const terms = this._constraintTerms(tp.constraint, new Set());
|
|
340
|
+
return terms !== null && terms.length > 0 && terms.every(pred);
|
|
341
|
+
},
|
|
342
|
+
|
|
343
|
+
// `scope` resolves the type parameters a term mentions (`~[]E`); it
|
|
344
|
+
// defaults to the package scope.
|
|
345
|
+
_constraintTerms(c, seen, scope = this.globals) {
|
|
346
|
+
if (!c || seen.has(c)) return null;
|
|
347
|
+
seen.add(c);
|
|
348
|
+
// Receiver type parameters keep their constraint as an AST node.
|
|
349
|
+
if (
|
|
350
|
+
c.kind === "TypeName" ||
|
|
351
|
+
c.kind === "InterfaceType" ||
|
|
352
|
+
c.kind === "GenericTypeName" ||
|
|
353
|
+
c.kind === "InstantiationExpr" ||
|
|
354
|
+
c.kind === "IndexExpr"
|
|
355
|
+
)
|
|
356
|
+
return this._constraintTerms(this.resolveTypeNode(c, scope), seen, scope);
|
|
357
|
+
if (c.kind === "named")
|
|
358
|
+
return this._constraintTerms(c.underlying, seen, scope);
|
|
359
|
+
if (c.kind === "interface") return this._interfaceTerms(c, seen, scope);
|
|
360
|
+
if (c.kind === "typeParam")
|
|
361
|
+
return this._constraintTerms(c.constraint, seen, scope);
|
|
362
|
+
if (c.kind !== "UnionConstraint") return isAny(c) ? null : [c];
|
|
363
|
+
const out = [];
|
|
364
|
+
for (const term of c.terms) {
|
|
365
|
+
const t = term.resolved ?? this.resolveTypeNode(term.type, scope);
|
|
366
|
+
const inner =
|
|
367
|
+
t?.kind === "named" && t.underlying?.kind === "interface"
|
|
368
|
+
? this._constraintTerms(t, seen, scope)
|
|
369
|
+
: [t];
|
|
370
|
+
if (inner === null) return null;
|
|
371
|
+
out.push(...inner);
|
|
372
|
+
}
|
|
373
|
+
return out;
|
|
374
|
+
},
|
|
375
|
+
|
|
376
|
+
// An interface's type set is the intersection of its own union and the
|
|
377
|
+
// type sets of the interfaces it embeds; null when it has none.
|
|
378
|
+
_interfaceTerms(iface, seen, scope) {
|
|
379
|
+
const sets = [iface.unionConstraint, ...(iface._typeSetEmbeds ?? [])]
|
|
380
|
+
.filter(Boolean)
|
|
381
|
+
.map((s) => this._constraintTerms(s, new Set(seen), scope));
|
|
382
|
+
if (sets.length === 0 || sets.includes(null)) return null;
|
|
383
|
+
const [first, ...rest] = sets;
|
|
384
|
+
return first.filter((t) =>
|
|
385
|
+
rest.every((set) => set.some((u) => typeStr(u) === typeStr(t))),
|
|
386
|
+
);
|
|
387
|
+
},
|
|
388
|
+
|
|
389
|
+
// Only structs and arrays are their own managed objects in wasm; every other
|
|
390
|
+
// value (numbers, strings, slices, maps, interfaces, pointers, funcs, SIMD
|
|
391
|
+
// vectors) lives inline in its parent struct, array or global, so wasm has
|
|
392
|
+
// no stable address for it. Type parameters are checked per instantiation.
|
|
393
|
+
_isScalarStorageType(t) {
|
|
394
|
+
const u = this.resolveType(t?.kind === "named" ? t.underlying : t) ?? t;
|
|
395
|
+
const k = u?.kind;
|
|
396
|
+
if (!k || u._tainted) return false;
|
|
397
|
+
return k !== "struct" && k !== "array" && k !== "typeParam";
|
|
398
|
+
},
|
|
399
|
+
|
|
400
|
+
// True when `operand` is a struct field, array/slice element or package-level
|
|
401
|
+
// variable holding a scalar, in a package compiled to wasm.
|
|
402
|
+
_isScalarInteriorOperand(operand, type, scope) {
|
|
403
|
+
if (!this._isWasmPackage()) return false;
|
|
404
|
+
let node = operand;
|
|
405
|
+
while (node?.kind === "ParenExpr") node = node.expr;
|
|
406
|
+
if (!node) return false;
|
|
407
|
+
if (node.kind === "Ident") {
|
|
408
|
+
if (scope.lookupScope(node.name) !== this.globals) return false;
|
|
409
|
+
if (scope.isConst?.(node.name)) return false;
|
|
410
|
+
const sym = this.globals.symbols.get(node.name);
|
|
411
|
+
if (sym?.kind === "func" || sym?.kind === "namespace") return false;
|
|
412
|
+
} else if (node.kind !== "SelectorExpr" && node.kind !== "IndexExpr") {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
if (node.kind === "SelectorExpr" && node.expr?._isTypeRef) return false;
|
|
416
|
+
if (node.kind === "SelectorExpr" && node.expr?._type?.kind === "namespace")
|
|
417
|
+
return false;
|
|
418
|
+
return this._isScalarStorageType(type);
|
|
419
|
+
},
|
|
420
|
+
|
|
421
|
+
// Postfix selectors and index expressions carry no position; report at
|
|
422
|
+
// their innermost positioned operand.
|
|
423
|
+
_positioned(node) {
|
|
424
|
+
let at = node;
|
|
425
|
+
while (at && !at._line && !at.line) at = at.operand ?? at.expr;
|
|
426
|
+
return at ?? node;
|
|
427
|
+
},
|
|
428
|
+
|
|
429
|
+
_errScalarInteriorAddress(node, type) {
|
|
430
|
+
return this.err(
|
|
431
|
+
`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`,
|
|
432
|
+
this._positioned(node),
|
|
433
|
+
);
|
|
434
|
+
},
|
|
435
|
+
|
|
436
|
+
// Go: map elements are not addressable, nor are fields and array elements
|
|
437
|
+
// reached from one by value (`&m[k]`, `&m[k].f`, `&m[k][i]` on an array).
|
|
438
|
+
_isMapElementOperand(operand) {
|
|
439
|
+
let node = operand;
|
|
440
|
+
while (node?.kind === "ParenExpr") node = node.expr;
|
|
441
|
+
if (node?.kind !== "SelectorExpr" && node?.kind !== "IndexExpr")
|
|
442
|
+
return false;
|
|
443
|
+
const bt = node.expr?._type;
|
|
444
|
+
const b = this.resolveType(bt?.kind === "named" ? bt.underlying : bt) ?? bt;
|
|
445
|
+
if (node.kind === "IndexExpr" && b?.kind === "map") return true;
|
|
446
|
+
if (b?.kind === "pointer" || b?.kind === "slice") return false;
|
|
447
|
+
if (node.kind === "SelectorExpr" && b?.kind !== "struct") return false;
|
|
448
|
+
if (
|
|
449
|
+
node.kind === "SelectorExpr" &&
|
|
450
|
+
this._promotedThroughPointer(bt, node.field)
|
|
451
|
+
)
|
|
452
|
+
return false;
|
|
453
|
+
return this._isMapElementOperand(node.expr);
|
|
454
|
+
},
|
|
455
|
+
|
|
456
|
+
// Whether field or method `name` of struct type `t` is promoted through an
|
|
457
|
+
// embedded pointer, so selecting it dereferences and is addressable.
|
|
458
|
+
_promotedThroughPointer(t, name) {
|
|
459
|
+
return this._selectorPath(t, name, 0) === "pointer";
|
|
460
|
+
},
|
|
461
|
+
|
|
462
|
+
// "pointer" when `name` is reached through an embedded pointer, "value"
|
|
463
|
+
// when it is found without one, null when `t` has no such member.
|
|
464
|
+
_selectorPath(t, name, depth) {
|
|
465
|
+
if (!t || depth > 8) return null;
|
|
466
|
+
if (t.kind === "named" && t.methods?.has(name)) return "value";
|
|
467
|
+
const st = this.resolveType(t.kind === "named" ? t.underlying : t) ?? t;
|
|
468
|
+
if (st?.kind !== "struct") return null;
|
|
469
|
+
for (const f of st._structFields ?? [])
|
|
470
|
+
if (!f.embedded && f.names?.includes(name)) return "value";
|
|
471
|
+
for (const e of st._embeds ?? []) {
|
|
472
|
+
const found = this._selectorPath(
|
|
473
|
+
e.kind === "pointer" ? e.base : e,
|
|
474
|
+
name,
|
|
475
|
+
depth + 1,
|
|
476
|
+
);
|
|
477
|
+
if (found) return e.kind === "pointer" ? "pointer" : found;
|
|
478
|
+
}
|
|
479
|
+
return null;
|
|
480
|
+
},
|
|
481
|
+
|
|
482
|
+
_errMapElementAddress(node) {
|
|
483
|
+
return this.err(
|
|
484
|
+
"cannot take the address of a map element; copy it to a local variable, or store pointers in the map",
|
|
485
|
+
this._positioned(node),
|
|
486
|
+
);
|
|
487
|
+
},
|
|
488
|
+
|
|
489
|
+
_isWasmPackage() {
|
|
490
|
+
return this.target === "wasm" || this.target === "both";
|
|
491
|
+
},
|
|
492
|
+
|
|
493
|
+
_isFuncType(t) {
|
|
494
|
+
const u = this.resolveType(t?.kind === "named" ? t.underlying : t) ?? t;
|
|
495
|
+
return u?.kind === "func";
|
|
496
|
+
},
|
|
497
|
+
|
|
498
|
+
// The WASM backend represents a func value by its closure reference and has
|
|
499
|
+
// no box for it, so a pointer to a func variable would lose writes.
|
|
500
|
+
_errFuncAddress(node, type) {
|
|
501
|
+
return this.err(
|
|
502
|
+
`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`,
|
|
503
|
+
this._positioned(node),
|
|
504
|
+
);
|
|
505
|
+
},
|
|
506
|
+
|
|
209
507
|
_checkAddressOfExpr(expr, scope, ot) {
|
|
508
|
+
if (this._isMapElementOperand(expr.operand)) {
|
|
509
|
+
this._errMapElementAddress(expr);
|
|
510
|
+
return { kind: "pointer", base: ot };
|
|
511
|
+
}
|
|
512
|
+
if (this._isScalarInteriorOperand(expr.operand, ot, scope)) {
|
|
513
|
+
this._errScalarInteriorAddress(expr, ot);
|
|
514
|
+
return { kind: "pointer", base: ot };
|
|
515
|
+
}
|
|
516
|
+
if (this._isWasmPackage() && this._isFuncType(ot)) {
|
|
517
|
+
this._errFuncAddress(expr, ot);
|
|
518
|
+
return { kind: "pointer", base: ot };
|
|
519
|
+
}
|
|
210
520
|
if (expr.operand.kind === "Ident") {
|
|
211
521
|
expr.operand._addressTaken = true;
|
|
212
522
|
const sym = scope.lookup(expr.operand.name);
|
|
@@ -215,13 +525,33 @@ export const expressionCheckMethods = {
|
|
|
215
525
|
return { kind: "pointer", base: ot };
|
|
216
526
|
},
|
|
217
527
|
|
|
528
|
+
// Go constants have no negative zero: `-0.0` is plain 0, so it is rewritten
|
|
529
|
+
// to a unary plus that both backends compile to +0.
|
|
530
|
+
_foldNegativeZeroLiteral(expr) {
|
|
531
|
+
const o = expr.operand;
|
|
532
|
+
if (expr.op !== "-" || o?.kind !== "BasicLit") return;
|
|
533
|
+
if (o.litKind !== "INT" && o.litKind !== "FLOAT") return;
|
|
534
|
+
if (Number(String(o.value).replaceAll("_", "")) === 0) expr.op = "+";
|
|
535
|
+
},
|
|
536
|
+
|
|
218
537
|
_checkUnaryExpr(expr, scope) {
|
|
538
|
+
this._foldNegativeZeroLiteral(expr);
|
|
539
|
+
if (expr.op === "*") expr.operand._typeRefOk = true;
|
|
219
540
|
const ot = this.checkExpr(expr.operand, scope);
|
|
220
541
|
if (expr.op === "!") return this._checkBoolUnaryOp(ot, expr);
|
|
221
542
|
if (expr.op === "-" || expr.op === "+")
|
|
222
543
|
return this._checkNumericUnaryOp(ot, expr);
|
|
544
|
+
if (expr.op === "^") return this._checkComplementUnaryOp(ot, expr);
|
|
223
545
|
if (expr.op === "&") return this._checkAddressOfExpr(expr, scope, ot);
|
|
224
|
-
if (expr.op === "*")
|
|
546
|
+
if (expr.op === "*") {
|
|
547
|
+
// `(*T).M`: the operand names a type, so `*T` is the pointer type
|
|
548
|
+
// of a method expression, not a dereference.
|
|
549
|
+
if (expr.operand._isTypeRef) {
|
|
550
|
+
expr._isTypeRef = true;
|
|
551
|
+
return this._typeRefOperand(expr, { kind: "pointer", base: ot });
|
|
552
|
+
}
|
|
553
|
+
return this._checkDeref(ot, expr);
|
|
554
|
+
}
|
|
225
555
|
return ot;
|
|
226
556
|
},
|
|
227
557
|
|
|
@@ -238,51 +568,162 @@ export const expressionCheckMethods = {
|
|
|
238
568
|
return base?.methods?.get(field) ?? baseType.methods?.get(field);
|
|
239
569
|
},
|
|
240
570
|
|
|
571
|
+
// Instantiated generic methods are substituted copies without `_ptrRecv`,
|
|
572
|
+
// so read it from the generic declaration's method.
|
|
573
|
+
_methodExprPtrRecv(recvBase, methodType, field) {
|
|
574
|
+
if (!methodType) return false;
|
|
575
|
+
if (Object.hasOwn(methodType, "_ptrRecv")) return methodType._ptrRecv;
|
|
576
|
+
const generic = recvBase.typeArgs
|
|
577
|
+
? this.types.get(recvBase.name)?._generic
|
|
578
|
+
: null;
|
|
579
|
+
return Boolean(generic?.methods?.get(field)?._ptrRecv);
|
|
580
|
+
},
|
|
581
|
+
|
|
582
|
+
// `x.M` or `x.M()` with a pointer receiver on a named scalar or slice local takes
|
|
583
|
+
// `&x`, so `x` must be boxed like an explicit `&x`.
|
|
584
|
+
_markPtrRecvAddress(expr, base, ft, scope) {
|
|
585
|
+
if (!ft._ptrRecv) return;
|
|
586
|
+
if (
|
|
587
|
+
!this._promotedThroughPointer(expr.expr._type, expr.field) &&
|
|
588
|
+
this._isMapElementOperand(expr.expr)
|
|
589
|
+
) {
|
|
590
|
+
this._errMapElementAddress(expr.expr);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
const k = base?.kind;
|
|
594
|
+
if (k !== "basic" && k !== "slice" && k !== "map" && k !== "func") return;
|
|
595
|
+
if (this._isScalarInteriorOperand(expr.expr, expr.expr._type, scope)) {
|
|
596
|
+
this._errScalarInteriorAddress(expr, expr.expr._type);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (k === "func" && this._isWasmPackage()) {
|
|
600
|
+
this._errFuncAddress(expr, expr.expr._type);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
if (expr.expr.kind !== "Ident") return;
|
|
604
|
+
expr.expr._addressTaken = true;
|
|
605
|
+
const sym = scope.lookup(expr.expr.name);
|
|
606
|
+
if (sym) sym._addressTaken = true;
|
|
607
|
+
},
|
|
608
|
+
|
|
609
|
+
// Resolved per file from the import path, so a local package sharing an
|
|
610
|
+
// identifier with a stdlib one (or a stdlib import in another file) never matches.
|
|
611
|
+
_checkExcludedNamespaceMember(expr, baseType) {
|
|
612
|
+
const pkgPath = this._fileImports
|
|
613
|
+
?.get(this._currentFile)
|
|
614
|
+
?.get(baseType.name);
|
|
615
|
+
if (!pkgPath || pkgPath.startsWith(".")) return null;
|
|
616
|
+
const key = `${pkgPath}.${expr.field}`;
|
|
617
|
+
if (!EXCLUDED_STDLIB_MEMBERS.has(key)) return null;
|
|
618
|
+
return this.err(
|
|
619
|
+
`${baseType.name}.${expr.field} is not available: ${EXCLUDED_STDLIB_MEMBERS.get(key)}`,
|
|
620
|
+
expr,
|
|
621
|
+
);
|
|
622
|
+
},
|
|
623
|
+
|
|
624
|
+
_checkExcludedSelectorExpr(expr, baseType) {
|
|
625
|
+
if (baseType?.kind === "namespace") {
|
|
626
|
+
return this._checkExcludedNamespaceMember(expr, baseType);
|
|
627
|
+
}
|
|
628
|
+
const recvCheck =
|
|
629
|
+
baseType?.kind === "pointer"
|
|
630
|
+
? (this.resolveType(baseType.base) ?? baseType.base)
|
|
631
|
+
: baseType;
|
|
632
|
+
const recvName = recvCheck?.name;
|
|
633
|
+
// Stdlib named types are qualified (`pkg.T` or mangled `T$$pkg`); a
|
|
634
|
+
// bare name is a user type and never matches the exclusion table.
|
|
635
|
+
if (!recvName || !/[.$]/.test(recvName)) return null;
|
|
636
|
+
const dName = displayTypeName(recvName);
|
|
637
|
+
const reason =
|
|
638
|
+
EXCLUDED_STDLIB_MEMBERS.get(`${dName}.${expr.field}`) ??
|
|
639
|
+
EXCLUDED_STDLIB_MEMBERS.get(`${recvName}.${expr.field}`);
|
|
640
|
+
if (!reason) return null;
|
|
641
|
+
return this.err(`${dName}.${expr.field} is not available: ${reason}`, expr);
|
|
642
|
+
},
|
|
643
|
+
|
|
644
|
+
_checkMethodExprSelector(expr, baseType) {
|
|
645
|
+
const recvBase = baseType.kind === "pointer" ? baseType.base : baseType;
|
|
646
|
+
const base = this.resolveType(
|
|
647
|
+
recvBase.kind === "named" ? recvBase.underlying : recvBase,
|
|
648
|
+
);
|
|
649
|
+
const methodType = this._resolveMethodExprType(recvBase, base, expr.field);
|
|
650
|
+
if (
|
|
651
|
+
this._methodExprPtrRecv(recvBase, methodType, expr.field) &&
|
|
652
|
+
baseType.kind !== "pointer"
|
|
653
|
+
) {
|
|
654
|
+
const t = typeStr(baseType);
|
|
655
|
+
return this.err(
|
|
656
|
+
`invalid method expression ${t}.${expr.field} (needs pointer receiver (*${t}).${expr.field})`,
|
|
657
|
+
expr,
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
if (!methodType) return null;
|
|
661
|
+
expr._isMethodExpr = true;
|
|
662
|
+
expr._methodExprRecv = baseType;
|
|
663
|
+
return {
|
|
664
|
+
kind: "func",
|
|
665
|
+
params: [baseType, ...(methodType.params ?? [])],
|
|
666
|
+
returns: methodType.returns ?? [],
|
|
667
|
+
variadic: methodType.variadic,
|
|
668
|
+
};
|
|
669
|
+
},
|
|
670
|
+
|
|
671
|
+
_markMethodValueSelector(expr, baseType, ft, scope) {
|
|
672
|
+
const recvT =
|
|
673
|
+
baseType.kind === "pointer"
|
|
674
|
+
? (this.resolveType(baseType.base) ?? baseType.base)
|
|
675
|
+
: baseType;
|
|
676
|
+
const base = this.resolveType(
|
|
677
|
+
recvT?.kind === "named" ? recvT.underlying : recvT,
|
|
678
|
+
);
|
|
679
|
+
if (base?.kind === "interface" && base.methods?.has(expr.field)) {
|
|
680
|
+
// `i.M` binds the interface value; the call dispatches when it runs.
|
|
681
|
+
expr._isMethodValue = true;
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
if (!recvT || !this._isStructOrNamedMethod(recvT, base, expr.field)) return;
|
|
685
|
+
expr._isMethodValue = true;
|
|
686
|
+
if (baseType.kind === "pointer")
|
|
687
|
+
expr._derefRecv = !this._methodExprPtrRecv(recvT, ft, expr.field);
|
|
688
|
+
else this._markPtrRecvAddress(expr, base, ft, scope);
|
|
689
|
+
},
|
|
690
|
+
|
|
691
|
+
// Whether the identifier `name`, which resolved to `t`, names a type. A
|
|
692
|
+
// local variable or parameter that shares its name with a type of the
|
|
693
|
+
// build (the type table spans packages) is a value.
|
|
694
|
+
_namesType(name, t, scope) {
|
|
695
|
+
if (!this.types.has(name)) return false;
|
|
696
|
+
return (
|
|
697
|
+
scope.lookupScope(name) === this.globals || this.types.get(name) === t
|
|
698
|
+
);
|
|
699
|
+
},
|
|
700
|
+
|
|
241
701
|
_checkSelectorExpr(expr, scope) {
|
|
702
|
+
expr.expr._typeRefOk = true;
|
|
242
703
|
const baseType = this.checkExpr(expr.expr, scope);
|
|
243
704
|
if (baseType?._tainted) return TAINTED_ANY;
|
|
705
|
+
const exclErr = this._checkExcludedSelectorExpr(expr, baseType);
|
|
706
|
+
if (exclErr) return exclErr;
|
|
244
707
|
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
|
-
}
|
|
708
|
+
const mExpr = this._checkMethodExprSelector(expr, baseType);
|
|
709
|
+
if (mExpr) return mExpr;
|
|
262
710
|
}
|
|
263
711
|
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
|
-
}
|
|
712
|
+
if (ft?.kind === "func")
|
|
713
|
+
this._markMethodValueSelector(expr, baseType, ft, scope);
|
|
270
714
|
if (
|
|
271
715
|
baseType?.kind === "namespace" &&
|
|
272
716
|
this.types.has(`${baseType.name}.${expr.field}`) &&
|
|
273
|
-
|
|
274
|
-
baseType.name === "binary" &&
|
|
275
|
-
(expr.field === "LittleEndian" || expr.field === "BigEndian")
|
|
276
|
-
)
|
|
717
|
+
baseType.name !== "binary"
|
|
277
718
|
) {
|
|
278
719
|
expr._isTypeRef = true;
|
|
279
720
|
}
|
|
280
721
|
return ft;
|
|
281
722
|
},
|
|
282
723
|
|
|
283
|
-
_checkArrayBoundsExpr(btu, bt, expr) {
|
|
724
|
+
_checkArrayBoundsExpr(btu, bt, expr, scope) {
|
|
284
725
|
if (btu.kind !== "array" || btu.size == null) return;
|
|
285
|
-
const idx = this._constIntValue(expr.index);
|
|
726
|
+
const idx = this._constIntValue(expr.index, scope);
|
|
286
727
|
if (idx === null) return;
|
|
287
728
|
if (idx < 0)
|
|
288
729
|
this.err(`invalid array index ${idx} (index must not be negative)`, expr);
|
|
@@ -293,34 +734,189 @@ export const expressionCheckMethods = {
|
|
|
293
734
|
);
|
|
294
735
|
},
|
|
295
736
|
|
|
737
|
+
// `Box[int]` in an expression (the receiver of a method expression such
|
|
738
|
+
// as `Box[int].Get` or `(*Box[int]).Set`) names the instantiated type.
|
|
739
|
+
// The node becomes an InstantiationExpr so monomorphisation rewrites it.
|
|
740
|
+
_genericTypeRef(expr, scope) {
|
|
741
|
+
const base = expr.expr;
|
|
742
|
+
let t;
|
|
743
|
+
if (base?.kind === "SelectorExpr" && base.expr?.kind === "Ident") {
|
|
744
|
+
// `iter.Seq[int]`: a generic type behind a package qualifier.
|
|
745
|
+
if (scope.lookup(base.expr.name)?.kind !== "namespace") return null;
|
|
746
|
+
t = this.types.get(`${base.expr.name}.${base.field}`);
|
|
747
|
+
if (!t?._generic || t._generic.isAlias) return null;
|
|
748
|
+
} else {
|
|
749
|
+
if (base?.kind !== "Ident") return null;
|
|
750
|
+
t = this.types.get(base.name);
|
|
751
|
+
if (!t?._generic || t._generic.isAlias || scope.lookup(base.name) !== t)
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
754
|
+
if (expr.kind === "IndexExpr") {
|
|
755
|
+
if (expr.index?.kind !== "Ident") return null;
|
|
756
|
+
expr.kind = "InstantiationExpr";
|
|
757
|
+
expr.typeArgs = [{ kind: "TypeName", name: expr.index.name }];
|
|
758
|
+
delete expr.index;
|
|
759
|
+
}
|
|
760
|
+
const resolved = this.resolveTypeNode(expr, scope);
|
|
761
|
+
expr._isTypeRef = true;
|
|
762
|
+
return this._typeRefOperand(expr, resolved);
|
|
763
|
+
},
|
|
764
|
+
|
|
765
|
+
// Underlying type with chains of defined types followed through
|
|
766
|
+
// (`type W V` where `V` is itself a named array).
|
|
767
|
+
_deepUnderlying(t) {
|
|
768
|
+
let u = t;
|
|
769
|
+
for (let i = 0; u?.kind === "named" && i < 32; i++)
|
|
770
|
+
u = this.resolveType(u.underlying) ?? u.underlying;
|
|
771
|
+
return u;
|
|
772
|
+
},
|
|
773
|
+
|
|
774
|
+
// Core type of type parameter `tp`: the one underlying type shared by every
|
|
775
|
+
// type in its constraint (`~[]E`, `~map[K]V`), or null when there is none.
|
|
776
|
+
_coreType(tp, scope) {
|
|
777
|
+
if (tp?.kind !== "typeParam") return null;
|
|
778
|
+
const terms = this._constraintTerms(tp.constraint, new Set(), scope);
|
|
779
|
+
if (!terms || terms.length === 0) return null;
|
|
780
|
+
const unders = terms.map((t) => this._deepUnderlying(this.resolveType(t)));
|
|
781
|
+
const key = typeStr(unders[0]);
|
|
782
|
+
if (unders.some((u) => !u || typeStr(u) !== key)) return null;
|
|
783
|
+
return unders[0];
|
|
784
|
+
},
|
|
785
|
+
|
|
786
|
+
// `m == nil` on a type parameter whose core type is a map: the backends
|
|
787
|
+
// compare it as a map, so record the core type on the operand.
|
|
788
|
+
_markTypeParamMapNil(expr, lt, rt, scope) {
|
|
789
|
+
if (expr.op !== "==" && expr.op !== "!=") return;
|
|
790
|
+
const [operand, other] =
|
|
791
|
+
rt === NIL
|
|
792
|
+
? [expr.left, lt]
|
|
793
|
+
: lt === NIL
|
|
794
|
+
? [expr.right, rt]
|
|
795
|
+
: [null, null];
|
|
796
|
+
if (!operand || other?.kind !== "typeParam") return;
|
|
797
|
+
const core = this._coreType(other, scope);
|
|
798
|
+
if (core?.kind === "map") operand._coreType = core;
|
|
799
|
+
},
|
|
800
|
+
|
|
801
|
+
// Underlying type of `t` for indexing, slicing, ranging and the like; a
|
|
802
|
+
// type parameter stands for its core type.
|
|
803
|
+
_coreUnderlying(t, scope) {
|
|
804
|
+
if (t?.kind === "typeParam") return this._coreType(t, scope) ?? t;
|
|
805
|
+
return this._deepUnderlying(t);
|
|
806
|
+
},
|
|
807
|
+
|
|
808
|
+
// One step of underlying type, or the core type for a type parameter.
|
|
809
|
+
_underlyingOrCore(t, scope) {
|
|
810
|
+
if (t?.kind === "typeParam") return this._coreType(t, scope) ?? t;
|
|
811
|
+
return t?.kind === "named" ? t.underlying : t;
|
|
812
|
+
},
|
|
813
|
+
|
|
814
|
+
_markCompositeKey(key, kb, node, srcType) {
|
|
815
|
+
if (!node || !srcType || isAny(key) || kb?.kind === "interface")
|
|
816
|
+
return false;
|
|
817
|
+
// Arrays, anonymous structs and pointers are not JS property names: the
|
|
818
|
+
// backend encodes them by value (`sk`) or by identity (`pk`).
|
|
819
|
+
if (kb?.kind === "pointer") node._mapKeyEnc = "pk";
|
|
820
|
+
else if (kb?.kind === "array" || (key?.kind === "struct" && kb === key))
|
|
821
|
+
node._mapKeyEnc = "sk";
|
|
822
|
+
return true;
|
|
823
|
+
},
|
|
824
|
+
|
|
825
|
+
// A key of an interface-keyed map (`map[any]T`): the backend encodes it
|
|
826
|
+
// with its dynamic type, so `L("1")`, `N(1)`, `"1"` and `1` stay distinct.
|
|
827
|
+
// A named basic value is boxed first, exactly as in any interface
|
|
828
|
+
// conversion; other values are keyed as they are.
|
|
829
|
+
_markIfaceKey(mapType, node, srcType) {
|
|
830
|
+
const key = this.resolveType(mapType.key);
|
|
831
|
+
const kb = key?.kind === "named" ? this.resolveType(key.underlying) : key;
|
|
832
|
+
const src = srcType && this.resolveType(srcType);
|
|
833
|
+
if (node && src?.kind === "typeParam") {
|
|
834
|
+
// Generic code: whether the key is boxed depends on the type argument,
|
|
835
|
+
// so the backend decides at run time (`__ikt`).
|
|
836
|
+
if (key?.kind === "typeParam" || isAny(key) || kb?.kind === "interface") {
|
|
837
|
+
node._ifaceKeyDyn = key?.kind === "typeParam" ? "same" : "box";
|
|
838
|
+
node._ifaceKeyParam = src.name;
|
|
839
|
+
}
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
if (this._markCompositeKey(key, kb, node, srcType)) return;
|
|
843
|
+
if (!node || !srcType || (!isAny(key) && kb?.kind !== "interface")) return;
|
|
844
|
+
node._ifaceKey = true;
|
|
845
|
+
node._ifaceKeyType = src;
|
|
846
|
+
this._markIfaceBox(key, this.resolveType(srcType), node);
|
|
847
|
+
if (node._ifaceBox === "named" || node._ifaceBox === "named_ptr") return;
|
|
848
|
+
node._ifaceBox = undefined;
|
|
849
|
+
node._ifaceSrc = undefined;
|
|
850
|
+
node._ifacePtrFields = undefined;
|
|
851
|
+
},
|
|
852
|
+
|
|
296
853
|
_checkIndexExpr(expr, scope) {
|
|
297
|
-
const
|
|
298
|
-
|
|
854
|
+
const typeRef = this._genericTypeRef(expr, scope);
|
|
855
|
+
if (typeRef) return typeRef;
|
|
856
|
+
const bt = this._derefArrayPtr(
|
|
857
|
+
expr,
|
|
858
|
+
"expr",
|
|
859
|
+
this.checkExpr(expr.expr, scope),
|
|
860
|
+
);
|
|
861
|
+
if (bt?.kind === "generic") {
|
|
862
|
+
const inst = this._genericFuncValueAsInstantiation(expr);
|
|
863
|
+
if (inst) return this._checkInstantiationExpr(inst, scope);
|
|
864
|
+
}
|
|
865
|
+
const indexType = this.checkExpr(expr.index, scope);
|
|
299
866
|
if (bt?._tainted) return TAINTED_ANY;
|
|
300
867
|
if (isAny(bt)) return ANY;
|
|
301
|
-
const btu =
|
|
302
|
-
bt.kind === "named"
|
|
303
|
-
? (this.resolveType(bt.underlying) ?? bt.underlying)
|
|
304
|
-
: bt;
|
|
868
|
+
const btu = this._coreUnderlying(bt, scope);
|
|
305
869
|
if (btu.kind === "slice" || btu.kind === "array") {
|
|
306
|
-
this._checkArrayBoundsExpr(btu, bt, expr);
|
|
870
|
+
this._checkArrayBoundsExpr(btu, bt, expr, scope);
|
|
307
871
|
return btu.elem;
|
|
308
872
|
}
|
|
309
873
|
if (btu.kind === "map") {
|
|
310
874
|
expr._mapValueType = btu.value;
|
|
875
|
+
this.assertAssignable(btu.key, indexType, expr.index);
|
|
876
|
+
this._markIfaceKey(btu, expr.index, indexType);
|
|
311
877
|
return btu.value;
|
|
312
878
|
}
|
|
313
879
|
if (btu.kind === "shared") {
|
|
314
880
|
expr._sharedElem = btu.elem;
|
|
315
|
-
const idx = this._constIntValue(expr.index);
|
|
881
|
+
const idx = this._constIntValue(expr.index, scope);
|
|
316
882
|
if (idx !== null && idx < 0)
|
|
317
883
|
this.err(`invalid index ${idx} (index must not be negative)`, expr);
|
|
318
884
|
return btu.elem;
|
|
319
885
|
}
|
|
320
|
-
if (isString(bt)) return
|
|
886
|
+
if (isString(bt)) return UINT8;
|
|
321
887
|
return this.err(`Cannot index type ${typeStr(bt)}`, expr);
|
|
322
888
|
},
|
|
323
889
|
|
|
890
|
+
// `Id[int]` used as a value reaches the typechecker as an IndexExpr (the
|
|
891
|
+
// parser only commits to type arguments before a call or literal). The
|
|
892
|
+
// node becomes an InstantiationExpr so both backends specialise it.
|
|
893
|
+
_genericFuncValueAsInstantiation(expr) {
|
|
894
|
+
const toTypeNode = (n) => {
|
|
895
|
+
if (n?.kind === "TypeExpr") return n.type;
|
|
896
|
+
// `Id[*int]`: the parser sees a dereference; on a generic function
|
|
897
|
+
// it is a pointer type argument.
|
|
898
|
+
if (n?.kind === "UnaryExpr" && n.op === "*") {
|
|
899
|
+
const base = toTypeNode(n.operand);
|
|
900
|
+
return base && { kind: "PointerType", base, _line: n._line };
|
|
901
|
+
}
|
|
902
|
+
const parts = [];
|
|
903
|
+
let cur = n;
|
|
904
|
+
while (cur?.kind === "SelectorExpr") {
|
|
905
|
+
parts.unshift(cur.field);
|
|
906
|
+
cur = cur.expr;
|
|
907
|
+
}
|
|
908
|
+
if (cur?.kind !== "Ident") return null;
|
|
909
|
+
parts.unshift(cur.name);
|
|
910
|
+
return { kind: "TypeName", name: parts.join("."), _line: n._line };
|
|
911
|
+
};
|
|
912
|
+
const typeArg = toTypeNode(expr.index);
|
|
913
|
+
if (!typeArg) return null;
|
|
914
|
+
expr.kind = "InstantiationExpr";
|
|
915
|
+
expr.typeArgs = [typeArg];
|
|
916
|
+
delete expr.index;
|
|
917
|
+
return expr;
|
|
918
|
+
},
|
|
919
|
+
|
|
324
920
|
// copy(dst, src): element types must be identical when either side is a
|
|
325
921
|
// shared buffer (the wasm emitter uses one element type for both sides).
|
|
326
922
|
_checkBuiltinCopy(e, at) {
|
|
@@ -346,15 +942,33 @@ export const expressionCheckMethods = {
|
|
|
346
942
|
return INT;
|
|
347
943
|
},
|
|
348
944
|
|
|
945
|
+
// Go's shorthand for pointers to arrays: `p[i]`, `p[lo:hi]`, `len(p)` and
|
|
946
|
+
// `range p` mean `(*p)[i]` and so on. The operand is rewritten to an
|
|
947
|
+
// explicit dereference so every backend sees the array.
|
|
948
|
+
_derefArrayPtr(holder, key, t) {
|
|
949
|
+
if (t?.kind !== "pointer") return t;
|
|
950
|
+
const base = t.base?.kind === "named" ? this.resolveType(t.base) : t.base;
|
|
951
|
+
const under = base?.kind === "named" ? base.underlying : base;
|
|
952
|
+
if (under?.kind !== "array") return t;
|
|
953
|
+
holder[key] = { kind: "UnaryExpr", op: "*", operand: holder[key] };
|
|
954
|
+
holder[key]._type = t.base;
|
|
955
|
+
return t.base;
|
|
956
|
+
},
|
|
957
|
+
|
|
349
958
|
_checkSliceExpr(expr, scope) {
|
|
350
|
-
const bt = this.
|
|
959
|
+
const bt = this._derefArrayPtr(
|
|
960
|
+
expr,
|
|
961
|
+
"expr",
|
|
962
|
+
this.checkExpr(expr.expr, scope),
|
|
963
|
+
);
|
|
351
964
|
if (expr.low) this.checkExpr(expr.low, scope);
|
|
352
965
|
if (expr.high) this.checkExpr(expr.high, scope);
|
|
353
966
|
if (expr.max) this.checkExpr(expr.max, scope);
|
|
354
967
|
if (bt?._tainted) return TAINTED_ANY;
|
|
355
968
|
if (isAny(bt)) return ANY;
|
|
356
|
-
|
|
357
|
-
|
|
969
|
+
const btu = this._coreUnderlying(bt, scope);
|
|
970
|
+
if (btu.kind === "slice" || btu.kind === "array")
|
|
971
|
+
return { kind: "slice", elem: btu.elem };
|
|
358
972
|
if (isString(bt)) return STRING;
|
|
359
973
|
if (sharedInfo(bt))
|
|
360
974
|
return this.err(
|
|
@@ -387,13 +1001,16 @@ export const expressionCheckMethods = {
|
|
|
387
1001
|
}
|
|
388
1002
|
const inner = new Scope(scope);
|
|
389
1003
|
for (const p of expr.params)
|
|
390
|
-
inner.define(p.name, p.type ? this.
|
|
1004
|
+
inner.define(p.name, p.type ? this._paramType(p, scope) : ANY);
|
|
391
1005
|
const ret = expr.returnType
|
|
392
1006
|
? this.resolveTypeNode(expr.returnType, scope)
|
|
393
1007
|
: VOID;
|
|
1008
|
+
const bodyRet = this._injectNamedReturns(expr.returnType, inner)
|
|
1009
|
+
? { ...ret, _hasNamedReturns: true }
|
|
1010
|
+
: ret;
|
|
394
1011
|
const savedDefer = this._deferCount;
|
|
395
1012
|
this._deferCount = 0;
|
|
396
|
-
this.checkBlock(expr.body, inner,
|
|
1013
|
+
this.checkBlock(expr.body, inner, bodyRet);
|
|
397
1014
|
this._reportUnused(inner, expr);
|
|
398
1015
|
if (this._deferCount > 0) expr.body._hasDefer = true;
|
|
399
1016
|
this._deferCount = savedDefer;
|
|
@@ -404,6 +1021,7 @@ export const expressionCheckMethods = {
|
|
|
404
1021
|
kind: "func",
|
|
405
1022
|
params: paramTypes,
|
|
406
1023
|
returns: [ret],
|
|
1024
|
+
variadic: expr.params.at(-1)?.variadic === true,
|
|
407
1025
|
async: expr.async,
|
|
408
1026
|
};
|
|
409
1027
|
},
|
|
@@ -429,6 +1047,19 @@ export const expressionCheckMethods = {
|
|
|
429
1047
|
);
|
|
430
1048
|
},
|
|
431
1049
|
|
|
1050
|
+
// Struct types with identical field names and types convert to each other.
|
|
1051
|
+
_sameStructFields(a, b) {
|
|
1052
|
+
if (a?.kind !== "struct" || b?.kind !== "struct") return false;
|
|
1053
|
+
if (a.fields.size !== b.fields.size) return false;
|
|
1054
|
+
const bf = [...b.fields];
|
|
1055
|
+
let i = 0;
|
|
1056
|
+
for (const [name, type] of a.fields) {
|
|
1057
|
+
const [bn, bt] = bf[i++];
|
|
1058
|
+
if (name !== bn || typeStr(type) !== typeStr(bt)) return false;
|
|
1059
|
+
}
|
|
1060
|
+
return true;
|
|
1061
|
+
},
|
|
1062
|
+
|
|
432
1063
|
_isInvalidArrayConv(tgt, src, srcType) {
|
|
433
1064
|
return tgt?.kind === "array" && src?.kind !== "array" && !isAny(srcType);
|
|
434
1065
|
},
|
|
@@ -436,6 +1067,9 @@ export const expressionCheckMethods = {
|
|
|
436
1067
|
_checkTypeConversion(expr, scope) {
|
|
437
1068
|
const srcType = this.checkExpr(expr.expr, scope);
|
|
438
1069
|
const targetType = this.resolveTypeNode(expr.targetType, scope);
|
|
1070
|
+
// `any(x)` / `error(x)`: box x as an implicit conversion would.
|
|
1071
|
+
this._markIfaceBox(targetType, srcType, expr.expr);
|
|
1072
|
+
this._rejectNilConversion(srcType, targetType, expr);
|
|
439
1073
|
const srcResolved =
|
|
440
1074
|
srcType?.kind === "named" ? srcType.underlying : srcType;
|
|
441
1075
|
const tgtResolved =
|
|
@@ -456,6 +1090,8 @@ export const expressionCheckMethods = {
|
|
|
456
1090
|
expr,
|
|
457
1091
|
);
|
|
458
1092
|
}
|
|
1093
|
+
if (tgtResolved?.kind === "slice")
|
|
1094
|
+
this._rejectSeqAsSlice(srcType, expr.expr, "a slice conversion");
|
|
459
1095
|
if (this._isMutualArraySlice(tgtResolved, srcResolved)) {
|
|
460
1096
|
this._checkElemTypeCompatible(
|
|
461
1097
|
tgtResolved.elem,
|
|
@@ -475,12 +1111,42 @@ export const expressionCheckMethods = {
|
|
|
475
1111
|
return targetType;
|
|
476
1112
|
},
|
|
477
1113
|
|
|
1114
|
+
// `int(nil)`: only a type with a nil value converts from `nil`.
|
|
1115
|
+
_rejectNilConversion(srcType, targetType, expr) {
|
|
1116
|
+
if (!isNil(srcType) || !targetType || isAny(targetType)) return;
|
|
1117
|
+
if (targetType.kind === "typeParam") return;
|
|
1118
|
+
const kind = this._deepUnderlying(targetType)?.kind;
|
|
1119
|
+
if (!kind || NILABLE_KINDS.has(kind)) return;
|
|
1120
|
+
this.err(`cannot convert nil to ${typeStr(targetType)}`, expr);
|
|
1121
|
+
},
|
|
1122
|
+
|
|
1123
|
+
// Pointers convert when their base types share an underlying type.
|
|
1124
|
+
_pointerConversionOk(srcPtr, tgtPtr, srcType, targetType, expr) {
|
|
1125
|
+
const sBase = this._deepUnderlying(srcPtr.base);
|
|
1126
|
+
const tBase = this._deepUnderlying(tgtPtr.base);
|
|
1127
|
+
if (typeStr(sBase) === typeStr(tBase)) return true;
|
|
1128
|
+
if (!this._sameStructFields(sBase, tBase)) return false;
|
|
1129
|
+
// WebAssembly struct types are nominal: the two pointers could not
|
|
1130
|
+
// share one object.
|
|
1131
|
+
if (this.target === "wasm" || this.target === "both") {
|
|
1132
|
+
this.recordBlocker("pointer conversion between struct types");
|
|
1133
|
+
this.err(
|
|
1134
|
+
`cannot convert ${typeStr(srcType)} to ${typeStr(targetType)} in a wasm package: pointers to distinct struct types cannot share storage; keep this code in a js package`,
|
|
1135
|
+
expr,
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
1138
|
+
return true;
|
|
1139
|
+
},
|
|
1140
|
+
|
|
478
1141
|
_checkConversionValidity(srcType, targetType, expr) {
|
|
479
1142
|
if (!srcType || !targetType || isAny(srcType) || isAny(targetType)) return;
|
|
480
1143
|
const srcResolved = srcType.kind === "named" ? srcType.underlying : srcType;
|
|
481
1144
|
const tgtResolved =
|
|
482
1145
|
targetType.kind === "named" ? targetType.underlying : targetType;
|
|
483
1146
|
|
|
1147
|
+
// `nil` converts to any type that has a nil value.
|
|
1148
|
+
if (isNil(srcType) && NILABLE_KINDS.has(tgtResolved?.kind)) return;
|
|
1149
|
+
|
|
484
1150
|
if (isNumeric(targetType) && isComplex(srcType)) {
|
|
485
1151
|
this.err(
|
|
486
1152
|
`cannot convert ${typeStr(srcType)} to ${typeStr(targetType)} (use real() or imag())`,
|
|
@@ -495,6 +1161,8 @@ export const expressionCheckMethods = {
|
|
|
495
1161
|
);
|
|
496
1162
|
return;
|
|
497
1163
|
}
|
|
1164
|
+
if (tgtResolved?.kind === "slice")
|
|
1165
|
+
this._rejectSeqAsSlice(srcType, expr.args?.[0], "a slice conversion");
|
|
498
1166
|
if (this._isMutualArraySlice(tgtResolved, srcResolved)) {
|
|
499
1167
|
this._checkElemTypeCompatible(
|
|
500
1168
|
tgtResolved.elem,
|
|
@@ -513,9 +1181,15 @@ export const expressionCheckMethods = {
|
|
|
513
1181
|
return;
|
|
514
1182
|
}
|
|
515
1183
|
if (typeStr(srcResolved) === typeStr(tgtResolved)) return;
|
|
1184
|
+
if (this._sameStructFields(srcResolved, tgtResolved)) return;
|
|
516
1185
|
if (isNumeric(srcResolved) && isNumeric(tgtResolved)) return;
|
|
517
1186
|
if (isString(srcResolved) && isString(tgtResolved)) return;
|
|
518
1187
|
if (isBool(srcResolved) && isBool(tgtResolved)) return;
|
|
1188
|
+
if (
|
|
1189
|
+
(isComplex(srcResolved) || isNumeric(srcResolved)) &&
|
|
1190
|
+
isComplex(tgtResolved)
|
|
1191
|
+
)
|
|
1192
|
+
return;
|
|
519
1193
|
if (
|
|
520
1194
|
isString(srcResolved) &&
|
|
521
1195
|
tgtResolved?.kind === "slice" &&
|
|
@@ -535,17 +1209,18 @@ export const expressionCheckMethods = {
|
|
|
535
1209
|
srcResolved.elem?.name === "int32")))
|
|
536
1210
|
)
|
|
537
1211
|
return;
|
|
538
|
-
if (
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
1212
|
+
if (
|
|
1213
|
+
srcResolved?.kind === "pointer" &&
|
|
1214
|
+
tgtResolved?.kind === "pointer" &&
|
|
1215
|
+
this._pointerConversionOk(
|
|
1216
|
+
srcResolved,
|
|
1217
|
+
tgtResolved,
|
|
1218
|
+
srcType,
|
|
1219
|
+
targetType,
|
|
1220
|
+
expr,
|
|
1221
|
+
)
|
|
1222
|
+
)
|
|
1223
|
+
return;
|
|
549
1224
|
this.err(
|
|
550
1225
|
`cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
|
|
551
1226
|
expr,
|
|
@@ -555,6 +1230,7 @@ export const expressionCheckMethods = {
|
|
|
555
1230
|
_checkTypeAssertExpr(expr, scope) {
|
|
556
1231
|
const srcType = this.checkExpr(expr.expr, scope);
|
|
557
1232
|
const targetType = this.resolveTypeNode(expr.type, scope);
|
|
1233
|
+
this._markDynamicType(expr.type, targetType);
|
|
558
1234
|
|
|
559
1235
|
// In Go, the source of a type assertion must be an interface type.
|
|
560
1236
|
// We allow `any` (which is GoFront's basic any) and interface types.
|
|
@@ -589,19 +1265,33 @@ export const expressionCheckMethods = {
|
|
|
589
1265
|
},
|
|
590
1266
|
|
|
591
1267
|
_checkRangeExpr(expr, scope) {
|
|
592
|
-
const collType = this.
|
|
593
|
-
|
|
594
|
-
|
|
1268
|
+
const collType = this._derefArrayPtr(
|
|
1269
|
+
expr,
|
|
1270
|
+
"expr",
|
|
1271
|
+
this.checkExpr(expr.expr, scope),
|
|
1272
|
+
);
|
|
1273
|
+
const resolved = this._underlyingOrCore(collType, scope);
|
|
1274
|
+
// Backends range over the core type of a type parameter.
|
|
1275
|
+
if (collType?.kind === "typeParam" && resolved !== collType)
|
|
1276
|
+
expr.expr._coreType = resolved;
|
|
595
1277
|
return this._rangeCollTypeTuple(resolved, collType);
|
|
596
1278
|
},
|
|
597
1279
|
|
|
598
1280
|
_checkInstantiationExpr(expr, scope) {
|
|
1281
|
+
const typeRef = this._genericTypeRef(expr, scope);
|
|
1282
|
+
if (typeRef) return typeRef;
|
|
599
1283
|
// Generic instantiation: Foo[int] or Foo[int, string]
|
|
600
1284
|
const baseType = this.checkExpr(expr.expr, scope);
|
|
601
1285
|
if (baseType?.kind === "generic") {
|
|
602
1286
|
const typeArgs = expr.typeArgs.map((ta) =>
|
|
603
1287
|
this.resolveTypeNode(ta, scope),
|
|
604
1288
|
);
|
|
1289
|
+
if (typeArgs.length > baseType.typeParams.length) {
|
|
1290
|
+
return this.err(
|
|
1291
|
+
`got ${typeArgs.length} type arguments but ${baseType.name ?? "function"} has ${baseType.typeParams.length} type parameters`,
|
|
1292
|
+
expr,
|
|
1293
|
+
);
|
|
1294
|
+
}
|
|
605
1295
|
// Check constraints
|
|
606
1296
|
for (
|
|
607
1297
|
let i = 0;
|
|
@@ -610,7 +1300,10 @@ export const expressionCheckMethods = {
|
|
|
610
1300
|
) {
|
|
611
1301
|
const tp = baseType.typeParams[i];
|
|
612
1302
|
const constraint = tp.constraint
|
|
613
|
-
? this.resolveTypeNode(
|
|
1303
|
+
? this.resolveTypeNode(
|
|
1304
|
+
tp.constraint,
|
|
1305
|
+
this._typeArgScope(baseType.typeParams, typeArgs, scope),
|
|
1306
|
+
)
|
|
614
1307
|
: null;
|
|
615
1308
|
if (constraint) this.checkConstraint(typeArgs[i], constraint, expr);
|
|
616
1309
|
}
|
|
@@ -625,9 +1318,28 @@ export const expressionCheckMethods = {
|
|
|
625
1318
|
// rewrite the node in place into an IndexExpr and check that instead.
|
|
626
1319
|
const indexExpr = this._instantiationAsIndex(expr, baseType);
|
|
627
1320
|
if (indexExpr) return this._checkIndexExpr(indexExpr, scope);
|
|
1321
|
+
if (baseType?.kind === "func" && !this._isPseudoGenericStdFunc(baseType))
|
|
1322
|
+
return this.err(
|
|
1323
|
+
`cannot instantiate ${this._calleeName(expr.expr)}: not a generic function`,
|
|
1324
|
+
expr,
|
|
1325
|
+
);
|
|
628
1326
|
return baseType;
|
|
629
1327
|
},
|
|
630
1328
|
|
|
1329
|
+
// Emitter-backed stdlib helpers (`slices.Sort`, `maps.Keys`) take `any`
|
|
1330
|
+
// parameters in place of type parameters and accept (and ignore) explicit
|
|
1331
|
+
// type arguments; a stdlib function with concrete parameters does not.
|
|
1332
|
+
_isPseudoGenericStdFunc(fnType) {
|
|
1333
|
+
return (fnType.params ?? []).some((p) => isAny(p));
|
|
1334
|
+
},
|
|
1335
|
+
|
|
1336
|
+
_calleeName(node) {
|
|
1337
|
+
if (node?.kind === "Ident") return node.name;
|
|
1338
|
+
if (node?.kind === "SelectorExpr")
|
|
1339
|
+
return `${this._calleeName(node.expr)}.${node.field}`;
|
|
1340
|
+
return "expression";
|
|
1341
|
+
},
|
|
1342
|
+
|
|
631
1343
|
_instantiationAsIndex(expr, baseType) {
|
|
632
1344
|
if (!baseType || expr.typeArgs.length !== 1) return null;
|
|
633
1345
|
const ta = expr.typeArgs[0];
|
|
@@ -652,32 +1364,97 @@ export const expressionCheckMethods = {
|
|
|
652
1364
|
return expr;
|
|
653
1365
|
},
|
|
654
1366
|
|
|
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
|
-
|
|
1367
|
+
// `int(K)`, `uint8(K)`, `MyInt(K)`: a conversion to an integer type.
|
|
1368
|
+
_isIntConversion(expr) {
|
|
1369
|
+
if (expr.args?.length !== 1) return false;
|
|
1370
|
+
const target = expr._conversionTargetType;
|
|
1371
|
+
if (expr._isTypeConversion && target)
|
|
1372
|
+
return isInteger(target.kind === "named" ? target.underlying : target);
|
|
1373
|
+
return (
|
|
1374
|
+
expr.func?.kind === "Ident" && SIZED_INT_NAMES.includes(expr.func.name)
|
|
1375
|
+
);
|
|
1376
|
+
},
|
|
1377
|
+
|
|
1378
|
+
// Keyed array literal indices (Go spec): each key must be a non-negative
|
|
1379
|
+
// integer constant, and no index may appear twice. The resolved index is
|
|
1380
|
+
// stored on the KeyValueExpr as `_index` for compositeLitIndices.
|
|
1381
|
+
_checkArrayLitKeys(expr, scope) {
|
|
1382
|
+
const seen = new Set();
|
|
1383
|
+
let next = 0;
|
|
1384
|
+
let unknown = false;
|
|
1385
|
+
for (const elem of expr.elems) {
|
|
1386
|
+
if (elem.kind === "KeyValueExpr") {
|
|
1387
|
+
this.checkExpr(elem.key, scope);
|
|
1388
|
+
const idx = this._constIntValue(elem.key, scope);
|
|
1389
|
+
const text = constKeyText(elem.key);
|
|
1390
|
+
if (idx === null && this._isConstExpr(elem.key, scope)) {
|
|
1391
|
+
// A constant key the folder cannot evaluate (`len(arr)`) keeps
|
|
1392
|
+
// the literal/positional index of compositeLitIndices; later
|
|
1393
|
+
// indices are not duplicate-checked.
|
|
1394
|
+
unknown = true;
|
|
1395
|
+
next++;
|
|
1396
|
+
continue;
|
|
1397
|
+
}
|
|
1398
|
+
if (idx === null)
|
|
1399
|
+
this.err(`index ${text} must be integer constant`, elem.key);
|
|
1400
|
+
else if (idx < 0)
|
|
1401
|
+
this.err(
|
|
1402
|
+
`index ${text} must be non-negative integer constant`,
|
|
1403
|
+
elem.key,
|
|
1404
|
+
);
|
|
1405
|
+
else next = idx;
|
|
1406
|
+
elem._index = next;
|
|
1407
|
+
}
|
|
1408
|
+
if (!unknown && seen.has(next))
|
|
1409
|
+
this.err(`duplicate index ${next} in array or slice literal`, elem);
|
|
1410
|
+
seen.add(next);
|
|
1411
|
+
next++;
|
|
676
1412
|
}
|
|
677
|
-
|
|
678
|
-
|
|
1413
|
+
},
|
|
1414
|
+
|
|
1415
|
+
// Whether `e` is a constant expression (whose value the folder may not
|
|
1416
|
+
// know): literals, constants, operators, conversions and builtin calls.
|
|
1417
|
+
_isConstExpr(e, scope) {
|
|
1418
|
+
switch (e?.kind) {
|
|
1419
|
+
case "BasicLit":
|
|
1420
|
+
return true;
|
|
1421
|
+
case "ParenExpr":
|
|
1422
|
+
return this._isConstExpr(e.expr, scope);
|
|
1423
|
+
case "UnaryExpr":
|
|
1424
|
+
return (
|
|
1425
|
+
["-", "+", "^", "!"].includes(e.op) &&
|
|
1426
|
+
this._isConstExpr(e.operand, scope)
|
|
1427
|
+
);
|
|
1428
|
+
case "BinaryExpr":
|
|
1429
|
+
return (
|
|
1430
|
+
this._isConstExpr(e.left, scope) && this._isConstExpr(e.right, scope)
|
|
1431
|
+
);
|
|
1432
|
+
case "Ident":
|
|
1433
|
+
return !!(scope ?? this.globals)
|
|
1434
|
+
.lookupScope(e.name)
|
|
1435
|
+
?._consts.has(e.name);
|
|
1436
|
+
case "SelectorExpr": {
|
|
1437
|
+
// Package members: constant unless the package says otherwise.
|
|
1438
|
+
const ns =
|
|
1439
|
+
e.expr?.kind === "Ident"
|
|
1440
|
+
? (scope ?? this.globals).lookup(e.expr.name)
|
|
1441
|
+
: null;
|
|
1442
|
+
if (ns?.kind !== "namespace") return false;
|
|
1443
|
+
return ns._consts ? ns._consts.has(e.field) : true;
|
|
1444
|
+
}
|
|
1445
|
+
case "TypeConversion":
|
|
1446
|
+
return this._isConstExpr(e.expr, scope);
|
|
1447
|
+
case "CallExpr":
|
|
1448
|
+
if (e._isTypeConversion || this._isIntConversion(e))
|
|
1449
|
+
return this._isConstExpr(e.args?.[0], scope);
|
|
1450
|
+
return (
|
|
1451
|
+
e.func?.kind === "Ident" &&
|
|
1452
|
+
CONST_BUILTINS.has(e.func.name) &&
|
|
1453
|
+
(scope ?? this.globals).lookup(e.func.name)?.kind === "builtin"
|
|
1454
|
+
);
|
|
1455
|
+
default:
|
|
1456
|
+
return false;
|
|
679
1457
|
}
|
|
680
|
-
return null;
|
|
681
1458
|
},
|
|
682
1459
|
|
|
683
1460
|
_resolveCallArgTypes(expr, scope) {
|
|
@@ -705,11 +1482,125 @@ export const expressionCheckMethods = {
|
|
|
705
1482
|
if (!resolved) return null;
|
|
706
1483
|
fnType = resolved;
|
|
707
1484
|
}
|
|
708
|
-
return fnType;
|
|
1485
|
+
return this._completePartialInstantiation(expr, argTypes, scope) ?? fnType;
|
|
1486
|
+
},
|
|
1487
|
+
|
|
1488
|
+
// `F[int](x)` with fewer explicit type arguments than type parameters:
|
|
1489
|
+
// infer the rest from the call arguments and record the full list.
|
|
1490
|
+
_completePartialInstantiation(expr, argTypes, scope) {
|
|
1491
|
+
const inst = expr.func;
|
|
1492
|
+
const generic = inst?.kind === "InstantiationExpr" && inst._genericFnType;
|
|
1493
|
+
if (!generic || inst._typeArgs.length >= generic.typeParams.length)
|
|
1494
|
+
return null;
|
|
1495
|
+
const typeArgs = this.inferTypeArgs(
|
|
1496
|
+
generic,
|
|
1497
|
+
this._inferArgTypes(expr, argTypes),
|
|
1498
|
+
inst._typeArgs,
|
|
1499
|
+
scope,
|
|
1500
|
+
);
|
|
1501
|
+
if (!typeArgs) {
|
|
1502
|
+
this.err(
|
|
1503
|
+
`Cannot infer type arguments for generic function '${generic.name}'`,
|
|
1504
|
+
expr,
|
|
1505
|
+
);
|
|
1506
|
+
return null;
|
|
1507
|
+
}
|
|
1508
|
+
inst._typeArgs = typeArgs;
|
|
1509
|
+
if (inst.expr) inst.expr._typeArgs = typeArgs;
|
|
1510
|
+
return this.instantiateGenericFunc(generic, typeArgs);
|
|
1511
|
+
},
|
|
1512
|
+
|
|
1513
|
+
// `T(x)` with T a type parameter: only a numeric x converts (the constraint
|
|
1514
|
+
// decides the target at instantiation).
|
|
1515
|
+
_checkTypeParamConversion(tp, argTypes, expr, scope) {
|
|
1516
|
+
expr._isTypeConversion = true;
|
|
1517
|
+
expr._conversionTargetType = tp;
|
|
1518
|
+
if (expr.args.length !== 1) {
|
|
1519
|
+
return this.err(
|
|
1520
|
+
`cannot convert ${expr.args.length} arguments to type ${typeStr(tp)}`,
|
|
1521
|
+
expr,
|
|
1522
|
+
);
|
|
1523
|
+
}
|
|
1524
|
+
// A constraint with a core type (`~[]E`) converts as that type does.
|
|
1525
|
+
const core = this._coreType(tp, scope);
|
|
1526
|
+
if (core) {
|
|
1527
|
+
const src = argTypes[0];
|
|
1528
|
+
const nilOk = ["slice", "map", "pointer", "func", "chan", "interface"];
|
|
1529
|
+
if (src === NIL || (src?.kind === "basic" && src.name === "nil")) {
|
|
1530
|
+
if (!nilOk.includes(core.kind))
|
|
1531
|
+
this.err(`cannot convert nil to ${typeStr(tp)}`, expr);
|
|
1532
|
+
} else if (src?.kind !== "typeParam") {
|
|
1533
|
+
this._checkConversionValidity(src, core, expr);
|
|
1534
|
+
}
|
|
1535
|
+
return tp;
|
|
1536
|
+
}
|
|
1537
|
+
if (
|
|
1538
|
+
isNumeric(argTypes[0]) &&
|
|
1539
|
+
!this._constraintHasTypeSet(tp.constraint, scope)
|
|
1540
|
+
)
|
|
1541
|
+
return this.err(
|
|
1542
|
+
`cannot convert ${typeStr(argTypes[0])} to type ${typeStr(tp)}: ${typeStr(tp)} is not constrained to a set of convertible types`,
|
|
1543
|
+
expr,
|
|
1544
|
+
);
|
|
1545
|
+
if (
|
|
1546
|
+
!isNumeric(argTypes[0]) &&
|
|
1547
|
+
!isAny(argTypes[0]) &&
|
|
1548
|
+
argTypes[0]?.kind !== "typeParam"
|
|
1549
|
+
)
|
|
1550
|
+
this.err(
|
|
1551
|
+
`cannot convert ${typeStr(argTypes[0])} to ${typeStr(tp)}`,
|
|
1552
|
+
expr,
|
|
1553
|
+
);
|
|
1554
|
+
return tp;
|
|
1555
|
+
},
|
|
1556
|
+
|
|
1557
|
+
// A conversion to a type parameter needs a constraint that lists types
|
|
1558
|
+
// (`~int | ~float64`, or an interface embedding such a union); `any`,
|
|
1559
|
+
// `comparable` and method-only constraints allow every type argument.
|
|
1560
|
+
_constraintHasTypeSet(constraint, scope = this.globals) {
|
|
1561
|
+
if (!constraint) return true;
|
|
1562
|
+
if (constraint.kind === "UnionConstraint") return true;
|
|
1563
|
+
// Receiver type parameters keep their constraint as an AST node.
|
|
1564
|
+
const c = this.resolveType(
|
|
1565
|
+
constraint.kind === "TypeName" || constraint.kind === "InterfaceType"
|
|
1566
|
+
? this.resolveTypeNode(constraint, scope)
|
|
1567
|
+
: constraint,
|
|
1568
|
+
);
|
|
1569
|
+
const base = c?.kind === "named" ? this.resolveType(c.underlying) : c;
|
|
1570
|
+
if (base?.kind !== "interface") return false;
|
|
1571
|
+
if (base.unionConstraint) return true;
|
|
1572
|
+
return (base._typeSetEmbeds ?? []).some((e) =>
|
|
1573
|
+
this._constraintHasTypeSet(e, scope),
|
|
1574
|
+
);
|
|
1575
|
+
},
|
|
1576
|
+
|
|
1577
|
+
// A direct call of a `//gofront:native` function is marked `_native` so a
|
|
1578
|
+
// backend may substitute host code. A function used as a value (`f :=
|
|
1579
|
+
// str.Index; f(a, b)`) is not a direct reference and calls the Go body.
|
|
1580
|
+
_markNativeCall(expr, fnType, scope) {
|
|
1581
|
+
const callee =
|
|
1582
|
+
expr.func.kind === "InstantiationExpr" ? expr.func.expr : expr.func;
|
|
1583
|
+
const native = fnType?.native ?? fnType?.underlying?.native ?? null;
|
|
1584
|
+
const direct =
|
|
1585
|
+
callee.kind === "Ident"
|
|
1586
|
+
? scope.lookupScope(callee.name) === this.globals &&
|
|
1587
|
+
Boolean(this._funcDeclNames?.has(callee.name))
|
|
1588
|
+
: callee.kind === "SelectorExpr" && Boolean(callee._stdName);
|
|
1589
|
+
expr._native = native && direct ? native : null;
|
|
709
1590
|
},
|
|
710
1591
|
|
|
711
1592
|
checkCall(expr, scope) {
|
|
1593
|
+
expr.func._typeRefOk = true;
|
|
712
1594
|
let fnType = this.checkExpr(expr.func, scope);
|
|
1595
|
+
// `slices.Sort[[]int](xs)` on an emitter-backed (non-generic) stdlib
|
|
1596
|
+
// function: the type arguments are inferred, so call it as `slices.Sort(xs)`.
|
|
1597
|
+
if (
|
|
1598
|
+
expr.func.kind === "InstantiationExpr" &&
|
|
1599
|
+
!expr.func._genericFnType &&
|
|
1600
|
+
expr.func.expr?.kind === "SelectorExpr" &&
|
|
1601
|
+
fnType?.kind === "func"
|
|
1602
|
+
)
|
|
1603
|
+
expr.func = expr.func.expr;
|
|
713
1604
|
this._checkSharedAllocSite(expr, scope);
|
|
714
1605
|
this._checkBoundaryCallInLoop(expr, scope);
|
|
715
1606
|
if (fnType.kind === "builtin") {
|
|
@@ -721,14 +1612,23 @@ export const expressionCheckMethods = {
|
|
|
721
1612
|
return this.checkBuiltin(fnType.name, expr, argTypes, scope);
|
|
722
1613
|
}
|
|
723
1614
|
|
|
1615
|
+
this._markNativeCall(expr, fnType, scope);
|
|
724
1616
|
const argTypes = this._resolveCallArgTypes(expr, scope);
|
|
725
1617
|
|
|
726
1618
|
if (fnType?._tainted) return TAINTED_ANY;
|
|
1619
|
+
// `any(x)` / `error(x)` / `Iface(x)`: box x as an implicit conversion would.
|
|
1620
|
+
if (expr.func._isTypeRef && expr.args.length === 1 && argTypes[0])
|
|
1621
|
+
this._markIfaceBox(fnType, argTypes[0], expr.args[0]);
|
|
727
1622
|
if (isAny(fnType)) return ANY;
|
|
728
1623
|
|
|
729
1624
|
// Named type used as a constructor/conversion: Greeter(fn), NodeFunc(fn)
|
|
730
1625
|
// The callee is a type name, not a callable variable.
|
|
731
|
-
if (
|
|
1626
|
+
if (
|
|
1627
|
+
(fnType.kind === "named" ||
|
|
1628
|
+
fnType.kind === "pointer" ||
|
|
1629
|
+
fnType.kind === "basic") &&
|
|
1630
|
+
expr.func._isTypeRef
|
|
1631
|
+
) {
|
|
732
1632
|
expr._isTypeConversion = true;
|
|
733
1633
|
expr._conversionTargetType = fnType;
|
|
734
1634
|
if (expr.args.length !== 1) {
|
|
@@ -742,6 +1642,10 @@ export const expressionCheckMethods = {
|
|
|
742
1642
|
return fnType;
|
|
743
1643
|
}
|
|
744
1644
|
|
|
1645
|
+
if (fnType.kind === "typeParam" && expr.func._isTypeRef) {
|
|
1646
|
+
return this._checkTypeParamConversion(fnType, argTypes, expr, scope);
|
|
1647
|
+
}
|
|
1648
|
+
|
|
745
1649
|
fnType = this._resolveCallFnType(fnType, argTypes, expr, scope);
|
|
746
1650
|
if (!fnType) return ANY;
|
|
747
1651
|
if (fnType.kind !== "func") {
|
|
@@ -750,13 +1654,6 @@ export const expressionCheckMethods = {
|
|
|
750
1654
|
|
|
751
1655
|
this._checkCallArgs(fnType, argTypes, expr, scope);
|
|
752
1656
|
|
|
753
|
-
// Pseudo-generic stdlib helpers (maps.Keys, slices.Clone, ...) declare
|
|
754
|
-
// how their result derives from the first argument.
|
|
755
|
-
if (fnType._derivedReturn) {
|
|
756
|
-
const derived = this._derivedReturnType(fnType._derivedReturn, argTypes);
|
|
757
|
-
if (derived) return derived;
|
|
758
|
-
}
|
|
759
|
-
|
|
760
1657
|
const ret = fnType.returns;
|
|
761
1658
|
if (!ret || ret.length === 0) return VOID;
|
|
762
1659
|
if (ret.length === 1) return ret[0];
|
|
@@ -806,27 +1703,34 @@ export const expressionCheckMethods = {
|
|
|
806
1703
|
);
|
|
807
1704
|
},
|
|
808
1705
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
1706
|
+
// Argument types for inference: a spread argument `xs...` stands for its
|
|
1707
|
+
// elements, which is what the variadic parameter type describes.
|
|
1708
|
+
_inferArgTypes(expr, argTypes) {
|
|
1709
|
+
return argTypes.map((t, i) => {
|
|
1710
|
+
if (!expr.args?.[i]?._spread) return t;
|
|
1711
|
+
const u = this.resolveType(t?.kind === "named" ? t.underlying : t);
|
|
1712
|
+
return u?.kind === "slice" ? u.elem : t;
|
|
1713
|
+
});
|
|
1714
|
+
},
|
|
1715
|
+
|
|
1716
|
+
// Scope where each type parameter name stands for its type argument, so a
|
|
1717
|
+
// constraint that mentions another parameter (`S ~[]E`) resolves.
|
|
1718
|
+
_typeArgScope(typeParams, typeArgs, scope) {
|
|
1719
|
+
const out = new Scope(scope);
|
|
1720
|
+
typeParams.forEach((tp, i) => {
|
|
1721
|
+
if (typeArgs[i])
|
|
1722
|
+
out.define(tp.name, { kind: "aliasArg", type: typeArgs[i] });
|
|
1723
|
+
});
|
|
1724
|
+
return out;
|
|
826
1725
|
},
|
|
827
1726
|
|
|
828
1727
|
_resolveGenericFnType(fnType, argTypes, expr, scope) {
|
|
829
|
-
const typeArgs = this.inferTypeArgs(
|
|
1728
|
+
const typeArgs = this.inferTypeArgs(
|
|
1729
|
+
fnType,
|
|
1730
|
+
this._inferArgTypes(expr, argTypes),
|
|
1731
|
+
[],
|
|
1732
|
+
scope,
|
|
1733
|
+
);
|
|
830
1734
|
if (!typeArgs) {
|
|
831
1735
|
this.err(
|
|
832
1736
|
`Cannot infer type arguments for generic function '${fnType.name}'`,
|
|
@@ -838,7 +1742,10 @@ export const expressionCheckMethods = {
|
|
|
838
1742
|
for (let i = 0; i < fnType.typeParams.length && i < typeArgs.length; i++) {
|
|
839
1743
|
const tp = fnType.typeParams[i];
|
|
840
1744
|
const constraint = tp.constraint
|
|
841
|
-
? this.resolveTypeNode(
|
|
1745
|
+
? this.resolveTypeNode(
|
|
1746
|
+
tp.constraint,
|
|
1747
|
+
this._typeArgScope(fnType.typeParams, typeArgs, scope),
|
|
1748
|
+
)
|
|
842
1749
|
: null;
|
|
843
1750
|
if (constraint) this.checkConstraint(typeArgs[i], constraint, expr);
|
|
844
1751
|
}
|
|
@@ -865,8 +1772,31 @@ export const expressionCheckMethods = {
|
|
|
865
1772
|
);
|
|
866
1773
|
},
|
|
867
1774
|
|
|
1775
|
+
// WASM packs a variadic call's trailing arguments into a slice, which it
|
|
1776
|
+
// cannot do for `f(g())` yet. Emitter-backed stdlib calls (`fmt.X`) are
|
|
1777
|
+
// not packed; GoFront and Go-source package functions are.
|
|
1778
|
+
_checkMultiForwardVariadic(fnType, expr) {
|
|
1779
|
+
if (!expr._multiForward || !fnType.variadic) return;
|
|
1780
|
+
if (this.target !== "wasm" && this.target !== "both") return;
|
|
1781
|
+
const ns =
|
|
1782
|
+
expr.func?.kind === "SelectorExpr" ? expr.func.expr?._type : null;
|
|
1783
|
+
const emitterBacked =
|
|
1784
|
+
ns?.kind === "namespace" &&
|
|
1785
|
+
!ns._gofront &&
|
|
1786
|
+
!ns._mangle?.has(expr.func.field);
|
|
1787
|
+
if (emitterBacked) return;
|
|
1788
|
+
// The generated wrapper of a Print-family call takes the results as
|
|
1789
|
+
// separate parameters (src/lower/fmt-formatters.js).
|
|
1790
|
+
if (ns?.name === "fmt" && FMT_PRINT_FUNCS.has(expr.func.field)) return;
|
|
1791
|
+
this.err(
|
|
1792
|
+
"passing multiple return values to a variadic function is not supported in wasm packages; assign them to variables first",
|
|
1793
|
+
expr,
|
|
1794
|
+
);
|
|
1795
|
+
},
|
|
1796
|
+
|
|
868
1797
|
_checkCallArgs(fnType, argTypes, expr, scope = null) {
|
|
869
1798
|
this._checkArgCount(fnType, argTypes, expr);
|
|
1799
|
+
this._checkMultiForwardVariadic(fnType, expr);
|
|
870
1800
|
const maxLen = fnType.variadic
|
|
871
1801
|
? argTypes.length
|
|
872
1802
|
: Math.min(fnType.params.length, argTypes.length);
|
|
@@ -875,37 +1805,63 @@ export const expressionCheckMethods = {
|
|
|
875
1805
|
fnType.variadic && i >= fnType.params.length - 1
|
|
876
1806
|
? fnType.params.length - 1
|
|
877
1807
|
: i;
|
|
878
|
-
if (expr.args[i]?._spread)
|
|
1808
|
+
if (expr.args[i]?._spread) {
|
|
1809
|
+
this._rejectSeqAsSlice(argTypes[i], expr.args[i], "a spread argument");
|
|
1810
|
+
continue;
|
|
1811
|
+
}
|
|
879
1812
|
const targetParam = fnType.params[paramIdx];
|
|
880
1813
|
const node = expr._multiForward ? null : expr.args[i];
|
|
881
|
-
this.assertAssignable(targetParam, argTypes[i], node);
|
|
1814
|
+
this.assertAssignable(targetParam, argTypes[i], node, "argument");
|
|
882
1815
|
}
|
|
883
1816
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1817
|
+
this._checkSimdLaneArgs(expr, scope);
|
|
1818
|
+
},
|
|
1819
|
+
|
|
1820
|
+
// Lane, WithLane, Swizzle and Shuffle take constant lane indices.
|
|
1821
|
+
_checkSimdLaneArgs(expr, scope) {
|
|
1822
|
+
if (expr.func?.kind !== "SelectorExpr") return;
|
|
1823
|
+
const recv = expr.func.expr?._type;
|
|
1824
|
+
const field = expr.func.field;
|
|
1825
|
+
let first = 0;
|
|
1826
|
+
let count = 0;
|
|
1827
|
+
let max = 3;
|
|
1828
|
+
if (recv?.kind === "namespace" && recv.members?.Mask32x4) {
|
|
1829
|
+
if (field !== "Shuffle") return;
|
|
1830
|
+
first = 2;
|
|
1831
|
+
count = 4;
|
|
1832
|
+
max = 7;
|
|
1833
|
+
} else if (this._isSimdType(recv)) {
|
|
1834
|
+
if (field === "Lane" || field === "WithLane") count = 1;
|
|
1835
|
+
else if (field === "Swizzle") count = 4;
|
|
1836
|
+
else return;
|
|
1837
|
+
} else {
|
|
1838
|
+
return;
|
|
1839
|
+
}
|
|
1840
|
+
for (let i = first; i < first + count; i++) {
|
|
1841
|
+
const arg = expr.args[i];
|
|
1842
|
+
if (!arg) continue;
|
|
1843
|
+
const idx = this._constIntValue(arg, scope);
|
|
1844
|
+
if (idx !== null) arg._constInt = idx;
|
|
1845
|
+
if (idx === null || idx < 0 || idx > max) {
|
|
1846
|
+
this.err(
|
|
1847
|
+
`lane index must be a constant integer in range [0, ${max}]`,
|
|
1848
|
+
arg,
|
|
1849
|
+
);
|
|
905
1850
|
}
|
|
906
1851
|
}
|
|
907
1852
|
},
|
|
908
1853
|
|
|
1854
|
+
_isSimdType(t) {
|
|
1855
|
+
return (
|
|
1856
|
+
t?.name === "simd.F32x4" ||
|
|
1857
|
+
t?.name === "F32x4" ||
|
|
1858
|
+
t?.name === "simd.Mask32x4" ||
|
|
1859
|
+
t?.name === "Mask32x4" ||
|
|
1860
|
+
t?.underlying?.kind === "simd" ||
|
|
1861
|
+
t?.kind === "simd"
|
|
1862
|
+
);
|
|
1863
|
+
},
|
|
1864
|
+
|
|
909
1865
|
checkBuiltin(name, expr, argTypes, scope) {
|
|
910
1866
|
const gen = BUILTIN_CHECK[name];
|
|
911
1867
|
return gen ? gen(this, name, expr, argTypes, scope) : ANY;
|
|
@@ -923,14 +1879,32 @@ export const expressionCheckMethods = {
|
|
|
923
1879
|
},
|
|
924
1880
|
|
|
925
1881
|
_checkBuiltinLen(name, expr, argTypes) {
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1882
|
+
this._rejectSeqAsSlice(argTypes[0], expr.args?.[0], `${name}()`);
|
|
1883
|
+
const operand = expr.args?.[0];
|
|
1884
|
+
if (expr.args?.length === 1)
|
|
1885
|
+
argTypes[0] = this._derefArrayPtr(expr.args, 0, argTypes[0]);
|
|
1886
|
+
// `len` and `cap` of an array (or pointer to one) are its length.
|
|
1887
|
+
const size = this._constLenFromType(argTypes[0]);
|
|
1888
|
+
if (size != null) {
|
|
1889
|
+
expr._constLen = size;
|
|
1890
|
+
// The operand is not dereferenced: a nil `*[N]T` has length N.
|
|
1891
|
+
if (expr.args.length === 1) expr.args[0] = operand;
|
|
1892
|
+
}
|
|
1893
|
+
if (name === "cap") {
|
|
1894
|
+
if (
|
|
1895
|
+
(argTypes[0]?.underlying ?? argTypes[0])?.kind === "map" ||
|
|
1896
|
+
isString(argTypes[0])
|
|
1897
|
+
) {
|
|
1898
|
+
this.err(
|
|
1899
|
+
`invalid argument: ${exprText(expr.args?.[0])} (${operandKind(expr.args?.[0], typeStr(argTypes[0]), false)}) for cap`,
|
|
1900
|
+
expr,
|
|
1901
|
+
);
|
|
1902
|
+
} else if (sharedInfo(argTypes[0])) {
|
|
1903
|
+
this.err(
|
|
1904
|
+
`cannot use cap() on ${typeStr(argTypes[0])}: shared buffers have a fixed size; use len()`,
|
|
1905
|
+
expr,
|
|
1906
|
+
);
|
|
1907
|
+
}
|
|
934
1908
|
}
|
|
935
1909
|
return INT;
|
|
936
1910
|
},
|
|
@@ -952,6 +1926,17 @@ export const expressionCheckMethods = {
|
|
|
952
1926
|
}
|
|
953
1927
|
if (crt.kind === "untyped" && cit.kind === "untyped")
|
|
954
1928
|
return UNTYPED_COMPLEX;
|
|
1929
|
+
if (crt.name === "float32" && cit.name === "float32") return COMPLEX64;
|
|
1930
|
+
if (crt.name === "float64" && cit.name === "float64") return COMPLEX128;
|
|
1931
|
+
if (crt.name === "float32" || cit.name === "float32") {
|
|
1932
|
+
if (crt.name === "float64" || cit.name === "float64") {
|
|
1933
|
+
this.err(
|
|
1934
|
+
`invalid complex call: mismatched float types (${typeStr(crt)} and ${typeStr(cit)})`,
|
|
1935
|
+
expr,
|
|
1936
|
+
);
|
|
1937
|
+
}
|
|
1938
|
+
return COMPLEX64;
|
|
1939
|
+
}
|
|
955
1940
|
return COMPLEX128;
|
|
956
1941
|
},
|
|
957
1942
|
|
|
@@ -966,6 +1951,7 @@ export const expressionCheckMethods = {
|
|
|
966
1951
|
return ANY;
|
|
967
1952
|
}
|
|
968
1953
|
if (zt.kind === "untyped") return UNTYPED_FLOAT;
|
|
1954
|
+
if (isComplex64(zt)) return FLOAT32;
|
|
969
1955
|
return FLOAT64;
|
|
970
1956
|
},
|
|
971
1957
|
|
|
@@ -997,6 +1983,11 @@ export const expressionCheckMethods = {
|
|
|
997
1983
|
: this.resolveTypeNode(expr.typeExpr, scope);
|
|
998
1984
|
const base = t.kind === "named" ? t.underlying : t;
|
|
999
1985
|
const typeNode = expr.typeExpr;
|
|
1986
|
+
if (expr.typeExpr === null && hintType && base?.kind === "basic") {
|
|
1987
|
+
this.err(`invalid composite literal type ${typeStr(t)}`, expr);
|
|
1988
|
+
return t;
|
|
1989
|
+
}
|
|
1990
|
+
if (this._isSliceOrArray(base)) this._checkArrayLitKeys(expr, scope);
|
|
1000
1991
|
this._inferArraySize(expr, typeNode, base);
|
|
1001
1992
|
if (base?.kind === "struct")
|
|
1002
1993
|
return this._checkStructCompositeLit(expr, scope, t, base);
|
|
@@ -1010,6 +2001,11 @@ export const expressionCheckMethods = {
|
|
|
1010
2001
|
},
|
|
1011
2002
|
|
|
1012
2003
|
_checkSimdCompositeLit(expr, scope, t, base) {
|
|
2004
|
+
if (base.simd === "Mask32x4") {
|
|
2005
|
+
if (expr.elems.length > 0)
|
|
2006
|
+
this.err("simd.Mask32x4 literal cannot have values", expr);
|
|
2007
|
+
return t;
|
|
2008
|
+
}
|
|
1013
2009
|
if (expr.elems.length > 4) {
|
|
1014
2010
|
this.err("too many values in simd.F32x4 literal", expr);
|
|
1015
2011
|
}
|
|
@@ -1021,7 +2017,11 @@ export const expressionCheckMethods = {
|
|
|
1021
2017
|
},
|
|
1022
2018
|
|
|
1023
2019
|
_checkStructPositionalElems(expr, scope, t, base) {
|
|
1024
|
-
const fieldNames =
|
|
2020
|
+
const fieldNames = base._structFields
|
|
2021
|
+
? base._structFields.flatMap((f) =>
|
|
2022
|
+
f.embedded ? [embeddedFieldName(f)] : f.names,
|
|
2023
|
+
)
|
|
2024
|
+
: [...(base.fields?.keys() ?? [])];
|
|
1025
2025
|
for (let i = 0; i < expr.elems.length; i++) {
|
|
1026
2026
|
const elem = expr.elems[i];
|
|
1027
2027
|
const fieldName = fieldNames[i];
|
|
@@ -1041,12 +2041,9 @@ export const expressionCheckMethods = {
|
|
|
1041
2041
|
for (const elem of expr.elems) {
|
|
1042
2042
|
if (elem.kind === "KeyValueExpr") {
|
|
1043
2043
|
const keyName = elem.key.name;
|
|
1044
|
-
const embed = base.
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
inner.kind === "named" ? inner.name.split(".").pop() : null;
|
|
1048
|
-
return name === keyName;
|
|
1049
|
-
});
|
|
2044
|
+
const embed = base._structFields?.find(
|
|
2045
|
+
(f) => f.embedded && embeddedFieldName(f) === keyName,
|
|
2046
|
+
)?.type;
|
|
1050
2047
|
if (embed) elem._isEmbedInit = true;
|
|
1051
2048
|
const fieldType = base.fields?.get(keyName) ?? embed;
|
|
1052
2049
|
if (fieldType) {
|
|
@@ -1078,8 +2075,19 @@ export const expressionCheckMethods = {
|
|
|
1078
2075
|
const et = this.checkCompositeLit(elem, scope, base.elem);
|
|
1079
2076
|
elem._type = et;
|
|
1080
2077
|
} else if (elem.kind === "KeyValueExpr") {
|
|
1081
|
-
|
|
1082
|
-
|
|
2078
|
+
if (
|
|
2079
|
+
elem.value.kind === "CompositeLit" &&
|
|
2080
|
+
elem.value.typeExpr === null
|
|
2081
|
+
) {
|
|
2082
|
+
elem.value._type = this.checkCompositeLit(
|
|
2083
|
+
elem.value,
|
|
2084
|
+
scope,
|
|
2085
|
+
base.elem,
|
|
2086
|
+
);
|
|
2087
|
+
} else {
|
|
2088
|
+
const vt = this.checkExpr(elem.value, scope);
|
|
2089
|
+
this.assertAssignable(base.elem, vt, elem.value);
|
|
2090
|
+
}
|
|
1083
2091
|
} else {
|
|
1084
2092
|
const et = this.checkExpr(elem, scope);
|
|
1085
2093
|
this.assertAssignable(base.elem, et, elem);
|
|
@@ -1103,7 +2111,13 @@ export const expressionCheckMethods = {
|
|
|
1103
2111
|
_checkMapCompositeLit(expr, scope, t, base) {
|
|
1104
2112
|
for (const elem of expr.elems) {
|
|
1105
2113
|
if (elem.kind === "KeyValueExpr") {
|
|
1106
|
-
|
|
2114
|
+
let kt;
|
|
2115
|
+
if (elem.key.kind === "CompositeLit" && elem.key.typeExpr === null) {
|
|
2116
|
+
kt = this.checkCompositeLit(elem.key, scope, base.key);
|
|
2117
|
+
elem.key._type = kt;
|
|
2118
|
+
} else {
|
|
2119
|
+
kt = this.checkExpr(elem.key, scope);
|
|
2120
|
+
}
|
|
1107
2121
|
// value may be an implicit composite lit
|
|
1108
2122
|
let vt;
|
|
1109
2123
|
if (
|
|
@@ -1116,6 +2130,7 @@ export const expressionCheckMethods = {
|
|
|
1116
2130
|
vt = this.checkExpr(elem.value, scope);
|
|
1117
2131
|
}
|
|
1118
2132
|
this.assertAssignable(base.key, kt, elem.key);
|
|
2133
|
+
this._markIfaceKey(base, elem.key, kt);
|
|
1119
2134
|
this.assertAssignable(base.value, vt, elem.value);
|
|
1120
2135
|
}
|
|
1121
2136
|
}
|
|
@@ -1123,15 +2138,37 @@ export const expressionCheckMethods = {
|
|
|
1123
2138
|
},
|
|
1124
2139
|
};
|
|
1125
2140
|
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
2141
|
+
// Builtins whose calls may be constant expressions (`len("abc")`).
|
|
2142
|
+
const CONST_BUILTINS = new Set([
|
|
2143
|
+
"len",
|
|
2144
|
+
"cap",
|
|
2145
|
+
"real",
|
|
2146
|
+
"imag",
|
|
2147
|
+
"complex",
|
|
2148
|
+
"min",
|
|
2149
|
+
"max",
|
|
2150
|
+
]);
|
|
2151
|
+
|
|
2152
|
+
// Source-like text of a literal key for diagnostics (`k`, `-1`, `K + 1`).
|
|
2153
|
+
function constKeyText(e) {
|
|
2154
|
+
switch (e?.kind) {
|
|
2155
|
+
case "Ident":
|
|
2156
|
+
return e.name;
|
|
2157
|
+
case "BasicLit":
|
|
2158
|
+
return String(e.value);
|
|
2159
|
+
case "UnaryExpr":
|
|
2160
|
+
return `${e.op}${constKeyText(e.operand)}`;
|
|
2161
|
+
case "BinaryExpr":
|
|
2162
|
+
return `${constKeyText(e.left)} ${e.op} ${constKeyText(e.right)}`;
|
|
2163
|
+
case "ParenExpr":
|
|
2164
|
+
return `(${constKeyText(e.expr)})`;
|
|
2165
|
+
case "SelectorExpr":
|
|
2166
|
+
return `${constKeyText(e.expr)}.${e.field}`;
|
|
2167
|
+
default:
|
|
2168
|
+
return "expression";
|
|
1135
2169
|
}
|
|
1136
|
-
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
function computeMaxIndex(elems) {
|
|
2173
|
+
return compositeLitMaxIndex(elems);
|
|
1137
2174
|
}
|