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
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { parseJsonTag } from "../parser/struct-tag.js";
|
|
5
5
|
import { foldConstExpr } from "./const-fold.js";
|
|
6
|
+
import { exprText, operandKind } from "./operand-text.js";
|
|
6
7
|
import { EXCLUDED_STDLIB_MEMBERS } from "./stdlib/excluded.js";
|
|
7
8
|
import {
|
|
8
9
|
ANY,
|
|
@@ -11,7 +12,10 @@ import {
|
|
|
11
12
|
DECLARED_ANY,
|
|
12
13
|
defaultType,
|
|
13
14
|
displayTypeName,
|
|
15
|
+
isAmbiguousFieldSelector,
|
|
16
|
+
isAmbiguousPromotion,
|
|
14
17
|
isAny,
|
|
18
|
+
isMethodPromotedOverField,
|
|
15
19
|
isTypedArraySlice,
|
|
16
20
|
Scope,
|
|
17
21
|
TAINTED_ANY,
|
|
@@ -28,6 +32,11 @@ import {
|
|
|
28
32
|
// instantiation is reported as a cycle.
|
|
29
33
|
const MAX_INSTANTIATION_DEPTH = 32;
|
|
30
34
|
|
|
35
|
+
// Type nodes in a constraint position, where a constraint interface may
|
|
36
|
+
// appear. A property of the AST node, so it is shared by every checker: an
|
|
37
|
+
// importing package resolves the constraints of the imported generics.
|
|
38
|
+
const CONSTRAINT_POS = new WeakSet();
|
|
39
|
+
|
|
31
40
|
// Two imported packages may both declare `Pt`, so the cache key of an
|
|
32
41
|
// instance tells declarations apart by identity instead of by display name.
|
|
33
42
|
const declIds = new WeakMap();
|
|
@@ -215,7 +224,8 @@ export const resolveMethods = {
|
|
|
215
224
|
case "TypeName": {
|
|
216
225
|
if (BASIC_TYPES[node.name])
|
|
217
226
|
return node.name === "any" ? DECLARED_ANY : BASIC_TYPES[node.name];
|
|
218
|
-
if (node.name === "comparable")
|
|
227
|
+
if (node.name === "comparable")
|
|
228
|
+
return this._checkConstraintUse(COMPARABLE, node);
|
|
219
229
|
if (scope) {
|
|
220
230
|
const fromScope = scope.lookup(node.name);
|
|
221
231
|
if (fromScope?.kind === "typeParam") return fromScope;
|
|
@@ -224,7 +234,7 @@ export const resolveMethods = {
|
|
|
224
234
|
const named = this.types.get(node.name);
|
|
225
235
|
if (named) {
|
|
226
236
|
this._useQualifiedTypeName(node, named);
|
|
227
|
-
return named;
|
|
237
|
+
return this._checkConstraintUse(named, node);
|
|
228
238
|
}
|
|
229
239
|
const jsOnly = this._jsOnlyMember(node.name);
|
|
230
240
|
if (jsOnly) return this._errJsOnly(jsOnly, node);
|
|
@@ -241,6 +251,7 @@ export const resolveMethods = {
|
|
|
241
251
|
case "IndexExpr":
|
|
242
252
|
return this._resolveGenericTypeName(node, scope);
|
|
243
253
|
case "TypeParam": {
|
|
254
|
+
this._markConstraintPos(node.constraint);
|
|
244
255
|
const constraint = node.constraint
|
|
245
256
|
? this.resolveTypeNode(node.constraint, scope)
|
|
246
257
|
: ANY;
|
|
@@ -264,7 +275,10 @@ export const resolveMethods = {
|
|
|
264
275
|
case "StructType":
|
|
265
276
|
return this._resolveStructType(node, scope);
|
|
266
277
|
case "InterfaceType":
|
|
267
|
-
return this.
|
|
278
|
+
return this._checkConstraintUse(
|
|
279
|
+
this._resolveInterfaceType(node, scope),
|
|
280
|
+
node,
|
|
281
|
+
);
|
|
268
282
|
case "UnaryExpr":
|
|
269
283
|
// `new(*T)`: the parser reads the pointer type as a dereference.
|
|
270
284
|
if (node.op !== "*") return ANY;
|
|
@@ -283,10 +297,9 @@ export const resolveMethods = {
|
|
|
283
297
|
if (node.expr?.kind === "Ident" && typeof node.field === "string") {
|
|
284
298
|
const qualified = `${node.expr.name}.${node.field}`;
|
|
285
299
|
if (this.types.has(qualified)) {
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
);
|
|
300
|
+
const inner = { kind: "TypeName", name: qualified };
|
|
301
|
+
this._inheritConstraintPos(node, inner);
|
|
302
|
+
const resolved = this.resolveTypeNode(inner, scope);
|
|
290
303
|
this._useQualifiedTypeName(node, resolved);
|
|
291
304
|
return resolved;
|
|
292
305
|
}
|
|
@@ -306,6 +319,95 @@ export const resolveMethods = {
|
|
|
306
319
|
}
|
|
307
320
|
},
|
|
308
321
|
|
|
322
|
+
// ── Constraint interfaces outside constraints ───────────────────
|
|
323
|
+
|
|
324
|
+
// Records a type node that sits in a constraint position (a type
|
|
325
|
+
// parameter's constraint, an interface's embedded element or union term,
|
|
326
|
+
// the right-hand side of a type declaration), where a constraint
|
|
327
|
+
// interface is allowed.
|
|
328
|
+
_markConstraintPos(node) {
|
|
329
|
+
if (!node || typeof node !== "object") return;
|
|
330
|
+
if (node.kind === "UnionConstraint") {
|
|
331
|
+
for (const term of node.terms) this._markConstraintPos(term.type);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
CONSTRAINT_POS.add(node);
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
// `inner` stands for `node` (a rewritten qualified name).
|
|
338
|
+
_inheritConstraintPos(node, inner) {
|
|
339
|
+
if (CONSTRAINT_POS.has(node)) CONSTRAINT_POS.add(inner);
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
// An interface's embedded elements and union terms are constraint positions.
|
|
343
|
+
_markInterfaceElements(node) {
|
|
344
|
+
for (const embed of node.embeds ?? []) this._markConstraintPos(embed);
|
|
345
|
+
this._markConstraintPos(node.unionConstraint);
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
// Marks the constraint positions of top-level declarations before any
|
|
349
|
+
// type resolves (constraints resolve lazily from several places).
|
|
350
|
+
_markConstraintPositions(programs) {
|
|
351
|
+
for (const p of programs) {
|
|
352
|
+
for (const d of p.decls) {
|
|
353
|
+
if (d.kind === "TypeDecl") this._markConstraintPos(d.type);
|
|
354
|
+
for (const tp of d.typeParams ?? [])
|
|
355
|
+
this._markConstraintPos(tp.constraint);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
|
|
360
|
+
// A constraint interface (one with a type set, or comparable) resolved
|
|
361
|
+
// outside a constraint position is an error. A type whose declaration has
|
|
362
|
+
// not resolved yet is judged once every package-level type has.
|
|
363
|
+
_checkConstraintUse(type, node) {
|
|
364
|
+
if (CONSTRAINT_POS.has(node)) return type;
|
|
365
|
+
if (type?.kind === "named" && type.underlying == null) {
|
|
366
|
+
this._pendingConstraintUses.push({
|
|
367
|
+
type,
|
|
368
|
+
node,
|
|
369
|
+
file: this._currentFile,
|
|
370
|
+
source: this._currentSource,
|
|
371
|
+
});
|
|
372
|
+
return type;
|
|
373
|
+
}
|
|
374
|
+
this._reportConstraintUse(type, node);
|
|
375
|
+
return type;
|
|
376
|
+
},
|
|
377
|
+
|
|
378
|
+
_reportConstraintUse(type, node) {
|
|
379
|
+
const u = this._deepUnderlying(type);
|
|
380
|
+
if (u === COMPARABLE || (u?.kind === "interface" && u._comparable)) {
|
|
381
|
+
this.err(
|
|
382
|
+
`cannot use type ${typeStr(type)} outside a type constraint: interface is (or embeds) comparable`,
|
|
383
|
+
node,
|
|
384
|
+
);
|
|
385
|
+
} else if (
|
|
386
|
+
u?.kind === "interface" &&
|
|
387
|
+
(u.unionConstraint || u._typeSetEmbeds)
|
|
388
|
+
) {
|
|
389
|
+
this.err(
|
|
390
|
+
`cannot use type ${typeStr(type)} outside a type constraint: interface contains type constraints`,
|
|
391
|
+
node,
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
|
|
396
|
+
_flushConstraintUses() {
|
|
397
|
+
const pending = this._pendingConstraintUses;
|
|
398
|
+
if (pending.length === 0) return;
|
|
399
|
+
this._pendingConstraintUses = [];
|
|
400
|
+
const file = this._currentFile;
|
|
401
|
+
const source = this._currentSource;
|
|
402
|
+
for (const u of pending) {
|
|
403
|
+
this._currentFile = u.file;
|
|
404
|
+
this._currentSource = u.source;
|
|
405
|
+
this._reportConstraintUse(u.type, u.node);
|
|
406
|
+
}
|
|
407
|
+
this._currentFile = file;
|
|
408
|
+
this._currentSource = source;
|
|
409
|
+
},
|
|
410
|
+
|
|
309
411
|
// `pkg.Name` when `pkg` is a Go-source package whose `js` files declare
|
|
310
412
|
// Name and this package is wasm or both (those files are not loaded).
|
|
311
413
|
_jsOnlyMember(qualified) {
|
|
@@ -342,18 +444,50 @@ export const resolveMethods = {
|
|
|
342
444
|
const e = depth < 16 ? scope.lookupConstExpr(name) : null;
|
|
343
445
|
return e ? foldConstExpr(e, (n) => resolve(n, depth + 1)) : undefined;
|
|
344
446
|
};
|
|
447
|
+
let n = Number.NaN;
|
|
345
448
|
try {
|
|
346
449
|
const lit = foldConstExpr(size, resolve);
|
|
347
|
-
|
|
348
|
-
if (Number.isInteger(n) && n >= 0) return n;
|
|
450
|
+
n = lit ? Number(lit.value) : Number.NaN;
|
|
349
451
|
} catch {
|
|
350
452
|
// fall through to the checker's evaluator
|
|
351
453
|
}
|
|
352
454
|
// `[math.MaxInt8]T`: a package constant the AST folder cannot see.
|
|
353
|
-
|
|
354
|
-
|
|
455
|
+
if (!Number.isInteger(n))
|
|
456
|
+
n = this._constIntValue(size, scope) ?? Number.NaN;
|
|
457
|
+
if (!Number.isInteger(n)) {
|
|
458
|
+
this._reportNonConstantArrayLen(size, scope);
|
|
459
|
+
return size;
|
|
460
|
+
}
|
|
355
461
|
this._useQualifiedConsts(size);
|
|
356
|
-
return
|
|
462
|
+
if (n >= 0) return n;
|
|
463
|
+
this.err(
|
|
464
|
+
`invalid array length ${exprText(size)} (untyped int constant ${n})`,
|
|
465
|
+
size,
|
|
466
|
+
);
|
|
467
|
+
return 0;
|
|
468
|
+
},
|
|
469
|
+
|
|
470
|
+
// A length that is not a constant at all (`[len(s)]T` with a slice, `[n]T`
|
|
471
|
+
// with a variable) cannot size an array. The operand's type is checked
|
|
472
|
+
// first, so it counts as used.
|
|
473
|
+
_reportNonConstantArrayLen(size, scope) {
|
|
474
|
+
if (size._type === undefined) this.checkExpr(size, scope);
|
|
475
|
+
this.err(
|
|
476
|
+
`invalid array length ${exprText(size)} (${operandKind(size, typeStr(size._type), false)}): must be constant`,
|
|
477
|
+
size,
|
|
478
|
+
);
|
|
479
|
+
},
|
|
480
|
+
|
|
481
|
+
// An array length must be an integer constant: `[1 == 1]T`, `["a"]T`,
|
|
482
|
+
// `[nil]T`, `[2.5]T`, `[2i]T` and a typed float constant are rejected.
|
|
483
|
+
_reportNonIntegerArrayLen(size, scope) {
|
|
484
|
+
const what = this._nonIntegerConst(size, scope);
|
|
485
|
+
if (!what) return false;
|
|
486
|
+
this.err(
|
|
487
|
+
`invalid array length ${exprText(size)} (${what}): must be integer`,
|
|
488
|
+
size,
|
|
489
|
+
);
|
|
490
|
+
return true;
|
|
357
491
|
},
|
|
358
492
|
|
|
359
493
|
// `pkg.K` inside a folded array length counts as using the import.
|
|
@@ -367,10 +501,24 @@ export const resolveMethods = {
|
|
|
367
501
|
},
|
|
368
502
|
|
|
369
503
|
_resolveArrayTypeNode(node, scope) {
|
|
504
|
+
if (
|
|
505
|
+
!node.inferLen &&
|
|
506
|
+
this._reportNonIntegerArrayLen(node._sizeExpr ?? node.size, scope)
|
|
507
|
+
)
|
|
508
|
+
return {
|
|
509
|
+
kind: "array",
|
|
510
|
+
size: 0,
|
|
511
|
+
elem: this.resolveTypeNode(node.elem, scope),
|
|
512
|
+
};
|
|
370
513
|
// Fold in place so backends that read the type node see a literal length.
|
|
371
|
-
|
|
514
|
+
// The node may come from the parse cache: keep the original expression so
|
|
515
|
+
// a later compile of the same AST still counts `pkg.K` as using `pkg`
|
|
516
|
+
// and still reports an invalid length.
|
|
517
|
+
if (node._sizeExpr) this._foldArrayLen(node._sizeExpr, scope);
|
|
518
|
+
else if (!node.inferLen && node.size?.value === undefined) {
|
|
372
519
|
const n = this._foldArrayLen(node.size, scope);
|
|
373
|
-
if (typeof n === "number")
|
|
520
|
+
if (typeof n === "number") {
|
|
521
|
+
node._sizeExpr = node.size;
|
|
374
522
|
node.size = {
|
|
375
523
|
kind: "BasicLit",
|
|
376
524
|
litKind: "INT",
|
|
@@ -378,6 +526,7 @@ export const resolveMethods = {
|
|
|
378
526
|
_line: node.size._line,
|
|
379
527
|
_col: node.size._col,
|
|
380
528
|
};
|
|
529
|
+
}
|
|
381
530
|
}
|
|
382
531
|
return {
|
|
383
532
|
kind: "array",
|
|
@@ -489,6 +638,7 @@ export const resolveMethods = {
|
|
|
489
638
|
},
|
|
490
639
|
|
|
491
640
|
_resolveInterfaceType(node, scope) {
|
|
641
|
+
this._markInterfaceElements(node);
|
|
492
642
|
const methods = new Map();
|
|
493
643
|
for (const m of node.methods) {
|
|
494
644
|
const params = m.params.map((p) => this.resolveTypeNode(p.type, scope));
|
|
@@ -504,24 +654,12 @@ export const resolveMethods = {
|
|
|
504
654
|
// Embedded interfaces that carry a type set (`interface{ Integer }`):
|
|
505
655
|
// kept so type-parameter operator checks can follow them.
|
|
506
656
|
const typeSetEmbeds = [];
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
for (const [mName, mType] of base.methods) {
|
|
514
|
-
if (!methods.has(mName)) methods.set(mName, mType);
|
|
515
|
-
}
|
|
516
|
-
if (base.unionConstraint || base._typeSetEmbeds)
|
|
517
|
-
typeSetEmbeds.push(base);
|
|
518
|
-
} else if (!isAny(resolved)) {
|
|
519
|
-
this.err(
|
|
520
|
-
`cannot embed non-interface type ${typeStr(resolved)}`,
|
|
521
|
-
embed,
|
|
522
|
-
);
|
|
523
|
-
}
|
|
524
|
-
}
|
|
657
|
+
// `interface{ comparable; ... }`: the type set is restricted to
|
|
658
|
+
// comparable types (directly or through an embedded constraint).
|
|
659
|
+
let comparable = false;
|
|
660
|
+
for (const embed of node.embeds ?? []) {
|
|
661
|
+
if (this._mergeInterfaceEmbed(embed, scope, methods, typeSetEmbeds))
|
|
662
|
+
comparable = true;
|
|
525
663
|
}
|
|
526
664
|
const result = { kind: "interface", methods };
|
|
527
665
|
if (node.unionConstraint) {
|
|
@@ -529,9 +667,28 @@ export const resolveMethods = {
|
|
|
529
667
|
result._unionScope = scope;
|
|
530
668
|
}
|
|
531
669
|
if (typeSetEmbeds.length > 0) result._typeSetEmbeds = typeSetEmbeds;
|
|
670
|
+
if (comparable) result._comparable = true;
|
|
532
671
|
return result;
|
|
533
672
|
},
|
|
534
673
|
|
|
674
|
+
// Folds one embedded element of an interface into `methods` and
|
|
675
|
+
// `typeSetEmbeds`; returns true when it makes the interface comparable-only.
|
|
676
|
+
_mergeInterfaceEmbed(embed, scope, methods, typeSetEmbeds) {
|
|
677
|
+
const resolved = this.resolveTypeNode(embed, scope);
|
|
678
|
+
const base = resolved?.kind === "named" ? resolved.underlying : resolved;
|
|
679
|
+
if (base === COMPARABLE) return true;
|
|
680
|
+
if (base?.kind !== "interface") {
|
|
681
|
+
if (!isAny(resolved))
|
|
682
|
+
this.err(`cannot embed non-interface type ${typeStr(resolved)}`, embed);
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
for (const [mName, mType] of base.methods) {
|
|
686
|
+
if (!methods.has(mName)) methods.set(mName, mType);
|
|
687
|
+
}
|
|
688
|
+
if (base.unionConstraint || base._typeSetEmbeds) typeSetEmbeds.push(base);
|
|
689
|
+
return Boolean(base._comparable);
|
|
690
|
+
},
|
|
691
|
+
|
|
535
692
|
// ── Generics — substitution, instantiation, inference ────────────
|
|
536
693
|
|
|
537
694
|
substituteType(type, map) {
|
|
@@ -715,7 +872,11 @@ export const resolveMethods = {
|
|
|
715
872
|
const before = map.size;
|
|
716
873
|
for (const tp of typeParams) {
|
|
717
874
|
const arg = map.get(tp.name);
|
|
718
|
-
if (!
|
|
875
|
+
if (!tp.constraint) continue;
|
|
876
|
+
if (!arg) {
|
|
877
|
+
this._inferFromPointerCore(tp, typeParams, map, scope);
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
719
880
|
const terms = this._constraintTerms(tp.constraint, new Set(), scope);
|
|
720
881
|
if (terms?.length !== 1) continue;
|
|
721
882
|
const argUnder =
|
|
@@ -727,6 +888,25 @@ export const resolveMethods = {
|
|
|
727
888
|
}
|
|
728
889
|
},
|
|
729
890
|
|
|
891
|
+
// An unbound type parameter whose constraint has the single exact term
|
|
892
|
+
// `*T` (`P interface{ *T; M() }`) is the pointer type, once `T` is known.
|
|
893
|
+
_inferFromPointerCore(tp, typeParams, map, scope) {
|
|
894
|
+
const terms = this._constraintTerms(tp.constraint, new Set(), scope);
|
|
895
|
+
if (terms?.length !== 1 || terms[0]?.kind !== "pointer") return;
|
|
896
|
+
const free = new Set(
|
|
897
|
+
typeParams.filter((p) => !map.has(p.name)).map((p) => p.name),
|
|
898
|
+
);
|
|
899
|
+
const inferred = this.substituteType(terms[0], map);
|
|
900
|
+
const mentionsFree = (t) =>
|
|
901
|
+
t?.kind === "typeParam"
|
|
902
|
+
? free.has(t.name)
|
|
903
|
+
: t?.kind === "pointer"
|
|
904
|
+
? mentionsFree(t.base)
|
|
905
|
+
: false;
|
|
906
|
+
if (!mentionsFree(inferred) && inferred.kind === "pointer")
|
|
907
|
+
map.set(tp.name, inferred);
|
|
908
|
+
},
|
|
909
|
+
|
|
730
910
|
_inferFromFuncPair(paramType, argType, map) {
|
|
731
911
|
for (
|
|
732
912
|
let i = 0;
|
|
@@ -745,6 +925,8 @@ export const resolveMethods = {
|
|
|
745
925
|
_inferFromStructuredPair(paramType, argType, map) {
|
|
746
926
|
if (paramType.kind === "slice" && argType.kind === "slice")
|
|
747
927
|
this._inferFromPair(paramType.elem, argType.elem, map);
|
|
928
|
+
if (paramType.kind === "array" && argType.kind === "array")
|
|
929
|
+
this._inferFromPair(paramType.elem, argType.elem, map);
|
|
748
930
|
if (paramType.kind === "pointer" && argType.kind === "pointer")
|
|
749
931
|
this._inferFromPair(paramType.base, argType.base, map);
|
|
750
932
|
if (paramType.kind === "map" && argType.kind === "map") {
|
|
@@ -798,16 +980,91 @@ export const resolveMethods = {
|
|
|
798
980
|
},
|
|
799
981
|
|
|
800
982
|
_checkUnionConstraint(typeArg, constraint, node) {
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
const termType =
|
|
804
|
-
term.resolved ?? this.resolveTypeNode(term.type, this.globals);
|
|
805
|
-
if (typeStr(typeArg) === typeStr(termType)) return;
|
|
806
|
-
}
|
|
983
|
+
if (typeArg?._tainted || this._unionAccepts(typeArg, constraint, new Set()))
|
|
984
|
+
return;
|
|
807
985
|
this.err(`type ${typeStr(typeArg)} does not satisfy constraint`, node);
|
|
808
986
|
},
|
|
809
987
|
|
|
988
|
+
// Whether `typeArg` is in the type set of a union: some term holds it.
|
|
989
|
+
// `scope` resolves the names its terms mention.
|
|
990
|
+
_unionAccepts(typeArg, union, active, scope = this.globals) {
|
|
991
|
+
return union.terms.some((term) => {
|
|
992
|
+
// `~[]E` may mention a type parameter that is not in `scope`, so an
|
|
993
|
+
// approximation term is resolved (and compared) only when it names
|
|
994
|
+
// a type.
|
|
995
|
+
if (!term.resolved && term.approx && term.type?.kind !== "TypeName")
|
|
996
|
+
return true;
|
|
997
|
+
const t = term.resolved ?? this.resolveTypeNode(term.type, scope);
|
|
998
|
+
return this._termAccepts(typeArg, t, term.approx, active);
|
|
999
|
+
});
|
|
1000
|
+
},
|
|
1001
|
+
|
|
1002
|
+
// One union term: a named interface contributes its own type set
|
|
1003
|
+
// (`S | U` with `type S interface{ ~int }`), `~T` any type with the
|
|
1004
|
+
// underlying type of `T`, and `T` exactly `T`.
|
|
1005
|
+
_termAccepts(typeArg, t, approx, active) {
|
|
1006
|
+
const base =
|
|
1007
|
+
t?.kind === "named"
|
|
1008
|
+
? (this.resolveType(t.underlying) ?? t.underlying)
|
|
1009
|
+
: t;
|
|
1010
|
+
if (base?.kind === "interface")
|
|
1011
|
+
return this._interfaceAccepts(typeArg, base, active);
|
|
1012
|
+
if (!approx) return typeStr(typeArg) === typeStr(t);
|
|
1013
|
+
const want = this._deepUnderlying(t);
|
|
1014
|
+
if (typeArg?.kind === "typeParam" || want?.kind !== "basic") return true;
|
|
1015
|
+
return typeStr(this._deepUnderlying(typeArg)) === typeStr(want);
|
|
1016
|
+
},
|
|
1017
|
+
|
|
1018
|
+
// An interface's type set is the intersection of its own union and the
|
|
1019
|
+
// type sets of the interfaces it embeds; one with neither holds every type.
|
|
1020
|
+
_interfaceAccepts(typeArg, iface, active) {
|
|
1021
|
+
if (active.has(iface)) return true;
|
|
1022
|
+
active.add(iface);
|
|
1023
|
+
const ok =
|
|
1024
|
+
(!iface.unionConstraint ||
|
|
1025
|
+
this._unionAccepts(
|
|
1026
|
+
typeArg,
|
|
1027
|
+
iface.unionConstraint,
|
|
1028
|
+
active,
|
|
1029
|
+
iface._unionScope ?? this.globals,
|
|
1030
|
+
)) &&
|
|
1031
|
+
(iface._typeSetEmbeds ?? []).every((e) =>
|
|
1032
|
+
this._interfaceAccepts(typeArg, e, active),
|
|
1033
|
+
);
|
|
1034
|
+
active.delete(iface);
|
|
1035
|
+
return ok;
|
|
1036
|
+
},
|
|
1037
|
+
|
|
1038
|
+
// Go's comparability: slices, maps and funcs are not comparable, nor are
|
|
1039
|
+
// arrays and structs that contain them.
|
|
1040
|
+
_isComparable(t, seen = new Set()) {
|
|
1041
|
+
const base = t?.kind === "named" ? t.underlying : t;
|
|
1042
|
+
if (!base || seen.has(base)) return true;
|
|
1043
|
+
seen.add(base);
|
|
1044
|
+
switch (base.kind) {
|
|
1045
|
+
case "slice":
|
|
1046
|
+
case "map":
|
|
1047
|
+
case "func":
|
|
1048
|
+
return false;
|
|
1049
|
+
case "array":
|
|
1050
|
+
return this._isComparable(base.elem, seen);
|
|
1051
|
+
case "struct":
|
|
1052
|
+
return [...(base.fields?.values() ?? [])].every((f) =>
|
|
1053
|
+
this._isComparable(f, seen),
|
|
1054
|
+
);
|
|
1055
|
+
default:
|
|
1056
|
+
return true;
|
|
1057
|
+
}
|
|
1058
|
+
},
|
|
1059
|
+
|
|
810
1060
|
_checkInterfaceConstraint(typeArg, base, constraint, node) {
|
|
1061
|
+
if (base._comparable && !this._isComparable(typeArg)) {
|
|
1062
|
+
this.err(
|
|
1063
|
+
`type ${typeStr(typeArg)} does not satisfy constraint ${typeStr(constraint)}`,
|
|
1064
|
+
node,
|
|
1065
|
+
);
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
811
1068
|
if (base.methods.size === 0 && !base.unionConstraint) return;
|
|
812
1069
|
if (base.unionConstraint) {
|
|
813
1070
|
this.checkConstraint(typeArg, base.unionConstraint, node);
|
|
@@ -925,7 +1182,11 @@ export const resolveMethods = {
|
|
|
925
1182
|
},
|
|
926
1183
|
|
|
927
1184
|
_fieldTypeStruct(base, baseType, field, node) {
|
|
1185
|
+
if (base.fields?.has(field) && isMethodPromotedOverField(base, field))
|
|
1186
|
+
return base.methods.get(field);
|
|
928
1187
|
if (base.fields?.has(field)) {
|
|
1188
|
+
if (isAmbiguousFieldSelector(base, field))
|
|
1189
|
+
return this._errAmbiguousSelector(baseType, field, node);
|
|
929
1190
|
if (
|
|
930
1191
|
field.startsWith("_") &&
|
|
931
1192
|
(baseType?.name?.startsWith("strings.") ||
|
|
@@ -939,6 +1200,8 @@ export const resolveMethods = {
|
|
|
939
1200
|
return base.fields.get(field);
|
|
940
1201
|
}
|
|
941
1202
|
if (base.methods?.has(field)) return base.methods.get(field);
|
|
1203
|
+
if (isAmbiguousPromotion(base, field))
|
|
1204
|
+
return this._errAmbiguousSelector(baseType, field, node);
|
|
942
1205
|
const typeName = baseType?.name || base.name;
|
|
943
1206
|
if (typeName) {
|
|
944
1207
|
const dName = displayTypeName(typeName);
|
|
@@ -963,6 +1226,12 @@ export const resolveMethods = {
|
|
|
963
1226
|
return this.err(`No field '${field}' on ${typeStr(baseType)}`, node);
|
|
964
1227
|
},
|
|
965
1228
|
|
|
1229
|
+
_errAmbiguousSelector(baseType, field, node) {
|
|
1230
|
+
const text = exprText(node?.expr);
|
|
1231
|
+
const recv = text === "expression" ? typeStr(baseType) : text;
|
|
1232
|
+
return this.err(`ambiguous selector ${recv}.${field}`, node);
|
|
1233
|
+
},
|
|
1234
|
+
|
|
966
1235
|
_fieldTypeInterface(base, baseType, field, node) {
|
|
967
1236
|
if (base.methods?.has(field)) return base.methods.get(field);
|
|
968
1237
|
if (base.fields?.has(field)) return base.fields.get(field);
|