kopscript 0.14.0 → 0.15.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 +38 -13
- package/README.md +30 -15
- package/dist/checker.js +137 -9
- package/dist/parser.js +11 -1
- package/dist/printer.js +2 -1
- package/package.json +1 -1
package/LLM.md
CHANGED
|
@@ -75,9 +75,10 @@ below). No circular `using` (compile error).
|
|
|
75
75
|
**Does not exist**: `any`/`unknown` as a writable annotation, a `let`/`var` keyword (see
|
|
76
76
|
Declarations below — there isn't one), type inference for declarations (every
|
|
77
77
|
local/`const`/param/field/return type is written out explicitly), union types, tuples,
|
|
78
|
-
structural/duck typing (all typing is nominal). Generics exist
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
structural/duck typing (all typing is nominal). Generics exist and cover multiple type
|
|
79
|
+
parameters, single-interface constraints, generic inheritance, and free generic functions
|
|
80
|
+
— see the Generics section for exactly what's still NOT supported there (variance,
|
|
81
|
+
multiple constraints per parameter, generic methods).
|
|
81
82
|
|
|
82
83
|
## Declarations
|
|
83
84
|
|
|
@@ -366,10 +367,30 @@ implemented/extended interface — never more than one) — `class Foo<T> : Box<
|
|
|
366
367
|
IContainer<T>` is a compile error ("has more than one generic entry in its base list") even
|
|
367
368
|
though either half alone would work.
|
|
368
369
|
|
|
370
|
+
**Generic functions**: only free/top-level functions, never a method inside a class (a
|
|
371
|
+
method still only ever uses its enclosing class's own type parameters):
|
|
372
|
+
```ks
|
|
373
|
+
T Identity<T>(T x) { return x; }
|
|
374
|
+
number n = Identity(5); // T inferred as number, from the argument
|
|
375
|
+
string s = Identity("hi"); // T inferred as string, independently
|
|
376
|
+
```
|
|
377
|
+
**No explicit type argument at a call site** — `Identity<number>(5)` isn't valid syntax;
|
|
378
|
+
`T` is always inferred from the actual argument types by structurally unifying each
|
|
379
|
+
declared (possibly abstract) parameter type against its argument's real type — including
|
|
380
|
+
through a nested generic type (`T Unwrap<T>(Box<T> b)` infers `T` from `Box<number>`) or a
|
|
381
|
+
lambda argument's own explicit parameter type (`void UseCallback<T>((T) => void cb)` infers
|
|
382
|
+
`T` from `(number n) => ...`). This is a real grammar constraint, not a missing feature:
|
|
383
|
+
`new Box<number>(...)` disambiguates `<` from a comparison only because `new` is a distinct
|
|
384
|
+
keyword context — a bare call has no such anchor, so `f<T>(x)` would be genuinely ambiguous
|
|
385
|
+
with a chained `<`/`>` comparison. If no argument determines a declared type parameter, or
|
|
386
|
+
two arguments would bind it to conflicting types, that's a compile error naming the
|
|
387
|
+
parameter, not a syntax feature to reach for. A constrained type parameter (`T :
|
|
388
|
+
IComparable`) works exactly like it does on a class — the inferred type must satisfy it,
|
|
389
|
+
checked after inference succeeds.
|
|
390
|
+
|
|
369
391
|
**Does not exist (v1 scope cuts, each deliberate)**:
|
|
370
|
-
- **Generic
|
|
371
|
-
|
|
372
|
-
generic class can use that class's own type parameters freely, same as any other member).
|
|
392
|
+
- **Generic methods.** A class method can't introduce its own new type parameter beyond
|
|
393
|
+
its enclosing class's (generic functions are free-function-only — see above).
|
|
373
394
|
- **Variance.** No `out`/`in`/covariance/contravariance — see invariance above.
|
|
374
395
|
|
|
375
396
|
## Nullable types — `T?`
|
|
@@ -541,10 +562,11 @@ Locals/params: **camelCase**. This is convention, not enforced by the compiler.
|
|
|
541
562
|
|
|
542
563
|
## Does not exist (don't reach for these)
|
|
543
564
|
|
|
544
|
-
Generics beyond one or more type parameters (each with at most one interface constraint)
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
565
|
+
Generics beyond one or more type parameters (each with at most one interface constraint),
|
|
566
|
+
single-generic-base inheritance, and free (never method-level) generic functions with
|
|
567
|
+
inference-only call sites (no `T : IFoo, IBar` multi-constraints, no explicit
|
|
568
|
+
`Identity<number>(5)` type arguments, no variance, no more than one generic entry per base
|
|
569
|
+
list — see Generics above for what *is* supported) · `any`/`unknown` annotations ·
|
|
548
570
|
a `let`/`var` keyword (locals are just `Type name = value;`) · decorators/annotations ·
|
|
549
571
|
reflection · type inference on declarations · ternary expression · union/tuple types ·
|
|
550
572
|
**object-literal syntax** (`{ key: value }` as a value — this doesn't exist *anywhere* in
|
|
@@ -579,9 +601,12 @@ Real, observed cases where a plausible-looking guess was wrong — not hypotheti
|
|
|
579
601
|
dedicated rewriter, `qualifyThis`, specifically because this isn't automatic.)
|
|
580
602
|
- **No `let`/`var`, ever.** Every local is `Type name = value;` — writing `let x = 5;` or a
|
|
581
603
|
bare `const x = 5;` (missing the type) is a parse error, not a lenient inferred form.
|
|
582
|
-
- **No generic
|
|
583
|
-
|
|
584
|
-
|
|
604
|
+
- **No explicit type argument at a generic function call site.** `Identity<number>(5)`
|
|
605
|
+
doesn't parse — a bare call has no keyword like `new` to disambiguate `<` from a
|
|
606
|
+
comparison, so type arguments are always inferred from the actual arguments instead.
|
|
607
|
+
Just call `Identity(5)`; if inference can't determine a type parameter, restructure the
|
|
608
|
+
call (e.g. an argument that actually mentions the type) rather than reaching for explicit
|
|
609
|
+
syntax that isn't there.
|
|
585
610
|
- **Early-return doesn't narrow a nullable type.** `if (x == null) { return; } print(x.Length);`
|
|
586
611
|
still errors on `x.Length` — narrowing is scope-based (an `if`/`else` block), not
|
|
587
612
|
control-flow/reachability-based. Wrap the rest of the logic in the `if (x != null) { ... }`
|
package/README.md
CHANGED
|
@@ -22,11 +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 invariant type parameters on classes and
|
|
26
|
-
(`class Box<T> { public T Value; }`, `
|
|
27
|
-
constrained to an interface (`class Box<T : IComparable> { ... }`), plus
|
|
28
|
-
inheritance (`class IntBox : Box<number> { }`) — erased at codegen with zero
|
|
29
|
-
cost, the same way `task<T>`/`state<T>` already are.
|
|
25
|
+
- **Generics**: one or more invariant type parameters on classes, interfaces, and free
|
|
26
|
+
functions (`class Box<T> { public T Value; }`, `T Identity<T>(T x) { return x; }`), each
|
|
27
|
+
optionally constrained to an interface (`class Box<T : IComparable> { ... }`), plus
|
|
28
|
+
generic inheritance (`class IntBox : Box<number> { }`) — erased at codegen with zero
|
|
29
|
+
runtime cost, the same way `task<T>`/`state<T>` already are.
|
|
30
30
|
- **Nullable types with compiler-enforced null-checking**: `T?` (`string?`, `Dog?`,
|
|
31
31
|
`number[]?`) — a `T?` can't be used where a `T` is expected without an `if (x != null)`
|
|
32
32
|
check first (checked statically, not just at runtime), and comparing a value that can
|
|
@@ -258,9 +258,6 @@ inside v1:
|
|
|
258
258
|
- **Invariant.** `Box<Dog>` is **not** assignable to `Box<Animal>` even though `Dog :
|
|
259
259
|
Animal`, and `Pair<number, string>` is not assignable to `Pair<string, number>` — every
|
|
260
260
|
slot must match exactly, in order.
|
|
261
|
-
- **No generic functions.** `T Identity<T>(T x)` isn't supported — only classes and
|
|
262
|
-
interfaces take type parameters. A method *inside* a generic class can still use that
|
|
263
|
-
class's own type parameters freely; it's just not introducing one of its own.
|
|
264
261
|
|
|
265
262
|
By default a type parameter is fully unconstrained, so you can't call any member on a bare
|
|
266
263
|
`T`/`K`/`V` value inside the generic class's own body — the same restriction C#/Java/
|
|
@@ -299,6 +296,24 @@ down. **v1 limit**: at most one generic entry across a whole base list (the supe
|
|
|
299
296
|
one implemented interface — not several at once) — `class Foo<T> : Box<T>, IContainer<T>`
|
|
300
297
|
is a compile error even though each half would work alone.
|
|
301
298
|
|
|
299
|
+
**Free functions can be generic too** — only free/top-level functions, not class methods
|
|
300
|
+
(a method inside a class still only uses its enclosing class's own type parameters, never
|
|
301
|
+
introduces a new one):
|
|
302
|
+
|
|
303
|
+
```ks
|
|
304
|
+
T Identity<T>(T x) { return x; }
|
|
305
|
+
number n = Identity(5); // T = number, inferred from the argument
|
|
306
|
+
string s = Identity("hi"); // T = string, independently
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**No explicit type argument at the call site** — `Identity<number>(5)` isn't valid syntax;
|
|
310
|
+
`T` is always inferred from the actual argument types. This is a real grammar constraint,
|
|
311
|
+
not a missing feature: `new Box<number>(...)` can disambiguate `<` from a comparison only
|
|
312
|
+
because `new` is a distinct keyword context — a bare call has no such anchor, so
|
|
313
|
+
`f<T>(x)` would be genuinely ambiguous with a chained `<`/`>` comparison. If inference
|
|
314
|
+
can't pin down a type parameter from the arguments given, that's a compile error naming the
|
|
315
|
+
unresolved parameter, not a syntax feature to reach for.
|
|
316
|
+
|
|
302
317
|
See `LLM.md`'s "Generics" section for the exhaustive rules if you're generating code
|
|
303
318
|
against this — nested generics (`Box<Box<number>>`, `Box<number>?[]`, `Pair<number,
|
|
304
319
|
Pair<string, bool>>`) and generic interfaces used as standalone parameter types both work
|
|
@@ -892,13 +907,13 @@ into your extensions folder).
|
|
|
892
907
|
|
|
893
908
|
## Status
|
|
894
909
|
|
|
895
|
-
This is a v1 / hobby-project scope. Nullable types (`T?`) and generics (
|
|
896
|
-
|
|
897
|
-
above for
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
functions/classes.
|
|
910
|
+
This is a v1 / hobby-project scope. Nullable types (`T?`) and generics (multiple type
|
|
911
|
+
parameters, single-interface constraints, generic inheritance, and free generic functions)
|
|
912
|
+
have all shipped — see above for the full "Generics" section. What generics still
|
|
913
|
+
deliberately doesn't cover: variance, multiple constraints per parameter, generic methods
|
|
914
|
+
(only free functions), and more than one generic entry per base list — each a real,
|
|
915
|
+
separable extension rather than a v1 oversight. Also not yet supported: static
|
|
916
|
+
auto-properties, interface properties (methods only), and nested functions/classes.
|
|
902
917
|
|
|
903
918
|
`async`/`await`, `task<T>`, and `try`/`catch`/`finally`/`throw` are now in place (see the
|
|
904
919
|
language tour above) — the ceiling that's left is what's *inside* those: no async lambdas,
|
package/dist/checker.js
CHANGED
|
@@ -194,7 +194,12 @@ export class Checker {
|
|
|
194
194
|
this.checkClassBody(c);
|
|
195
195
|
}
|
|
196
196
|
registerExternFunction(decl) {
|
|
197
|
+
// extern functions never take a type parameter of their own in v1 —
|
|
198
|
+
// generic functions (see registerFunction) are a real-function-only
|
|
199
|
+
// feature; an extern binding always describes a concrete signature.
|
|
197
200
|
this.functions.set(decl.name, {
|
|
201
|
+
typeParams: [],
|
|
202
|
+
typeParamConstraints: [],
|
|
198
203
|
params: decl.params.map((p) => this.resolveType(p.type, decl.line, decl.col)),
|
|
199
204
|
returnType: this.resolveType(decl.returnType, decl.line, decl.col),
|
|
200
205
|
});
|
|
@@ -504,6 +509,68 @@ export class Checker {
|
|
|
504
509
|
return type;
|
|
505
510
|
}
|
|
506
511
|
}
|
|
512
|
+
// The reverse of substituteTypeParams: derives bindings *from* matching a
|
|
513
|
+
// generic function's declared (abstract) parameter type against one real
|
|
514
|
+
// argument's actual (concrete) type, instead of substituting bindings
|
|
515
|
+
// *into* a type. Structural unification, same recursive shape as
|
|
516
|
+
// substituteTypeParams/typesEqual — whenever `declared` is a TypeParamType
|
|
517
|
+
// for one of `typeParamNames`, records `bindings.set(name, actual)` the
|
|
518
|
+
// first time it's seen; a later argument that would bind the same name to
|
|
519
|
+
// a genuinely different type is recorded in `conflicts` instead of
|
|
520
|
+
// silently overwriting (see checkGenericFunctionCall's own error for
|
|
521
|
+
// that). A structural mismatch elsewhere (e.g. declared is `T[]` but
|
|
522
|
+
// actual isn't an array at all) simply infers nothing from that
|
|
523
|
+
// position — the normal post-substitution assignability check catches
|
|
524
|
+
// the real type error afterward, with a clearer message than unification
|
|
525
|
+
// failing silently here would give.
|
|
526
|
+
inferTypeParamBindings(declared, actual, typeParamNames, bindings, conflicts) {
|
|
527
|
+
if (declared.kind === "typeParam" && typeParamNames.has(declared.name)) {
|
|
528
|
+
const existing = bindings.get(declared.name);
|
|
529
|
+
if (existing) {
|
|
530
|
+
if (!T.typesEqual(existing, actual))
|
|
531
|
+
conflicts.set(declared.name, actual);
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
bindings.set(declared.name, actual);
|
|
535
|
+
}
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (declared.kind === "array" && actual.kind === "array") {
|
|
539
|
+
this.inferTypeParamBindings(declared.element, actual.element, typeParamNames, bindings, conflicts);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
if (declared.kind === "nullable") {
|
|
543
|
+
const actualInner = actual.kind === "nullable" ? actual.inner : actual;
|
|
544
|
+
this.inferTypeParamBindings(declared.inner, actualInner, typeParamNames, bindings, conflicts);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (declared.kind === "task" && actual.kind === "task") {
|
|
548
|
+
this.inferTypeParamBindings(declared.resultType, actual.resultType, typeParamNames, bindings, conflicts);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
if (declared.kind === "state" && actual.kind === "state") {
|
|
552
|
+
this.inferTypeParamBindings(declared.valueType, actual.valueType, typeParamNames, bindings, conflicts);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
if ((declared.kind === "class" || declared.kind === "interface") && declared.kind === actual.kind && declared.name === actual.name) {
|
|
556
|
+
const declaredArgs = declared.typeArgs ?? [];
|
|
557
|
+
const actualArgs = actual.typeArgs ?? [];
|
|
558
|
+
declaredArgs.forEach((d, i) => {
|
|
559
|
+
if (actualArgs[i])
|
|
560
|
+
this.inferTypeParamBindings(d, actualArgs[i], typeParamNames, bindings, conflicts);
|
|
561
|
+
});
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
if (declared.kind === "function" && actual.kind === "function") {
|
|
565
|
+
declared.params.forEach((d, i) => {
|
|
566
|
+
if (actual.params[i])
|
|
567
|
+
this.inferTypeParamBindings(d, actual.params[i], typeParamNames, bindings, conflicts);
|
|
568
|
+
});
|
|
569
|
+
this.inferTypeParamBindings(declared.returnType, actual.returnType, typeParamNames, bindings, conflicts);
|
|
570
|
+
}
|
|
571
|
+
// Anything else (primitive/enum/void/unknown, or a structural
|
|
572
|
+
// mismatch) has nothing to unify — no-op.
|
|
573
|
+
}
|
|
507
574
|
// Builds the bindings map for a generic reference (a `ClassType`/
|
|
508
575
|
// `InterfaceType`'s own `typeArgs`, zipped against its declared
|
|
509
576
|
// `typeParams`) — the "own bindings" every member-lookup/conformance
|
|
@@ -879,10 +946,18 @@ export class Checker {
|
|
|
879
946
|
}
|
|
880
947
|
}
|
|
881
948
|
registerFunction(decl) {
|
|
882
|
-
const params
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
949
|
+
const { params, returnType } = this.withTypeParamsInScope(decl.typeParams, () => ({
|
|
950
|
+
params: decl.params.map((p) => this.resolveType(p.type, decl.line, decl.col)),
|
|
951
|
+
returnType: this.resolveType(decl.returnType, decl.line, decl.col),
|
|
952
|
+
}));
|
|
953
|
+
this.functions.set(decl.name, {
|
|
954
|
+
typeParams: decl.typeParams.map((p) => p.name),
|
|
955
|
+
typeParamConstraints: decl.typeParams.map((p) => p.constraint),
|
|
956
|
+
params,
|
|
957
|
+
returnType,
|
|
958
|
+
});
|
|
959
|
+
const nameWithTypeParam = decl.typeParams.length > 0 ? `${decl.name}<${decl.typeParams.map((p) => p.name).join(", ")}>` : decl.name;
|
|
960
|
+
this.recordHover(decl.line, decl.col, `function ${nameWithTypeParam}(${params.map(T.typeToString).join(", ")}): ${T.typeToString(returnType)}`);
|
|
886
961
|
}
|
|
887
962
|
isSubclass(sub, sup) {
|
|
888
963
|
let current = sub;
|
|
@@ -1082,11 +1157,17 @@ export class Checker {
|
|
|
1082
1157
|
this.checkStatement(stmt, scope, { returnType: T.VOID, currentClass: null, inConstructor: false, loopDepth: 0, isAsync: true });
|
|
1083
1158
|
}
|
|
1084
1159
|
checkFunctionBody(decl, parentScope) {
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
this.
|
|
1160
|
+
// Same withTypeParamsInScope treatment checkClassBody gives a generic
|
|
1161
|
+
// class's own method bodies — without it, a generic function's type
|
|
1162
|
+
// parameter would resolve in its declared signature but not inside a
|
|
1163
|
+
// local declaration/lambda written in its own body.
|
|
1164
|
+
this.withTypeParamsInScope(decl.typeParams, () => {
|
|
1165
|
+
const info = this.functions.get(decl.name);
|
|
1166
|
+
const scope = parentScope.child();
|
|
1167
|
+
decl.params.forEach((p, i) => scope.declare(p.name, info.params[i], false));
|
|
1168
|
+
const returnType = this.resolveBodyReturnType(info.returnType, decl.isAsync, decl.line, decl.col);
|
|
1169
|
+
this.checkBlock(decl.body, scope, { returnType, currentClass: null, inConstructor: false, loopDepth: 0, isAsync: decl.isAsync });
|
|
1170
|
+
});
|
|
1090
1171
|
}
|
|
1091
1172
|
checkClassBody(decl) {
|
|
1092
1173
|
// Constructor/method *bodies* run in this same scope registerClass used
|
|
@@ -1626,6 +1707,9 @@ export class Checker {
|
|
|
1626
1707
|
return T.UNKNOWN;
|
|
1627
1708
|
}
|
|
1628
1709
|
this.recordHover(expr.callee.line, expr.callee.col, `function ${expr.callee.name}(${info.params.map(T.typeToString).join(", ")}): ${T.typeToString(info.returnType)}`);
|
|
1710
|
+
if (info.typeParams.length > 0) {
|
|
1711
|
+
return this.checkGenericFunctionCall(expr, expr.callee.name, info, scope, ctx);
|
|
1712
|
+
}
|
|
1629
1713
|
this.checkArgs(expr, info.params, scope, ctx);
|
|
1630
1714
|
return info.returnType;
|
|
1631
1715
|
}
|
|
@@ -1678,6 +1762,50 @@ export class Checker {
|
|
|
1678
1762
|
}
|
|
1679
1763
|
});
|
|
1680
1764
|
}
|
|
1765
|
+
// A generic function call has no explicit type-argument syntax (see
|
|
1766
|
+
// README's "Generics" — a real grammar-ambiguity reason, `f<T>(x)` is
|
|
1767
|
+
// indistinguishable from a chained comparison with no keyword like `new`
|
|
1768
|
+
// to disambiguate it), so every type parameter is inferred from the
|
|
1769
|
+
// actual argument types instead. Checks argument count first (an arity
|
|
1770
|
+
// mismatch would make inference itself meaningless), infers bindings by
|
|
1771
|
+
// unifying each declared (abstract) parameter type against its actual
|
|
1772
|
+
// argument's real type (see inferTypeParamBindings), then re-runs the
|
|
1773
|
+
// normal assignability check with the now-concrete substituted parameter
|
|
1774
|
+
// types — catching anything structural unification alone wouldn't (e.g.
|
|
1775
|
+
// an argument assignable to, but not identical to, its inferred slot).
|
|
1776
|
+
checkGenericFunctionCall(expr, functionName, info, scope, ctx) {
|
|
1777
|
+
if (expr.args.length !== info.params.length) {
|
|
1778
|
+
this.diagnostics.error("KS4098", `Expected ${info.params.length} argument(s), got ${expr.args.length}`, expr.line, expr.col);
|
|
1779
|
+
expr.args.forEach((a) => this.checkExpression(a, scope, ctx));
|
|
1780
|
+
return T.UNKNOWN;
|
|
1781
|
+
}
|
|
1782
|
+
const actualArgTypes = expr.args.map((a) => this.checkExpression(a, scope, ctx));
|
|
1783
|
+
const typeParamNames = new Set(info.typeParams);
|
|
1784
|
+
const bindings = new Map();
|
|
1785
|
+
const conflicts = new Map();
|
|
1786
|
+
info.params.forEach((declared, i) => this.inferTypeParamBindings(declared, actualArgTypes[i], typeParamNames, bindings, conflicts));
|
|
1787
|
+
for (const [name, conflictingType] of conflicts) {
|
|
1788
|
+
this.diagnostics.error("KS4099", `Type parameter '${name}' inferred as both '${T.typeToString(bindings.get(name))}' and '${T.typeToString(conflictingType)}' from different arguments — conflicting types`, expr.line, expr.col);
|
|
1789
|
+
}
|
|
1790
|
+
const unresolved = info.typeParams.filter((name) => !bindings.has(name));
|
|
1791
|
+
if (unresolved.length > 0) {
|
|
1792
|
+
this.diagnostics.error("KS4100", `Cannot infer type parameter${unresolved.length === 1 ? "" : "s"} '${unresolved.join("', '")}' for '${functionName}' — no argument determines ${unresolved.length === 1 ? "it" : "them"}`, expr.line, expr.col);
|
|
1793
|
+
return T.UNKNOWN;
|
|
1794
|
+
}
|
|
1795
|
+
info.typeParams.forEach((name, i) => {
|
|
1796
|
+
const constraint = info.typeParamConstraints[i];
|
|
1797
|
+
if (constraint)
|
|
1798
|
+
this.checkConstraintSatisfied(bindings.get(name), constraint, name, expr.line, expr.col);
|
|
1799
|
+
});
|
|
1800
|
+
const substitutedParams = info.params.map((p) => this.substituteTypeParams(p, bindings));
|
|
1801
|
+
expr.args.forEach((arg, i) => {
|
|
1802
|
+
const expected = substitutedParams[i];
|
|
1803
|
+
if (!this.isAssignableType(actualArgTypes[i], expected)) {
|
|
1804
|
+
this.diagnostics.error("KS4101", `Argument ${i + 1} has type '${T.typeToString(actualArgTypes[i])}', expected '${T.typeToString(expected)}'`, arg.line, arg.col);
|
|
1805
|
+
}
|
|
1806
|
+
});
|
|
1807
|
+
return this.substituteTypeParams(info.returnType, bindings);
|
|
1808
|
+
}
|
|
1681
1809
|
checkNew(expr, scope, ctx) {
|
|
1682
1810
|
if (this.interfaces.has(expr.className)) {
|
|
1683
1811
|
this.diagnostics.error("KS4066", `Cannot instantiate interface '${expr.className}'`, expr.line, expr.col);
|
package/dist/parser.js
CHANGED
|
@@ -198,14 +198,24 @@ export class Parser {
|
|
|
198
198
|
const type = this.parseType();
|
|
199
199
|
const nameTok = this.consume(TokenKind.Identifier, "Expected name");
|
|
200
200
|
const name = nameTok.lexeme;
|
|
201
|
+
// `T Identity<T>(T x) { ... }` — a free function's own type-param list,
|
|
202
|
+
// between its name and `(`. Only meaningful for the function branch
|
|
203
|
+
// below; a local/top-level variable declaration never has `<` right
|
|
204
|
+
// after its name, so trying this first costs nothing in that case
|
|
205
|
+
// (parseTypeParamList returns immediately without consuming anything
|
|
206
|
+
// when `<` isn't there).
|
|
207
|
+
const typeParams = this.parseTypeParamList();
|
|
201
208
|
if (this.check(TokenKind.LParen)) {
|
|
202
209
|
const params = this.parseParamList();
|
|
203
210
|
const body = this.parseBlock();
|
|
204
|
-
return { kind: "FunctionDecl", isExported, isAsync, name, params, returnType: type, body, line: start.line, col: start.col };
|
|
211
|
+
return { kind: "FunctionDecl", isExported, isAsync, name, typeParams, params, returnType: type, body, line: start.line, col: start.col };
|
|
205
212
|
}
|
|
206
213
|
if (isAsync) {
|
|
207
214
|
this.diagnostics.error("KS2006", "'async' cannot modify a variable declaration", start.line, start.col);
|
|
208
215
|
}
|
|
216
|
+
if (typeParams.length > 0) {
|
|
217
|
+
this.diagnostics.error("KS2023", "A type parameter list is only allowed on a function declaration", start.line, start.col);
|
|
218
|
+
}
|
|
209
219
|
this.consume(TokenKind.Assign, "Expected '=' in variable declaration");
|
|
210
220
|
const init = this.parseExpression();
|
|
211
221
|
this.consume(TokenKind.Semicolon, "Expected ';' after variable declaration");
|
package/dist/printer.js
CHANGED
|
@@ -208,7 +208,8 @@ export class Printer {
|
|
|
208
208
|
printFunction(decl, indent) {
|
|
209
209
|
const pad = indentStr(indent);
|
|
210
210
|
const prefix = `${decl.isExported ? "" : "private "}${decl.isAsync ? "async " : ""}`;
|
|
211
|
-
|
|
211
|
+
const nameWithTypeParam = this.printTypeParamName(decl);
|
|
212
|
+
return `${pad}${prefix}${this.printType(decl.returnType)} ${nameWithTypeParam}(${this.printParams(decl.params)}) ${this.printBlock(decl.body, indent).trimStart()}`;
|
|
212
213
|
}
|
|
213
214
|
// Canonical member order: fields, then properties, then the constructor,
|
|
214
215
|
// then methods — regardless of how the original source interleaved them.
|
package/package.json
CHANGED