kopscript 0.13.0 → 0.14.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 +40 -20
- package/README.md +34 -15
- package/dist/checker.js +127 -14
- package/dist/parser.js +9 -5
- package/dist/printer.js +9 -3
- package/package.json +1 -1
package/LLM.md
CHANGED
|
@@ -303,9 +303,10 @@ class Pair<K, V> {
|
|
|
303
303
|
Pair<number, string> p = new Pair<number, string>(1, "a");
|
|
304
304
|
```
|
|
305
305
|
|
|
306
|
-
One or more
|
|
307
|
-
`extern`
|
|
308
|
-
|
|
306
|
+
One or more invariant type parameters per class/interface (real *or* `extern` — see the
|
|
307
|
+
`extern` section below), comma-separated: `<T>`, `<K, V>`, `<A, B, C>`, however many the
|
|
308
|
+
declaration needs — each optionally constrained to a single interface: `<T : IComparable>`,
|
|
309
|
+
`<K : IHashable, V>` (mixing constrained and unconstrained params is fine).
|
|
309
310
|
Erased at codegen (JS has no generics either; `Box<number>` and `Box<string>` both
|
|
310
311
|
compile to the exact same plain `class Box`), so there's no runtime cost and no way to
|
|
311
312
|
inspect a type parameter at runtime.
|
|
@@ -322,10 +323,30 @@ inspect a type parameter at runtime.
|
|
|
322
323
|
- Nesting works: `Box<Box<number>>`, `Box<number[]>`, `Box<number>?`, `Box<number>?[]`,
|
|
323
324
|
`Pair<number, Pair<string, bool>>`.
|
|
324
325
|
- Inside the class/interface's own body, each type parameter is fully abstract — you can
|
|
325
|
-
store it, return it, pass it around, but
|
|
326
|
-
a bare type-parameter value** (`this.value.Foo()`
|
|
327
|
-
"Cannot access member 'Foo' on type 'T'"). This is
|
|
328
|
-
unconstrained type parameter in C#/Java/TypeScript.
|
|
326
|
+
store it, return it, pass it around, but an **unconstrained** parameter (no `: IFoo`)
|
|
327
|
+
means you **cannot call any member on a bare type-parameter value** (`this.value.Foo()`
|
|
328
|
+
where `value: T` is a compile error: "Cannot access member 'Foo' on type 'T'"). This is
|
|
329
|
+
correct, not a bug — same as an unconstrained type parameter in C#/Java/TypeScript.
|
|
330
|
+
|
|
331
|
+
**Constraints**: `<T : IComparable>` lifts the "no member calls" restriction — a member
|
|
332
|
+
call on a constrained `T` resolves against the constraint interface's own signature:
|
|
333
|
+
```ks
|
|
334
|
+
interface IComparable { number CompareTo(); }
|
|
335
|
+
class Box<T : IComparable> {
|
|
336
|
+
public T Value;
|
|
337
|
+
constructor(T v) { this.Value = v; }
|
|
338
|
+
public number Compare() { return this.Value.CompareTo(); } // legal — T : IComparable
|
|
339
|
+
}
|
|
340
|
+
Box<Money> b = new Box<Money>(new Money(5)); // Money must implement IComparable
|
|
341
|
+
```
|
|
342
|
+
A type argument satisfies a constraint by implementing the interface directly, through an
|
|
343
|
+
ancestor (`classImplementsInterface` walks the whole superclass chain), or — inside another
|
|
344
|
+
generic body — by already being an equally- or more-constrained type parameter of its own
|
|
345
|
+
(`class Wrapper<T : IComparable> { Box<T> MakeBox(T v) { return new Box<T>(v); } }` is
|
|
346
|
+
legal without ever naming a concrete type). Checked everywhere a type argument is bound: on
|
|
347
|
+
`new`, on a bare generic type reference, and on a generic base in a base list.
|
|
348
|
+
**v1 limit**: at most one constraint per parameter (`T : IFoo, IBar` isn't supported), and
|
|
349
|
+
the constraint must be an interface, never a class.
|
|
329
350
|
|
|
330
351
|
**Generic inheritance**: a class/interface base list CAN name a generic base, with its own
|
|
331
352
|
type arguments, in either direction:
|
|
@@ -349,8 +370,6 @@ though either half alone would work.
|
|
|
349
370
|
- **Generic functions.** `T Identity<T>(T x)` doesn't parse — only classes and interfaces
|
|
350
371
|
take type parameters, not free functions/methods themselves (a method *inside* a
|
|
351
372
|
generic class can use that class's own type parameters freely, same as any other member).
|
|
352
|
-
- **Constraints.** No `T : IComparable` — a type parameter is always fully unconstrained,
|
|
353
|
-
which is why you can't call members on a bare one (see above).
|
|
354
373
|
- **Variance.** No `out`/`in`/covariance/contravariance — see invariance above.
|
|
355
374
|
|
|
356
375
|
## Nullable types — `T?`
|
|
@@ -428,13 +447,14 @@ KopScript-declared name exactly. Extern class members use real JS member names v
|
|
|
428
447
|
(camelCase, no rename mechanism). No inheritance modeling between two `extern class`
|
|
429
448
|
declarations — each stands alone.
|
|
430
449
|
|
|
431
|
-
`extern class` can carry type parameters (`extern class Box<T
|
|
432
|
-
{ get; } } from "some-package";`) — identical
|
|
433
|
-
|
|
434
|
-
included), so a generic type from another package
|
|
435
|
-
like a local one (`Box<number>`, arity/invariance
|
|
436
|
-
|
|
437
|
-
argument, same as extending a real generic
|
|
450
|
+
`extern class` can carry type parameters, optionally constrained (`extern class Box<T :
|
|
451
|
+
IComparable> { constructor(T v); T Value { get; } } from "some-package";`) — identical
|
|
452
|
+
rules to a real generic class (see "Generics" above: invariant, erased, generic
|
|
453
|
+
inheritance and constraints both included), so a generic type from another package
|
|
454
|
+
instantiates and type-checks exactly like a local one (`Box<number>`, arity/invariance/
|
|
455
|
+
constraint errors included) — and a real KopScript class can extend a generic `extern
|
|
456
|
+
class` with a concrete or threaded-through type argument, same as extending a real generic
|
|
457
|
+
base.
|
|
438
458
|
|
|
439
459
|
**Never write `async` on an extern function/method signature** — declare its return type
|
|
440
460
|
as `task`/`task<T>` directly (`task<string> text();`, not `async task<string> text();`).
|
|
@@ -521,10 +541,10 @@ Locals/params: **camelCase**. This is convention, not enforced by the compiler.
|
|
|
521
541
|
|
|
522
542
|
## Does not exist (don't reach for these)
|
|
523
543
|
|
|
524
|
-
Generics beyond one or more
|
|
525
|
-
inheritance (no `T : IFoo` constraints, no generic
|
|
526
|
-
generic entry per base list — see Generics above
|
|
527
|
-
`any`/`unknown` annotations ·
|
|
544
|
+
Generics beyond one or more type parameters (each with at most one interface constraint)
|
|
545
|
+
and single-generic-base inheritance (no `T : IFoo, IBar` multi-constraints, no generic
|
|
546
|
+
functions, no variance, no more than one generic entry per base list — see Generics above
|
|
547
|
+
for what *is* supported) · `any`/`unknown` annotations ·
|
|
528
548
|
a `let`/`var` keyword (locals are just `Type name = value;`) · decorators/annotations ·
|
|
529
549
|
reflection · type inference on declarations · ternary expression · union/tuple types ·
|
|
530
550
|
**object-literal syntax** (`{ key: value }` as a value — this doesn't exist *anywhere* in
|
package/README.md
CHANGED
|
@@ -22,9 +22,11 @@ LLM's context, as opposed to this README's narrative explanation.
|
|
|
22
22
|
explicit `virtual`/`override` dispatch (methods are sealed unless marked `virtual`), and a full
|
|
23
23
|
`public`/`protected`/`private` access model enforced at compile time.
|
|
24
24
|
- **Strongly typed**: every declaration is explicitly typed and checked at compile time.
|
|
25
|
-
- **Generics**: one or more
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
- **Generics**: one or more invariant type parameters on classes and interfaces
|
|
26
|
+
(`class Box<T> { public T Value; }`, `class Pair<K, V> { ... }`), each optionally
|
|
27
|
+
constrained to an interface (`class Box<T : IComparable> { ... }`), plus generic
|
|
28
|
+
inheritance (`class IntBox : Box<number> { }`) — erased at codegen with zero runtime
|
|
29
|
+
cost, the same way `task<T>`/`state<T>` already are.
|
|
28
30
|
- **Nullable types with compiler-enforced null-checking**: `T?` (`string?`, `Dog?`,
|
|
29
31
|
`number[]?`) — a `T?` can't be used where a `T` is expected without an `if (x != null)`
|
|
30
32
|
check first (checked statically, not just at runtime), and comparing a value that can
|
|
@@ -253,17 +255,33 @@ compile to the identical plain `class Box`, so there's no runtime cost.
|
|
|
253
255
|
Still a deliberately small slice of generics, not a scaled-down promise of more later
|
|
254
256
|
inside v1:
|
|
255
257
|
|
|
256
|
-
- **Invariant
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
C#/Java/TypeScript put on an unconstrained parameter, not a bug). Invariant means
|
|
260
|
-
`Box<Dog>` is **not** assignable to `Box<Animal>` even though `Dog : Animal`, and
|
|
261
|
-
`Pair<number, string>` is not assignable to `Pair<string, number>` — every slot must
|
|
262
|
-
match exactly, in order.
|
|
258
|
+
- **Invariant.** `Box<Dog>` is **not** assignable to `Box<Animal>` even though `Dog :
|
|
259
|
+
Animal`, and `Pair<number, string>` is not assignable to `Pair<string, number>` — every
|
|
260
|
+
slot must match exactly, in order.
|
|
263
261
|
- **No generic functions.** `T Identity<T>(T x)` isn't supported — only classes and
|
|
264
262
|
interfaces take type parameters. A method *inside* a generic class can still use that
|
|
265
263
|
class's own type parameters freely; it's just not introducing one of its own.
|
|
266
264
|
|
|
265
|
+
By default a type parameter is fully unconstrained, so you can't call any member on a bare
|
|
266
|
+
`T`/`K`/`V` value inside the generic class's own body — the same restriction C#/Java/
|
|
267
|
+
TypeScript put on an unconstrained parameter, not a bug. A single optional `: IFoo`
|
|
268
|
+
constraint per parameter lifts that:
|
|
269
|
+
|
|
270
|
+
```ks
|
|
271
|
+
interface IComparable { number CompareTo(); }
|
|
272
|
+
class Box<T : IComparable> {
|
|
273
|
+
public T Value;
|
|
274
|
+
constructor(T v) { this.Value = v; }
|
|
275
|
+
public number Compare() { return this.Value.CompareTo(); } // legal — T is constrained
|
|
276
|
+
}
|
|
277
|
+
Box<Money> b = new Box<Money>(new Money(5)); // Money must implement IComparable
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
The constraint must be an interface (never a class), and a type argument satisfies it by
|
|
281
|
+
implementing it directly, through an ancestor, or (inside another generic body) by already
|
|
282
|
+
being an equally-or-more-constrained type parameter of its own. **v1 limit**: at most one
|
|
283
|
+
constraint per parameter — `T : IFoo, IBar` isn't supported.
|
|
284
|
+
|
|
267
285
|
A class or interface *can* extend/implement a generic base, type arguments and all:
|
|
268
286
|
|
|
269
287
|
```ks
|
|
@@ -383,11 +401,12 @@ consumes `Component`/`Router` themselves via `extern class ... from "kopular/...
|
|
|
383
401
|
proving `extern` works as a real cross-*package* boundary, not just for describing DOM
|
|
384
402
|
globals within a single project.
|
|
385
403
|
|
|
386
|
-
An `extern class` can carry its own type parameter(s),
|
|
387
|
-
`extern class
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
404
|
+
An `extern class` can carry its own type parameter(s), optionally constrained —
|
|
405
|
+
`extern class Box<T> { ... }`, `extern class Pair<K, V> { ... }`, `extern class Box<T :
|
|
406
|
+
IComparable> { ... }` — exactly the same rules as a real generic class (see "Generics"
|
|
407
|
+
above: invariant, erased, and a real class can extend it as a generic base), so a generic
|
|
408
|
+
type from another package (e.g. Kopular's `FormField<T>`) can be described and
|
|
409
|
+
instantiated generically, not just per concrete type:
|
|
391
410
|
|
|
392
411
|
```ks
|
|
393
412
|
extern class Box<T> {
|
package/dist/checker.js
CHANGED
|
@@ -64,6 +64,19 @@ export class Checker {
|
|
|
64
64
|
// doubles as the arity table (its length is how many type arguments a
|
|
65
65
|
// reference to that name must supply). See resolveType/validateGenericArity.
|
|
66
66
|
this.genericTypeParams = new Map();
|
|
67
|
+
// Same shape/population sites as genericTypeParams, but each entry is a
|
|
68
|
+
// parallel array of that generic's per-parameter constraint interface
|
|
69
|
+
// name (or null if unconstrained) — e.g. "Pair" -> [null, "IHashable"]
|
|
70
|
+
// for `class Pair<K, V : IHashable>`. See resolveBaseTypeArgs/checkNew/
|
|
71
|
+
// checkConstraintSatisfied.
|
|
72
|
+
this.genericTypeParamConstraints = new Map();
|
|
73
|
+
// The constraint interface (or null) for every type parameter *currently
|
|
74
|
+
// in scope* while checking a generic declaration's own body — set/
|
|
75
|
+
// restored by withTypeParamsInScope alongside namedTypes, so a bare `T`
|
|
76
|
+
// value's member access (`this.value.Foo()`) can resolve Foo against the
|
|
77
|
+
// constraint's own signature instead of always erroring (see
|
|
78
|
+
// checkMemberInner's "typeParam" branch).
|
|
79
|
+
this.activeConstraints = new Map();
|
|
67
80
|
this.importedNames = new Set();
|
|
68
81
|
this.rawContents = new Map();
|
|
69
82
|
this.hoverEntries = [];
|
|
@@ -75,13 +88,17 @@ export class Checker {
|
|
|
75
88
|
}
|
|
76
89
|
for (const [name, info] of this.imports.classes) {
|
|
77
90
|
this.classes.set(name, info);
|
|
78
|
-
if (info.typeParams.length > 0)
|
|
91
|
+
if (info.typeParams.length > 0) {
|
|
79
92
|
this.genericTypeParams.set(name, info.typeParams);
|
|
93
|
+
this.genericTypeParamConstraints.set(name, info.typeParamConstraints);
|
|
94
|
+
}
|
|
80
95
|
}
|
|
81
96
|
for (const [name, info] of this.imports.interfaces) {
|
|
82
97
|
this.interfaces.set(name, info);
|
|
83
|
-
if (info.typeParams.length > 0)
|
|
98
|
+
if (info.typeParams.length > 0) {
|
|
84
99
|
this.genericTypeParams.set(name, info.typeParams);
|
|
100
|
+
this.genericTypeParamConstraints.set(name, info.typeParamConstraints);
|
|
101
|
+
}
|
|
85
102
|
}
|
|
86
103
|
for (const [name, info] of this.imports.enums)
|
|
87
104
|
this.enums.set(name, info);
|
|
@@ -110,15 +127,24 @@ export class Checker {
|
|
|
110
127
|
this.namedTypes.set(e.name, "enum");
|
|
111
128
|
for (const c of externClassDecls)
|
|
112
129
|
this.namedTypes.set(c.name, "class");
|
|
113
|
-
for (const c of classDecls)
|
|
114
|
-
if (c.typeParams.length
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
for (const
|
|
120
|
-
if (
|
|
121
|
-
|
|
130
|
+
for (const c of classDecls) {
|
|
131
|
+
if (c.typeParams.length === 0)
|
|
132
|
+
continue;
|
|
133
|
+
this.genericTypeParams.set(c.name, c.typeParams.map((p) => p.name));
|
|
134
|
+
this.genericTypeParamConstraints.set(c.name, c.typeParams.map((p) => p.constraint));
|
|
135
|
+
}
|
|
136
|
+
for (const i of interfaceDecls) {
|
|
137
|
+
if (i.typeParams.length === 0)
|
|
138
|
+
continue;
|
|
139
|
+
this.genericTypeParams.set(i.name, i.typeParams.map((p) => p.name));
|
|
140
|
+
this.genericTypeParamConstraints.set(i.name, i.typeParams.map((p) => p.constraint));
|
|
141
|
+
}
|
|
142
|
+
for (const c of externClassDecls) {
|
|
143
|
+
if (c.typeParams.length === 0)
|
|
144
|
+
continue;
|
|
145
|
+
this.genericTypeParams.set(c.name, c.typeParams.map((p) => p.name));
|
|
146
|
+
this.genericTypeParamConstraints.set(c.name, c.typeParams.map((p) => p.constraint));
|
|
147
|
+
}
|
|
122
148
|
for (const e of enumDecls)
|
|
123
149
|
this.registerEnum(e);
|
|
124
150
|
for (const i of interfaceDecls)
|
|
@@ -203,6 +229,7 @@ export class Checker {
|
|
|
203
229
|
this.classes.set(decl.name, {
|
|
204
230
|
name: decl.name,
|
|
205
231
|
typeParams: decl.typeParams.map((p) => p.name),
|
|
232
|
+
typeParamConstraints: decl.typeParams.map((p) => p.constraint),
|
|
206
233
|
superclass: null,
|
|
207
234
|
superclassTypeArgs: null,
|
|
208
235
|
interfaces: [],
|
|
@@ -394,6 +421,14 @@ export class Checker {
|
|
|
394
421
|
else if (declaredParams && suppliedCount > 0 && suppliedCount !== declaredParams.length) {
|
|
395
422
|
this.diagnostics.error("KS4090", `Type '${type.name}' expects ${this.describeArity(declaredParams)}, got ${suppliedCount}`, line, col);
|
|
396
423
|
}
|
|
424
|
+
else if (declaredParams && suppliedCount === declaredParams.length) {
|
|
425
|
+
const constraints = this.genericTypeParamConstraints.get(type.name);
|
|
426
|
+
type.typeArgs.forEach((arg, i) => {
|
|
427
|
+
const constraint = constraints?.[i];
|
|
428
|
+
if (constraint)
|
|
429
|
+
this.checkConstraintSatisfied(arg, constraint, declaredParams[i], line, col);
|
|
430
|
+
});
|
|
431
|
+
}
|
|
397
432
|
type.typeArgs?.forEach((a) => this.validateGenericArity(a, line, col));
|
|
398
433
|
return;
|
|
399
434
|
}
|
|
@@ -415,8 +450,11 @@ export class Checker {
|
|
|
415
450
|
if (typeParams.length === 0)
|
|
416
451
|
return fn();
|
|
417
452
|
const previous = typeParams.map((p) => this.namedTypes.get(p.name));
|
|
418
|
-
|
|
453
|
+
const previousConstraints = typeParams.map((p) => this.activeConstraints.get(p.name));
|
|
454
|
+
for (const p of typeParams) {
|
|
419
455
|
this.namedTypes.set(p.name, "typeParam");
|
|
456
|
+
this.activeConstraints.set(p.name, p.constraint);
|
|
457
|
+
}
|
|
420
458
|
try {
|
|
421
459
|
return fn();
|
|
422
460
|
}
|
|
@@ -427,6 +465,11 @@ export class Checker {
|
|
|
427
465
|
this.namedTypes.delete(p.name);
|
|
428
466
|
else
|
|
429
467
|
this.namedTypes.set(p.name, prev);
|
|
468
|
+
const prevConstraint = previousConstraints[i];
|
|
469
|
+
if (prevConstraint === undefined)
|
|
470
|
+
this.activeConstraints.delete(p.name);
|
|
471
|
+
else
|
|
472
|
+
this.activeConstraints.set(p.name, prevConstraint);
|
|
430
473
|
});
|
|
431
474
|
}
|
|
432
475
|
}
|
|
@@ -551,7 +594,14 @@ export class Checker {
|
|
|
551
594
|
}
|
|
552
595
|
return { methods, bases, baseTypeArgs };
|
|
553
596
|
});
|
|
554
|
-
this.interfaces.set(decl.name, {
|
|
597
|
+
this.interfaces.set(decl.name, {
|
|
598
|
+
name: decl.name,
|
|
599
|
+
typeParams: decl.typeParams.map((p) => p.name),
|
|
600
|
+
typeParamConstraints: decl.typeParams.map((p) => p.constraint),
|
|
601
|
+
bases,
|
|
602
|
+
baseTypeArgs,
|
|
603
|
+
methods,
|
|
604
|
+
});
|
|
555
605
|
this.recordHover(decl.line, decl.col, decl.typeParams.length > 0 ? `interface ${decl.name}<${decl.typeParams.map((p) => p.name).join(", ")}>` : `interface ${decl.name}`);
|
|
556
606
|
}
|
|
557
607
|
checkInterfaceHierarchy(decl) {
|
|
@@ -677,6 +727,7 @@ export class Checker {
|
|
|
677
727
|
this.classes.set(decl.name, {
|
|
678
728
|
name: decl.name,
|
|
679
729
|
typeParams: decl.typeParams.map((p) => p.name),
|
|
730
|
+
typeParamConstraints: decl.typeParams.map((p) => p.constraint),
|
|
680
731
|
superclass,
|
|
681
732
|
superclassTypeArgs,
|
|
682
733
|
interfaces,
|
|
@@ -715,7 +766,15 @@ export class Checker {
|
|
|
715
766
|
if (genericBaseCount.count > 1) {
|
|
716
767
|
this.diagnostics.error("KS4095", `'${declName}' has more than one generic entry in its base list — v1 supports at most one`, declLine, declCol);
|
|
717
768
|
}
|
|
718
|
-
|
|
769
|
+
const resolvedArgs = typeArgNodes.map((a) => this.resolveType(a, declLine, declCol));
|
|
770
|
+
const constraints = this.genericTypeParamConstraints.get(baseName);
|
|
771
|
+
const declaredParamNames = this.genericTypeParams.get(baseName);
|
|
772
|
+
resolvedArgs.forEach((arg, i) => {
|
|
773
|
+
const constraint = constraints?.[i];
|
|
774
|
+
if (constraint)
|
|
775
|
+
this.checkConstraintSatisfied(arg, constraint, declaredParamNames[i], declLine, declCol);
|
|
776
|
+
});
|
|
777
|
+
return resolvedArgs;
|
|
719
778
|
}
|
|
720
779
|
checkClassHierarchy(decl) {
|
|
721
780
|
const info = this.classes.get(decl.name);
|
|
@@ -846,6 +905,38 @@ export class Checker {
|
|
|
846
905
|
}
|
|
847
906
|
return false;
|
|
848
907
|
}
|
|
908
|
+
// Checks that `argType` (bound to type parameter `paramName`, declared
|
|
909
|
+
// `paramName : constraintInterfaceName`) actually satisfies the
|
|
910
|
+
// constraint: a class implementing the interface (possibly via an
|
|
911
|
+
// ancestor — classImplementsInterface already walks the chain), the
|
|
912
|
+
// interface itself (or one extending it), or a currently-in-scope type
|
|
913
|
+
// parameter whose own constraint already extends this one (so a
|
|
914
|
+
// constrained T can be threaded into another equally-constrained slot,
|
|
915
|
+
// e.g. `class Wrapper<T : IComparable> { void F(Box<T> b) { ... } }`
|
|
916
|
+
// passing Wrapper's own T to a `Box<T : IComparable>`). Anything else —
|
|
917
|
+
// a primitive, an array, an unconstrained type parameter, an unrelated
|
|
918
|
+
// class — is a compile error.
|
|
919
|
+
checkConstraintSatisfied(argType, constraintInterfaceName, paramName, line, col) {
|
|
920
|
+
if (argType.kind === "unknown")
|
|
921
|
+
return; // an earlier error already reported; don't cascade
|
|
922
|
+
let satisfied;
|
|
923
|
+
if (argType.kind === "class") {
|
|
924
|
+
satisfied = this.classImplementsInterface(argType.name, constraintInterfaceName);
|
|
925
|
+
}
|
|
926
|
+
else if (argType.kind === "interface") {
|
|
927
|
+
satisfied = this.interfaceExtends(argType.name, constraintInterfaceName);
|
|
928
|
+
}
|
|
929
|
+
else if (argType.kind === "typeParam") {
|
|
930
|
+
const ownConstraint = this.activeConstraints.get(argType.name);
|
|
931
|
+
satisfied = !!ownConstraint && this.interfaceExtends(ownConstraint, constraintInterfaceName);
|
|
932
|
+
}
|
|
933
|
+
else {
|
|
934
|
+
satisfied = false;
|
|
935
|
+
}
|
|
936
|
+
if (!satisfied) {
|
|
937
|
+
this.diagnostics.error("KS4096", `Type argument '${T.typeToString(argType)}' does not satisfy constraint '${constraintInterfaceName}' for type parameter '${paramName}'`, line, col);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
849
940
|
lookupField(className, fieldName) {
|
|
850
941
|
let current = className;
|
|
851
942
|
while (current) {
|
|
@@ -1626,6 +1717,12 @@ export class Checker {
|
|
|
1626
1717
|
return T.UNKNOWN;
|
|
1627
1718
|
}
|
|
1628
1719
|
typeArgs = expr.typeArgs.map((a) => this.resolveType(a, expr.line, expr.col));
|
|
1720
|
+
const constraints = this.genericTypeParamConstraints.get(expr.className);
|
|
1721
|
+
typeArgs.forEach((arg, i) => {
|
|
1722
|
+
const constraint = constraints?.[i];
|
|
1723
|
+
if (constraint)
|
|
1724
|
+
this.checkConstraintSatisfied(arg, constraint, info.typeParams[i], expr.line, expr.col);
|
|
1725
|
+
});
|
|
1629
1726
|
}
|
|
1630
1727
|
this.recordHover(expr.line, expr.col, typeArgs ? `class ${expr.className}<${typeArgs.map(T.typeToString).join(", ")}>` : `class ${expr.className}`);
|
|
1631
1728
|
// The constructor being called may be inherited from a generic
|
|
@@ -1851,6 +1948,22 @@ export class Checker {
|
|
|
1851
1948
|
this.diagnostics.error("KS4084", `Interface '${objectType.name}' has no member '${expr.property}'`, expr.line, expr.col);
|
|
1852
1949
|
return { type: T.UNKNOWN, methodInfo: null };
|
|
1853
1950
|
}
|
|
1951
|
+
// A bare type-parameter receiver (`this.value.Foo()` where `value: T`)
|
|
1952
|
+
// — resolves against the constraint interface's own signature if `T`
|
|
1953
|
+
// is constrained; an unconstrained `T` falls through to the generic
|
|
1954
|
+
// "cannot access member" error below, unchanged from before constraints
|
|
1955
|
+
// existed (see README/LLM.md's own documented restriction).
|
|
1956
|
+
if (objectType.kind === "typeParam") {
|
|
1957
|
+
const constraintName = this.activeConstraints.get(objectType.name);
|
|
1958
|
+
if (constraintName) {
|
|
1959
|
+
const sig = this.collectInterfaceMethods(constraintName).find((m) => m.name === expr.property);
|
|
1960
|
+
if (sig) {
|
|
1961
|
+
return { type: sig.returnType, methodInfo: { params: sig.params, returnType: sig.returnType, visibility: "public", isVirtual: false, isOverride: false } };
|
|
1962
|
+
}
|
|
1963
|
+
this.diagnostics.error("KS4097", `Interface '${constraintName}' (the constraint on type parameter '${objectType.name}') has no member '${expr.property}'`, expr.line, expr.col);
|
|
1964
|
+
return { type: T.UNKNOWN, methodInfo: null };
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1854
1967
|
if (objectType.kind !== "unknown") {
|
|
1855
1968
|
this.diagnostics.error("KS4085", `Cannot access member '${expr.property}' on type '${T.typeToString(objectType)}'`, expr.line, expr.col);
|
|
1856
1969
|
}
|
package/dist/parser.js
CHANGED
|
@@ -243,17 +243,21 @@ export class Parser {
|
|
|
243
243
|
this.consume(TokenKind.RParen, "Expected ')' after parameters");
|
|
244
244
|
return params;
|
|
245
245
|
}
|
|
246
|
-
// `<K, V>` right after a class/interface name — one
|
|
247
|
-
// parameters, comma-separated
|
|
248
|
-
//
|
|
249
|
-
//
|
|
246
|
+
// `<K : IHashable, V>` right after a class/interface/function name — one
|
|
247
|
+
// or more type parameters, comma-separated, each with an optional
|
|
248
|
+
// `: InterfaceName` constraint. Empty array if absent, the overwhelmingly
|
|
249
|
+
// common case.
|
|
250
250
|
parseTypeParamList() {
|
|
251
251
|
if (!this.match(TokenKind.Lt))
|
|
252
252
|
return [];
|
|
253
253
|
const params = [];
|
|
254
254
|
do {
|
|
255
255
|
const nameTok = this.consume(TokenKind.Identifier, "Expected a type parameter name");
|
|
256
|
-
|
|
256
|
+
let constraint = null;
|
|
257
|
+
if (this.match(TokenKind.Colon)) {
|
|
258
|
+
constraint = this.consume(TokenKind.Identifier, "Expected a constraint interface name after ':'").lexeme;
|
|
259
|
+
}
|
|
260
|
+
params.push({ name: nameTok.lexeme, constraint, line: nameTok.line, col: nameTok.col });
|
|
257
261
|
} while (this.match(TokenKind.Comma));
|
|
258
262
|
this.consume(TokenKind.Gt, "Expected '>' after type parameter list");
|
|
259
263
|
return params;
|
package/dist/printer.js
CHANGED
|
@@ -121,6 +121,12 @@ export class Printer {
|
|
|
121
121
|
printBaseListEntry(entry) {
|
|
122
122
|
return entry.typeArgs ? `${entry.name}<${entry.typeArgs.map((t) => this.printType(t)).join(", ")}>` : entry.name;
|
|
123
123
|
}
|
|
124
|
+
printTypeParamName(decl) {
|
|
125
|
+
if (decl.typeParams.length === 0)
|
|
126
|
+
return decl.name;
|
|
127
|
+
const params = decl.typeParams.map((p) => (p.constraint ? `${p.name} : ${p.constraint}` : p.name)).join(", ");
|
|
128
|
+
return `${decl.name}<${params}>`;
|
|
129
|
+
}
|
|
124
130
|
// ---------- top-level declarations ----------
|
|
125
131
|
printStatement(stmt, indent) {
|
|
126
132
|
const pad = indentStr(indent);
|
|
@@ -215,7 +221,7 @@ export class Printer {
|
|
|
215
221
|
printClass(decl, indent) {
|
|
216
222
|
const pad = indentStr(indent);
|
|
217
223
|
const memberPad = indentStr(indent + 1);
|
|
218
|
-
const nameWithTypeParam =
|
|
224
|
+
const nameWithTypeParam = this.printTypeParamName(decl);
|
|
219
225
|
const header = decl.baseList.length > 0 ? `class ${nameWithTypeParam} : ${decl.baseList.map((b) => this.printBaseListEntry(b)).join(", ")} {` : `class ${nameWithTypeParam} {`;
|
|
220
226
|
const prefix = decl.isExported ? "" : "private ";
|
|
221
227
|
const memberParts = [];
|
|
@@ -253,7 +259,7 @@ export class Printer {
|
|
|
253
259
|
printInterface(decl, indent) {
|
|
254
260
|
const pad = indentStr(indent);
|
|
255
261
|
const memberPad = indentStr(indent + 1);
|
|
256
|
-
const nameWithTypeParam =
|
|
262
|
+
const nameWithTypeParam = this.printTypeParamName(decl);
|
|
257
263
|
const header = decl.baseList.length > 0 ? `interface ${nameWithTypeParam} : ${decl.baseList.map((b) => this.printBaseListEntry(b)).join(", ")} {` : `interface ${nameWithTypeParam} {`;
|
|
258
264
|
const prefix = decl.isExported ? "" : "private ";
|
|
259
265
|
if (decl.methods.length === 0)
|
|
@@ -282,7 +288,7 @@ export class Printer {
|
|
|
282
288
|
const pad = indentStr(indent);
|
|
283
289
|
const memberPad = indentStr(indent + 1);
|
|
284
290
|
const prefix = decl.isExported ? "" : "private ";
|
|
285
|
-
const nameWithTypeParam =
|
|
291
|
+
const nameWithTypeParam = this.printTypeParamName(decl);
|
|
286
292
|
const lines = [];
|
|
287
293
|
if (decl.hasConstructor)
|
|
288
294
|
lines.push(`${memberPad}constructor(${this.printParams(decl.ctorParams)});`);
|
package/package.json
CHANGED