gofront 1.6.1 → 1.7.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 +35 -39
- package/package.json +37 -4
- package/src/backend/js/expressions.js +323 -37
- package/src/backend/js/index.js +137 -49
- package/src/backend/js/runtime.js +570 -6
- package/src/backend/js/statements.js +36 -6
- package/src/backend/js/stdlib/errors.js +5 -0
- package/src/backend/js/stdlib/index.js +12 -0
- package/src/backend/js/stdlib/json.js +972 -0
- package/src/backend/js/stdlib/simd.js +24 -0
- package/src/backend/wasm/boundary.js +18 -0
- package/src/backend/wasm/emit-builtins.js +17 -0
- package/src/backend/wasm/emit-exprs.js +11 -0
- package/src/backend/wasm/emit.js +9 -0
- package/src/backend/wasm/encode.js +85 -0
- package/src/backend/wasm/optimize.js +4 -2
- package/src/backend/wasm/stdlib/index.js +3 -0
- package/src/backend/wasm/stdlib/simd.js +234 -0
- package/src/backend/wasm/types.js +21 -0
- package/src/backend/wasm/wat.js +37 -0
- package/src/dts-parser.js +71 -14
- package/src/lower/boxing.js +1 -6
- package/src/lower/embedding.js +2 -1
- package/src/parser/struct-tag.js +144 -0
- package/src/parser/types.js +63 -6
- package/src/typechecker/assignability.js +45 -0
- package/src/typechecker/expressions.js +66 -14
- package/src/typechecker/index.js +35 -8
- package/src/typechecker/resolve.js +151 -2
- package/src/typechecker/statements.js +68 -9
- package/src/typechecker/stdlib/extended.js +37 -0
- package/src/typechecker/stdlib/simd.js +73 -0
- package/src/typechecker/stdlib.js +2 -0
- package/src/typechecker/types.js +18 -3
- package/src/vendor.js +86 -11
package/src/dts-parser.js
CHANGED
|
@@ -203,9 +203,48 @@ export class DtsParser {
|
|
|
203
203
|
|
|
204
204
|
// ── Type parsing ─────────────────────────────────────────────
|
|
205
205
|
|
|
206
|
+
_parseTupleElement() {
|
|
207
|
+
this.skip();
|
|
208
|
+
if (this.startsWith("...")) this.pos += 3;
|
|
209
|
+
|
|
210
|
+
const savedPos = this.pos;
|
|
211
|
+
const ident = this.readIdent();
|
|
212
|
+
if (ident) {
|
|
213
|
+
this.skip();
|
|
214
|
+
if (this.ch() === "?") {
|
|
215
|
+
this.pos++;
|
|
216
|
+
this.skip();
|
|
217
|
+
}
|
|
218
|
+
if (this.ch() === ":") {
|
|
219
|
+
this.pos++;
|
|
220
|
+
return this.parseType(false);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
this.pos = savedPos;
|
|
224
|
+
return this.parseType(false);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
_parseTupleType(inReturn) {
|
|
228
|
+
this.consume("[");
|
|
229
|
+
const elements = [];
|
|
230
|
+
while (!this.eof() && this.peek() !== "]") {
|
|
231
|
+
this.skip();
|
|
232
|
+
if (this.ch() === "]") break;
|
|
233
|
+
elements.push(this._parseTupleElement());
|
|
234
|
+
this.skip();
|
|
235
|
+
if (this.ch() !== ",") break;
|
|
236
|
+
this.pos++;
|
|
237
|
+
}
|
|
238
|
+
this.consume("]");
|
|
239
|
+
if (inReturn) {
|
|
240
|
+
return { kind: "tuple", types: elements };
|
|
241
|
+
}
|
|
242
|
+
return { kind: "slice", elem: ANY };
|
|
243
|
+
}
|
|
244
|
+
|
|
206
245
|
// Parse a TypeScript type expression and return a GoFront type.
|
|
207
246
|
// Always returns something; falls back to ANY for complex types.
|
|
208
|
-
parseType() {
|
|
247
|
+
parseType(inReturn = false) {
|
|
209
248
|
this.skip();
|
|
210
249
|
|
|
211
250
|
// Skip modifiers
|
|
@@ -226,13 +265,16 @@ export class DtsParser {
|
|
|
226
265
|
this.skip();
|
|
227
266
|
if (this.ch() === "|" || this.ch() === "&") this.pos++;
|
|
228
267
|
|
|
229
|
-
let base = this._parseBaseType();
|
|
268
|
+
let base = this._parseBaseType(inReturn);
|
|
230
269
|
|
|
231
270
|
this.skip();
|
|
232
271
|
// Array suffix: T[] or T[][]
|
|
233
272
|
while (this.startsWith("[]")) {
|
|
234
273
|
this.pos += 2;
|
|
235
|
-
base = {
|
|
274
|
+
base = {
|
|
275
|
+
kind: "slice",
|
|
276
|
+
elem: base.kind === "tuple" ? { kind: "slice", elem: ANY } : base,
|
|
277
|
+
};
|
|
236
278
|
this.skip();
|
|
237
279
|
}
|
|
238
280
|
|
|
@@ -285,14 +327,11 @@ export class DtsParser {
|
|
|
285
327
|
return { kind: "basic", name: "any", alias: name };
|
|
286
328
|
}
|
|
287
329
|
|
|
288
|
-
_parseBaseType() {
|
|
330
|
+
_parseBaseType(inReturn = false) {
|
|
289
331
|
this.skip();
|
|
290
332
|
const c = this.ch();
|
|
291
333
|
if (c === "[") {
|
|
292
|
-
this.
|
|
293
|
-
this.skipTypeExpr("]");
|
|
294
|
-
this.consume("]");
|
|
295
|
-
return ANY;
|
|
334
|
+
return this._parseTupleType(inReturn);
|
|
296
335
|
}
|
|
297
336
|
if (c === "{") {
|
|
298
337
|
this.skipBlock();
|
|
@@ -399,15 +438,24 @@ export class DtsParser {
|
|
|
399
438
|
let ret = VOID;
|
|
400
439
|
if (this.ch() === ":") {
|
|
401
440
|
this.pos++;
|
|
402
|
-
ret = this.parseType();
|
|
441
|
+
ret = this.parseType(true);
|
|
403
442
|
}
|
|
404
443
|
this.consume(";");
|
|
444
|
+
const isVariadic = params.some((p) => p.variadic);
|
|
405
445
|
return {
|
|
406
446
|
name,
|
|
407
447
|
val: {
|
|
408
448
|
kind: "func",
|
|
409
|
-
params: params.map((p) =>
|
|
410
|
-
|
|
449
|
+
params: params.map((p) =>
|
|
450
|
+
p.variadic && p.type?.kind === "slice" ? p.type.elem : p.type,
|
|
451
|
+
),
|
|
452
|
+
returns:
|
|
453
|
+
ret.kind === "tuple"
|
|
454
|
+
? ret.types.length > 0
|
|
455
|
+
? ret.types
|
|
456
|
+
: [VOID]
|
|
457
|
+
: [ret],
|
|
458
|
+
...(isVariadic ? { variadic: true } : {}),
|
|
411
459
|
},
|
|
412
460
|
};
|
|
413
461
|
}
|
|
@@ -545,13 +593,22 @@ export class DtsParser {
|
|
|
545
593
|
let ret = VOID;
|
|
546
594
|
if (this.ch() === ":") {
|
|
547
595
|
this.pos++;
|
|
548
|
-
ret = this.parseType();
|
|
596
|
+
ret = this.parseType(true);
|
|
549
597
|
}
|
|
550
598
|
this.consume(";");
|
|
599
|
+
const isVariadic = params.some((p) => p.variadic);
|
|
551
600
|
out[name] = {
|
|
552
601
|
kind: "func",
|
|
553
|
-
params: params.map((p) =>
|
|
554
|
-
|
|
602
|
+
params: params.map((p) =>
|
|
603
|
+
p.variadic && p.type?.kind === "slice" ? p.type.elem : p.type,
|
|
604
|
+
),
|
|
605
|
+
returns:
|
|
606
|
+
ret.kind === "tuple"
|
|
607
|
+
? ret.types.length > 0
|
|
608
|
+
? ret.types
|
|
609
|
+
: [VOID]
|
|
610
|
+
: [ret],
|
|
611
|
+
...(isVariadic ? { variadic: true } : {}),
|
|
555
612
|
};
|
|
556
613
|
} else if (this.ch() === ":") {
|
|
557
614
|
this.pos++;
|
package/src/lower/boxing.js
CHANGED
|
@@ -6,12 +6,7 @@ import { forEachChild } from "./walk.js";
|
|
|
6
6
|
export function isReferenceType(t) {
|
|
7
7
|
if (!t) return false;
|
|
8
8
|
const base = t.kind === "named" ? t.underlying : t;
|
|
9
|
-
return
|
|
10
|
-
base?.kind === "struct" ||
|
|
11
|
-
base?.kind === "slice" ||
|
|
12
|
-
base?.kind === "map" ||
|
|
13
|
-
base?.kind === "func"
|
|
14
|
-
);
|
|
9
|
+
return base?.kind === "struct" || base?.kind === "func";
|
|
15
10
|
}
|
|
16
11
|
|
|
17
12
|
// Scan AST node for _addressTaken idents on scalars and populate a Set of boxed variable names.
|
package/src/lower/embedding.js
CHANGED
|
@@ -9,7 +9,8 @@ export function computeEmbeddedStubs(name, methodDecls, checker) {
|
|
|
9
9
|
const declared = new Set(methodDecls.map((m) => m.name));
|
|
10
10
|
const stubs = [];
|
|
11
11
|
|
|
12
|
-
for (const
|
|
12
|
+
for (const rawEmbed of resolvedType._embeds) {
|
|
13
|
+
const embed = rawEmbed.kind === "pointer" ? rawEmbed.base : rawEmbed;
|
|
13
14
|
const embedName = embed.kind === "named" ? embed.name : null;
|
|
14
15
|
if (!embedName) continue;
|
|
15
16
|
const embedBase = embed.kind === "named" ? embed.underlying : embed;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// src/parser/struct-tag.js
|
|
2
|
+
// Parse Go struct field tags matching Go's reflect.StructTag specification.
|
|
3
|
+
|
|
4
|
+
function unquoteTagValue(s) {
|
|
5
|
+
if (s.length < 2 || s[0] !== '"' || s[s.length - 1] !== '"') return s;
|
|
6
|
+
const inner = s.slice(1, -1);
|
|
7
|
+
let res = "";
|
|
8
|
+
for (let i = 0; i < inner.length; i++) {
|
|
9
|
+
if (inner[i] === "\\" && i + 1 < inner.length) {
|
|
10
|
+
i++;
|
|
11
|
+
const esc = inner[i];
|
|
12
|
+
switch (esc) {
|
|
13
|
+
case "n":
|
|
14
|
+
res += "\n";
|
|
15
|
+
break;
|
|
16
|
+
case "t":
|
|
17
|
+
res += "\t";
|
|
18
|
+
break;
|
|
19
|
+
case "r":
|
|
20
|
+
res += "\r";
|
|
21
|
+
break;
|
|
22
|
+
case '"':
|
|
23
|
+
res += '"';
|
|
24
|
+
break;
|
|
25
|
+
case "\\":
|
|
26
|
+
res += "\\";
|
|
27
|
+
break;
|
|
28
|
+
case "a":
|
|
29
|
+
res += "\x07";
|
|
30
|
+
break;
|
|
31
|
+
case "b":
|
|
32
|
+
res += "\b";
|
|
33
|
+
break;
|
|
34
|
+
case "f":
|
|
35
|
+
res += "\f";
|
|
36
|
+
break;
|
|
37
|
+
case "v":
|
|
38
|
+
res += "\v";
|
|
39
|
+
break;
|
|
40
|
+
case "0":
|
|
41
|
+
res += "\0";
|
|
42
|
+
break;
|
|
43
|
+
default:
|
|
44
|
+
res += `\\${esc}`;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
res += inner[i];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return res;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Parses a raw struct tag string into a Map of key -> unquoted string value,
|
|
56
|
+
* adhering to Go reflect.StructTag convention.
|
|
57
|
+
*
|
|
58
|
+
* @param {string | null | undefined} tag
|
|
59
|
+
* @returns {Map<string, string>}
|
|
60
|
+
*/
|
|
61
|
+
export function parseStructTag(tag) {
|
|
62
|
+
const map = new Map();
|
|
63
|
+
if (!tag || typeof tag !== "string") return map;
|
|
64
|
+
|
|
65
|
+
let str = tag;
|
|
66
|
+
while (str.length > 0) {
|
|
67
|
+
// Skip leading spaces
|
|
68
|
+
let i = 0;
|
|
69
|
+
while (i < str.length && str.charCodeAt(i) === 32) i++;
|
|
70
|
+
str = str.slice(i);
|
|
71
|
+
if (str.length === 0) break;
|
|
72
|
+
|
|
73
|
+
// Scan to colon: key must not contain space, quote, colon, or control characters
|
|
74
|
+
i = 0;
|
|
75
|
+
while (
|
|
76
|
+
i < str.length &&
|
|
77
|
+
str.charCodeAt(i) > 32 &&
|
|
78
|
+
str[i] !== ":" &&
|
|
79
|
+
str[i] !== '"' &&
|
|
80
|
+
str.charCodeAt(i) !== 127
|
|
81
|
+
) {
|
|
82
|
+
i++;
|
|
83
|
+
}
|
|
84
|
+
if (
|
|
85
|
+
i === 0 ||
|
|
86
|
+
i + 1 >= str.length ||
|
|
87
|
+
str[i] !== ":" ||
|
|
88
|
+
str[i + 1] !== '"'
|
|
89
|
+
) {
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
const key = str.slice(0, i);
|
|
93
|
+
str = str.slice(i + 1); // skip colon, now points to opening '"'
|
|
94
|
+
|
|
95
|
+
// Scan quoted string to find closing '"'
|
|
96
|
+
i = 1;
|
|
97
|
+
while (i < str.length && str[i] !== '"') {
|
|
98
|
+
if (str[i] === "\\") i++; // skip escaped char
|
|
99
|
+
i++;
|
|
100
|
+
}
|
|
101
|
+
if (i >= str.length) break;
|
|
102
|
+
|
|
103
|
+
const qval = str.slice(0, i + 1);
|
|
104
|
+
str = str.slice(i + 1);
|
|
105
|
+
|
|
106
|
+
if (!map.has(key)) {
|
|
107
|
+
map.set(key, unquoteTagValue(qval));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return map;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Parses the "json" key from a struct tag string or parsed Map according to encoding/json rules.
|
|
115
|
+
*
|
|
116
|
+
* @param {string | Map<string, string> | null | undefined} tag
|
|
117
|
+
* @returns {{ name: string | null, omitempty: boolean, ignored: boolean, options: string[] }}
|
|
118
|
+
*/
|
|
119
|
+
export function parseJsonTag(tag) {
|
|
120
|
+
if (!tag) {
|
|
121
|
+
return { name: null, omitempty: false, ignored: false, options: [] };
|
|
122
|
+
}
|
|
123
|
+
const map = typeof tag === "string" ? parseStructTag(tag) : tag;
|
|
124
|
+
if (!map.has("json")) {
|
|
125
|
+
return { name: null, omitempty: false, ignored: false, options: [] };
|
|
126
|
+
}
|
|
127
|
+
const raw = map.get("json");
|
|
128
|
+
const parts = raw.split(",");
|
|
129
|
+
const rawName = parts[0];
|
|
130
|
+
const options = parts.slice(1);
|
|
131
|
+
const omitempty = options.includes("omitempty");
|
|
132
|
+
|
|
133
|
+
if (rawName === "-" && parts.length === 1) {
|
|
134
|
+
return { name: "-", omitempty: false, ignored: true, options };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const name = rawName === "" ? null : rawName;
|
|
138
|
+
return {
|
|
139
|
+
name,
|
|
140
|
+
omitempty,
|
|
141
|
+
ignored: false,
|
|
142
|
+
options,
|
|
143
|
+
};
|
|
144
|
+
}
|
package/src/parser/types.js
CHANGED
|
@@ -175,23 +175,80 @@ export const typeParserMethods = {
|
|
|
175
175
|
this.expect(T.LBRACE);
|
|
176
176
|
const fields = [];
|
|
177
177
|
while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
|
|
178
|
+
// Embedded pointer field: *Base or *pkg.Base
|
|
179
|
+
if (this.check(T.STAR)) {
|
|
180
|
+
this.advance();
|
|
181
|
+
let typeName = this.expect(T.IDENT).value;
|
|
182
|
+
if (this.check(T.DOT)) {
|
|
183
|
+
this.advance();
|
|
184
|
+
typeName += `.${this.expect(T.IDENT).value}`;
|
|
185
|
+
}
|
|
186
|
+
const type = {
|
|
187
|
+
kind: "PointerType",
|
|
188
|
+
base: { kind: "TypeName", name: typeName },
|
|
189
|
+
};
|
|
190
|
+
let tag = null;
|
|
191
|
+
if (this.check(T.STRING)) {
|
|
192
|
+
tag = this.advance().value;
|
|
193
|
+
}
|
|
194
|
+
fields.push({
|
|
195
|
+
names: [],
|
|
196
|
+
type,
|
|
197
|
+
embedded: true,
|
|
198
|
+
tag,
|
|
199
|
+
});
|
|
200
|
+
this.semi();
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
178
204
|
const names = this.parseIdentList();
|
|
179
|
-
// Check if this is
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const typeName = names[0]
|
|
205
|
+
// Check if this is a qualified embedded field: pkg.Base
|
|
206
|
+
if (names.length === 1 && this.check(T.DOT)) {
|
|
207
|
+
this.advance();
|
|
208
|
+
const typeName = `${names[0]}.${this.expect(T.IDENT).value}`;
|
|
209
|
+
let tag = null;
|
|
210
|
+
if (this.check(T.STRING)) {
|
|
211
|
+
tag = this.advance().value;
|
|
212
|
+
}
|
|
183
213
|
fields.push({
|
|
184
214
|
names: [],
|
|
185
215
|
type: { kind: "TypeName", name: typeName },
|
|
186
216
|
embedded: true,
|
|
217
|
+
tag,
|
|
187
218
|
});
|
|
188
219
|
this.semi();
|
|
189
220
|
continue;
|
|
190
221
|
}
|
|
222
|
+
|
|
223
|
+
// Embedded field (just a type name, e.g. Base), optionally tagged
|
|
224
|
+
if (
|
|
225
|
+
names.length === 1 &&
|
|
226
|
+
(this.check(T.SEMICOLON) ||
|
|
227
|
+
this.check(T.RBRACE) ||
|
|
228
|
+
this.check(T.STRING))
|
|
229
|
+
) {
|
|
230
|
+
const typeName = names[0];
|
|
231
|
+
let tag = null;
|
|
232
|
+
if (this.check(T.STRING)) {
|
|
233
|
+
tag = this.advance().value;
|
|
234
|
+
}
|
|
235
|
+
fields.push({
|
|
236
|
+
names: [],
|
|
237
|
+
type: { kind: "TypeName", name: typeName },
|
|
238
|
+
embedded: true,
|
|
239
|
+
tag,
|
|
240
|
+
});
|
|
241
|
+
this.semi();
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
|
|
191
245
|
const type = this.parseType();
|
|
192
246
|
// Consume optional struct tag (backtick string), e.g. `json:"name"`
|
|
193
|
-
|
|
194
|
-
|
|
247
|
+
let tag = null;
|
|
248
|
+
if (this.check(T.STRING)) {
|
|
249
|
+
tag = this.advance().value;
|
|
250
|
+
}
|
|
251
|
+
fields.push({ names, type, tag });
|
|
195
252
|
this.semi();
|
|
196
253
|
}
|
|
197
254
|
this.expect(T.RBRACE);
|
|
@@ -27,6 +27,16 @@ import {
|
|
|
27
27
|
UNTYPED_INT,
|
|
28
28
|
} from "./types.js";
|
|
29
29
|
|
|
30
|
+
// Unnamed type literal kinds for Go's "identical underlying types" assignability rule.
|
|
31
|
+
const TYPE_LITERAL_KINDS = new Set([
|
|
32
|
+
"slice",
|
|
33
|
+
"map",
|
|
34
|
+
"func",
|
|
35
|
+
"pointer",
|
|
36
|
+
"array",
|
|
37
|
+
"struct",
|
|
38
|
+
]);
|
|
39
|
+
|
|
30
40
|
/** @typedef {import('./index.js').TypeChecker} TypeChecker */
|
|
31
41
|
|
|
32
42
|
// Untyped constant assignability: maps source.base → Set of compatible target.name values
|
|
@@ -195,6 +205,12 @@ export const assignabilityMethods = {
|
|
|
195
205
|
target.kind === "named" ? target.underlying : target,
|
|
196
206
|
);
|
|
197
207
|
if (!isAny(target) && tBase?.kind !== "interface") return;
|
|
208
|
+
const named = this._namedMethodBoxSource(source);
|
|
209
|
+
if (named) {
|
|
210
|
+
node._ifaceBox = named.ptr ? "named_ptr" : "named";
|
|
211
|
+
node._ifaceNamedType = named.type;
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
198
214
|
const sBase = this.resolveType(
|
|
199
215
|
source.kind === "named" ? source.underlying : source,
|
|
200
216
|
);
|
|
@@ -204,6 +220,24 @@ export const assignabilityMethods = {
|
|
|
204
220
|
if (info.ptrFields) node._ifacePtrFields = info.ptrFields;
|
|
205
221
|
},
|
|
206
222
|
|
|
223
|
+
// A named non-struct type with methods (or a pointer to one) entering an
|
|
224
|
+
// interface: the JS backend keeps such values raw and boxes them here so
|
|
225
|
+
// dynamic method dispatch works.
|
|
226
|
+
_namedMethodBoxSource(source) {
|
|
227
|
+
const isPtr = source?.kind === "pointer";
|
|
228
|
+
const t = isPtr ? source.base : source;
|
|
229
|
+
if (t?.kind !== "named") return null;
|
|
230
|
+
const u = this.resolveType(t.underlying);
|
|
231
|
+
if (!u || u.kind === "struct" || u.kind === "interface") return null;
|
|
232
|
+
const methods = t.methods ?? t._generic?.methods;
|
|
233
|
+
if (!methods || methods.size === 0) return null;
|
|
234
|
+
// Only user-declared types (methods carry _ptrRecv); stdlib named types
|
|
235
|
+
// such as time.Duration have no box class.
|
|
236
|
+
if (![...methods.values()].some((m) => Object.hasOwn(m ?? {}, "_ptrRecv")))
|
|
237
|
+
return null;
|
|
238
|
+
return { type: t, ptr: isPtr };
|
|
239
|
+
},
|
|
240
|
+
|
|
207
241
|
_getStructPtrFields(st, prefix = "", seen = new Set()) {
|
|
208
242
|
if (st?.kind !== "struct" || !st?.fields) return [];
|
|
209
243
|
if (seen.has(st)) return [];
|
|
@@ -316,9 +350,20 @@ export const assignabilityMethods = {
|
|
|
316
350
|
);
|
|
317
351
|
return;
|
|
318
352
|
}
|
|
353
|
+
if (this._identicalUnderlyingLiteral(target, source, tBase, sBase)) return;
|
|
319
354
|
this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
|
|
320
355
|
},
|
|
321
356
|
|
|
357
|
+
// Identical underlying types are assignable when one side is an unnamed
|
|
358
|
+
// type literal (`var n Names = []string{...}`), as in Go.
|
|
359
|
+
_identicalUnderlyingLiteral(target, source, tBase, sBase) {
|
|
360
|
+
if ((target.kind === "named") === (source.kind === "named")) return false;
|
|
361
|
+
const unnamed = target.kind === "named" ? source : target;
|
|
362
|
+
return (
|
|
363
|
+
TYPE_LITERAL_KINDS.has(unnamed.kind) && typeStr(tBase) === typeStr(sBase)
|
|
364
|
+
);
|
|
365
|
+
},
|
|
366
|
+
|
|
322
367
|
_isUntypedAssignable(target, source) {
|
|
323
368
|
if (isUntyped(target)) return true;
|
|
324
369
|
// Named types with a basic underlying type (`type Mode string`) accept
|
|
@@ -228,6 +228,7 @@ export const expressionCheckMethods = {
|
|
|
228
228
|
_isStructOrNamedMethod(baseType, base, field) {
|
|
229
229
|
return (
|
|
230
230
|
(base?.kind === "struct" && base.methods?.has(field)) ||
|
|
231
|
+
(base?.kind === "simd" && base.methods?.has(field)) ||
|
|
231
232
|
(baseType.kind === "named" && baseType.methods?.has(field))
|
|
232
233
|
);
|
|
233
234
|
},
|
|
@@ -513,6 +514,8 @@ export const expressionCheckMethods = {
|
|
|
513
514
|
}
|
|
514
515
|
if (typeStr(srcResolved) === typeStr(tgtResolved)) return;
|
|
515
516
|
if (isNumeric(srcResolved) && isNumeric(tgtResolved)) return;
|
|
517
|
+
if (isString(srcResolved) && isString(tgtResolved)) return;
|
|
518
|
+
if (isBool(srcResolved) && isBool(tgtResolved)) return;
|
|
516
519
|
if (
|
|
517
520
|
isString(srcResolved) &&
|
|
518
521
|
tgtResolved?.kind === "slice" &&
|
|
@@ -649,7 +652,8 @@ export const expressionCheckMethods = {
|
|
|
649
652
|
return expr;
|
|
650
653
|
},
|
|
651
654
|
|
|
652
|
-
_constIntValue(expr) {
|
|
655
|
+
_constIntValue(expr, scope = null) {
|
|
656
|
+
if (!expr) return null;
|
|
653
657
|
if (expr.kind === "BasicLit" && expr.litKind === "INT")
|
|
654
658
|
return Number(expr.value);
|
|
655
659
|
if (
|
|
@@ -660,6 +664,19 @@ export const expressionCheckMethods = {
|
|
|
660
664
|
) {
|
|
661
665
|
return -Number(expr.operand.value);
|
|
662
666
|
}
|
|
667
|
+
if (expr.kind === "UnaryExpr" && expr.op === "+") {
|
|
668
|
+
return this._constIntValue(expr.operand, scope);
|
|
669
|
+
}
|
|
670
|
+
if (expr.kind === "Ident") {
|
|
671
|
+
const c =
|
|
672
|
+
scope?.lookupConstExpr?.(expr.name) ??
|
|
673
|
+
this.globals.lookupConstExpr?.(expr.name) ??
|
|
674
|
+
this._constExprs?.get(expr.name);
|
|
675
|
+
if (c) return this._constIntValue(c, scope);
|
|
676
|
+
}
|
|
677
|
+
if (expr.kind === "ParenExpr") {
|
|
678
|
+
return this._constIntValue(expr.expr, scope);
|
|
679
|
+
}
|
|
663
680
|
return null;
|
|
664
681
|
},
|
|
665
682
|
|
|
@@ -731,7 +748,7 @@ export const expressionCheckMethods = {
|
|
|
731
748
|
return this.err(`Cannot call non-function type ${typeStr(fnType)}`, expr);
|
|
732
749
|
}
|
|
733
750
|
|
|
734
|
-
this._checkCallArgs(fnType, argTypes, expr);
|
|
751
|
+
this._checkCallArgs(fnType, argTypes, expr, scope);
|
|
735
752
|
|
|
736
753
|
// Pseudo-generic stdlib helpers (maps.Keys, slices.Clone, ...) declare
|
|
737
754
|
// how their result derives from the first argument.
|
|
@@ -848,7 +865,7 @@ export const expressionCheckMethods = {
|
|
|
848
865
|
);
|
|
849
866
|
},
|
|
850
867
|
|
|
851
|
-
_checkCallArgs(fnType, argTypes, expr) {
|
|
868
|
+
_checkCallArgs(fnType, argTypes, expr, scope = null) {
|
|
852
869
|
this._checkArgCount(fnType, argTypes, expr);
|
|
853
870
|
const maxLen = fnType.variadic
|
|
854
871
|
? argTypes.length
|
|
@@ -863,6 +880,30 @@ export const expressionCheckMethods = {
|
|
|
863
880
|
const node = expr._multiForward ? null : expr.args[i];
|
|
864
881
|
this.assertAssignable(targetParam, argTypes[i], node);
|
|
865
882
|
}
|
|
883
|
+
|
|
884
|
+
// Validate Lane and WithLane constant integer lane index
|
|
885
|
+
if (expr.func?.kind === "SelectorExpr") {
|
|
886
|
+
const recvType = expr.func.expr?._type;
|
|
887
|
+
const isSimd =
|
|
888
|
+
recvType?.name === "simd.F32x4" ||
|
|
889
|
+
recvType?.name === "F32x4" ||
|
|
890
|
+
recvType?.underlying?.kind === "simd";
|
|
891
|
+
if (
|
|
892
|
+
isSimd &&
|
|
893
|
+
(expr.func.field === "Lane" || expr.func.field === "WithLane")
|
|
894
|
+
) {
|
|
895
|
+
const laneArg = expr.args[0];
|
|
896
|
+
if (laneArg) {
|
|
897
|
+
const idx = this._constIntValue(laneArg, scope);
|
|
898
|
+
if (idx === null || idx < 0 || idx > 3) {
|
|
899
|
+
this.err(
|
|
900
|
+
"lane index must be a constant integer in range [0, 3]",
|
|
901
|
+
laneArg,
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
866
907
|
},
|
|
867
908
|
|
|
868
909
|
checkBuiltin(name, expr, argTypes, scope) {
|
|
@@ -959,6 +1000,8 @@ export const expressionCheckMethods = {
|
|
|
959
1000
|
this._inferArraySize(expr, typeNode, base);
|
|
960
1001
|
if (base?.kind === "struct")
|
|
961
1002
|
return this._checkStructCompositeLit(expr, scope, t, base);
|
|
1003
|
+
if (base?.kind === "simd")
|
|
1004
|
+
return this._checkSimdCompositeLit(expr, scope, t, base);
|
|
962
1005
|
if (this._isSliceOrArray(base))
|
|
963
1006
|
return this._checkSliceArrayCompositeLit(expr, scope, t, base, typeNode);
|
|
964
1007
|
if (base?.kind === "map")
|
|
@@ -966,6 +1009,17 @@ export const expressionCheckMethods = {
|
|
|
966
1009
|
return this._checkUnknownCompositeLit(expr, scope, t);
|
|
967
1010
|
},
|
|
968
1011
|
|
|
1012
|
+
_checkSimdCompositeLit(expr, scope, t, base) {
|
|
1013
|
+
if (expr.elems.length > 4) {
|
|
1014
|
+
this.err("too many values in simd.F32x4 literal", expr);
|
|
1015
|
+
}
|
|
1016
|
+
for (const elem of expr.elems) {
|
|
1017
|
+
const vt = this.checkExpr(elem, scope);
|
|
1018
|
+
this.assertAssignable(base.elem ?? FLOAT32, vt, elem);
|
|
1019
|
+
}
|
|
1020
|
+
return t;
|
|
1021
|
+
},
|
|
1022
|
+
|
|
969
1023
|
_checkStructPositionalElems(expr, scope, t, base) {
|
|
970
1024
|
const fieldNames = [...(base.fields?.keys() ?? [])];
|
|
971
1025
|
for (let i = 0; i < expr.elems.length; i++) {
|
|
@@ -987,21 +1041,19 @@ export const expressionCheckMethods = {
|
|
|
987
1041
|
for (const elem of expr.elems) {
|
|
988
1042
|
if (elem.kind === "KeyValueExpr") {
|
|
989
1043
|
const keyName = elem.key.name;
|
|
990
|
-
const
|
|
1044
|
+
const embed = base._embeds?.find((e) => {
|
|
1045
|
+
const inner = e.kind === "pointer" ? e.base : e;
|
|
1046
|
+
const name =
|
|
1047
|
+
inner.kind === "named" ? inner.name.split(".").pop() : null;
|
|
1048
|
+
return name === keyName;
|
|
1049
|
+
});
|
|
1050
|
+
if (embed) elem._isEmbedInit = true;
|
|
1051
|
+
const fieldType = base.fields?.get(keyName) ?? embed;
|
|
991
1052
|
if (fieldType) {
|
|
992
1053
|
const vt = this.checkExpr(elem.value, scope);
|
|
993
1054
|
this.assertAssignable(fieldType, vt, elem.value);
|
|
994
1055
|
} else {
|
|
995
|
-
|
|
996
|
-
(e) => (e.kind === "named" ? e.name : null) === keyName,
|
|
997
|
-
);
|
|
998
|
-
if (embed) {
|
|
999
|
-
const vt = this.checkExpr(elem.value, scope);
|
|
1000
|
-
this.assertAssignable(embed, vt, elem.value);
|
|
1001
|
-
elem._isEmbedInit = true;
|
|
1002
|
-
} else {
|
|
1003
|
-
this.err(`Unknown field '${keyName}'`, elem.key);
|
|
1004
|
-
}
|
|
1056
|
+
this.err(`Unknown field '${keyName}'`, elem.key);
|
|
1005
1057
|
}
|
|
1006
1058
|
} else {
|
|
1007
1059
|
this.checkExpr(elem, scope);
|