gofront 0.0.8 → 1.0.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 +88 -12
- package/package.json +8 -4
- package/src/codegen/expressions.js +520 -1717
- package/src/{codegen.js → codegen/index.js} +155 -184
- package/src/codegen/runtime.js +151 -0
- package/src/codegen/statements.js +305 -296
- package/src/codegen/stdlib/builder.js +36 -0
- package/src/codegen/stdlib/bytes.js +62 -0
- package/src/codegen/stdlib/errors.js +17 -0
- package/src/codegen/stdlib/fmt.js +101 -0
- package/src/codegen/stdlib/gom.js +152 -0
- package/src/codegen/stdlib/html.js +17 -0
- package/src/codegen/stdlib/index.js +95 -0
- package/src/codegen/stdlib/io.js +33 -0
- package/src/codegen/stdlib/maps.js +42 -0
- package/src/codegen/stdlib/math.js +53 -0
- package/src/codegen/stdlib/os.js +10 -0
- package/src/codegen/stdlib/path.js +31 -0
- package/src/codegen/stdlib/rand.js +24 -0
- package/src/codegen/stdlib/regexp.js +51 -0
- package/src/codegen/stdlib/slices.js +48 -0
- package/src/codegen/stdlib/sort.js +28 -0
- package/src/codegen/stdlib/strconv.js +30 -0
- package/src/codegen/stdlib/strings.js +83 -0
- package/src/codegen/stdlib/time.js +54 -0
- package/src/codegen/stdlib/unicode.js +22 -0
- package/src/codegen/stdlib/utf8.js +23 -0
- package/src/codegen/templ.js +281 -0
- package/src/compiler.js +102 -62
- package/src/dts-parser.js +355 -371
- package/src/index.js +22 -54
- package/src/lexer.js +200 -286
- package/src/minifier.js +305 -318
- package/src/parser/expressions.js +54 -36
- package/src/{parser.js → parser/index.js} +12 -5
- package/src/parser/statements.js +29 -35
- package/src/parser/types.js +31 -29
- package/src/templ-lexer.js +737 -0
- package/src/templ-parser.js +235 -0
- package/src/tokens.js +109 -0
- package/src/typechecker/assignability.js +258 -0
- package/src/typechecker/expressions.js +559 -549
- package/src/typechecker/index.js +555 -0
- package/src/typechecker/resolve.js +421 -0
- package/src/typechecker/statements.js +267 -280
- package/src/typechecker/stdlib/core.js +592 -0
- package/src/typechecker/stdlib/extended.js +332 -0
- package/src/typechecker/stdlib.js +9 -1060
- package/src/typechecker/termination.js +83 -0
- package/src/typechecker/types.js +6 -3
- package/src/typechecker.js +0 -1271
|
@@ -26,6 +26,60 @@ import {
|
|
|
26
26
|
VOID,
|
|
27
27
|
} from "./types.js";
|
|
28
28
|
|
|
29
|
+
// Dispatch table for builtin function type checking.
|
|
30
|
+
// Each value is (self, name, expr, argTypes, scope) => returnType.
|
|
31
|
+
const BUILTIN_CHECK = {
|
|
32
|
+
len: (s, n, e, at) => s._checkBuiltinLen(n, e, at),
|
|
33
|
+
cap: (s, n, e, at) => s._checkBuiltinLen(n, e, at),
|
|
34
|
+
append: (s, _n, e, at) => {
|
|
35
|
+
const st = at[0] ?? ANY;
|
|
36
|
+
if (isArray(st)) {
|
|
37
|
+
s.err(`cannot append to array (type ${typeStr(st)})`, e);
|
|
38
|
+
return st;
|
|
39
|
+
}
|
|
40
|
+
return st;
|
|
41
|
+
},
|
|
42
|
+
copy: () => INT,
|
|
43
|
+
delete: () => VOID,
|
|
44
|
+
make: (s, _n, e, _at, sc) => {
|
|
45
|
+
if (e.args.length < 1) return ANY;
|
|
46
|
+
const ta = e.args[0];
|
|
47
|
+
const tn = ta.kind === "TypeExpr" ? ta.type : ta;
|
|
48
|
+
return s.resolveTypeNode(tn, sc) ?? ANY;
|
|
49
|
+
},
|
|
50
|
+
new: (s, _n, e, _at, sc) => {
|
|
51
|
+
if (e.args.length < 1) return ANY;
|
|
52
|
+
const inner = s.resolveTypeNode(e.args[0], sc);
|
|
53
|
+
return { kind: "pointer", base: inner };
|
|
54
|
+
},
|
|
55
|
+
print: () => VOID,
|
|
56
|
+
println: () => VOID,
|
|
57
|
+
panic: () => VOID,
|
|
58
|
+
recover: () => ANY,
|
|
59
|
+
error: () => ERROR,
|
|
60
|
+
min: (_s, _n, _e, at) => at[0] ?? ANY,
|
|
61
|
+
max: (_s, _n, _e, at) => at[0] ?? ANY,
|
|
62
|
+
clear: () => VOID,
|
|
63
|
+
complex: (s, _n, e, at) => s._checkBuiltinComplex(e, at),
|
|
64
|
+
real: (s, n, e, at) => s._checkBuiltinRealImag(n, e, at),
|
|
65
|
+
imag: (s, n, e, at) => s._checkBuiltinRealImag(n, e, at),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Method-name dispatch for _checkExpr — string values add no static call edges.
|
|
69
|
+
const CHECK_EXPR_DELEGATE = {
|
|
70
|
+
UnaryExpr: "_checkUnaryExpr",
|
|
71
|
+
CallExpr: "checkCall",
|
|
72
|
+
SelectorExpr: "_checkSelectorExpr",
|
|
73
|
+
IndexExpr: "_checkIndexExpr",
|
|
74
|
+
SliceExpr: "_checkSliceExpr",
|
|
75
|
+
CompositeLit: "checkCompositeLit",
|
|
76
|
+
FuncLit: "_checkFuncLit",
|
|
77
|
+
TypeConversion: "_checkTypeConversion",
|
|
78
|
+
TypeAssertExpr: "_checkTypeAssertExpr",
|
|
79
|
+
RangeExpr: "_checkRangeExpr",
|
|
80
|
+
InstantiationExpr: "_checkInstantiationExpr",
|
|
81
|
+
};
|
|
82
|
+
|
|
29
83
|
export const expressionCheckMethods = {
|
|
30
84
|
checkExpr(expr, scope) {
|
|
31
85
|
const t = this._checkExpr(expr, scope);
|
|
@@ -50,49 +104,14 @@ export const expressionCheckMethods = {
|
|
|
50
104
|
}
|
|
51
105
|
break;
|
|
52
106
|
}
|
|
53
|
-
|
|
54
107
|
case "ImagLit":
|
|
55
108
|
return UNTYPED_COMPLEX;
|
|
56
|
-
|
|
57
109
|
case "Ident": {
|
|
58
110
|
const t = scope.lookup(expr.name);
|
|
59
111
|
if (!t) return this.err(`Undefined: '${expr.name}'`, expr);
|
|
60
|
-
// Mark type-name identifiers so SelectorExpr can detect method expressions
|
|
61
112
|
if (this.types.has(expr.name)) expr._isTypeRef = true;
|
|
62
113
|
return t;
|
|
63
114
|
}
|
|
64
|
-
|
|
65
|
-
case "UnaryExpr": {
|
|
66
|
-
const ot = this.checkExpr(expr.operand, scope);
|
|
67
|
-
if (expr.op === "!") {
|
|
68
|
-
if (!isBool(ot) && !isAny(ot)) this.err("! requires bool", expr);
|
|
69
|
-
return BOOL;
|
|
70
|
-
}
|
|
71
|
-
if (expr.op === "-" || expr.op === "+") {
|
|
72
|
-
if (!isNumeric(ot) && !isComplex(ot) && !isAny(ot))
|
|
73
|
-
this.err(`${expr.op} requires numeric`, expr);
|
|
74
|
-
return ot;
|
|
75
|
-
}
|
|
76
|
-
if (expr.op === "&") {
|
|
77
|
-
// Mark the operand as address-taken for codegen
|
|
78
|
-
if (expr.operand.kind === "Ident") {
|
|
79
|
-
expr.operand._addressTaken = true;
|
|
80
|
-
const sym = scope.lookup(expr.operand.name);
|
|
81
|
-
if (sym) sym._addressTaken = true;
|
|
82
|
-
}
|
|
83
|
-
return { kind: "pointer", base: ot };
|
|
84
|
-
}
|
|
85
|
-
if (expr.op === "*") {
|
|
86
|
-
if (ot === ANY) return ANY;
|
|
87
|
-
if (ot.kind === "pointer") return ot.base;
|
|
88
|
-
if (ot.kind === "named" && ot.underlying?.kind === "pointer")
|
|
89
|
-
return ot.underlying.base;
|
|
90
|
-
this.err(`cannot dereference non-pointer type ${typeStr(ot)}`, expr);
|
|
91
|
-
return ANY;
|
|
92
|
-
}
|
|
93
|
-
return ot;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
115
|
case "BinaryExpr": {
|
|
97
116
|
const lt = this.checkExpr(expr.left, scope);
|
|
98
117
|
const rt = this.checkExpr(expr.right, scope);
|
|
@@ -100,285 +119,298 @@ export const expressionCheckMethods = {
|
|
|
100
119
|
return this.binaryResultType(expr.op, ANY, ANY, expr);
|
|
101
120
|
return this.binaryResultType(expr.op, lt, rt, expr);
|
|
102
121
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
122
|
+
case "AwaitExpr":
|
|
123
|
+
return this.checkExpr(expr.expr, scope);
|
|
124
|
+
case "InstantiationExpr":
|
|
125
|
+
return this._checkInstantiationExpr(expr, scope);
|
|
126
|
+
default: {
|
|
127
|
+
const m = CHECK_EXPR_DELEGATE[expr.kind];
|
|
128
|
+
if (m) return this[m](expr, scope);
|
|
129
|
+
return ANY;
|
|
106
130
|
}
|
|
131
|
+
}
|
|
132
|
+
return ANY;
|
|
133
|
+
},
|
|
107
134
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
methodType = base.methods.get(expr.field);
|
|
117
|
-
} else if (
|
|
118
|
-
baseType.kind === "named" &&
|
|
119
|
-
baseType.methods?.has(expr.field)
|
|
120
|
-
) {
|
|
121
|
-
methodType = baseType.methods.get(expr.field);
|
|
122
|
-
}
|
|
123
|
-
if (methodType) {
|
|
124
|
-
// Return a func type with the receiver prepended to params
|
|
125
|
-
expr._isMethodExpr = true;
|
|
126
|
-
expr._methodExprRecv = baseType;
|
|
127
|
-
return {
|
|
128
|
-
kind: "func",
|
|
129
|
-
params: [baseType, ...(methodType.params ?? [])],
|
|
130
|
-
returns: methodType.returns ?? [],
|
|
131
|
-
variadic: methodType.variadic,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
const ft = this.fieldType(baseType, expr.field, expr);
|
|
136
|
-
// Mark method selectors so codegen can emit .bind() when used as a value
|
|
137
|
-
if (ft?.kind === "func") {
|
|
138
|
-
let base = baseType.kind === "named" ? baseType.underlying : baseType;
|
|
139
|
-
base = this.resolveType(base);
|
|
140
|
-
if (
|
|
141
|
-
(base?.kind === "struct" && base.methods?.has(expr.field)) ||
|
|
142
|
-
(baseType.kind === "named" && baseType.methods?.has(expr.field))
|
|
143
|
-
) {
|
|
144
|
-
expr._isMethodValue = true;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return ft;
|
|
148
|
-
}
|
|
135
|
+
_checkDeref(ot, expr) {
|
|
136
|
+
if (ot === ANY) return ANY;
|
|
137
|
+
if (ot.kind === "pointer") return ot.base;
|
|
138
|
+
if (ot.kind === "named" && ot.underlying?.kind === "pointer")
|
|
139
|
+
return ot.underlying.base;
|
|
140
|
+
this.err(`cannot dereference non-pointer type ${typeStr(ot)}`, expr);
|
|
141
|
+
return ANY;
|
|
142
|
+
},
|
|
149
143
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
// Named types (e.g. type Nums []int) are indexable via their underlying
|
|
155
|
-
const btu =
|
|
156
|
-
bt.kind === "named"
|
|
157
|
-
? (this.resolveType(bt.underlying) ?? bt.underlying)
|
|
158
|
-
: bt;
|
|
159
|
-
if (btu.kind === "slice" || btu.kind === "array") {
|
|
160
|
-
// Compile-time bounds checking for arrays
|
|
161
|
-
if (btu.kind === "array" && btu.size != null) {
|
|
162
|
-
const idx = this._constIntValue(expr.index);
|
|
163
|
-
if (idx !== null) {
|
|
164
|
-
if (idx < 0) {
|
|
165
|
-
this.err(
|
|
166
|
-
`invalid array index ${idx} (index must not be negative)`,
|
|
167
|
-
expr,
|
|
168
|
-
);
|
|
169
|
-
} else if (idx >= btu.size) {
|
|
170
|
-
this.err(
|
|
171
|
-
`invalid array index ${idx} (out of bounds for ${typeStr(bt)})`,
|
|
172
|
-
expr,
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return btu.elem;
|
|
178
|
-
}
|
|
179
|
-
if (btu.kind === "map") {
|
|
180
|
-
expr._mapValueType = btu.value; // for codegen zero-value fallback
|
|
181
|
-
return btu.value;
|
|
182
|
-
}
|
|
183
|
-
if (isString(bt)) return INT; // byte
|
|
184
|
-
return this.err(`Cannot index type ${typeStr(bt)}`, expr);
|
|
185
|
-
}
|
|
144
|
+
_checkBoolUnaryOp(ot, expr) {
|
|
145
|
+
if (!isBool(ot) && !isAny(ot)) this.err("! requires bool", expr);
|
|
146
|
+
return BOOL;
|
|
147
|
+
},
|
|
186
148
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (isAny(bt)) return ANY;
|
|
193
|
-
if (bt.kind === "slice" || bt.kind === "array")
|
|
194
|
-
return { kind: "slice", elem: bt.elem };
|
|
195
|
-
if (isString(bt)) return STRING;
|
|
196
|
-
return this.err(`Cannot slice type ${typeStr(bt)}`, expr);
|
|
197
|
-
}
|
|
149
|
+
_checkNumericUnaryOp(ot, expr) {
|
|
150
|
+
if (!isNumeric(ot) && !isComplex(ot) && !isAny(ot))
|
|
151
|
+
this.err(`${expr.op} requires numeric`, expr);
|
|
152
|
+
return ot;
|
|
153
|
+
},
|
|
198
154
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
155
|
+
_checkAddressOfExpr(expr, scope, ot) {
|
|
156
|
+
if (expr.operand.kind === "Ident") {
|
|
157
|
+
expr.operand._addressTaken = true;
|
|
158
|
+
const sym = scope.lookup(expr.operand.name);
|
|
159
|
+
if (sym) sym._addressTaken = true;
|
|
160
|
+
}
|
|
161
|
+
return { kind: "pointer", base: ot };
|
|
162
|
+
},
|
|
202
163
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
164
|
+
_checkUnaryExpr(expr, scope) {
|
|
165
|
+
const ot = this.checkExpr(expr.operand, scope);
|
|
166
|
+
if (expr.op === "!") return this._checkBoolUnaryOp(ot, expr);
|
|
167
|
+
if (expr.op === "-" || expr.op === "+")
|
|
168
|
+
return this._checkNumericUnaryOp(ot, expr);
|
|
169
|
+
if (expr.op === "&") return this._checkAddressOfExpr(expr, scope, ot);
|
|
170
|
+
if (expr.op === "*") return this._checkDeref(ot, expr);
|
|
171
|
+
return ot;
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
_isStructOrNamedMethod(baseType, base, field) {
|
|
175
|
+
return (
|
|
176
|
+
(base?.kind === "struct" && base.methods?.has(field)) ||
|
|
177
|
+
(baseType.kind === "named" && baseType.methods?.has(field))
|
|
178
|
+
);
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
_resolveMethodExprType(baseType, base, field) {
|
|
182
|
+
if (!this._isStructOrNamedMethod(baseType, base, field)) return null;
|
|
183
|
+
return base?.methods?.get(field) ?? baseType.methods?.get(field);
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
_checkSelectorExpr(expr, scope) {
|
|
187
|
+
const baseType = this.checkExpr(expr.expr, scope);
|
|
188
|
+
if (expr.expr._isTypeRef) {
|
|
189
|
+
let base = baseType.kind === "named" ? baseType.underlying : baseType;
|
|
190
|
+
base = this.resolveType(base);
|
|
191
|
+
const methodType = this._resolveMethodExprType(
|
|
192
|
+
baseType,
|
|
193
|
+
base,
|
|
194
|
+
expr.field,
|
|
195
|
+
);
|
|
196
|
+
if (methodType) {
|
|
197
|
+
expr._isMethodExpr = true;
|
|
198
|
+
expr._methodExprRecv = baseType;
|
|
222
199
|
return {
|
|
223
200
|
kind: "func",
|
|
224
|
-
params:
|
|
225
|
-
returns: [
|
|
226
|
-
|
|
201
|
+
params: [baseType, ...(methodType.params ?? [])],
|
|
202
|
+
returns: methodType.returns ?? [],
|
|
203
|
+
variadic: methodType.variadic,
|
|
227
204
|
};
|
|
228
205
|
}
|
|
206
|
+
}
|
|
207
|
+
const ft = this.fieldType(baseType, expr.field, expr);
|
|
208
|
+
if (ft?.kind === "func") {
|
|
209
|
+
let base = baseType.kind === "named" ? baseType.underlying : baseType;
|
|
210
|
+
base = this.resolveType(base);
|
|
211
|
+
if (this._isStructOrNamedMethod(baseType, base, expr.field))
|
|
212
|
+
expr._isMethodValue = true;
|
|
213
|
+
}
|
|
214
|
+
return ft;
|
|
215
|
+
},
|
|
229
216
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
217
|
+
_checkArrayBoundsExpr(btu, bt, expr) {
|
|
218
|
+
if (btu.kind !== "array" || btu.size == null) return;
|
|
219
|
+
const idx = this._constIntValue(expr.index);
|
|
220
|
+
if (idx === null) return;
|
|
221
|
+
if (idx < 0)
|
|
222
|
+
this.err(`invalid array index ${idx} (index must not be negative)`, expr);
|
|
223
|
+
else if (idx >= btu.size)
|
|
224
|
+
this.err(
|
|
225
|
+
`invalid array index ${idx} (out of bounds for ${typeStr(bt)})`,
|
|
226
|
+
expr,
|
|
227
|
+
);
|
|
228
|
+
},
|
|
234
229
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
expr,
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
// Slice → array conversion: [N]T(slice)
|
|
259
|
-
if (tgtResolved?.kind === "array" && srcResolved?.kind === "slice") {
|
|
260
|
-
const tgtElem = tgtResolved.elem;
|
|
261
|
-
const srcElem = srcResolved.elem;
|
|
262
|
-
if (
|
|
263
|
-
tgtElem &&
|
|
264
|
-
srcElem &&
|
|
265
|
-
!isAny(tgtElem) &&
|
|
266
|
-
!isAny(srcElem) &&
|
|
267
|
-
typeStr(tgtElem) !== typeStr(srcElem)
|
|
268
|
-
) {
|
|
269
|
-
this.err(
|
|
270
|
-
`cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
|
|
271
|
-
expr,
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
return targetType;
|
|
275
|
-
}
|
|
276
|
-
// Array → slice conversion: []T(array)
|
|
277
|
-
if (tgtResolved?.kind === "slice" && srcResolved?.kind === "array") {
|
|
278
|
-
const tgtElem = tgtResolved.elem;
|
|
279
|
-
const srcElem = srcResolved.elem;
|
|
280
|
-
if (
|
|
281
|
-
tgtElem &&
|
|
282
|
-
srcElem &&
|
|
283
|
-
!isAny(tgtElem) &&
|
|
284
|
-
!isAny(srcElem) &&
|
|
285
|
-
typeStr(tgtElem) !== typeStr(srcElem)
|
|
286
|
-
) {
|
|
287
|
-
this.err(
|
|
288
|
-
`cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
|
|
289
|
-
expr,
|
|
290
|
-
);
|
|
291
|
-
}
|
|
292
|
-
return targetType;
|
|
293
|
-
}
|
|
294
|
-
// Reject conversions between array and incompatible types
|
|
295
|
-
if (tgtResolved?.kind === "array" && srcResolved?.kind !== "array") {
|
|
296
|
-
if (!isAny(srcType)) {
|
|
297
|
-
this.err(
|
|
298
|
-
`cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
|
|
299
|
-
expr,
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
return targetType;
|
|
304
|
-
}
|
|
230
|
+
_checkIndexExpr(expr, scope) {
|
|
231
|
+
const bt = this.checkExpr(expr.expr, scope);
|
|
232
|
+
this.checkExpr(expr.index, scope);
|
|
233
|
+
if (isAny(bt)) return ANY;
|
|
234
|
+
const btu =
|
|
235
|
+
bt.kind === "named"
|
|
236
|
+
? (this.resolveType(bt.underlying) ?? bt.underlying)
|
|
237
|
+
: bt;
|
|
238
|
+
if (btu.kind === "slice" || btu.kind === "array") {
|
|
239
|
+
this._checkArrayBoundsExpr(btu, bt, expr);
|
|
240
|
+
return btu.elem;
|
|
241
|
+
}
|
|
242
|
+
if (btu.kind === "map") {
|
|
243
|
+
expr._mapValueType = btu.value;
|
|
244
|
+
return btu.value;
|
|
245
|
+
}
|
|
246
|
+
if (isString(bt)) return INT;
|
|
247
|
+
return this.err(`Cannot index type ${typeStr(bt)}`, expr);
|
|
248
|
+
},
|
|
305
249
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
this.err(
|
|
318
|
-
`invalid type assertion: ${typeStr(srcType)} is not an interface`,
|
|
319
|
-
expr,
|
|
320
|
-
);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
250
|
+
_checkSliceExpr(expr, scope) {
|
|
251
|
+
const bt = this.checkExpr(expr.expr, scope);
|
|
252
|
+
if (expr.low) this.checkExpr(expr.low, scope);
|
|
253
|
+
if (expr.high) this.checkExpr(expr.high, scope);
|
|
254
|
+
if (expr.max) this.checkExpr(expr.max, scope);
|
|
255
|
+
if (isAny(bt)) return ANY;
|
|
256
|
+
if (bt.kind === "slice" || bt.kind === "array")
|
|
257
|
+
return { kind: "slice", elem: bt.elem };
|
|
258
|
+
if (isString(bt)) return STRING;
|
|
259
|
+
return this.err(`Cannot slice type ${typeStr(bt)}`, expr);
|
|
260
|
+
},
|
|
323
261
|
|
|
324
|
-
|
|
325
|
-
|
|
262
|
+
_checkFuncLit(expr, scope) {
|
|
263
|
+
const inner = new Scope(scope);
|
|
264
|
+
for (const p of expr.params)
|
|
265
|
+
inner.define(p.name, p.type ? this.resolveTypeNode(p.type, scope) : ANY);
|
|
266
|
+
const ret = expr.returnType
|
|
267
|
+
? this.resolveTypeNode(expr.returnType, scope)
|
|
268
|
+
: VOID;
|
|
269
|
+
const savedDefer = this._deferCount;
|
|
270
|
+
this._deferCount = 0;
|
|
271
|
+
this.checkBlock(expr.body, inner, ret);
|
|
272
|
+
this._reportUnused(inner, expr);
|
|
273
|
+
if (this._deferCount > 0) expr.body._hasDefer = true;
|
|
274
|
+
this._deferCount = savedDefer;
|
|
275
|
+
const paramTypes = expr.params.map((p) =>
|
|
276
|
+
p.type ? this.resolveTypeNode(p.type, scope) : ANY,
|
|
277
|
+
);
|
|
278
|
+
return {
|
|
279
|
+
kind: "func",
|
|
280
|
+
params: paramTypes,
|
|
281
|
+
returns: [ret],
|
|
282
|
+
async: expr.async,
|
|
283
|
+
};
|
|
284
|
+
},
|
|
326
285
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
if (resolved?.kind === "slice" || resolved?.kind === "array") {
|
|
342
|
-
return { kind: "tuple", types: [INT, resolved.elem ?? ANY] };
|
|
343
|
-
}
|
|
344
|
-
if (resolved?.kind === "map") {
|
|
345
|
-
return {
|
|
346
|
-
kind: "tuple",
|
|
347
|
-
types: [resolved.key ?? ANY, resolved.value ?? ANY],
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
// integer range or unknown — return single-element tuple
|
|
351
|
-
return { kind: "tuple", types: [collType] };
|
|
352
|
-
}
|
|
286
|
+
_checkElemTypeCompatible(tgtElem, srcElem, srcType, targetType, expr) {
|
|
287
|
+
if (
|
|
288
|
+
tgtElem &&
|
|
289
|
+
srcElem &&
|
|
290
|
+
!isAny(tgtElem) &&
|
|
291
|
+
!isAny(srcElem) &&
|
|
292
|
+
typeStr(tgtElem) !== typeStr(srcElem)
|
|
293
|
+
)
|
|
294
|
+
this.err(
|
|
295
|
+
`cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
|
|
296
|
+
expr,
|
|
297
|
+
);
|
|
298
|
+
},
|
|
353
299
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
375
|
-
|
|
300
|
+
_isMutualArraySlice(tgt, src) {
|
|
301
|
+
return (
|
|
302
|
+
(tgt?.kind === "array" && src?.kind === "slice") ||
|
|
303
|
+
(tgt?.kind === "slice" && src?.kind === "array")
|
|
304
|
+
);
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
_isInvalidArrayConv(tgt, src, srcType) {
|
|
308
|
+
return tgt?.kind === "array" && src?.kind !== "array" && !isAny(srcType);
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
_checkTypeConversion(expr, scope) {
|
|
312
|
+
const srcType = this.checkExpr(expr.expr, scope);
|
|
313
|
+
const targetType = this.resolveTypeNode(expr.targetType, scope);
|
|
314
|
+
const srcResolved =
|
|
315
|
+
srcType?.kind === "named" ? srcType.underlying : srcType;
|
|
316
|
+
const tgtResolved =
|
|
317
|
+
targetType?.kind === "named" ? targetType.underlying : targetType;
|
|
318
|
+
if (isNumeric(targetType) && isComplex(srcType)) {
|
|
319
|
+
this.err(
|
|
320
|
+
`cannot convert ${typeStr(srcType)} to ${typeStr(targetType)} (use real() or imag())`,
|
|
321
|
+
expr,
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
if (
|
|
325
|
+
isComplex(targetType) &&
|
|
326
|
+
!isComplexOrNumeric(srcType) &&
|
|
327
|
+
!isAny(srcType)
|
|
328
|
+
) {
|
|
329
|
+
this.err(
|
|
330
|
+
`cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
|
|
331
|
+
expr,
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
if (this._isMutualArraySlice(tgtResolved, srcResolved)) {
|
|
335
|
+
this._checkElemTypeCompatible(
|
|
336
|
+
tgtResolved.elem,
|
|
337
|
+
srcResolved.elem,
|
|
338
|
+
srcType,
|
|
339
|
+
targetType,
|
|
340
|
+
expr,
|
|
341
|
+
);
|
|
342
|
+
return targetType;
|
|
343
|
+
}
|
|
344
|
+
if (this._isInvalidArrayConv(tgtResolved, srcResolved, srcType)) {
|
|
345
|
+
this.err(
|
|
346
|
+
`cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
|
|
347
|
+
expr,
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
return targetType;
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
_checkTypeAssertExpr(expr, scope) {
|
|
354
|
+
const srcType = this.checkExpr(expr.expr, scope);
|
|
355
|
+
const targetType = this.resolveTypeNode(expr.type, scope);
|
|
356
|
+
|
|
357
|
+
// In Go, the source of a type assertion must be an interface type.
|
|
358
|
+
// We allow `any` (which is GoFront's basic any) and interface types.
|
|
359
|
+
if (srcType && !isAny(srcType)) {
|
|
360
|
+
let underlying = srcType.kind === "named" ? srcType.underlying : srcType;
|
|
361
|
+
underlying = this.resolveType(underlying);
|
|
362
|
+
if (underlying?.kind !== "interface") {
|
|
363
|
+
this.err(
|
|
364
|
+
`invalid type assertion: ${typeStr(srcType)} is not an interface`,
|
|
365
|
+
expr,
|
|
366
|
+
);
|
|
376
367
|
}
|
|
368
|
+
}
|
|
377
369
|
|
|
378
|
-
|
|
379
|
-
|
|
370
|
+
return targetType;
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
_rangeCollTypeTuple(resolved, collType) {
|
|
374
|
+
if (isString(resolved)) return { kind: "tuple", types: [INT, INT] };
|
|
375
|
+
if (resolved?.kind === "slice" || resolved?.kind === "array")
|
|
376
|
+
return { kind: "tuple", types: [INT, resolved.elem ?? ANY] };
|
|
377
|
+
if (resolved?.kind === "map")
|
|
378
|
+
return {
|
|
379
|
+
kind: "tuple",
|
|
380
|
+
types: [resolved.key ?? ANY, resolved.value ?? ANY],
|
|
381
|
+
};
|
|
382
|
+
return { kind: "tuple", types: [collType] };
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
_checkRangeExpr(expr, scope) {
|
|
386
|
+
const collType = this.checkExpr(expr.expr, scope);
|
|
387
|
+
const resolved =
|
|
388
|
+
collType?.kind === "named" ? collType.underlying : collType;
|
|
389
|
+
return this._rangeCollTypeTuple(resolved, collType);
|
|
390
|
+
},
|
|
391
|
+
|
|
392
|
+
_checkInstantiationExpr(expr, scope) {
|
|
393
|
+
// Generic instantiation: Foo[int] or Foo[int, string]
|
|
394
|
+
const baseType = this.checkExpr(expr.expr, scope);
|
|
395
|
+
if (baseType?.kind === "generic") {
|
|
396
|
+
const typeArgs = expr.typeArgs.map((ta) =>
|
|
397
|
+
this.resolveTypeNode(ta, scope),
|
|
398
|
+
);
|
|
399
|
+
// Check constraints
|
|
400
|
+
for (
|
|
401
|
+
let i = 0;
|
|
402
|
+
i < baseType.typeParams.length && i < typeArgs.length;
|
|
403
|
+
i++
|
|
404
|
+
) {
|
|
405
|
+
const tp = baseType.typeParams[i];
|
|
406
|
+
const constraint = tp.constraint
|
|
407
|
+
? this.resolveTypeNode(tp.constraint, scope)
|
|
408
|
+
: null;
|
|
409
|
+
if (constraint) this.checkConstraint(typeArgs[i], constraint, expr);
|
|
410
|
+
}
|
|
411
|
+
return this.instantiateGenericFunc(baseType, typeArgs);
|
|
380
412
|
}
|
|
381
|
-
return
|
|
413
|
+
return baseType;
|
|
382
414
|
},
|
|
383
415
|
|
|
384
416
|
_constIntValue(expr) {
|
|
@@ -395,11 +427,36 @@ export const expressionCheckMethods = {
|
|
|
395
427
|
return null;
|
|
396
428
|
},
|
|
397
429
|
|
|
430
|
+
_resolveCallArgTypes(expr, scope) {
|
|
431
|
+
if (expr.args.length === 1 && expr.args[0].kind === "CallExpr") {
|
|
432
|
+
const argType = this.checkExpr(expr.args[0], scope);
|
|
433
|
+
if (argType?.kind === "tuple") {
|
|
434
|
+
expr._multiForward = true;
|
|
435
|
+
return argType.types;
|
|
436
|
+
}
|
|
437
|
+
return [argType];
|
|
438
|
+
}
|
|
439
|
+
return expr.args.map((a) => this.checkExpr(a, scope));
|
|
440
|
+
},
|
|
441
|
+
|
|
442
|
+
_resolveCallFnType(fnType, argTypes, expr, scope) {
|
|
443
|
+
if (fnType.kind === "named" && fnType.underlying?.kind === "func")
|
|
444
|
+
fnType = fnType.underlying;
|
|
445
|
+
if (fnType.kind === "generic") {
|
|
446
|
+
const resolved = this._resolveGenericFnType(
|
|
447
|
+
fnType,
|
|
448
|
+
argTypes,
|
|
449
|
+
expr,
|
|
450
|
+
scope,
|
|
451
|
+
);
|
|
452
|
+
if (!resolved) return null;
|
|
453
|
+
fnType = resolved;
|
|
454
|
+
}
|
|
455
|
+
return fnType;
|
|
456
|
+
},
|
|
457
|
+
|
|
398
458
|
checkCall(expr, scope) {
|
|
399
459
|
let fnType = this.checkExpr(expr.func, scope);
|
|
400
|
-
|
|
401
|
-
// Handle built-ins before evaluating args; some (e.g. new) take type names as
|
|
402
|
-
// arguments, which would otherwise produce false "Undefined" errors.
|
|
403
460
|
if (fnType.kind === "builtin") {
|
|
404
461
|
const argTypes = expr.args.map((a, i) => {
|
|
405
462
|
// new(T) — first arg is a type name, not a value expression
|
|
@@ -409,20 +466,7 @@ export const expressionCheckMethods = {
|
|
|
409
466
|
return this.checkBuiltin(fnType.name, expr, argTypes, scope);
|
|
410
467
|
}
|
|
411
468
|
|
|
412
|
-
|
|
413
|
-
// When a single argument is a multi-return call, flatten its tuple into args.
|
|
414
|
-
let argTypes;
|
|
415
|
-
if (expr.args.length === 1 && expr.args[0].kind === "CallExpr") {
|
|
416
|
-
const argType = this.checkExpr(expr.args[0], scope);
|
|
417
|
-
if (argType?.kind === "tuple") {
|
|
418
|
-
expr._multiForward = true; // flag for codegen
|
|
419
|
-
argTypes = argType.types;
|
|
420
|
-
} else {
|
|
421
|
-
argTypes = [argType];
|
|
422
|
-
}
|
|
423
|
-
} else {
|
|
424
|
-
argTypes = expr.args.map((a) => this.checkExpr(a, scope));
|
|
425
|
-
}
|
|
469
|
+
const argTypes = this._resolveCallArgTypes(expr, scope);
|
|
426
470
|
|
|
427
471
|
if (isAny(fnType)) return ANY;
|
|
428
472
|
|
|
@@ -434,300 +478,266 @@ export const expressionCheckMethods = {
|
|
|
434
478
|
return fnType;
|
|
435
479
|
}
|
|
436
480
|
|
|
437
|
-
|
|
438
|
-
if (fnType
|
|
439
|
-
|
|
481
|
+
fnType = this._resolveCallFnType(fnType, argTypes, expr, scope);
|
|
482
|
+
if (!fnType) return ANY;
|
|
483
|
+
if (fnType.kind !== "func") {
|
|
484
|
+
return this.err(`Cannot call non-function type ${typeStr(fnType)}`, expr);
|
|
440
485
|
}
|
|
441
486
|
|
|
442
|
-
|
|
443
|
-
if (fnType.kind === "generic") {
|
|
444
|
-
const typeArgs = this.inferTypeArgs(fnType, argTypes);
|
|
445
|
-
if (!typeArgs) {
|
|
446
|
-
return this.err(
|
|
447
|
-
`Cannot infer type arguments for generic function '${fnType.name}'`,
|
|
448
|
-
expr,
|
|
449
|
-
);
|
|
450
|
-
}
|
|
451
|
-
// Check constraints
|
|
452
|
-
for (
|
|
453
|
-
let i = 0;
|
|
454
|
-
i < fnType.typeParams.length && i < typeArgs.length;
|
|
455
|
-
i++
|
|
456
|
-
) {
|
|
457
|
-
const tp = fnType.typeParams[i];
|
|
458
|
-
const constraint = tp.constraint
|
|
459
|
-
? this.resolveTypeNode(tp.constraint, scope)
|
|
460
|
-
: null;
|
|
461
|
-
if (constraint) this.checkConstraint(typeArgs[i], constraint, expr);
|
|
462
|
-
}
|
|
463
|
-
fnType = this.instantiateGenericFunc(fnType, typeArgs);
|
|
464
|
-
}
|
|
487
|
+
this._checkCallArgs(fnType, argTypes, expr);
|
|
465
488
|
|
|
466
|
-
|
|
467
|
-
|
|
489
|
+
const ret = fnType.returns;
|
|
490
|
+
if (!ret || ret.length === 0) return VOID;
|
|
491
|
+
if (ret.length === 1) return ret[0];
|
|
492
|
+
return { kind: "tuple", types: ret };
|
|
493
|
+
},
|
|
494
|
+
|
|
495
|
+
_resolveGenericFnType(fnType, argTypes, expr, scope) {
|
|
496
|
+
const typeArgs = this.inferTypeArgs(fnType, argTypes);
|
|
497
|
+
if (!typeArgs) {
|
|
498
|
+
this.err(
|
|
499
|
+
`Cannot infer type arguments for generic function '${fnType.name}'`,
|
|
500
|
+
expr,
|
|
501
|
+
);
|
|
502
|
+
return null;
|
|
503
|
+
}
|
|
504
|
+
// Check constraints
|
|
505
|
+
for (let i = 0; i < fnType.typeParams.length && i < typeArgs.length; i++) {
|
|
506
|
+
const tp = fnType.typeParams[i];
|
|
507
|
+
const constraint = tp.constraint
|
|
508
|
+
? this.resolveTypeNode(tp.constraint, scope)
|
|
509
|
+
: null;
|
|
510
|
+
if (constraint) this.checkConstraint(typeArgs[i], constraint, expr);
|
|
468
511
|
}
|
|
512
|
+
return this.instantiateGenericFunc(fnType, typeArgs);
|
|
513
|
+
},
|
|
469
514
|
|
|
470
|
-
|
|
515
|
+
_checkArgCount(fnType, argTypes, expr) {
|
|
471
516
|
const minArgs = fnType.params.filter(
|
|
472
517
|
(_, i) => !fnType.variadic || i < fnType.params.length - 1,
|
|
473
518
|
).length;
|
|
474
|
-
if (argTypes.length < minArgs)
|
|
519
|
+
if (argTypes.length < minArgs)
|
|
475
520
|
this.err(
|
|
476
521
|
`Too few arguments: expected ${fnType.params.length}, got ${argTypes.length}`,
|
|
477
522
|
expr,
|
|
478
523
|
);
|
|
479
|
-
|
|
480
|
-
if (!fnType.variadic && argTypes.length > fnType.params.length) {
|
|
524
|
+
if (!fnType.variadic && argTypes.length > fnType.params.length)
|
|
481
525
|
this.err(
|
|
482
526
|
`Too many arguments: expected ${fnType.params.length}, got ${argTypes.length}`,
|
|
483
527
|
expr,
|
|
484
528
|
);
|
|
485
|
-
|
|
529
|
+
},
|
|
486
530
|
|
|
487
|
-
|
|
531
|
+
_checkCallArgs(fnType, argTypes, expr) {
|
|
532
|
+
this._checkArgCount(fnType, argTypes, expr);
|
|
488
533
|
for (let i = 0; i < fnType.params.length && i < argTypes.length; i++) {
|
|
489
534
|
const paramIdx =
|
|
490
535
|
fnType.variadic && i >= fnType.params.length - 1
|
|
491
536
|
? fnType.params.length - 1
|
|
492
537
|
: i;
|
|
493
|
-
// Spread arg (f(slice...)) passes a whole slice into a variadic — skip per-element check
|
|
494
538
|
if (expr.args[i]?._spread) continue;
|
|
495
539
|
this.assertAssignable(fnType.params[paramIdx], argTypes[i], expr.args[i]);
|
|
496
540
|
}
|
|
497
|
-
|
|
498
|
-
const ret = fnType.returns;
|
|
499
|
-
if (!ret || ret.length === 0) return VOID;
|
|
500
|
-
if (ret.length === 1) return ret[0];
|
|
501
|
-
return { kind: "tuple", types: ret };
|
|
502
541
|
},
|
|
503
542
|
|
|
504
543
|
checkBuiltin(name, expr, argTypes, scope) {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
case "max":
|
|
558
|
-
return argTypes[0] ?? ANY;
|
|
559
|
-
case "clear":
|
|
560
|
-
return VOID;
|
|
561
|
-
case "complex": {
|
|
562
|
-
if (expr.args.length !== 2) {
|
|
563
|
-
this.err("complex() requires exactly 2 arguments", expr);
|
|
564
|
-
return ANY;
|
|
565
|
-
}
|
|
566
|
-
const crt = argTypes[0];
|
|
567
|
-
const cit = argTypes[1];
|
|
568
|
-
if (!isNumeric(crt)) {
|
|
569
|
-
this.err(`cannot use ${typeStr(crt)} as float in complex()`, expr);
|
|
570
|
-
return ANY;
|
|
571
|
-
}
|
|
572
|
-
if (!isNumeric(cit)) {
|
|
573
|
-
this.err(`cannot use ${typeStr(cit)} as float in complex()`, expr);
|
|
574
|
-
return ANY;
|
|
575
|
-
}
|
|
576
|
-
if (crt.kind === "untyped" && cit.kind === "untyped")
|
|
577
|
-
return UNTYPED_COMPLEX;
|
|
578
|
-
return COMPLEX128;
|
|
579
|
-
}
|
|
580
|
-
case "real":
|
|
581
|
-
case "imag": {
|
|
582
|
-
if (expr.args.length !== 1) {
|
|
583
|
-
this.err(`${name}() requires exactly 1 argument`, expr);
|
|
584
|
-
return ANY;
|
|
585
|
-
}
|
|
586
|
-
const zt = argTypes[0];
|
|
587
|
-
if (!isComplex(zt)) {
|
|
588
|
-
this.err(`cannot use ${typeStr(zt)} as complex in ${name}()`, expr);
|
|
589
|
-
return ANY;
|
|
590
|
-
}
|
|
591
|
-
if (zt.kind === "untyped") return UNTYPED_FLOAT;
|
|
592
|
-
return FLOAT64;
|
|
593
|
-
}
|
|
594
|
-
default:
|
|
595
|
-
return ANY;
|
|
544
|
+
const gen = BUILTIN_CHECK[name];
|
|
545
|
+
return gen ? gen(this, name, expr, argTypes, scope) : ANY;
|
|
546
|
+
},
|
|
547
|
+
|
|
548
|
+
_constLenFromType(arg0) {
|
|
549
|
+
if (arg0?.kind === "array" && arg0.size != null) return arg0.size;
|
|
550
|
+
if (
|
|
551
|
+
arg0?.kind === "named" &&
|
|
552
|
+
arg0.underlying?.kind === "array" &&
|
|
553
|
+
arg0.underlying.size != null
|
|
554
|
+
)
|
|
555
|
+
return arg0.underlying.size;
|
|
556
|
+
return null;
|
|
557
|
+
},
|
|
558
|
+
|
|
559
|
+
_checkBuiltinLen(name, expr, argTypes) {
|
|
560
|
+
if (name === "len") {
|
|
561
|
+
const size = this._constLenFromType(argTypes[0]);
|
|
562
|
+
if (size != null) expr._constLen = size;
|
|
563
|
+
}
|
|
564
|
+
return INT;
|
|
565
|
+
},
|
|
566
|
+
|
|
567
|
+
_checkBuiltinComplex(expr, argTypes) {
|
|
568
|
+
if (expr.args.length !== 2) {
|
|
569
|
+
this.err("complex() requires exactly 2 arguments", expr);
|
|
570
|
+
return ANY;
|
|
571
|
+
}
|
|
572
|
+
const crt = argTypes[0];
|
|
573
|
+
const cit = argTypes[1];
|
|
574
|
+
if (!isNumeric(crt)) {
|
|
575
|
+
this.err(`cannot use ${typeStr(crt)} as float in complex()`, expr);
|
|
576
|
+
return ANY;
|
|
577
|
+
}
|
|
578
|
+
if (!isNumeric(cit)) {
|
|
579
|
+
this.err(`cannot use ${typeStr(cit)} as float in complex()`, expr);
|
|
580
|
+
return ANY;
|
|
581
|
+
}
|
|
582
|
+
if (crt.kind === "untyped" && cit.kind === "untyped")
|
|
583
|
+
return UNTYPED_COMPLEX;
|
|
584
|
+
return COMPLEX128;
|
|
585
|
+
},
|
|
586
|
+
|
|
587
|
+
_checkBuiltinRealImag(name, expr, argTypes) {
|
|
588
|
+
if (expr.args.length !== 1) {
|
|
589
|
+
this.err(`${name}() requires exactly 1 argument`, expr);
|
|
590
|
+
return ANY;
|
|
591
|
+
}
|
|
592
|
+
const zt = argTypes[0];
|
|
593
|
+
if (!isComplex(zt)) {
|
|
594
|
+
this.err(`cannot use ${typeStr(zt)} as complex in ${name}()`, expr);
|
|
595
|
+
return ANY;
|
|
596
596
|
}
|
|
597
|
+
if (zt.kind === "untyped") return UNTYPED_FLOAT;
|
|
598
|
+
return FLOAT64;
|
|
599
|
+
},
|
|
600
|
+
|
|
601
|
+
_inferArraySize(expr, typeNode, base) {
|
|
602
|
+
if (
|
|
603
|
+
typeNode?.kind === "ArrayType" &&
|
|
604
|
+
typeNode.inferLen &&
|
|
605
|
+
base?.kind === "array"
|
|
606
|
+
)
|
|
607
|
+
base.size = computeMaxIndex(expr.elems) + 1;
|
|
608
|
+
},
|
|
609
|
+
|
|
610
|
+
_isSliceOrArray(base) {
|
|
611
|
+
return base?.kind === "slice" || base?.kind === "array";
|
|
612
|
+
},
|
|
613
|
+
|
|
614
|
+
_checkUnknownCompositeLit(expr, scope, t) {
|
|
615
|
+
for (const elem of expr.elems) {
|
|
616
|
+
if (elem.kind === "KeyValueExpr") this.checkExpr(elem.value, scope);
|
|
617
|
+
else this.checkExpr(elem, scope);
|
|
618
|
+
}
|
|
619
|
+
return t ?? ANY;
|
|
597
620
|
},
|
|
598
621
|
|
|
599
622
|
checkCompositeLit(expr, scope, hintType = null) {
|
|
600
|
-
// null typeExpr = implicit lit inside a slice/map: {X:1} in []Point{{X:1}}
|
|
601
623
|
const t =
|
|
602
624
|
expr.typeExpr === null
|
|
603
625
|
? (hintType ?? ANY)
|
|
604
626
|
: this.resolveTypeNode(expr.typeExpr, scope);
|
|
605
627
|
const base = t.kind === "named" ? t.underlying : t;
|
|
606
|
-
|
|
607
|
-
// [...]T size inference: count elements and set size
|
|
608
628
|
const typeNode = expr.typeExpr;
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
}
|
|
629
|
+
this._inferArraySize(expr, typeNode, base);
|
|
630
|
+
if (base?.kind === "struct")
|
|
631
|
+
return this._checkStructCompositeLit(expr, scope, t, base);
|
|
632
|
+
if (this._isSliceOrArray(base))
|
|
633
|
+
return this._checkSliceArrayCompositeLit(expr, scope, t, base, typeNode);
|
|
634
|
+
if (base?.kind === "map")
|
|
635
|
+
return this._checkMapCompositeLit(expr, scope, t, base);
|
|
636
|
+
return this._checkUnknownCompositeLit(expr, scope, t);
|
|
637
|
+
},
|
|
619
638
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
const
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
// Map positional elements to struct fields by declaration order
|
|
629
|
-
const fieldNames = [...(base.fields?.keys() ?? [])];
|
|
630
|
-
for (let i = 0; i < expr.elems.length; i++) {
|
|
631
|
-
const elem = expr.elems[i];
|
|
632
|
-
const fieldName = fieldNames[i];
|
|
633
|
-
if (!fieldName) {
|
|
634
|
-
this.err("too many values in struct literal", elem);
|
|
635
|
-
continue;
|
|
636
|
-
}
|
|
637
|
-
const fieldType = base.fields.get(fieldName);
|
|
638
|
-
const vt = this.checkExpr(elem, scope);
|
|
639
|
-
if (fieldType) this.assertAssignable(fieldType, vt, elem);
|
|
640
|
-
// Annotate for codegen
|
|
641
|
-
elem._positionalField = fieldName;
|
|
642
|
-
}
|
|
643
|
-
return t;
|
|
639
|
+
_checkStructPositionalElems(expr, scope, t, base) {
|
|
640
|
+
const fieldNames = [...(base.fields?.keys() ?? [])];
|
|
641
|
+
for (let i = 0; i < expr.elems.length; i++) {
|
|
642
|
+
const elem = expr.elems[i];
|
|
643
|
+
const fieldName = fieldNames[i];
|
|
644
|
+
if (!fieldName) {
|
|
645
|
+
this.err("too many values in struct literal", elem);
|
|
646
|
+
continue;
|
|
644
647
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
648
|
+
const fieldType = base.fields.get(fieldName);
|
|
649
|
+
const vt = this.checkExpr(elem, scope);
|
|
650
|
+
if (fieldType) this.assertAssignable(fieldType, vt, elem);
|
|
651
|
+
elem._positionalField = fieldName;
|
|
652
|
+
}
|
|
653
|
+
return t;
|
|
654
|
+
},
|
|
655
|
+
|
|
656
|
+
_checkStructKeyedElems(expr, scope, t, base) {
|
|
657
|
+
for (const elem of expr.elems) {
|
|
658
|
+
if (elem.kind === "KeyValueExpr") {
|
|
659
|
+
const keyName = elem.key.name;
|
|
660
|
+
const fieldType = base.fields?.get(keyName);
|
|
661
|
+
if (fieldType) {
|
|
662
|
+
const vt = this.checkExpr(elem.value, scope);
|
|
663
|
+
this.assertAssignable(fieldType, vt, elem.value);
|
|
664
|
+
} else {
|
|
665
|
+
const embed = base._embeds?.find(
|
|
666
|
+
(e) => (e.kind === "named" ? e.name : null) === keyName,
|
|
667
|
+
);
|
|
668
|
+
if (embed) {
|
|
650
669
|
const vt = this.checkExpr(elem.value, scope);
|
|
651
|
-
this.assertAssignable(
|
|
670
|
+
this.assertAssignable(embed, vt, elem.value);
|
|
671
|
+
elem._isEmbedInit = true;
|
|
652
672
|
} else {
|
|
653
|
-
|
|
654
|
-
const embed = base._embeds?.find(
|
|
655
|
-
(e) => (e.kind === "named" ? e.name : null) === keyName,
|
|
656
|
-
);
|
|
657
|
-
if (embed) {
|
|
658
|
-
const vt = this.checkExpr(elem.value, scope);
|
|
659
|
-
this.assertAssignable(embed, vt, elem.value);
|
|
660
|
-
elem._isEmbedInit = true;
|
|
661
|
-
} else {
|
|
662
|
-
this.err(`Unknown field '${keyName}'`, elem.key);
|
|
663
|
-
}
|
|
673
|
+
this.err(`Unknown field '${keyName}'`, elem.key);
|
|
664
674
|
}
|
|
665
|
-
} else {
|
|
666
|
-
this.checkExpr(elem, scope);
|
|
667
675
|
}
|
|
676
|
+
} else {
|
|
677
|
+
this.checkExpr(elem, scope);
|
|
668
678
|
}
|
|
669
|
-
return t;
|
|
670
679
|
}
|
|
680
|
+
return t;
|
|
681
|
+
},
|
|
671
682
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
const et = this.checkExpr(elem, scope);
|
|
682
|
-
this.assertAssignable(base.elem, et, elem);
|
|
683
|
-
}
|
|
684
|
-
}
|
|
683
|
+
_checkStructCompositeLit(expr, scope, t, base) {
|
|
684
|
+
const hasPositional = expr.elems.some((e) => e.kind !== "KeyValueExpr");
|
|
685
|
+
const hasKeyed = expr.elems.some((e) => e.kind === "KeyValueExpr");
|
|
686
|
+
if (hasPositional && hasKeyed)
|
|
687
|
+
this.err("mixture of field:value and value initializers", expr);
|
|
688
|
+
if (hasPositional)
|
|
689
|
+
return this._checkStructPositionalElems(expr, scope, t, base);
|
|
690
|
+
return this._checkStructKeyedElems(expr, scope, t, base);
|
|
691
|
+
},
|
|
685
692
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
693
|
+
_checkSliceArrayCompositeLit(expr, scope, t, base, typeNode) {
|
|
694
|
+
for (const elem of expr.elems) {
|
|
695
|
+
if (elem.kind === "CompositeLit" && elem.typeExpr === null) {
|
|
696
|
+
const et = this.checkCompositeLit(elem, scope, base.elem);
|
|
697
|
+
elem._type = et;
|
|
698
|
+
} else if (elem.kind === "KeyValueExpr") {
|
|
699
|
+
const vt = this.checkExpr(elem.value, scope);
|
|
700
|
+
this.assertAssignable(base.elem, vt, elem.value);
|
|
701
|
+
} else {
|
|
702
|
+
const et = this.checkExpr(elem, scope);
|
|
703
|
+
this.assertAssignable(base.elem, et, elem);
|
|
695
704
|
}
|
|
705
|
+
}
|
|
696
706
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
let vt;
|
|
706
|
-
if (
|
|
707
|
-
elem.value.kind === "CompositeLit" &&
|
|
708
|
-
elem.value.typeExpr === null
|
|
709
|
-
) {
|
|
710
|
-
vt = this.checkCompositeLit(elem.value, scope, base.value);
|
|
711
|
-
elem.value._type = vt;
|
|
712
|
-
} else {
|
|
713
|
-
vt = this.checkExpr(elem.value, scope);
|
|
714
|
-
}
|
|
715
|
-
this.assertAssignable(base.key, kt, elem.key);
|
|
716
|
-
this.assertAssignable(base.value, vt, elem.value);
|
|
717
|
-
}
|
|
707
|
+
// Composite literal element count validation for explicit-size arrays
|
|
708
|
+
if (base.kind === "array" && base.size != null && !typeNode?.inferLen) {
|
|
709
|
+
const maxIndex = computeMaxIndex(expr.elems);
|
|
710
|
+
if (maxIndex >= base.size) {
|
|
711
|
+
this.err(
|
|
712
|
+
`array index ${maxIndex} out of bounds [0:${base.size}]`,
|
|
713
|
+
expr,
|
|
714
|
+
);
|
|
718
715
|
}
|
|
719
|
-
return t;
|
|
720
716
|
}
|
|
721
717
|
|
|
722
|
-
|
|
718
|
+
return t;
|
|
719
|
+
},
|
|
720
|
+
|
|
721
|
+
_checkMapCompositeLit(expr, scope, t, base) {
|
|
723
722
|
for (const elem of expr.elems) {
|
|
724
723
|
if (elem.kind === "KeyValueExpr") {
|
|
725
|
-
this.checkExpr(elem.
|
|
726
|
-
|
|
727
|
-
|
|
724
|
+
const kt = this.checkExpr(elem.key, scope);
|
|
725
|
+
// value may be an implicit composite lit
|
|
726
|
+
let vt;
|
|
727
|
+
if (
|
|
728
|
+
elem.value.kind === "CompositeLit" &&
|
|
729
|
+
elem.value.typeExpr === null
|
|
730
|
+
) {
|
|
731
|
+
vt = this.checkCompositeLit(elem.value, scope, base.value);
|
|
732
|
+
elem.value._type = vt;
|
|
733
|
+
} else {
|
|
734
|
+
vt = this.checkExpr(elem.value, scope);
|
|
735
|
+
}
|
|
736
|
+
this.assertAssignable(base.key, kt, elem.key);
|
|
737
|
+
this.assertAssignable(base.value, vt, elem.value);
|
|
728
738
|
}
|
|
729
739
|
}
|
|
730
|
-
return t
|
|
740
|
+
return t;
|
|
731
741
|
},
|
|
732
742
|
};
|
|
733
743
|
|