gofront 1.9.0 → 1.9.1
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 +2 -2
- 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/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/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
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
FLOAT64,
|
|
13
13
|
is64BitInt,
|
|
14
14
|
isAny,
|
|
15
|
+
isBool,
|
|
15
16
|
isComplex,
|
|
16
17
|
isComplex64,
|
|
17
18
|
isComplexOrNumeric,
|
|
@@ -27,9 +28,11 @@ import {
|
|
|
27
28
|
SIZED_INT_NAMES,
|
|
28
29
|
STRING,
|
|
29
30
|
typeStr,
|
|
31
|
+
UNTYPED_BOOL,
|
|
30
32
|
UNTYPED_COMPLEX,
|
|
31
33
|
UNTYPED_FLOAT,
|
|
32
34
|
UNTYPED_INT,
|
|
35
|
+
UNTYPED_RUNE,
|
|
33
36
|
} from "./types.js";
|
|
34
37
|
|
|
35
38
|
// Unnamed type literal kinds for Go's "identical underlying types" assignability rule.
|
|
@@ -63,6 +66,23 @@ const UNTYPED_COMPAT = {
|
|
|
63
66
|
complex128: new Set(["complex128", "complex64"]),
|
|
64
67
|
};
|
|
65
68
|
|
|
69
|
+
// `&&` and `||` of two untyped bools stay untyped; a typed operand (including
|
|
70
|
+
// a defined bool type) gives its type, as in Go.
|
|
71
|
+
// Two typed operands must have the same type, and the result has it.
|
|
72
|
+
function logicalResultType(lt, rt) {
|
|
73
|
+
if (lt?.kind === "untyped" && rt?.kind === "untyped") return UNTYPED_BOOL;
|
|
74
|
+
if (rt?.kind === "untyped" && isBool(lt)) return lt;
|
|
75
|
+
if (lt?.kind === "untyped" && isBool(rt)) return rt;
|
|
76
|
+
if (isBool(lt) && isBool(rt) && typeStr(lt) === typeStr(rt)) return lt;
|
|
77
|
+
return BOOL;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Two untyped numeric operands combine to the later of int, rune, float.
|
|
81
|
+
function untypedIntegerKind(isFloat, lt, rt) {
|
|
82
|
+
if (isFloat) return UNTYPED_FLOAT;
|
|
83
|
+
return lt.rune || rt.rune ? UNTYPED_RUNE : UNTYPED_INT;
|
|
84
|
+
}
|
|
85
|
+
|
|
66
86
|
/** @type {ThisType<TypeChecker>} */
|
|
67
87
|
export const assignabilityMethods = {
|
|
68
88
|
// ── Binary result types ───────────────────────────────────────────
|
|
@@ -71,12 +91,26 @@ export const assignabilityMethods = {
|
|
|
71
91
|
if (lt?.kind === "typeParam" || rt?.kind === "typeParam")
|
|
72
92
|
return this._binaryResultTypeTypeParam(op, lt, rt);
|
|
73
93
|
if (CMP_OPS.has(op)) return this._binaryResultTypeCmp(op, lt, rt, node);
|
|
74
|
-
if (LOG_OPS.has(op))
|
|
94
|
+
if (LOG_OPS.has(op)) {
|
|
95
|
+
this._checkLogicalOperands(lt, rt, node);
|
|
96
|
+
return logicalResultType(lt, rt);
|
|
97
|
+
}
|
|
75
98
|
return this._binaryResultTypeNonComplex(op, lt, rt, node);
|
|
76
99
|
},
|
|
77
100
|
|
|
101
|
+
// `B && bool` mixes two distinct bool types, which Go rejects.
|
|
102
|
+
_checkLogicalOperands(lt, rt, node) {
|
|
103
|
+
if (!node || lt?.kind === "untyped" || rt?.kind === "untyped") return;
|
|
104
|
+
if (!isBool(lt) || !isBool(rt) || typeStr(lt) === typeStr(rt)) return;
|
|
105
|
+
this.err(
|
|
106
|
+
`invalid operation: ${exprText(node)} (mismatched types ${typeStr(lt)} and ${typeStr(rt)})`,
|
|
107
|
+
node,
|
|
108
|
+
);
|
|
109
|
+
},
|
|
110
|
+
|
|
78
111
|
_binaryResultTypeTypeParam(op, lt, rt) {
|
|
79
|
-
if (CMP_OPS.has(op)
|
|
112
|
+
if (CMP_OPS.has(op)) return UNTYPED_BOOL;
|
|
113
|
+
if (LOG_OPS.has(op)) return BOOL;
|
|
80
114
|
return lt?.kind === "typeParam" ? lt : rt;
|
|
81
115
|
},
|
|
82
116
|
|
|
@@ -137,7 +171,8 @@ export const assignabilityMethods = {
|
|
|
137
171
|
if (this._checkComplexCmpOp(op, lt, rt, node)) return ANY;
|
|
138
172
|
this._checkIntegerCmpKinds(op, lt, rt, node);
|
|
139
173
|
this._checkEqualityComparable(op, lt, rt, node);
|
|
140
|
-
|
|
174
|
+
// A comparison is an untyped bool: it also fits a defined bool type.
|
|
175
|
+
return UNTYPED_BOOL;
|
|
141
176
|
},
|
|
142
177
|
|
|
143
178
|
_binaryResultTypeComplex(op, lt, rt, node) {
|
|
@@ -163,7 +198,7 @@ export const assignabilityMethods = {
|
|
|
163
198
|
const rFloat = rName === "float32" || rName === "float64";
|
|
164
199
|
const isFloat = lFloat || rFloat;
|
|
165
200
|
if (lt.kind === "untyped" && rt.kind === "untyped")
|
|
166
|
-
return isFloat
|
|
201
|
+
return untypedIntegerKind(isFloat, lt, rt);
|
|
167
202
|
if (lt.kind === "untyped")
|
|
168
203
|
return isFloat && rt.name !== "float64" && rt.name !== "float32"
|
|
169
204
|
? FLOAT64
|
|
@@ -229,6 +264,19 @@ export const assignabilityMethods = {
|
|
|
229
264
|
}
|
|
230
265
|
},
|
|
231
266
|
|
|
267
|
+
// An untyped numeric constant that takes type float32 is rounded to
|
|
268
|
+
// float32 in JS output (`var c float32 = 0.01`, `v == 0.01`).
|
|
269
|
+
_tagFloat32Const(target, source, node) {
|
|
270
|
+
if (!node || typeof node !== "object" || !node._const) return;
|
|
271
|
+
if (!isUntyped(source) || this._float32Base(target) !== "float32") return;
|
|
272
|
+
node._f32Const = true;
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
_float32Base(t) {
|
|
276
|
+
const r = t?.kind === "named" ? t.underlying : t;
|
|
277
|
+
return r?.kind === "basic" || r?.kind === "untyped" ? r.name : null;
|
|
278
|
+
},
|
|
279
|
+
|
|
232
280
|
// `var m uint64 = (1 << k) - 1`: a non-constant shift whose left operand is
|
|
233
281
|
// an untyped constant takes the type its context gives it, so codegen
|
|
234
282
|
// computes it in that type (BigInt for 64-bit integers).
|
|
@@ -265,6 +313,17 @@ export const assignabilityMethods = {
|
|
|
265
313
|
!isComplex(source)
|
|
266
314
|
)
|
|
267
315
|
valueNode._asComplex = true;
|
|
316
|
+
this._tagComplex64Const(target, source, valueNode);
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
// An untyped constant that takes type complex64 is rounded to float32 in
|
|
320
|
+
// JS output: a real constant through `_f32Const`, a complex constant
|
|
321
|
+
// (`1 + 0.01i`) through `_c64Const`.
|
|
322
|
+
_tagComplex64Const(target, source, node) {
|
|
323
|
+
if (!node || typeof node !== "object") return;
|
|
324
|
+
if (!isComplex64(target) || !isUntyped(source)) return;
|
|
325
|
+
if (isComplex(source)) node._c64Const = true;
|
|
326
|
+
else if (node._const) node._f32Const = true;
|
|
268
327
|
},
|
|
269
328
|
|
|
270
329
|
// `operand` is the expression whose value flows in, when `node` (the
|
|
@@ -275,6 +334,7 @@ export const assignabilityMethods = {
|
|
|
275
334
|
target = this.resolveType(target);
|
|
276
335
|
source = this.resolveType(source);
|
|
277
336
|
this._tag64BitNode(target, node);
|
|
337
|
+
this._tagFloat32Const(target, source, operand ?? node);
|
|
278
338
|
this._retypeUntypedShift(target, source, operand ?? node);
|
|
279
339
|
this._tagComplexConst(target, source, operand ?? node);
|
|
280
340
|
this._markIfaceBox(target, source, node);
|
|
@@ -442,11 +502,13 @@ export const assignabilityMethods = {
|
|
|
442
502
|
},
|
|
443
503
|
|
|
444
504
|
// An interface-typed operand needs an explicit type assertion before it can
|
|
445
|
-
// flow into a concrete (non-interface) type
|
|
505
|
+
// flow into a concrete (non-interface) type, or into an interface with
|
|
506
|
+
// methods (`var e error = b` with `b any`).
|
|
446
507
|
_checkIfaceToConcrete(target, source, node, ctx, operand) {
|
|
447
508
|
if (!source?._declared || !operand || typeof operand !== "object")
|
|
448
509
|
return false;
|
|
449
|
-
if (!this._isConcreteTarget(target)
|
|
510
|
+
if (!this._isConcreteTarget(target) && !this._isMethodIfaceTarget(target))
|
|
511
|
+
return false;
|
|
450
512
|
this.err(
|
|
451
513
|
`cannot use ${exprText(operand)} (${operandKind(operand, typeStr(source))}) as ${typeStr(target)} value in ${ctx}: need type assertion`,
|
|
452
514
|
node,
|
|
@@ -454,6 +516,31 @@ export const assignabilityMethods = {
|
|
|
454
516
|
return true;
|
|
455
517
|
},
|
|
456
518
|
|
|
519
|
+
// `int(x)`, `(*T)(x)` and `I(x)` (I with methods) with a source-written
|
|
520
|
+
// `any` operand need an assertion.
|
|
521
|
+
_checkIfaceConversion(srcType, targetType, expr, operand) {
|
|
522
|
+
if (!srcType?._declared || !operand) return false;
|
|
523
|
+
if (
|
|
524
|
+
!this._isConcreteTarget(targetType) &&
|
|
525
|
+
!this._isMethodIfaceTarget(targetType)
|
|
526
|
+
)
|
|
527
|
+
return false;
|
|
528
|
+
this.err(
|
|
529
|
+
`cannot convert ${exprText(operand)} (${operandKind(operand, typeStr(srcType))}) to type ${typeStr(targetType)}: need type assertion`,
|
|
530
|
+
expr,
|
|
531
|
+
);
|
|
532
|
+
return true;
|
|
533
|
+
},
|
|
534
|
+
|
|
535
|
+
// An interface type (named or literal) with at least one method.
|
|
536
|
+
_isMethodIfaceTarget(target) {
|
|
537
|
+
if (!target || isAny(target)) return false;
|
|
538
|
+
const base = this.resolveType(
|
|
539
|
+
target.kind === "named" ? target.underlying : target,
|
|
540
|
+
);
|
|
541
|
+
return base?.kind === "interface" && base.methods?.size > 0;
|
|
542
|
+
},
|
|
543
|
+
|
|
457
544
|
_isConcreteTarget(target) {
|
|
458
545
|
if (!target || target.kind === "typeParam" || isAny(target)) return false;
|
|
459
546
|
if (isNil(target) || isUntyped(target)) return false;
|
|
@@ -584,12 +671,7 @@ export const assignabilityMethods = {
|
|
|
584
671
|
_isNumericCoercible(target, source) {
|
|
585
672
|
if (target.kind !== "basic" || source.kind !== "basic") return false;
|
|
586
673
|
const pair = `${target.name}:${source.name}`;
|
|
587
|
-
return
|
|
588
|
-
pair === "float64:int" ||
|
|
589
|
-
// rune was historically int in GoFront; keep rune/int interchangeable.
|
|
590
|
-
pair === "int:int32" ||
|
|
591
|
-
pair === "int32:int"
|
|
592
|
-
);
|
|
674
|
+
return pair === "float64:int";
|
|
593
675
|
},
|
|
594
676
|
|
|
595
677
|
// The lenient numeric pairs: accepted, except that a generic call result
|