gofront 0.0.4 → 0.0.6
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 +123 -139
- package/package.json +2 -3
- package/src/codegen/expressions.js +778 -253
- package/src/codegen/source-map.js +13 -5
- package/src/codegen/statements.js +193 -6
- package/src/codegen.js +115 -15
- package/src/compiler.js +10 -3
- package/src/dev-server.js +83 -0
- package/src/dts-parser.js +1 -1
- package/src/index.js +47 -16
- 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 +698 -16
package/src/typechecker.js
CHANGED
|
@@ -13,14 +13,17 @@ import {
|
|
|
13
13
|
BASIC_TYPES,
|
|
14
14
|
BOOL,
|
|
15
15
|
CMP_OPS,
|
|
16
|
+
COMPLEX128,
|
|
16
17
|
defaultType,
|
|
17
18
|
ERROR,
|
|
18
19
|
FLOAT64,
|
|
19
20
|
INT,
|
|
20
21
|
isAny,
|
|
21
|
-
|
|
22
|
+
isComplex,
|
|
23
|
+
isComplexOrNumeric,
|
|
22
24
|
isNil,
|
|
23
25
|
isNumeric,
|
|
26
|
+
isPointer,
|
|
24
27
|
isString,
|
|
25
28
|
isUntyped,
|
|
26
29
|
isVoid,
|
|
@@ -29,6 +32,7 @@ import {
|
|
|
29
32
|
STRING,
|
|
30
33
|
TypeCheckError,
|
|
31
34
|
typeStr,
|
|
35
|
+
UNTYPED_COMPLEX,
|
|
32
36
|
UNTYPED_FLOAT,
|
|
33
37
|
UNTYPED_INT,
|
|
34
38
|
VOID,
|
|
@@ -50,6 +54,8 @@ export class TypeChecker {
|
|
|
50
54
|
this._deferCount = 0; // tracks defer usage in current function body
|
|
51
55
|
this._imports = []; // tracked imports for unused-import detection
|
|
52
56
|
this._setupGlobals();
|
|
57
|
+
// Register error as a named type (interface)
|
|
58
|
+
this.types.set("error", ERROR);
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
_setupGlobals() {
|
|
@@ -140,6 +146,24 @@ export class TypeChecker {
|
|
|
140
146
|
Printf: fmtVariadic(VOID),
|
|
141
147
|
Println: fmtVariadic(VOID),
|
|
142
148
|
Print: fmtVariadic(VOID),
|
|
149
|
+
Fprintf: {
|
|
150
|
+
kind: "func",
|
|
151
|
+
params: [ANY, STRING],
|
|
152
|
+
returns: [INT, ERROR],
|
|
153
|
+
variadic: true,
|
|
154
|
+
},
|
|
155
|
+
Fprintln: {
|
|
156
|
+
kind: "func",
|
|
157
|
+
params: [ANY],
|
|
158
|
+
returns: [INT, ERROR],
|
|
159
|
+
variadic: true,
|
|
160
|
+
},
|
|
161
|
+
Fprint: {
|
|
162
|
+
kind: "func",
|
|
163
|
+
params: [ANY],
|
|
164
|
+
returns: [INT, ERROR],
|
|
165
|
+
variadic: true,
|
|
166
|
+
},
|
|
143
167
|
},
|
|
144
168
|
});
|
|
145
169
|
|
|
@@ -194,6 +218,47 @@ export class TypeChecker {
|
|
|
194
218
|
},
|
|
195
219
|
});
|
|
196
220
|
|
|
221
|
+
// bytes package — operates on []byte (JS arrays of numbers)
|
|
222
|
+
const BYTE_SLICE = { kind: "slice", elem: INT };
|
|
223
|
+
const byFn1 = (ret) => ({
|
|
224
|
+
kind: "func",
|
|
225
|
+
params: [BYTE_SLICE],
|
|
226
|
+
returns: [ret],
|
|
227
|
+
});
|
|
228
|
+
const byFn2 = (p2, ret) => ({
|
|
229
|
+
kind: "func",
|
|
230
|
+
params: [BYTE_SLICE, p2],
|
|
231
|
+
returns: [ret],
|
|
232
|
+
});
|
|
233
|
+
this.globals.define("bytes", {
|
|
234
|
+
kind: "namespace",
|
|
235
|
+
name: "bytes",
|
|
236
|
+
members: {
|
|
237
|
+
Contains: byFn2(BYTE_SLICE, BOOL),
|
|
238
|
+
HasPrefix: byFn2(BYTE_SLICE, BOOL),
|
|
239
|
+
HasSuffix: byFn2(BYTE_SLICE, BOOL),
|
|
240
|
+
Index: byFn2(BYTE_SLICE, INT),
|
|
241
|
+
Count: byFn2(BYTE_SLICE, INT),
|
|
242
|
+
Repeat: byFn2(INT, BYTE_SLICE),
|
|
243
|
+
Replace: {
|
|
244
|
+
kind: "func",
|
|
245
|
+
params: [BYTE_SLICE, BYTE_SLICE, BYTE_SLICE, INT],
|
|
246
|
+
returns: [BYTE_SLICE],
|
|
247
|
+
},
|
|
248
|
+
ToUpper: byFn1(BYTE_SLICE),
|
|
249
|
+
ToLower: byFn1(BYTE_SLICE),
|
|
250
|
+
TrimSpace: byFn1(BYTE_SLICE),
|
|
251
|
+
Trim: byFn2(STRING, BYTE_SLICE),
|
|
252
|
+
Equal: byFn2(BYTE_SLICE, BOOL),
|
|
253
|
+
Split: byFn2(BYTE_SLICE, { kind: "slice", elem: BYTE_SLICE }),
|
|
254
|
+
Join: {
|
|
255
|
+
kind: "func",
|
|
256
|
+
params: [{ kind: "slice", elem: BYTE_SLICE }, BYTE_SLICE],
|
|
257
|
+
returns: [BYTE_SLICE],
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
|
|
197
262
|
// strconv package
|
|
198
263
|
this.globals.define("strconv", {
|
|
199
264
|
kind: "namespace",
|
|
@@ -329,6 +394,8 @@ export class TypeChecker {
|
|
|
329
394
|
name: "errors",
|
|
330
395
|
members: {
|
|
331
396
|
New: { kind: "func", params: [STRING], returns: [ERROR] },
|
|
397
|
+
Is: { kind: "func", params: [ERROR, ERROR], returns: [BOOL] },
|
|
398
|
+
Unwrap: { kind: "func", params: [ERROR], returns: [ERROR] },
|
|
332
399
|
},
|
|
333
400
|
});
|
|
334
401
|
|
|
@@ -382,6 +449,195 @@ export class TypeChecker {
|
|
|
382
449
|
},
|
|
383
450
|
});
|
|
384
451
|
|
|
452
|
+
// slices package (Go 1.21)
|
|
453
|
+
// Functions are generic — use ANY for slice params so any concrete slice type
|
|
454
|
+
// is accepted without an assignability error (same pattern as sort.Slice).
|
|
455
|
+
this.globals.define("slices", {
|
|
456
|
+
kind: "namespace",
|
|
457
|
+
name: "slices",
|
|
458
|
+
members: {
|
|
459
|
+
Contains: { kind: "func", params: [ANY, ANY], returns: [BOOL] },
|
|
460
|
+
Index: { kind: "func", params: [ANY, ANY], returns: [INT] },
|
|
461
|
+
Equal: { kind: "func", params: [ANY, ANY], returns: [BOOL] },
|
|
462
|
+
Compare: { kind: "func", params: [ANY, ANY], returns: [INT] },
|
|
463
|
+
Sort: { kind: "func", params: [ANY], returns: [VOID] },
|
|
464
|
+
SortFunc: { kind: "func", params: [ANY, ANY], returns: [VOID] },
|
|
465
|
+
SortStableFunc: { kind: "func", params: [ANY, ANY], returns: [VOID] },
|
|
466
|
+
IsSorted: { kind: "func", params: [ANY], returns: [BOOL] },
|
|
467
|
+
IsSortedFunc: { kind: "func", params: [ANY, ANY], returns: [BOOL] },
|
|
468
|
+
Reverse: { kind: "func", params: [ANY], returns: [VOID] },
|
|
469
|
+
Max: { kind: "func", params: [ANY], returns: [ANY] },
|
|
470
|
+
Min: { kind: "func", params: [ANY], returns: [ANY] },
|
|
471
|
+
MaxFunc: { kind: "func", params: [ANY, ANY], returns: [ANY] },
|
|
472
|
+
MinFunc: { kind: "func", params: [ANY, ANY], returns: [ANY] },
|
|
473
|
+
Clone: { kind: "func", params: [ANY], returns: [ANY] },
|
|
474
|
+
Compact: { kind: "func", params: [ANY], returns: [ANY] },
|
|
475
|
+
CompactFunc: { kind: "func", params: [ANY, ANY], returns: [ANY] },
|
|
476
|
+
Concat: { kind: "func", params: [ANY], returns: [ANY], variadic: true },
|
|
477
|
+
Delete: { kind: "func", params: [ANY, INT, INT], returns: [ANY] },
|
|
478
|
+
DeleteFunc: { kind: "func", params: [ANY, ANY], returns: [ANY] },
|
|
479
|
+
Insert: {
|
|
480
|
+
kind: "func",
|
|
481
|
+
params: [ANY, INT, ANY],
|
|
482
|
+
returns: [ANY],
|
|
483
|
+
variadic: true,
|
|
484
|
+
},
|
|
485
|
+
Replace: {
|
|
486
|
+
kind: "func",
|
|
487
|
+
params: [ANY, INT, INT, ANY],
|
|
488
|
+
returns: [ANY],
|
|
489
|
+
variadic: true,
|
|
490
|
+
},
|
|
491
|
+
Grow: { kind: "func", params: [ANY, INT], returns: [ANY] },
|
|
492
|
+
Clip: { kind: "func", params: [ANY], returns: [ANY] },
|
|
493
|
+
},
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// html package
|
|
497
|
+
this.globals.define("html", {
|
|
498
|
+
kind: "namespace",
|
|
499
|
+
name: "html",
|
|
500
|
+
members: {
|
|
501
|
+
EscapeString: { kind: "func", params: [STRING], returns: [STRING] },
|
|
502
|
+
UnescapeString: { kind: "func", params: [STRING], returns: [STRING] },
|
|
503
|
+
},
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// maps package (Go 1.21)
|
|
507
|
+
this.globals.define("maps", {
|
|
508
|
+
kind: "namespace",
|
|
509
|
+
name: "maps",
|
|
510
|
+
members: {
|
|
511
|
+
Keys: { kind: "func", params: [ANY], returns: [ANY] },
|
|
512
|
+
Values: { kind: "func", params: [ANY], returns: [ANY] },
|
|
513
|
+
Clone: { kind: "func", params: [ANY], returns: [ANY] },
|
|
514
|
+
Copy: { kind: "func", params: [ANY, ANY], returns: [VOID] },
|
|
515
|
+
Equal: { kind: "func", params: [ANY, ANY], returns: [BOOL] },
|
|
516
|
+
EqualFunc: { kind: "func", params: [ANY, ANY, ANY], returns: [BOOL] },
|
|
517
|
+
Delete: { kind: "func", params: [ANY, ANY], returns: [VOID] },
|
|
518
|
+
DeleteFunc: { kind: "func", params: [ANY, ANY], returns: [VOID] },
|
|
519
|
+
},
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
// regexp package
|
|
523
|
+
const REGEXP_T = {
|
|
524
|
+
kind: "named",
|
|
525
|
+
name: "regexp.Regexp",
|
|
526
|
+
underlying: { kind: "struct", fields: new Map(), methods: new Map() },
|
|
527
|
+
};
|
|
528
|
+
const REGEXP_PTR = { kind: "pointer", base: REGEXP_T };
|
|
529
|
+
const STR_SLICE = { kind: "slice", elem: STRING };
|
|
530
|
+
const INT_SLICE = { kind: "slice", elem: INT };
|
|
531
|
+
const regexpMethods = new Map([
|
|
532
|
+
["MatchString", { kind: "func", params: [STRING], returns: [BOOL] }],
|
|
533
|
+
["FindString", { kind: "func", params: [STRING], returns: [STRING] }],
|
|
534
|
+
[
|
|
535
|
+
"FindStringIndex",
|
|
536
|
+
{ kind: "func", params: [STRING], returns: [INT_SLICE] },
|
|
537
|
+
],
|
|
538
|
+
[
|
|
539
|
+
"FindAllString",
|
|
540
|
+
{ kind: "func", params: [STRING, INT], returns: [STR_SLICE] },
|
|
541
|
+
],
|
|
542
|
+
[
|
|
543
|
+
"FindStringSubmatch",
|
|
544
|
+
{ kind: "func", params: [STRING], returns: [STR_SLICE] },
|
|
545
|
+
],
|
|
546
|
+
[
|
|
547
|
+
"FindAllStringSubmatch",
|
|
548
|
+
{
|
|
549
|
+
kind: "func",
|
|
550
|
+
params: [STRING, INT],
|
|
551
|
+
returns: [{ kind: "slice", elem: STR_SLICE }],
|
|
552
|
+
},
|
|
553
|
+
],
|
|
554
|
+
[
|
|
555
|
+
"ReplaceAllString",
|
|
556
|
+
{ kind: "func", params: [STRING, STRING], returns: [STRING] },
|
|
557
|
+
],
|
|
558
|
+
[
|
|
559
|
+
"ReplaceAllLiteralString",
|
|
560
|
+
{ kind: "func", params: [STRING, STRING], returns: [STRING] },
|
|
561
|
+
],
|
|
562
|
+
["Split", { kind: "func", params: [STRING, INT], returns: [STR_SLICE] }],
|
|
563
|
+
["String", { kind: "func", params: [], returns: [STRING] }],
|
|
564
|
+
]);
|
|
565
|
+
REGEXP_T.underlying.methods = regexpMethods;
|
|
566
|
+
this.types.set("regexp.Regexp", REGEXP_T);
|
|
567
|
+
this.globals.define("regexp", {
|
|
568
|
+
kind: "namespace",
|
|
569
|
+
name: "regexp",
|
|
570
|
+
members: {
|
|
571
|
+
MustCompile: { kind: "func", params: [STRING], returns: [REGEXP_PTR] },
|
|
572
|
+
Compile: {
|
|
573
|
+
kind: "func",
|
|
574
|
+
params: [STRING],
|
|
575
|
+
returns: [REGEXP_PTR, ERROR],
|
|
576
|
+
},
|
|
577
|
+
MatchString: {
|
|
578
|
+
kind: "func",
|
|
579
|
+
params: [STRING, STRING],
|
|
580
|
+
returns: [BOOL, ERROR],
|
|
581
|
+
},
|
|
582
|
+
QuoteMeta: { kind: "func", params: [STRING], returns: [STRING] },
|
|
583
|
+
},
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
// strings.Builder
|
|
587
|
+
const BYTE_SLICE_T = { kind: "slice", elem: INT };
|
|
588
|
+
const strBuilderMethods = new Map([
|
|
589
|
+
[
|
|
590
|
+
"WriteString",
|
|
591
|
+
{ kind: "func", params: [STRING], returns: [INT, ERROR] },
|
|
592
|
+
],
|
|
593
|
+
["WriteByte", { kind: "func", params: [INT], returns: [ERROR] }],
|
|
594
|
+
["WriteRune", { kind: "func", params: [INT], returns: [INT, ERROR] }],
|
|
595
|
+
[
|
|
596
|
+
"Write",
|
|
597
|
+
{ kind: "func", params: [BYTE_SLICE_T], returns: [INT, ERROR] },
|
|
598
|
+
],
|
|
599
|
+
["String", { kind: "func", params: [], returns: [STRING] }],
|
|
600
|
+
["Len", { kind: "func", params: [], returns: [INT] }],
|
|
601
|
+
["Reset", { kind: "func", params: [], returns: [VOID] }],
|
|
602
|
+
["Grow", { kind: "func", params: [INT], returns: [VOID] }],
|
|
603
|
+
]);
|
|
604
|
+
this.types.set("strings.Builder", {
|
|
605
|
+
kind: "named",
|
|
606
|
+
name: "strings.Builder",
|
|
607
|
+
underlying: {
|
|
608
|
+
kind: "struct",
|
|
609
|
+
fields: new Map(),
|
|
610
|
+
methods: strBuilderMethods,
|
|
611
|
+
},
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
// bytes.Buffer
|
|
615
|
+
const bytesBufferMethods = new Map([
|
|
616
|
+
[
|
|
617
|
+
"WriteString",
|
|
618
|
+
{ kind: "func", params: [STRING], returns: [INT, ERROR] },
|
|
619
|
+
],
|
|
620
|
+
["WriteByte", { kind: "func", params: [INT], returns: [ERROR] }],
|
|
621
|
+
[
|
|
622
|
+
"Write",
|
|
623
|
+
{ kind: "func", params: [BYTE_SLICE_T], returns: [INT, ERROR] },
|
|
624
|
+
],
|
|
625
|
+
["String", { kind: "func", params: [], returns: [STRING] }],
|
|
626
|
+
["Bytes", { kind: "func", params: [], returns: [BYTE_SLICE_T] }],
|
|
627
|
+
["Len", { kind: "func", params: [], returns: [INT] }],
|
|
628
|
+
["Reset", { kind: "func", params: [], returns: [VOID] }],
|
|
629
|
+
["Grow", { kind: "func", params: [INT], returns: [VOID] }],
|
|
630
|
+
]);
|
|
631
|
+
this.types.set("bytes.Buffer", {
|
|
632
|
+
kind: "named",
|
|
633
|
+
name: "bytes.Buffer",
|
|
634
|
+
underlying: {
|
|
635
|
+
kind: "struct",
|
|
636
|
+
fields: new Map(),
|
|
637
|
+
methods: bytesBufferMethods,
|
|
638
|
+
},
|
|
639
|
+
});
|
|
640
|
+
|
|
385
641
|
this.globals.define("append", { kind: "builtin", name: "append" });
|
|
386
642
|
this.globals.define("len", { kind: "builtin", name: "len" });
|
|
387
643
|
this.globals.define("cap", { kind: "builtin", name: "cap" });
|
|
@@ -397,6 +653,9 @@ export class TypeChecker {
|
|
|
397
653
|
this.globals.define("min", { kind: "builtin", name: "min" });
|
|
398
654
|
this.globals.define("max", { kind: "builtin", name: "max" });
|
|
399
655
|
this.globals.define("clear", { kind: "builtin", name: "clear" });
|
|
656
|
+
this.globals.define("complex", { kind: "builtin", name: "complex" });
|
|
657
|
+
this.globals.define("real", { kind: "builtin", name: "real" });
|
|
658
|
+
this.globals.define("imag", { kind: "builtin", name: "imag" });
|
|
400
659
|
}
|
|
401
660
|
|
|
402
661
|
addDefinitions(types, values) {
|
|
@@ -584,6 +843,36 @@ export class TypeChecker {
|
|
|
584
843
|
// ── Type collection ──────────────────────────────────────────
|
|
585
844
|
|
|
586
845
|
collectType(decl) {
|
|
846
|
+
if (decl.typeParams) {
|
|
847
|
+
// Generic type — resolve underlying with type params as ANY for struct fields
|
|
848
|
+
const typeScope = new Scope(this.globals);
|
|
849
|
+
const typeParamTypes = decl.typeParams.map((tp) => {
|
|
850
|
+
const t = {
|
|
851
|
+
kind: "typeParam",
|
|
852
|
+
name: tp.name,
|
|
853
|
+
constraint: tp.constraint,
|
|
854
|
+
};
|
|
855
|
+
typeScope.define(tp.name, t);
|
|
856
|
+
return t;
|
|
857
|
+
});
|
|
858
|
+
const underlying = this.resolveTypeNode(decl.type, typeScope);
|
|
859
|
+
const named = {
|
|
860
|
+
kind: "named",
|
|
861
|
+
name: decl.name,
|
|
862
|
+
underlying,
|
|
863
|
+
_generic: {
|
|
864
|
+
typeParams: typeParamTypes,
|
|
865
|
+
declNode: decl,
|
|
866
|
+
},
|
|
867
|
+
};
|
|
868
|
+
if (underlying.kind === "struct") {
|
|
869
|
+
underlying.name = decl.name;
|
|
870
|
+
underlying.methods = new Map();
|
|
871
|
+
}
|
|
872
|
+
this.types.set(decl.name, named);
|
|
873
|
+
this.globals.define(decl.name, named);
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
587
876
|
const underlying = this.resolveTypeNode(decl.type, this.globals);
|
|
588
877
|
if (decl.isAlias) {
|
|
589
878
|
// type A = B — transparent alias, A and B are identical types
|
|
@@ -604,11 +893,40 @@ export class TypeChecker {
|
|
|
604
893
|
}
|
|
605
894
|
|
|
606
895
|
collectFunc(decl) {
|
|
896
|
+
// For generic functions, create type param scope for resolving param/return types
|
|
897
|
+
let resolveScope = this.globals;
|
|
898
|
+
let typeParamTypes = null;
|
|
899
|
+
if (decl.typeParams) {
|
|
900
|
+
resolveScope = new Scope(this.globals);
|
|
901
|
+
typeParamTypes = decl.typeParams.map((tp) => {
|
|
902
|
+
const t = {
|
|
903
|
+
kind: "typeParam",
|
|
904
|
+
name: tp.name,
|
|
905
|
+
constraint: tp.constraint,
|
|
906
|
+
};
|
|
907
|
+
resolveScope.define(tp.name, t);
|
|
908
|
+
return t;
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
// For methods on generic types, inject the receiver's type params
|
|
912
|
+
if (decl.kind === "MethodDecl") {
|
|
913
|
+
const recvTypeName =
|
|
914
|
+
decl.recvType.kind === "GenericTypeName"
|
|
915
|
+
? decl.recvType.name
|
|
916
|
+
: decl.recvType.name;
|
|
917
|
+
const recvNamedType = this.types.get(recvTypeName);
|
|
918
|
+
if (recvNamedType?._generic) {
|
|
919
|
+
resolveScope = new Scope(this.globals);
|
|
920
|
+
for (const tp of recvNamedType._generic.typeParams) {
|
|
921
|
+
resolveScope.define(tp.name, tp);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
607
925
|
const paramTypes = decl.params.map((p) =>
|
|
608
|
-
this.resolveTypeNode(p.type,
|
|
926
|
+
this.resolveTypeNode(p.type, resolveScope),
|
|
609
927
|
);
|
|
610
928
|
const returnType = decl.returnType
|
|
611
|
-
? this.resolveTypeNode(decl.returnType,
|
|
929
|
+
? this.resolveTypeNode(decl.returnType, resolveScope)
|
|
612
930
|
: VOID;
|
|
613
931
|
const isVariadic =
|
|
614
932
|
decl.params.length > 0 && decl.params[decl.params.length - 1].variadic;
|
|
@@ -623,13 +941,38 @@ export class TypeChecker {
|
|
|
623
941
|
if (this.globals.symbols.has(decl.name) && decl.name !== "init") {
|
|
624
942
|
this.err(`${decl.name} redeclared in this block`, decl);
|
|
625
943
|
}
|
|
626
|
-
|
|
944
|
+
if (typeParamTypes) {
|
|
945
|
+
this.globals.define(decl.name, {
|
|
946
|
+
kind: "generic",
|
|
947
|
+
name: decl.name,
|
|
948
|
+
typeParams: typeParamTypes,
|
|
949
|
+
underlying: funcType,
|
|
950
|
+
});
|
|
951
|
+
} else {
|
|
952
|
+
this.globals.define(decl.name, funcType);
|
|
953
|
+
}
|
|
627
954
|
} else {
|
|
628
955
|
// Method: attach to struct type
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
956
|
+
const recvTypeName =
|
|
957
|
+
decl.recvType.kind === "GenericTypeName"
|
|
958
|
+
? decl.recvType.name
|
|
959
|
+
: decl.recvType.name;
|
|
960
|
+
const recvNamedType = this.types.get(recvTypeName);
|
|
961
|
+
if (recvNamedType?._generic) {
|
|
962
|
+
// Store method on the generic metadata and on the underlying struct
|
|
963
|
+
if (!recvNamedType._generic.methods)
|
|
964
|
+
recvNamedType._generic.methods = new Map();
|
|
965
|
+
recvNamedType._generic.methods.set(decl.name, funcType);
|
|
966
|
+
const base = recvNamedType.underlying;
|
|
967
|
+
if (base?.kind === "struct") {
|
|
968
|
+
base.methods.set(decl.name, funcType);
|
|
969
|
+
}
|
|
970
|
+
} else {
|
|
971
|
+
const recvType = this.resolveTypeNodeName(decl.recvType, this.globals);
|
|
972
|
+
const base = recvType?.underlying ?? recvType;
|
|
973
|
+
if (base?.kind === "struct") {
|
|
974
|
+
base.methods.set(decl.name, funcType);
|
|
975
|
+
}
|
|
633
976
|
}
|
|
634
977
|
}
|
|
635
978
|
}
|
|
@@ -657,11 +1000,20 @@ export class TypeChecker {
|
|
|
657
1000
|
|
|
658
1001
|
checkFuncDecl(decl, outer) {
|
|
659
1002
|
const inner = new Scope(outer);
|
|
1003
|
+
// Inject type params into scope for generic functions
|
|
1004
|
+
if (decl.typeParams) {
|
|
1005
|
+
for (const tp of decl.typeParams) {
|
|
1006
|
+
const constraint = tp.constraint
|
|
1007
|
+
? this.resolveTypeNode(tp.constraint, outer)
|
|
1008
|
+
: ANY;
|
|
1009
|
+
inner.define(tp.name, { kind: "typeParam", name: tp.name, constraint });
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
660
1012
|
for (const p of decl.params) {
|
|
661
|
-
inner.define(p.name, this.resolveTypeNode(p.type,
|
|
1013
|
+
inner.define(p.name, this.resolveTypeNode(p.type, inner));
|
|
662
1014
|
}
|
|
663
1015
|
let returnType = decl.returnType
|
|
664
|
-
? this.resolveTypeNode(decl.returnType,
|
|
1016
|
+
? this.resolveTypeNode(decl.returnType, inner)
|
|
665
1017
|
: VOID;
|
|
666
1018
|
const hasNamedReturns = this._injectNamedReturns(
|
|
667
1019
|
decl.returnType,
|
|
@@ -688,18 +1040,29 @@ export class TypeChecker {
|
|
|
688
1040
|
|
|
689
1041
|
checkMethodDecl(decl, outer) {
|
|
690
1042
|
const inner = new Scope(outer);
|
|
1043
|
+
// For generic receiver types (e.g. Stack[T]), inject type params
|
|
1044
|
+
const recvTypeName =
|
|
1045
|
+
decl.recvType.kind === "GenericTypeName"
|
|
1046
|
+
? decl.recvType.name
|
|
1047
|
+
: decl.recvType.name;
|
|
1048
|
+
const recvNamedType = this.types.get(recvTypeName);
|
|
1049
|
+
if (recvNamedType?._generic) {
|
|
1050
|
+
for (const tp of recvNamedType._generic.typeParams) {
|
|
1051
|
+
inner.define(tp.name, tp);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
691
1054
|
const recvType = this.resolveTypeNodeName(decl.recvType, outer);
|
|
692
1055
|
inner.define(decl.recvName, recvType);
|
|
693
1056
|
for (const p of decl.params) {
|
|
694
|
-
inner.define(p.name, this.resolveTypeNode(p.type,
|
|
1057
|
+
inner.define(p.name, this.resolveTypeNode(p.type, inner));
|
|
695
1058
|
}
|
|
696
1059
|
let returnType = decl.returnType
|
|
697
|
-
? this.resolveTypeNode(decl.returnType,
|
|
1060
|
+
? this.resolveTypeNode(decl.returnType, inner)
|
|
698
1061
|
: VOID;
|
|
699
1062
|
const hasNamedReturns = this._injectNamedReturns(
|
|
700
1063
|
decl.returnType,
|
|
701
1064
|
inner,
|
|
702
|
-
|
|
1065
|
+
inner,
|
|
703
1066
|
);
|
|
704
1067
|
if (hasNamedReturns && returnType)
|
|
705
1068
|
returnType = { ...returnType, _hasNamedReturns: true };
|
|
@@ -842,10 +1205,37 @@ export class TypeChecker {
|
|
|
842
1205
|
switch (node.kind) {
|
|
843
1206
|
case "TypeName": {
|
|
844
1207
|
if (BASIC_TYPES[node.name]) return BASIC_TYPES[node.name];
|
|
1208
|
+
if (node.name === "comparable")
|
|
1209
|
+
return { kind: "basic", name: "comparable" };
|
|
1210
|
+
// Check scope for type params before checking this.types
|
|
1211
|
+
if (scope) {
|
|
1212
|
+
const fromScope = scope.lookup(node.name);
|
|
1213
|
+
if (fromScope?.kind === "typeParam") return fromScope;
|
|
1214
|
+
}
|
|
845
1215
|
const named = this.types.get(node.name);
|
|
846
1216
|
if (named) return named;
|
|
847
1217
|
return this.err(`Unknown type '${node.name}'`, node);
|
|
848
1218
|
}
|
|
1219
|
+
case "GenericTypeName": {
|
|
1220
|
+
const base = this.types.get(node.name);
|
|
1221
|
+
if (!base) return this.err(`Unknown type '${node.name}'`, node);
|
|
1222
|
+
if (base.kind === "named" && base._generic) {
|
|
1223
|
+
return this.instantiateGenericType(
|
|
1224
|
+
base._generic,
|
|
1225
|
+
node.typeArgs,
|
|
1226
|
+
scope,
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
return this.err(`Type '${node.name}' is not generic`, node);
|
|
1230
|
+
}
|
|
1231
|
+
case "TypeParam": {
|
|
1232
|
+
const constraint = node.constraint
|
|
1233
|
+
? this.resolveTypeNode(node.constraint, scope)
|
|
1234
|
+
: ANY;
|
|
1235
|
+
return { kind: "typeParam", name: node.name, constraint };
|
|
1236
|
+
}
|
|
1237
|
+
case "UnionConstraint":
|
|
1238
|
+
return node;
|
|
849
1239
|
case "SliceType":
|
|
850
1240
|
return {
|
|
851
1241
|
kind: "slice",
|
|
@@ -964,11 +1354,207 @@ export class TypeChecker {
|
|
|
964
1354
|
const named = this.types.get(node.name);
|
|
965
1355
|
if (named) return named;
|
|
966
1356
|
}
|
|
1357
|
+
if (node.kind === "GenericTypeName") {
|
|
1358
|
+
const named = this.types.get(node.name);
|
|
1359
|
+
if (named) return named;
|
|
1360
|
+
}
|
|
967
1361
|
return this.resolveTypeNode(node, scope);
|
|
968
1362
|
}
|
|
969
1363
|
|
|
970
1364
|
// ── Helpers ───────────────────────────────────────────────────
|
|
971
1365
|
|
|
1366
|
+
// ── Generics — instantiation, inference, substitution ────────
|
|
1367
|
+
|
|
1368
|
+
substituteType(type, map) {
|
|
1369
|
+
if (!type) return type;
|
|
1370
|
+
if (type.kind === "typeParam") {
|
|
1371
|
+
const bound = map.get(type.name);
|
|
1372
|
+
return bound ?? type;
|
|
1373
|
+
}
|
|
1374
|
+
switch (type.kind) {
|
|
1375
|
+
case "basic":
|
|
1376
|
+
case "untyped":
|
|
1377
|
+
return type;
|
|
1378
|
+
case "slice":
|
|
1379
|
+
return { kind: "slice", elem: this.substituteType(type.elem, map) };
|
|
1380
|
+
case "array":
|
|
1381
|
+
return {
|
|
1382
|
+
kind: "array",
|
|
1383
|
+
size: type.size,
|
|
1384
|
+
elem: this.substituteType(type.elem, map),
|
|
1385
|
+
};
|
|
1386
|
+
case "map":
|
|
1387
|
+
return {
|
|
1388
|
+
kind: "map",
|
|
1389
|
+
key: this.substituteType(type.key, map),
|
|
1390
|
+
value: this.substituteType(type.value, map),
|
|
1391
|
+
};
|
|
1392
|
+
case "func": {
|
|
1393
|
+
return {
|
|
1394
|
+
kind: "func",
|
|
1395
|
+
params: type.params.map((p) => this.substituteType(p, map)),
|
|
1396
|
+
returns: type.returns.map((r) => this.substituteType(r, map)),
|
|
1397
|
+
variadic: type.variadic,
|
|
1398
|
+
async: type.async,
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
case "struct": {
|
|
1402
|
+
const fields = new Map();
|
|
1403
|
+
for (const [k, v] of type.fields)
|
|
1404
|
+
fields.set(k, this.substituteType(v, map));
|
|
1405
|
+
const methods = new Map();
|
|
1406
|
+
for (const [k, v] of type.methods)
|
|
1407
|
+
methods.set(k, this.substituteType(v, map));
|
|
1408
|
+
return {
|
|
1409
|
+
kind: "struct",
|
|
1410
|
+
name: type.name,
|
|
1411
|
+
fields,
|
|
1412
|
+
methods,
|
|
1413
|
+
_embeds: type._embeds,
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
case "interface": {
|
|
1417
|
+
const methods = new Map();
|
|
1418
|
+
for (const [k, v] of type.methods)
|
|
1419
|
+
methods.set(k, this.substituteType(v, map));
|
|
1420
|
+
return { kind: "interface", name: type.name, methods };
|
|
1421
|
+
}
|
|
1422
|
+
case "named":
|
|
1423
|
+
return {
|
|
1424
|
+
kind: "named",
|
|
1425
|
+
name: type.name,
|
|
1426
|
+
underlying: this.substituteType(type.underlying, map),
|
|
1427
|
+
};
|
|
1428
|
+
case "pointer":
|
|
1429
|
+
return { kind: "pointer", base: this.substituteType(type.base, map) };
|
|
1430
|
+
case "tuple":
|
|
1431
|
+
return {
|
|
1432
|
+
kind: "tuple",
|
|
1433
|
+
types: type.types.map((t) => this.substituteType(t, map)),
|
|
1434
|
+
};
|
|
1435
|
+
default:
|
|
1436
|
+
return type;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
instantiateGenericFunc(genericType, typeArgs) {
|
|
1441
|
+
const map = new Map();
|
|
1442
|
+
for (let i = 0; i < genericType.typeParams.length; i++) {
|
|
1443
|
+
map.set(genericType.typeParams[i].name, typeArgs[i] ?? ANY);
|
|
1444
|
+
}
|
|
1445
|
+
return this.substituteType(genericType.underlying, map);
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
instantiateGenericType(generic, typeArgNodes, scope) {
|
|
1449
|
+
const map = new Map();
|
|
1450
|
+
const typeArgs = typeArgNodes.map((n) => this.resolveTypeNode(n, scope));
|
|
1451
|
+
for (let i = 0; i < generic.typeParams.length; i++) {
|
|
1452
|
+
map.set(generic.typeParams[i].name, typeArgs[i] ?? ANY);
|
|
1453
|
+
}
|
|
1454
|
+
// Resolve the underlying type from the decl node using a scope with type params bound
|
|
1455
|
+
const typeScope = new Scope(this.globals);
|
|
1456
|
+
for (const [name, type] of map) typeScope.define(name, type);
|
|
1457
|
+
const underlying = this.resolveTypeNode(generic.declNode.type, typeScope);
|
|
1458
|
+
const instantiated = this.substituteType(underlying, map);
|
|
1459
|
+
if (instantiated.kind === "struct") {
|
|
1460
|
+
instantiated.name = generic.declNode.name;
|
|
1461
|
+
if (!instantiated.methods) instantiated.methods = new Map();
|
|
1462
|
+
// Copy and substitute methods from the generic
|
|
1463
|
+
if (generic.methods) {
|
|
1464
|
+
for (const [mName, mType] of generic.methods) {
|
|
1465
|
+
instantiated.methods.set(mName, this.substituteType(mType, map));
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
return {
|
|
1470
|
+
kind: "named",
|
|
1471
|
+
name: generic.declNode.name,
|
|
1472
|
+
underlying: instantiated,
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
inferTypeArgs(genericType, argTypes) {
|
|
1477
|
+
const map = new Map();
|
|
1478
|
+
const params = genericType.underlying.params;
|
|
1479
|
+
for (let i = 0; i < params.length && i < argTypes.length; i++) {
|
|
1480
|
+
this._inferFromPair(params[i], argTypes[i], map);
|
|
1481
|
+
}
|
|
1482
|
+
// Check all type params are resolved
|
|
1483
|
+
for (const tp of genericType.typeParams) {
|
|
1484
|
+
if (!map.has(tp.name)) return null;
|
|
1485
|
+
}
|
|
1486
|
+
// Apply defaultType to resolve untyped constants
|
|
1487
|
+
const result = [];
|
|
1488
|
+
for (const tp of genericType.typeParams) {
|
|
1489
|
+
result.push(defaultType(map.get(tp.name)));
|
|
1490
|
+
}
|
|
1491
|
+
return result;
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
_inferFromPair(paramType, argType, map) {
|
|
1495
|
+
if (!paramType || !argType) return;
|
|
1496
|
+
if (paramType.kind === "typeParam") {
|
|
1497
|
+
if (!map.has(paramType.name)) {
|
|
1498
|
+
map.set(paramType.name, argType);
|
|
1499
|
+
}
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
if (paramType.kind === "slice" && argType.kind === "slice") {
|
|
1503
|
+
this._inferFromPair(paramType.elem, argType.elem, map);
|
|
1504
|
+
}
|
|
1505
|
+
if (paramType.kind === "map" && argType.kind === "map") {
|
|
1506
|
+
this._inferFromPair(paramType.key, argType.key, map);
|
|
1507
|
+
this._inferFromPair(paramType.value, argType.value, map);
|
|
1508
|
+
}
|
|
1509
|
+
if (paramType.kind === "func" && argType.kind === "func") {
|
|
1510
|
+
for (
|
|
1511
|
+
let i = 0;
|
|
1512
|
+
i < paramType.params.length && i < argType.params.length;
|
|
1513
|
+
i++
|
|
1514
|
+
) {
|
|
1515
|
+
this._inferFromPair(paramType.params[i], argType.params[i], map);
|
|
1516
|
+
}
|
|
1517
|
+
for (
|
|
1518
|
+
let i = 0;
|
|
1519
|
+
i < paramType.returns.length && i < argType.returns.length;
|
|
1520
|
+
i++
|
|
1521
|
+
) {
|
|
1522
|
+
this._inferFromPair(paramType.returns[i], argType.returns[i], map);
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
checkConstraint(typeArg, constraint, node) {
|
|
1528
|
+
if (!constraint) return;
|
|
1529
|
+
if (isAny(constraint)) return;
|
|
1530
|
+
if (constraint.kind === "basic" && constraint.name === "comparable") return;
|
|
1531
|
+
if (constraint.kind === "UnionConstraint") {
|
|
1532
|
+
// Check if typeArg matches any term; ~terms always pass (v1)
|
|
1533
|
+
for (const term of constraint.terms) {
|
|
1534
|
+
if (term.approx) return; // ~ means approximate, always passes
|
|
1535
|
+
const termType = this.resolveTypeNode(term.type, this.globals);
|
|
1536
|
+
if (typeStr(typeArg) === typeStr(termType)) return;
|
|
1537
|
+
}
|
|
1538
|
+
this.err(`type ${typeStr(typeArg)} does not satisfy constraint`, node);
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
// Interface constraint — unwrap named types
|
|
1542
|
+
const base =
|
|
1543
|
+
constraint.kind === "named" ? constraint.underlying : constraint;
|
|
1544
|
+
if (base?.kind === "interface") {
|
|
1545
|
+
if (base.methods.size === 0 && !base.unionConstraint) return;
|
|
1546
|
+
if (base.unionConstraint) {
|
|
1547
|
+
return this.checkConstraint(typeArg, base.unionConstraint, node);
|
|
1548
|
+
}
|
|
1549
|
+
if (!this.implements(typeArg, base, node)) {
|
|
1550
|
+
this.err(
|
|
1551
|
+
`type ${typeStr(typeArg)} does not satisfy constraint ${typeStr(constraint)}`,
|
|
1552
|
+
node,
|
|
1553
|
+
);
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
|
|
972
1558
|
resolveType(t) {
|
|
973
1559
|
if (!t) return ANY;
|
|
974
1560
|
if (t.kind === "basic" && t.alias) {
|
|
@@ -981,6 +1567,15 @@ export class TypeChecker {
|
|
|
981
1567
|
fieldType(baseType, field, node) {
|
|
982
1568
|
baseType = this.resolveType(baseType);
|
|
983
1569
|
if (!baseType || isAny(baseType)) return ANY;
|
|
1570
|
+
// Transparent pointer dereference for named-type method calls: (*T).Method works like T.Method
|
|
1571
|
+
// (only unwrap when the pointer's base is a named type that has methods)
|
|
1572
|
+
if (
|
|
1573
|
+
baseType.kind === "pointer" &&
|
|
1574
|
+
field !== "value" &&
|
|
1575
|
+
baseType.base?.kind === "named"
|
|
1576
|
+
) {
|
|
1577
|
+
baseType = this.resolveType(baseType.base);
|
|
1578
|
+
}
|
|
984
1579
|
let base = baseType.kind === "named" ? baseType.underlying : baseType;
|
|
985
1580
|
base = this.resolveType(base);
|
|
986
1581
|
if (base?.kind === "struct") {
|
|
@@ -988,6 +1583,10 @@ export class TypeChecker {
|
|
|
988
1583
|
if (base.methods?.has(field)) return base.methods.get(field);
|
|
989
1584
|
return this.err(`No field '${field}' on ${typeStr(baseType)}`, node);
|
|
990
1585
|
}
|
|
1586
|
+
if (base?.kind === "interface") {
|
|
1587
|
+
if (base.methods?.has(field)) return base.methods.get(field);
|
|
1588
|
+
return this.err(`No method '${field}' on ${typeStr(baseType)}`, node);
|
|
1589
|
+
}
|
|
991
1590
|
if (base?.kind === "namespace") {
|
|
992
1591
|
// Enforce exported identifier rule for GoFront packages only
|
|
993
1592
|
if (
|
|
@@ -1007,9 +1606,6 @@ export class TypeChecker {
|
|
|
1007
1606
|
// For explicitly typed non-any values, report the bad access rather than
|
|
1008
1607
|
// silently returning any — this catches typos on structs and primitive misuse.
|
|
1009
1608
|
if (base && !isAny(base)) {
|
|
1010
|
-
// error.Error() is valid — it returns the error string
|
|
1011
|
-
if (isError(base) && field === "Error")
|
|
1012
|
-
return { kind: "func", params: [], returns: [STRING], async: false };
|
|
1013
1609
|
// pointer.value is the GoFront new(T) pattern
|
|
1014
1610
|
if (base.kind === "pointer" && field === "value") return base.base ?? ANY;
|
|
1015
1611
|
const badKinds = ["basic", "slice", "array", "map", "func", "tuple"];
|
|
@@ -1024,7 +1620,23 @@ export class TypeChecker {
|
|
|
1024
1620
|
}
|
|
1025
1621
|
|
|
1026
1622
|
binaryResultType(op, lt, rt, node) {
|
|
1623
|
+
// Type parameters are permissive — allow operations and return the typeParam
|
|
1624
|
+
if (lt?.kind === "typeParam" || rt?.kind === "typeParam") {
|
|
1625
|
+
if (CMP_OPS.has(op)) return BOOL;
|
|
1626
|
+
if (LOG_OPS.has(op)) return BOOL;
|
|
1627
|
+
return lt?.kind === "typeParam" ? lt : rt;
|
|
1628
|
+
}
|
|
1027
1629
|
if (CMP_OPS.has(op)) {
|
|
1630
|
+
// Complex only supports == and !=
|
|
1631
|
+
if (isComplex(lt) || isComplex(rt)) {
|
|
1632
|
+
if (op !== "==" && op !== "!=") {
|
|
1633
|
+
this.err(
|
|
1634
|
+
`invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`,
|
|
1635
|
+
node,
|
|
1636
|
+
);
|
|
1637
|
+
return ANY;
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1028
1640
|
// Reject slice/map/func comparisons unless one side is nil (Go spec)
|
|
1029
1641
|
if (op === "==" || op === "!=") {
|
|
1030
1642
|
const lNil = isNil(lt);
|
|
@@ -1046,6 +1658,26 @@ export class TypeChecker {
|
|
|
1046
1658
|
}
|
|
1047
1659
|
if (LOG_OPS.has(op)) return BOOL;
|
|
1048
1660
|
if (isAny(lt) || isAny(rt)) return ANY;
|
|
1661
|
+
// Complex arithmetic: +, -, *, /
|
|
1662
|
+
if (isComplex(lt) || isComplex(rt)) {
|
|
1663
|
+
if (!isComplexOrNumeric(lt) || !isComplexOrNumeric(rt)) {
|
|
1664
|
+
this.err(
|
|
1665
|
+
`invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`,
|
|
1666
|
+
node,
|
|
1667
|
+
);
|
|
1668
|
+
return ANY;
|
|
1669
|
+
}
|
|
1670
|
+
if (op !== "+" && op !== "-" && op !== "*" && op !== "/") {
|
|
1671
|
+
this.err(
|
|
1672
|
+
`invalid operation: ${typeStr(lt)} ${op} ${typeStr(rt)}`,
|
|
1673
|
+
node,
|
|
1674
|
+
);
|
|
1675
|
+
return ANY;
|
|
1676
|
+
}
|
|
1677
|
+
if (lt.kind === "untyped" && rt.kind === "untyped")
|
|
1678
|
+
return UNTYPED_COMPLEX;
|
|
1679
|
+
return COMPLEX128;
|
|
1680
|
+
}
|
|
1049
1681
|
if (isNumeric(lt) && isNumeric(rt)) {
|
|
1050
1682
|
const lFloat = (lt.kind === "untyped" ? lt.base : lt.name) === "float64";
|
|
1051
1683
|
const rFloat = (rt.kind === "untyped" ? rt.base : rt.name) === "float64";
|
|
@@ -1076,8 +1708,19 @@ export class TypeChecker {
|
|
|
1076
1708
|
if (!target || !source) return;
|
|
1077
1709
|
target = this.resolveType(target);
|
|
1078
1710
|
source = this.resolveType(source);
|
|
1711
|
+
// Type params are permissive — allow any assignment
|
|
1712
|
+
if (target?.kind === "typeParam" || source?.kind === "typeParam") return;
|
|
1079
1713
|
if (isAny(target) || isAny(source)) return;
|
|
1080
1714
|
if (isNil(source)) return; // nil is assignable to anything nullable
|
|
1715
|
+
// Pointer type: nil → *T is allowed (handled above); *T → *T check base types
|
|
1716
|
+
if (isPointer(target) && isPointer(source)) {
|
|
1717
|
+
this.assertAssignable(
|
|
1718
|
+
target.base ?? target.underlying?.base,
|
|
1719
|
+
source.base ?? source.underlying?.base,
|
|
1720
|
+
node,
|
|
1721
|
+
);
|
|
1722
|
+
return;
|
|
1723
|
+
}
|
|
1081
1724
|
|
|
1082
1725
|
// Untyped constants coerce to any compatible typed target
|
|
1083
1726
|
if (isUntyped(source)) {
|
|
@@ -1099,6 +1742,18 @@ export class TypeChecker {
|
|
|
1099
1742
|
if (source.base === "string" && target.name === "string") return;
|
|
1100
1743
|
// untyped bool → bool
|
|
1101
1744
|
if (source.base === "bool" && target.name === "bool") return;
|
|
1745
|
+
// untyped complex → complex128 or complex64
|
|
1746
|
+
if (
|
|
1747
|
+
source.base === "complex128" &&
|
|
1748
|
+
(target.name === "complex128" || target.name === "complex64")
|
|
1749
|
+
)
|
|
1750
|
+
return;
|
|
1751
|
+
// untyped int/float → complex (Go allows: var z complex128 = 5)
|
|
1752
|
+
if (
|
|
1753
|
+
isComplex(target) &&
|
|
1754
|
+
(source.base === "int" || source.base === "float64")
|
|
1755
|
+
)
|
|
1756
|
+
return;
|
|
1102
1757
|
}
|
|
1103
1758
|
}
|
|
1104
1759
|
|
|
@@ -1107,6 +1762,33 @@ export class TypeChecker {
|
|
|
1107
1762
|
if (target.name === "float64" && source.name === "int") return;
|
|
1108
1763
|
if (target.name === "int" && source.name === "float64") return; // truncating
|
|
1109
1764
|
}
|
|
1765
|
+
|
|
1766
|
+
// Array-specific checks: size matching and array/slice incompatibility
|
|
1767
|
+
if (target.kind === "array" && source.kind === "array") {
|
|
1768
|
+
if (
|
|
1769
|
+
target.size != null &&
|
|
1770
|
+
source.size != null &&
|
|
1771
|
+
target.size !== source.size
|
|
1772
|
+
) {
|
|
1773
|
+
this.err(
|
|
1774
|
+
`Cannot assign ${typeStr(source)} to ${typeStr(target)} (different array lengths)`,
|
|
1775
|
+
node,
|
|
1776
|
+
);
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
// Check element type compatibility
|
|
1780
|
+
this.assertAssignable(target.elem, source.elem, node);
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
if (target.kind === "array" && source.kind === "slice") {
|
|
1784
|
+
this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
if (target.kind === "slice" && source.kind === "array") {
|
|
1788
|
+
this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
|
|
1789
|
+
return;
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1110
1792
|
if (typeStr(target) !== typeStr(source)) {
|
|
1111
1793
|
// Interface satisfaction check
|
|
1112
1794
|
let tBase = target.kind === "named" ? target.underlying : target;
|