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
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
UNTYPED_COMPLEX,
|
|
45
45
|
UNTYPED_FLOAT,
|
|
46
46
|
UNTYPED_INT,
|
|
47
|
+
UNTYPED_RUNE,
|
|
47
48
|
UNTYPED_STRING,
|
|
48
49
|
VOID,
|
|
49
50
|
} from "./types.js";
|
|
@@ -71,7 +72,7 @@ function minMaxType(at) {
|
|
|
71
72
|
// Each value is (self, name, expr, argTypes, scope) => returnType.
|
|
72
73
|
const BUILTIN_CHECK = {
|
|
73
74
|
len: (s, n, e, at) => s._checkBuiltinLen(n, e, at),
|
|
74
|
-
cap: (s, n, e, at) => s._checkBuiltinLen(n, e, at),
|
|
75
|
+
cap: (s, n, e, at, sc) => s._checkBuiltinLen(n, e, at, sc),
|
|
75
76
|
append: (s, _n, e, at) => {
|
|
76
77
|
const st = at[0] ?? ANY;
|
|
77
78
|
if (isArray(st)) {
|
|
@@ -93,6 +94,11 @@ const BUILTIN_CHECK = {
|
|
|
93
94
|
s._rejectSeqAsSlice(at[i], e.args[i], "a spread argument");
|
|
94
95
|
if (!e.args[i]._spread && at[i]) {
|
|
95
96
|
if (is64) e.args[i]._is64Bit = true;
|
|
97
|
+
s._tagComplexConst(
|
|
98
|
+
s.resolveType(sb.elem),
|
|
99
|
+
s.resolveType(at[i]),
|
|
100
|
+
e.args[i],
|
|
101
|
+
);
|
|
96
102
|
s._markIfaceBox(
|
|
97
103
|
s.resolveType(sb.elem),
|
|
98
104
|
s.resolveType(at[i]),
|
|
@@ -106,7 +112,10 @@ const BUILTIN_CHECK = {
|
|
|
106
112
|
copy: (s, _n, e, at) => s._checkBuiltinCopy(e, at),
|
|
107
113
|
delete: (s, _n, e, at) => {
|
|
108
114
|
const mt = s._deepUnderlying(at[0]);
|
|
109
|
-
if (mt?.kind === "map" && e.args[1])
|
|
115
|
+
if (mt?.kind === "map" && e.args[1]) {
|
|
116
|
+
s._tagFloat32Const(s.resolveType(mt.key), at[1], e.args[1]);
|
|
117
|
+
s._markIfaceKey(mt, e.args[1], at[1]);
|
|
118
|
+
}
|
|
110
119
|
return VOID;
|
|
111
120
|
},
|
|
112
121
|
make: (s, _n, e, _at, sc) => {
|
|
@@ -117,6 +126,8 @@ const BUILTIN_CHECK = {
|
|
|
117
126
|
// `make(S, n)` with S a type parameter makes its core type.
|
|
118
127
|
const core = made.kind === "typeParam" ? s._coreType(made, sc) : null;
|
|
119
128
|
if (core) ta._coreType = core;
|
|
129
|
+
for (const size of e.args.slice(1, 3))
|
|
130
|
+
s._checkIntegerConstOperand(size, "size", sc);
|
|
120
131
|
return made;
|
|
121
132
|
},
|
|
122
133
|
new: (s, _n, e, _at, sc) => {
|
|
@@ -134,14 +145,37 @@ const BUILTIN_CHECK = {
|
|
|
134
145
|
},
|
|
135
146
|
recover: () => ANY,
|
|
136
147
|
error: () => ERROR,
|
|
137
|
-
min: (
|
|
138
|
-
max: (
|
|
148
|
+
min: (s, _n, e, at) => s._checkMinMax(e, at),
|
|
149
|
+
max: (s, _n, e, at) => s._checkMinMax(e, at),
|
|
139
150
|
clear: () => VOID,
|
|
140
151
|
complex: (s, _n, e, at) => s._checkBuiltinComplex(e, at),
|
|
141
152
|
real: (s, n, e, at) => s._checkBuiltinRealImag(n, e, at),
|
|
142
153
|
imag: (s, n, e, at) => s._checkBuiltinRealImag(n, e, at),
|
|
143
154
|
};
|
|
144
155
|
|
|
156
|
+
// Type kinds that are never valid as an index, slice bound or `make` size.
|
|
157
|
+
const NON_INTEGER_KINDS = new Set([
|
|
158
|
+
"struct",
|
|
159
|
+
"pointer",
|
|
160
|
+
"slice",
|
|
161
|
+
"map",
|
|
162
|
+
"func",
|
|
163
|
+
"array",
|
|
164
|
+
"interface",
|
|
165
|
+
"simd",
|
|
166
|
+
]);
|
|
167
|
+
|
|
168
|
+
// Basic types that are never valid as an index, slice bound or `make` size.
|
|
169
|
+
const NON_INTEGER_BASICS = new Set([
|
|
170
|
+
"float32",
|
|
171
|
+
"float64",
|
|
172
|
+
"complex64",
|
|
173
|
+
"complex128",
|
|
174
|
+
"bool",
|
|
175
|
+
"string",
|
|
176
|
+
"any",
|
|
177
|
+
]);
|
|
178
|
+
|
|
145
179
|
// Method-name dispatch for _checkExpr — string values add no static call edges.
|
|
146
180
|
const CHECK_EXPR_DELEGATE = {
|
|
147
181
|
UnaryExpr: "_checkUnaryExpr",
|
|
@@ -171,7 +205,7 @@ export const expressionCheckMethods = {
|
|
|
171
205
|
case "BasicLit": {
|
|
172
206
|
switch (expr.litKind) {
|
|
173
207
|
case "INT":
|
|
174
|
-
return UNTYPED_INT;
|
|
208
|
+
return expr.rune ? UNTYPED_RUNE : UNTYPED_INT;
|
|
175
209
|
case "FLOAT":
|
|
176
210
|
return UNTYPED_FLOAT;
|
|
177
211
|
case "STRING":
|
|
@@ -260,6 +294,10 @@ export const expressionCheckMethods = {
|
|
|
260
294
|
this._retypeUntypedShift(lt, rt, expr.right);
|
|
261
295
|
this._retypeUntypedShift(rt, lt, expr.left);
|
|
262
296
|
}
|
|
297
|
+
this._tagFloat32Const(lt, rt, expr.right);
|
|
298
|
+
this._tagFloat32Const(rt, lt, expr.left);
|
|
299
|
+
this._tagComplex64Const(lt, rt, expr.right);
|
|
300
|
+
this._tagComplex64Const(rt, lt, expr.left);
|
|
263
301
|
if (is64BitInt(lt) && rt?.kind === "untyped" && expr.right)
|
|
264
302
|
expr.right._is64Bit = true;
|
|
265
303
|
if (is64BitInt(rt) && lt?.kind === "untyped" && expr.left)
|
|
@@ -310,7 +348,8 @@ export const expressionCheckMethods = {
|
|
|
310
348
|
|
|
311
349
|
_checkBoolUnaryOp(ot, expr) {
|
|
312
350
|
if (!isBool(ot) && !isAny(ot)) this.err("! requires bool", expr);
|
|
313
|
-
|
|
351
|
+
// `!` keeps an untyped or defined bool type.
|
|
352
|
+
return isBool(ot) ? ot : BOOL;
|
|
314
353
|
},
|
|
315
354
|
|
|
316
355
|
_checkNumericUnaryOp(ot, expr) {
|
|
@@ -826,6 +865,15 @@ export const expressionCheckMethods = {
|
|
|
826
865
|
// with its dynamic type, so `L("1")`, `N(1)`, `"1"` and `1` stay distinct.
|
|
827
866
|
// A named basic value is boxed first, exactly as in any interface
|
|
828
867
|
// conversion; other values are keyed as they are.
|
|
868
|
+
// Untyped constant operands take the result type, so a float32 result
|
|
869
|
+
// rounds them like the typed operands.
|
|
870
|
+
_checkMinMax(e, at) {
|
|
871
|
+
const rt = minMaxType(at);
|
|
872
|
+
for (let i = 0; i < e.args.length; i++)
|
|
873
|
+
this._tagFloat32Const(rt, at[i], e.args[i]);
|
|
874
|
+
return rt;
|
|
875
|
+
},
|
|
876
|
+
|
|
829
877
|
_markIfaceKey(mapType, node, srcType) {
|
|
830
878
|
const key = this.resolveType(mapType.key);
|
|
831
879
|
const kb = key?.kind === "named" ? this.resolveType(key.underlying) : key;
|
|
@@ -866,6 +914,8 @@ export const expressionCheckMethods = {
|
|
|
866
914
|
if (bt?._tainted) return TAINTED_ANY;
|
|
867
915
|
if (isAny(bt)) return ANY;
|
|
868
916
|
const btu = this._coreUnderlying(bt, scope);
|
|
917
|
+
if (btu.kind !== "map")
|
|
918
|
+
this._checkIntegerConstOperand(expr.index, "index", scope);
|
|
869
919
|
if (btu.kind === "slice" || btu.kind === "array") {
|
|
870
920
|
this._checkArrayBoundsExpr(btu, bt, expr, scope);
|
|
871
921
|
return btu.elem;
|
|
@@ -955,15 +1005,61 @@ export const expressionCheckMethods = {
|
|
|
955
1005
|
return t.base;
|
|
956
1006
|
},
|
|
957
1007
|
|
|
1008
|
+
// An index, slice bound or `make` size that is a constant must be an
|
|
1009
|
+
// integer (`s[1 == 1]`, `make([]int, "a")`).
|
|
1010
|
+
// A typed operand must have an integer type too (`s[f]` with `f float64`).
|
|
1011
|
+
_checkIntegerConstOperand(operand, what, scope) {
|
|
1012
|
+
const kind = this._nonIntegerConst(operand, scope);
|
|
1013
|
+
if (kind) {
|
|
1014
|
+
this.err(
|
|
1015
|
+
`invalid argument: ${what} ${exprText(operand)} (${kind}) must be integer`,
|
|
1016
|
+
operand,
|
|
1017
|
+
);
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
const t = operand?._type;
|
|
1021
|
+
if (!this._isNonIntegerType(t, scope)) return;
|
|
1022
|
+
this.err(
|
|
1023
|
+
`invalid argument: ${what} ${exprText(operand)} (${operandKind(operand, typeStr(t), false)}) must be integer`,
|
|
1024
|
+
operand,
|
|
1025
|
+
);
|
|
1026
|
+
},
|
|
1027
|
+
|
|
1028
|
+
// Whether `t` is a type (or a defined type over one) that is not an
|
|
1029
|
+
// integer: a float, complex, bool, string, `any`, struct, pointer, slice,
|
|
1030
|
+
// map, func, array, interface, simd vector or a type parameter whose type
|
|
1031
|
+
// set is not all integers. Untyped constants are judged by
|
|
1032
|
+
// `_nonIntegerConst`, so only untyped non-integers that reach here
|
|
1033
|
+
// (`s[a == b]`) count.
|
|
1034
|
+
_isNonIntegerType(t, scope) {
|
|
1035
|
+
if (!t || t._tainted) return false;
|
|
1036
|
+
// A type parameter is an integer only when every type in its type set
|
|
1037
|
+
// is (`~int | ~uint8`); `any`, `comparable` and method-only
|
|
1038
|
+
// constraints have no integer type set.
|
|
1039
|
+
if (t.kind === "typeParam") {
|
|
1040
|
+
const terms = this._constraintTerms(t.constraint, new Set(), scope);
|
|
1041
|
+
return !(terms?.length > 0 && terms.every(isInteger));
|
|
1042
|
+
}
|
|
1043
|
+
if (t.kind === "untyped")
|
|
1044
|
+
return (
|
|
1045
|
+
t.base === "bool" || t.base === "string" || t.base === "complex128"
|
|
1046
|
+
);
|
|
1047
|
+
const u = this._deepUnderlying(t);
|
|
1048
|
+
if (u?.kind === "basic") return NON_INTEGER_BASICS.has(u.name);
|
|
1049
|
+
return NON_INTEGER_KINDS.has(u?.kind);
|
|
1050
|
+
},
|
|
1051
|
+
|
|
958
1052
|
_checkSliceExpr(expr, scope) {
|
|
959
1053
|
const bt = this._derefArrayPtr(
|
|
960
1054
|
expr,
|
|
961
1055
|
"expr",
|
|
962
1056
|
this.checkExpr(expr.expr, scope),
|
|
963
1057
|
);
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1058
|
+
for (const bound of [expr.low, expr.high, expr.max]) {
|
|
1059
|
+
if (!bound) continue;
|
|
1060
|
+
this.checkExpr(bound, scope);
|
|
1061
|
+
this._checkIntegerConstOperand(bound, "index", scope);
|
|
1062
|
+
}
|
|
967
1063
|
if (bt?._tainted) return TAINTED_ANY;
|
|
968
1064
|
if (isAny(bt)) return ANY;
|
|
969
1065
|
const btu = this._coreUnderlying(bt, scope);
|
|
@@ -1070,6 +1166,16 @@ export const expressionCheckMethods = {
|
|
|
1070
1166
|
// `any(x)` / `error(x)`: box x as an implicit conversion would.
|
|
1071
1167
|
this._markIfaceBox(targetType, srcType, expr.expr);
|
|
1072
1168
|
this._rejectNilConversion(srcType, targetType, expr);
|
|
1169
|
+
if (this._checkIfaceConversion(srcType, targetType, expr, expr.expr))
|
|
1170
|
+
return targetType;
|
|
1171
|
+
if (
|
|
1172
|
+
srcType &&
|
|
1173
|
+
targetType &&
|
|
1174
|
+
!isAny(srcType) &&
|
|
1175
|
+
!isAny(targetType) &&
|
|
1176
|
+
this._checkIfaceTargetConversion(srcType, targetType, expr.expr)
|
|
1177
|
+
)
|
|
1178
|
+
return targetType;
|
|
1073
1179
|
const srcResolved =
|
|
1074
1180
|
srcType?.kind === "named" ? srcType.underlying : srcType;
|
|
1075
1181
|
const tgtResolved =
|
|
@@ -1090,6 +1196,8 @@ export const expressionCheckMethods = {
|
|
|
1090
1196
|
expr,
|
|
1091
1197
|
);
|
|
1092
1198
|
}
|
|
1199
|
+
// `complex64(1 + 0.01i)`: the constant's components round to float32.
|
|
1200
|
+
this._tagComplex64Const(targetType, srcType, expr.expr);
|
|
1093
1201
|
if (tgtResolved?.kind === "slice")
|
|
1094
1202
|
this._rejectSeqAsSlice(srcType, expr.expr, "a slice conversion");
|
|
1095
1203
|
if (this._isMutualArraySlice(tgtResolved, srcResolved)) {
|
|
@@ -1138,8 +1246,25 @@ export const expressionCheckMethods = {
|
|
|
1138
1246
|
return true;
|
|
1139
1247
|
},
|
|
1140
1248
|
|
|
1249
|
+
// `I(x)` with I an interface type converts exactly when x is assignable to
|
|
1250
|
+
// I, so the method-set check (and its wording) is the assignment one.
|
|
1251
|
+
_checkIfaceTargetConversion(srcType, targetType, operand) {
|
|
1252
|
+
// `error("msg")` builds an error value (GoFront extension).
|
|
1253
|
+
if (targetType === ERROR && isString(srcType)) return false;
|
|
1254
|
+
const base = this.resolveType(
|
|
1255
|
+
targetType.kind === "named" ? targetType.underlying : targetType,
|
|
1256
|
+
);
|
|
1257
|
+
if (base?.kind !== "interface" || !operand) return false;
|
|
1258
|
+
this.assertAssignable(targetType, srcType, operand, "conversion");
|
|
1259
|
+
return true;
|
|
1260
|
+
},
|
|
1261
|
+
|
|
1141
1262
|
_checkConversionValidity(srcType, targetType, expr) {
|
|
1263
|
+
if (this._checkIfaceConversion(srcType, targetType, expr, expr.args?.[0]))
|
|
1264
|
+
return;
|
|
1142
1265
|
if (!srcType || !targetType || isAny(srcType) || isAny(targetType)) return;
|
|
1266
|
+
if (this._checkIfaceTargetConversion(srcType, targetType, expr.args?.[0]))
|
|
1267
|
+
return;
|
|
1143
1268
|
const srcResolved = srcType.kind === "named" ? srcType.underlying : srcType;
|
|
1144
1269
|
const tgtResolved =
|
|
1145
1270
|
targetType.kind === "named" ? targetType.underlying : targetType;
|
|
@@ -1813,6 +1938,14 @@ export const expressionCheckMethods = {
|
|
|
1813
1938
|
const node = expr._multiForward ? null : expr.args[i];
|
|
1814
1939
|
this.assertAssignable(targetParam, argTypes[i], node, "argument");
|
|
1815
1940
|
}
|
|
1941
|
+
// `g(f())`: per-element box marks for lowering to split the forward.
|
|
1942
|
+
if (
|
|
1943
|
+
expr._multiForward &&
|
|
1944
|
+
!fnType.variadic &&
|
|
1945
|
+
!this._skipIfaceMark &&
|
|
1946
|
+
fnType.params.length === argTypes.length
|
|
1947
|
+
)
|
|
1948
|
+
this._markForwardedElems(fnType.params, argTypes, expr.args[0]);
|
|
1816
1949
|
|
|
1817
1950
|
this._checkSimdLaneArgs(expr, scope);
|
|
1818
1951
|
},
|
|
@@ -1878,7 +2011,28 @@ export const expressionCheckMethods = {
|
|
|
1878
2011
|
return null;
|
|
1879
2012
|
},
|
|
1880
2013
|
|
|
1881
|
-
|
|
2014
|
+
_capOperandKind(operand, type) {
|
|
2015
|
+
const untypedConst = type?.kind === "untyped" && type.base !== "nil";
|
|
2016
|
+
return untypedConst
|
|
2017
|
+
? operandKind(operand, type.base, true)
|
|
2018
|
+
: operandKind(operand, typeStr(type), false);
|
|
2019
|
+
},
|
|
2020
|
+
|
|
2021
|
+
// `cap` takes a slice or an array (a pointer to an array is dereferenced
|
|
2022
|
+
// before this check). A type parameter needs a slice or array core type;
|
|
2023
|
+
// one with no single core type is left to instantiation. Unknown and
|
|
2024
|
+
// `any` operands (JS interop) are not judged.
|
|
2025
|
+
_capOperandOk(t, scope) {
|
|
2026
|
+
if (!t || t._tainted || isAny(t)) return true;
|
|
2027
|
+
if (t.kind === "typeParam") {
|
|
2028
|
+
const core = this._coreType(t, scope);
|
|
2029
|
+
return !core || core.kind === "slice" || core.kind === "array";
|
|
2030
|
+
}
|
|
2031
|
+
const kind = this._deepUnderlying(t)?.kind;
|
|
2032
|
+
return kind === "slice" || kind === "array";
|
|
2033
|
+
},
|
|
2034
|
+
|
|
2035
|
+
_checkBuiltinLen(name, expr, argTypes, scope) {
|
|
1882
2036
|
this._rejectSeqAsSlice(argTypes[0], expr.args?.[0], `${name}()`);
|
|
1883
2037
|
const operand = expr.args?.[0];
|
|
1884
2038
|
if (expr.args?.length === 1)
|
|
@@ -1891,17 +2045,14 @@ export const expressionCheckMethods = {
|
|
|
1891
2045
|
if (expr.args.length === 1) expr.args[0] = operand;
|
|
1892
2046
|
}
|
|
1893
2047
|
if (name === "cap") {
|
|
1894
|
-
if (
|
|
1895
|
-
(argTypes[0]?.underlying ?? argTypes[0])?.kind === "map" ||
|
|
1896
|
-
isString(argTypes[0])
|
|
1897
|
-
) {
|
|
2048
|
+
if (sharedInfo(argTypes[0])) {
|
|
1898
2049
|
this.err(
|
|
1899
|
-
`
|
|
2050
|
+
`cannot use cap() on ${typeStr(argTypes[0])}: shared buffers have a fixed size; use len()`,
|
|
1900
2051
|
expr,
|
|
1901
2052
|
);
|
|
1902
|
-
} else if (
|
|
2053
|
+
} else if (!this._capOperandOk(argTypes[0], scope)) {
|
|
1903
2054
|
this.err(
|
|
1904
|
-
`
|
|
2055
|
+
`invalid argument: ${exprText(expr.args?.[0])} (${this._capOperandKind(expr.args?.[0], argTypes[0])}) for cap`,
|
|
1905
2056
|
expr,
|
|
1906
2057
|
);
|
|
1907
2058
|
}
|
|
@@ -1935,6 +2086,9 @@ export const expressionCheckMethods = {
|
|
|
1935
2086
|
expr,
|
|
1936
2087
|
);
|
|
1937
2088
|
}
|
|
2089
|
+
// An untyped operand takes type float32 (`complex(f, 0.01)`).
|
|
2090
|
+
this._tagFloat32Const(FLOAT32, crt, expr.args[0]);
|
|
2091
|
+
this._tagFloat32Const(FLOAT32, cit, expr.args[1]);
|
|
1938
2092
|
return COMPLEX64;
|
|
1939
2093
|
}
|
|
1940
2094
|
return COMPLEX128;
|
package/src/typechecker/index.js
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
isBool,
|
|
30
30
|
isVoid,
|
|
31
31
|
NATIVES,
|
|
32
|
+
promotedMethods,
|
|
32
33
|
Scope,
|
|
33
34
|
TAINTED_ANY,
|
|
34
35
|
TypeCheckError,
|
|
@@ -99,6 +100,50 @@ const JS_BUILTIN_CLASSES = new Set([
|
|
|
99
100
|
"WebAssembly",
|
|
100
101
|
]);
|
|
101
102
|
|
|
103
|
+
// The promoted-method entry for candidate `cand`, reusing `prev` when
|
|
104
|
+
// nothing changed so the method keeps its identity across passes. A pointer
|
|
105
|
+
// method reached through an embedded pointer is in the value method set.
|
|
106
|
+
function promotedEntry(cand, prev) {
|
|
107
|
+
if (cand.ambiguous) return { depth: cand.depth, ambiguous: true };
|
|
108
|
+
if (
|
|
109
|
+
!prev?.ambiguous &&
|
|
110
|
+
prev?.source === cand.source &&
|
|
111
|
+
prev.viaPtr === cand.viaPtr &&
|
|
112
|
+
prev.depth === cand.depth
|
|
113
|
+
)
|
|
114
|
+
return prev;
|
|
115
|
+
const mType =
|
|
116
|
+
cand.viaPtr && cand.source?._ptrRecv
|
|
117
|
+
? { ...cand.source, _ptrRecv: false }
|
|
118
|
+
: cand.source;
|
|
119
|
+
return { ...cand, mType };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Offers every method of the embedded interface `iface` at depth 1.
|
|
123
|
+
function offerInterfaceMethods(iface, offer) {
|
|
124
|
+
for (const [mName, mType] of iface.methods ?? [])
|
|
125
|
+
offer(mName, 1, mType, false);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Replaces the promoted methods `old` with `next` in `methods`, leaving
|
|
129
|
+
// the struct's own methods alone; returns whether anything changed.
|
|
130
|
+
function applyPromotions(methods, old, next, isOwn) {
|
|
131
|
+
let changed = old.size !== next.size;
|
|
132
|
+
for (const [mName, prev] of old) {
|
|
133
|
+
const entry = next.get(mName);
|
|
134
|
+
if (entry?.mType) continue;
|
|
135
|
+
if (prev.mType && !isOwn(mName)) methods.delete(mName);
|
|
136
|
+
if (entry?.ambiguous !== prev.ambiguous || entry?.depth !== prev.depth)
|
|
137
|
+
changed = true;
|
|
138
|
+
}
|
|
139
|
+
for (const [mName, entry] of next) {
|
|
140
|
+
if (entry.ambiguous) continue;
|
|
141
|
+
if (old.get(mName)?.mType !== entry.mType) changed = true;
|
|
142
|
+
methods.set(mName, entry.mType);
|
|
143
|
+
}
|
|
144
|
+
return changed;
|
|
145
|
+
}
|
|
146
|
+
|
|
102
147
|
export class TypeChecker {
|
|
103
148
|
constructor() {
|
|
104
149
|
this.types = new Map(); // named types
|
|
@@ -130,6 +175,8 @@ export class TypeChecker {
|
|
|
130
175
|
this._constExprs = new Map(); // name -> AST initializer expr
|
|
131
176
|
this._importedConsts = new Map(); // dot-imported const name -> value
|
|
132
177
|
this._externalNames = new Set(); // package-scope names from .d.ts / npm definitions
|
|
178
|
+
this._importedTypes = new WeakSet(); // named types reached through an imported package
|
|
179
|
+
this._pendingConstraintUses = []; // constraint-interface checks waiting on a type declaration
|
|
133
180
|
this._setupGlobals();
|
|
134
181
|
// Register error as a named type (interface)
|
|
135
182
|
this.types.set("error", ERROR);
|
|
@@ -189,6 +236,7 @@ export class TypeChecker {
|
|
|
189
236
|
// otherwise reuse the imported placeholder. It is reached as `pkg.Pt`.
|
|
190
237
|
if (type.name === name) this.types.set(name, type);
|
|
191
238
|
this.types.set(`${pkgName}.${name}`, type);
|
|
239
|
+
this._importedTypes.add(type);
|
|
192
240
|
}
|
|
193
241
|
}
|
|
194
242
|
|
|
@@ -383,13 +431,17 @@ export class TypeChecker {
|
|
|
383
431
|
|
|
384
432
|
this._checkDirectivesAndImportsPass(programs);
|
|
385
433
|
this._registerConstExprs(programs);
|
|
434
|
+
this._registerVarSpecs(programs);
|
|
435
|
+
this._markConstraintPositions(programs);
|
|
386
436
|
this._collectTypesPass(programs);
|
|
437
|
+
this._flushConstraintUses();
|
|
387
438
|
this._collectFuncsPass(programs);
|
|
388
439
|
this._checkGlobalTypeNamesPass(programs);
|
|
389
440
|
this._promoteEmbeddedMethods();
|
|
390
441
|
this._collectVarsConstsPass(programs);
|
|
391
442
|
this._checkTopDeclsPass(programs);
|
|
392
443
|
this._reportInitCycles(programs);
|
|
444
|
+
this._flushConstraintUses();
|
|
393
445
|
|
|
394
446
|
if (this.target === "both") {
|
|
395
447
|
this._checkMutablePackageVars(programs);
|
|
@@ -707,6 +759,19 @@ export class TypeChecker {
|
|
|
707
759
|
}
|
|
708
760
|
}
|
|
709
761
|
|
|
762
|
+
// Package variables by name, so an array length such as `[len(base)]T`
|
|
763
|
+
// can use the type of one that is declared later.
|
|
764
|
+
_registerVarSpecs(programs) {
|
|
765
|
+
this._pkgVarSpecs = new Map();
|
|
766
|
+
for (const p of programs)
|
|
767
|
+
for (const d of p.decls)
|
|
768
|
+
if (d.kind === "VarDecl")
|
|
769
|
+
for (const spec of d.decls)
|
|
770
|
+
spec.names.forEach((name, index) => {
|
|
771
|
+
this._pkgVarSpecs.set(name, { spec, index, busy: false });
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
|
|
710
775
|
_collectVarsConstsPass(programs) {
|
|
711
776
|
for (const p of programs) {
|
|
712
777
|
this._setCurrentFile(p);
|
|
@@ -798,45 +863,100 @@ export class TypeChecker {
|
|
|
798
863
|
}
|
|
799
864
|
}
|
|
800
865
|
const count = () =>
|
|
801
|
-
structs.reduce(
|
|
802
|
-
(n, st) => n + (st?.methods?.size ?? 0) + (st?.fields?.size ?? 0),
|
|
803
|
-
0,
|
|
804
|
-
);
|
|
866
|
+
structs.reduce((n, st) => n + (st?.fields?.size ?? 0), 0);
|
|
805
867
|
let before;
|
|
868
|
+
let changed;
|
|
806
869
|
do {
|
|
807
870
|
before = count();
|
|
808
|
-
|
|
809
|
-
|
|
871
|
+
changed = false;
|
|
872
|
+
for (const st of structs)
|
|
873
|
+
if (this._promoteEmbeddedInto(st)) changed = true;
|
|
874
|
+
} while (changed || count() !== before);
|
|
810
875
|
}
|
|
811
876
|
|
|
877
|
+
// Recomputes the methods `struct` gets from its embedded fields, as Go's
|
|
878
|
+
// method set: the shallowest method of each name wins, a method declared
|
|
879
|
+
// on the struct itself shadows every promoted one, and a pointer method
|
|
880
|
+
// reached through an embedded pointer belongs to the value method set.
|
|
881
|
+
// Returns whether the promoted methods changed.
|
|
812
882
|
_promoteEmbeddedInto(struct) {
|
|
813
|
-
if (struct?.kind !== "struct" || !struct._embeds) return;
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
883
|
+
if (struct?.kind !== "struct" || !struct._embeds) return false;
|
|
884
|
+
const old = promotedMethods.get(struct) ?? new Map();
|
|
885
|
+
const best = new Map();
|
|
886
|
+
for (const embed of struct._embeds)
|
|
887
|
+
this._offerEmbedMethods(struct, embed, best);
|
|
888
|
+
if (best.size === 0 && old.size === 0) return false;
|
|
889
|
+
struct.methods ??= new Map();
|
|
890
|
+
// A method declared on the struct (or set since the last pass) is
|
|
891
|
+
// not one this pass promoted.
|
|
892
|
+
const isOwn = (mName) =>
|
|
893
|
+
struct.methods.has(mName) &&
|
|
894
|
+
struct.methods.get(mName) !== old.get(mName)?.mType;
|
|
895
|
+
const next = new Map();
|
|
896
|
+
for (const [mName, cand] of best)
|
|
897
|
+
if (!isOwn(mName)) next.set(mName, promotedEntry(cand, old.get(mName)));
|
|
898
|
+
promotedMethods.set(struct, next);
|
|
899
|
+
return applyPromotions(struct.methods, old, next, isOwn);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Adds the methods one embedded field offers to `best` (name -> the
|
|
903
|
+
// shallowest candidate), and copies its fields into the embedder.
|
|
904
|
+
_offerEmbedMethods(struct, embed, best) {
|
|
905
|
+
const isPtr = embed.kind === "pointer";
|
|
906
|
+
const inner = isPtr ? embed.base : embed;
|
|
907
|
+
const base = inner.kind === "named" ? inner.underlying : inner;
|
|
908
|
+
// Two candidates at the same depth make the name ambiguous, as does
|
|
909
|
+
// an ambiguous name of the embedded type.
|
|
910
|
+
const offer = (name, depth, source, viaPtr, ambiguous = false) => {
|
|
911
|
+
const cur = best.get(name);
|
|
912
|
+
if (!cur || depth < cur.depth)
|
|
913
|
+
best.set(name, { depth, source, viaPtr, ambiguous });
|
|
914
|
+
else if (depth === cur.depth) cur.ambiguous = true;
|
|
915
|
+
};
|
|
916
|
+
if (base?.kind === "struct") {
|
|
917
|
+
this._offerStructEmbed(struct, base, isPtr, offer);
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
// An embedded interface (named, literal or `error`) promotes every
|
|
921
|
+
// method of its method set, as a value method.
|
|
922
|
+
if (base?.kind === "interface") {
|
|
923
|
+
if (!isPtr) offerInterfaceMethods(base, offer);
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
// A named non-struct type (`time.Duration`) keeps its methods on the
|
|
927
|
+
// named type itself.
|
|
928
|
+
if (inner.kind === "named")
|
|
929
|
+
for (const [mName, mType] of inner.methods ?? [])
|
|
930
|
+
offer(mName, 1, mType, isPtr);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// Offers the methods of the embedded struct `base`, and copies its fields
|
|
934
|
+
// into the embedder.
|
|
935
|
+
_offerStructEmbed(struct, base, isPtr, offer) {
|
|
936
|
+
// Fields of an embedded type declared after the embedder were not
|
|
937
|
+
// there when the embedder's fields were collected.
|
|
938
|
+
if (struct.fields)
|
|
939
|
+
for (const [k, v] of base.fields)
|
|
940
|
+
if (!struct.fields.has(k)) struct.fields.set(k, v);
|
|
941
|
+
const innerPromoted = promotedMethods.get(base);
|
|
942
|
+
for (const [mName, mType] of base.methods ?? []) {
|
|
943
|
+
const p = innerPromoted?.get(mName);
|
|
944
|
+
offer(
|
|
945
|
+
mName,
|
|
946
|
+
1 + (p?.depth ?? 0),
|
|
947
|
+
p?.source ?? mType,
|
|
948
|
+
isPtr || Boolean(p?.viaPtr),
|
|
949
|
+
);
|
|
834
950
|
}
|
|
951
|
+
for (const [mName, p] of innerPromoted ?? [])
|
|
952
|
+
if (p.ambiguous) offer(mName, 1 + p.depth, null, false, true);
|
|
835
953
|
}
|
|
836
954
|
|
|
837
955
|
// ── Type collection ──────────────────────────────────────────
|
|
838
956
|
|
|
839
957
|
collectType(decl) {
|
|
958
|
+
// `type N interface{ ~int }` and `type D N` declare constraints.
|
|
959
|
+
this._markConstraintPos(decl.type);
|
|
840
960
|
if (decl.typeParams && decl.isAlias) {
|
|
841
961
|
this._collectGenericAlias(decl);
|
|
842
962
|
return;
|
|
@@ -897,8 +1017,10 @@ export class TypeChecker {
|
|
|
897
1017
|
return;
|
|
898
1018
|
}
|
|
899
1019
|
const existing = this.types.get(decl.name);
|
|
1020
|
+
// An imported package's type is never reused: a local declaration of
|
|
1021
|
+
// the same name gets its own type and leaves `pkg.T` untouched.
|
|
900
1022
|
const named =
|
|
901
|
-
existing?.kind === "named"
|
|
1023
|
+
existing?.kind === "named" && !this._importedTypes.has(existing)
|
|
902
1024
|
? existing
|
|
903
1025
|
: { kind: "named", name: decl.name };
|
|
904
1026
|
named.underlying = underlying;
|
|
@@ -917,7 +1039,8 @@ export class TypeChecker {
|
|
|
917
1039
|
}
|
|
918
1040
|
|
|
919
1041
|
_registerLocalTypePlaceholder(decl) {
|
|
920
|
-
|
|
1042
|
+
const prior = this.types.get(decl.name);
|
|
1043
|
+
if (decl.isAlias || (prior && !this._importedTypes.has(prior))) return;
|
|
921
1044
|
const placeholder = { kind: "named", name: decl.name, underlying: null };
|
|
922
1045
|
this.types.set(decl.name, placeholder);
|
|
923
1046
|
this.globals.define(decl.name, placeholder);
|
|
@@ -1510,6 +1633,12 @@ export class TypeChecker {
|
|
|
1510
1633
|
);
|
|
1511
1634
|
// `const D Duration = 1` is a BigInt in JS.
|
|
1512
1635
|
if (spec.type && expr) this._tag64BitNode(this.resolveType(type), expr);
|
|
1636
|
+
if (spec.type && expr)
|
|
1637
|
+
this._tagFloat32Const(
|
|
1638
|
+
this.resolveType(type),
|
|
1639
|
+
valTypes[i] ?? valTypes[0],
|
|
1640
|
+
expr,
|
|
1641
|
+
);
|
|
1513
1642
|
scope.defineConst(name, type, expr);
|
|
1514
1643
|
}
|
|
1515
1644
|
}
|
|
@@ -16,6 +16,7 @@ function litText(node) {
|
|
|
16
16
|
const PRINTERS = {
|
|
17
17
|
Ident: (n) => n.name,
|
|
18
18
|
BasicLit: litText,
|
|
19
|
+
ImagLit: (n) => `${n.value}i`,
|
|
19
20
|
ParenExpr: (n) => `(${exprText(n.expr)})`,
|
|
20
21
|
SelectorExpr: (n) => `${exprText(n.expr)}.${n.field}`,
|
|
21
22
|
UnaryExpr: (n) => `${n.op}${exprText(n.operand)}`,
|