kopscript 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LLM.md +29 -12
- package/README.md +20 -7
- package/dist/checker.js +224 -79
- package/dist/codegen.js +1 -1
- package/dist/parser.js +17 -2
- package/dist/printer.js +5 -2
- package/package.json +1 -1
package/LLM.md
CHANGED
|
@@ -327,16 +327,30 @@ inspect a type parameter at runtime.
|
|
|
327
327
|
"Cannot access member 'Foo' on type 'T'"). This is correct, not a bug — same as an
|
|
328
328
|
unconstrained type parameter in C#/Java/TypeScript.
|
|
329
329
|
|
|
330
|
+
**Generic inheritance**: a class/interface base list CAN name a generic base, with its own
|
|
331
|
+
type arguments, in either direction:
|
|
332
|
+
```ks
|
|
333
|
+
class IntBox : Box<number> { } // concrete — threads a fixed type through
|
|
334
|
+
class Container<T> : Box<T> { // generic — threads its OWN T through
|
|
335
|
+
public T GetAgain() { return this.Get(); }
|
|
336
|
+
}
|
|
337
|
+
interface INumberContainer : IContainer<number> { } // interfaces work the same way
|
|
338
|
+
```
|
|
339
|
+
Member resolution (fields, methods, an inherited constructor via `base(...)` or an implicit
|
|
340
|
+
default) composes substitutions correctly however many generic-base links up the chain a
|
|
341
|
+
member was actually declared — `Container<number>.Get()` (declared on `Box<T>`, two links
|
|
342
|
+
away in a longer chain) resolves to `number`, not the abstract `T`. **v1 limit**: at most
|
|
343
|
+
one generic entry across a class/interface's *whole* base list (its superclass, or one
|
|
344
|
+
implemented/extended interface — never more than one) — `class Foo<T> : Box<T>,
|
|
345
|
+
IContainer<T>` is a compile error ("has more than one generic entry in its base list") even
|
|
346
|
+
though either half alone would work.
|
|
347
|
+
|
|
330
348
|
**Does not exist (v1 scope cuts, each deliberate)**:
|
|
331
349
|
- **Generic functions.** `T Identity<T>(T x)` doesn't parse — only classes and interfaces
|
|
332
350
|
take type parameters, not free functions/methods themselves (a method *inside* a
|
|
333
351
|
generic class can use that class's own type parameters freely, same as any other member).
|
|
334
352
|
- **Constraints.** No `T : IComparable` — a type parameter is always fully unconstrained,
|
|
335
353
|
which is why you can't call members on a bare one (see above).
|
|
336
|
-
- **Generic inheritance.** A class/interface's base list can only name a *non-generic*
|
|
337
|
-
type — `class Foo : Box<number>` and even `class Foo<T> : SomeGenericBase<T>` are both
|
|
338
|
-
compile errors ("cannot extend/implement generic type '...' — not supported in v1"). A
|
|
339
|
-
generic class/interface can still extend/implement ordinary non-generic bases normally.
|
|
340
354
|
- **Variance.** No `out`/`in`/covariance/contravariance — see invariance above.
|
|
341
355
|
|
|
342
356
|
## Nullable types — `T?`
|
|
@@ -414,11 +428,13 @@ KopScript-declared name exactly. Extern class members use real JS member names v
|
|
|
414
428
|
(camelCase, no rename mechanism). No inheritance modeling between two `extern class`
|
|
415
429
|
declarations — each stands alone.
|
|
416
430
|
|
|
417
|
-
`extern class` can carry
|
|
418
|
-
from "some-package";`) — identical rules to a real generic class (see "Generics"
|
|
419
|
-
one unconstrained invariant
|
|
420
|
-
from another package instantiates and type-checks exactly
|
|
421
|
-
(`Box<number>`, arity/invariance errors included)
|
|
431
|
+
`extern class` can carry type parameters (`extern class Box<T> { constructor(T v); T Value
|
|
432
|
+
{ get; } } from "some-package";`) — identical rules to a real generic class (see "Generics"
|
|
433
|
+
above: one or more unconstrained invariant parameters, erased, generic inheritance
|
|
434
|
+
included), so a generic type from another package instantiates and type-checks exactly
|
|
435
|
+
like a local one (`Box<number>`, arity/invariance errors included) — and a real KopScript
|
|
436
|
+
class can extend a generic `extern class` with a concrete or threaded-through type
|
|
437
|
+
argument, same as extending a real generic base.
|
|
422
438
|
|
|
423
439
|
**Never write `async` on an extern function/method signature** — declare its return type
|
|
424
440
|
as `task`/`task<T>` directly (`task<string> text();`, not `async task<string> text();`).
|
|
@@ -505,9 +521,10 @@ Locals/params: **camelCase**. This is convention, not enforced by the compiler.
|
|
|
505
521
|
|
|
506
522
|
## Does not exist (don't reach for these)
|
|
507
523
|
|
|
508
|
-
Generics beyond one or more unconstrained type parameters
|
|
509
|
-
|
|
510
|
-
|
|
524
|
+
Generics beyond one or more unconstrained type parameters and single-generic-base
|
|
525
|
+
inheritance (no `T : IFoo` constraints, no generic functions, no variance, no more than one
|
|
526
|
+
generic entry per base list — see Generics above for what *is* supported) ·
|
|
527
|
+
`any`/`unknown` annotations ·
|
|
511
528
|
a `let`/`var` keyword (locals are just `Type name = value;`) · decorators/annotations ·
|
|
512
529
|
reflection · type inference on declarations · ternary expression · union/tuple types ·
|
|
513
530
|
**object-literal syntax** (`{ key: value }` as a value — this doesn't exist *anywhere* in
|
package/README.md
CHANGED
|
@@ -263,10 +263,23 @@ inside v1:
|
|
|
263
263
|
- **No generic functions.** `T Identity<T>(T x)` isn't supported — only classes and
|
|
264
264
|
interfaces take type parameters. A method *inside* a generic class can still use that
|
|
265
265
|
class's own type parameters freely; it's just not introducing one of its own.
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
266
|
+
|
|
267
|
+
A class or interface *can* extend/implement a generic base, type arguments and all:
|
|
268
|
+
|
|
269
|
+
```ks
|
|
270
|
+
class IntBox : Box<number> { } // concrete: threads a fixed type through
|
|
271
|
+
class Container<T> : Box<T> { // generic: threads its OWN T through
|
|
272
|
+
public T GetAgain() { return this.Get(); }
|
|
273
|
+
}
|
|
274
|
+
Container<number> c = new Container<number>(5);
|
|
275
|
+
print(c.GetAgain()); // 5, and c.Value/c.Get() work too
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Member lookup (fields, methods, an inherited constructor) resolves correctly however many
|
|
279
|
+
generic-base links up the chain a member was actually declared, substituting all the way
|
|
280
|
+
down. **v1 limit**: at most one generic entry across a whole base list (the superclass, or
|
|
281
|
+
one implemented interface — not several at once) — `class Foo<T> : Box<T>, IContainer<T>`
|
|
282
|
+
is a compile error even though each half would work alone.
|
|
270
283
|
|
|
271
284
|
See `LLM.md`'s "Generics" section for the exhaustive rules if you're generating code
|
|
272
285
|
against this — nested generics (`Box<Box<number>>`, `Box<number>?[]`, `Pair<number,
|
|
@@ -372,9 +385,9 @@ globals within a single project.
|
|
|
372
385
|
|
|
373
386
|
An `extern class` can carry its own type parameter(s), `extern class Box<T> { ... }` or
|
|
374
387
|
`extern class Pair<K, V> { ... }` — exactly the same rules as a real generic class (see
|
|
375
|
-
"Generics" above: invariant, unconstrained, erased,
|
|
376
|
-
generic type from another package (e.g. Kopular's `FormField<T>`) can
|
|
377
|
-
instantiated generically, not just per concrete type:
|
|
388
|
+
"Generics" above: invariant, unconstrained, erased, and a real class can extend it as a
|
|
389
|
+
generic base), so a generic type from another package (e.g. Kopular's `FormField<T>`) can
|
|
390
|
+
be described and instantiated generically, not just per concrete type:
|
|
378
391
|
|
|
379
392
|
```ks
|
|
380
393
|
extern class Box<T> {
|
package/dist/checker.js
CHANGED
|
@@ -204,7 +204,9 @@ export class Checker {
|
|
|
204
204
|
name: decl.name,
|
|
205
205
|
typeParams: decl.typeParams.map((p) => p.name),
|
|
206
206
|
superclass: null,
|
|
207
|
+
superclassTypeArgs: null,
|
|
207
208
|
interfaces: [],
|
|
209
|
+
interfaceTypeArgs: new Map(),
|
|
208
210
|
fields,
|
|
209
211
|
methods,
|
|
210
212
|
staticFields,
|
|
@@ -459,6 +461,59 @@ export class Checker {
|
|
|
459
461
|
return type;
|
|
460
462
|
}
|
|
461
463
|
}
|
|
464
|
+
// Builds the bindings map for a generic reference (a `ClassType`/
|
|
465
|
+
// `InterfaceType`'s own `typeArgs`, zipped against its declared
|
|
466
|
+
// `typeParams`) — the "own bindings" every member-lookup/conformance
|
|
467
|
+
// check starts from. Null when the reference isn't actually generic
|
|
468
|
+
// (no declared params) or carries no type arguments (an arity error
|
|
469
|
+
// already diagnosed elsewhere — see validateGenericArity).
|
|
470
|
+
ownBindings(typeParams, typeArgs) {
|
|
471
|
+
if (typeParams.length === 0 || !typeArgs)
|
|
472
|
+
return null;
|
|
473
|
+
return new Map(typeParams.map((name, i) => [name, typeArgs[i]]));
|
|
474
|
+
}
|
|
475
|
+
// Maps `typeParams` to themselves, as abstract TypeParamTypes — used when
|
|
476
|
+
// resolving a base-list/interface type argument that may reference the
|
|
477
|
+
// *declaring* class/interface's own (still-abstract) type parameter,
|
|
478
|
+
// rather than a concrete instantiation (see checkInterfaceConformance and
|
|
479
|
+
// the base(...) call check in checkClassBodyInner).
|
|
480
|
+
identityBindings(typeParams) {
|
|
481
|
+
return new Map(typeParams.map((name) => [name, T.typeParamType(name)]));
|
|
482
|
+
}
|
|
483
|
+
// One step of substitution composition, walking from a class/interface
|
|
484
|
+
// ("owner") to one of its own generic base-list entries. `baseArgs` are
|
|
485
|
+
// the type arguments `owner` supplied to `baseName` in its own base list
|
|
486
|
+
// — still in `owner`'s own type-param-name space (may reference `owner`'s
|
|
487
|
+
// own type parameters, not yet concrete). `ownerBindings` is `owner`'s
|
|
488
|
+
// current bindings at the point of this lookup. Returns `baseName`'s own
|
|
489
|
+
// bindings map (its declared type-param names -> resolved types), ready
|
|
490
|
+
// for the next composition step or a final substituteTypeParams call.
|
|
491
|
+
composeBaseBindings(baseName, baseArgs, ownerBindings) {
|
|
492
|
+
const baseParams = this.genericTypeParams.get(baseName) ?? [];
|
|
493
|
+
const resolvedArgs = baseArgs.map((t) => this.substituteTypeParams(t, ownerBindings));
|
|
494
|
+
return new Map(baseParams.map((name, i) => [name, resolvedArgs[i]]));
|
|
495
|
+
}
|
|
496
|
+
// Starting from `className` with its own bindings (from ownBindings,
|
|
497
|
+
// above — empty if non-generic or no type arguments), walks up the
|
|
498
|
+
// superclass chain composing each generic link's stored
|
|
499
|
+
// superclassTypeArgs, until it reaches `targetOwner` (the ancestor that
|
|
500
|
+
// actually declared the member being resolved — see lookupField/
|
|
501
|
+
// lookupMethod/etc.'s `owner` result). Returns the bindings in
|
|
502
|
+
// `targetOwner`'s own type-param-name space. A non-generic link along the
|
|
503
|
+
// way (superclassTypeArgs null) resets to empty bindings, same as today's
|
|
504
|
+
// behavior of never substituting through a non-generic ancestor.
|
|
505
|
+
bindingsAtAncestor(className, ownBindings, targetOwner) {
|
|
506
|
+
let current = className;
|
|
507
|
+
let bindings = ownBindings;
|
|
508
|
+
while (current !== targetOwner) {
|
|
509
|
+
const info = this.classes.get(current);
|
|
510
|
+
if (!info || !info.superclass)
|
|
511
|
+
return bindings;
|
|
512
|
+
bindings = info.superclassTypeArgs ? this.composeBaseBindings(info.superclass, info.superclassTypeArgs, bindings) : new Map();
|
|
513
|
+
current = info.superclass;
|
|
514
|
+
}
|
|
515
|
+
return bindings;
|
|
516
|
+
}
|
|
462
517
|
registerEnum(decl) {
|
|
463
518
|
const members = new Map();
|
|
464
519
|
decl.members.forEach((name, index) => {
|
|
@@ -472,28 +527,31 @@ export class Checker {
|
|
|
472
527
|
this.recordHover(decl.line, decl.col, `enum ${decl.name}`);
|
|
473
528
|
}
|
|
474
529
|
registerInterface(decl) {
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
this.
|
|
492
|
-
|
|
530
|
+
const genericBaseCount = { count: 0 };
|
|
531
|
+
const { methods, bases, baseTypeArgs } = this.withTypeParamsInScope(decl.typeParams, () => {
|
|
532
|
+
const methods = decl.methods.map((m) => {
|
|
533
|
+
const params = m.params.map((p) => this.resolveType(p.type, m.line, m.col));
|
|
534
|
+
const returnType = this.resolveType(m.returnType, m.line, m.col);
|
|
535
|
+
this.recordHover(m.nameLine, m.nameCol, `${m.name}(${params.map(T.typeToString).join(", ")}): ${T.typeToString(returnType)}`);
|
|
536
|
+
return { name: m.name, params, returnType };
|
|
537
|
+
});
|
|
538
|
+
const bases = [];
|
|
539
|
+
const baseTypeArgs = new Map();
|
|
540
|
+
for (const entry of decl.baseList) {
|
|
541
|
+
if (this.namedTypes.get(entry.name) !== "interface") {
|
|
542
|
+
this.diagnostics.error("KS4012", `Interface '${decl.name}' can only extend other interfaces (unknown interface '${entry.name}')`, decl.line, decl.col);
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
bases.push(entry.name);
|
|
546
|
+
if (entry.typeArgs || this.genericTypeParams.has(entry.name)) {
|
|
547
|
+
const resolved = this.resolveBaseTypeArgs(decl.name, entry.name, entry.typeArgs, decl.line, decl.col, genericBaseCount);
|
|
548
|
+
if (resolved)
|
|
549
|
+
baseTypeArgs.set(entry.name, resolved);
|
|
550
|
+
}
|
|
493
551
|
}
|
|
494
|
-
bases
|
|
495
|
-
}
|
|
496
|
-
this.interfaces.set(decl.name, { name: decl.name, typeParams: decl.typeParams.map((p) => p.name), bases, methods });
|
|
552
|
+
return { methods, bases, baseTypeArgs };
|
|
553
|
+
});
|
|
554
|
+
this.interfaces.set(decl.name, { name: decl.name, typeParams: decl.typeParams.map((p) => p.name), bases, baseTypeArgs, methods });
|
|
497
555
|
this.recordHover(decl.line, decl.col, decl.typeParams.length > 0 ? `interface ${decl.name}<${decl.typeParams.map((p) => p.name).join(", ")}>` : `interface ${decl.name}`);
|
|
498
556
|
}
|
|
499
557
|
checkInterfaceHierarchy(decl) {
|
|
@@ -517,16 +575,31 @@ export class Checker {
|
|
|
517
575
|
}
|
|
518
576
|
}
|
|
519
577
|
// All methods required to satisfy an interface: its own plus every
|
|
520
|
-
// transitively-inherited parent interface's (cycle-safe).
|
|
521
|
-
|
|
578
|
+
// transitively-inherited parent interface's (cycle-safe). `bindings` maps
|
|
579
|
+
// `interfaceName`'s own type-param names to resolved types (from
|
|
580
|
+
// ownBindings, at the root call — see its two call sites) — every
|
|
581
|
+
// returned signature is substituted through it, and a generic base link
|
|
582
|
+
// along the way (`interface IDerived<T> : IBase<T>`) composes its own
|
|
583
|
+
// stored baseTypeArgs through `bindings` before recursing, so a signature
|
|
584
|
+
// inherited from several generic-base links up still resolves correctly.
|
|
585
|
+
collectInterfaceMethods(interfaceName, bindings = new Map(), seen = new Set()) {
|
|
522
586
|
if (seen.has(interfaceName))
|
|
523
587
|
return [];
|
|
524
588
|
seen.add(interfaceName);
|
|
525
589
|
const info = this.interfaces.get(interfaceName);
|
|
526
590
|
if (!info)
|
|
527
591
|
return [];
|
|
528
|
-
const inherited = info.bases.flatMap((b) =>
|
|
529
|
-
|
|
592
|
+
const inherited = info.bases.flatMap((b) => {
|
|
593
|
+
const baseArgs = info.baseTypeArgs.get(b);
|
|
594
|
+
const baseBindings = baseArgs ? this.composeBaseBindings(b, baseArgs, bindings) : new Map();
|
|
595
|
+
return this.collectInterfaceMethods(b, baseBindings, seen);
|
|
596
|
+
});
|
|
597
|
+
const ownMethods = info.methods.map((m) => ({
|
|
598
|
+
name: m.name,
|
|
599
|
+
params: m.params.map((p) => this.substituteTypeParams(p, bindings)),
|
|
600
|
+
returnType: this.substituteTypeParams(m.returnType, bindings),
|
|
601
|
+
}));
|
|
602
|
+
return [...inherited, ...ownMethods];
|
|
530
603
|
}
|
|
531
604
|
interfaceExtends(sub, sup) {
|
|
532
605
|
if (sub === sup)
|
|
@@ -538,7 +611,8 @@ export class Checker {
|
|
|
538
611
|
}
|
|
539
612
|
registerClass(decl) {
|
|
540
613
|
this.recordHover(decl.line, decl.col, decl.typeParams.length > 0 ? `class ${decl.name}<${decl.typeParams.map((p) => p.name).join(", ")}>` : `class ${decl.name}`);
|
|
541
|
-
const
|
|
614
|
+
const genericBaseCount = { count: 0 };
|
|
615
|
+
const { fields, staticFields, methods, staticMethods, ownCtorParams, superclass, superclassTypeArgs, interfaces, interfaceTypeArgs } = this.withTypeParamsInScope(decl.typeParams, () => {
|
|
542
616
|
const fields = new Map();
|
|
543
617
|
const staticFields = new Map();
|
|
544
618
|
for (const f of decl.fields) {
|
|
@@ -567,39 +641,46 @@ export class Checker {
|
|
|
567
641
|
const ownCtorParams = decl.constructor
|
|
568
642
|
? decl.constructor.params.map((p) => this.resolveType(p.type, decl.constructor.line, decl.constructor.col))
|
|
569
643
|
: null;
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
this.
|
|
644
|
+
// Base-list resolution runs in this same scope — so `Box<T>` in
|
|
645
|
+
// `class Container<T> : Box<T>` resolves its inner `T` as
|
|
646
|
+
// Container's own type parameter, exactly like a field type would.
|
|
647
|
+
let superclass = null;
|
|
648
|
+
let superclassTypeArgs = null;
|
|
649
|
+
const interfaces = [];
|
|
650
|
+
const interfaceTypeArgs = new Map();
|
|
651
|
+
for (const entry of decl.baseList) {
|
|
652
|
+
const kind = this.namedTypes.get(entry.name);
|
|
653
|
+
if (kind === "class") {
|
|
654
|
+
if (superclass !== null) {
|
|
655
|
+
this.diagnostics.error("KS4016", `Class '${decl.name}' cannot extend multiple classes ('${superclass}' and '${entry.name}')`, decl.line, decl.col);
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
superclass = entry.name;
|
|
659
|
+
if (entry.typeArgs || this.genericTypeParams.has(entry.name)) {
|
|
660
|
+
superclassTypeArgs = this.resolveBaseTypeArgs(decl.name, entry.name, entry.typeArgs, decl.line, decl.col, genericBaseCount);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
else if (kind === "interface") {
|
|
664
|
+
interfaces.push(entry.name);
|
|
665
|
+
if (entry.typeArgs || this.genericTypeParams.has(entry.name)) {
|
|
666
|
+
const resolved = this.resolveBaseTypeArgs(decl.name, entry.name, entry.typeArgs, decl.line, decl.col, genericBaseCount);
|
|
667
|
+
if (resolved)
|
|
668
|
+
interfaceTypeArgs.set(entry.name, resolved);
|
|
669
|
+
}
|
|
586
670
|
}
|
|
587
671
|
else {
|
|
588
|
-
|
|
672
|
+
this.diagnostics.error("KS4017", `Unknown base class or interface '${entry.name}'`, decl.line, decl.col);
|
|
589
673
|
}
|
|
590
674
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
}
|
|
594
|
-
else {
|
|
595
|
-
this.diagnostics.error("KS4017", `Unknown base class or interface '${baseName}'`, decl.line, decl.col);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
675
|
+
return { fields, staticFields, methods, staticMethods, ownCtorParams, superclass, superclassTypeArgs, interfaces, interfaceTypeArgs };
|
|
676
|
+
});
|
|
598
677
|
this.classes.set(decl.name, {
|
|
599
678
|
name: decl.name,
|
|
600
679
|
typeParams: decl.typeParams.map((p) => p.name),
|
|
601
680
|
superclass,
|
|
681
|
+
superclassTypeArgs,
|
|
602
682
|
interfaces,
|
|
683
|
+
interfaceTypeArgs,
|
|
603
684
|
fields,
|
|
604
685
|
methods,
|
|
605
686
|
staticFields,
|
|
@@ -607,6 +688,35 @@ export class Checker {
|
|
|
607
688
|
ownCtorParams,
|
|
608
689
|
});
|
|
609
690
|
}
|
|
691
|
+
// Resolves a base-list entry's own type arguments against `baseName`'s
|
|
692
|
+
// declared arity — `typeArgNodes` is null when the entry was written bare
|
|
693
|
+
// (`: Box`, not `: Box<number>`). Must be called from inside the
|
|
694
|
+
// declaring class/interface's own withTypeParamsInScope, so a type
|
|
695
|
+
// argument that references the declaring type's own parameter (`Box<T>`
|
|
696
|
+
// in `class Container<T> : Box<T>`) resolves correctly. `genericBaseCount`
|
|
697
|
+
// is a shared mutable counter across one declaration's whole base list,
|
|
698
|
+
// enforcing v1's "at most one generic entry per base list" cut.
|
|
699
|
+
resolveBaseTypeArgs(declName, baseName, typeArgNodes, declLine, declCol, genericBaseCount) {
|
|
700
|
+
const declaredParams = this.genericTypeParams.get(baseName);
|
|
701
|
+
if (!declaredParams) {
|
|
702
|
+
if (typeArgNodes)
|
|
703
|
+
this.diagnostics.error("KS4092", `'${baseName}' is not generic — it doesn't take a type argument`, declLine, declCol);
|
|
704
|
+
return null;
|
|
705
|
+
}
|
|
706
|
+
if (!typeArgNodes) {
|
|
707
|
+
this.diagnostics.error("KS4093", `Generic base '${baseName}' requires ${this.describeArity(declaredParams)} (e.g. '${baseName}<${declaredParams.join(", ")}>')`, declLine, declCol);
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
if (typeArgNodes.length !== declaredParams.length) {
|
|
711
|
+
this.diagnostics.error("KS4094", `Generic base '${baseName}' expects ${this.describeArity(declaredParams)}, got ${typeArgNodes.length}`, declLine, declCol);
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
genericBaseCount.count++;
|
|
715
|
+
if (genericBaseCount.count > 1) {
|
|
716
|
+
this.diagnostics.error("KS4095", `'${declName}' has more than one generic entry in its base list — v1 supports at most one`, declLine, declCol);
|
|
717
|
+
}
|
|
718
|
+
return typeArgNodes.map((a) => this.resolveType(a, declLine, declCol));
|
|
719
|
+
}
|
|
610
720
|
checkClassHierarchy(decl) {
|
|
611
721
|
const info = this.classes.get(decl.name);
|
|
612
722
|
this.checkBaseCall(decl, info);
|
|
@@ -684,10 +794,19 @@ export class Checker {
|
|
|
684
794
|
}
|
|
685
795
|
checkInterfaceConformance(decl) {
|
|
686
796
|
const classInfo = this.classes.get(decl.name);
|
|
797
|
+
// This is a conformance check against the class's own *declared*
|
|
798
|
+
// (still-abstract) signatures, not an instantiated use site, so a
|
|
799
|
+
// generic interface arg that references the class's own type param
|
|
800
|
+
// (`IContainer<T>` in `class Container<T> : IContainer<T>`) should
|
|
801
|
+
// compose down to that same abstract TypeParamType, matching how the
|
|
802
|
+
// class's own methods are stored — see identityBindings.
|
|
803
|
+
const selfBindings = this.identityBindings(classInfo.typeParams);
|
|
687
804
|
for (const ifaceName of classInfo.interfaces) {
|
|
688
805
|
if (!this.interfaces.has(ifaceName))
|
|
689
806
|
continue; // already reported as an unknown base type
|
|
690
|
-
|
|
807
|
+
const ifaceArgs = classInfo.interfaceTypeArgs.get(ifaceName);
|
|
808
|
+
const bindings = ifaceArgs ? this.composeBaseBindings(ifaceName, ifaceArgs, selfBindings) : new Map();
|
|
809
|
+
for (const sig of this.collectInterfaceMethods(ifaceName, bindings)) {
|
|
691
810
|
const found = this.lookupMethod(decl.name, sig.name);
|
|
692
811
|
if (!found) {
|
|
693
812
|
this.diagnostics.error("KS4026", `Class '${decl.name}' does not implement method '${sig.name}' required by interface '${ifaceName}'`, decl.line, decl.col);
|
|
@@ -896,7 +1015,18 @@ export class Checker {
|
|
|
896
1015
|
// params but no `this` — matching real base()/super() semantics,
|
|
897
1016
|
// which must run before `this` becomes available.
|
|
898
1017
|
if (decl.constructor.baseArgs && info.superclass) {
|
|
899
|
-
|
|
1018
|
+
// One step from `decl` to its immediate superclass (Container's own
|
|
1019
|
+
// type params, still abstract, composed through whatever it
|
|
1020
|
+
// supplied Box in its base list — see identityBindings), then
|
|
1021
|
+
// however many further steps lookupCtorParams' own `owner` implies
|
|
1022
|
+
// (Box itself has no constructor of its own, inherits its
|
|
1023
|
+
// superclass's) via bindingsAtAncestor.
|
|
1024
|
+
const superBindings = info.superclassTypeArgs
|
|
1025
|
+
? this.composeBaseBindings(info.superclass, info.superclassTypeArgs, this.identityBindings(info.typeParams))
|
|
1026
|
+
: new Map();
|
|
1027
|
+
const { params: rawBaseCtorParams, owner: baseCtorOwner } = this.lookupCtorParams(info.superclass);
|
|
1028
|
+
const baseCtorBindings = this.bindingsAtAncestor(info.superclass, superBindings, baseCtorOwner);
|
|
1029
|
+
const baseCtorParams = rawBaseCtorParams.map((p) => this.substituteTypeParams(p, baseCtorBindings));
|
|
900
1030
|
const baseCtx = { returnType: T.VOID, currentClass: info, inConstructor: false, loopDepth: 0, isAsync: false };
|
|
901
1031
|
if (decl.constructor.baseArgs.length !== baseCtorParams.length) {
|
|
902
1032
|
this.diagnostics.error("KS4032", `Expected ${baseCtorParams.length} base constructor argument(s), got ${decl.constructor.baseArgs.length}`, decl.constructor.line, decl.constructor.col);
|
|
@@ -1498,11 +1628,16 @@ export class Checker {
|
|
|
1498
1628
|
typeArgs = expr.typeArgs.map((a) => this.resolveType(a, expr.line, expr.col));
|
|
1499
1629
|
}
|
|
1500
1630
|
this.recordHover(expr.line, expr.col, typeArgs ? `class ${expr.className}<${typeArgs.map(T.typeToString).join(", ")}>` : `class ${expr.className}`);
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1631
|
+
// The constructor being called may be inherited from a generic
|
|
1632
|
+
// ancestor several base-list links up (e.g. `new NumberBox(5)` where
|
|
1633
|
+
// `NumberBox : Box<number>` has no constructor of its own) — compose
|
|
1634
|
+
// bindings all the way to whichever class's own constructor
|
|
1635
|
+
// lookupCtorParams actually found (bindingsAtAncestor is a no-op single
|
|
1636
|
+
// step when it's expr.className's own, the common case).
|
|
1637
|
+
const { params: rawCtorParams, owner: ctorOwner } = this.lookupCtorParams(expr.className);
|
|
1638
|
+
const ownCtorBindings = this.ownBindings(info.typeParams, typeArgs) ?? new Map();
|
|
1639
|
+
const ctorBindings = this.bindingsAtAncestor(expr.className, ownCtorBindings, ctorOwner);
|
|
1640
|
+
const ctorParams = rawCtorParams.map((p) => this.substituteTypeParams(p, ctorBindings));
|
|
1506
1641
|
if (expr.args.length !== ctorParams.length) {
|
|
1507
1642
|
this.diagnostics.error("KS4071", `Expected ${ctorParams.length} constructor argument(s), got ${expr.args.length}`, expr.line, expr.col);
|
|
1508
1643
|
}
|
|
@@ -1515,17 +1650,23 @@ export class Checker {
|
|
|
1515
1650
|
});
|
|
1516
1651
|
return T.classType(expr.className, typeArgs);
|
|
1517
1652
|
}
|
|
1653
|
+
// `owner` is which class up the chain actually declared the constructor
|
|
1654
|
+
// being inherited (itself, or the nearest ancestor with one) — needed so
|
|
1655
|
+
// a caller can compose the right type-parameter bindings when that
|
|
1656
|
+
// ancestor is a generic base (see bindingsAtAncestor). `params` is stored
|
|
1657
|
+
// abstractly in `owner`'s own type-param-name space, same as a field or
|
|
1658
|
+
// method's type would be.
|
|
1518
1659
|
lookupCtorParams(className) {
|
|
1519
1660
|
let current = className;
|
|
1520
1661
|
while (current) {
|
|
1521
1662
|
const info = this.classes.get(current);
|
|
1522
1663
|
if (!info)
|
|
1523
|
-
return [];
|
|
1664
|
+
return { params: [], owner: className };
|
|
1524
1665
|
if (info.ownCtorParams !== null)
|
|
1525
|
-
return info.ownCtorParams;
|
|
1666
|
+
return { params: info.ownCtorParams, owner: current };
|
|
1526
1667
|
current = info.superclass;
|
|
1527
1668
|
}
|
|
1528
|
-
return [];
|
|
1669
|
+
return { params: [], owner: className };
|
|
1529
1670
|
}
|
|
1530
1671
|
// Array stdlib with a fixed (non-polymorphic) signature, given the
|
|
1531
1672
|
// array's own element type — everything except Map, which checkCall
|
|
@@ -1663,13 +1804,14 @@ export class Checker {
|
|
|
1663
1804
|
return { type: T.UNKNOWN, methodInfo: null };
|
|
1664
1805
|
}
|
|
1665
1806
|
if (objectType.kind === "class") {
|
|
1666
|
-
//
|
|
1667
|
-
//
|
|
1668
|
-
//
|
|
1669
|
-
//
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1807
|
+
// A member found on an *ancestor* (owner !== objectType.name — v1 now
|
|
1808
|
+
// supports a generic superclass, see registerClass/
|
|
1809
|
+
// bindingsAtAncestor) needs bindings composed through however many
|
|
1810
|
+
// generic base links separate objectType from that ancestor, not just
|
|
1811
|
+
// objectType's own type arguments — bindingsAtAncestor does that walk
|
|
1812
|
+
// (a no-op single step when owner IS objectType.name, the common
|
|
1813
|
+
// case, same result as before this existed).
|
|
1814
|
+
const ownBindings = this.ownBindings(this.genericTypeParams.get(objectType.name) ?? [], objectType.typeArgs) ?? new Map();
|
|
1673
1815
|
const field = this.lookupField(objectType.name, expr.property);
|
|
1674
1816
|
if (field) {
|
|
1675
1817
|
this.checkAccessibility(field.info.visibility, field.owner, ctx, expr.property, expr.line, expr.col);
|
|
@@ -1679,29 +1821,32 @@ export class Checker {
|
|
|
1679
1821
|
this.diagnostics.error("KS4082", `'${expr.property}' has no setter and can only be assigned within ${field.owner}'s constructor`, expr.line, expr.col);
|
|
1680
1822
|
}
|
|
1681
1823
|
}
|
|
1682
|
-
|
|
1824
|
+
const bindings = this.bindingsAtAncestor(objectType.name, ownBindings, field.owner);
|
|
1825
|
+
return { type: this.substituteTypeParams(field.info.type, bindings), methodInfo: null };
|
|
1683
1826
|
}
|
|
1684
1827
|
const method = this.lookupMethod(objectType.name, expr.property);
|
|
1685
1828
|
if (method) {
|
|
1686
1829
|
this.checkAccessibility(method.info.visibility, method.owner, ctx, expr.property, expr.line, expr.col);
|
|
1687
|
-
const
|
|
1830
|
+
const bindings = this.bindingsAtAncestor(objectType.name, ownBindings, method.owner);
|
|
1831
|
+
const info = {
|
|
1832
|
+
...method.info,
|
|
1833
|
+
params: method.info.params.map((p) => this.substituteTypeParams(p, bindings)),
|
|
1834
|
+
returnType: this.substituteTypeParams(method.info.returnType, bindings),
|
|
1835
|
+
};
|
|
1688
1836
|
return { type: info.returnType, methodInfo: info };
|
|
1689
1837
|
}
|
|
1690
1838
|
this.diagnostics.error("KS4083", `Class '${objectType.name}' has no member '${expr.property}'`, expr.line, expr.col);
|
|
1691
1839
|
return { type: T.UNKNOWN, methodInfo: null };
|
|
1692
1840
|
}
|
|
1693
1841
|
if (objectType.kind === "interface") {
|
|
1694
|
-
|
|
1842
|
+
// collectInterfaceMethods itself now substitutes through `bindings` —
|
|
1843
|
+
// including across a generic base-interface link — so passing
|
|
1844
|
+
// objectType's own bindings once at the root is enough; no separate
|
|
1845
|
+
// re-substitution needed here (see its own header comment).
|
|
1846
|
+
const ownBindings = this.ownBindings(this.genericTypeParams.get(objectType.name) ?? [], objectType.typeArgs) ?? new Map();
|
|
1847
|
+
const sig = this.collectInterfaceMethods(objectType.name, ownBindings).find((m) => m.name === expr.property);
|
|
1695
1848
|
if (sig) {
|
|
1696
|
-
|
|
1697
|
-
// as classes — see registerInterface), so a signature found here
|
|
1698
|
-
// was always declared directly on this same interface.
|
|
1699
|
-
const declaredParams = objectType.typeArgs ? this.genericTypeParams.get(objectType.name) : undefined;
|
|
1700
|
-
const bindings = declaredParams && objectType.typeArgs ? new Map(declaredParams.map((name, i) => [name, objectType.typeArgs[i]])) : null;
|
|
1701
|
-
const substitute = (type) => (bindings ? this.substituteTypeParams(type, bindings) : type);
|
|
1702
|
-
const params = sig.params.map(substitute);
|
|
1703
|
-
const returnType = substitute(sig.returnType);
|
|
1704
|
-
return { type: returnType, methodInfo: { params, returnType, visibility: "public", isVirtual: false, isOverride: false } };
|
|
1849
|
+
return { type: sig.returnType, methodInfo: { params: sig.params, returnType: sig.returnType, visibility: "public", isVirtual: false, isOverride: false } };
|
|
1705
1850
|
}
|
|
1706
1851
|
this.diagnostics.error("KS4084", `Interface '${objectType.name}' has no member '${expr.property}'`, expr.line, expr.col);
|
|
1707
1852
|
return { type: T.UNKNOWN, methodInfo: null };
|
package/dist/codegen.js
CHANGED
|
@@ -286,7 +286,7 @@ export class CodeGenerator {
|
|
|
286
286
|
const pad = this.indentStr(indent);
|
|
287
287
|
// The base list mixes an optional superclass with interface names (checker-validated);
|
|
288
288
|
// only the non-interface entry, if any, becomes a JS `extends` clause.
|
|
289
|
-
const superclass = decl.baseList.find((
|
|
289
|
+
const superclass = decl.baseList.find((b) => !this.interfaceNames.has(b.name))?.name ?? null;
|
|
290
290
|
const header = superclass ? `class ${decl.name} extends ${superclass} {` : `class ${decl.name} {`;
|
|
291
291
|
const memberPad = this.indentStr(indent + 1);
|
|
292
292
|
const memberCol = memberPad.length;
|
package/dist/parser.js
CHANGED
|
@@ -258,6 +258,21 @@ export class Parser {
|
|
|
258
258
|
this.consume(TokenKind.Gt, "Expected '>' after type parameter list");
|
|
259
259
|
return params;
|
|
260
260
|
}
|
|
261
|
+
// `Bar<T>` or `IBaz` — one base-list entry, with or without its own type
|
|
262
|
+
// arguments. Used by both class and interface base lists.
|
|
263
|
+
parseBaseListEntry() {
|
|
264
|
+
const name = this.consume(TokenKind.Identifier, "Expected base class or interface name").lexeme;
|
|
265
|
+
let typeArgs = null;
|
|
266
|
+
if (this.check(TokenKind.Lt)) {
|
|
267
|
+
this.advance();
|
|
268
|
+
typeArgs = [this.parseType()];
|
|
269
|
+
while (this.match(TokenKind.Comma)) {
|
|
270
|
+
typeArgs.push(this.parseType());
|
|
271
|
+
}
|
|
272
|
+
this.consume(TokenKind.Gt, "Expected '>' after type argument list");
|
|
273
|
+
}
|
|
274
|
+
return { name, typeArgs };
|
|
275
|
+
}
|
|
261
276
|
parseClassDecl(isExported) {
|
|
262
277
|
const start = this.advance(); // 'class'
|
|
263
278
|
const name = this.consume(TokenKind.Identifier, "Expected class name").lexeme;
|
|
@@ -265,7 +280,7 @@ export class Parser {
|
|
|
265
280
|
const baseList = [];
|
|
266
281
|
if (this.match(TokenKind.Colon)) {
|
|
267
282
|
do {
|
|
268
|
-
baseList.push(this.
|
|
283
|
+
baseList.push(this.parseBaseListEntry());
|
|
269
284
|
} while (this.match(TokenKind.Comma));
|
|
270
285
|
}
|
|
271
286
|
this.consume(TokenKind.LBrace, "Expected '{' before class body");
|
|
@@ -426,7 +441,7 @@ export class Parser {
|
|
|
426
441
|
const baseList = [];
|
|
427
442
|
if (this.match(TokenKind.Colon)) {
|
|
428
443
|
do {
|
|
429
|
-
baseList.push(this.
|
|
444
|
+
baseList.push(this.parseBaseListEntry());
|
|
430
445
|
} while (this.match(TokenKind.Comma));
|
|
431
446
|
}
|
|
432
447
|
this.consume(TokenKind.LBrace, "Expected '{' before interface body");
|
package/dist/printer.js
CHANGED
|
@@ -118,6 +118,9 @@ export class Printer {
|
|
|
118
118
|
printParams(params) {
|
|
119
119
|
return params.map((p) => `${this.printType(p.type)} ${p.name}`).join(", ");
|
|
120
120
|
}
|
|
121
|
+
printBaseListEntry(entry) {
|
|
122
|
+
return entry.typeArgs ? `${entry.name}<${entry.typeArgs.map((t) => this.printType(t)).join(", ")}>` : entry.name;
|
|
123
|
+
}
|
|
121
124
|
// ---------- top-level declarations ----------
|
|
122
125
|
printStatement(stmt, indent) {
|
|
123
126
|
const pad = indentStr(indent);
|
|
@@ -213,7 +216,7 @@ export class Printer {
|
|
|
213
216
|
const pad = indentStr(indent);
|
|
214
217
|
const memberPad = indentStr(indent + 1);
|
|
215
218
|
const nameWithTypeParam = decl.typeParams.length > 0 ? `${decl.name}<${decl.typeParams.map((p) => p.name).join(", ")}>` : decl.name;
|
|
216
|
-
const header = decl.baseList.length > 0 ? `class ${nameWithTypeParam} : ${decl.baseList.join(", ")} {` : `class ${nameWithTypeParam} {`;
|
|
219
|
+
const header = decl.baseList.length > 0 ? `class ${nameWithTypeParam} : ${decl.baseList.map((b) => this.printBaseListEntry(b)).join(", ")} {` : `class ${nameWithTypeParam} {`;
|
|
217
220
|
const prefix = decl.isExported ? "" : "private ";
|
|
218
221
|
const memberParts = [];
|
|
219
222
|
const pushMember = (node, text) => {
|
|
@@ -251,7 +254,7 @@ export class Printer {
|
|
|
251
254
|
const pad = indentStr(indent);
|
|
252
255
|
const memberPad = indentStr(indent + 1);
|
|
253
256
|
const nameWithTypeParam = decl.typeParams.length > 0 ? `${decl.name}<${decl.typeParams.map((p) => p.name).join(", ")}>` : decl.name;
|
|
254
|
-
const header = decl.baseList.length > 0 ? `interface ${nameWithTypeParam} : ${decl.baseList.join(", ")} {` : `interface ${nameWithTypeParam} {`;
|
|
257
|
+
const header = decl.baseList.length > 0 ? `interface ${nameWithTypeParam} : ${decl.baseList.map((b) => this.printBaseListEntry(b)).join(", ")} {` : `interface ${nameWithTypeParam} {`;
|
|
255
258
|
const prefix = decl.isExported ? "" : "private ";
|
|
256
259
|
if (decl.methods.length === 0)
|
|
257
260
|
return `${pad}${prefix}${header}\n${pad}}`;
|
package/package.json
CHANGED