gofront 0.0.3 → 0.0.5
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 +110 -135
- package/package.json +2 -3
- package/src/codegen/expressions.js +529 -234
- package/src/codegen/statements.js +259 -14
- package/src/codegen.js +98 -9
- package/src/dts-parser.js +1 -1
- package/src/index.js +13 -12
- package/src/lexer.js +24 -1
- package/src/minifier.js +698 -0
- package/src/parser/expressions.js +115 -15
- package/src/parser/statements.js +4 -4
- package/src/parser/types.js +108 -3
- package/src/parser.js +93 -18
- package/src/typechecker/expressions.js +370 -13
- package/src/typechecker/statements.js +103 -5
- package/src/typechecker/types.js +96 -28
- package/src/typechecker.js +658 -22
|
@@ -3,10 +3,15 @@
|
|
|
3
3
|
import {
|
|
4
4
|
ANY,
|
|
5
5
|
BOOL,
|
|
6
|
+
COMPLEX128,
|
|
6
7
|
ERROR,
|
|
8
|
+
FLOAT64,
|
|
7
9
|
INT,
|
|
8
10
|
isAny,
|
|
11
|
+
isArray,
|
|
9
12
|
isBool,
|
|
13
|
+
isComplex,
|
|
14
|
+
isComplexOrNumeric,
|
|
10
15
|
isNumeric,
|
|
11
16
|
isString,
|
|
12
17
|
NIL,
|
|
@@ -14,6 +19,7 @@ import {
|
|
|
14
19
|
STRING,
|
|
15
20
|
typeStr,
|
|
16
21
|
UNTYPED_BOOL,
|
|
22
|
+
UNTYPED_COMPLEX,
|
|
17
23
|
UNTYPED_FLOAT,
|
|
18
24
|
UNTYPED_INT,
|
|
19
25
|
UNTYPED_STRING,
|
|
@@ -45,9 +51,14 @@ export const expressionCheckMethods = {
|
|
|
45
51
|
break;
|
|
46
52
|
}
|
|
47
53
|
|
|
54
|
+
case "ImagLit":
|
|
55
|
+
return UNTYPED_COMPLEX;
|
|
56
|
+
|
|
48
57
|
case "Ident": {
|
|
49
58
|
const t = scope.lookup(expr.name);
|
|
50
59
|
if (!t) return this.err(`Undefined: '${expr.name}'`, expr);
|
|
60
|
+
// Mark type-name identifiers so SelectorExpr can detect method expressions
|
|
61
|
+
if (this.types.has(expr.name)) expr._isTypeRef = true;
|
|
51
62
|
return t;
|
|
52
63
|
}
|
|
53
64
|
|
|
@@ -58,17 +69,35 @@ export const expressionCheckMethods = {
|
|
|
58
69
|
return BOOL;
|
|
59
70
|
}
|
|
60
71
|
if (expr.op === "-" || expr.op === "+") {
|
|
61
|
-
if (!isNumeric(ot) && !isAny(ot))
|
|
72
|
+
if (!isNumeric(ot) && !isComplex(ot) && !isAny(ot))
|
|
62
73
|
this.err(`${expr.op} requires numeric`, expr);
|
|
63
74
|
return ot;
|
|
64
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
|
+
}
|
|
65
93
|
return ot;
|
|
66
94
|
}
|
|
67
95
|
|
|
68
96
|
case "BinaryExpr": {
|
|
69
97
|
const lt = this.checkExpr(expr.left, scope);
|
|
70
98
|
const rt = this.checkExpr(expr.right, scope);
|
|
71
|
-
if (isAny(lt) || isAny(rt))
|
|
99
|
+
if (isAny(lt) || isAny(rt))
|
|
100
|
+
return this.binaryResultType(expr.op, ANY, ANY, expr);
|
|
72
101
|
return this.binaryResultType(expr.op, lt, rt, expr);
|
|
73
102
|
}
|
|
74
103
|
|
|
@@ -78,14 +107,59 @@ export const expressionCheckMethods = {
|
|
|
78
107
|
|
|
79
108
|
case "SelectorExpr": {
|
|
80
109
|
const baseType = this.checkExpr(expr.expr, scope);
|
|
81
|
-
|
|
110
|
+
// Method expression: TypeName.MethodName → func(ReceiverType, ...params) ret
|
|
111
|
+
if (expr.expr._isTypeRef) {
|
|
112
|
+
let base = baseType.kind === "named" ? baseType.underlying : baseType;
|
|
113
|
+
base = this.resolveType(base);
|
|
114
|
+
if (base?.kind === "struct" && base.methods?.has(expr.field)) {
|
|
115
|
+
const methodType = base.methods.get(expr.field);
|
|
116
|
+
// Return a func type with the receiver prepended to params
|
|
117
|
+
expr._isMethodExpr = true;
|
|
118
|
+
expr._methodExprRecv = baseType;
|
|
119
|
+
return {
|
|
120
|
+
kind: "func",
|
|
121
|
+
params: [baseType, ...(methodType.params ?? [])],
|
|
122
|
+
returns: methodType.returns ?? [],
|
|
123
|
+
variadic: methodType.variadic,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const ft = this.fieldType(baseType, expr.field, expr);
|
|
128
|
+
// Mark method selectors so codegen can emit .bind() when used as a value
|
|
129
|
+
if (ft?.kind === "func") {
|
|
130
|
+
let base = baseType.kind === "named" ? baseType.underlying : baseType;
|
|
131
|
+
base = this.resolveType(base);
|
|
132
|
+
if (base?.kind === "struct" && base.methods?.has(expr.field)) {
|
|
133
|
+
expr._isMethodValue = true;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return ft;
|
|
82
137
|
}
|
|
83
138
|
|
|
84
139
|
case "IndexExpr": {
|
|
85
140
|
const bt = this.checkExpr(expr.expr, scope);
|
|
86
141
|
this.checkExpr(expr.index, scope);
|
|
87
142
|
if (isAny(bt)) return ANY;
|
|
88
|
-
if (bt.kind === "slice" || bt.kind === "array")
|
|
143
|
+
if (bt.kind === "slice" || bt.kind === "array") {
|
|
144
|
+
// Compile-time bounds checking for arrays
|
|
145
|
+
if (bt.kind === "array" && bt.size != null) {
|
|
146
|
+
const idx = this._constIntValue(expr.index);
|
|
147
|
+
if (idx !== null) {
|
|
148
|
+
if (idx < 0) {
|
|
149
|
+
this.err(
|
|
150
|
+
`invalid array index ${idx} (index must not be negative)`,
|
|
151
|
+
expr,
|
|
152
|
+
);
|
|
153
|
+
} else if (idx >= bt.size) {
|
|
154
|
+
this.err(
|
|
155
|
+
`invalid array index ${idx} (out of bounds for ${typeStr(bt)})`,
|
|
156
|
+
expr,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return bt.elem;
|
|
162
|
+
}
|
|
89
163
|
if (bt.kind === "map") {
|
|
90
164
|
expr._mapValueType = bt.value; // for codegen zero-value fallback
|
|
91
165
|
return bt.value;
|
|
@@ -143,8 +217,74 @@ export const expressionCheckMethods = {
|
|
|
143
217
|
}
|
|
144
218
|
|
|
145
219
|
case "TypeConversion": {
|
|
146
|
-
this.checkExpr(expr.expr, scope);
|
|
147
|
-
|
|
220
|
+
const srcType = this.checkExpr(expr.expr, scope);
|
|
221
|
+
const targetType = this.resolveTypeNode(expr.targetType, scope);
|
|
222
|
+
const srcResolved =
|
|
223
|
+
srcType?.kind === "named" ? srcType.underlying : srcType;
|
|
224
|
+
const tgtResolved =
|
|
225
|
+
targetType?.kind === "named" ? targetType.underlying : targetType;
|
|
226
|
+
// Reject float64(complex) — must use real()/imag()
|
|
227
|
+
if (isNumeric(targetType) && isComplex(srcType)) {
|
|
228
|
+
this.err(
|
|
229
|
+
`cannot convert ${typeStr(srcType)} to ${typeStr(targetType)} (use real() or imag())`,
|
|
230
|
+
expr,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
// Allow complex128/64(numeric) and complex128/64(complex)
|
|
234
|
+
if (isComplex(targetType)) {
|
|
235
|
+
if (!isComplexOrNumeric(srcType) && !isAny(srcType)) {
|
|
236
|
+
this.err(
|
|
237
|
+
`cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
|
|
238
|
+
expr,
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Slice → array conversion: [N]T(slice)
|
|
243
|
+
if (tgtResolved?.kind === "array" && srcResolved?.kind === "slice") {
|
|
244
|
+
const tgtElem = tgtResolved.elem;
|
|
245
|
+
const srcElem = srcResolved.elem;
|
|
246
|
+
if (
|
|
247
|
+
tgtElem &&
|
|
248
|
+
srcElem &&
|
|
249
|
+
!isAny(tgtElem) &&
|
|
250
|
+
!isAny(srcElem) &&
|
|
251
|
+
typeStr(tgtElem) !== typeStr(srcElem)
|
|
252
|
+
) {
|
|
253
|
+
this.err(
|
|
254
|
+
`cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
|
|
255
|
+
expr,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
return targetType;
|
|
259
|
+
}
|
|
260
|
+
// Array → slice conversion: []T(array)
|
|
261
|
+
if (tgtResolved?.kind === "slice" && srcResolved?.kind === "array") {
|
|
262
|
+
const tgtElem = tgtResolved.elem;
|
|
263
|
+
const srcElem = srcResolved.elem;
|
|
264
|
+
if (
|
|
265
|
+
tgtElem &&
|
|
266
|
+
srcElem &&
|
|
267
|
+
!isAny(tgtElem) &&
|
|
268
|
+
!isAny(srcElem) &&
|
|
269
|
+
typeStr(tgtElem) !== typeStr(srcElem)
|
|
270
|
+
) {
|
|
271
|
+
this.err(
|
|
272
|
+
`cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
|
|
273
|
+
expr,
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
return targetType;
|
|
277
|
+
}
|
|
278
|
+
// Reject conversions between array and incompatible types
|
|
279
|
+
if (tgtResolved?.kind === "array" && srcResolved?.kind !== "array") {
|
|
280
|
+
if (!isAny(srcType)) {
|
|
281
|
+
this.err(
|
|
282
|
+
`cannot convert ${typeStr(srcType)} to ${typeStr(targetType)}`,
|
|
283
|
+
expr,
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return targetType;
|
|
148
288
|
}
|
|
149
289
|
|
|
150
290
|
case "TypeAssertExpr": {
|
|
@@ -170,7 +310,53 @@ export const expressionCheckMethods = {
|
|
|
170
310
|
|
|
171
311
|
case "RangeExpr": {
|
|
172
312
|
// Type-check the iterated expression so its _type is annotated
|
|
173
|
-
|
|
313
|
+
const collType = this.checkExpr(expr.expr, scope);
|
|
314
|
+
const resolved =
|
|
315
|
+
collType?.kind === "named" ? collType.underlying : collType;
|
|
316
|
+
// Return a tuple of (index/key type, value type) so DefineStmt can
|
|
317
|
+
// assign the correct types to range variables.
|
|
318
|
+
const isString =
|
|
319
|
+
(resolved?.kind === "basic" && resolved.name === "string") ||
|
|
320
|
+
(resolved?.kind === "untyped" && resolved.base === "string");
|
|
321
|
+
if (isString) {
|
|
322
|
+
// string range: (int index, rune value) — rune is an alias for int
|
|
323
|
+
return { kind: "tuple", types: [INT, INT] };
|
|
324
|
+
}
|
|
325
|
+
if (resolved?.kind === "slice" || resolved?.kind === "array") {
|
|
326
|
+
return { kind: "tuple", types: [INT, resolved.elem ?? ANY] };
|
|
327
|
+
}
|
|
328
|
+
if (resolved?.kind === "map") {
|
|
329
|
+
return {
|
|
330
|
+
kind: "tuple",
|
|
331
|
+
types: [resolved.key ?? ANY, resolved.value ?? ANY],
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
// integer range or unknown — return single-element tuple
|
|
335
|
+
return { kind: "tuple", types: [collType] };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
case "InstantiationExpr": {
|
|
339
|
+
// Generic instantiation: Foo[int] or Foo[int, string]
|
|
340
|
+
const baseType = this.checkExpr(expr.expr, scope);
|
|
341
|
+
if (baseType?.kind === "generic") {
|
|
342
|
+
const typeArgs = expr.typeArgs.map((ta) =>
|
|
343
|
+
this.resolveTypeNode(ta, scope),
|
|
344
|
+
);
|
|
345
|
+
// Check constraints
|
|
346
|
+
for (
|
|
347
|
+
let i = 0;
|
|
348
|
+
i < baseType.typeParams.length && i < typeArgs.length;
|
|
349
|
+
i++
|
|
350
|
+
) {
|
|
351
|
+
const tp = baseType.typeParams[i];
|
|
352
|
+
const constraint = tp.constraint
|
|
353
|
+
? this.resolveTypeNode(tp.constraint, scope)
|
|
354
|
+
: null;
|
|
355
|
+
if (constraint) this.checkConstraint(typeArgs[i], constraint, expr);
|
|
356
|
+
}
|
|
357
|
+
return this.instantiateGenericFunc(baseType, typeArgs);
|
|
358
|
+
}
|
|
359
|
+
return baseType;
|
|
174
360
|
}
|
|
175
361
|
|
|
176
362
|
default:
|
|
@@ -179,8 +365,22 @@ export const expressionCheckMethods = {
|
|
|
179
365
|
return ANY;
|
|
180
366
|
},
|
|
181
367
|
|
|
368
|
+
_constIntValue(expr) {
|
|
369
|
+
if (expr.kind === "BasicLit" && expr.litKind === "INT")
|
|
370
|
+
return Number(expr.value);
|
|
371
|
+
if (
|
|
372
|
+
expr.kind === "UnaryExpr" &&
|
|
373
|
+
expr.op === "-" &&
|
|
374
|
+
expr.operand?.kind === "BasicLit" &&
|
|
375
|
+
expr.operand.litKind === "INT"
|
|
376
|
+
) {
|
|
377
|
+
return -Number(expr.operand.value);
|
|
378
|
+
}
|
|
379
|
+
return null;
|
|
380
|
+
},
|
|
381
|
+
|
|
182
382
|
checkCall(expr, scope) {
|
|
183
|
-
|
|
383
|
+
let fnType = this.checkExpr(expr.func, scope);
|
|
184
384
|
|
|
185
385
|
// Handle built-ins before evaluating args; some (e.g. new) take type names as
|
|
186
386
|
// arguments, which would otherwise produce false "Undefined" errors.
|
|
@@ -193,10 +393,47 @@ export const expressionCheckMethods = {
|
|
|
193
393
|
return this.checkBuiltin(fnType.name, expr, argTypes, scope);
|
|
194
394
|
}
|
|
195
395
|
|
|
196
|
-
|
|
396
|
+
// Multi-value forwarding: f(g()) where g() returns multiple values
|
|
397
|
+
// When a single argument is a multi-return call, flatten its tuple into args.
|
|
398
|
+
let argTypes;
|
|
399
|
+
if (expr.args.length === 1 && expr.args[0].kind === "CallExpr") {
|
|
400
|
+
const argType = this.checkExpr(expr.args[0], scope);
|
|
401
|
+
if (argType?.kind === "tuple") {
|
|
402
|
+
expr._multiForward = true; // flag for codegen
|
|
403
|
+
argTypes = argType.types;
|
|
404
|
+
} else {
|
|
405
|
+
argTypes = [argType];
|
|
406
|
+
}
|
|
407
|
+
} else {
|
|
408
|
+
argTypes = expr.args.map((a) => this.checkExpr(a, scope));
|
|
409
|
+
}
|
|
197
410
|
|
|
198
411
|
if (isAny(fnType)) return ANY;
|
|
199
412
|
|
|
413
|
+
// Generic function inference
|
|
414
|
+
if (fnType.kind === "generic") {
|
|
415
|
+
const typeArgs = this.inferTypeArgs(fnType, argTypes);
|
|
416
|
+
if (!typeArgs) {
|
|
417
|
+
return this.err(
|
|
418
|
+
`Cannot infer type arguments for generic function '${fnType.name}'`,
|
|
419
|
+
expr,
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
// Check constraints
|
|
423
|
+
for (
|
|
424
|
+
let i = 0;
|
|
425
|
+
i < fnType.typeParams.length && i < typeArgs.length;
|
|
426
|
+
i++
|
|
427
|
+
) {
|
|
428
|
+
const tp = fnType.typeParams[i];
|
|
429
|
+
const constraint = tp.constraint
|
|
430
|
+
? this.resolveTypeNode(tp.constraint, scope)
|
|
431
|
+
: null;
|
|
432
|
+
if (constraint) this.checkConstraint(typeArgs[i], constraint, expr);
|
|
433
|
+
}
|
|
434
|
+
fnType = this.instantiateGenericFunc(fnType, typeArgs);
|
|
435
|
+
}
|
|
436
|
+
|
|
200
437
|
if (fnType.kind !== "func") {
|
|
201
438
|
return this.err(`Cannot call non-function type ${typeStr(fnType)}`, expr);
|
|
202
439
|
}
|
|
@@ -238,10 +475,32 @@ export const expressionCheckMethods = {
|
|
|
238
475
|
checkBuiltin(name, expr, argTypes, scope) {
|
|
239
476
|
switch (name) {
|
|
240
477
|
case "len":
|
|
241
|
-
case "cap":
|
|
478
|
+
case "cap": {
|
|
479
|
+
// Compile-time len() for fixed arrays
|
|
480
|
+
if (
|
|
481
|
+
name === "len" &&
|
|
482
|
+
argTypes[0]?.kind === "array" &&
|
|
483
|
+
argTypes[0].size != null
|
|
484
|
+
) {
|
|
485
|
+
expr._constLen = argTypes[0].size;
|
|
486
|
+
} else if (
|
|
487
|
+
name === "len" &&
|
|
488
|
+
argTypes[0]?.kind === "named" &&
|
|
489
|
+
argTypes[0].underlying?.kind === "array" &&
|
|
490
|
+
argTypes[0].underlying.size != null
|
|
491
|
+
) {
|
|
492
|
+
expr._constLen = argTypes[0].underlying.size;
|
|
493
|
+
}
|
|
242
494
|
return INT;
|
|
243
|
-
|
|
244
|
-
|
|
495
|
+
}
|
|
496
|
+
case "append": {
|
|
497
|
+
const sliceType = argTypes[0] ?? ANY;
|
|
498
|
+
if (isArray(sliceType)) {
|
|
499
|
+
this.err(`cannot append to array (type ${typeStr(sliceType)})`, expr);
|
|
500
|
+
return sliceType;
|
|
501
|
+
}
|
|
502
|
+
return sliceType;
|
|
503
|
+
}
|
|
245
504
|
case "copy":
|
|
246
505
|
return INT;
|
|
247
506
|
case "delete":
|
|
@@ -270,6 +529,39 @@ export const expressionCheckMethods = {
|
|
|
270
529
|
return argTypes[0] ?? ANY;
|
|
271
530
|
case "clear":
|
|
272
531
|
return VOID;
|
|
532
|
+
case "complex": {
|
|
533
|
+
if (expr.args.length !== 2) {
|
|
534
|
+
this.err("complex() requires exactly 2 arguments", expr);
|
|
535
|
+
return ANY;
|
|
536
|
+
}
|
|
537
|
+
const crt = argTypes[0];
|
|
538
|
+
const cit = argTypes[1];
|
|
539
|
+
if (!isNumeric(crt)) {
|
|
540
|
+
this.err(`cannot use ${typeStr(crt)} as float in complex()`, expr);
|
|
541
|
+
return ANY;
|
|
542
|
+
}
|
|
543
|
+
if (!isNumeric(cit)) {
|
|
544
|
+
this.err(`cannot use ${typeStr(cit)} as float in complex()`, expr);
|
|
545
|
+
return ANY;
|
|
546
|
+
}
|
|
547
|
+
if (crt.kind === "untyped" && cit.kind === "untyped")
|
|
548
|
+
return UNTYPED_COMPLEX;
|
|
549
|
+
return COMPLEX128;
|
|
550
|
+
}
|
|
551
|
+
case "real":
|
|
552
|
+
case "imag": {
|
|
553
|
+
if (expr.args.length !== 1) {
|
|
554
|
+
this.err(`${name}() requires exactly 1 argument`, expr);
|
|
555
|
+
return ANY;
|
|
556
|
+
}
|
|
557
|
+
const zt = argTypes[0];
|
|
558
|
+
if (!isComplex(zt)) {
|
|
559
|
+
this.err(`cannot use ${typeStr(zt)} as complex in ${name}()`, expr);
|
|
560
|
+
return ANY;
|
|
561
|
+
}
|
|
562
|
+
if (zt.kind === "untyped") return UNTYPED_FLOAT;
|
|
563
|
+
return FLOAT64;
|
|
564
|
+
}
|
|
273
565
|
default:
|
|
274
566
|
return ANY;
|
|
275
567
|
}
|
|
@@ -283,7 +575,44 @@ export const expressionCheckMethods = {
|
|
|
283
575
|
: this.resolveTypeNode(expr.typeExpr, scope);
|
|
284
576
|
const base = t.kind === "named" ? t.underlying : t;
|
|
285
577
|
|
|
578
|
+
// [...]T size inference: count elements and set size
|
|
579
|
+
const typeNode = expr.typeExpr;
|
|
580
|
+
if (
|
|
581
|
+
typeNode?.kind === "ArrayType" &&
|
|
582
|
+
typeNode.inferLen &&
|
|
583
|
+
base?.kind === "array"
|
|
584
|
+
) {
|
|
585
|
+
const maxIndex = computeMaxIndex(expr.elems);
|
|
586
|
+
base.size = maxIndex + 1;
|
|
587
|
+
// If t is a named type wrapping the array, the underlying is already base
|
|
588
|
+
// If t === base, it's the same object
|
|
589
|
+
}
|
|
590
|
+
|
|
286
591
|
if (base?.kind === "struct") {
|
|
592
|
+
// Detect positional (unkeyed) literal: all elements must be non-KeyValueExpr
|
|
593
|
+
const hasPositional = expr.elems.some((e) => e.kind !== "KeyValueExpr");
|
|
594
|
+
const hasKeyed = expr.elems.some((e) => e.kind === "KeyValueExpr");
|
|
595
|
+
if (hasPositional && hasKeyed) {
|
|
596
|
+
this.err("mixture of field:value and value initializers", expr);
|
|
597
|
+
}
|
|
598
|
+
if (hasPositional) {
|
|
599
|
+
// Map positional elements to struct fields by declaration order
|
|
600
|
+
const fieldNames = [...(base.fields?.keys() ?? [])];
|
|
601
|
+
for (let i = 0; i < expr.elems.length; i++) {
|
|
602
|
+
const elem = expr.elems[i];
|
|
603
|
+
const fieldName = fieldNames[i];
|
|
604
|
+
if (!fieldName) {
|
|
605
|
+
this.err("too many values in struct literal", elem);
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
const fieldType = base.fields.get(fieldName);
|
|
609
|
+
const vt = this.checkExpr(elem, scope);
|
|
610
|
+
if (fieldType) this.assertAssignable(fieldType, vt, elem);
|
|
611
|
+
// Annotate for codegen
|
|
612
|
+
elem._positionalField = fieldName;
|
|
613
|
+
}
|
|
614
|
+
return t;
|
|
615
|
+
}
|
|
287
616
|
for (const elem of expr.elems) {
|
|
288
617
|
if (elem.kind === "KeyValueExpr") {
|
|
289
618
|
const keyName = elem.key.name;
|
|
@@ -316,11 +645,26 @@ export const expressionCheckMethods = {
|
|
|
316
645
|
if (elem.kind === "CompositeLit" && elem.typeExpr === null) {
|
|
317
646
|
const et = this.checkCompositeLit(elem, scope, base.elem);
|
|
318
647
|
elem._type = et;
|
|
319
|
-
} else if (elem.kind
|
|
648
|
+
} else if (elem.kind === "KeyValueExpr") {
|
|
649
|
+
const vt = this.checkExpr(elem.value, scope);
|
|
650
|
+
this.assertAssignable(base.elem, vt, elem.value);
|
|
651
|
+
} else {
|
|
320
652
|
const et = this.checkExpr(elem, scope);
|
|
321
653
|
this.assertAssignable(base.elem, et, elem);
|
|
322
654
|
}
|
|
323
655
|
}
|
|
656
|
+
|
|
657
|
+
// Composite literal element count validation for explicit-size arrays
|
|
658
|
+
if (base.kind === "array" && base.size != null && !typeNode?.inferLen) {
|
|
659
|
+
const maxIndex = computeMaxIndex(expr.elems);
|
|
660
|
+
if (maxIndex >= base.size) {
|
|
661
|
+
this.err(
|
|
662
|
+
`array index ${maxIndex} out of bounds [0:${base.size}]`,
|
|
663
|
+
expr,
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
324
668
|
return t;
|
|
325
669
|
}
|
|
326
670
|
|
|
@@ -357,3 +701,16 @@ export const expressionCheckMethods = {
|
|
|
357
701
|
return t ?? ANY;
|
|
358
702
|
},
|
|
359
703
|
};
|
|
704
|
+
|
|
705
|
+
function computeMaxIndex(elems) {
|
|
706
|
+
let sequential = 0;
|
|
707
|
+
let maxKeyed = -1;
|
|
708
|
+
for (const e of elems) {
|
|
709
|
+
if (e.kind === "KeyValueExpr" && e.key?.value !== undefined) {
|
|
710
|
+
maxKeyed = Math.max(maxKeyed, Number(e.key.value));
|
|
711
|
+
} else {
|
|
712
|
+
sequential++;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return Math.max(sequential - 1, maxKeyed);
|
|
716
|
+
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
isAny,
|
|
8
8
|
isBool,
|
|
9
9
|
isVoid,
|
|
10
|
+
iteratorYieldParams,
|
|
10
11
|
Scope,
|
|
11
12
|
} from "./types.js";
|
|
12
13
|
|
|
@@ -72,9 +73,37 @@ export const statementCheckMethods = {
|
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
case "AssignStmt": {
|
|
76
|
+
// comma-ok map index: v, ok = m["key"]
|
|
77
|
+
// Evaluate map expr first so _type is populated, then detect the pattern.
|
|
78
|
+
if (
|
|
79
|
+
stmt.lhs.length === 2 &&
|
|
80
|
+
stmt.rhs.length === 1 &&
|
|
81
|
+
stmt.rhs[0].kind === "IndexExpr"
|
|
82
|
+
) {
|
|
83
|
+
const mapBaseType = this.checkExpr(stmt.rhs[0].expr, scope);
|
|
84
|
+
const resolvedBase =
|
|
85
|
+
mapBaseType?.kind === "named"
|
|
86
|
+
? mapBaseType.underlying
|
|
87
|
+
: mapBaseType;
|
|
88
|
+
if (resolvedBase?.kind === "map") {
|
|
89
|
+
this.checkExpr(stmt.rhs[0].index, scope);
|
|
90
|
+
stmt._commaOkMap = true;
|
|
91
|
+
// Store the map value type so codegen can emit the zero-value fallback
|
|
92
|
+
stmt.rhs[0]._mapValueType = resolvedBase.value;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// comma-ok type assertion: v, ok = x.(T)
|
|
97
|
+
if (
|
|
98
|
+
stmt.lhs.length === 2 &&
|
|
99
|
+
stmt.rhs.length === 1 &&
|
|
100
|
+
stmt.rhs[0].kind === "TypeAssertExpr"
|
|
101
|
+
) {
|
|
102
|
+
stmt.rhs[0]._commaOk = true;
|
|
103
|
+
}
|
|
75
104
|
// Check for const reassignment before evaluating RHS
|
|
76
105
|
for (const lhs of stmt.lhs) {
|
|
77
|
-
if (lhs.kind === "Ident") {
|
|
106
|
+
if (lhs.kind === "Ident" && lhs.name !== "_") {
|
|
78
107
|
const ownerScope = scope.lookupScope(lhs.name);
|
|
79
108
|
if (ownerScope?.isConst(lhs.name))
|
|
80
109
|
this.err(`cannot assign to const '${lhs.name}'`, lhs);
|
|
@@ -83,8 +112,13 @@ export const statementCheckMethods = {
|
|
|
83
112
|
const rhs = stmt.rhs.map((e) => this.checkExpr(e, scope));
|
|
84
113
|
const rhsFlat =
|
|
85
114
|
rhs.length === 1 && rhs[0]?.kind === "tuple" ? rhs[0].types : rhs;
|
|
86
|
-
const lhsTypes = stmt.lhs.map((e) =>
|
|
115
|
+
const lhsTypes = stmt.lhs.map((e) => {
|
|
116
|
+
if (e.kind === "Ident" && e.name === "_") return ANY;
|
|
117
|
+
return this.checkExpr(e, scope);
|
|
118
|
+
});
|
|
87
119
|
for (let i = 0; i < lhsTypes.length; i++) {
|
|
120
|
+
if (stmt.lhs[i]?.kind === "Ident" && stmt.lhs[i]?.name === "_")
|
|
121
|
+
continue;
|
|
88
122
|
const r = rhsFlat[i] ?? ANY;
|
|
89
123
|
if (!isAny(lhsTypes[i]) && !isAny(r)) {
|
|
90
124
|
this.assertAssignable(lhsTypes[i], r, stmt.lhs[i]);
|
|
@@ -149,15 +183,30 @@ export const statementCheckMethods = {
|
|
|
149
183
|
|
|
150
184
|
case "ForStmt": {
|
|
151
185
|
const inner = new Scope(scope);
|
|
152
|
-
|
|
186
|
+
let iterInfo = null;
|
|
187
|
+
if (stmt.init?.rhs?.[0]?.kind === "RangeExpr") {
|
|
188
|
+
iterInfo = this._checkRangeIterStmt(stmt.init, inner, returnType);
|
|
189
|
+
}
|
|
190
|
+
if (!iterInfo && stmt.init)
|
|
191
|
+
this.checkStmt(stmt.init, inner, returnType);
|
|
153
192
|
if (stmt.cond) {
|
|
154
|
-
// for range N { } — RangeExpr as condition is valid (Go 1.22 int range)
|
|
155
193
|
if (stmt.cond.kind !== "RangeExpr") {
|
|
156
194
|
const ct = this.checkExpr(stmt.cond, inner);
|
|
157
195
|
if (!isBool(ct) && !isAny(ct))
|
|
158
196
|
this.err("For condition must be bool", stmt);
|
|
159
197
|
} else {
|
|
160
|
-
|
|
198
|
+
// for range expr — could be int range or 0-param iterator
|
|
199
|
+
const ct = this.checkExpr(stmt.cond.expr, inner);
|
|
200
|
+
const info = iteratorYieldParams(ct);
|
|
201
|
+
if (info) {
|
|
202
|
+
stmt.cond._isIterator = true;
|
|
203
|
+
stmt.cond._yieldParams = info.yieldParams;
|
|
204
|
+
} else if (ct?.kind === "func") {
|
|
205
|
+
this.err(
|
|
206
|
+
"cannot range over func: not an iterator function",
|
|
207
|
+
stmt,
|
|
208
|
+
);
|
|
209
|
+
}
|
|
161
210
|
}
|
|
162
211
|
}
|
|
163
212
|
if (stmt.post) this.checkStmt(stmt.post, inner, returnType);
|
|
@@ -200,6 +249,10 @@ export const statementCheckMethods = {
|
|
|
200
249
|
? this.resolveTypeNode(c.types[0], inner)
|
|
201
250
|
: ANY;
|
|
202
251
|
caseScope.defineLocal(stmt.assign, bindType);
|
|
252
|
+
// The capture variable is always considered used — it's the whole
|
|
253
|
+
// point of the switch-with-assign form. Each case body uses its own
|
|
254
|
+
// typed copy of the variable.
|
|
255
|
+
caseScope.lookup(stmt.assign);
|
|
203
256
|
}
|
|
204
257
|
for (const s of c.stmts) this.checkStmt(s, caseScope, returnType);
|
|
205
258
|
this._reportUnused(caseScope, stmt);
|
|
@@ -247,4 +300,49 @@ export const statementCheckMethods = {
|
|
|
247
300
|
break;
|
|
248
301
|
}
|
|
249
302
|
},
|
|
303
|
+
|
|
304
|
+
_checkRangeIterStmt(initStmt, scope, _returnType) {
|
|
305
|
+
const rangeExpr = initStmt.rhs[0];
|
|
306
|
+
const iterType = this.checkExpr(rangeExpr.expr, scope);
|
|
307
|
+
const info = iteratorYieldParams(iterType);
|
|
308
|
+
if (!info) {
|
|
309
|
+
// If it's a func but not a valid iterator shape, reject it
|
|
310
|
+
const underlying =
|
|
311
|
+
iterType?.kind === "named" ? iterType.underlying : iterType;
|
|
312
|
+
if (underlying?.kind === "func") {
|
|
313
|
+
this.err("cannot range over func: not an iterator function", initStmt);
|
|
314
|
+
}
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const lhs = initStmt.lhs;
|
|
319
|
+
if (lhs.length > info.yieldParams.length) {
|
|
320
|
+
this.err(
|
|
321
|
+
`range over iterator: too many loop variables (got ${lhs.length}, max ${info.yieldParams.length})`,
|
|
322
|
+
initStmt,
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Annotate for codegen
|
|
327
|
+
rangeExpr._isIterator = true;
|
|
328
|
+
rangeExpr._yieldParams = info.yieldParams;
|
|
329
|
+
|
|
330
|
+
// Bind loop variables to their yield param types
|
|
331
|
+
if (initStmt.kind === "DefineStmt") {
|
|
332
|
+
for (let i = 0; i < lhs.length; i++) {
|
|
333
|
+
const name = lhs[i].name ?? lhs[i];
|
|
334
|
+
if (name === "_") continue;
|
|
335
|
+
const varType = info.yieldParams[i] ?? ANY;
|
|
336
|
+
scope.defineLocal(name, varType);
|
|
337
|
+
}
|
|
338
|
+
} else {
|
|
339
|
+
// AssignStmt — just type-check the lhs expressions
|
|
340
|
+
for (const e of lhs) {
|
|
341
|
+
if (e.kind === "Ident" && e.name !== "_") {
|
|
342
|
+
this.checkExpr(e, scope);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return info;
|
|
347
|
+
},
|
|
250
348
|
};
|