gofront 0.0.4 → 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 +104 -126
- package/package.json +2 -3
- package/src/codegen/expressions.js +448 -256
- package/src/codegen/statements.js +193 -6
- package/src/codegen.js +89 -3
- 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 +57 -4
- package/src/typechecker/expressions.js +279 -10
- package/src/typechecker/statements.js +64 -3
- package/src/typechecker/types.js +96 -10
- package/src/typechecker.js +482 -16
|
@@ -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,6 +51,9 @@ 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,10 +69,27 @@ export const expressionCheckMethods = {
|
|
|
60
69
|
return BOOL;
|
|
61
70
|
}
|
|
62
71
|
if (expr.op === "-" || expr.op === "+") {
|
|
63
|
-
if (!isNumeric(ot) && !isAny(ot))
|
|
72
|
+
if (!isNumeric(ot) && !isComplex(ot) && !isAny(ot))
|
|
64
73
|
this.err(`${expr.op} requires numeric`, expr);
|
|
65
74
|
return ot;
|
|
66
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
|
+
}
|
|
67
93
|
return ot;
|
|
68
94
|
}
|
|
69
95
|
|
|
@@ -114,7 +140,26 @@ export const expressionCheckMethods = {
|
|
|
114
140
|
const bt = this.checkExpr(expr.expr, scope);
|
|
115
141
|
this.checkExpr(expr.index, scope);
|
|
116
142
|
if (isAny(bt)) return ANY;
|
|
117
|
-
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
|
+
}
|
|
118
163
|
if (bt.kind === "map") {
|
|
119
164
|
expr._mapValueType = bt.value; // for codegen zero-value fallback
|
|
120
165
|
return bt.value;
|
|
@@ -172,8 +217,74 @@ export const expressionCheckMethods = {
|
|
|
172
217
|
}
|
|
173
218
|
|
|
174
219
|
case "TypeConversion": {
|
|
175
|
-
this.checkExpr(expr.expr, scope);
|
|
176
|
-
|
|
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;
|
|
177
288
|
}
|
|
178
289
|
|
|
179
290
|
case "TypeAssertExpr": {
|
|
@@ -211,7 +322,7 @@ export const expressionCheckMethods = {
|
|
|
211
322
|
// string range: (int index, rune value) — rune is an alias for int
|
|
212
323
|
return { kind: "tuple", types: [INT, INT] };
|
|
213
324
|
}
|
|
214
|
-
if (resolved?.kind === "slice") {
|
|
325
|
+
if (resolved?.kind === "slice" || resolved?.kind === "array") {
|
|
215
326
|
return { kind: "tuple", types: [INT, resolved.elem ?? ANY] };
|
|
216
327
|
}
|
|
217
328
|
if (resolved?.kind === "map") {
|
|
@@ -224,14 +335,52 @@ export const expressionCheckMethods = {
|
|
|
224
335
|
return { kind: "tuple", types: [collType] };
|
|
225
336
|
}
|
|
226
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;
|
|
360
|
+
}
|
|
361
|
+
|
|
227
362
|
default:
|
|
228
363
|
return ANY;
|
|
229
364
|
}
|
|
230
365
|
return ANY;
|
|
231
366
|
},
|
|
232
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
|
+
|
|
233
382
|
checkCall(expr, scope) {
|
|
234
|
-
|
|
383
|
+
let fnType = this.checkExpr(expr.func, scope);
|
|
235
384
|
|
|
236
385
|
// Handle built-ins before evaluating args; some (e.g. new) take type names as
|
|
237
386
|
// arguments, which would otherwise produce false "Undefined" errors.
|
|
@@ -261,6 +410,30 @@ export const expressionCheckMethods = {
|
|
|
261
410
|
|
|
262
411
|
if (isAny(fnType)) return ANY;
|
|
263
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
|
+
|
|
264
437
|
if (fnType.kind !== "func") {
|
|
265
438
|
return this.err(`Cannot call non-function type ${typeStr(fnType)}`, expr);
|
|
266
439
|
}
|
|
@@ -302,10 +475,32 @@ export const expressionCheckMethods = {
|
|
|
302
475
|
checkBuiltin(name, expr, argTypes, scope) {
|
|
303
476
|
switch (name) {
|
|
304
477
|
case "len":
|
|
305
|
-
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
|
+
}
|
|
306
494
|
return INT;
|
|
307
|
-
|
|
308
|
-
|
|
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
|
+
}
|
|
309
504
|
case "copy":
|
|
310
505
|
return INT;
|
|
311
506
|
case "delete":
|
|
@@ -334,6 +529,39 @@ export const expressionCheckMethods = {
|
|
|
334
529
|
return argTypes[0] ?? ANY;
|
|
335
530
|
case "clear":
|
|
336
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
|
+
}
|
|
337
565
|
default:
|
|
338
566
|
return ANY;
|
|
339
567
|
}
|
|
@@ -347,6 +575,19 @@ export const expressionCheckMethods = {
|
|
|
347
575
|
: this.resolveTypeNode(expr.typeExpr, scope);
|
|
348
576
|
const base = t.kind === "named" ? t.underlying : t;
|
|
349
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
|
+
|
|
350
591
|
if (base?.kind === "struct") {
|
|
351
592
|
// Detect positional (unkeyed) literal: all elements must be non-KeyValueExpr
|
|
352
593
|
const hasPositional = expr.elems.some((e) => e.kind !== "KeyValueExpr");
|
|
@@ -404,11 +645,26 @@ export const expressionCheckMethods = {
|
|
|
404
645
|
if (elem.kind === "CompositeLit" && elem.typeExpr === null) {
|
|
405
646
|
const et = this.checkCompositeLit(elem, scope, base.elem);
|
|
406
647
|
elem._type = et;
|
|
407
|
-
} 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 {
|
|
408
652
|
const et = this.checkExpr(elem, scope);
|
|
409
653
|
this.assertAssignable(base.elem, et, elem);
|
|
410
654
|
}
|
|
411
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
|
+
|
|
412
668
|
return t;
|
|
413
669
|
}
|
|
414
670
|
|
|
@@ -445,3 +701,16 @@ export const expressionCheckMethods = {
|
|
|
445
701
|
return t ?? ANY;
|
|
446
702
|
},
|
|
447
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
|
|
|
@@ -182,15 +183,30 @@ export const statementCheckMethods = {
|
|
|
182
183
|
|
|
183
184
|
case "ForStmt": {
|
|
184
185
|
const inner = new Scope(scope);
|
|
185
|
-
|
|
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);
|
|
186
192
|
if (stmt.cond) {
|
|
187
|
-
// for range N { } — RangeExpr as condition is valid (Go 1.22 int range)
|
|
188
193
|
if (stmt.cond.kind !== "RangeExpr") {
|
|
189
194
|
const ct = this.checkExpr(stmt.cond, inner);
|
|
190
195
|
if (!isBool(ct) && !isAny(ct))
|
|
191
196
|
this.err("For condition must be bool", stmt);
|
|
192
197
|
} else {
|
|
193
|
-
|
|
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
|
+
}
|
|
194
210
|
}
|
|
195
211
|
}
|
|
196
212
|
if (stmt.post) this.checkStmt(stmt.post, inner, returnType);
|
|
@@ -284,4 +300,49 @@ export const statementCheckMethods = {
|
|
|
284
300
|
break;
|
|
285
301
|
}
|
|
286
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
|
+
},
|
|
287
348
|
};
|
package/src/typechecker/types.js
CHANGED
|
@@ -47,7 +47,13 @@ export const BOOL = { kind: "basic", name: "bool" };
|
|
|
47
47
|
export const ANY = { kind: "basic", name: "any" };
|
|
48
48
|
export const VOID = { kind: "basic", name: "void" };
|
|
49
49
|
export const NIL = { kind: "basic", name: "nil" };
|
|
50
|
-
export const ERROR = {
|
|
50
|
+
export const ERROR = {
|
|
51
|
+
kind: "interface",
|
|
52
|
+
name: "error",
|
|
53
|
+
methods: new Map([
|
|
54
|
+
["Error", { kind: "func", params: [], returns: [STRING], async: false }],
|
|
55
|
+
]),
|
|
56
|
+
};
|
|
51
57
|
|
|
52
58
|
// ── Untyped constant types (Go spec §Constants) ─────────────
|
|
53
59
|
// Untyped constants coerce to any compatible typed context.
|
|
@@ -56,6 +62,11 @@ export const UNTYPED_FLOAT = { kind: "untyped", base: "float64" };
|
|
|
56
62
|
export const UNTYPED_STRING = { kind: "untyped", base: "string" };
|
|
57
63
|
export const UNTYPED_BOOL = { kind: "untyped", base: "bool" };
|
|
58
64
|
|
|
65
|
+
// ── Complex types ────────────────────────────────────────────
|
|
66
|
+
export const COMPLEX128 = { kind: "basic", name: "complex128" };
|
|
67
|
+
export const COMPLEX64 = { kind: "basic", name: "complex64" };
|
|
68
|
+
export const UNTYPED_COMPLEX = { kind: "untyped", base: "complex128" };
|
|
69
|
+
|
|
59
70
|
export const BASIC_TYPES = {
|
|
60
71
|
int: INT,
|
|
61
72
|
float64: FLOAT64,
|
|
@@ -64,7 +75,6 @@ export const BASIC_TYPES = {
|
|
|
64
75
|
any: ANY,
|
|
65
76
|
byte: INT,
|
|
66
77
|
rune: INT,
|
|
67
|
-
error: ERROR,
|
|
68
78
|
// Sized integer / float aliases — all map to int or float64 at runtime
|
|
69
79
|
uint: INT,
|
|
70
80
|
int8: INT,
|
|
@@ -77,19 +87,51 @@ export const BASIC_TYPES = {
|
|
|
77
87
|
uint64: INT,
|
|
78
88
|
uintptr: INT,
|
|
79
89
|
float32: FLOAT64,
|
|
90
|
+
complex64: COMPLEX64,
|
|
91
|
+
complex128: COMPLEX128,
|
|
80
92
|
};
|
|
81
93
|
|
|
94
|
+
export const COMPARABLE = { kind: "basic", name: "comparable" };
|
|
95
|
+
|
|
96
|
+
export function isTypeParam(t) {
|
|
97
|
+
return t?.kind === "typeParam";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function isGeneric(t) {
|
|
101
|
+
return t?.kind === "generic";
|
|
102
|
+
}
|
|
103
|
+
|
|
82
104
|
export function isNumeric(t) {
|
|
83
|
-
if (t
|
|
84
|
-
|
|
105
|
+
if (!t) return false;
|
|
106
|
+
if (t.kind === "untyped") return t.base === "int" || t.base === "float64";
|
|
107
|
+
if (t.kind === "basic") return t.name === "int" || t.name === "float64";
|
|
108
|
+
if (t.kind === "named") return isNumeric(t.underlying);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
export function isComplex(t) {
|
|
112
|
+
if (!t) return false;
|
|
113
|
+
if (t.kind === "basic" && (t.name === "complex128" || t.name === "complex64"))
|
|
114
|
+
return true;
|
|
115
|
+
if (t.kind === "untyped" && t.base === "complex128") return true;
|
|
116
|
+
if (t.kind === "named") return isComplex(t.underlying);
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
export function isComplexOrNumeric(t) {
|
|
120
|
+
return isNumeric(t) || isComplex(t);
|
|
85
121
|
}
|
|
86
122
|
export function isString(t) {
|
|
87
|
-
if (t
|
|
88
|
-
|
|
123
|
+
if (!t) return false;
|
|
124
|
+
if (t.kind === "untyped") return t.base === "string";
|
|
125
|
+
if (t.kind === "basic") return t.name === "string";
|
|
126
|
+
if (t.kind === "named") return isString(t.underlying);
|
|
127
|
+
return false;
|
|
89
128
|
}
|
|
90
129
|
export function isBool(t) {
|
|
91
|
-
if (t
|
|
92
|
-
|
|
130
|
+
if (!t) return false;
|
|
131
|
+
if (t.kind === "untyped") return t.base === "bool";
|
|
132
|
+
if (t.kind === "basic") return t.name === "bool";
|
|
133
|
+
if (t.kind === "named") return isBool(t.underlying);
|
|
134
|
+
return false;
|
|
93
135
|
}
|
|
94
136
|
export function isAny(t) {
|
|
95
137
|
return t?.kind === "basic" && t.name === "any";
|
|
@@ -101,7 +143,23 @@ export function isVoid(t) {
|
|
|
101
143
|
return t?.kind === "basic" && t.name === "void";
|
|
102
144
|
}
|
|
103
145
|
export function isError(t) {
|
|
104
|
-
|
|
146
|
+
if (!t) return false;
|
|
147
|
+
if (t === ERROR) return true;
|
|
148
|
+
if (t.kind === "interface" && t.name === "error") return true;
|
|
149
|
+
if (t.kind === "named" && isError(t.underlying)) return true;
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
export function isPointer(t) {
|
|
153
|
+
if (!t) return false;
|
|
154
|
+
if (t.kind === "pointer") return true;
|
|
155
|
+
if (t.kind === "named") return t.underlying?.kind === "pointer";
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
export function isArray(t) {
|
|
159
|
+
if (!t) return false;
|
|
160
|
+
if (t.kind === "array") return true;
|
|
161
|
+
if (t.kind === "named") return t.underlying?.kind === "array";
|
|
162
|
+
return false;
|
|
105
163
|
}
|
|
106
164
|
export function isUntyped(t) {
|
|
107
165
|
return t?.kind === "untyped";
|
|
@@ -119,6 +177,8 @@ export function defaultType(t) {
|
|
|
119
177
|
return STRING;
|
|
120
178
|
case "bool":
|
|
121
179
|
return BOOL;
|
|
180
|
+
case "complex128":
|
|
181
|
+
return COMPLEX128;
|
|
122
182
|
default:
|
|
123
183
|
return ANY;
|
|
124
184
|
}
|
|
@@ -135,7 +195,7 @@ export function typeStr(t) {
|
|
|
135
195
|
case "slice":
|
|
136
196
|
return `[]${typeStr(t.elem)}`;
|
|
137
197
|
case "array":
|
|
138
|
-
return `[${t.size}]${typeStr(t.elem)}`;
|
|
198
|
+
return `[${t.size ?? "..."}]${typeStr(t.elem)}`;
|
|
139
199
|
case "map":
|
|
140
200
|
return `map[${typeStr(t.key)}]${typeStr(t.value)}`;
|
|
141
201
|
case "struct":
|
|
@@ -152,11 +212,37 @@ export function typeStr(t) {
|
|
|
152
212
|
return t.name;
|
|
153
213
|
case "pointer":
|
|
154
214
|
return `*${typeStr(t.base)}`;
|
|
215
|
+
case "typeParam":
|
|
216
|
+
return t.name;
|
|
217
|
+
case "generic":
|
|
218
|
+
return t.name || `generic(${typeStr(t.underlying)})`;
|
|
155
219
|
default:
|
|
156
220
|
return "?";
|
|
157
221
|
}
|
|
158
222
|
}
|
|
159
223
|
|
|
224
|
+
// ── Iterator function detection (Go 1.23 range-over-func) ───
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Returns null if t is not an iterator function.
|
|
228
|
+
* Returns { yieldParams: Type[] } if it is, where yieldParams are the
|
|
229
|
+
* types the range variables will be bound to (0, 1, or 2 elements).
|
|
230
|
+
*
|
|
231
|
+
* An iterator is func(yield func(...) bool) — single param that is a
|
|
232
|
+
* func returning bool with 0-2 params.
|
|
233
|
+
*/
|
|
234
|
+
export function iteratorYieldParams(t) {
|
|
235
|
+
const fn = t?.kind === "named" ? t.underlying : t;
|
|
236
|
+
if (fn?.kind !== "func") return null;
|
|
237
|
+
if (fn.params.length !== 1) return null;
|
|
238
|
+
const yieldFn =
|
|
239
|
+
fn.params[0]?.kind === "named" ? fn.params[0].underlying : fn.params[0];
|
|
240
|
+
if (yieldFn?.kind !== "func") return null;
|
|
241
|
+
if (yieldFn.returns.length !== 1 || !isBool(yieldFn.returns[0])) return null;
|
|
242
|
+
if (yieldFn.params.length > 2) return null;
|
|
243
|
+
return { yieldParams: yieldFn.params };
|
|
244
|
+
}
|
|
245
|
+
|
|
160
246
|
// ── Scope / environment ──────────────────────────────────────
|
|
161
247
|
|
|
162
248
|
export class Scope {
|