gofront 1.6.1 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -49
- package/package.json +12 -8
- package/src/backend/js/expressions.js +1627 -148
- package/src/backend/js/fmt-runtime.js +573 -0
- package/src/backend/js/index.js +508 -102
- package/src/backend/js/runtime.js +1997 -106
- package/src/backend/js/statements.js +931 -87
- package/src/backend/js/stdlib/bits.js +87 -14
- package/src/backend/js/stdlib/builder.js +41 -6
- package/src/backend/js/stdlib/bytes.js +253 -7
- package/src/backend/js/stdlib/errors.js +7 -1
- package/src/backend/js/stdlib/fmt.js +349 -10
- package/src/backend/js/stdlib/index.js +19 -2
- package/src/backend/js/stdlib/json.js +1167 -0
- package/src/backend/js/stdlib/maps.js +0 -8
- package/src/backend/js/stdlib/math.js +17 -1
- package/src/backend/js/stdlib/simd.js +34 -0
- package/src/backend/js/stdlib/slices.js +36 -6
- package/src/backend/js/stdlib/strconv.js +10 -5
- package/src/backend/js/stdlib/strings.js +83 -4
- package/src/backend/js/stdlib/testing.js +7 -0
- package/src/backend/js/stdlib/time.js +79 -55
- package/src/backend/js/stdlib/unicode.js +1 -1
- package/src/backend/js/stdlib/utf8.js +1 -1
- package/src/backend/js/templ.js +38 -76
- package/src/backend/js/type-info.js +272 -0
- package/src/backend/shared/fmt-types.js +188 -0
- package/src/backend/wasm/boundary.js +446 -72
- package/src/backend/wasm/emit-builtins.js +320 -13
- package/src/backend/wasm/emit-exprs.js +1404 -199
- package/src/backend/wasm/emit-maps.js +61 -13
- package/src/backend/wasm/emit-stmts.js +1359 -164
- package/src/backend/wasm/emit.js +518 -74
- package/src/backend/wasm/encode.js +128 -13
- package/src/backend/wasm/globals.js +452 -0
- package/src/backend/wasm/index.js +194 -2489
- package/src/backend/wasm/method-expr.js +381 -0
- package/src/backend/wasm/module-emitter.js +2240 -0
- package/src/backend/wasm/monomorph.js +62 -4
- package/src/backend/wasm/optimize.js +4 -2
- package/src/backend/wasm/prescan.js +397 -0
- package/src/backend/wasm/stdlib/bytes.js +1804 -127
- package/src/backend/wasm/stdlib/core.js +1574 -199
- package/src/backend/wasm/stdlib/index.js +3 -5
- package/src/backend/wasm/stdlib/io.js +1567 -150
- package/src/backend/wasm/stdlib/packages.js +2 -0
- package/src/backend/wasm/stdlib/simd.js +499 -0
- package/src/backend/wasm/stdlib/time.js +1041 -12
- package/src/backend/wasm/types.js +221 -15
- package/src/backend/wasm/variadic.js +93 -0
- package/src/backend/wasm/wat.js +84 -20
- package/src/{cli-core.js → cli/cli-core.js} +236 -10
- package/src/{test-runner.js → cli/test-runner.js} +332 -39
- package/src/{vendor.js → cli/vendor.js} +89 -15
- package/src/compiler.js +107 -269
- package/src/dts-parser.js +71 -14
- package/src/index.js +15 -169
- package/src/{lexer.js → lexer/lexer.js} +102 -55
- package/src/{tokens.js → lexer/tokens.js} +1 -0
- package/src/link/sourcemap.js +97 -0
- package/src/link/stdlib.js +515 -0
- package/src/link/wasm.js +294 -0
- package/src/lower/bounds.js +394 -0
- package/src/lower/boxing.js +1 -6
- package/src/lower/captures.js +176 -32
- package/src/lower/embedding.js +2 -1
- package/src/lower/index.js +26 -18
- package/src/lower/json-codecs.js +1398 -0
- package/src/lower/ownership.js +22 -1
- package/src/lower/range.js +86 -1
- package/src/lower/reachability.js +132 -0
- package/src/parser/declarations.js +58 -13
- package/src/parser/expressions.js +29 -7
- package/src/parser/index.js +2 -1
- package/src/parser/statements.js +37 -22
- package/src/parser/struct-tag.js +144 -0
- package/src/{templ-parser.js → parser/templ.js} +138 -15
- package/src/parser/types.js +130 -13
- package/src/resolver.js +22 -1
- package/src/typechecker/assignability.js +215 -11
- package/src/typechecker/const-fold.js +356 -0
- package/src/typechecker/constants.js +256 -0
- package/src/typechecker/expressions.js +960 -99
- package/src/typechecker/index.js +491 -72
- package/src/typechecker/init-order.js +330 -0
- package/src/typechecker/resolve.js +596 -52
- package/src/typechecker/statements.js +157 -17
- package/src/typechecker/stdlib/core.js +333 -55
- package/src/typechecker/stdlib/excluded.js +898 -0
- package/src/typechecker/stdlib/extended.js +359 -26
- package/src/typechecker/{stdlib.js → stdlib/index.js} +6 -4
- package/src/typechecker/stdlib/simd.js +123 -0
- package/src/typechecker/termination.js +1 -1
- package/src/typechecker/types.js +165 -26
- package/stdlib/bytes/iter.go +192 -0
- package/stdlib/bytes/search.go +240 -0
- package/stdlib/bytes/split.go +218 -0
- package/stdlib/cmp/cmp.go +65 -0
- package/stdlib/encoding/json/errors.go +31 -0
- package/stdlib/encoding/json/indent.go +71 -0
- package/stdlib/encoding/json/number.go +12 -0
- package/stdlib/encoding/json/stream.go +173 -0
- package/stdlib/errors/overlay.go +12 -0
- package/stdlib/hash/hash.go +53 -0
- package/stdlib/internal/jsonrt/dec.go +738 -0
- package/stdlib/internal/jsonrt/enc.go +366 -0
- package/stdlib/internal/jsonrt/indent.go +135 -0
- package/stdlib/internal/jsonrt/number.go +27 -0
- package/stdlib/internal/jsonrt/reader.go +73 -0
- package/stdlib/internal/jsonrt/scanner.go +649 -0
- package/stdlib/io/io.go +346 -0
- package/stdlib/iter/iter.go +18 -0
- package/stdlib/maps/iter.go +65 -0
- package/stdlib/math/extra.go +270 -0
- package/stdlib/slices/extra.go +73 -0
- package/stdlib/slices/iter.go +113 -0
- package/stdlib/sort/sort.go +561 -0
- package/stdlib/strconv/quote.go +341 -0
- package/stdlib/strings/extra.go +35 -0
- package/stdlib/strings/iter.go +122 -0
- package/stdlib/sync/extra.go +105 -0
- package/stdlib/unicode/unicode.go +112 -0
- package/stdlib/unicode/utf8/bytes.go +239 -0
- package/src/backend/wasm/const-fold.js +0 -205
- package/src/backend/wasm/emit-stdlib.js +0 -13
- /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
- /package/src/{colors.js → cli/colors.js} +0 -0
- /package/src/{dev-server.js → cli/dev-server.js} +0 -0
- /package/src/{minifier.js → cli/minifier.js} +0 -0
- /package/src/{project-config.js → cli/project-config.js} +0 -0
- /package/src/{pwa.js → cli/pwa.js} +0 -0
- /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
package/src/typechecker/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
// Split into sub-modules under typechecker/:
|
|
5
5
|
// types.js — shared type constants, predicates, Scope
|
|
6
|
-
// stdlib.js
|
|
6
|
+
// stdlib/index.js — browser globals + all built-in package registrations
|
|
7
7
|
// statements.js — checkBlock, checkStmt
|
|
8
8
|
// expressions.js — checkExpr, checkCall, checkBuiltin, checkCompositeLit
|
|
9
9
|
// termination.js — _isTerminating* family
|
|
@@ -11,15 +11,21 @@
|
|
|
11
11
|
// assignability.js — assertAssignable, binaryResultType, implements
|
|
12
12
|
|
|
13
13
|
import { assignabilityMethods } from "./assignability.js";
|
|
14
|
+
import { constantCheckMethods } from "./constants.js";
|
|
14
15
|
import { expressionCheckMethods } from "./expressions.js";
|
|
16
|
+
import { orderPackageInits, orderVarSpecsForTyping } from "./init-order.js";
|
|
15
17
|
import { resolveMethods } from "./resolve.js";
|
|
16
18
|
import { statementCheckMethods } from "./statements.js";
|
|
17
|
-
import {
|
|
19
|
+
import { EXCLUDED_STDLIB_PACKAGES } from "./stdlib/excluded.js";
|
|
20
|
+
import { setupGlobals } from "./stdlib/index.js";
|
|
18
21
|
import { terminationMethods } from "./termination.js";
|
|
19
22
|
import {
|
|
20
23
|
ANY,
|
|
21
24
|
defaultType,
|
|
22
25
|
ERROR,
|
|
26
|
+
INTRINSICS,
|
|
27
|
+
isAny,
|
|
28
|
+
isBool,
|
|
23
29
|
isVoid,
|
|
24
30
|
Scope,
|
|
25
31
|
TAINTED_ANY,
|
|
@@ -29,6 +35,16 @@ import {
|
|
|
29
35
|
VOID,
|
|
30
36
|
} from "./types.js";
|
|
31
37
|
|
|
38
|
+
// Checker method per templ body node kind (see _checkTemplNodes).
|
|
39
|
+
const TEMPL_NODE_CHECKS = {
|
|
40
|
+
TemplElement: "_checkTemplElement",
|
|
41
|
+
TemplExpr: "_checkTemplLeaf",
|
|
42
|
+
TemplComponent: "_checkTemplLeaf",
|
|
43
|
+
TemplIf: "_checkTemplIf",
|
|
44
|
+
TemplFor: "_checkTemplFor",
|
|
45
|
+
TemplSwitch: "_checkTemplSwitch",
|
|
46
|
+
};
|
|
47
|
+
|
|
32
48
|
function rootIdentName(e) {
|
|
33
49
|
if (!e) return null;
|
|
34
50
|
if (e.kind === "Ident") return e.name;
|
|
@@ -38,12 +54,56 @@ function rootIdentName(e) {
|
|
|
38
54
|
}
|
|
39
55
|
|
|
40
56
|
// Re-export for consumers that import from typechecker.js
|
|
41
|
-
export { TypeCheckError, typeStr };
|
|
57
|
+
export { EXCLUDED_STDLIB_PACKAGES, TypeCheckError, typeStr };
|
|
58
|
+
|
|
59
|
+
// JavaScript built-ins that generated JS relies on; a JS class of the same
|
|
60
|
+
// name in the bundle would shadow them (see _checkGlobalTypeNamesPass).
|
|
61
|
+
const JS_BUILTIN_CLASSES = new Set([
|
|
62
|
+
"Array",
|
|
63
|
+
"ArrayBuffer",
|
|
64
|
+
"BigInt",
|
|
65
|
+
"BigInt64Array",
|
|
66
|
+
"BigUint64Array",
|
|
67
|
+
"Boolean",
|
|
68
|
+
"DataView",
|
|
69
|
+
"Date",
|
|
70
|
+
"Error",
|
|
71
|
+
"Float32Array",
|
|
72
|
+
"Float64Array",
|
|
73
|
+
"Int16Array",
|
|
74
|
+
"Int32Array",
|
|
75
|
+
"Int8Array",
|
|
76
|
+
"JSON",
|
|
77
|
+
"Map",
|
|
78
|
+
"Math",
|
|
79
|
+
"Number",
|
|
80
|
+
"Object",
|
|
81
|
+
"Promise",
|
|
82
|
+
"RangeError",
|
|
83
|
+
"Reflect",
|
|
84
|
+
"RegExp",
|
|
85
|
+
"Set",
|
|
86
|
+
"String",
|
|
87
|
+
"Symbol",
|
|
88
|
+
"TextDecoder",
|
|
89
|
+
"TextEncoder",
|
|
90
|
+
"TypeError",
|
|
91
|
+
"Uint16Array",
|
|
92
|
+
"Uint32Array",
|
|
93
|
+
"Uint8Array",
|
|
94
|
+
"Uint8ClampedArray",
|
|
95
|
+
"WeakMap",
|
|
96
|
+
"WebAssembly",
|
|
97
|
+
]);
|
|
42
98
|
|
|
43
99
|
export class TypeChecker {
|
|
44
100
|
constructor() {
|
|
45
101
|
this.types = new Map(); // named types
|
|
46
|
-
|
|
102
|
+
// Go's universe scope: builtins, browser globals and built-in package
|
|
103
|
+
// namespaces. Package declarations live in `globals` above it, so they
|
|
104
|
+
// shadow predeclared names (`func Array()`, `var document int`).
|
|
105
|
+
this.universe = new Scope();
|
|
106
|
+
this.globals = new Scope(this.universe);
|
|
47
107
|
this.errors = [];
|
|
48
108
|
this.warnings = []; // non-fatal diagnostics (e.g. boundary-call-in-loop hints)
|
|
49
109
|
this.target = "js";
|
|
@@ -57,21 +117,35 @@ export class TypeChecker {
|
|
|
57
117
|
this._typeSwitchDepth = 0; // for rejecting fallthrough in type switch
|
|
58
118
|
this._deferCount = 0; // tracks defer usage in current function body
|
|
59
119
|
this._imports = []; // tracked imports for unused-import detection
|
|
120
|
+
this._constExprs = new Map(); // name -> AST initializer expr
|
|
121
|
+
this._importedConsts = new Map(); // dot-imported const name -> value
|
|
122
|
+
this._externalNames = new Set(); // package-scope names from .d.ts / npm definitions
|
|
60
123
|
this._setupGlobals();
|
|
61
124
|
// Register error as a named type (interface)
|
|
62
125
|
this.types.set("error", ERROR);
|
|
63
126
|
}
|
|
64
127
|
|
|
65
128
|
_setupGlobals() {
|
|
66
|
-
setupGlobals(this.
|
|
129
|
+
setupGlobals(this.universe, this.types);
|
|
67
130
|
}
|
|
68
131
|
|
|
69
|
-
|
|
132
|
+
// `external` marks values as real JS names (.d.ts / npm definitions) that
|
|
133
|
+
// must not be renamed; dot-imported GoFront packages pass false so their
|
|
134
|
+
// browser-global-named members get the same `$` renaming as local ones.
|
|
135
|
+
// `consts` (dot-imported GoFront packages) maps constant names to their
|
|
136
|
+
// folded values.
|
|
137
|
+
addDefinitions(types, values, { external = true, consts = null } = {}) {
|
|
70
138
|
for (const [name, type] of types) {
|
|
71
139
|
this.types.set(name, type);
|
|
72
140
|
}
|
|
73
141
|
for (const [name, type] of values) {
|
|
74
142
|
this.globals.define(name, type);
|
|
143
|
+
if (external) this._externalNames.add(name);
|
|
144
|
+
}
|
|
145
|
+
for (const [name, v] of consts ?? []) {
|
|
146
|
+
if (!v || !values.has(name)) continue;
|
|
147
|
+
this.globals._consts.add(name);
|
|
148
|
+
this._importedConsts.set(name, v);
|
|
75
149
|
}
|
|
76
150
|
}
|
|
77
151
|
|
|
@@ -79,7 +153,9 @@ export class TypeChecker {
|
|
|
79
153
|
// e.g. addPackageNamespace('utils', symbolsMap, typesMap)
|
|
80
154
|
// lets callers type-check `utils.Foo` via SelectorExpr.
|
|
81
155
|
// `target` is the imported package's compile target ("js"/"wasm"/"both").
|
|
82
|
-
|
|
156
|
+
// `consts` maps the package's constant names to their folded value (see
|
|
157
|
+
// typechecker/const-fold.js), or null when the folder cannot evaluate one.
|
|
158
|
+
addPackageNamespace(pkgName, symbols, types, target = null, consts = null) {
|
|
83
159
|
const members = {};
|
|
84
160
|
for (const [name, type] of symbols) members[name] = type;
|
|
85
161
|
// _gofront: true marks this as a GoFront package — exported identifier rules apply
|
|
@@ -89,8 +165,15 @@ export class TypeChecker {
|
|
|
89
165
|
members,
|
|
90
166
|
_gofront: true,
|
|
91
167
|
_target: target,
|
|
168
|
+
_consts: consts,
|
|
92
169
|
});
|
|
93
170
|
for (const [name, type] of types) {
|
|
171
|
+
const prior = this.types.get(name);
|
|
172
|
+
if (prior && prior !== type && prior.kind === "named") {
|
|
173
|
+
this._ambiguousImported ??= new WeakSet();
|
|
174
|
+
this._ambiguousImported.add(prior);
|
|
175
|
+
this._ambiguousImported.add(type);
|
|
176
|
+
}
|
|
94
177
|
this.types.set(name, type);
|
|
95
178
|
this.types.set(`${pkgName}.${name}`, type);
|
|
96
179
|
}
|
|
@@ -107,7 +190,7 @@ export class TypeChecker {
|
|
|
107
190
|
// Report any tracked imports whose package name was never referenced.
|
|
108
191
|
reportUnusedImports() {
|
|
109
192
|
for (const { name, node, filename, source } of this._imports) {
|
|
110
|
-
if (!this.globals._used.has(name)) {
|
|
193
|
+
if (!this.globals._used.has(name) && !this.universe._used.has(name)) {
|
|
111
194
|
const saved = [this._currentFile, this._currentSource];
|
|
112
195
|
this._currentFile = filename;
|
|
113
196
|
this._currentSource = source;
|
|
@@ -129,6 +212,15 @@ export class TypeChecker {
|
|
|
129
212
|
return out;
|
|
130
213
|
}
|
|
131
214
|
|
|
215
|
+
getExportedConsts() {
|
|
216
|
+
const out = new Map();
|
|
217
|
+
for (const name of this.globals._consts) {
|
|
218
|
+
if (!this.globals.lookupConstExpr(name)) continue;
|
|
219
|
+
out.set(name, this._constValue({ kind: "Ident", name }, this.globals));
|
|
220
|
+
}
|
|
221
|
+
return out;
|
|
222
|
+
}
|
|
223
|
+
|
|
132
224
|
getExportedTypes() {
|
|
133
225
|
return new Map(this.types);
|
|
134
226
|
}
|
|
@@ -220,7 +312,11 @@ export class TypeChecker {
|
|
|
220
312
|
const type = spec.type
|
|
221
313
|
? this.resolveTypeNode(spec.type, this.globals)
|
|
222
314
|
: ANY;
|
|
223
|
-
for (
|
|
315
|
+
for (let i = 0; i < spec.names.length; i++) {
|
|
316
|
+
const name = spec.names[i];
|
|
317
|
+
const expr = spec.value?.[i] ?? spec.value?.[0] ?? null;
|
|
318
|
+
this.globals.defineConst(name, type, expr);
|
|
319
|
+
}
|
|
224
320
|
}
|
|
225
321
|
}
|
|
226
322
|
|
|
@@ -264,11 +360,15 @@ export class TypeChecker {
|
|
|
264
360
|
}
|
|
265
361
|
}
|
|
266
362
|
|
|
363
|
+
this._checkDirectivesAndImportsPass(programs);
|
|
364
|
+
this._registerConstExprs(programs);
|
|
267
365
|
this._collectTypesPass(programs);
|
|
268
366
|
this._collectFuncsPass(programs);
|
|
367
|
+
this._checkGlobalTypeNamesPass(programs);
|
|
269
368
|
this._promoteEmbeddedMethods();
|
|
270
369
|
this._collectVarsConstsPass(programs);
|
|
271
370
|
this._checkTopDeclsPass(programs);
|
|
371
|
+
this._reportInitCycles(programs);
|
|
272
372
|
|
|
273
373
|
if (this.target === "both") {
|
|
274
374
|
this._checkMutablePackageVars(programs);
|
|
@@ -285,6 +385,42 @@ export class TypeChecker {
|
|
|
285
385
|
return this.errors;
|
|
286
386
|
}
|
|
287
387
|
|
|
388
|
+
_checkEmbedDirectives(directives) {
|
|
389
|
+
for (const { line, col } of directives ?? []) {
|
|
390
|
+
this.err("//go:embed is not supported yet", { line, col });
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
_checkDirectivesAndImportsPass(programs) {
|
|
395
|
+
this._importedPaths = new Set();
|
|
396
|
+
this._fileImports = new Map();
|
|
397
|
+
for (const p of programs) {
|
|
398
|
+
this._setCurrentFile(p);
|
|
399
|
+
const names = new Map();
|
|
400
|
+
this._fileImports.set(this._currentFile, names);
|
|
401
|
+
this._checkEmbedDirectives(p._embedDirectives);
|
|
402
|
+
for (const imp of p.imports ?? []) {
|
|
403
|
+
for (const spec of imp.imports ?? []) {
|
|
404
|
+
this._importedPaths.add(spec.path);
|
|
405
|
+
names.set(
|
|
406
|
+
spec.alias ??
|
|
407
|
+
spec.path
|
|
408
|
+
.split("/")
|
|
409
|
+
.filter((s) => !/^v\d+$/.test(s))
|
|
410
|
+
.at(-1),
|
|
411
|
+
spec.path,
|
|
412
|
+
);
|
|
413
|
+
if (EXCLUDED_STDLIB_PACKAGES.has(spec.path)) {
|
|
414
|
+
this.err(
|
|
415
|
+
`package '${spec.path}' is not available: ${EXCLUDED_STDLIB_PACKAGES.get(spec.path)}`,
|
|
416
|
+
spec,
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
288
424
|
_checkMutablePackageVars(programs) {
|
|
289
425
|
const pkgVars = new Set();
|
|
290
426
|
for (const p of programs) {
|
|
@@ -404,6 +540,18 @@ export class TypeChecker {
|
|
|
404
540
|
for (const d of p.decls) {
|
|
405
541
|
if (d.kind === "TypeDecl" && !d.isAlias && !this.types.has(d.name)) {
|
|
406
542
|
const placeholder = { kind: "named", name: d.name, underlying: null };
|
|
543
|
+
// A generic type is known to be generic before any field resolves,
|
|
544
|
+
// so types declared earlier can instantiate it.
|
|
545
|
+
if (d.typeParams) {
|
|
546
|
+
placeholder._generic = {
|
|
547
|
+
typeParams: d.typeParams.map((tp) => ({
|
|
548
|
+
kind: "typeParam",
|
|
549
|
+
name: tp.name,
|
|
550
|
+
constraint: tp.constraint,
|
|
551
|
+
})),
|
|
552
|
+
declNode: d,
|
|
553
|
+
};
|
|
554
|
+
}
|
|
407
555
|
this.types.set(d.name, placeholder);
|
|
408
556
|
this.globals.define(d.name, placeholder);
|
|
409
557
|
}
|
|
@@ -413,6 +561,7 @@ export class TypeChecker {
|
|
|
413
561
|
for (const p of programs) {
|
|
414
562
|
for (const d of p.decls) if (d.kind === "TypeDecl") all.push({ p, d });
|
|
415
563
|
}
|
|
564
|
+
this._expandGenericStructDefs(all.map((x) => x.d));
|
|
416
565
|
// Collect in dependency order so aliases, generics, embedded and by-value
|
|
417
566
|
// field types declared later (or in other files) are ready when needed.
|
|
418
567
|
let pending = all;
|
|
@@ -431,6 +580,57 @@ export class TypeChecker {
|
|
|
431
580
|
}
|
|
432
581
|
}
|
|
433
582
|
|
|
583
|
+
// `type S[T any] Base` with `Base` a struct declared in the package: S is a
|
|
584
|
+
// new struct type with Base's fields (and none of its methods), so its
|
|
585
|
+
// declaration becomes a copy of Base's struct type.
|
|
586
|
+
_expandGenericStructDefs(decls) {
|
|
587
|
+
const byName = new Map(decls.map((d) => [d.name, d]));
|
|
588
|
+
for (const d of decls) {
|
|
589
|
+
if (!d.typeParams || d.isAlias) continue;
|
|
590
|
+
let target = d.type;
|
|
591
|
+
for (let i = 0; i < 16 && target?.kind === "TypeName"; i++) {
|
|
592
|
+
const base = byName.get(target.name);
|
|
593
|
+
if (!base || base.typeParams || base.isAlias) break;
|
|
594
|
+
target = base.type;
|
|
595
|
+
}
|
|
596
|
+
// A struct written in place is already this declaration's own copy;
|
|
597
|
+
// cloning it would also choke on an AST a previous target pass
|
|
598
|
+
// annotated with cycles.
|
|
599
|
+
if (target?.kind === "StructType" && target !== d.type)
|
|
600
|
+
d.type = JSON.parse(JSON.stringify(target));
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// A struct type, or a named type with methods, becomes a JS class of the
|
|
605
|
+
// same name in `js` and `both` packages. Functions and variables that
|
|
606
|
+
// shadow a browser global get a `$` suffix, but type names reach the
|
|
607
|
+
// output through the checker's types (class declarations, `new T`,
|
|
608
|
+
// `instanceof T`, static methods), so a class named after a JavaScript
|
|
609
|
+
// built-in the generated code calls (`Array.isArray`, `new Error`) is
|
|
610
|
+
// rejected instead. Interfaces and method-less named types emit no class.
|
|
611
|
+
_checkGlobalTypeNamesPass(programs) {
|
|
612
|
+
if (this.target === "wasm" || this.isStdlib) return;
|
|
613
|
+
for (const p of programs) {
|
|
614
|
+
for (const d of p.decls) {
|
|
615
|
+
if (
|
|
616
|
+
d.kind !== "TypeDecl" ||
|
|
617
|
+
d.isAlias ||
|
|
618
|
+
!JS_BUILTIN_CLASSES.has(d.name)
|
|
619
|
+
)
|
|
620
|
+
continue;
|
|
621
|
+
const t = this.types.get(d.name);
|
|
622
|
+
const isClass =
|
|
623
|
+
t?.underlying?.kind === "struct" || (t?.methods?.size ?? 0) > 0;
|
|
624
|
+
if (!isClass) continue;
|
|
625
|
+
this._setCurrentFile(p);
|
|
626
|
+
this.err(
|
|
627
|
+
`type ${d.name} shadows the JavaScript global ${d.name}; rename the type`,
|
|
628
|
+
d,
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
434
634
|
_typeDeclBlocked(decl, pendingNames) {
|
|
435
635
|
const visit = (node, indirect) => {
|
|
436
636
|
if (!node || typeof node !== "object") return false;
|
|
@@ -470,6 +670,22 @@ export class TypeChecker {
|
|
|
470
670
|
}
|
|
471
671
|
}
|
|
472
672
|
|
|
673
|
+
// Constant initializers are known before types are resolved, so an array
|
|
674
|
+
// length may name a constant declared later (`[N]int`, `[N + 1]T`).
|
|
675
|
+
_registerConstExprs(programs) {
|
|
676
|
+
for (const p of programs) {
|
|
677
|
+
for (const d of p.decls) {
|
|
678
|
+
if (d.kind !== "ConstDecl") continue;
|
|
679
|
+
for (const spec of d.decls) {
|
|
680
|
+
spec.names.forEach((name, i) => {
|
|
681
|
+
const expr = spec.value?.[i] ?? spec.value?.[0] ?? null;
|
|
682
|
+
if (expr) this.globals._constExprs.set(name, expr);
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
473
689
|
_collectVarsConstsPass(programs) {
|
|
474
690
|
for (const p of programs) {
|
|
475
691
|
this._setCurrentFile(p);
|
|
@@ -480,14 +696,30 @@ export class TypeChecker {
|
|
|
480
696
|
}
|
|
481
697
|
}
|
|
482
698
|
|
|
699
|
+
// Go rejects package variables whose initialisers depend on each other.
|
|
700
|
+
_reportInitCycles(programs) {
|
|
701
|
+
if (this.errors.length > 0) return;
|
|
702
|
+
const { cycles } = orderPackageInits(programs);
|
|
703
|
+
for (const c of cycles) {
|
|
704
|
+
const owner = programs.find((p) =>
|
|
705
|
+
p.decls?.some((d) => d.kind === "VarDecl" && d.decls?.includes(c.spec)),
|
|
706
|
+
);
|
|
707
|
+
if (owner) this._setCurrentFile(owner);
|
|
708
|
+
this.err(c.message, c.spec);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
483
712
|
_checkTopDeclsPass(programs) {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
713
|
+
// A constant may name one declared later, so each is typed after those.
|
|
714
|
+
for (const e of orderPackageInits(programs).consts) {
|
|
715
|
+
this._setCurrentFile(e.program);
|
|
716
|
+
this.checkTopDecl({ ...e.decl, decls: [e.spec] }, this.globals);
|
|
717
|
+
}
|
|
718
|
+
// A variable's type may come from one declared later (`var a = b + 1;
|
|
719
|
+
// var b = f()`), so initialisers are typed after what they name.
|
|
720
|
+
for (const e of orderVarSpecsForTyping(programs)) {
|
|
721
|
+
this._setCurrentFile(e.program);
|
|
722
|
+
this.checkTopDecl({ ...e.decl, decls: [e.spec] }, this.globals);
|
|
491
723
|
}
|
|
492
724
|
for (const p of programs) {
|
|
493
725
|
this._setCurrentFile(p);
|
|
@@ -507,15 +739,20 @@ export class TypeChecker {
|
|
|
507
739
|
|
|
508
740
|
_promoteEmbeddedMethods() {
|
|
509
741
|
for (const type of this.types.values()) {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
742
|
+
this._promoteEmbeddedInto(type.underlying);
|
|
743
|
+
for (const inst of type._generic?._instances?.values() ?? [])
|
|
744
|
+
this._promoteEmbeddedInto(inst.underlying);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
_promoteEmbeddedInto(struct) {
|
|
749
|
+
if (struct?.kind !== "struct" || !struct._embeds) return;
|
|
750
|
+
for (const embed of struct._embeds) {
|
|
751
|
+
const inner = embed.kind === "pointer" ? embed.base : embed;
|
|
752
|
+
const base = inner.kind === "named" ? inner.underlying : inner;
|
|
753
|
+
if (base?.kind !== "struct" || !base.methods) continue;
|
|
754
|
+
for (const [mName, mType] of base.methods.entries()) {
|
|
755
|
+
if (!struct.methods.has(mName)) struct.methods.set(mName, mType);
|
|
519
756
|
}
|
|
520
757
|
}
|
|
521
758
|
}
|
|
@@ -523,30 +760,39 @@ export class TypeChecker {
|
|
|
523
760
|
// ── Type collection ──────────────────────────────────────────
|
|
524
761
|
|
|
525
762
|
collectType(decl) {
|
|
763
|
+
if (decl.typeParams && decl.isAlias) {
|
|
764
|
+
this._collectGenericAlias(decl);
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
526
767
|
if (decl.typeParams) {
|
|
527
768
|
// Generic type — resolve underlying with type params as ANY for struct fields
|
|
528
769
|
const typeScope = new Scope(this.globals);
|
|
529
|
-
const
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
770
|
+
const preset = this.types.get(decl.name)?._generic;
|
|
771
|
+
const typeParamTypes = decl.typeParams.map((tp, i) => {
|
|
772
|
+
const t =
|
|
773
|
+
preset?.declNode === decl
|
|
774
|
+
? preset.typeParams[i]
|
|
775
|
+
: {
|
|
776
|
+
kind: "typeParam",
|
|
777
|
+
name: tp.name,
|
|
778
|
+
constraint: tp.constraint,
|
|
779
|
+
};
|
|
535
780
|
typeScope.define(tp.name, t);
|
|
536
781
|
return t;
|
|
537
782
|
});
|
|
538
|
-
const underlying = this.resolveTypeNode(decl.type, typeScope);
|
|
539
783
|
const existing = this.types.get(decl.name);
|
|
540
784
|
const named =
|
|
541
785
|
existing?.kind === "named"
|
|
542
786
|
? existing
|
|
543
787
|
: { kind: "named", name: decl.name };
|
|
788
|
+
// Registered before the fields resolve so `Next *List[T]` finds the
|
|
789
|
+
// generic declaration it is part of.
|
|
790
|
+
if (preset?.declNode === decl) named._generic = preset;
|
|
791
|
+
else named._generic = { typeParams: typeParamTypes, declNode: decl };
|
|
792
|
+
this.types.set(decl.name, named);
|
|
793
|
+
const underlying = this.resolveTypeNode(decl.type, typeScope);
|
|
544
794
|
named.underlying = underlying;
|
|
545
795
|
named._target = this.target;
|
|
546
|
-
named._generic = {
|
|
547
|
-
typeParams: typeParamTypes,
|
|
548
|
-
declNode: decl,
|
|
549
|
-
};
|
|
550
796
|
if (underlying.kind === "struct") {
|
|
551
797
|
underlying.name = decl.name;
|
|
552
798
|
underlying.methods = new Map();
|
|
@@ -555,7 +801,15 @@ export class TypeChecker {
|
|
|
555
801
|
this.globals.define(decl.name, named);
|
|
556
802
|
return;
|
|
557
803
|
}
|
|
558
|
-
|
|
804
|
+
let underlying = this.resolveTypeNode(decl.type, this.globals);
|
|
805
|
+
// `type W V` with V a named array: W's underlying type is V's array.
|
|
806
|
+
if (!decl.isAlias && underlying.kind === "named") {
|
|
807
|
+
const inner = this._deepUnderlying(underlying);
|
|
808
|
+
if (inner?.kind === "array") underlying = inner;
|
|
809
|
+
// `type S Base` with Base a struct: S gets its own copy of the fields.
|
|
810
|
+
else if (inner?.kind === "struct")
|
|
811
|
+
underlying = { ...inner, name: decl.name, methods: new Map() };
|
|
812
|
+
}
|
|
559
813
|
if (decl.isAlias) {
|
|
560
814
|
// type A = B — transparent alias, A and B are identical types
|
|
561
815
|
this.types.set(decl.name, underlying);
|
|
@@ -582,6 +836,29 @@ export class TypeChecker {
|
|
|
582
836
|
this.globals.define(decl.name, named);
|
|
583
837
|
}
|
|
584
838
|
|
|
839
|
+
// type A[P any] = T[P] — Go 1.24 generic alias. The declaration is a
|
|
840
|
+
// template only: each instantiation A[X] substitutes X into the aliased
|
|
841
|
+
// type and resolves to exactly that type (no new named type).
|
|
842
|
+
_collectGenericAlias(decl) {
|
|
843
|
+
const typeScope = new Scope(this.globals);
|
|
844
|
+
const typeParamTypes = decl.typeParams.map((tp) => {
|
|
845
|
+
const t = { kind: "typeParam", name: tp.name, constraint: tp.constraint };
|
|
846
|
+
typeScope.define(tp.name, t);
|
|
847
|
+
return t;
|
|
848
|
+
});
|
|
849
|
+
// Resolve once with the parameters in scope to report errors in the
|
|
850
|
+
// aliased type at the declaration.
|
|
851
|
+
const underlying = this.resolveTypeNode(decl.type, typeScope);
|
|
852
|
+
const alias = {
|
|
853
|
+
kind: "named",
|
|
854
|
+
name: decl.name,
|
|
855
|
+
underlying,
|
|
856
|
+
_generic: { typeParams: typeParamTypes, declNode: decl, isAlias: true },
|
|
857
|
+
};
|
|
858
|
+
this.types.set(decl.name, alias);
|
|
859
|
+
this.globals.define(decl.name, alias);
|
|
860
|
+
}
|
|
861
|
+
|
|
585
862
|
_buildCollectFuncScope(decl) {
|
|
586
863
|
let resolveScope = this.globals;
|
|
587
864
|
let typeParamTypes = null;
|
|
@@ -617,6 +894,7 @@ export class TypeChecker {
|
|
|
617
894
|
name: decl.name,
|
|
618
895
|
typeParams: typeParamTypes,
|
|
619
896
|
underlying: funcType,
|
|
897
|
+
declNode: decl,
|
|
620
898
|
});
|
|
621
899
|
} else {
|
|
622
900
|
this.globals.define(decl.name, funcType);
|
|
@@ -657,14 +935,18 @@ export class TypeChecker {
|
|
|
657
935
|
const recvType = this.resolveTypeNodeName(decl.recvType, this.globals);
|
|
658
936
|
const base = recvType?.underlying ?? recvType;
|
|
659
937
|
if (base?.kind === "struct") {
|
|
938
|
+
base.methods ??= new Map();
|
|
660
939
|
base.methods.set(decl.name, funcType);
|
|
661
|
-
}
|
|
940
|
+
}
|
|
941
|
+
if (recvType?.kind === "named") {
|
|
942
|
+
recvType.methods ??= new Map();
|
|
662
943
|
recvType.methods.set(decl.name, funcType);
|
|
663
944
|
}
|
|
664
945
|
}
|
|
665
946
|
|
|
666
947
|
_attachMethod(decl, funcType) {
|
|
667
948
|
funcType._ptrRecv = Boolean(decl.recvPointer);
|
|
949
|
+
funcType._decl = decl;
|
|
668
950
|
const recvNamedType = this.types.get(decl.recvType.name);
|
|
669
951
|
if (recvNamedType?._generic)
|
|
670
952
|
this._attachGenericMethod(recvNamedType, decl, funcType);
|
|
@@ -691,42 +973,111 @@ export class TypeChecker {
|
|
|
691
973
|
case "CssDecl":
|
|
692
974
|
break;
|
|
693
975
|
case "TemplDecl":
|
|
694
|
-
this.
|
|
976
|
+
this._checkTemplDecl(decl, scope);
|
|
695
977
|
break;
|
|
696
978
|
}
|
|
697
979
|
}
|
|
698
980
|
|
|
699
|
-
|
|
981
|
+
// A templ body is checked like a function body whose parameters are the
|
|
982
|
+
// templ's: every Go expression TemplParser attached to a node (`expr`,
|
|
983
|
+
// attribute `expr`, `forStmt`) is type-checked in the enclosing scope.
|
|
984
|
+
_checkTemplDecl(decl, outer) {
|
|
985
|
+
const inner = new Scope(outer);
|
|
986
|
+
for (const p of decl.params)
|
|
987
|
+
inner.define(p.name, this._paramType(p, inner));
|
|
988
|
+
this._checkTemplNodes(decl.body, inner);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
_checkTemplNodes(nodes, scope) {
|
|
992
|
+
for (const n of nodes ?? []) {
|
|
993
|
+
const m = TEMPL_NODE_CHECKS[n.kind];
|
|
994
|
+
if (m) this[m](n, scope);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// A malformed expression has no AST: the code generator reports its parse
|
|
999
|
+
// error. Its names count as used so that error is not preceded by a
|
|
1000
|
+
// misleading `declared and not used`.
|
|
1001
|
+
_checkTemplExpr(node, tokens, scope) {
|
|
1002
|
+
if (node.exprError) {
|
|
1003
|
+
this._markTokensUsed(tokens, scope);
|
|
1004
|
+
return null;
|
|
1005
|
+
}
|
|
1006
|
+
return node.expr ? this.checkExpr(node.expr, scope) : null;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
_checkTemplElement(n, scope) {
|
|
1010
|
+
for (const a of n.attrs ?? []) {
|
|
1011
|
+
if (a.kind === "expr" || a.kind === "cond-bool")
|
|
1012
|
+
this._checkTemplExpr(a, a.tokens, scope);
|
|
1013
|
+
}
|
|
1014
|
+
this._checkTemplNodes(n.children, scope);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
_checkTemplLeaf(n, scope) {
|
|
1018
|
+
this._checkTemplExpr(n, n.tokens, scope);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
_checkTemplIf(n, scope) {
|
|
1022
|
+
const ct = this._checkTemplExpr(n, n.condTokens, scope);
|
|
1023
|
+
if (ct && !isBool(ct) && !isAny(ct))
|
|
1024
|
+
this.err("If condition must be bool", n.expr);
|
|
1025
|
+
this._checkTemplNodes(n.then, scope);
|
|
1026
|
+
this._checkTemplNodes(n.else_, scope);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
_checkTemplSwitch(n, scope) {
|
|
1030
|
+
this._checkTemplExpr(n, n.exprTokens, scope);
|
|
1031
|
+
for (const c of n.cases ?? []) {
|
|
1032
|
+
this._checkTemplExpr(c, c.caseTokens, scope);
|
|
1033
|
+
this._checkTemplNodes(c.body, scope);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// A header that did not parse is reported by the code generator; its body
|
|
1038
|
+
// is not checked, since the loop variables it would declare are unknown.
|
|
1039
|
+
_checkTemplFor(n, scope) {
|
|
1040
|
+
if (!n.forStmt) {
|
|
1041
|
+
this._markTokensUsed(n.stmtTokens, scope);
|
|
1042
|
+
this._markTemplNodesUsed(n.body, scope);
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
this._checkForStmt(n.forStmt, scope, VOID, (bodyScope) =>
|
|
1046
|
+
this._checkTemplNodes(n.body, bodyScope),
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
_markTokensUsed(tokens, scope) {
|
|
700
1051
|
if (!tokens) return;
|
|
701
1052
|
for (const t of tokens) {
|
|
702
1053
|
if (t.type === "IDENT" || t.type?.name === "IDENT") {
|
|
703
|
-
|
|
1054
|
+
scope.lookup(t.value);
|
|
704
1055
|
}
|
|
705
1056
|
}
|
|
706
1057
|
}
|
|
707
1058
|
|
|
708
|
-
_markTemplNodesUsed(nodes) {
|
|
1059
|
+
_markTemplNodesUsed(nodes, scope) {
|
|
709
1060
|
if (!nodes) return;
|
|
710
1061
|
for (const n of nodes) {
|
|
711
1062
|
if (n.kind === "TemplElement") {
|
|
712
1063
|
for (const a of n.attrs || []) {
|
|
713
|
-
if (a.tokens) this._markTokensUsed(a.tokens);
|
|
1064
|
+
if (a.tokens) this._markTokensUsed(a.tokens, scope);
|
|
714
1065
|
}
|
|
715
|
-
this._markTemplNodesUsed(n.children);
|
|
1066
|
+
this._markTemplNodesUsed(n.children, scope);
|
|
716
1067
|
} else if (n.kind === "TemplExpr" || n.kind === "TemplComponent") {
|
|
717
|
-
this._markTokensUsed(n.tokens);
|
|
1068
|
+
this._markTokensUsed(n.tokens, scope);
|
|
718
1069
|
} else if (n.kind === "TemplIf") {
|
|
719
|
-
this._markTokensUsed(n.condTokens);
|
|
720
|
-
this._markTemplNodesUsed(n.then);
|
|
721
|
-
this._markTemplNodesUsed(n.else_);
|
|
1070
|
+
this._markTokensUsed(n.condTokens, scope);
|
|
1071
|
+
this._markTemplNodesUsed(n.then, scope);
|
|
1072
|
+
this._markTemplNodesUsed(n.else_, scope);
|
|
722
1073
|
} else if (n.kind === "TemplFor") {
|
|
723
|
-
this._markTokensUsed(n.stmtTokens);
|
|
724
|
-
this._markTemplNodesUsed(n.body);
|
|
1074
|
+
this._markTokensUsed(n.stmtTokens, scope);
|
|
1075
|
+
this._markTemplNodesUsed(n.body, scope);
|
|
725
1076
|
} else if (n.kind === "TemplSwitch") {
|
|
726
|
-
this._markTokensUsed(n.exprTokens);
|
|
1077
|
+
this._markTokensUsed(n.exprTokens, scope);
|
|
727
1078
|
for (const c of n.cases || []) {
|
|
728
|
-
this._markTokensUsed(c.caseTokens);
|
|
729
|
-
this._markTemplNodesUsed(c.body);
|
|
1079
|
+
this._markTokensUsed(c.caseTokens, scope);
|
|
1080
|
+
this._markTemplNodesUsed(c.body, scope);
|
|
730
1081
|
}
|
|
731
1082
|
}
|
|
732
1083
|
}
|
|
@@ -780,7 +1131,24 @@ export class TypeChecker {
|
|
|
780
1131
|
this.globals.define(decl.name, funcType);
|
|
781
1132
|
}
|
|
782
1133
|
|
|
1134
|
+
_checkIntrinsic(decl) {
|
|
1135
|
+
const want = INTRINSICS.get(decl.intrinsic);
|
|
1136
|
+
if (!want) return this.err(`unknown intrinsic '${decl.intrinsic}'`, decl);
|
|
1137
|
+
if (!decl._bodyless)
|
|
1138
|
+
return this.err(
|
|
1139
|
+
`intrinsic '${decl.intrinsic}' must not have a body`,
|
|
1140
|
+
decl,
|
|
1141
|
+
);
|
|
1142
|
+
const got = typeStr(this.globals.symbols.get(decl.name));
|
|
1143
|
+
if (got !== want)
|
|
1144
|
+
this.err(
|
|
1145
|
+
`intrinsic '${decl.intrinsic}' must have type ${want}, not ${got}`,
|
|
1146
|
+
decl,
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
783
1150
|
checkFuncDecl(decl, outer) {
|
|
1151
|
+
if (decl.intrinsic) this._checkIntrinsic(decl);
|
|
784
1152
|
if (
|
|
785
1153
|
(this.target === "wasm" || this.target === "both") &&
|
|
786
1154
|
(decl.async || decl.isAsync)
|
|
@@ -794,9 +1162,9 @@ export class TypeChecker {
|
|
|
794
1162
|
const inner = new Scope(outer);
|
|
795
1163
|
this._injectTypeParams(decl, inner, outer);
|
|
796
1164
|
for (const p of decl.params) {
|
|
797
|
-
inner.define(p.name, this.
|
|
1165
|
+
inner.define(p.name, this._paramType(p, inner));
|
|
798
1166
|
}
|
|
799
|
-
const returnType = this._setupFuncReturnType(decl, inner
|
|
1167
|
+
const returnType = this._setupFuncReturnType(decl, inner);
|
|
800
1168
|
const savedSharedAlloc = this._sharedAllocOk;
|
|
801
1169
|
this._sharedAllocOk = decl.name === "init";
|
|
802
1170
|
this._runFuncBody(decl, inner, returnType);
|
|
@@ -822,19 +1190,26 @@ export class TypeChecker {
|
|
|
822
1190
|
: decl.recvType.name;
|
|
823
1191
|
const recvNamedType = this.types.get(recvTypeName);
|
|
824
1192
|
this._injectGenericReceiverTypeParams(inner, recvNamedType);
|
|
825
|
-
const recvType = this.
|
|
1193
|
+
const recvType = this._methodReceiverType(decl, recvNamedType, outer);
|
|
826
1194
|
inner.define(
|
|
827
1195
|
decl.recvName,
|
|
828
1196
|
decl.recvPointer ? { kind: "pointer", base: recvType } : recvType,
|
|
829
1197
|
);
|
|
830
1198
|
for (const p of decl.params) {
|
|
831
|
-
inner.define(p.name, this.
|
|
1199
|
+
inner.define(p.name, this._paramType(p, inner));
|
|
832
1200
|
}
|
|
833
|
-
const returnType = this._setupFuncReturnType(decl, inner
|
|
1201
|
+
const returnType = this._setupFuncReturnType(decl, inner);
|
|
834
1202
|
this._runFuncBody(decl, inner, returnType);
|
|
835
1203
|
this._checkMissingReturn(decl, returnType, "method");
|
|
836
1204
|
}
|
|
837
1205
|
|
|
1206
|
+
// A variadic parameter `xs ...T` is a `[]T` inside the body (the signature
|
|
1207
|
+
// keeps the element type plus the `variadic` flag).
|
|
1208
|
+
_paramType(p, scope) {
|
|
1209
|
+
const t = this.resolveTypeNode(p.type, scope);
|
|
1210
|
+
return p.variadic ? { kind: "slice", elem: t } : t;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
838
1213
|
_injectTypeParams(decl, inner, outer) {
|
|
839
1214
|
if (!decl.typeParams) return;
|
|
840
1215
|
for (const tp of decl.typeParams) {
|
|
@@ -845,6 +1220,16 @@ export class TypeChecker {
|
|
|
845
1220
|
}
|
|
846
1221
|
}
|
|
847
1222
|
|
|
1223
|
+
// Inside a method of `G[T]` the receiver is the instance `G[T]` over the
|
|
1224
|
+
// receiver's own type parameters, so it is assignable to results and locals
|
|
1225
|
+
// written as `G[T]`.
|
|
1226
|
+
_methodReceiverType(decl, recvNamedType, outer) {
|
|
1227
|
+
const generic = recvNamedType?._generic;
|
|
1228
|
+
if (!generic || generic.isAlias || !generic.typeParams?.length)
|
|
1229
|
+
return this.resolveTypeNodeName(decl.recvType, outer);
|
|
1230
|
+
return this.instantiateGenericWithArgs(generic, generic.typeParams);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
848
1233
|
_injectGenericReceiverTypeParams(inner, recvNamedType) {
|
|
849
1234
|
if (!recvNamedType?._generic) return;
|
|
850
1235
|
for (const tp of recvNamedType._generic.typeParams) {
|
|
@@ -852,15 +1237,11 @@ export class TypeChecker {
|
|
|
852
1237
|
}
|
|
853
1238
|
}
|
|
854
1239
|
|
|
855
|
-
_setupFuncReturnType(decl, inner
|
|
1240
|
+
_setupFuncReturnType(decl, inner) {
|
|
856
1241
|
let returnType = decl.returnType
|
|
857
1242
|
? this.resolveTypeNode(decl.returnType, inner)
|
|
858
1243
|
: VOID;
|
|
859
|
-
const hasNamedReturns = this._injectNamedReturns(
|
|
860
|
-
decl.returnType,
|
|
861
|
-
inner,
|
|
862
|
-
outer,
|
|
863
|
-
);
|
|
1244
|
+
const hasNamedReturns = this._injectNamedReturns(decl.returnType, inner);
|
|
864
1245
|
if (hasNamedReturns && returnType)
|
|
865
1246
|
returnType = { ...returnType, _hasNamedReturns: true };
|
|
866
1247
|
return returnType;
|
|
@@ -884,10 +1265,12 @@ export class TypeChecker {
|
|
|
884
1265
|
}
|
|
885
1266
|
}
|
|
886
1267
|
|
|
887
|
-
|
|
1268
|
+
// Named result types resolve in the function scope, so a result of a type
|
|
1269
|
+
// parameter's type (`(r T)`) sees T.
|
|
1270
|
+
_injectNamedReturns(returnTypeNode, scope) {
|
|
888
1271
|
if (!returnTypeNode?._namedReturns) return false;
|
|
889
1272
|
for (const { name, type } of returnTypeNode._namedReturns) {
|
|
890
|
-
if (name) scope.define(name, this.resolveTypeNode(type,
|
|
1273
|
+
if (name) scope.define(name, this.resolveTypeNode(type, scope));
|
|
891
1274
|
}
|
|
892
1275
|
return true;
|
|
893
1276
|
}
|
|
@@ -899,14 +1282,38 @@ export class TypeChecker {
|
|
|
899
1282
|
let type = spec.type ? this.resolveTypeNode(spec.type, scope) : null;
|
|
900
1283
|
if (spec.value) {
|
|
901
1284
|
const valTypes = spec.value.map((v) => this.checkExpr(v, scope));
|
|
902
|
-
|
|
1285
|
+
const isTuple = valTypes.length === 1 && valTypes[0]?.kind === "tuple";
|
|
1286
|
+
const valTypesFlat = isTuple ? valTypes[0].types : valTypes;
|
|
1287
|
+
if (spec.names.length !== valTypesFlat.length) {
|
|
1288
|
+
const vars =
|
|
1289
|
+
spec.names.length === 1
|
|
1290
|
+
? "1 variable"
|
|
1291
|
+
: `${spec.names.length} variables`;
|
|
1292
|
+
const vals = isTuple
|
|
1293
|
+
? `returns ${valTypesFlat.length} values`
|
|
1294
|
+
: valTypesFlat.length === 1
|
|
1295
|
+
? "1 value"
|
|
1296
|
+
: `${valTypesFlat.length} values`;
|
|
1297
|
+
this.err(`assignment mismatch: ${vars} but ${vals}`, spec);
|
|
1298
|
+
}
|
|
903
1299
|
for (let i = 0; i < spec.names.length; i++) {
|
|
904
|
-
const vt =
|
|
905
|
-
|
|
1300
|
+
const vt = valTypesFlat[i] ?? ANY;
|
|
1301
|
+
const varType = type ?? defaultType(vt) ?? ANY;
|
|
1302
|
+
if (!type && spec.names.length === spec.value.length)
|
|
1303
|
+
this._checkConstDefault(vt, spec.value[i], "variable declaration");
|
|
1304
|
+
if (type)
|
|
1305
|
+
this.assertAssignable(
|
|
1306
|
+
type,
|
|
1307
|
+
vt,
|
|
1308
|
+
spec.value[i] ?? spec.value[0],
|
|
1309
|
+
"variable declaration",
|
|
1310
|
+
);
|
|
1311
|
+
scope.defineLocal(spec.names[i], varType);
|
|
906
1312
|
}
|
|
1313
|
+
} else {
|
|
1314
|
+
if (!type) type = ANY;
|
|
1315
|
+
for (const name of spec.names) scope.defineLocal(name, type);
|
|
907
1316
|
}
|
|
908
|
-
if (!type) type = ANY;
|
|
909
|
-
for (const name of spec.names) scope.defineLocal(name, type);
|
|
910
1317
|
}
|
|
911
1318
|
this._sharedAllocOk = savedSharedAlloc;
|
|
912
1319
|
}
|
|
@@ -918,7 +1325,18 @@ export class TypeChecker {
|
|
|
918
1325
|
const type = spec.type
|
|
919
1326
|
? this.resolveTypeNode(spec.type, scope)
|
|
920
1327
|
: (valTypes[0] ?? ANY);
|
|
921
|
-
for (
|
|
1328
|
+
for (let i = 0; i < spec.names.length; i++) {
|
|
1329
|
+
const name = spec.names[i];
|
|
1330
|
+
const expr = spec.value?.[i] ?? spec.value?.[0] ?? null;
|
|
1331
|
+
if (spec.type && expr)
|
|
1332
|
+
this._checkConstOverflow(
|
|
1333
|
+
type,
|
|
1334
|
+
valTypes[i] ?? valTypes[0],
|
|
1335
|
+
expr,
|
|
1336
|
+
"constant declaration",
|
|
1337
|
+
);
|
|
1338
|
+
scope.defineConst(name, type, expr);
|
|
1339
|
+
}
|
|
922
1340
|
}
|
|
923
1341
|
}
|
|
924
1342
|
|
|
@@ -930,3 +1348,4 @@ Object.assign(TypeChecker.prototype, expressionCheckMethods);
|
|
|
930
1348
|
Object.assign(TypeChecker.prototype, terminationMethods);
|
|
931
1349
|
Object.assign(TypeChecker.prototype, resolveMethods);
|
|
932
1350
|
Object.assign(TypeChecker.prototype, assignabilityMethods);
|
|
1351
|
+
Object.assign(TypeChecker.prototype, constantCheckMethods);
|