gofront 1.9.0 → 1.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +3 -3
- package/src/backend/js/expressions.js +329 -58
- package/src/backend/js/index.js +90 -10
- package/src/backend/js/runtime.js +51 -9
- package/src/backend/js/statements.js +193 -84
- package/src/backend/js/type-info.js +123 -8
- package/src/backend/shared/fmt-types.js +30 -1
- package/src/backend/wasm/emit-builtins.js +250 -9
- package/src/backend/wasm/emit-exprs.js +63 -22
- package/src/backend/wasm/emit-shared.js +4 -1
- package/src/backend/wasm/emit-stmts.js +239 -205
- package/src/backend/wasm/emit.js +73 -26
- package/src/backend/wasm/globals.js +1 -0
- package/src/backend/wasm/index.js +8 -0
- package/src/backend/wasm/module-emitter.js +211 -57
- package/src/backend/wasm/monomorph.js +36 -2
- package/src/backend/wasm/natives.js +4 -0
- package/src/backend/wasm/types.js +32 -3
- package/src/backend/wasm/variadic.js +6 -1
- package/src/cli/minifier.js +20 -6
- package/src/compiler.js +6 -1
- package/src/lexer/lexer.js +1 -0
- package/src/link/stdlib.js +1 -0
- package/src/link/type-names.js +72 -16
- package/src/lower/embedding.js +43 -15
- package/src/lower/fmt-formatters.js +38 -14
- package/src/lower/functions.js +223 -7
- package/src/lower/gen-types.js +21 -2
- package/src/lower/index.js +7 -1
- package/src/lower/json-codecs.js +114 -6
- package/src/parser/declarations.js +4 -2
- package/src/parser/expressions.js +25 -41
- package/src/parser/statements.js +107 -0
- package/src/parser/types.js +11 -0
- package/src/typechecker/assignability.js +94 -12
- package/src/typechecker/constants.js +124 -2
- package/src/typechecker/expressions.js +171 -17
- package/src/typechecker/index.js +158 -29
- package/src/typechecker/operand-text.js +1 -0
- package/src/typechecker/resolve.js +308 -39
- package/src/typechecker/statements.js +171 -21
- package/src/typechecker/types.js +84 -2
package/src/lower/gen-types.js
CHANGED
|
@@ -59,8 +59,16 @@ export function walkNodes(root, fn) {
|
|
|
59
59
|
}
|
|
60
60
|
fn(root);
|
|
61
61
|
forEachChild(root, (c) => walkNodes(c, fn));
|
|
62
|
+
// templ bodies keep their Go expressions in non-enumerable properties
|
|
63
|
+
// (see parser/templ.js), which forEachChild does not visit.
|
|
64
|
+
for (const name of TEMPL_EXPR_PROPS) {
|
|
65
|
+
const d = Object.getOwnPropertyDescriptor(root, name);
|
|
66
|
+
if (d && !d.enumerable) walkNodes(d.value, fn);
|
|
67
|
+
}
|
|
62
68
|
}
|
|
63
69
|
|
|
70
|
+
const TEMPL_EXPR_PROPS = ["expr", "exprs", "forStmt"];
|
|
71
|
+
|
|
64
72
|
// True when `t` mentions a type parameter (a generic declaration, or a type
|
|
65
73
|
// built from one inside a generic function).
|
|
66
74
|
export function mentionsTypeParam(t, seen = new Set()) {
|
|
@@ -126,8 +134,9 @@ export function importsFor(qualifiers) {
|
|
|
126
134
|
|
|
127
135
|
// Every type a closed-world generator may need to describe: the entries
|
|
128
136
|
// { type, node, via } for the type arguments of generic calls (`typeArg`),
|
|
129
|
-
// the type of every expression (`expr`)
|
|
130
|
-
//
|
|
137
|
+
// the type of every expression (`expr`), the static type of every value
|
|
138
|
+
// converted to an interface (`boxed`) and the package's declared named types
|
|
139
|
+
// (`declared`, node null).
|
|
131
140
|
export function collectProgramTypes(programs, checker) {
|
|
132
141
|
const found = [];
|
|
133
142
|
for (const p of programs)
|
|
@@ -136,6 +145,14 @@ export function collectProgramTypes(programs, checker) {
|
|
|
136
145
|
found.push({ type: defaultType(a), node, via: "typeArg" });
|
|
137
146
|
if (node._type)
|
|
138
147
|
found.push({ type: defaultType(node._type), node, via: "expr" });
|
|
148
|
+
if (node._ifaceSrc)
|
|
149
|
+
found.push({ type: defaultType(node._ifaceSrc), node, via: "boxed" });
|
|
150
|
+
// A multi-value call forwarded into interfaces (`return f()`,
|
|
151
|
+
// `g(f())`) keeps one mark holder per element until lowering
|
|
152
|
+
// splits it.
|
|
153
|
+
for (const h of node._fwdIface ?? [])
|
|
154
|
+
if (h?._ifaceSrc)
|
|
155
|
+
found.push({ type: defaultType(h._ifaceSrc), node, via: "boxed" });
|
|
139
156
|
});
|
|
140
157
|
for (const [name, t] of checker.types) {
|
|
141
158
|
if (t?.kind !== "named" || t.name !== name) continue;
|
|
@@ -253,6 +270,8 @@ export class TypeSpeller {
|
|
|
253
270
|
const byKind = {
|
|
254
271
|
basic: () => t.name !== "void" && t.name !== "nil",
|
|
255
272
|
named: () => this.spellableNamed(t, ok, seen),
|
|
273
|
+
// A JS-interop (.d.ts) type has no Go source to write.
|
|
274
|
+
namespace: () => false,
|
|
256
275
|
pointer: () => ok(t.base),
|
|
257
276
|
slice: () => ok(t.elem),
|
|
258
277
|
array: () => ok(t.elem),
|
package/src/lower/index.js
CHANGED
|
@@ -16,7 +16,12 @@ import { scanAddressTaken } from "./boxing.js";
|
|
|
16
16
|
import { analyzeCaptures } from "./captures.js";
|
|
17
17
|
import { computeEmbeddedStubs } from "./embedding.js";
|
|
18
18
|
import { paramEscapes } from "./escape.js";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
extractNamedReturns,
|
|
21
|
+
hasDefer,
|
|
22
|
+
normalizeDefers,
|
|
23
|
+
splitForwardedTuples,
|
|
24
|
+
} from "./functions.js";
|
|
20
25
|
import { computeGlobalAnalysis } from "./globals.js";
|
|
21
26
|
import { OwnershipContext, takesElementAddress } from "./ownership.js";
|
|
22
27
|
|
|
@@ -83,6 +88,7 @@ export function lower(programOrPrograms, checker = null) {
|
|
|
83
88
|
? programOrPrograms
|
|
84
89
|
: [programOrPrograms];
|
|
85
90
|
|
|
91
|
+
splitForwardedTuples(programs);
|
|
86
92
|
normalizeDefers(programs);
|
|
87
93
|
|
|
88
94
|
res.elemAddrTaken = programs.some((p) => takesElementAddress(p?.decls));
|
package/src/lower/json-codecs.js
CHANGED
|
@@ -220,6 +220,8 @@ export class JsonCodecGen extends TypeSpeller {
|
|
|
220
220
|
return `map[${this.refl(t.key)}]${this.refl(t.value)}`;
|
|
221
221
|
case "interface":
|
|
222
222
|
return t.name === "error" ? "error" : "interface {}";
|
|
223
|
+
case "namespace":
|
|
224
|
+
return t.name;
|
|
223
225
|
case "func":
|
|
224
226
|
return this.funcString(t, (x) => this.refl(x));
|
|
225
227
|
default:
|
|
@@ -273,9 +275,49 @@ export class JsonCodecGen extends TypeSpeller {
|
|
|
273
275
|
|
|
274
276
|
// Statements that encode the Go expression `x` of type `t`.
|
|
275
277
|
encValue(t, x, addr) {
|
|
278
|
+
if (holdsInterop(t)) return this.encInterop(t, x);
|
|
276
279
|
return [`${this.encFn(t, addr)}(e, ${x})`];
|
|
277
280
|
}
|
|
278
281
|
|
|
282
|
+
// A JS-interop value has no Go source to spell, so it is encoded where it
|
|
283
|
+
// sits: nil is `null` (an empty slice, map or array is `[]` or `{}`), and
|
|
284
|
+
// anything else fails with the type's name.
|
|
285
|
+
encInterop(t, x) {
|
|
286
|
+
const u = under(t);
|
|
287
|
+
const fail = (name) => `\te.FailType(${q(name)})`;
|
|
288
|
+
if (u.kind === "namespace")
|
|
289
|
+
return [`if ${x} == nil {`, "\te.Null()", "} else {", fail(u.name), "}"];
|
|
290
|
+
if (u.kind === "pointer")
|
|
291
|
+
return [
|
|
292
|
+
`if ${x} == nil {`,
|
|
293
|
+
"\te.Null()",
|
|
294
|
+
"} else {",
|
|
295
|
+
fail(this.refl(t)),
|
|
296
|
+
"}",
|
|
297
|
+
];
|
|
298
|
+
const [empty, elem] =
|
|
299
|
+
u.kind === "map"
|
|
300
|
+
? ["{}", holdsInterop(u.key) ? u.key : u.value]
|
|
301
|
+
: ["[]", u.elem];
|
|
302
|
+
if (u.kind === "array")
|
|
303
|
+
return [
|
|
304
|
+
`if ${u.size} == 0 {`,
|
|
305
|
+
`\te.Lit(${q(empty)})`,
|
|
306
|
+
"} else {",
|
|
307
|
+
fail(this.refl(elem)),
|
|
308
|
+
"}",
|
|
309
|
+
];
|
|
310
|
+
return [
|
|
311
|
+
`if ${x} == nil {`,
|
|
312
|
+
"\te.Null()",
|
|
313
|
+
`} else if len(${x}) == 0 {`,
|
|
314
|
+
`\te.Lit(${q(empty)})`,
|
|
315
|
+
"} else {",
|
|
316
|
+
fail(this.refl(elem)),
|
|
317
|
+
"}",
|
|
318
|
+
];
|
|
319
|
+
}
|
|
320
|
+
|
|
279
321
|
// `mt` is the type that declares the method (the pointee for a pointer).
|
|
280
322
|
marshalCall(x, t, mt = t) {
|
|
281
323
|
if (!marshalerOf(mt) && textMarshalerOf(mt))
|
|
@@ -413,6 +455,7 @@ export class JsonCodecGen extends TypeSpeller {
|
|
|
413
455
|
if (u.kind === "slice" || u.kind === "map") return `len(${x}) == 0`;
|
|
414
456
|
if (u.kind === "array") return u.size === 0 ? "true" : "false";
|
|
415
457
|
if (u.kind === "struct") return "false";
|
|
458
|
+
if (u.kind === "namespace") return `${x} == nil`;
|
|
416
459
|
const n = unalias(u.name);
|
|
417
460
|
if (n === "bool") return `!${x}`;
|
|
418
461
|
if (n === "string") return `${x} == ""`;
|
|
@@ -779,6 +822,9 @@ export class JsonCodecGen extends TypeSpeller {
|
|
|
779
822
|
|
|
780
823
|
decStruct(u, t, typ) {
|
|
781
824
|
const fields = resolveJsonFields(u);
|
|
825
|
+
for (const f of fields)
|
|
826
|
+
if (holdsInterop(f.type))
|
|
827
|
+
throw new Error(`json: cannot unmarshal into ${interopName(f.type)}`);
|
|
782
828
|
const sname = this.shortName(t);
|
|
783
829
|
const lines = [
|
|
784
830
|
"if d.Null() {",
|
|
@@ -1106,7 +1152,8 @@ export class JsonCodecGen extends TypeSpeller {
|
|
|
1106
1152
|
this.encFn(t, false);
|
|
1107
1153
|
}
|
|
1108
1154
|
for (const { type: t, via } of this.collectedTypes()) {
|
|
1109
|
-
if (via !== "
|
|
1155
|
+
if (via !== "boxed" || !t || mentionsTypeParam(t)) continue;
|
|
1156
|
+
if (!this.spellable(t)) continue;
|
|
1110
1157
|
const u = under(t);
|
|
1111
1158
|
if (!u || u.kind === "interface" || u.kind === "func") continue;
|
|
1112
1159
|
this.encFn(t, false);
|
|
@@ -1114,12 +1161,15 @@ export class JsonCodecGen extends TypeSpeller {
|
|
|
1114
1161
|
}
|
|
1115
1162
|
}
|
|
1116
1163
|
|
|
1164
|
+
// A struct holding a JS-interop value (a `.d.ts` interface) cannot be
|
|
1165
|
+
// written as a dynamic case, so it is not spellable either.
|
|
1117
1166
|
spellableNamed(t, ok) {
|
|
1167
|
+
if (isJsonValue(t)) return true;
|
|
1118
1168
|
return (
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1169
|
+
!t.name.includes("$$") &&
|
|
1170
|
+
!t.name.includes("[") &&
|
|
1171
|
+
(t.typeArgs ?? []).every(ok) &&
|
|
1172
|
+
!reachesInterop(t)
|
|
1123
1173
|
);
|
|
1124
1174
|
}
|
|
1125
1175
|
|
|
@@ -1157,7 +1207,12 @@ export class JsonCodecGen extends TypeSpeller {
|
|
|
1157
1207
|
}
|
|
1158
1208
|
// Slices, arrays and maps reach an interface from any expression
|
|
1159
1209
|
// (a function result, a variable), not only from a literal
|
|
1160
|
-
if (
|
|
1210
|
+
if (
|
|
1211
|
+
!buildsValue(node) &&
|
|
1212
|
+
!isCompositeType(t) &&
|
|
1213
|
+
!isFuncComposite(t) &&
|
|
1214
|
+
!isJsonValue(t)
|
|
1215
|
+
)
|
|
1161
1216
|
continue;
|
|
1162
1217
|
if (this.spellable(t) && under(t)?.kind !== "interface")
|
|
1163
1218
|
found.set(this.key(t), t);
|
|
@@ -1196,6 +1251,48 @@ const isTaggedSlice = (t) =>
|
|
|
1196
1251
|
|
|
1197
1252
|
// json.RawMessage and json.Number values can reach an interface from any
|
|
1198
1253
|
// expression (a conversion, a field read).
|
|
1254
|
+
// True when `t` is, or holds through pointers, elements and struct fields, a
|
|
1255
|
+
// JS-interop type (a `.d.ts` interface) that has no Go source to spell.
|
|
1256
|
+
export function reachesInterop(t, seen = new Set(), viaStruct = true) {
|
|
1257
|
+
if (!t || seen.has(t)) return false;
|
|
1258
|
+
seen.add(t);
|
|
1259
|
+
const u = under(t) ?? t;
|
|
1260
|
+
switch (u.kind) {
|
|
1261
|
+
case "namespace":
|
|
1262
|
+
return true;
|
|
1263
|
+
case "pointer":
|
|
1264
|
+
return reachesInterop(u.base, seen, viaStruct);
|
|
1265
|
+
case "slice":
|
|
1266
|
+
case "array":
|
|
1267
|
+
return reachesInterop(u.elem, seen, viaStruct);
|
|
1268
|
+
case "map":
|
|
1269
|
+
return (
|
|
1270
|
+
reachesInterop(u.key, seen, viaStruct) ||
|
|
1271
|
+
reachesInterop(u.value, seen, viaStruct)
|
|
1272
|
+
);
|
|
1273
|
+
case "struct":
|
|
1274
|
+
return (
|
|
1275
|
+
viaStruct &&
|
|
1276
|
+
(u._structFields ?? []).some((f) => reachesInterop(f.type, seen))
|
|
1277
|
+
);
|
|
1278
|
+
default:
|
|
1279
|
+
return false;
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// True when a value of type `t` is, or holds through pointers and elements
|
|
1284
|
+
// (not through a named struct, which has its own codec), a JS-interop value.
|
|
1285
|
+
const holdsInterop = (t) => reachesInterop(t, new Set(), false);
|
|
1286
|
+
|
|
1287
|
+
// The name of the JS-interop type that `t` is or wraps.
|
|
1288
|
+
function interopName(t) {
|
|
1289
|
+
const u = under(t);
|
|
1290
|
+
if (u.kind === "namespace") return u.name;
|
|
1291
|
+
if (u.kind === "map")
|
|
1292
|
+
return interopName(holdsInterop(u.key) ? u.key : u.value);
|
|
1293
|
+
return interopName(u.base ?? u.elem);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1199
1296
|
const isJsonValue = (t) => isRaw(t) || isNumber(t);
|
|
1200
1297
|
|
|
1201
1298
|
export const isCompositeType = (t) => {
|
|
@@ -1207,6 +1304,17 @@ export const isCompositeType = (t) => {
|
|
|
1207
1304
|
);
|
|
1208
1305
|
};
|
|
1209
1306
|
|
|
1307
|
+
// A slice, array or map holding funcs: it never encodes, but a nil one in an
|
|
1308
|
+
// interface still does, as `null`, and a non-nil one names the func.
|
|
1309
|
+
export const isFuncComposite = (t) => {
|
|
1310
|
+
const k = under(t)?.kind;
|
|
1311
|
+
return (
|
|
1312
|
+
(k === "slice" || k === "array" || k === "map") &&
|
|
1313
|
+
holdsFunc(t) &&
|
|
1314
|
+
!hasSharedRepr(t)
|
|
1315
|
+
);
|
|
1316
|
+
};
|
|
1317
|
+
|
|
1210
1318
|
// Func values are not JSON-encodable, and `case []func():` does not parse.
|
|
1211
1319
|
export function holdsFunc(t, depth = 0) {
|
|
1212
1320
|
if (!t || depth > 8) return false;
|
|
@@ -227,12 +227,14 @@ export const declarationParseMethods = {
|
|
|
227
227
|
this.check(T.EOF)
|
|
228
228
|
)
|
|
229
229
|
return null;
|
|
230
|
-
// Inside a type expression (e.g. func type in a tuple
|
|
231
|
-
// `case func():`), `,`, `)` and
|
|
230
|
+
// Inside a type expression (e.g. func type in a tuple, a type-switch
|
|
231
|
+
// `case func():` or a type argument `f[func(int)]`), `,`, `)`, `]` and
|
|
232
|
+
// `:` also end the type
|
|
232
233
|
if (
|
|
233
234
|
inTypeExpr &&
|
|
234
235
|
(this.check(T.COMMA) ||
|
|
235
236
|
this.check(T.RPAREN) ||
|
|
237
|
+
this.check(T.RBRACKET) ||
|
|
236
238
|
this.check(T.COLON) ||
|
|
237
239
|
this.check(T.STRING) ||
|
|
238
240
|
this.check(T.ASSIGN))
|
|
@@ -402,48 +402,20 @@ export const expressionParserMethods = {
|
|
|
402
402
|
return { kind: "CallExpr", func: fn, args };
|
|
403
403
|
},
|
|
404
404
|
|
|
405
|
-
// Returns true if the current position
|
|
406
|
-
// e.g. []int{1,2,3}
|
|
407
|
-
//
|
|
405
|
+
// Returns true if the current position starts a composite literal argument,
|
|
406
|
+
// e.g. []int{1,2,3}, map[string]int{"a":1} or []func() int{f}: the type is
|
|
407
|
+
// parsed speculatively and must be followed by `{`. The position is
|
|
408
|
+
// always restored.
|
|
408
409
|
looksLikeCompositeLitArg() {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
depth++;
|
|
418
|
-
i++;
|
|
419
|
-
continue;
|
|
420
|
-
}
|
|
421
|
-
if (tt === T.RBRACKET) {
|
|
422
|
-
depth--;
|
|
423
|
-
i++;
|
|
424
|
-
continue;
|
|
425
|
-
}
|
|
426
|
-
if (depth > 0) {
|
|
427
|
-
i++;
|
|
428
|
-
continue;
|
|
429
|
-
}
|
|
430
|
-
if (
|
|
431
|
-
tt === T.MAP ||
|
|
432
|
-
tt === T.IDENT ||
|
|
433
|
-
tt === T.DOT || // qualified types: []pkg.Type{...}
|
|
434
|
-
tt === T.STAR ||
|
|
435
|
-
tt === T.INT ||
|
|
436
|
-
tt === T.FLOAT ||
|
|
437
|
-
tt === T.STRING
|
|
438
|
-
) {
|
|
439
|
-
i++;
|
|
440
|
-
continue;
|
|
441
|
-
}
|
|
442
|
-
break;
|
|
410
|
+
const start = this.pos;
|
|
411
|
+
try {
|
|
412
|
+
this.parseType();
|
|
413
|
+
return this.check(T.LBRACE);
|
|
414
|
+
} catch {
|
|
415
|
+
return false;
|
|
416
|
+
} finally {
|
|
417
|
+
this.pos = start;
|
|
443
418
|
}
|
|
444
|
-
// Skip whitespace tokens (shouldn't exist post-lex, but be safe)
|
|
445
|
-
while (i < src.length && src[i].type === T.SEMICOLON) i++;
|
|
446
|
-
return i < src.length && src[i].type === T.LBRACE;
|
|
447
419
|
},
|
|
448
420
|
|
|
449
421
|
parseCompositeLit(typeExpr) {
|
|
@@ -494,7 +466,9 @@ export const expressionParserMethods = {
|
|
|
494
466
|
// Literals
|
|
495
467
|
if (t.type === T.INT) {
|
|
496
468
|
this.advance();
|
|
497
|
-
|
|
469
|
+
const lit = { kind: "BasicLit", litKind: "INT", value: t.value };
|
|
470
|
+
if (t.rune) lit.rune = true;
|
|
471
|
+
return lit;
|
|
498
472
|
}
|
|
499
473
|
if (t.type === T.FLOAT) {
|
|
500
474
|
this.advance();
|
|
@@ -589,6 +563,16 @@ export const expressionParserMethods = {
|
|
|
589
563
|
return this.parseCompositeLit(typeExpr);
|
|
590
564
|
}
|
|
591
565
|
|
|
566
|
+
// Interface conversion: interface{ M() }(v). `interface{…}{` is not a
|
|
567
|
+
// composite literal in Go, so only the conversion form is accepted.
|
|
568
|
+
if (t.type === T.INTERFACE) {
|
|
569
|
+
const targetType = this.parseInterfaceType();
|
|
570
|
+
this.expect(T.LPAREN);
|
|
571
|
+
const expr = this.parseExpr();
|
|
572
|
+
this.expect(T.RPAREN);
|
|
573
|
+
return { kind: "TypeConversion", targetType, expr };
|
|
574
|
+
}
|
|
575
|
+
|
|
592
576
|
if (t.type === T.CHAN) {
|
|
593
577
|
this.err("channels are not supported in GoFront");
|
|
594
578
|
}
|
package/src/parser/statements.js
CHANGED
|
@@ -2,6 +2,57 @@
|
|
|
2
2
|
|
|
3
3
|
import { T } from "../lexer/tokens.js";
|
|
4
4
|
|
|
5
|
+
// Statement keywords: none of them can appear inside a composite literal or a
|
|
6
|
+
// statement header's expressions.
|
|
7
|
+
const STMT_KEYWORDS = new Set([
|
|
8
|
+
T.VAR,
|
|
9
|
+
T.CONST,
|
|
10
|
+
T.TYPE,
|
|
11
|
+
T.IF,
|
|
12
|
+
T.ELSE,
|
|
13
|
+
T.FOR,
|
|
14
|
+
T.RETURN,
|
|
15
|
+
T.BREAK,
|
|
16
|
+
T.CONTINUE,
|
|
17
|
+
T.SWITCH,
|
|
18
|
+
T.CASE,
|
|
19
|
+
T.DEFAULT,
|
|
20
|
+
T.SELECT,
|
|
21
|
+
T.DEFER,
|
|
22
|
+
T.GO,
|
|
23
|
+
T.FALLTHROUGH,
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
// Tokens that only appear in statements: a brace pair holding one at its top
|
|
27
|
+
// level is a block, not a composite literal.
|
|
28
|
+
const STMT_ONLY_TOKENS = new Set([
|
|
29
|
+
...STMT_KEYWORDS,
|
|
30
|
+
T.SEMICOLON,
|
|
31
|
+
T.ASSIGN,
|
|
32
|
+
T.DEFINE,
|
|
33
|
+
T.PLUS_ASSIGN,
|
|
34
|
+
T.MINUS_ASSIGN,
|
|
35
|
+
T.STAR_ASSIGN,
|
|
36
|
+
T.SLASH_ASSIGN,
|
|
37
|
+
T.PERCENT_ASSIGN,
|
|
38
|
+
T.AMP_ASSIGN,
|
|
39
|
+
T.PIPE_ASSIGN,
|
|
40
|
+
T.AND_NOT_ASSIGN,
|
|
41
|
+
T.CARET_ASSIGN,
|
|
42
|
+
T.LSHIFT_ASSIGN,
|
|
43
|
+
T.RSHIFT_ASSIGN,
|
|
44
|
+
T.INC,
|
|
45
|
+
T.DEC,
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
const OPEN_BRACKETS = new Set([T.LBRACE, T.LPAREN, T.LBRACKET]);
|
|
49
|
+
const CLOSE_BRACKETS = new Set([T.RBRACE, T.RPAREN, T.RBRACKET]);
|
|
50
|
+
|
|
51
|
+
function bracketDelta(tt) {
|
|
52
|
+
if (OPEN_BRACKETS.has(tt)) return 1;
|
|
53
|
+
return CLOSE_BRACKETS.has(tt) ? -1 : 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
5
56
|
// Method-name dispatch for pure-delegation cases in _parseStmt.
|
|
6
57
|
const PARSE_STMT_DELEGATE = {
|
|
7
58
|
[T.VAR]: "parseVarDecl",
|
|
@@ -130,6 +181,7 @@ export const statementParserMethods = {
|
|
|
130
181
|
let cond;
|
|
131
182
|
// if init; cond { } — use noCompLit to avoid "{" being parsed as composite literal
|
|
132
183
|
const first = this.parseSimpleStmtRaw(true);
|
|
184
|
+
this._rejectHeaderCompositeLit("if", 0, first);
|
|
133
185
|
// Only treat as "init; cond" if semicolon is followed by a non-brace token
|
|
134
186
|
if (this.check(T.SEMICOLON) && this.peek2().type !== T.LBRACE) {
|
|
135
187
|
this.advance(); // consume ;
|
|
@@ -166,6 +218,7 @@ export const statementParserMethods = {
|
|
|
166
218
|
let first = null;
|
|
167
219
|
if (!this.check(T.SEMICOLON)) {
|
|
168
220
|
first = this.parseSimpleStmtRaw(true);
|
|
221
|
+
this._rejectHeaderCompositeLit("for", 1, first);
|
|
169
222
|
|
|
170
223
|
if (this.check(T.LBRACE)) {
|
|
171
224
|
// for k, v := range expr { } — range stmt ends right before {
|
|
@@ -201,6 +254,59 @@ export const statementParserMethods = {
|
|
|
201
254
|
return { kind: "ForStmt", init: first, cond, post, body };
|
|
202
255
|
},
|
|
203
256
|
|
|
257
|
+
// `if s := &T{f: 1}; s.f > 0 {`: in a header, `{` after a type name opens
|
|
258
|
+
// the body, so Go requires the literal to be parenthesized. Reported when
|
|
259
|
+
// the brace pair holds no statement tokens, is followed by an explicit `;`,
|
|
260
|
+
// and the header then continues to its body `{` (with at most `semis`
|
|
261
|
+
// further `;`, one for a `for` post statement). A bare expression or a
|
|
262
|
+
// range clause before the `{` (`if ok { f() };`) means it is the body.
|
|
263
|
+
_rejectHeaderCompositeLit(keyword, semis, first) {
|
|
264
|
+
if (!this.check(T.LBRACE)) return;
|
|
265
|
+
if (first?.kind === "ExprStmt" || this.isRangeStmt(first)) return;
|
|
266
|
+
const prev = this.tokens[this.pos - 1]?.type;
|
|
267
|
+
if (prev !== T.IDENT && prev !== T.RBRACKET) return;
|
|
268
|
+
const close = this._closingBrace(this.pos);
|
|
269
|
+
if (close < 0) return;
|
|
270
|
+
const semi = this.tokens[close + 1];
|
|
271
|
+
if (semi?.type !== T.SEMICOLON || !this._isExplicitSemi(semi)) return;
|
|
272
|
+
if (!this._headerContinues(close + 2, semis)) return;
|
|
273
|
+
this.err(`composite literal in ${keyword} header must be parenthesized`);
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
// Index of the `}` matching the `{` at `open`, or -1 when the pair holds a
|
|
277
|
+
// statement-only token at its top level (then it is a block).
|
|
278
|
+
_closingBrace(open) {
|
|
279
|
+
let depth = 0;
|
|
280
|
+
for (let i = open; i < this.tokens.length; i++) {
|
|
281
|
+
const tt = this.tokens[i].type;
|
|
282
|
+
depth += bracketDelta(tt);
|
|
283
|
+
if (depth === 0) return tt === T.RBRACE ? i : -1;
|
|
284
|
+
if (tt === T.EOF || (depth === 1 && STMT_ONLY_TOKENS.has(tt))) return -1;
|
|
285
|
+
}
|
|
286
|
+
return -1;
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
_isExplicitSemi(tok) {
|
|
290
|
+
if (!this.source) return false;
|
|
291
|
+
this._sourceLines ??= this.source.split("\n");
|
|
292
|
+
return this._sourceLines[tok.line - 1]?.[tok.col - 1] === ";";
|
|
293
|
+
},
|
|
294
|
+
|
|
295
|
+
// Whether the tokens from `i` reach a top-level `{` (the body) without a
|
|
296
|
+
// statement keyword and with at most `semis` semicolons on the way.
|
|
297
|
+
_headerContinues(i, semis) {
|
|
298
|
+
let depth = 0;
|
|
299
|
+
for (; i < this.tokens.length; i++) {
|
|
300
|
+
const tt = this.tokens[i].type;
|
|
301
|
+
if (tt === T.LBRACE && depth === 0) return true;
|
|
302
|
+
depth += bracketDelta(tt);
|
|
303
|
+
if (tt === T.SEMICOLON && depth === 0) semis--;
|
|
304
|
+
if (depth < 0 || semis < 0) return false;
|
|
305
|
+
if (tt === T.EOF || STMT_KEYWORDS.has(tt)) return false;
|
|
306
|
+
}
|
|
307
|
+
return false;
|
|
308
|
+
},
|
|
309
|
+
|
|
204
310
|
isRangeStmt(stmt) {
|
|
205
311
|
if (!stmt) return false;
|
|
206
312
|
if (stmt.kind !== "DefineStmt" && stmt.kind !== "AssignStmt") return false;
|
|
@@ -213,6 +319,7 @@ export const statementParserMethods = {
|
|
|
213
319
|
let tag = null;
|
|
214
320
|
if (!this.check(T.LBRACE)) {
|
|
215
321
|
const first = this.parseSimpleStmtRaw(true);
|
|
322
|
+
this._rejectHeaderCompositeLit("switch", 0, first);
|
|
216
323
|
if (this.match(T.SEMICOLON)) {
|
|
217
324
|
init = first;
|
|
218
325
|
if (!this.check(T.LBRACE))
|
package/src/parser/types.js
CHANGED
|
@@ -329,6 +329,17 @@ export const typeParserMethods = {
|
|
|
329
329
|
this.semi();
|
|
330
330
|
continue;
|
|
331
331
|
}
|
|
332
|
+
// Type-set element that is not a bare name: *T, []int, map[K]V, ...
|
|
333
|
+
if (!this.check(T.IDENT)) {
|
|
334
|
+
const terms = [{ approx: false, type: this.parseType() }];
|
|
335
|
+
while (this.match(T.PIPE)) {
|
|
336
|
+
const approx = this.match(T.TILDE);
|
|
337
|
+
terms.push({ approx, type: this.parseType() });
|
|
338
|
+
}
|
|
339
|
+
unionConstraint = { kind: "UnionConstraint", terms };
|
|
340
|
+
this.semi();
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
332
343
|
const name = this.expect(T.IDENT).value;
|
|
333
344
|
if (this.check(T.LPAREN)) {
|
|
334
345
|
// Method signature: Name(params) returnType
|