kopscript 0.11.0 → 0.12.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 +37 -25
- package/README.md +38 -27
- package/dist/checker.js +113 -94
- package/dist/parser.js +34 -22
- package/dist/printer.js +3 -3
- package/dist/types.js +29 -24
- package/package.json +1 -1
package/LLM.md
CHANGED
|
@@ -133,8 +133,8 @@ Dog d = new Dog("Rex");
|
|
|
133
133
|
the interface in the base list and provide matching public methods, checked at compile time.
|
|
134
134
|
- No nested classes/functions.
|
|
135
135
|
- No multiple inheritance (at most one class in the base list; the rest must be interfaces).
|
|
136
|
-
- `class Box<T> { ... }`
|
|
137
|
-
full (deliberately scoped-down) rules.
|
|
136
|
+
- `class Box<T> { ... }` / `class Pair<K, V> { ... }` — one or more type parameters are
|
|
137
|
+
allowed; see Generics below for the full (deliberately scoped-down) rules.
|
|
138
138
|
|
|
139
139
|
### Interfaces
|
|
140
140
|
|
|
@@ -145,7 +145,7 @@ interface IShape {
|
|
|
145
145
|
interface INamedShape : IShape { // interfaces can extend other interfaces (not classes)
|
|
146
146
|
string GetName();
|
|
147
147
|
}
|
|
148
|
-
interface IContainer<T> { // interfaces take
|
|
148
|
+
interface IContainer<T> { // interfaces take type parameters too — see Generics
|
|
149
149
|
T Get();
|
|
150
150
|
}
|
|
151
151
|
```
|
|
@@ -281,7 +281,7 @@ Not a use of user-declared generics (see below) — `state<T>` is one of exactly
|
|
|
281
281
|
hardcoded parametrized forms in the type system (the other is `task<T>`), each with only
|
|
282
282
|
ever one built-in meaning, unlike a real generic class/interface.
|
|
283
283
|
|
|
284
|
-
## Generics —
|
|
284
|
+
## Generics — one or more type parameters on classes/interfaces
|
|
285
285
|
|
|
286
286
|
```ks
|
|
287
287
|
class Box<T> {
|
|
@@ -293,34 +293,46 @@ class Box<T> {
|
|
|
293
293
|
Box<number> nb = new Box<number>(5); // type argument required on both the type AND `new`
|
|
294
294
|
Box<string> sb = new Box<string>("hi");
|
|
295
295
|
print(nb.Get()); // 5
|
|
296
|
+
|
|
297
|
+
class Pair<K, V> {
|
|
298
|
+
public K First;
|
|
299
|
+
public V Second;
|
|
300
|
+
constructor(K k, V v) { this.First = k; this.Second = v; }
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
Pair<number, string> p = new Pair<number, string>(1, "a");
|
|
296
304
|
```
|
|
297
305
|
|
|
298
|
-
One unconstrained, invariant type
|
|
299
|
-
the `extern` section below)
|
|
306
|
+
One or more unconstrained, invariant type parameters per class/interface (real *or*
|
|
307
|
+
`extern` — see the `extern` section below), comma-separated: `<T>`, `<K, V>`, `<A, B, C>`,
|
|
308
|
+
however many the declaration needs.
|
|
300
309
|
Erased at codegen (JS has no generics either; `Box<number>` and `Box<string>` both
|
|
301
310
|
compile to the exact same plain `class Box`), so there's no runtime cost and no way to
|
|
302
|
-
inspect
|
|
311
|
+
inspect a type parameter at runtime.
|
|
303
312
|
|
|
304
313
|
**Rules:**
|
|
305
|
-
- The type argument
|
|
306
|
-
type (`Box<number>`) and separately on `new` (`new Box<number>(...)`); a bare
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
314
|
+
- The type argument(s) are **required everywhere** a generic type is named — on the
|
|
315
|
+
variable type (`Box<number>`) and separately on `new` (`new Box<number>(...)`); a bare
|
|
316
|
+
`Box`/`Pair` (no arguments) is a compile error, not "any"/inferred. The count must match
|
|
317
|
+
the declaration exactly — `Pair<number>` (too few) and `Pair<number, string, bool>` (too
|
|
318
|
+
many) are both arity errors, not truncated/padded.
|
|
319
|
+
- **Invariant, per slot**: `Box<Dog>` is not assignable to `Box<Animal>` even if `Dog :
|
|
320
|
+
Animal`, and `Pair<number, string>` is not assignable to `Pair<string, number>` — every
|
|
321
|
+
type argument must match exactly, in the same position, not just be compatible.
|
|
322
|
+
- Nesting works: `Box<Box<number>>`, `Box<number[]>`, `Box<number>?`, `Box<number>?[]`,
|
|
323
|
+
`Pair<number, Pair<string, bool>>`.
|
|
324
|
+
- Inside the class/interface's own body, each type parameter is fully abstract — you can
|
|
325
|
+
store it, return it, pass it around, but (unconstrained) you **cannot call any member on
|
|
326
|
+
a bare type-parameter value** (`this.value.Foo()` where `value: T` is a compile error:
|
|
327
|
+
"Cannot access member 'Foo' on type 'T'"). This is correct, not a bug — same as an
|
|
328
|
+
unconstrained type parameter in C#/Java/TypeScript.
|
|
316
329
|
|
|
317
330
|
**Does not exist (v1 scope cuts, each deliberate)**:
|
|
318
331
|
- **Generic functions.** `T Identity<T>(T x)` doesn't parse — only classes and interfaces
|
|
319
|
-
take
|
|
320
|
-
generic class can use that class's own
|
|
321
|
-
- **Multiple type parameters.** No `Map<K, V>` — exactly one `<T>` or none.
|
|
332
|
+
take type parameters, not free functions/methods themselves (a method *inside* a
|
|
333
|
+
generic class can use that class's own type parameters freely, same as any other member).
|
|
322
334
|
- **Constraints.** No `T : IComparable` — a type parameter is always fully unconstrained,
|
|
323
|
-
which is why you can't call members on a bare
|
|
335
|
+
which is why you can't call members on a bare one (see above).
|
|
324
336
|
- **Generic inheritance.** A class/interface's base list can only name a *non-generic*
|
|
325
337
|
type — `class Foo : Box<number>` and even `class Foo<T> : SomeGenericBase<T>` are both
|
|
326
338
|
compile errors ("cannot extend/implement generic type '...' — not supported in v1"). A
|
|
@@ -493,9 +505,9 @@ Locals/params: **camelCase**. This is convention, not enforced by the compiler.
|
|
|
493
505
|
|
|
494
506
|
## Does not exist (don't reach for these)
|
|
495
507
|
|
|
496
|
-
Generics beyond
|
|
497
|
-
|
|
498
|
-
|
|
508
|
+
Generics beyond one or more unconstrained type parameters (no `T : IFoo` constraints, no
|
|
509
|
+
generic functions, no generic inheritance, no variance — see Generics above for what *is*
|
|
510
|
+
supported) · `any`/`unknown` annotations ·
|
|
499
511
|
a `let`/`var` keyword (locals are just `Type name = value;`) · decorators/annotations ·
|
|
500
512
|
reflection · type inference on declarations · ternary expression · union/tuple types ·
|
|
501
513
|
**object-literal syntax** (`{ key: value }` as a value — this doesn't exist *anywhere* in
|
package/README.md
CHANGED
|
@@ -22,9 +22,9 @@ 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**:
|
|
26
|
-
interfaces (`class Box<T> { public T Value; }`) — erased at
|
|
27
|
-
cost, the same way `task<T>`/`state<T>` already are.
|
|
25
|
+
- **Generics**: one or more unconstrained, invariant type parameters on classes and
|
|
26
|
+
interfaces (`class Box<T> { public T Value; }`, `class Pair<K, V> { ... }`) — erased at
|
|
27
|
+
codegen with zero runtime cost, the same way `task<T>`/`state<T>` already are.
|
|
28
28
|
- **Nullable types with compiler-enforced null-checking**: `T?` (`string?`, `Dog?`,
|
|
29
29
|
`number[]?`) — a `T?` can't be used where a `T` is expected without an `if (x != null)`
|
|
30
30
|
check first (checked statically, not just at runtime), and comparing a value that can
|
|
@@ -221,7 +221,7 @@ void Announce(ISpeaker s) {
|
|
|
221
221
|
|
|
222
222
|
### Generics
|
|
223
223
|
|
|
224
|
-
Classes and interfaces can take
|
|
224
|
+
Classes and interfaces can take one or more type parameters:
|
|
225
225
|
|
|
226
226
|
```ks
|
|
227
227
|
class Box<T> {
|
|
@@ -233,34 +233,45 @@ class Box<T> {
|
|
|
233
233
|
Box<number> nb = new Box<number>(5);
|
|
234
234
|
Box<string> sb = new Box<string>("hi");
|
|
235
235
|
print(nb.Get()); // 5
|
|
236
|
+
|
|
237
|
+
class Pair<K, V> {
|
|
238
|
+
public K First;
|
|
239
|
+
public V Second;
|
|
240
|
+
constructor(K k, V v) { this.First = k; this.Second = v; }
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
Pair<number, string> p = new Pair<number, string>(1, "a");
|
|
236
244
|
```
|
|
237
245
|
|
|
238
|
-
The type argument
|
|
246
|
+
The type argument(s) are required everywhere the generic type is named — on the variable's
|
|
239
247
|
declared type *and* separately on `new` (`Box<number> nb = new Box<number>(5);`, not
|
|
240
|
-
just one or the other)
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
more later
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
call any member on a bare `T` value
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
`Dog
|
|
248
|
+
just one or the other) — and the count must match the declaration exactly (`Pair<number>`
|
|
249
|
+
alone, or `Pair<number, string, bool>`, are both arity errors). Generics are erased at
|
|
250
|
+
codegen exactly like `task<T>`/`state<T>` already are — `Box<number>` and `Box<string>`
|
|
251
|
+
compile to the identical plain `class Box`, so there's no runtime cost.
|
|
252
|
+
|
|
253
|
+
Still a deliberately small slice of generics, not a scaled-down promise of more later
|
|
254
|
+
inside v1:
|
|
255
|
+
|
|
256
|
+
- **Invariant, unconstrained.** No `T : ISomething` (constraints) — and because a type
|
|
257
|
+
parameter is fully unconstrained, you can't call any member on a bare `T`/`K`/`V` value
|
|
258
|
+
inside the generic class's own body (that's correct behavior, the same restriction
|
|
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.
|
|
253
263
|
- **No generic functions.** `T Identity<T>(T x)` isn't supported — only classes and
|
|
254
|
-
interfaces take
|
|
255
|
-
class's own
|
|
264
|
+
interfaces take type parameters. A method *inside* a generic class can still use that
|
|
265
|
+
class's own type parameters freely; it's just not introducing one of its own.
|
|
256
266
|
- **No generic inheritance.** A class or interface's base list can only name a
|
|
257
267
|
*non-generic* type. `class Foo : Box<number>` and `class Foo<T> : SomeBase<T>` are both
|
|
258
268
|
compile errors — a generic class can still extend/implement ordinary non-generic bases
|
|
259
269
|
normally, it just can't be the one on either side of a generic base relationship.
|
|
260
270
|
|
|
261
271
|
See `LLM.md`'s "Generics" section for the exhaustive rules if you're generating code
|
|
262
|
-
against this — nested generics (`Box<Box<number>>`, `Box<number>?[]`
|
|
263
|
-
interfaces used as standalone parameter types both work
|
|
272
|
+
against this — nested generics (`Box<Box<number>>`, `Box<number>?[]`, `Pair<number,
|
|
273
|
+
Pair<string, bool>>`) and generic interfaces used as standalone parameter types both work
|
|
274
|
+
and are covered there.
|
|
264
275
|
|
|
265
276
|
### Enums
|
|
266
277
|
|
|
@@ -359,11 +370,11 @@ consumes `Component`/`Router` themselves via `extern class ... from "kopular/...
|
|
|
359
370
|
proving `extern` works as a real cross-*package* boundary, not just for describing DOM
|
|
360
371
|
globals within a single project.
|
|
361
372
|
|
|
362
|
-
An `extern class` can carry its own type parameter, `extern class Box<T> { ... }`
|
|
363
|
-
exactly the same
|
|
364
|
-
above: invariant, unconstrained, erased, can't appear in a base list), so a
|
|
365
|
-
from another package (e.g. Kopular's `FormField<T>`) can be described and
|
|
366
|
-
generically, not just per concrete type:
|
|
373
|
+
An `extern class` can carry its own type parameter(s), `extern class Box<T> { ... }` or
|
|
374
|
+
`extern class Pair<K, V> { ... }` — exactly the same rules as a real generic class (see
|
|
375
|
+
"Generics" above: invariant, unconstrained, erased, can't appear in a base list), so a
|
|
376
|
+
generic type from another package (e.g. Kopular's `FormField<T>`) can be described and
|
|
377
|
+
instantiated generically, not just per concrete type:
|
|
367
378
|
|
|
368
379
|
```ks
|
|
369
380
|
extern class Box<T> {
|
package/dist/checker.js
CHANGED
|
@@ -56,11 +56,13 @@ export class Checker {
|
|
|
56
56
|
this.functions = new Map();
|
|
57
57
|
this.externValues = new Map();
|
|
58
58
|
this.namedTypes = new Map();
|
|
59
|
-
// className/interfaceName -> its type
|
|
60
|
-
// for every generic class/interface —
|
|
61
|
-
// resolution runs, specifically so a
|
|
62
|
-
// referencing generic class B, declared
|
|
63
|
-
// resolves B's genericity correctly.
|
|
59
|
+
// className/interfaceName -> its type parameters' names, in declared order
|
|
60
|
+
// (e.g. "Pair" -> ["K", "V"]), for every generic class/interface —
|
|
61
|
+
// populated up front, before any type resolution runs, specifically so a
|
|
62
|
+
// forward reference (class A's field referencing generic class B, declared
|
|
63
|
+
// later in the same file) still resolves B's genericity correctly. Also
|
|
64
|
+
// doubles as the arity table (its length is how many type arguments a
|
|
65
|
+
// reference to that name must supply). See resolveType/validateGenericArity.
|
|
64
66
|
this.genericTypeParams = new Map();
|
|
65
67
|
this.importedNames = new Set();
|
|
66
68
|
this.rawContents = new Map();
|
|
@@ -73,13 +75,13 @@ export class Checker {
|
|
|
73
75
|
}
|
|
74
76
|
for (const [name, info] of this.imports.classes) {
|
|
75
77
|
this.classes.set(name, info);
|
|
76
|
-
if (info.
|
|
77
|
-
this.genericTypeParams.set(name, info.
|
|
78
|
+
if (info.typeParams.length > 0)
|
|
79
|
+
this.genericTypeParams.set(name, info.typeParams);
|
|
78
80
|
}
|
|
79
81
|
for (const [name, info] of this.imports.interfaces) {
|
|
80
82
|
this.interfaces.set(name, info);
|
|
81
|
-
if (info.
|
|
82
|
-
this.genericTypeParams.set(name, info.
|
|
83
|
+
if (info.typeParams.length > 0)
|
|
84
|
+
this.genericTypeParams.set(name, info.typeParams);
|
|
83
85
|
}
|
|
84
86
|
for (const [name, info] of this.imports.enums)
|
|
85
87
|
this.enums.set(name, info);
|
|
@@ -109,14 +111,14 @@ export class Checker {
|
|
|
109
111
|
for (const c of externClassDecls)
|
|
110
112
|
this.namedTypes.set(c.name, "class");
|
|
111
113
|
for (const c of classDecls)
|
|
112
|
-
if (c.
|
|
113
|
-
this.genericTypeParams.set(c.name, c.
|
|
114
|
+
if (c.typeParams.length > 0)
|
|
115
|
+
this.genericTypeParams.set(c.name, c.typeParams.map((p) => p.name));
|
|
114
116
|
for (const i of interfaceDecls)
|
|
115
|
-
if (i.
|
|
116
|
-
this.genericTypeParams.set(i.name, i.
|
|
117
|
+
if (i.typeParams.length > 0)
|
|
118
|
+
this.genericTypeParams.set(i.name, i.typeParams.map((p) => p.name));
|
|
117
119
|
for (const c of externClassDecls)
|
|
118
|
-
if (c.
|
|
119
|
-
this.genericTypeParams.set(c.name, c.
|
|
120
|
+
if (c.typeParams.length > 0)
|
|
121
|
+
this.genericTypeParams.set(c.name, c.typeParams.map((p) => p.name));
|
|
120
122
|
for (const e of enumDecls)
|
|
121
123
|
this.registerEnum(e);
|
|
122
124
|
for (const i of interfaceDecls)
|
|
@@ -172,11 +174,11 @@ export class Checker {
|
|
|
172
174
|
});
|
|
173
175
|
}
|
|
174
176
|
registerExternClass(decl) {
|
|
175
|
-
// Same
|
|
176
|
-
// signature (fields/methods/ctor params) — an extern class's own
|
|
177
|
-
//
|
|
178
|
-
// resolved, e.g. `state<T> Value;` in an `extern class FormField<T>`.
|
|
179
|
-
const { fields, staticFields, methods, staticMethods, ownCtorParams } = this.
|
|
177
|
+
// Same withTypeParamsInScope treatment registerClass gives a real class's
|
|
178
|
+
// signature (fields/methods/ctor params) — an extern class's own type
|
|
179
|
+
// params need to resolve the same way while its declared members are
|
|
180
|
+
// being resolved, e.g. `state<T> Value;` in an `extern class FormField<T>`.
|
|
181
|
+
const { fields, staticFields, methods, staticMethods, ownCtorParams } = this.withTypeParamsInScope(decl.typeParams, () => {
|
|
180
182
|
const fields = new Map();
|
|
181
183
|
const staticFields = new Map();
|
|
182
184
|
for (const p of decl.properties) {
|
|
@@ -200,7 +202,7 @@ export class Checker {
|
|
|
200
202
|
});
|
|
201
203
|
this.classes.set(decl.name, {
|
|
202
204
|
name: decl.name,
|
|
203
|
-
|
|
205
|
+
typeParams: decl.typeParams.map((p) => p.name),
|
|
204
206
|
superclass: null,
|
|
205
207
|
interfaces: [],
|
|
206
208
|
fields,
|
|
@@ -341,8 +343,9 @@ export class Checker {
|
|
|
341
343
|
});
|
|
342
344
|
if (!resolved) {
|
|
343
345
|
const name = node.kind === "NamedType" ? node.name : "[]";
|
|
344
|
-
|
|
345
|
-
|
|
346
|
+
const declaredParams = this.genericTypeParams.get(name);
|
|
347
|
+
if (name !== "[]" && declaredParams && !node.typeArgs) {
|
|
348
|
+
this.diagnostics.error("KS4007", `Generic type '${name}' requires ${this.describeArity(declaredParams)} (e.g. '${name}<${declaredParams.join(", ")}>')`, line, col);
|
|
346
349
|
}
|
|
347
350
|
else {
|
|
348
351
|
this.diagnostics.error("KS4008", `Unknown type '${name}'`, line, col);
|
|
@@ -378,70 +381,80 @@ export class Checker {
|
|
|
378
381
|
return;
|
|
379
382
|
case "class":
|
|
380
383
|
case "interface": {
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
+
const declaredParams = this.genericTypeParams.get(type.name);
|
|
385
|
+
const suppliedCount = type.typeArgs?.length ?? 0;
|
|
386
|
+
if (declaredParams && suppliedCount === 0) {
|
|
387
|
+
this.diagnostics.error("KS4009", `Generic type '${type.name}' requires ${this.describeArity(declaredParams)} (e.g. '${type.name}<${declaredParams.join(", ")}>')`, line, col);
|
|
384
388
|
}
|
|
385
|
-
else if (!
|
|
389
|
+
else if (!declaredParams && suppliedCount > 0) {
|
|
386
390
|
this.diagnostics.error("KS4010", `Type '${type.name}' is not generic — it doesn't take a type argument`, line, col);
|
|
387
391
|
}
|
|
388
|
-
if (
|
|
389
|
-
this.
|
|
392
|
+
else if (declaredParams && suppliedCount > 0 && suppliedCount !== declaredParams.length) {
|
|
393
|
+
this.diagnostics.error("KS4090", `Type '${type.name}' expects ${this.describeArity(declaredParams)}, got ${suppliedCount}`, line, col);
|
|
394
|
+
}
|
|
395
|
+
type.typeArgs?.forEach((a) => this.validateGenericArity(a, line, col));
|
|
390
396
|
return;
|
|
391
397
|
}
|
|
392
398
|
default:
|
|
393
399
|
return;
|
|
394
400
|
}
|
|
395
401
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
//
|
|
400
|
-
//
|
|
401
|
-
//
|
|
402
|
-
//
|
|
403
|
-
|
|
404
|
-
|
|
402
|
+
describeArity(params) {
|
|
403
|
+
return params.length === 1 ? "a type argument" : `${params.length} type arguments`;
|
|
404
|
+
}
|
|
405
|
+
// Temporarily makes each of `typeParams` (a class/interface/function's own
|
|
406
|
+
// type parameters, e.g. ["K", "V"]) resolve as a TypeParamType, for the
|
|
407
|
+
// duration of `fn` — used only while registering that declaration's own
|
|
408
|
+
// signature (fields, methods, ctor/function params), so `T Value;`/`K
|
|
409
|
+
// key;` resolve correctly. Scoped this narrowly (set, run, delete/
|
|
410
|
+
// restore) rather than left in `namedTypes` permanently, since it's only
|
|
411
|
+
// ever meaningful inside that one declaration's own body.
|
|
412
|
+
withTypeParamsInScope(typeParams, fn) {
|
|
413
|
+
if (typeParams.length === 0)
|
|
405
414
|
return fn();
|
|
406
|
-
const previous = this.namedTypes.get(
|
|
407
|
-
|
|
415
|
+
const previous = typeParams.map((p) => this.namedTypes.get(p.name));
|
|
416
|
+
for (const p of typeParams)
|
|
417
|
+
this.namedTypes.set(p.name, "typeParam");
|
|
408
418
|
try {
|
|
409
419
|
return fn();
|
|
410
420
|
}
|
|
411
421
|
finally {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
//
|
|
422
|
-
//
|
|
423
|
-
//
|
|
424
|
-
|
|
422
|
+
typeParams.forEach((p, i) => {
|
|
423
|
+
const prev = previous[i];
|
|
424
|
+
if (prev === undefined)
|
|
425
|
+
this.namedTypes.delete(p.name);
|
|
426
|
+
else
|
|
427
|
+
this.namedTypes.set(p.name, prev);
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
// Replaces every occurrence of a type parameter named in `bindings`
|
|
432
|
+
// (mapping name -> concrete Type) inside `type` — the substitution step
|
|
433
|
+
// that turns Pair<K, V>'s abstractly-stored field type `K` into `number`
|
|
434
|
+
// when someone actually asks about `Pair<number, string>.First`. A type
|
|
435
|
+
// with none of `bindings`' names occurring anywhere in it is returned
|
|
436
|
+
// unchanged (including every non-generic type, the overwhelming majority).
|
|
437
|
+
substituteTypeParams(type, bindings) {
|
|
425
438
|
switch (type.kind) {
|
|
426
439
|
case "typeParam":
|
|
427
|
-
return type.name
|
|
440
|
+
return bindings.get(type.name) ?? type;
|
|
428
441
|
case "array":
|
|
429
|
-
return T.arrayOf(this.
|
|
442
|
+
return T.arrayOf(this.substituteTypeParams(type.element, bindings));
|
|
430
443
|
case "nullable":
|
|
431
|
-
return T.nullableOf(this.
|
|
444
|
+
return T.nullableOf(this.substituteTypeParams(type.inner, bindings));
|
|
432
445
|
case "task":
|
|
433
|
-
return T.taskType(this.
|
|
446
|
+
return T.taskType(this.substituteTypeParams(type.resultType, bindings));
|
|
434
447
|
case "state":
|
|
435
|
-
return T.stateType(this.
|
|
448
|
+
return T.stateType(this.substituteTypeParams(type.valueType, bindings));
|
|
436
449
|
case "function":
|
|
437
|
-
return T.functionType(type.params.map((p) => this.
|
|
438
|
-
// A nested generic's own type
|
|
439
|
-
//
|
|
440
|
-
// to handle correctly): substitute inside
|
|
450
|
+
return T.functionType(type.params.map((p) => this.substituteTypeParams(p, bindings)), this.substituteTypeParams(type.returnType, bindings));
|
|
451
|
+
// A nested generic's own type arguments can themselves mention an
|
|
452
|
+
// outer type param (`Pair<K, V>`'s field being List<K> — a v1 corner
|
|
453
|
+
// case, but cheap to handle correctly): substitute inside each.
|
|
441
454
|
case "class":
|
|
442
|
-
return type.
|
|
455
|
+
return type.typeArgs ? T.classType(type.name, type.typeArgs.map((a) => this.substituteTypeParams(a, bindings))) : type;
|
|
443
456
|
case "interface":
|
|
444
|
-
return type.
|
|
457
|
+
return type.typeArgs ? T.interfaceType(type.name, type.typeArgs.map((a) => this.substituteTypeParams(a, bindings))) : type;
|
|
445
458
|
default:
|
|
446
459
|
return type;
|
|
447
460
|
}
|
|
@@ -459,7 +472,7 @@ export class Checker {
|
|
|
459
472
|
this.recordHover(decl.line, decl.col, `enum ${decl.name}`);
|
|
460
473
|
}
|
|
461
474
|
registerInterface(decl) {
|
|
462
|
-
const methods = this.
|
|
475
|
+
const methods = this.withTypeParamsInScope(decl.typeParams, () => decl.methods.map((m) => {
|
|
463
476
|
const params = m.params.map((p) => this.resolveType(p.type, m.line, m.col));
|
|
464
477
|
const returnType = this.resolveType(m.returnType, m.line, m.col);
|
|
465
478
|
this.recordHover(m.nameLine, m.nameCol, `${m.name}(${params.map(T.typeToString).join(", ")}): ${T.typeToString(returnType)}`);
|
|
@@ -480,8 +493,8 @@ export class Checker {
|
|
|
480
493
|
}
|
|
481
494
|
bases.push(baseName);
|
|
482
495
|
}
|
|
483
|
-
this.interfaces.set(decl.name, { name: decl.name,
|
|
484
|
-
this.recordHover(decl.line, decl.col, decl.
|
|
496
|
+
this.interfaces.set(decl.name, { name: decl.name, typeParams: decl.typeParams.map((p) => p.name), bases, methods });
|
|
497
|
+
this.recordHover(decl.line, decl.col, decl.typeParams.length > 0 ? `interface ${decl.name}<${decl.typeParams.map((p) => p.name).join(", ")}>` : `interface ${decl.name}`);
|
|
485
498
|
}
|
|
486
499
|
checkInterfaceHierarchy(decl) {
|
|
487
500
|
const info = this.interfaces.get(decl.name);
|
|
@@ -524,8 +537,8 @@ export class Checker {
|
|
|
524
537
|
return info.bases.some((b) => this.interfaceExtends(b, sup));
|
|
525
538
|
}
|
|
526
539
|
registerClass(decl) {
|
|
527
|
-
this.recordHover(decl.line, decl.col, decl.
|
|
528
|
-
const { fields, staticFields, methods, staticMethods, ownCtorParams } = this.
|
|
540
|
+
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 { fields, staticFields, methods, staticMethods, ownCtorParams } = this.withTypeParamsInScope(decl.typeParams, () => {
|
|
529
542
|
const fields = new Map();
|
|
530
543
|
const staticFields = new Map();
|
|
531
544
|
for (const f of decl.fields) {
|
|
@@ -584,7 +597,7 @@ export class Checker {
|
|
|
584
597
|
}
|
|
585
598
|
this.classes.set(decl.name, {
|
|
586
599
|
name: decl.name,
|
|
587
|
-
|
|
600
|
+
typeParams: decl.typeParams.map((p) => p.name),
|
|
588
601
|
superclass,
|
|
589
602
|
interfaces,
|
|
590
603
|
fields,
|
|
@@ -825,11 +838,11 @@ export class Checker {
|
|
|
825
838
|
// only ever arises from an arity error already diagnosed at the
|
|
826
839
|
// reference site — see validateGenericArity).
|
|
827
840
|
typeArgsMatch(a, b) {
|
|
828
|
-
if (!a.
|
|
841
|
+
if (!a.typeArgs && !b.typeArgs)
|
|
829
842
|
return true;
|
|
830
|
-
if (!a.
|
|
843
|
+
if (!a.typeArgs || !b.typeArgs || a.typeArgs.length !== b.typeArgs.length)
|
|
831
844
|
return false;
|
|
832
|
-
return T.typesEqual(
|
|
845
|
+
return a.typeArgs.every((t, i) => T.typesEqual(t, b.typeArgs[i]));
|
|
833
846
|
}
|
|
834
847
|
// ---------- top-level ----------
|
|
835
848
|
// Validates that `isAsync` and the declared return type agree — `async`
|
|
@@ -867,12 +880,12 @@ export class Checker {
|
|
|
867
880
|
}
|
|
868
881
|
checkClassBody(decl) {
|
|
869
882
|
// Constructor/method *bodies* run in this same scope registerClass used
|
|
870
|
-
// for the *signatures* (see
|
|
883
|
+
// for the *signatures* (see withTypeParamsInScope) — without it, `T`
|
|
871
884
|
// resolves everywhere in a generic class's declared field/param/return
|
|
872
885
|
// types but not inside a method body itself (a local `T x = ...;`, or a
|
|
873
886
|
// lambda parameter typed `T`), which would make the type parameter
|
|
874
887
|
// usable only at the class's boundary and not inside its own logic.
|
|
875
|
-
this.
|
|
888
|
+
this.withTypeParamsInScope(decl.typeParams, () => this.checkClassBodyInner(decl));
|
|
876
889
|
}
|
|
877
890
|
checkClassBodyInner(decl) {
|
|
878
891
|
const info = this.classes.get(decl.name);
|
|
@@ -1461,30 +1474,34 @@ export class Checker {
|
|
|
1461
1474
|
expr.args.forEach((a) => this.checkExpression(a, scope, ctx));
|
|
1462
1475
|
return T.UNKNOWN;
|
|
1463
1476
|
}
|
|
1464
|
-
let
|
|
1465
|
-
if (info.
|
|
1466
|
-
this.diagnostics.error("KS4069", `Generic class '${expr.className}' requires
|
|
1477
|
+
let typeArgs;
|
|
1478
|
+
if (info.typeParams.length > 0 && !expr.typeArgs) {
|
|
1479
|
+
this.diagnostics.error("KS4069", `Generic class '${expr.className}' requires ${this.describeArity(info.typeParams)} (e.g. 'new ${expr.className}<${info.typeParams.join(", ")}>(...)')`, expr.line, expr.col);
|
|
1467
1480
|
// Abstractly-typed (T-containing) ctor params, un-substitutable
|
|
1468
|
-
// without
|
|
1481
|
+
// without real type arguments, would otherwise cascade into a
|
|
1469
1482
|
// confusing "expected 'T'" error on every argument — one clear error
|
|
1470
1483
|
// beats that pile-on.
|
|
1471
1484
|
expr.args.forEach((a) => this.checkExpression(a, scope, ctx));
|
|
1472
1485
|
return T.UNKNOWN;
|
|
1473
1486
|
}
|
|
1474
|
-
if (
|
|
1487
|
+
if (info.typeParams.length === 0 && expr.typeArgs) {
|
|
1475
1488
|
this.diagnostics.error("KS4070", `Class '${expr.className}' is not generic — it doesn't take a type argument`, expr.line, expr.col);
|
|
1476
1489
|
expr.args.forEach((a) => this.checkExpression(a, scope, ctx));
|
|
1477
1490
|
return T.UNKNOWN;
|
|
1478
1491
|
}
|
|
1479
|
-
if (info.
|
|
1480
|
-
|
|
1492
|
+
if (info.typeParams.length > 0 && expr.typeArgs) {
|
|
1493
|
+
if (expr.typeArgs.length !== info.typeParams.length) {
|
|
1494
|
+
this.diagnostics.error("KS4091", `Generic class '${expr.className}' expects ${this.describeArity(info.typeParams)}, got ${expr.typeArgs.length}`, expr.line, expr.col);
|
|
1495
|
+
expr.args.forEach((a) => this.checkExpression(a, scope, ctx));
|
|
1496
|
+
return T.UNKNOWN;
|
|
1497
|
+
}
|
|
1498
|
+
typeArgs = expr.typeArgs.map((a) => this.resolveType(a, expr.line, expr.col));
|
|
1481
1499
|
}
|
|
1482
|
-
this.recordHover(expr.line, expr.col,
|
|
1500
|
+
this.recordHover(expr.line, expr.col, typeArgs ? `class ${expr.className}<${typeArgs.map(T.typeToString).join(", ")}>` : `class ${expr.className}`);
|
|
1483
1501
|
let ctorParams = this.lookupCtorParams(expr.className);
|
|
1484
|
-
if (info.
|
|
1485
|
-
const
|
|
1486
|
-
|
|
1487
|
-
ctorParams = ctorParams.map((p) => this.substituteTypeParam(p, paramName, arg));
|
|
1502
|
+
if (info.typeParams.length > 0 && typeArgs) {
|
|
1503
|
+
const bindings = new Map(info.typeParams.map((name, i) => [name, typeArgs[i]]));
|
|
1504
|
+
ctorParams = ctorParams.map((p) => this.substituteTypeParams(p, bindings));
|
|
1488
1505
|
}
|
|
1489
1506
|
if (expr.args.length !== ctorParams.length) {
|
|
1490
1507
|
this.diagnostics.error("KS4071", `Expected ${ctorParams.length} constructor argument(s), got ${expr.args.length}`, expr.line, expr.col);
|
|
@@ -1496,7 +1513,7 @@ export class Checker {
|
|
|
1496
1513
|
this.diagnostics.error("KS4072", `Constructor argument ${i + 1} has type '${T.typeToString(argType)}', expected '${T.typeToString(expected)}'`, arg.line, arg.col);
|
|
1497
1514
|
}
|
|
1498
1515
|
});
|
|
1499
|
-
return T.classType(expr.className,
|
|
1516
|
+
return T.classType(expr.className, typeArgs);
|
|
1500
1517
|
}
|
|
1501
1518
|
lookupCtorParams(className) {
|
|
1502
1519
|
let current = className;
|
|
@@ -1649,9 +1666,10 @@ export class Checker {
|
|
|
1649
1666
|
// v1 has no generic inheritance (a generic class's base list can only
|
|
1650
1667
|
// name non-generic types), so a member found on a generic instance
|
|
1651
1668
|
// was always declared directly on that same class — substituting by
|
|
1652
|
-
// its own type
|
|
1653
|
-
const
|
|
1654
|
-
const
|
|
1669
|
+
// its own type parameters, not some ancestor's, is always correct.
|
|
1670
|
+
const declaredParams = objectType.typeArgs ? this.genericTypeParams.get(objectType.name) : undefined;
|
|
1671
|
+
const bindings = declaredParams && objectType.typeArgs ? new Map(declaredParams.map((name, i) => [name, objectType.typeArgs[i]])) : null;
|
|
1672
|
+
const substitute = (type) => (bindings ? this.substituteTypeParams(type, bindings) : type);
|
|
1655
1673
|
const field = this.lookupField(objectType.name, expr.property);
|
|
1656
1674
|
if (field) {
|
|
1657
1675
|
this.checkAccessibility(field.info.visibility, field.owner, ctx, expr.property, expr.line, expr.col);
|
|
@@ -1666,7 +1684,7 @@ export class Checker {
|
|
|
1666
1684
|
const method = this.lookupMethod(objectType.name, expr.property);
|
|
1667
1685
|
if (method) {
|
|
1668
1686
|
this.checkAccessibility(method.info.visibility, method.owner, ctx, expr.property, expr.line, expr.col);
|
|
1669
|
-
const info =
|
|
1687
|
+
const info = bindings ? { ...method.info, params: method.info.params.map(substitute), returnType: substitute(method.info.returnType) } : method.info;
|
|
1670
1688
|
return { type: info.returnType, methodInfo: info };
|
|
1671
1689
|
}
|
|
1672
1690
|
this.diagnostics.error("KS4083", `Class '${objectType.name}' has no member '${expr.property}'`, expr.line, expr.col);
|
|
@@ -1678,8 +1696,9 @@ export class Checker {
|
|
|
1678
1696
|
// v1 has no generic interface inheritance either (same restriction
|
|
1679
1697
|
// as classes — see registerInterface), so a signature found here
|
|
1680
1698
|
// was always declared directly on this same interface.
|
|
1681
|
-
const
|
|
1682
|
-
const
|
|
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);
|
|
1683
1702
|
const params = sig.params.map(substitute);
|
|
1684
1703
|
const returnType = substitute(sig.returnType);
|
|
1685
1704
|
return { type: returnType, methodInfo: { params, returnType, visibility: "public", isVirtual: false, isOverride: false } };
|
package/dist/parser.js
CHANGED
|
@@ -243,20 +243,25 @@ export class Parser {
|
|
|
243
243
|
this.consume(TokenKind.RParen, "Expected ')' after parameters");
|
|
244
244
|
return params;
|
|
245
245
|
}
|
|
246
|
-
// `<
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
|
|
246
|
+
// `<K, V>` right after a class/interface name — one or more type
|
|
247
|
+
// parameters, comma-separated. Empty array if absent, the overwhelmingly
|
|
248
|
+
// common case. Constraint syntax (`<T : IFoo>`) isn't parsed yet — every
|
|
249
|
+
// entry's `constraint` is always null for now (see AST.TypeParamDecl).
|
|
250
|
+
parseTypeParamList() {
|
|
250
251
|
if (!this.match(TokenKind.Lt))
|
|
251
|
-
return
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
252
|
+
return [];
|
|
253
|
+
const params = [];
|
|
254
|
+
do {
|
|
255
|
+
const nameTok = this.consume(TokenKind.Identifier, "Expected a type parameter name");
|
|
256
|
+
params.push({ name: nameTok.lexeme, constraint: null, line: nameTok.line, col: nameTok.col });
|
|
257
|
+
} while (this.match(TokenKind.Comma));
|
|
258
|
+
this.consume(TokenKind.Gt, "Expected '>' after type parameter list");
|
|
259
|
+
return params;
|
|
255
260
|
}
|
|
256
261
|
parseClassDecl(isExported) {
|
|
257
262
|
const start = this.advance(); // 'class'
|
|
258
263
|
const name = this.consume(TokenKind.Identifier, "Expected class name").lexeme;
|
|
259
|
-
const
|
|
264
|
+
const typeParams = this.parseTypeParamList();
|
|
260
265
|
const baseList = [];
|
|
261
266
|
if (this.match(TokenKind.Colon)) {
|
|
262
267
|
do {
|
|
@@ -412,12 +417,12 @@ export class Parser {
|
|
|
412
417
|
}
|
|
413
418
|
}
|
|
414
419
|
this.consume(TokenKind.RBrace, "Expected '}' after class body");
|
|
415
|
-
return { kind: "ClassDecl", isExported, name,
|
|
420
|
+
return { kind: "ClassDecl", isExported, name, typeParams, baseList, fields, properties, constructor: ctor, methods, template, line: start.line, col: start.col };
|
|
416
421
|
}
|
|
417
422
|
parseInterfaceDecl(isExported) {
|
|
418
423
|
const start = this.advance(); // 'interface'
|
|
419
424
|
const name = this.consume(TokenKind.Identifier, "Expected interface name").lexeme;
|
|
420
|
-
const
|
|
425
|
+
const typeParams = this.parseTypeParamList();
|
|
421
426
|
const baseList = [];
|
|
422
427
|
if (this.match(TokenKind.Colon)) {
|
|
423
428
|
do {
|
|
@@ -443,7 +448,7 @@ export class Parser {
|
|
|
443
448
|
});
|
|
444
449
|
}
|
|
445
450
|
this.consume(TokenKind.RBrace, "Expected '}' after interface body");
|
|
446
|
-
return { kind: "InterfaceDecl", isExported, name,
|
|
451
|
+
return { kind: "InterfaceDecl", isExported, name, typeParams, baseList, methods, line: start.line, col: start.col };
|
|
447
452
|
}
|
|
448
453
|
parseEnumDecl(isExported) {
|
|
449
454
|
const start = this.advance(); // 'enum'
|
|
@@ -510,7 +515,7 @@ export class Parser {
|
|
|
510
515
|
}
|
|
511
516
|
parseExternClassBody(start, isExported) {
|
|
512
517
|
const name = this.consume(TokenKind.Identifier, "Expected class name").lexeme;
|
|
513
|
-
const
|
|
518
|
+
const typeParams = this.parseTypeParamList();
|
|
514
519
|
this.consume(TokenKind.LBrace, "Expected '{' before extern class body");
|
|
515
520
|
let hasConstructor = false;
|
|
516
521
|
let ctorParams = [];
|
|
@@ -575,7 +580,7 @@ export class Parser {
|
|
|
575
580
|
this.consume(TokenKind.RBrace, "Expected '}' after extern class body");
|
|
576
581
|
const { modulePath, jsName } = this.parseExternTail(name);
|
|
577
582
|
this.consume(TokenKind.Semicolon, "Expected ';' after extern class declaration");
|
|
578
|
-
return { kind: "ExternClassDecl", isExported, name,
|
|
583
|
+
return { kind: "ExternClassDecl", isExported, name, typeParams, jsName, hasConstructor, ctorParams, properties, methods, modulePath, line: start.line, col: start.col };
|
|
579
584
|
}
|
|
580
585
|
parseBlock() {
|
|
581
586
|
const start = this.consume(TokenKind.LBrace, "Expected '{'");
|
|
@@ -705,15 +710,18 @@ export class Parser {
|
|
|
705
710
|
}
|
|
706
711
|
else {
|
|
707
712
|
const nameToken = this.check(TokenKind.Void) ? this.advance() : this.consume(TokenKind.Identifier, "Expected type name");
|
|
708
|
-
// `
|
|
709
|
-
//
|
|
710
|
-
//
|
|
713
|
+
// `Pair<number, string>` — a generic type reference, comma-separated
|
|
714
|
+
// type arguments. Whether `name` actually refers to a declared
|
|
715
|
+
// generic type (and whether the count matches its arity) is a
|
|
711
716
|
// semantic question the checker answers, not the parser.
|
|
712
717
|
let typeArgs = null;
|
|
713
718
|
if (this.check(TokenKind.Lt)) {
|
|
714
719
|
this.advance();
|
|
715
720
|
typeArgs = [this.parseType()];
|
|
716
|
-
this.
|
|
721
|
+
while (this.match(TokenKind.Comma)) {
|
|
722
|
+
typeArgs.push(this.parseType());
|
|
723
|
+
}
|
|
724
|
+
this.consume(TokenKind.Gt, "Expected '>' after type argument list");
|
|
717
725
|
}
|
|
718
726
|
type = { kind: "NamedType", name: nameToken.lexeme, typeArgs, line: nameToken.line, col: nameToken.col };
|
|
719
727
|
}
|
|
@@ -893,14 +901,18 @@ export class Parser {
|
|
|
893
901
|
if (this.check(TokenKind.New)) {
|
|
894
902
|
this.advance();
|
|
895
903
|
const className = this.consume(TokenKind.Identifier, "Expected class name after 'new'").lexeme;
|
|
896
|
-
// `new
|
|
897
|
-
// always followed by `(`, generic type args or not,
|
|
898
|
-
// instead can only mean a type argument list, never a
|
|
904
|
+
// `new Pair<number, string>(...)` — unambiguous here: `new
|
|
905
|
+
// <Identifier>` is always followed by `(`, generic type args or not,
|
|
906
|
+
// so seeing `<` instead can only mean a type argument list, never a
|
|
907
|
+
// comparison.
|
|
899
908
|
let typeArgs = null;
|
|
900
909
|
if (this.check(TokenKind.Lt)) {
|
|
901
910
|
this.advance();
|
|
902
911
|
typeArgs = [this.parseType()];
|
|
903
|
-
this.
|
|
912
|
+
while (this.match(TokenKind.Comma)) {
|
|
913
|
+
typeArgs.push(this.parseType());
|
|
914
|
+
}
|
|
915
|
+
this.consume(TokenKind.Gt, "Expected '>' after type argument list");
|
|
904
916
|
}
|
|
905
917
|
this.consume(TokenKind.LParen, "Expected '(' after class name");
|
|
906
918
|
const args = [];
|
package/dist/printer.js
CHANGED
|
@@ -212,7 +212,7 @@ export class Printer {
|
|
|
212
212
|
printClass(decl, indent) {
|
|
213
213
|
const pad = indentStr(indent);
|
|
214
214
|
const memberPad = indentStr(indent + 1);
|
|
215
|
-
const nameWithTypeParam = decl.
|
|
215
|
+
const nameWithTypeParam = decl.typeParams.length > 0 ? `${decl.name}<${decl.typeParams.map((p) => p.name).join(", ")}>` : decl.name;
|
|
216
216
|
const header = decl.baseList.length > 0 ? `class ${nameWithTypeParam} : ${decl.baseList.join(", ")} {` : `class ${nameWithTypeParam} {`;
|
|
217
217
|
const prefix = decl.isExported ? "" : "private ";
|
|
218
218
|
const memberParts = [];
|
|
@@ -250,7 +250,7 @@ export class Printer {
|
|
|
250
250
|
printInterface(decl, indent) {
|
|
251
251
|
const pad = indentStr(indent);
|
|
252
252
|
const memberPad = indentStr(indent + 1);
|
|
253
|
-
const nameWithTypeParam = decl.
|
|
253
|
+
const nameWithTypeParam = decl.typeParams.length > 0 ? `${decl.name}<${decl.typeParams.map((p) => p.name).join(", ")}>` : decl.name;
|
|
254
254
|
const header = decl.baseList.length > 0 ? `interface ${nameWithTypeParam} : ${decl.baseList.join(", ")} {` : `interface ${nameWithTypeParam} {`;
|
|
255
255
|
const prefix = decl.isExported ? "" : "private ";
|
|
256
256
|
if (decl.methods.length === 0)
|
|
@@ -279,7 +279,7 @@ export class Printer {
|
|
|
279
279
|
const pad = indentStr(indent);
|
|
280
280
|
const memberPad = indentStr(indent + 1);
|
|
281
281
|
const prefix = decl.isExported ? "" : "private ";
|
|
282
|
-
const nameWithTypeParam = decl.
|
|
282
|
+
const nameWithTypeParam = decl.typeParams.length > 0 ? `${decl.name}<${decl.typeParams.map((p) => p.name).join(", ")}>` : decl.name;
|
|
283
283
|
const lines = [];
|
|
284
284
|
if (decl.hasConstructor)
|
|
285
285
|
lines.push(`${memberPad}constructor(${this.printParams(decl.ctorParams)});`);
|
package/dist/types.js
CHANGED
|
@@ -6,11 +6,11 @@ export const UNKNOWN = { kind: "unknown" };
|
|
|
6
6
|
export function arrayOf(element) {
|
|
7
7
|
return { kind: "array", element };
|
|
8
8
|
}
|
|
9
|
-
export function classType(name,
|
|
10
|
-
return
|
|
9
|
+
export function classType(name, typeArgs) {
|
|
10
|
+
return typeArgs && typeArgs.length > 0 ? { kind: "class", name, typeArgs } : { kind: "class", name };
|
|
11
11
|
}
|
|
12
|
-
export function interfaceType(name,
|
|
13
|
-
return
|
|
12
|
+
export function interfaceType(name, typeArgs) {
|
|
13
|
+
return typeArgs && typeArgs.length > 0 ? { kind: "interface", name, typeArgs } : { kind: "interface", name };
|
|
14
14
|
}
|
|
15
15
|
export function typeParamType(name) {
|
|
16
16
|
return { kind: "typeParam", name };
|
|
@@ -44,7 +44,7 @@ export function typeToString(t) {
|
|
|
44
44
|
return t.name;
|
|
45
45
|
case "class":
|
|
46
46
|
case "interface":
|
|
47
|
-
return t.
|
|
47
|
+
return t.typeArgs && t.typeArgs.length > 0 ? `${t.name}<${t.typeArgs.map(typeToString).join(", ")}>` : t.name;
|
|
48
48
|
case "function":
|
|
49
49
|
return `(${t.params.map(typeToString).join(", ")}) => ${typeToString(t.returnType)}`;
|
|
50
50
|
case "task":
|
|
@@ -91,31 +91,36 @@ export function resolveTypeNode(node, namedTypes, onNamedType) {
|
|
|
91
91
|
const inner = resolveTypeNode(node.inner, namedTypes, onNamedType);
|
|
92
92
|
return inner ? nullableOf(inner) : null;
|
|
93
93
|
}
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
|
|
94
|
+
// Resolve every type argument given, in order — a primitive/enum/
|
|
95
|
+
// type-param name below never accepts any (arity error, caught as a plain
|
|
96
|
+
// resolution failure here). Arity *against a class/interface's own
|
|
97
|
+
// declared parameter count* is a separate check the caller makes, since
|
|
98
|
+
// only it knows which names are actually declared generic and with how
|
|
99
|
+
// many parameters (see Checker.validateGenericArity).
|
|
100
|
+
let typeArgs = null;
|
|
100
101
|
if (node.typeArgs) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
typeArgs = [];
|
|
103
|
+
for (const argNode of node.typeArgs) {
|
|
104
|
+
const resolvedArg = resolveTypeNode(argNode, namedTypes, onNamedType);
|
|
105
|
+
if (!resolvedArg)
|
|
106
|
+
return null;
|
|
107
|
+
typeArgs.push(resolvedArg);
|
|
108
|
+
}
|
|
104
109
|
}
|
|
105
110
|
let resolved;
|
|
106
111
|
if (PRIMITIVE_NAMES.has(node.name)) {
|
|
107
|
-
resolved =
|
|
112
|
+
resolved = typeArgs ? null : { kind: node.name };
|
|
108
113
|
}
|
|
109
114
|
else {
|
|
110
115
|
const kind = namedTypes.get(node.name);
|
|
111
116
|
if (kind === "class")
|
|
112
|
-
resolved = classType(node.name,
|
|
117
|
+
resolved = classType(node.name, typeArgs ?? undefined);
|
|
113
118
|
else if (kind === "interface")
|
|
114
|
-
resolved = interfaceType(node.name,
|
|
119
|
+
resolved = interfaceType(node.name, typeArgs ?? undefined);
|
|
115
120
|
else if (kind === "enum")
|
|
116
|
-
resolved =
|
|
121
|
+
resolved = typeArgs ? null : enumType(node.name);
|
|
117
122
|
else if (kind === "typeParam")
|
|
118
|
-
resolved =
|
|
123
|
+
resolved = typeArgs ? null : typeParamType(node.name);
|
|
119
124
|
else
|
|
120
125
|
resolved = null;
|
|
121
126
|
}
|
|
@@ -153,12 +158,12 @@ export function typesEqual(a, b) {
|
|
|
153
158
|
const other = b;
|
|
154
159
|
if (a.name !== other.name)
|
|
155
160
|
return false;
|
|
156
|
-
if ("
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
if (!
|
|
161
|
+
if ("typeArgs" in a || "typeArgs" in other) {
|
|
162
|
+
const aArgs = a.typeArgs;
|
|
163
|
+
const bArgs = other.typeArgs;
|
|
164
|
+
if (!aArgs || !bArgs || aArgs.length !== bArgs.length)
|
|
160
165
|
return false;
|
|
161
|
-
return typesEqual(
|
|
166
|
+
return aArgs.every((t, i) => typesEqual(t, bArgs[i]));
|
|
162
167
|
}
|
|
163
168
|
return true;
|
|
164
169
|
}
|
package/package.json
CHANGED