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
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
isAny,
|
|
25
25
|
isBool,
|
|
26
26
|
isNil,
|
|
27
|
+
isString,
|
|
27
28
|
isUntyped,
|
|
28
29
|
isVoid,
|
|
29
30
|
iteratorYieldParams,
|
|
@@ -33,6 +34,9 @@ import {
|
|
|
33
34
|
|
|
34
35
|
/** @typedef {import('./index.js').TypeChecker} TypeChecker */
|
|
35
36
|
|
|
37
|
+
// Tag kinds with no nil value: `case nil` against them cannot compile.
|
|
38
|
+
const NON_NILABLE_TAG_KINDS = new Set(["basic", "struct", "array"]);
|
|
39
|
+
|
|
36
40
|
/** @type {ThisType<TypeChecker>} */
|
|
37
41
|
export const statementCheckMethods = {
|
|
38
42
|
checkBlock(block, scope, returnType) {
|
|
@@ -79,11 +83,34 @@ export const statementCheckMethods = {
|
|
|
79
83
|
return true;
|
|
80
84
|
},
|
|
81
85
|
|
|
86
|
+
_assignOperand(stmt, i) {
|
|
87
|
+
if (stmt.rhs.length === stmt.lhs.length) return stmt.rhs[i];
|
|
88
|
+
return stmt.rhs.length === 1 ? stmt.rhs[0] : undefined;
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
_checkRedeclAssign(target, source, stmt, i) {
|
|
92
|
+
if (!target || !source || !this._needsAssignCheck(target, source)) return;
|
|
93
|
+
this._skipIfaceMark = true;
|
|
94
|
+
this.assertAssignable(
|
|
95
|
+
target,
|
|
96
|
+
source,
|
|
97
|
+
stmt.lhs[i],
|
|
98
|
+
"assignment",
|
|
99
|
+
this._assignOperand(stmt, i),
|
|
100
|
+
);
|
|
101
|
+
this._skipIfaceMark = false;
|
|
102
|
+
},
|
|
103
|
+
|
|
82
104
|
_declareDefineVars(stmt, rhsFlat, scope) {
|
|
83
105
|
for (let i = 0; i < stmt.lhs.length; i++) {
|
|
84
106
|
const name = stmt.lhs[i].name ?? stmt.lhs[i];
|
|
85
107
|
if (name === "_") continue;
|
|
86
|
-
if (scope.symbols.has(name))
|
|
108
|
+
if (scope.symbols.has(name)) {
|
|
109
|
+
stmt.lhs[i]._redecl = true;
|
|
110
|
+
// Redeclared variables keep their type; the value must be assignable.
|
|
111
|
+
this._checkRedeclAssign(scope.symbols.get(name), rhsFlat[i], stmt, i);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
87
114
|
if (stmt.rhs.length === stmt.lhs.length)
|
|
88
115
|
this._checkConstDefault(
|
|
89
116
|
rhsFlat[i],
|
|
@@ -195,10 +222,37 @@ export const statementCheckMethods = {
|
|
|
195
222
|
return this.checkExpr(e, scope);
|
|
196
223
|
},
|
|
197
224
|
|
|
225
|
+
// A source-written `any` operand still goes through assertAssignable so a
|
|
226
|
+
// concrete target reports `need type assertion`; interop `any` stays exempt.
|
|
227
|
+
_needsAssignCheck(target, source) {
|
|
228
|
+
return !isAny(target) && (!isAny(source) || !!source._declared);
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
// A range assignment (`for _, s = range xs`) stores each key and element
|
|
232
|
+
// into an existing variable; one of interface type boxes the value as
|
|
233
|
+
// any other conversion does. The box is recorded on a holder, not on the
|
|
234
|
+
// target, which codegen also reads as an assignable expression.
|
|
235
|
+
_markRangeAssignBox(stmt, i, target, source) {
|
|
236
|
+
if (stmt.rhs[0]?.kind !== "RangeExpr") return;
|
|
237
|
+
this._markRangeTargetBox(stmt.lhs[i], target, source);
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
_markRangeTargetBox(node, target, source) {
|
|
241
|
+
if (!target || !source) return;
|
|
242
|
+
const holder = {};
|
|
243
|
+
this._markIfaceBox(
|
|
244
|
+
this.resolveType(target),
|
|
245
|
+
this.resolveType(source),
|
|
246
|
+
holder,
|
|
247
|
+
);
|
|
248
|
+
if (holder._ifaceBox) node._rangeBox = holder;
|
|
249
|
+
},
|
|
250
|
+
|
|
198
251
|
_checkAssignTypes(stmt, rhsFlat, lhsTypes) {
|
|
199
252
|
for (let i = 0; i < lhsTypes.length; i++) {
|
|
200
253
|
if (stmt.lhs[i]?.kind === "Ident" && stmt.lhs[i]?.name === "_") continue;
|
|
201
254
|
const r = rhsFlat[i] ?? ANY;
|
|
255
|
+
this._markRangeAssignBox(stmt, i, lhsTypes[i], r);
|
|
202
256
|
if (stmt.rhs.length === lhsTypes.length && lhsTypes[i] && r)
|
|
203
257
|
this._markIfaceBox(
|
|
204
258
|
this.resolveType(lhsTypes[i]),
|
|
@@ -215,14 +269,14 @@ export const statementCheckMethods = {
|
|
|
215
269
|
this._checkConstOverflow(lhsTypes[i], r, stmt.rhs[i], "assignment")
|
|
216
270
|
)
|
|
217
271
|
continue;
|
|
218
|
-
if (
|
|
272
|
+
if (this._needsAssignCheck(lhsTypes[i], r)) {
|
|
219
273
|
this._skipIfaceMark = true;
|
|
220
274
|
this.assertAssignable(
|
|
221
275
|
lhsTypes[i],
|
|
222
276
|
r,
|
|
223
277
|
stmt.lhs[i],
|
|
224
278
|
"assignment",
|
|
225
|
-
stmt
|
|
279
|
+
this._assignOperand(stmt, i),
|
|
226
280
|
);
|
|
227
281
|
this._skipIfaceMark = false;
|
|
228
282
|
}
|
|
@@ -260,9 +314,34 @@ export const statementCheckMethods = {
|
|
|
260
314
|
}
|
|
261
315
|
}
|
|
262
316
|
const lhsTypes = stmt.lhs.map((e) => this._checkAssignLhsType(e, scope));
|
|
317
|
+
// A rejected range index is not checked again as an ordinary value.
|
|
318
|
+
if (this._checkRangeIndexAssign(stmt, lhsTypes))
|
|
319
|
+
rhsFlat = [ANY, ...rhsFlat.slice(1)];
|
|
263
320
|
this._checkAssignTypes(stmt, rhsFlat, lhsTypes);
|
|
264
321
|
},
|
|
265
322
|
|
|
323
|
+
// The index of a string, slice or array range is a typed `int`: Go assigns
|
|
324
|
+
// it only to `int` (or an interface), never to another integer type.
|
|
325
|
+
_checkRangeIndexAssign(stmt, lhsTypes) {
|
|
326
|
+
const range = stmt.rhs[0];
|
|
327
|
+
const lhs = stmt.lhs[0];
|
|
328
|
+
if (range?.kind !== "RangeExpr" || range._isIterator) return false;
|
|
329
|
+
if (!lhs || (lhs.kind === "Ident" && lhs.name === "_")) return false;
|
|
330
|
+
const target = lhsTypes[0];
|
|
331
|
+
if (!target || isAny(target) || typeStr(target) === "int") return false;
|
|
332
|
+
const coll = this.resolveType(range.expr._coreType ?? range.expr._type);
|
|
333
|
+
const cu =
|
|
334
|
+
coll?.kind === "named" ? this.resolveType(coll.underlying) : coll;
|
|
335
|
+
if (!isString(cu) && cu?.kind !== "slice" && cu?.kind !== "array")
|
|
336
|
+
return false;
|
|
337
|
+
const tu = this.resolveType(
|
|
338
|
+
target.kind === "named" ? target.underlying : target,
|
|
339
|
+
);
|
|
340
|
+
if (tu?.kind === "interface") return false;
|
|
341
|
+
this.err(`Cannot assign int to ${typeStr(target)}`, lhs);
|
|
342
|
+
return true;
|
|
343
|
+
},
|
|
344
|
+
|
|
266
345
|
_checkReturnStmt(stmt, scope, returnType) {
|
|
267
346
|
const types = stmt.values.map((v) => this.checkExpr(v, scope));
|
|
268
347
|
stmt._types = types;
|
|
@@ -287,14 +366,12 @@ export const statementCheckMethods = {
|
|
|
287
366
|
);
|
|
288
367
|
return;
|
|
289
368
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
);
|
|
297
|
-
}
|
|
369
|
+
this._checkForwardedValues(
|
|
370
|
+
expected,
|
|
371
|
+
forwarded,
|
|
372
|
+
stmt.values[0],
|
|
373
|
+
"return statement",
|
|
374
|
+
);
|
|
298
375
|
return;
|
|
299
376
|
}
|
|
300
377
|
for (let i = 0; i < expected.length; i++) {
|
|
@@ -309,6 +386,35 @@ export const statementCheckMethods = {
|
|
|
309
386
|
}
|
|
310
387
|
},
|
|
311
388
|
|
|
389
|
+
// Checks each result of a forwarded multi-value call (`return f()`,
|
|
390
|
+
// `g(f())`) against its target. Every element gets its own interface-box
|
|
391
|
+
// marks in `call._fwdIface` (null where nothing is boxed): tagging the
|
|
392
|
+
// shared call node would give every element the marks of the last one.
|
|
393
|
+
// Lowering splits such forwards into temps that carry these marks.
|
|
394
|
+
_checkForwardedValues(expected, forwarded, call, ctx) {
|
|
395
|
+
const skip = this._skipIfaceMark;
|
|
396
|
+
this._skipIfaceMark = true;
|
|
397
|
+
try {
|
|
398
|
+
for (let i = 0; i < expected.length; i++)
|
|
399
|
+
this.assertAssignable(expected[i], forwarded[i], call, ctx);
|
|
400
|
+
} finally {
|
|
401
|
+
this._skipIfaceMark = skip;
|
|
402
|
+
}
|
|
403
|
+
if (!skip) this._markForwardedElems(expected, forwarded, call);
|
|
404
|
+
},
|
|
405
|
+
|
|
406
|
+
// Records in `call._fwdIface` one interface-box mark holder per forwarded
|
|
407
|
+
// element (null where the element is not boxed), or clears it when no
|
|
408
|
+
// element enters an interface.
|
|
409
|
+
_markForwardedElems(expected, forwarded, call) {
|
|
410
|
+
const holders = expected.map((t, i) => {
|
|
411
|
+
const holder = {};
|
|
412
|
+
this._markIfaceBox(t, forwarded[i], holder);
|
|
413
|
+
return "_ifaceSrc" in holder ? holder : null;
|
|
414
|
+
});
|
|
415
|
+
call._fwdIface = holders.some(Boolean) ? holders : undefined;
|
|
416
|
+
},
|
|
417
|
+
|
|
312
418
|
_checkIfStmt(stmt, scope, returnType) {
|
|
313
419
|
const inner = new Scope(scope);
|
|
314
420
|
if (stmt.init) this.checkStmt(stmt.init, inner, returnType);
|
|
@@ -382,6 +488,7 @@ export const statementCheckMethods = {
|
|
|
382
488
|
for (const e of c.list) {
|
|
383
489
|
this.checkExpr(e, caseScope);
|
|
384
490
|
this._checkCaseValue(stmt.tag, e);
|
|
491
|
+
if (stmt.tag) this._tagFloat32Const(stmt.tag._type, e._type, e);
|
|
385
492
|
if (tagIs64 && e._type?.kind === "untyped") e._is64Bit = true;
|
|
386
493
|
}
|
|
387
494
|
}
|
|
@@ -402,9 +509,12 @@ export const statementCheckMethods = {
|
|
|
402
509
|
}
|
|
403
510
|
let tagType = this.resolveType(tag._type);
|
|
404
511
|
// `switch "foo" {` compares as the constant's default type.
|
|
405
|
-
if (isUntyped(tagType) && !isNil(tagType)
|
|
406
|
-
tagType = defaultType(tagType);
|
|
512
|
+
if (isUntyped(tagType) && !isNil(tagType)) tagType = defaultType(tagType);
|
|
407
513
|
if (this._caseValueUnchecked(tagType, caseType)) return;
|
|
514
|
+
if (isNil(caseType)) {
|
|
515
|
+
this.err(`cannot convert nil to type ${typeStr(tagType)}`, e);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
408
518
|
if (isUntyped(caseType)) {
|
|
409
519
|
if (this._isUntypedAssignable(tagType, caseType)) return;
|
|
410
520
|
this.err(
|
|
@@ -427,7 +537,11 @@ export const statementCheckMethods = {
|
|
|
427
537
|
// A tagless `switch` is `switch true`, so every case must be boolean.
|
|
428
538
|
_checkTaglessCase(e, caseType) {
|
|
429
539
|
if (!caseType || caseType._tainted || caseType.kind === "typeParam") return;
|
|
430
|
-
if (
|
|
540
|
+
if (isNil(caseType)) {
|
|
541
|
+
this.err(`cannot convert nil to type bool`, e);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
if (isAny(caseType) || isBool(caseType)) return;
|
|
431
545
|
if (isUntyped(caseType) && caseType.base === "bool") return;
|
|
432
546
|
const under = this.resolveType(caseType.underlying ?? caseType);
|
|
433
547
|
if (isBool(under)) return;
|
|
@@ -442,13 +556,22 @@ export const statementCheckMethods = {
|
|
|
442
556
|
_caseValueUnchecked(tagType, caseType) {
|
|
443
557
|
if (!tagType || !caseType || tagType._tainted || caseType._tainted)
|
|
444
558
|
return true;
|
|
445
|
-
if (isAny(tagType) || isAny(caseType)
|
|
559
|
+
if (isAny(tagType) || isAny(caseType)) return true;
|
|
446
560
|
if (isUntyped(tagType) || tagType.kind === "typeParam") return true;
|
|
561
|
+
if (isNil(caseType)) return this._nilCaseOk(tagType);
|
|
447
562
|
return (
|
|
448
563
|
this.resolveType(tagType.underlying ?? tagType)?.kind === "interface"
|
|
449
564
|
);
|
|
450
565
|
},
|
|
451
566
|
|
|
567
|
+
// `case nil` needs a tag with a nil value (pointer, slice, map, func,
|
|
568
|
+
// chan, interface); a basic, struct or array tag makes the caller report
|
|
569
|
+
// `cannot convert nil`.
|
|
570
|
+
_nilCaseOk(tagType) {
|
|
571
|
+
const kind = this._deepUnderlying(tagType)?.kind;
|
|
572
|
+
return !NON_NILABLE_TAG_KINDS.has(kind);
|
|
573
|
+
},
|
|
574
|
+
|
|
452
575
|
// Whether assertAssignable(target, source) reports nothing.
|
|
453
576
|
_assignableQuietly(target, source) {
|
|
454
577
|
const saved = this.errors;
|
|
@@ -509,7 +632,8 @@ export const statementCheckMethods = {
|
|
|
509
632
|
this._markDynamicType(t, this.resolveTypeNode(t, inner));
|
|
510
633
|
}
|
|
511
634
|
if (stmt.assign) {
|
|
512
|
-
// Bind the variable to the single case type, or
|
|
635
|
+
// Bind the variable to the single case type, or the switch operand's type
|
|
636
|
+
// for multi-type and default clauses, as Go does.
|
|
513
637
|
// `case nil` binds the switch expression's own type, as in Go.
|
|
514
638
|
const only = c.types?.length === 1 ? c.types[0] : null;
|
|
515
639
|
const isNilCase = only?.kind === "TypeName" && only.name === "nil";
|
|
@@ -517,7 +641,7 @@ export const statementCheckMethods = {
|
|
|
517
641
|
? (stmt.expr._type ?? ANY)
|
|
518
642
|
: only
|
|
519
643
|
? this.resolveTypeNode(only, inner)
|
|
520
|
-
: ANY;
|
|
644
|
+
: (stmt.expr._type ?? ANY);
|
|
521
645
|
caseScope.defineLocal(stmt.assign, bindType);
|
|
522
646
|
// The capture variable is always considered used — it's the whole
|
|
523
647
|
// point of the switch-with-assign form. Each case body uses its own
|
|
@@ -564,9 +688,35 @@ export const statementCheckMethods = {
|
|
|
564
688
|
}
|
|
565
689
|
},
|
|
566
690
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
691
|
+
// Each yielded value is stored into an existing variable; one of
|
|
692
|
+
// interface type boxes the value as any other conversion does.
|
|
693
|
+
_checkRangeIterAssignVars(lhs, scope, yieldParams = []) {
|
|
694
|
+
lhs.forEach((e, i) => {
|
|
695
|
+
if (e.kind === "Ident" && e.name === "_") return;
|
|
696
|
+
const target = this.checkExpr(e, scope);
|
|
697
|
+
this._markRangeTargetBox(e, target, yieldParams[i]);
|
|
698
|
+
this._checkRangeIterAssignable(e, target, yieldParams[i]);
|
|
699
|
+
});
|
|
700
|
+
},
|
|
701
|
+
|
|
702
|
+
// The yielded value is assigned to the target, so it must be assignable
|
|
703
|
+
// to it (`for s = range seq` with `s string` and `seq` yielding `int`).
|
|
704
|
+
// The lenient `float64 = int` pair of plain assignments is reported here:
|
|
705
|
+
// the iterator loop stores the yielded value without converting it.
|
|
706
|
+
_checkRangeIterAssignable(node, target, source) {
|
|
707
|
+
if (!target || !source) return;
|
|
708
|
+
const t = this.resolveType(target);
|
|
709
|
+
const v = this.resolveType(source);
|
|
710
|
+
if (this._isNumericCoercible(t, v)) {
|
|
711
|
+
this.err(`Cannot assign ${typeStr(v)} to ${typeStr(t)}`, node);
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
if (!this._needsAssignCheck(target, source)) return;
|
|
715
|
+
this._skipIfaceMark = true;
|
|
716
|
+
try {
|
|
717
|
+
this.assertAssignable(target, source, node, "assignment");
|
|
718
|
+
} finally {
|
|
719
|
+
this._skipIfaceMark = false;
|
|
570
720
|
}
|
|
571
721
|
},
|
|
572
722
|
|
|
@@ -592,7 +742,7 @@ export const statementCheckMethods = {
|
|
|
592
742
|
|
|
593
743
|
if (initStmt.kind === "DefineStmt")
|
|
594
744
|
this._bindRangeIterDefineVars(lhs, info, scope);
|
|
595
|
-
else this._checkRangeIterAssignVars(lhs, scope);
|
|
745
|
+
else this._checkRangeIterAssignVars(lhs, scope, info.yieldParams);
|
|
596
746
|
return info;
|
|
597
747
|
},
|
|
598
748
|
};
|
package/src/typechecker/types.js
CHANGED
|
@@ -323,6 +323,9 @@ export const UNTYPED_INT = { kind: "untyped", base: "int" };
|
|
|
323
323
|
export const UNTYPED_FLOAT = { kind: "untyped", base: "float64" };
|
|
324
324
|
export const UNTYPED_STRING = { kind: "untyped", base: "string" };
|
|
325
325
|
export const UNTYPED_BOOL = { kind: "untyped", base: "bool" };
|
|
326
|
+
// An untyped rune constant is an untyped integer whose default type is
|
|
327
|
+
// `rune`; every `base === "int"` rule applies to it unchanged.
|
|
328
|
+
export const UNTYPED_RUNE = { kind: "untyped", base: "int", rune: true };
|
|
326
329
|
|
|
327
330
|
// ── Complex types ────────────────────────────────────────────
|
|
328
331
|
export const COMPLEX128 = { kind: "basic", name: "complex128" };
|
|
@@ -530,7 +533,7 @@ export function defaultType(t) {
|
|
|
530
533
|
if (t?.kind !== "untyped") return t;
|
|
531
534
|
switch (t.base) {
|
|
532
535
|
case "int":
|
|
533
|
-
return INT;
|
|
536
|
+
return t.rune ? RUNE : INT;
|
|
534
537
|
case "float64":
|
|
535
538
|
return FLOAT64;
|
|
536
539
|
case "string":
|
|
@@ -551,7 +554,7 @@ export function typeStr(t) {
|
|
|
551
554
|
case "basic":
|
|
552
555
|
return t.name;
|
|
553
556
|
case "untyped":
|
|
554
|
-
return `untyped ${t.base}`;
|
|
557
|
+
return t.rune ? "untyped rune" : `untyped ${t.base}`;
|
|
555
558
|
case "slice":
|
|
556
559
|
return `[]${typeStr(t.elem)}`;
|
|
557
560
|
case "shared":
|
|
@@ -596,6 +599,27 @@ export function typeStr(t) {
|
|
|
596
599
|
|
|
597
600
|
// The field an embedded type declares is named after the type as written, so
|
|
598
601
|
// an alias (`type A = Base; struct{ A }`) gives the field `A`.
|
|
602
|
+
// Per struct type, the methods promoted into its method set from embedded
|
|
603
|
+
// fields: name -> { depth, viaPtr, source, mType }. `depth` counts embeds
|
|
604
|
+
// from the struct, `viaPtr` is set when an embed on the path is a pointer
|
|
605
|
+
// and `source` is the method as declared. Kept outside the struct object so
|
|
606
|
+
// a copy of the struct (`type S Base`) does not share it.
|
|
607
|
+
export const promotedMethods = new WeakMap();
|
|
608
|
+
|
|
609
|
+
// Two embedded fields at the same, shallowest depth declare method `name`,
|
|
610
|
+
// so it is in neither method set of `struct` and selecting it is an error.
|
|
611
|
+
export function isAmbiguousPromotion(struct, name) {
|
|
612
|
+
return promotedMethods.get(struct)?.get(name)?.ambiguous === true;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// The ambiguous method names of `struct` (see isAmbiguousPromotion).
|
|
616
|
+
export function ambiguousPromotions(struct) {
|
|
617
|
+
const names = [];
|
|
618
|
+
for (const [name, p] of promotedMethods.get(struct) ?? [])
|
|
619
|
+
if (p.ambiguous) names.push(name);
|
|
620
|
+
return names;
|
|
621
|
+
}
|
|
622
|
+
|
|
599
623
|
export function embeddedFieldName(f) {
|
|
600
624
|
const ast = f.astType?.kind === "PointerType" ? f.astType.base : f.astType;
|
|
601
625
|
const t = f.type?.kind === "pointer" ? f.type.base : f.type;
|
|
@@ -605,6 +629,64 @@ export function embeddedFieldName(f) {
|
|
|
605
629
|
.replace(/\$\$.*$/, "");
|
|
606
630
|
}
|
|
607
631
|
|
|
632
|
+
// Whether `st` declares `name` itself: a field of that name or an embedded
|
|
633
|
+
// field named after its type.
|
|
634
|
+
function declaresField(st, name) {
|
|
635
|
+
return (st._structFields ?? []).some((f) =>
|
|
636
|
+
f.embedded ? embeddedFieldName(f) === name : f.names?.includes(name),
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// The shallowest depth at which `struct` promotes a field `name` from its
|
|
641
|
+
// embedded types, and how many promotion paths reach it at that depth:
|
|
642
|
+
// { depth, count }, or null when no embedded type offers one.
|
|
643
|
+
function promotedFieldDepth(struct, name, path = new Set()) {
|
|
644
|
+
if (path.has(struct)) return null;
|
|
645
|
+
path.add(struct);
|
|
646
|
+
let best = null;
|
|
647
|
+
for (const e of struct._embeds ?? []) {
|
|
648
|
+
const inner = e?.kind === "pointer" ? e.base : e;
|
|
649
|
+
const st = inner?.kind === "named" ? inner.underlying : inner;
|
|
650
|
+
if (st?.kind !== "struct" || !st._structFields) continue;
|
|
651
|
+
const sub = declaresField(st, name)
|
|
652
|
+
? { depth: 0, count: 1 }
|
|
653
|
+
: promotedFieldDepth(st, name, path);
|
|
654
|
+
if (!sub) continue;
|
|
655
|
+
const depth = 1 + sub.depth;
|
|
656
|
+
if (!best || depth < best.depth) best = { depth, count: sub.count };
|
|
657
|
+
else if (depth === best.depth) best.count += sub.count;
|
|
658
|
+
}
|
|
659
|
+
path.delete(struct);
|
|
660
|
+
return best;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Whether selecting `name` on `struct`, whose flattened field map has it, is
|
|
664
|
+
// ambiguous: two promotion paths reach a field of that name at the same
|
|
665
|
+
// shallowest depth, or a field and a method promoted at that depth do. A
|
|
666
|
+
// field declared on the struct itself and a method declared on it always win.
|
|
667
|
+
export function isAmbiguousFieldSelector(struct, name) {
|
|
668
|
+
if (declaresField(struct, name)) return false;
|
|
669
|
+
const promoted = promotedMethods.get(struct)?.get(name);
|
|
670
|
+
if (struct.methods?.has(name) && promoted?.mType !== struct.methods.get(name))
|
|
671
|
+
return false;
|
|
672
|
+
const field = promotedFieldDepth(struct, name);
|
|
673
|
+
if (!field) return false;
|
|
674
|
+
if (!promoted || field.depth < promoted.depth) return field.count > 1;
|
|
675
|
+
return field.depth === promoted.depth || promoted.ambiguous === true;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Whether selecting `name` on `struct`, whose flattened field map has it,
|
|
679
|
+
// reaches a method promoted at a shallower depth than the field (Go's depth
|
|
680
|
+
// rule): the method is selected and the deeper field is shadowed.
|
|
681
|
+
export function isMethodPromotedOverField(struct, name) {
|
|
682
|
+
if (declaresField(struct, name)) return false;
|
|
683
|
+
const promoted = promotedMethods.get(struct)?.get(name);
|
|
684
|
+
if (!promoted?.mType || promoted.ambiguous) return false;
|
|
685
|
+
if (struct.methods?.get(name) !== promoted.mType) return false;
|
|
686
|
+
const field = promotedFieldDepth(struct, name);
|
|
687
|
+
return field !== null && promoted.depth < field.depth;
|
|
688
|
+
}
|
|
689
|
+
|
|
608
690
|
// A function-local type shadowed by another type is renamed `Name__L<n>`.
|
|
609
691
|
export const LOCAL_TYPE_SUFFIX = /__L\d+/;
|
|
610
692
|
|