kopscript 0.12.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 +58 -21
- package/README.md +50 -18
- package/dist/checker.js +349 -91
- package/dist/codegen.js +1 -1
- package/dist/parser.js +26 -7
- package/dist/printer.js +14 -5
- 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,21 +323,53 @@ 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.
|
|
350
|
+
|
|
351
|
+
**Generic inheritance**: a class/interface base list CAN name a generic base, with its own
|
|
352
|
+
type arguments, in either direction:
|
|
353
|
+
```ks
|
|
354
|
+
class IntBox : Box<number> { } // concrete — threads a fixed type through
|
|
355
|
+
class Container<T> : Box<T> { // generic — threads its OWN T through
|
|
356
|
+
public T GetAgain() { return this.Get(); }
|
|
357
|
+
}
|
|
358
|
+
interface INumberContainer : IContainer<number> { } // interfaces work the same way
|
|
359
|
+
```
|
|
360
|
+
Member resolution (fields, methods, an inherited constructor via `base(...)` or an implicit
|
|
361
|
+
default) composes substitutions correctly however many generic-base links up the chain a
|
|
362
|
+
member was actually declared — `Container<number>.Get()` (declared on `Box<T>`, two links
|
|
363
|
+
away in a longer chain) resolves to `number`, not the abstract `T`. **v1 limit**: at most
|
|
364
|
+
one generic entry across a class/interface's *whole* base list (its superclass, or one
|
|
365
|
+
implemented/extended interface — never more than one) — `class Foo<T> : Box<T>,
|
|
366
|
+
IContainer<T>` is a compile error ("has more than one generic entry in its base list") even
|
|
367
|
+
though either half alone would work.
|
|
329
368
|
|
|
330
369
|
**Does not exist (v1 scope cuts, each deliberate)**:
|
|
331
370
|
- **Generic functions.** `T Identity<T>(T x)` doesn't parse — only classes and interfaces
|
|
332
371
|
take type parameters, not free functions/methods themselves (a method *inside* a
|
|
333
372
|
generic class can use that class's own type parameters freely, same as any other member).
|
|
334
|
-
- **Constraints.** No `T : IComparable` — a type parameter is always fully unconstrained,
|
|
335
|
-
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
373
|
- **Variance.** No `out`/`in`/covariance/contravariance — see invariance above.
|
|
341
374
|
|
|
342
375
|
## Nullable types — `T?`
|
|
@@ -414,11 +447,14 @@ KopScript-declared name exactly. Extern class members use real JS member names v
|
|
|
414
447
|
(camelCase, no rename mechanism). No inheritance modeling between two `extern class`
|
|
415
448
|
declarations — each stands alone.
|
|
416
449
|
|
|
417
|
-
`extern class` can carry
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
(`Box<number>`, arity/invariance
|
|
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.
|
|
422
458
|
|
|
423
459
|
**Never write `async` on an extern function/method signature** — declare its return type
|
|
424
460
|
as `task`/`task<T>` directly (`task<string> text();`, not `async task<string> text();`).
|
|
@@ -505,9 +541,10 @@ Locals/params: **camelCase**. This is convention, not enforced by the compiler.
|
|
|
505
541
|
|
|
506
542
|
## Does not exist (don't reach for these)
|
|
507
543
|
|
|
508
|
-
Generics beyond one or more
|
|
509
|
-
|
|
510
|
-
|
|
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 ·
|
|
511
548
|
a `let`/`var` keyword (locals are just `Type name = value;`) · decorators/annotations ·
|
|
512
549
|
reflection · type inference on declarations · ternary expression · union/tuple types ·
|
|
513
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,20 +255,49 @@ 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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
+
|
|
285
|
+
A class or interface *can* extend/implement a generic base, type arguments and all:
|
|
286
|
+
|
|
287
|
+
```ks
|
|
288
|
+
class IntBox : Box<number> { } // concrete: threads a fixed type through
|
|
289
|
+
class Container<T> : Box<T> { // generic: threads its OWN T through
|
|
290
|
+
public T GetAgain() { return this.Get(); }
|
|
291
|
+
}
|
|
292
|
+
Container<number> c = new Container<number>(5);
|
|
293
|
+
print(c.GetAgain()); // 5, and c.Value/c.Get() work too
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Member lookup (fields, methods, an inherited constructor) resolves correctly however many
|
|
297
|
+
generic-base links up the chain a member was actually declared, substituting all the way
|
|
298
|
+
down. **v1 limit**: at most one generic entry across a whole base list (the superclass, or
|
|
299
|
+
one implemented interface — not several at once) — `class Foo<T> : Box<T>, IContainer<T>`
|
|
300
|
+
is a compile error even though each half would work alone.
|
|
270
301
|
|
|
271
302
|
See `LLM.md`'s "Generics" section for the exhaustive rules if you're generating code
|
|
272
303
|
against this — nested generics (`Box<Box<number>>`, `Box<number>?[]`, `Pair<number,
|
|
@@ -370,10 +401,11 @@ consumes `Component`/`Router` themselves via `extern class ... from "kopular/...
|
|
|
370
401
|
proving `extern` works as a real cross-*package* boundary, not just for describing DOM
|
|
371
402
|
globals within a single project.
|
|
372
403
|
|
|
373
|
-
An `extern class` can carry its own type parameter(s),
|
|
374
|
-
`extern class
|
|
375
|
-
|
|
376
|
-
|
|
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
|
|
377
409
|
instantiated generically, not just per concrete type:
|
|
378
410
|
|
|
379
411
|
```ks
|
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,8 +229,11 @@ 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,
|
|
234
|
+
superclassTypeArgs: null,
|
|
207
235
|
interfaces: [],
|
|
236
|
+
interfaceTypeArgs: new Map(),
|
|
208
237
|
fields,
|
|
209
238
|
methods,
|
|
210
239
|
staticFields,
|
|
@@ -392,6 +421,14 @@ export class Checker {
|
|
|
392
421
|
else if (declaredParams && suppliedCount > 0 && suppliedCount !== declaredParams.length) {
|
|
393
422
|
this.diagnostics.error("KS4090", `Type '${type.name}' expects ${this.describeArity(declaredParams)}, got ${suppliedCount}`, line, col);
|
|
394
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
|
+
}
|
|
395
432
|
type.typeArgs?.forEach((a) => this.validateGenericArity(a, line, col));
|
|
396
433
|
return;
|
|
397
434
|
}
|
|
@@ -413,8 +450,11 @@ export class Checker {
|
|
|
413
450
|
if (typeParams.length === 0)
|
|
414
451
|
return fn();
|
|
415
452
|
const previous = typeParams.map((p) => this.namedTypes.get(p.name));
|
|
416
|
-
|
|
453
|
+
const previousConstraints = typeParams.map((p) => this.activeConstraints.get(p.name));
|
|
454
|
+
for (const p of typeParams) {
|
|
417
455
|
this.namedTypes.set(p.name, "typeParam");
|
|
456
|
+
this.activeConstraints.set(p.name, p.constraint);
|
|
457
|
+
}
|
|
418
458
|
try {
|
|
419
459
|
return fn();
|
|
420
460
|
}
|
|
@@ -425,6 +465,11 @@ export class Checker {
|
|
|
425
465
|
this.namedTypes.delete(p.name);
|
|
426
466
|
else
|
|
427
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);
|
|
428
473
|
});
|
|
429
474
|
}
|
|
430
475
|
}
|
|
@@ -459,6 +504,59 @@ export class Checker {
|
|
|
459
504
|
return type;
|
|
460
505
|
}
|
|
461
506
|
}
|
|
507
|
+
// Builds the bindings map for a generic reference (a `ClassType`/
|
|
508
|
+
// `InterfaceType`'s own `typeArgs`, zipped against its declared
|
|
509
|
+
// `typeParams`) — the "own bindings" every member-lookup/conformance
|
|
510
|
+
// check starts from. Null when the reference isn't actually generic
|
|
511
|
+
// (no declared params) or carries no type arguments (an arity error
|
|
512
|
+
// already diagnosed elsewhere — see validateGenericArity).
|
|
513
|
+
ownBindings(typeParams, typeArgs) {
|
|
514
|
+
if (typeParams.length === 0 || !typeArgs)
|
|
515
|
+
return null;
|
|
516
|
+
return new Map(typeParams.map((name, i) => [name, typeArgs[i]]));
|
|
517
|
+
}
|
|
518
|
+
// Maps `typeParams` to themselves, as abstract TypeParamTypes — used when
|
|
519
|
+
// resolving a base-list/interface type argument that may reference the
|
|
520
|
+
// *declaring* class/interface's own (still-abstract) type parameter,
|
|
521
|
+
// rather than a concrete instantiation (see checkInterfaceConformance and
|
|
522
|
+
// the base(...) call check in checkClassBodyInner).
|
|
523
|
+
identityBindings(typeParams) {
|
|
524
|
+
return new Map(typeParams.map((name) => [name, T.typeParamType(name)]));
|
|
525
|
+
}
|
|
526
|
+
// One step of substitution composition, walking from a class/interface
|
|
527
|
+
// ("owner") to one of its own generic base-list entries. `baseArgs` are
|
|
528
|
+
// the type arguments `owner` supplied to `baseName` in its own base list
|
|
529
|
+
// — still in `owner`'s own type-param-name space (may reference `owner`'s
|
|
530
|
+
// own type parameters, not yet concrete). `ownerBindings` is `owner`'s
|
|
531
|
+
// current bindings at the point of this lookup. Returns `baseName`'s own
|
|
532
|
+
// bindings map (its declared type-param names -> resolved types), ready
|
|
533
|
+
// for the next composition step or a final substituteTypeParams call.
|
|
534
|
+
composeBaseBindings(baseName, baseArgs, ownerBindings) {
|
|
535
|
+
const baseParams = this.genericTypeParams.get(baseName) ?? [];
|
|
536
|
+
const resolvedArgs = baseArgs.map((t) => this.substituteTypeParams(t, ownerBindings));
|
|
537
|
+
return new Map(baseParams.map((name, i) => [name, resolvedArgs[i]]));
|
|
538
|
+
}
|
|
539
|
+
// Starting from `className` with its own bindings (from ownBindings,
|
|
540
|
+
// above — empty if non-generic or no type arguments), walks up the
|
|
541
|
+
// superclass chain composing each generic link's stored
|
|
542
|
+
// superclassTypeArgs, until it reaches `targetOwner` (the ancestor that
|
|
543
|
+
// actually declared the member being resolved — see lookupField/
|
|
544
|
+
// lookupMethod/etc.'s `owner` result). Returns the bindings in
|
|
545
|
+
// `targetOwner`'s own type-param-name space. A non-generic link along the
|
|
546
|
+
// way (superclassTypeArgs null) resets to empty bindings, same as today's
|
|
547
|
+
// behavior of never substituting through a non-generic ancestor.
|
|
548
|
+
bindingsAtAncestor(className, ownBindings, targetOwner) {
|
|
549
|
+
let current = className;
|
|
550
|
+
let bindings = ownBindings;
|
|
551
|
+
while (current !== targetOwner) {
|
|
552
|
+
const info = this.classes.get(current);
|
|
553
|
+
if (!info || !info.superclass)
|
|
554
|
+
return bindings;
|
|
555
|
+
bindings = info.superclassTypeArgs ? this.composeBaseBindings(info.superclass, info.superclassTypeArgs, bindings) : new Map();
|
|
556
|
+
current = info.superclass;
|
|
557
|
+
}
|
|
558
|
+
return bindings;
|
|
559
|
+
}
|
|
462
560
|
registerEnum(decl) {
|
|
463
561
|
const members = new Map();
|
|
464
562
|
decl.members.forEach((name, index) => {
|
|
@@ -472,28 +570,38 @@ export class Checker {
|
|
|
472
570
|
this.recordHover(decl.line, decl.col, `enum ${decl.name}`);
|
|
473
571
|
}
|
|
474
572
|
registerInterface(decl) {
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
this.
|
|
492
|
-
|
|
573
|
+
const genericBaseCount = { count: 0 };
|
|
574
|
+
const { methods, bases, baseTypeArgs } = this.withTypeParamsInScope(decl.typeParams, () => {
|
|
575
|
+
const methods = decl.methods.map((m) => {
|
|
576
|
+
const params = m.params.map((p) => this.resolveType(p.type, m.line, m.col));
|
|
577
|
+
const returnType = this.resolveType(m.returnType, m.line, m.col);
|
|
578
|
+
this.recordHover(m.nameLine, m.nameCol, `${m.name}(${params.map(T.typeToString).join(", ")}): ${T.typeToString(returnType)}`);
|
|
579
|
+
return { name: m.name, params, returnType };
|
|
580
|
+
});
|
|
581
|
+
const bases = [];
|
|
582
|
+
const baseTypeArgs = new Map();
|
|
583
|
+
for (const entry of decl.baseList) {
|
|
584
|
+
if (this.namedTypes.get(entry.name) !== "interface") {
|
|
585
|
+
this.diagnostics.error("KS4012", `Interface '${decl.name}' can only extend other interfaces (unknown interface '${entry.name}')`, decl.line, decl.col);
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
bases.push(entry.name);
|
|
589
|
+
if (entry.typeArgs || this.genericTypeParams.has(entry.name)) {
|
|
590
|
+
const resolved = this.resolveBaseTypeArgs(decl.name, entry.name, entry.typeArgs, decl.line, decl.col, genericBaseCount);
|
|
591
|
+
if (resolved)
|
|
592
|
+
baseTypeArgs.set(entry.name, resolved);
|
|
593
|
+
}
|
|
493
594
|
}
|
|
494
|
-
bases
|
|
495
|
-
}
|
|
496
|
-
this.interfaces.set(decl.name, {
|
|
595
|
+
return { methods, bases, baseTypeArgs };
|
|
596
|
+
});
|
|
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
|
+
});
|
|
497
605
|
this.recordHover(decl.line, decl.col, decl.typeParams.length > 0 ? `interface ${decl.name}<${decl.typeParams.map((p) => p.name).join(", ")}>` : `interface ${decl.name}`);
|
|
498
606
|
}
|
|
499
607
|
checkInterfaceHierarchy(decl) {
|
|
@@ -517,16 +625,31 @@ export class Checker {
|
|
|
517
625
|
}
|
|
518
626
|
}
|
|
519
627
|
// All methods required to satisfy an interface: its own plus every
|
|
520
|
-
// transitively-inherited parent interface's (cycle-safe).
|
|
521
|
-
|
|
628
|
+
// transitively-inherited parent interface's (cycle-safe). `bindings` maps
|
|
629
|
+
// `interfaceName`'s own type-param names to resolved types (from
|
|
630
|
+
// ownBindings, at the root call — see its two call sites) — every
|
|
631
|
+
// returned signature is substituted through it, and a generic base link
|
|
632
|
+
// along the way (`interface IDerived<T> : IBase<T>`) composes its own
|
|
633
|
+
// stored baseTypeArgs through `bindings` before recursing, so a signature
|
|
634
|
+
// inherited from several generic-base links up still resolves correctly.
|
|
635
|
+
collectInterfaceMethods(interfaceName, bindings = new Map(), seen = new Set()) {
|
|
522
636
|
if (seen.has(interfaceName))
|
|
523
637
|
return [];
|
|
524
638
|
seen.add(interfaceName);
|
|
525
639
|
const info = this.interfaces.get(interfaceName);
|
|
526
640
|
if (!info)
|
|
527
641
|
return [];
|
|
528
|
-
const inherited = info.bases.flatMap((b) =>
|
|
529
|
-
|
|
642
|
+
const inherited = info.bases.flatMap((b) => {
|
|
643
|
+
const baseArgs = info.baseTypeArgs.get(b);
|
|
644
|
+
const baseBindings = baseArgs ? this.composeBaseBindings(b, baseArgs, bindings) : new Map();
|
|
645
|
+
return this.collectInterfaceMethods(b, baseBindings, seen);
|
|
646
|
+
});
|
|
647
|
+
const ownMethods = info.methods.map((m) => ({
|
|
648
|
+
name: m.name,
|
|
649
|
+
params: m.params.map((p) => this.substituteTypeParams(p, bindings)),
|
|
650
|
+
returnType: this.substituteTypeParams(m.returnType, bindings),
|
|
651
|
+
}));
|
|
652
|
+
return [...inherited, ...ownMethods];
|
|
530
653
|
}
|
|
531
654
|
interfaceExtends(sub, sup) {
|
|
532
655
|
if (sub === sup)
|
|
@@ -538,7 +661,8 @@ export class Checker {
|
|
|
538
661
|
}
|
|
539
662
|
registerClass(decl) {
|
|
540
663
|
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
|
|
664
|
+
const genericBaseCount = { count: 0 };
|
|
665
|
+
const { fields, staticFields, methods, staticMethods, ownCtorParams, superclass, superclassTypeArgs, interfaces, interfaceTypeArgs } = this.withTypeParamsInScope(decl.typeParams, () => {
|
|
542
666
|
const fields = new Map();
|
|
543
667
|
const staticFields = new Map();
|
|
544
668
|
for (const f of decl.fields) {
|
|
@@ -567,39 +691,47 @@ export class Checker {
|
|
|
567
691
|
const ownCtorParams = decl.constructor
|
|
568
692
|
? decl.constructor.params.map((p) => this.resolveType(p.type, decl.constructor.line, decl.constructor.col))
|
|
569
693
|
: null;
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
this.
|
|
694
|
+
// Base-list resolution runs in this same scope — so `Box<T>` in
|
|
695
|
+
// `class Container<T> : Box<T>` resolves its inner `T` as
|
|
696
|
+
// Container's own type parameter, exactly like a field type would.
|
|
697
|
+
let superclass = null;
|
|
698
|
+
let superclassTypeArgs = null;
|
|
699
|
+
const interfaces = [];
|
|
700
|
+
const interfaceTypeArgs = new Map();
|
|
701
|
+
for (const entry of decl.baseList) {
|
|
702
|
+
const kind = this.namedTypes.get(entry.name);
|
|
703
|
+
if (kind === "class") {
|
|
704
|
+
if (superclass !== null) {
|
|
705
|
+
this.diagnostics.error("KS4016", `Class '${decl.name}' cannot extend multiple classes ('${superclass}' and '${entry.name}')`, decl.line, decl.col);
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
superclass = entry.name;
|
|
709
|
+
if (entry.typeArgs || this.genericTypeParams.has(entry.name)) {
|
|
710
|
+
superclassTypeArgs = this.resolveBaseTypeArgs(decl.name, entry.name, entry.typeArgs, decl.line, decl.col, genericBaseCount);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
else if (kind === "interface") {
|
|
714
|
+
interfaces.push(entry.name);
|
|
715
|
+
if (entry.typeArgs || this.genericTypeParams.has(entry.name)) {
|
|
716
|
+
const resolved = this.resolveBaseTypeArgs(decl.name, entry.name, entry.typeArgs, decl.line, decl.col, genericBaseCount);
|
|
717
|
+
if (resolved)
|
|
718
|
+
interfaceTypeArgs.set(entry.name, resolved);
|
|
719
|
+
}
|
|
586
720
|
}
|
|
587
721
|
else {
|
|
588
|
-
|
|
722
|
+
this.diagnostics.error("KS4017", `Unknown base class or interface '${entry.name}'`, decl.line, decl.col);
|
|
589
723
|
}
|
|
590
724
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
}
|
|
594
|
-
else {
|
|
595
|
-
this.diagnostics.error("KS4017", `Unknown base class or interface '${baseName}'`, decl.line, decl.col);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
725
|
+
return { fields, staticFields, methods, staticMethods, ownCtorParams, superclass, superclassTypeArgs, interfaces, interfaceTypeArgs };
|
|
726
|
+
});
|
|
598
727
|
this.classes.set(decl.name, {
|
|
599
728
|
name: decl.name,
|
|
600
729
|
typeParams: decl.typeParams.map((p) => p.name),
|
|
730
|
+
typeParamConstraints: decl.typeParams.map((p) => p.constraint),
|
|
601
731
|
superclass,
|
|
732
|
+
superclassTypeArgs,
|
|
602
733
|
interfaces,
|
|
734
|
+
interfaceTypeArgs,
|
|
603
735
|
fields,
|
|
604
736
|
methods,
|
|
605
737
|
staticFields,
|
|
@@ -607,6 +739,43 @@ export class Checker {
|
|
|
607
739
|
ownCtorParams,
|
|
608
740
|
});
|
|
609
741
|
}
|
|
742
|
+
// Resolves a base-list entry's own type arguments against `baseName`'s
|
|
743
|
+
// declared arity — `typeArgNodes` is null when the entry was written bare
|
|
744
|
+
// (`: Box`, not `: Box<number>`). Must be called from inside the
|
|
745
|
+
// declaring class/interface's own withTypeParamsInScope, so a type
|
|
746
|
+
// argument that references the declaring type's own parameter (`Box<T>`
|
|
747
|
+
// in `class Container<T> : Box<T>`) resolves correctly. `genericBaseCount`
|
|
748
|
+
// is a shared mutable counter across one declaration's whole base list,
|
|
749
|
+
// enforcing v1's "at most one generic entry per base list" cut.
|
|
750
|
+
resolveBaseTypeArgs(declName, baseName, typeArgNodes, declLine, declCol, genericBaseCount) {
|
|
751
|
+
const declaredParams = this.genericTypeParams.get(baseName);
|
|
752
|
+
if (!declaredParams) {
|
|
753
|
+
if (typeArgNodes)
|
|
754
|
+
this.diagnostics.error("KS4092", `'${baseName}' is not generic — it doesn't take a type argument`, declLine, declCol);
|
|
755
|
+
return null;
|
|
756
|
+
}
|
|
757
|
+
if (!typeArgNodes) {
|
|
758
|
+
this.diagnostics.error("KS4093", `Generic base '${baseName}' requires ${this.describeArity(declaredParams)} (e.g. '${baseName}<${declaredParams.join(", ")}>')`, declLine, declCol);
|
|
759
|
+
return null;
|
|
760
|
+
}
|
|
761
|
+
if (typeArgNodes.length !== declaredParams.length) {
|
|
762
|
+
this.diagnostics.error("KS4094", `Generic base '${baseName}' expects ${this.describeArity(declaredParams)}, got ${typeArgNodes.length}`, declLine, declCol);
|
|
763
|
+
return null;
|
|
764
|
+
}
|
|
765
|
+
genericBaseCount.count++;
|
|
766
|
+
if (genericBaseCount.count > 1) {
|
|
767
|
+
this.diagnostics.error("KS4095", `'${declName}' has more than one generic entry in its base list — v1 supports at most one`, declLine, declCol);
|
|
768
|
+
}
|
|
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;
|
|
778
|
+
}
|
|
610
779
|
checkClassHierarchy(decl) {
|
|
611
780
|
const info = this.classes.get(decl.name);
|
|
612
781
|
this.checkBaseCall(decl, info);
|
|
@@ -684,10 +853,19 @@ export class Checker {
|
|
|
684
853
|
}
|
|
685
854
|
checkInterfaceConformance(decl) {
|
|
686
855
|
const classInfo = this.classes.get(decl.name);
|
|
856
|
+
// This is a conformance check against the class's own *declared*
|
|
857
|
+
// (still-abstract) signatures, not an instantiated use site, so a
|
|
858
|
+
// generic interface arg that references the class's own type param
|
|
859
|
+
// (`IContainer<T>` in `class Container<T> : IContainer<T>`) should
|
|
860
|
+
// compose down to that same abstract TypeParamType, matching how the
|
|
861
|
+
// class's own methods are stored — see identityBindings.
|
|
862
|
+
const selfBindings = this.identityBindings(classInfo.typeParams);
|
|
687
863
|
for (const ifaceName of classInfo.interfaces) {
|
|
688
864
|
if (!this.interfaces.has(ifaceName))
|
|
689
865
|
continue; // already reported as an unknown base type
|
|
690
|
-
|
|
866
|
+
const ifaceArgs = classInfo.interfaceTypeArgs.get(ifaceName);
|
|
867
|
+
const bindings = ifaceArgs ? this.composeBaseBindings(ifaceName, ifaceArgs, selfBindings) : new Map();
|
|
868
|
+
for (const sig of this.collectInterfaceMethods(ifaceName, bindings)) {
|
|
691
869
|
const found = this.lookupMethod(decl.name, sig.name);
|
|
692
870
|
if (!found) {
|
|
693
871
|
this.diagnostics.error("KS4026", `Class '${decl.name}' does not implement method '${sig.name}' required by interface '${ifaceName}'`, decl.line, decl.col);
|
|
@@ -727,6 +905,38 @@ export class Checker {
|
|
|
727
905
|
}
|
|
728
906
|
return false;
|
|
729
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
|
+
}
|
|
730
940
|
lookupField(className, fieldName) {
|
|
731
941
|
let current = className;
|
|
732
942
|
while (current) {
|
|
@@ -896,7 +1106,18 @@ export class Checker {
|
|
|
896
1106
|
// params but no `this` — matching real base()/super() semantics,
|
|
897
1107
|
// which must run before `this` becomes available.
|
|
898
1108
|
if (decl.constructor.baseArgs && info.superclass) {
|
|
899
|
-
|
|
1109
|
+
// One step from `decl` to its immediate superclass (Container's own
|
|
1110
|
+
// type params, still abstract, composed through whatever it
|
|
1111
|
+
// supplied Box in its base list — see identityBindings), then
|
|
1112
|
+
// however many further steps lookupCtorParams' own `owner` implies
|
|
1113
|
+
// (Box itself has no constructor of its own, inherits its
|
|
1114
|
+
// superclass's) via bindingsAtAncestor.
|
|
1115
|
+
const superBindings = info.superclassTypeArgs
|
|
1116
|
+
? this.composeBaseBindings(info.superclass, info.superclassTypeArgs, this.identityBindings(info.typeParams))
|
|
1117
|
+
: new Map();
|
|
1118
|
+
const { params: rawBaseCtorParams, owner: baseCtorOwner } = this.lookupCtorParams(info.superclass);
|
|
1119
|
+
const baseCtorBindings = this.bindingsAtAncestor(info.superclass, superBindings, baseCtorOwner);
|
|
1120
|
+
const baseCtorParams = rawBaseCtorParams.map((p) => this.substituteTypeParams(p, baseCtorBindings));
|
|
900
1121
|
const baseCtx = { returnType: T.VOID, currentClass: info, inConstructor: false, loopDepth: 0, isAsync: false };
|
|
901
1122
|
if (decl.constructor.baseArgs.length !== baseCtorParams.length) {
|
|
902
1123
|
this.diagnostics.error("KS4032", `Expected ${baseCtorParams.length} base constructor argument(s), got ${decl.constructor.baseArgs.length}`, decl.constructor.line, decl.constructor.col);
|
|
@@ -1496,13 +1717,24 @@ export class Checker {
|
|
|
1496
1717
|
return T.UNKNOWN;
|
|
1497
1718
|
}
|
|
1498
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
|
+
});
|
|
1499
1726
|
}
|
|
1500
1727
|
this.recordHover(expr.line, expr.col, typeArgs ? `class ${expr.className}<${typeArgs.map(T.typeToString).join(", ")}>` : `class ${expr.className}`);
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1728
|
+
// The constructor being called may be inherited from a generic
|
|
1729
|
+
// ancestor several base-list links up (e.g. `new NumberBox(5)` where
|
|
1730
|
+
// `NumberBox : Box<number>` has no constructor of its own) — compose
|
|
1731
|
+
// bindings all the way to whichever class's own constructor
|
|
1732
|
+
// lookupCtorParams actually found (bindingsAtAncestor is a no-op single
|
|
1733
|
+
// step when it's expr.className's own, the common case).
|
|
1734
|
+
const { params: rawCtorParams, owner: ctorOwner } = this.lookupCtorParams(expr.className);
|
|
1735
|
+
const ownCtorBindings = this.ownBindings(info.typeParams, typeArgs) ?? new Map();
|
|
1736
|
+
const ctorBindings = this.bindingsAtAncestor(expr.className, ownCtorBindings, ctorOwner);
|
|
1737
|
+
const ctorParams = rawCtorParams.map((p) => this.substituteTypeParams(p, ctorBindings));
|
|
1506
1738
|
if (expr.args.length !== ctorParams.length) {
|
|
1507
1739
|
this.diagnostics.error("KS4071", `Expected ${ctorParams.length} constructor argument(s), got ${expr.args.length}`, expr.line, expr.col);
|
|
1508
1740
|
}
|
|
@@ -1515,17 +1747,23 @@ export class Checker {
|
|
|
1515
1747
|
});
|
|
1516
1748
|
return T.classType(expr.className, typeArgs);
|
|
1517
1749
|
}
|
|
1750
|
+
// `owner` is which class up the chain actually declared the constructor
|
|
1751
|
+
// being inherited (itself, or the nearest ancestor with one) — needed so
|
|
1752
|
+
// a caller can compose the right type-parameter bindings when that
|
|
1753
|
+
// ancestor is a generic base (see bindingsAtAncestor). `params` is stored
|
|
1754
|
+
// abstractly in `owner`'s own type-param-name space, same as a field or
|
|
1755
|
+
// method's type would be.
|
|
1518
1756
|
lookupCtorParams(className) {
|
|
1519
1757
|
let current = className;
|
|
1520
1758
|
while (current) {
|
|
1521
1759
|
const info = this.classes.get(current);
|
|
1522
1760
|
if (!info)
|
|
1523
|
-
return [];
|
|
1761
|
+
return { params: [], owner: className };
|
|
1524
1762
|
if (info.ownCtorParams !== null)
|
|
1525
|
-
return info.ownCtorParams;
|
|
1763
|
+
return { params: info.ownCtorParams, owner: current };
|
|
1526
1764
|
current = info.superclass;
|
|
1527
1765
|
}
|
|
1528
|
-
return [];
|
|
1766
|
+
return { params: [], owner: className };
|
|
1529
1767
|
}
|
|
1530
1768
|
// Array stdlib with a fixed (non-polymorphic) signature, given the
|
|
1531
1769
|
// array's own element type — everything except Map, which checkCall
|
|
@@ -1663,13 +1901,14 @@ export class Checker {
|
|
|
1663
1901
|
return { type: T.UNKNOWN, methodInfo: null };
|
|
1664
1902
|
}
|
|
1665
1903
|
if (objectType.kind === "class") {
|
|
1666
|
-
//
|
|
1667
|
-
//
|
|
1668
|
-
//
|
|
1669
|
-
//
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1904
|
+
// A member found on an *ancestor* (owner !== objectType.name — v1 now
|
|
1905
|
+
// supports a generic superclass, see registerClass/
|
|
1906
|
+
// bindingsAtAncestor) needs bindings composed through however many
|
|
1907
|
+
// generic base links separate objectType from that ancestor, not just
|
|
1908
|
+
// objectType's own type arguments — bindingsAtAncestor does that walk
|
|
1909
|
+
// (a no-op single step when owner IS objectType.name, the common
|
|
1910
|
+
// case, same result as before this existed).
|
|
1911
|
+
const ownBindings = this.ownBindings(this.genericTypeParams.get(objectType.name) ?? [], objectType.typeArgs) ?? new Map();
|
|
1673
1912
|
const field = this.lookupField(objectType.name, expr.property);
|
|
1674
1913
|
if (field) {
|
|
1675
1914
|
this.checkAccessibility(field.info.visibility, field.owner, ctx, expr.property, expr.line, expr.col);
|
|
@@ -1679,33 +1918,52 @@ export class Checker {
|
|
|
1679
1918
|
this.diagnostics.error("KS4082", `'${expr.property}' has no setter and can only be assigned within ${field.owner}'s constructor`, expr.line, expr.col);
|
|
1680
1919
|
}
|
|
1681
1920
|
}
|
|
1682
|
-
|
|
1921
|
+
const bindings = this.bindingsAtAncestor(objectType.name, ownBindings, field.owner);
|
|
1922
|
+
return { type: this.substituteTypeParams(field.info.type, bindings), methodInfo: null };
|
|
1683
1923
|
}
|
|
1684
1924
|
const method = this.lookupMethod(objectType.name, expr.property);
|
|
1685
1925
|
if (method) {
|
|
1686
1926
|
this.checkAccessibility(method.info.visibility, method.owner, ctx, expr.property, expr.line, expr.col);
|
|
1687
|
-
const
|
|
1927
|
+
const bindings = this.bindingsAtAncestor(objectType.name, ownBindings, method.owner);
|
|
1928
|
+
const info = {
|
|
1929
|
+
...method.info,
|
|
1930
|
+
params: method.info.params.map((p) => this.substituteTypeParams(p, bindings)),
|
|
1931
|
+
returnType: this.substituteTypeParams(method.info.returnType, bindings),
|
|
1932
|
+
};
|
|
1688
1933
|
return { type: info.returnType, methodInfo: info };
|
|
1689
1934
|
}
|
|
1690
1935
|
this.diagnostics.error("KS4083", `Class '${objectType.name}' has no member '${expr.property}'`, expr.line, expr.col);
|
|
1691
1936
|
return { type: T.UNKNOWN, methodInfo: null };
|
|
1692
1937
|
}
|
|
1693
1938
|
if (objectType.kind === "interface") {
|
|
1694
|
-
|
|
1939
|
+
// collectInterfaceMethods itself now substitutes through `bindings` —
|
|
1940
|
+
// including across a generic base-interface link — so passing
|
|
1941
|
+
// objectType's own bindings once at the root is enough; no separate
|
|
1942
|
+
// re-substitution needed here (see its own header comment).
|
|
1943
|
+
const ownBindings = this.ownBindings(this.genericTypeParams.get(objectType.name) ?? [], objectType.typeArgs) ?? new Map();
|
|
1944
|
+
const sig = this.collectInterfaceMethods(objectType.name, ownBindings).find((m) => m.name === expr.property);
|
|
1695
1945
|
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 } };
|
|
1946
|
+
return { type: sig.returnType, methodInfo: { params: sig.params, returnType: sig.returnType, visibility: "public", isVirtual: false, isOverride: false } };
|
|
1705
1947
|
}
|
|
1706
1948
|
this.diagnostics.error("KS4084", `Interface '${objectType.name}' has no member '${expr.property}'`, expr.line, expr.col);
|
|
1707
1949
|
return { type: T.UNKNOWN, methodInfo: null };
|
|
1708
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
|
+
}
|
|
1709
1967
|
if (objectType.kind !== "unknown") {
|
|
1710
1968
|
this.diagnostics.error("KS4085", `Cannot access member '${expr.property}' on type '${T.typeToString(objectType)}'`, expr.line, expr.col);
|
|
1711
1969
|
}
|
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
|
@@ -243,21 +243,40 @@ 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;
|
|
260
264
|
}
|
|
265
|
+
// `Bar<T>` or `IBaz` — one base-list entry, with or without its own type
|
|
266
|
+
// arguments. Used by both class and interface base lists.
|
|
267
|
+
parseBaseListEntry() {
|
|
268
|
+
const name = this.consume(TokenKind.Identifier, "Expected base class or interface name").lexeme;
|
|
269
|
+
let typeArgs = null;
|
|
270
|
+
if (this.check(TokenKind.Lt)) {
|
|
271
|
+
this.advance();
|
|
272
|
+
typeArgs = [this.parseType()];
|
|
273
|
+
while (this.match(TokenKind.Comma)) {
|
|
274
|
+
typeArgs.push(this.parseType());
|
|
275
|
+
}
|
|
276
|
+
this.consume(TokenKind.Gt, "Expected '>' after type argument list");
|
|
277
|
+
}
|
|
278
|
+
return { name, typeArgs };
|
|
279
|
+
}
|
|
261
280
|
parseClassDecl(isExported) {
|
|
262
281
|
const start = this.advance(); // 'class'
|
|
263
282
|
const name = this.consume(TokenKind.Identifier, "Expected class name").lexeme;
|
|
@@ -265,7 +284,7 @@ export class Parser {
|
|
|
265
284
|
const baseList = [];
|
|
266
285
|
if (this.match(TokenKind.Colon)) {
|
|
267
286
|
do {
|
|
268
|
-
baseList.push(this.
|
|
287
|
+
baseList.push(this.parseBaseListEntry());
|
|
269
288
|
} while (this.match(TokenKind.Comma));
|
|
270
289
|
}
|
|
271
290
|
this.consume(TokenKind.LBrace, "Expected '{' before class body");
|
|
@@ -426,7 +445,7 @@ export class Parser {
|
|
|
426
445
|
const baseList = [];
|
|
427
446
|
if (this.match(TokenKind.Colon)) {
|
|
428
447
|
do {
|
|
429
|
-
baseList.push(this.
|
|
448
|
+
baseList.push(this.parseBaseListEntry());
|
|
430
449
|
} while (this.match(TokenKind.Comma));
|
|
431
450
|
}
|
|
432
451
|
this.consume(TokenKind.LBrace, "Expected '{' before interface body");
|
package/dist/printer.js
CHANGED
|
@@ -118,6 +118,15 @@ 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
|
+
}
|
|
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
|
+
}
|
|
121
130
|
// ---------- top-level declarations ----------
|
|
122
131
|
printStatement(stmt, indent) {
|
|
123
132
|
const pad = indentStr(indent);
|
|
@@ -212,8 +221,8 @@ export class Printer {
|
|
|
212
221
|
printClass(decl, indent) {
|
|
213
222
|
const pad = indentStr(indent);
|
|
214
223
|
const memberPad = indentStr(indent + 1);
|
|
215
|
-
const nameWithTypeParam =
|
|
216
|
-
const header = decl.baseList.length > 0 ? `class ${nameWithTypeParam} : ${decl.baseList.join(", ")} {` : `class ${nameWithTypeParam} {`;
|
|
224
|
+
const nameWithTypeParam = this.printTypeParamName(decl);
|
|
225
|
+
const header = decl.baseList.length > 0 ? `class ${nameWithTypeParam} : ${decl.baseList.map((b) => this.printBaseListEntry(b)).join(", ")} {` : `class ${nameWithTypeParam} {`;
|
|
217
226
|
const prefix = decl.isExported ? "" : "private ";
|
|
218
227
|
const memberParts = [];
|
|
219
228
|
const pushMember = (node, text) => {
|
|
@@ -250,8 +259,8 @@ export class Printer {
|
|
|
250
259
|
printInterface(decl, indent) {
|
|
251
260
|
const pad = indentStr(indent);
|
|
252
261
|
const memberPad = indentStr(indent + 1);
|
|
253
|
-
const nameWithTypeParam =
|
|
254
|
-
const header = decl.baseList.length > 0 ? `interface ${nameWithTypeParam} : ${decl.baseList.join(", ")} {` : `interface ${nameWithTypeParam} {`;
|
|
262
|
+
const nameWithTypeParam = this.printTypeParamName(decl);
|
|
263
|
+
const header = decl.baseList.length > 0 ? `interface ${nameWithTypeParam} : ${decl.baseList.map((b) => this.printBaseListEntry(b)).join(", ")} {` : `interface ${nameWithTypeParam} {`;
|
|
255
264
|
const prefix = decl.isExported ? "" : "private ";
|
|
256
265
|
if (decl.methods.length === 0)
|
|
257
266
|
return `${pad}${prefix}${header}\n${pad}}`;
|
|
@@ -279,7 +288,7 @@ export class Printer {
|
|
|
279
288
|
const pad = indentStr(indent);
|
|
280
289
|
const memberPad = indentStr(indent + 1);
|
|
281
290
|
const prefix = decl.isExported ? "" : "private ";
|
|
282
|
-
const nameWithTypeParam =
|
|
291
|
+
const nameWithTypeParam = this.printTypeParamName(decl);
|
|
283
292
|
const lines = [];
|
|
284
293
|
if (decl.hasConstructor)
|
|
285
294
|
lines.push(`${memberPad}constructor(${this.printParams(decl.ctorParams)});`);
|
package/package.json
CHANGED