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 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 but are deliberately
79
- scoped down see the Generics section for exactly what's NOT supported there (multiple
80
- type parameters, constraints, generic functions, generic inheritance).
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 functions.** `T Identity<T>(T x)` doesn't parse only classes and interfaces
371
- take type parameters, not free functions/methods themselves (a method *inside* a
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
- 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 ·
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 functions.** `T Identity<T>(T x) { return x; }` doesn't parse — only classes
583
- and interfaces take `<T>`. Don't reach for this even though every mainstream generic
584
- language supports it.
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 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.
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 (a single
896
- unconstrained, invariant type parameter on classes/interfaces) have both shipped — see
897
- above for both. What generics deliberately doesn't cover: multiple type parameters
898
- (`Map<K, V>`), constraints (`T : IFoo`), generic functions, generic inheritance, and
899
- variance each a real, separable extension rather than a v1 oversight. Also not yet
900
- supported: static auto-properties, interface properties (methods only), and nested
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 = decl.params.map((p) => this.resolveType(p.type, decl.line, decl.col));
883
- const returnType = this.resolveType(decl.returnType, decl.line, decl.col);
884
- this.functions.set(decl.name, { params, returnType });
885
- this.recordHover(decl.line, decl.col, `function ${decl.name}(${params.map(T.typeToString).join(", ")}): ${T.typeToString(returnType)}`);
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
- const info = this.functions.get(decl.name);
1086
- const scope = parentScope.child();
1087
- decl.params.forEach((p, i) => scope.declare(p.name, info.params[i], false));
1088
- const returnType = this.resolveBodyReturnType(info.returnType, decl.isAsync, decl.line, decl.col);
1089
- this.checkBlock(decl.body, scope, { returnType, currentClass: null, inConstructor: false, loopDepth: 0, isAsync: decl.isAsync });
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
- return `${pad}${prefix}${this.printType(decl.returnType)} ${decl.name}(${this.printParams(decl.params)}) ${this.printBlock(decl.body, indent).trimStart()}`;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kopscript",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "KopScript: a small OOP, strongly-typed language that transpiles to JavaScript, with generics and nullable types",
5
5
  "type": "module",
6
6
  "license": "MIT",