gofront 1.6.0 → 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 +559 -144
- package/src/backend/js/index.js +174 -49
- package/src/backend/js/runtime.js +1518 -54
- package/src/backend/js/statements.js +91 -73
- package/src/backend/js/stdlib/base64.js +12 -0
- package/src/backend/js/stdlib/binary.js +102 -0
- package/src/backend/js/stdlib/bits.js +15 -1
- package/src/backend/js/stdlib/builder.js +28 -9
- package/src/backend/js/stdlib/bytes.js +75 -18
- package/src/backend/js/stdlib/errors.js +25 -1
- package/src/backend/js/stdlib/fmt.js +70 -13
- package/src/backend/js/stdlib/hex.js +17 -0
- package/src/backend/js/stdlib/html.js +1 -1
- package/src/backend/js/stdlib/index.js +28 -0
- package/src/backend/js/stdlib/io.js +105 -3
- package/src/backend/js/stdlib/json.js +972 -0
- package/src/backend/js/stdlib/math.js +13 -1
- package/src/backend/js/stdlib/path.js +18 -4
- package/src/backend/js/stdlib/rand.js +10 -5
- package/src/backend/js/stdlib/sha256.js +12 -0
- package/src/backend/js/stdlib/simd.js +24 -0
- package/src/backend/js/stdlib/slices.js +52 -26
- package/src/backend/js/stdlib/strconv.js +21 -5
- package/src/backend/js/stdlib/strings.js +148 -15
- package/src/backend/js/stdlib/sync.js +13 -0
- package/src/backend/js/stdlib/time.js +90 -19
- package/src/backend/js/stdlib/unicode.js +2 -2
- package/src/backend/wasm/boundary.js +1225 -41
- package/src/backend/wasm/const-fold.js +205 -0
- package/src/backend/wasm/emit-builtins.js +220 -76
- package/src/backend/wasm/emit-exprs.js +475 -276
- package/src/backend/wasm/emit-maps.js +144 -8
- package/src/backend/wasm/emit-stdlib.js +12 -3005
- package/src/backend/wasm/emit-stmts.js +264 -270
- package/src/backend/wasm/emit.js +124 -87
- package/src/backend/wasm/encode.js +140 -29
- package/src/backend/wasm/index.js +1001 -32
- package/src/backend/wasm/optimize.js +17 -6
- package/src/backend/wasm/stdlib/bytes.js +5039 -0
- package/src/backend/wasm/stdlib/core.js +8903 -0
- package/src/backend/wasm/stdlib/index.js +43 -0
- package/src/backend/wasm/stdlib/io.js +2161 -0
- package/src/backend/wasm/stdlib/packages.js +2400 -0
- package/src/backend/wasm/stdlib/pure.js +720 -0
- package/src/backend/wasm/stdlib/simd.js +234 -0
- package/src/backend/wasm/stdlib/time.js +1200 -0
- package/src/backend/wasm/types.js +174 -1
- package/src/backend/wasm/wat.js +37 -0
- package/src/cli-core.js +1 -1
- package/src/compiler.js +18 -4
- package/src/dts-parser.js +150 -140
- package/src/index.js +2 -6
- package/src/lexer.js +80 -29
- package/src/lower/boxing.js +1 -7
- package/src/lower/captures.js +2 -6
- package/src/lower/embedding.js +2 -1
- package/src/lower/ownership.js +30 -9
- package/src/minifier.js +20 -68
- package/src/parser/declarations.js +3 -6
- package/src/parser/expressions.js +3 -8
- package/src/parser/index.js +18 -3
- package/src/parser/statements.js +25 -38
- package/src/parser/struct-tag.js +144 -0
- package/src/parser/types.js +64 -8
- package/src/resolver.js +2 -27
- package/src/templ-lexer.js +29 -63
- package/src/templ-parser.js +22 -45
- package/src/typechecker/assignability.js +126 -13
- package/src/typechecker/expressions.js +168 -16
- package/src/typechecker/index.js +35 -8
- package/src/typechecker/resolve.js +187 -8
- package/src/typechecker/statements.js +68 -9
- package/src/typechecker/stdlib/core.js +280 -89
- package/src/typechecker/stdlib/extended.js +490 -1
- package/src/typechecker/stdlib/simd.js +73 -0
- package/src/typechecker/stdlib.js +2 -0
- package/src/typechecker/types.js +362 -9
- package/src/vendor.js +86 -11
|
@@ -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
|
},
|
|
@@ -266,6 +267,16 @@ export const expressionCheckMethods = {
|
|
|
266
267
|
if (this._isStructOrNamedMethod(baseType, base, expr.field))
|
|
267
268
|
expr._isMethodValue = true;
|
|
268
269
|
}
|
|
270
|
+
if (
|
|
271
|
+
baseType?.kind === "namespace" &&
|
|
272
|
+
this.types.has(`${baseType.name}.${expr.field}`) &&
|
|
273
|
+
!(
|
|
274
|
+
baseType.name === "binary" &&
|
|
275
|
+
(expr.field === "LittleEndian" || expr.field === "BigEndian")
|
|
276
|
+
)
|
|
277
|
+
) {
|
|
278
|
+
expr._isTypeRef = true;
|
|
279
|
+
}
|
|
269
280
|
return ft;
|
|
270
281
|
},
|
|
271
282
|
|
|
@@ -464,6 +475,83 @@ export const expressionCheckMethods = {
|
|
|
464
475
|
return targetType;
|
|
465
476
|
},
|
|
466
477
|
|
|
478
|
+
_checkConversionValidity(srcType, targetType, expr) {
|
|
479
|
+
if (!srcType || !targetType || isAny(srcType) || isAny(targetType)) return;
|
|
480
|
+
const srcResolved = srcType.kind === "named" ? srcType.underlying : srcType;
|
|
481
|
+
const tgtResolved =
|
|
482
|
+
targetType.kind === "named" ? targetType.underlying : targetType;
|
|
483
|
+
|
|
484
|
+
if (isNumeric(targetType) && isComplex(srcType)) {
|
|
485
|
+
this.err(
|
|
486
|
+
`cannot convert ${typeStr(srcType)} to ${typeStr(targetType)} (use real() or imag())`,
|
|
487
|
+
expr,
|
|
488
|
+
);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
if (isComplex(targetType) && !isComplexOrNumeric(srcType)) {
|
|
492
|
+
this.err(
|
|
493
|
+
`cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
|
|
494
|
+
expr,
|
|
495
|
+
);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
if (this._isMutualArraySlice(tgtResolved, srcResolved)) {
|
|
499
|
+
this._checkElemTypeCompatible(
|
|
500
|
+
tgtResolved.elem,
|
|
501
|
+
srcResolved.elem,
|
|
502
|
+
srcType,
|
|
503
|
+
targetType,
|
|
504
|
+
expr,
|
|
505
|
+
);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
if (this._isInvalidArrayConv(tgtResolved, srcResolved, srcType)) {
|
|
509
|
+
this.err(
|
|
510
|
+
`cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
|
|
511
|
+
expr,
|
|
512
|
+
);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
if (typeStr(srcResolved) === typeStr(tgtResolved)) return;
|
|
516
|
+
if (isNumeric(srcResolved) && isNumeric(tgtResolved)) return;
|
|
517
|
+
if (isString(srcResolved) && isString(tgtResolved)) return;
|
|
518
|
+
if (isBool(srcResolved) && isBool(tgtResolved)) return;
|
|
519
|
+
if (
|
|
520
|
+
isString(srcResolved) &&
|
|
521
|
+
tgtResolved?.kind === "slice" &&
|
|
522
|
+
(tgtResolved.elem?.name === "byte" ||
|
|
523
|
+
tgtResolved.elem?.name === "rune" ||
|
|
524
|
+
tgtResolved.elem?.name === "uint8" ||
|
|
525
|
+
tgtResolved.elem?.name === "int32")
|
|
526
|
+
)
|
|
527
|
+
return;
|
|
528
|
+
if (
|
|
529
|
+
isString(tgtResolved) &&
|
|
530
|
+
(isNumeric(srcResolved) ||
|
|
531
|
+
(srcResolved?.kind === "slice" &&
|
|
532
|
+
(srcResolved.elem?.name === "byte" ||
|
|
533
|
+
srcResolved.elem?.name === "rune" ||
|
|
534
|
+
srcResolved.elem?.name === "uint8" ||
|
|
535
|
+
srcResolved.elem?.name === "int32")))
|
|
536
|
+
)
|
|
537
|
+
return;
|
|
538
|
+
if (srcResolved?.kind === "pointer" && tgtResolved?.kind === "pointer") {
|
|
539
|
+
const sBase =
|
|
540
|
+
srcResolved.base?.kind === "named"
|
|
541
|
+
? srcResolved.base.underlying
|
|
542
|
+
: srcResolved.base;
|
|
543
|
+
const tBase =
|
|
544
|
+
tgtResolved.base?.kind === "named"
|
|
545
|
+
? tgtResolved.base.underlying
|
|
546
|
+
: tgtResolved.base;
|
|
547
|
+
if (typeStr(sBase) === typeStr(tBase)) return;
|
|
548
|
+
}
|
|
549
|
+
this.err(
|
|
550
|
+
`cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
|
|
551
|
+
expr,
|
|
552
|
+
);
|
|
553
|
+
},
|
|
554
|
+
|
|
467
555
|
_checkTypeAssertExpr(expr, scope) {
|
|
468
556
|
const srcType = this.checkExpr(expr.expr, scope);
|
|
469
557
|
const targetType = this.resolveTypeNode(expr.type, scope);
|
|
@@ -564,7 +652,8 @@ export const expressionCheckMethods = {
|
|
|
564
652
|
return expr;
|
|
565
653
|
},
|
|
566
654
|
|
|
567
|
-
_constIntValue(expr) {
|
|
655
|
+
_constIntValue(expr, scope = null) {
|
|
656
|
+
if (!expr) return null;
|
|
568
657
|
if (expr.kind === "BasicLit" && expr.litKind === "INT")
|
|
569
658
|
return Number(expr.value);
|
|
570
659
|
if (
|
|
@@ -575,6 +664,19 @@ export const expressionCheckMethods = {
|
|
|
575
664
|
) {
|
|
576
665
|
return -Number(expr.operand.value);
|
|
577
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
|
+
}
|
|
578
680
|
return null;
|
|
579
681
|
},
|
|
580
682
|
|
|
@@ -629,6 +731,14 @@ export const expressionCheckMethods = {
|
|
|
629
731
|
if (fnType.kind === "named" && expr.func._isTypeRef) {
|
|
630
732
|
expr._isTypeConversion = true;
|
|
631
733
|
expr._conversionTargetType = fnType;
|
|
734
|
+
if (expr.args.length !== 1) {
|
|
735
|
+
return this.err(
|
|
736
|
+
`cannot convert ${expr.args.length} arguments to type ${typeStr(fnType)}`,
|
|
737
|
+
expr,
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
const argType = argTypes[0];
|
|
741
|
+
this._checkConversionValidity(argType, fnType, expr);
|
|
632
742
|
return fnType;
|
|
633
743
|
}
|
|
634
744
|
|
|
@@ -638,7 +748,7 @@ export const expressionCheckMethods = {
|
|
|
638
748
|
return this.err(`Cannot call non-function type ${typeStr(fnType)}`, expr);
|
|
639
749
|
}
|
|
640
750
|
|
|
641
|
-
this._checkCallArgs(fnType, argTypes, expr);
|
|
751
|
+
this._checkCallArgs(fnType, argTypes, expr, scope);
|
|
642
752
|
|
|
643
753
|
// Pseudo-generic stdlib helpers (maps.Keys, slices.Clone, ...) declare
|
|
644
754
|
// how their result derives from the first argument.
|
|
@@ -702,6 +812,8 @@ export const expressionCheckMethods = {
|
|
|
702
812
|
switch (mode) {
|
|
703
813
|
case "arg0":
|
|
704
814
|
return under ? arg : null;
|
|
815
|
+
case "elemOfArg0":
|
|
816
|
+
return under?.elem ?? null;
|
|
705
817
|
case "keysOfArg0":
|
|
706
818
|
return under?.key ? { kind: "slice", elem: under.key } : null;
|
|
707
819
|
case "valuesOfArg0": {
|
|
@@ -753,15 +865,44 @@ export const expressionCheckMethods = {
|
|
|
753
865
|
);
|
|
754
866
|
},
|
|
755
867
|
|
|
756
|
-
_checkCallArgs(fnType, argTypes, expr) {
|
|
868
|
+
_checkCallArgs(fnType, argTypes, expr, scope = null) {
|
|
757
869
|
this._checkArgCount(fnType, argTypes, expr);
|
|
758
|
-
|
|
870
|
+
const maxLen = fnType.variadic
|
|
871
|
+
? argTypes.length
|
|
872
|
+
: Math.min(fnType.params.length, argTypes.length);
|
|
873
|
+
for (let i = 0; i < maxLen; i++) {
|
|
759
874
|
const paramIdx =
|
|
760
875
|
fnType.variadic && i >= fnType.params.length - 1
|
|
761
876
|
? fnType.params.length - 1
|
|
762
877
|
: i;
|
|
763
878
|
if (expr.args[i]?._spread) continue;
|
|
764
|
-
|
|
879
|
+
const targetParam = fnType.params[paramIdx];
|
|
880
|
+
const node = expr._multiForward ? null : expr.args[i];
|
|
881
|
+
this.assertAssignable(targetParam, argTypes[i], node);
|
|
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
|
+
}
|
|
765
906
|
}
|
|
766
907
|
},
|
|
767
908
|
|
|
@@ -859,6 +1000,8 @@ export const expressionCheckMethods = {
|
|
|
859
1000
|
this._inferArraySize(expr, typeNode, base);
|
|
860
1001
|
if (base?.kind === "struct")
|
|
861
1002
|
return this._checkStructCompositeLit(expr, scope, t, base);
|
|
1003
|
+
if (base?.kind === "simd")
|
|
1004
|
+
return this._checkSimdCompositeLit(expr, scope, t, base);
|
|
862
1005
|
if (this._isSliceOrArray(base))
|
|
863
1006
|
return this._checkSliceArrayCompositeLit(expr, scope, t, base, typeNode);
|
|
864
1007
|
if (base?.kind === "map")
|
|
@@ -866,6 +1009,17 @@ export const expressionCheckMethods = {
|
|
|
866
1009
|
return this._checkUnknownCompositeLit(expr, scope, t);
|
|
867
1010
|
},
|
|
868
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
|
+
|
|
869
1023
|
_checkStructPositionalElems(expr, scope, t, base) {
|
|
870
1024
|
const fieldNames = [...(base.fields?.keys() ?? [])];
|
|
871
1025
|
for (let i = 0; i < expr.elems.length; i++) {
|
|
@@ -887,21 +1041,19 @@ export const expressionCheckMethods = {
|
|
|
887
1041
|
for (const elem of expr.elems) {
|
|
888
1042
|
if (elem.kind === "KeyValueExpr") {
|
|
889
1043
|
const keyName = elem.key.name;
|
|
890
|
-
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;
|
|
891
1052
|
if (fieldType) {
|
|
892
1053
|
const vt = this.checkExpr(elem.value, scope);
|
|
893
1054
|
this.assertAssignable(fieldType, vt, elem.value);
|
|
894
1055
|
} else {
|
|
895
|
-
|
|
896
|
-
(e) => (e.kind === "named" ? e.name : null) === keyName,
|
|
897
|
-
);
|
|
898
|
-
if (embed) {
|
|
899
|
-
const vt = this.checkExpr(elem.value, scope);
|
|
900
|
-
this.assertAssignable(embed, vt, elem.value);
|
|
901
|
-
elem._isEmbedInit = true;
|
|
902
|
-
} else {
|
|
903
|
-
this.err(`Unknown field '${keyName}'`, elem.key);
|
|
904
|
-
}
|
|
1056
|
+
this.err(`Unknown field '${keyName}'`, elem.key);
|
|
905
1057
|
}
|
|
906
1058
|
} else {
|
|
907
1059
|
this.checkExpr(elem, scope);
|
package/src/typechecker/index.js
CHANGED
|
@@ -57,6 +57,7 @@ export class TypeChecker {
|
|
|
57
57
|
this._typeSwitchDepth = 0; // for rejecting fallthrough in type switch
|
|
58
58
|
this._deferCount = 0; // tracks defer usage in current function body
|
|
59
59
|
this._imports = []; // tracked imports for unused-import detection
|
|
60
|
+
this._constExprs = new Map(); // name -> AST initializer expr
|
|
60
61
|
this._setupGlobals();
|
|
61
62
|
// Register error as a named type (interface)
|
|
62
63
|
this.types.set("error", ERROR);
|
|
@@ -220,7 +221,11 @@ export class TypeChecker {
|
|
|
220
221
|
const type = spec.type
|
|
221
222
|
? this.resolveTypeNode(spec.type, this.globals)
|
|
222
223
|
: ANY;
|
|
223
|
-
for (
|
|
224
|
+
for (let i = 0; i < spec.names.length; i++) {
|
|
225
|
+
const name = spec.names[i];
|
|
226
|
+
const expr = spec.value?.[i] ?? spec.value?.[0] ?? null;
|
|
227
|
+
this.globals.defineConst(name, type, expr);
|
|
228
|
+
}
|
|
224
229
|
}
|
|
225
230
|
}
|
|
226
231
|
|
|
@@ -510,7 +515,8 @@ export class TypeChecker {
|
|
|
510
515
|
const struct = type.underlying;
|
|
511
516
|
if (struct?.kind === "struct" && struct._embeds) {
|
|
512
517
|
for (const embed of struct._embeds) {
|
|
513
|
-
const
|
|
518
|
+
const inner = embed.kind === "pointer" ? embed.base : embed;
|
|
519
|
+
const base = inner.kind === "named" ? inner.underlying : inner;
|
|
514
520
|
if (base?.kind !== "struct" || !base.methods) continue;
|
|
515
521
|
for (const [mName, mType] of base.methods.entries()) {
|
|
516
522
|
if (!struct.methods.has(mName)) struct.methods.set(mName, mType);
|
|
@@ -899,14 +905,31 @@ export class TypeChecker {
|
|
|
899
905
|
let type = spec.type ? this.resolveTypeNode(spec.type, scope) : null;
|
|
900
906
|
if (spec.value) {
|
|
901
907
|
const valTypes = spec.value.map((v) => this.checkExpr(v, scope));
|
|
902
|
-
|
|
908
|
+
const isTuple = valTypes.length === 1 && valTypes[0]?.kind === "tuple";
|
|
909
|
+
const valTypesFlat = isTuple ? valTypes[0].types : valTypes;
|
|
910
|
+
if (spec.names.length !== valTypesFlat.length) {
|
|
911
|
+
const vars =
|
|
912
|
+
spec.names.length === 1
|
|
913
|
+
? "1 variable"
|
|
914
|
+
: `${spec.names.length} variables`;
|
|
915
|
+
const vals = isTuple
|
|
916
|
+
? `returns ${valTypesFlat.length} values`
|
|
917
|
+
: valTypesFlat.length === 1
|
|
918
|
+
? "1 value"
|
|
919
|
+
: `${valTypesFlat.length} values`;
|
|
920
|
+
this.err(`assignment mismatch: ${vars} but ${vals}`, spec);
|
|
921
|
+
}
|
|
903
922
|
for (let i = 0; i < spec.names.length; i++) {
|
|
904
|
-
const vt =
|
|
905
|
-
|
|
923
|
+
const vt = valTypesFlat[i] ?? ANY;
|
|
924
|
+
const varType = type ?? defaultType(vt) ?? ANY;
|
|
925
|
+
if (type)
|
|
926
|
+
this.assertAssignable(type, vt, spec.value[i] ?? spec.value[0]);
|
|
927
|
+
scope.defineLocal(spec.names[i], varType);
|
|
906
928
|
}
|
|
929
|
+
} else {
|
|
930
|
+
if (!type) type = ANY;
|
|
931
|
+
for (const name of spec.names) scope.defineLocal(name, type);
|
|
907
932
|
}
|
|
908
|
-
if (!type) type = ANY;
|
|
909
|
-
for (const name of spec.names) scope.defineLocal(name, type);
|
|
910
933
|
}
|
|
911
934
|
this._sharedAllocOk = savedSharedAlloc;
|
|
912
935
|
}
|
|
@@ -918,7 +941,11 @@ export class TypeChecker {
|
|
|
918
941
|
const type = spec.type
|
|
919
942
|
? this.resolveTypeNode(spec.type, scope)
|
|
920
943
|
: (valTypes[0] ?? ANY);
|
|
921
|
-
for (
|
|
944
|
+
for (let i = 0; i < spec.names.length; i++) {
|
|
945
|
+
const name = spec.names[i];
|
|
946
|
+
const expr = spec.value?.[i] ?? spec.value?.[0] ?? null;
|
|
947
|
+
scope.defineConst(name, type, expr);
|
|
948
|
+
}
|
|
922
949
|
}
|
|
923
950
|
}
|
|
924
951
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Type resolution methods — resolveTypeNode, resolveType, fieldType, generics.
|
|
2
2
|
// Installed as a mixin on TypeChecker.prototype.
|
|
3
3
|
|
|
4
|
+
import { parseJsonTag } from "../parser/struct-tag.js";
|
|
4
5
|
import {
|
|
5
6
|
ANY,
|
|
6
7
|
BASIC_TYPES,
|
|
@@ -97,20 +98,29 @@ export const resolveMethods = {
|
|
|
97
98
|
}
|
|
98
99
|
return this.err(`Unknown type '${node.name}'`, node);
|
|
99
100
|
}
|
|
100
|
-
case "GenericTypeName":
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
case "GenericTypeName":
|
|
102
|
+
case "InstantiationExpr":
|
|
103
|
+
case "IndexExpr": {
|
|
104
|
+
const name =
|
|
105
|
+
node.name ?? (node.expr?.kind === "Ident" ? node.expr.name : null);
|
|
106
|
+
const typeArgs = node.typeArgs ?? (node.index ? [node.index] : []);
|
|
107
|
+
const base = name
|
|
108
|
+
? this.types.get(name)
|
|
109
|
+
: node.expr
|
|
110
|
+
? this.resolveTypeNode(node.expr, scope)
|
|
111
|
+
: null;
|
|
112
|
+
if (!base) return this.err(`Unknown type '${name ?? "?"}'`, node);
|
|
103
113
|
if (base.kind === "named" && base._generic) {
|
|
104
114
|
const res = this.instantiateGenericType(
|
|
105
115
|
base._generic,
|
|
106
|
-
|
|
116
|
+
typeArgs,
|
|
107
117
|
scope,
|
|
108
118
|
);
|
|
109
119
|
node._typeArgs = res.typeArgs;
|
|
110
120
|
node._genericType = base;
|
|
111
121
|
return res;
|
|
112
122
|
}
|
|
113
|
-
return this.err(`Type '${
|
|
123
|
+
return this.err(`Type '${name ?? "?"}' is not generic`, node);
|
|
114
124
|
}
|
|
115
125
|
case "TypeParam": {
|
|
116
126
|
const constraint = node.constraint
|
|
@@ -200,10 +210,28 @@ export const resolveMethods = {
|
|
|
200
210
|
_resolveStructType(node, scope) {
|
|
201
211
|
const fields = new Map();
|
|
202
212
|
const embeds = [];
|
|
213
|
+
const structFields = [];
|
|
203
214
|
for (const f of node.fields) {
|
|
204
215
|
const ft = this.resolveTypeNode(f.type, scope);
|
|
216
|
+
structFields.push({
|
|
217
|
+
names: f.names,
|
|
218
|
+
type: ft,
|
|
219
|
+
tag: f.tag ?? null,
|
|
220
|
+
embedded: Boolean(f.embedded),
|
|
221
|
+
astType: f.type,
|
|
222
|
+
});
|
|
205
223
|
if (f.embedded) {
|
|
206
|
-
const
|
|
224
|
+
const inner = ft.kind === "pointer" ? ft.base : ft;
|
|
225
|
+
const embedName = (
|
|
226
|
+
f.astType?.name ??
|
|
227
|
+
f.type?.name ??
|
|
228
|
+
f.type?.base?.name ??
|
|
229
|
+
(inner.kind === "named" ? inner.name : "")
|
|
230
|
+
)
|
|
231
|
+
.split(".")
|
|
232
|
+
.pop();
|
|
233
|
+
if (embedName) fields.set(embedName, ft);
|
|
234
|
+
const base = inner.kind === "named" ? inner.underlying : inner;
|
|
207
235
|
if (base?.kind === "struct") {
|
|
208
236
|
for (const [k, v] of base.fields.entries()) fields.set(k, v);
|
|
209
237
|
}
|
|
@@ -212,7 +240,13 @@ export const resolveMethods = {
|
|
|
212
240
|
for (const n of f.names) fields.set(n, ft);
|
|
213
241
|
}
|
|
214
242
|
}
|
|
215
|
-
return {
|
|
243
|
+
return {
|
|
244
|
+
kind: "struct",
|
|
245
|
+
fields,
|
|
246
|
+
methods: new Map(),
|
|
247
|
+
_embeds: embeds,
|
|
248
|
+
_structFields: structFields,
|
|
249
|
+
};
|
|
216
250
|
},
|
|
217
251
|
|
|
218
252
|
_resolveInterfaceType(node, scope) {
|
|
@@ -291,6 +325,7 @@ export const resolveMethods = {
|
|
|
291
325
|
name: generic.declNode.name,
|
|
292
326
|
typeArgs,
|
|
293
327
|
underlying: instantiated,
|
|
328
|
+
methods: instantiated.methods,
|
|
294
329
|
};
|
|
295
330
|
},
|
|
296
331
|
|
|
@@ -381,6 +416,14 @@ export const resolveMethods = {
|
|
|
381
416
|
const found = this.types.get(t.alias);
|
|
382
417
|
if (found) return found;
|
|
383
418
|
}
|
|
419
|
+
if (
|
|
420
|
+
t.kind === "named" &&
|
|
421
|
+
(!t.typeArgs || t.typeArgs.length === 0) &&
|
|
422
|
+
(!t.underlying || !t.methods)
|
|
423
|
+
) {
|
|
424
|
+
const found = this.types.get(t.name);
|
|
425
|
+
if (found) return found;
|
|
426
|
+
}
|
|
384
427
|
return t;
|
|
385
428
|
},
|
|
386
429
|
|
|
@@ -416,6 +459,7 @@ export const resolveMethods = {
|
|
|
416
459
|
case "namespace":
|
|
417
460
|
return this._fieldTypeNamespace(base, field, node);
|
|
418
461
|
case "shared":
|
|
462
|
+
case "simd":
|
|
419
463
|
if (base.methods?.has(field)) return base.methods.get(field);
|
|
420
464
|
return this.err(`No method '${field}' on ${typeStr(baseType)}`, node);
|
|
421
465
|
case "slice":
|
|
@@ -432,7 +476,19 @@ export const resolveMethods = {
|
|
|
432
476
|
},
|
|
433
477
|
|
|
434
478
|
_fieldTypeStruct(base, baseType, field, node) {
|
|
435
|
-
if (base.fields?.has(field))
|
|
479
|
+
if (base.fields?.has(field)) {
|
|
480
|
+
if (
|
|
481
|
+
field.startsWith("_") &&
|
|
482
|
+
(baseType?.name?.startsWith("strings.") ||
|
|
483
|
+
baseType?.name?.startsWith("bytes."))
|
|
484
|
+
) {
|
|
485
|
+
return this.err(
|
|
486
|
+
`Cannot refer to unexported field '${field}' on ${typeStr(baseType)}`,
|
|
487
|
+
node,
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
return base.fields.get(field);
|
|
491
|
+
}
|
|
436
492
|
if (base.methods?.has(field)) return base.methods.get(field);
|
|
437
493
|
return this.err(`No field '${field}' on ${typeStr(baseType)}`, node);
|
|
438
494
|
},
|
|
@@ -507,4 +563,127 @@ export const resolveMethods = {
|
|
|
507
563
|
}
|
|
508
564
|
return this.err(`No member '${field}' in namespace ${base.name}`, node);
|
|
509
565
|
},
|
|
566
|
+
|
|
567
|
+
resolveJsonFields(structType) {
|
|
568
|
+
return resolveJsonFields(structType);
|
|
569
|
+
},
|
|
510
570
|
};
|
|
571
|
+
|
|
572
|
+
function isExported(name) {
|
|
573
|
+
if (!name || typeof name !== "string") return false;
|
|
574
|
+
const code = name.charCodeAt(0);
|
|
575
|
+
return code >= 65 && code <= 90;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function _processJsonField(f, depth, path, visited, queue, candidates) {
|
|
579
|
+
const tagInfo = parseJsonTag(f.tag);
|
|
580
|
+
if (tagInfo.ignored) return;
|
|
581
|
+
|
|
582
|
+
if (f.embedded) {
|
|
583
|
+
const isPtr = f.type.kind === "pointer" || f.type.kind === "PointerType";
|
|
584
|
+
const inner = isPtr ? f.type.base : f.type;
|
|
585
|
+
const embedStruct = inner.kind === "named" ? inner.underlying : inner;
|
|
586
|
+
const embedName = (
|
|
587
|
+
f.astType?.name ??
|
|
588
|
+
f.astType?.base?.name ??
|
|
589
|
+
inner.name ??
|
|
590
|
+
""
|
|
591
|
+
)
|
|
592
|
+
.split(".")
|
|
593
|
+
.pop();
|
|
594
|
+
|
|
595
|
+
if (tagInfo.name !== null) {
|
|
596
|
+
if (isExported(embedName)) {
|
|
597
|
+
candidates.push({
|
|
598
|
+
jsonKey: tagInfo.name,
|
|
599
|
+
goName: embedName,
|
|
600
|
+
type: f.type,
|
|
601
|
+
omitempty: tagInfo.omitempty,
|
|
602
|
+
depth,
|
|
603
|
+
tagged: true,
|
|
604
|
+
isEmbedded: true,
|
|
605
|
+
path: [...path, { name: embedName, isPtr, typeName: embedName }],
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
} else if (embedStruct?.kind === "struct") {
|
|
609
|
+
if (!visited.has(embedStruct)) {
|
|
610
|
+
const nextVisited = new Set(visited);
|
|
611
|
+
nextVisited.add(embedStruct);
|
|
612
|
+
queue.push({
|
|
613
|
+
struct: embedStruct,
|
|
614
|
+
depth: depth + 1,
|
|
615
|
+
path: [...path, { name: embedName, isPtr, typeName: embedName }],
|
|
616
|
+
visited: nextVisited,
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
for (const name of f.names) {
|
|
624
|
+
if (!isExported(name)) continue;
|
|
625
|
+
candidates.push({
|
|
626
|
+
jsonKey: tagInfo.name !== null ? tagInfo.name : name,
|
|
627
|
+
goName: name,
|
|
628
|
+
type: f.type,
|
|
629
|
+
omitempty: tagInfo.omitempty,
|
|
630
|
+
quoted: tagInfo.options.includes("string"),
|
|
631
|
+
depth,
|
|
632
|
+
tagged: tagInfo.name !== null,
|
|
633
|
+
path: [...path, { name, isPtr: false }],
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function _resolveCandidateCollisions(candidates) {
|
|
639
|
+
const byKey = new Map();
|
|
640
|
+
for (const cand of candidates) {
|
|
641
|
+
if (!byKey.has(cand.jsonKey)) byKey.set(cand.jsonKey, []);
|
|
642
|
+
byKey.get(cand.jsonKey).push(cand);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const winningFields = [];
|
|
646
|
+
for (const [, group] of byKey.entries()) {
|
|
647
|
+
let minDepth = Infinity;
|
|
648
|
+
for (const c of group) {
|
|
649
|
+
if (c.depth < minDepth) minDepth = c.depth;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const atMin = group.filter((c) => c.depth === minDepth);
|
|
653
|
+
if (atMin.length === 1) {
|
|
654
|
+
winningFields.push(atMin[0]);
|
|
655
|
+
} else {
|
|
656
|
+
const tagged = atMin.filter((c) => c.tagged);
|
|
657
|
+
if (tagged.length === 1) winningFields.push(tagged[0]);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
winningFields.sort((a, b) => candidates.indexOf(a) - candidates.indexOf(b));
|
|
662
|
+
return winningFields;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
export function resolveJsonFields(structType) {
|
|
666
|
+
if (!structType) return [];
|
|
667
|
+
const underlying =
|
|
668
|
+
structType.kind === "named" ? structType.underlying : structType;
|
|
669
|
+
if (underlying?.kind !== "struct") return [];
|
|
670
|
+
if (underlying._jsonFields) return underlying._jsonFields;
|
|
671
|
+
|
|
672
|
+
const queue = [
|
|
673
|
+
{ struct: underlying, depth: 0, path: [], visited: new Set([underlying]) },
|
|
674
|
+
];
|
|
675
|
+
const candidates = [];
|
|
676
|
+
|
|
677
|
+
while (queue.length > 0) {
|
|
678
|
+
const { struct: curr, depth, path, visited } = queue.shift();
|
|
679
|
+
if (curr?.kind !== "struct") continue;
|
|
680
|
+
|
|
681
|
+
for (const f of curr._structFields || []) {
|
|
682
|
+
_processJsonField(f, depth, path, visited, queue, candidates);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const winning = _resolveCandidateCollisions(candidates);
|
|
687
|
+
underlying._jsonFields = winning;
|
|
688
|
+
return winning;
|
|
689
|
+
}
|