# KopScript — LLM reference

Complete, dense reference for generating correct KopScript (`.ks`) code. This is a spec,
not a tutorial — see `README.md` for narrative explanation and rationale. Every construct
below is exhaustive: if something isn't listed here, it doesn't exist in the language.
Read the "Does not exist" section before assuming any feature from another language
carries over.

Compiles to plain ES modules.

## CLI

```
ks build <file.ks>          # type-check + emit <file>.js and <file>.js.map next to the source
ks run <file.ks>            # build, then execute with node --enable-source-maps
ks watch <file.ks>          # build, then rebuild on every change to any file in the graph
ks check <file.ks>          # type-check only, no output files
ks fmt <file.ks>             # rewrite to canonical formatting, in place
ks fmt <file.ks> --check     # exit 1 if not already formatted; writes nothing either way
ks build|check <file.ks> --json   # single JSON object on stdout instead of human text
```

`--json` output shape (also what a tool/agent should parse instead of scraping text):

```json
{ "success": false, "diagnostics": [{ "code": "KS4083", "severity": "error", "message": "Class 'Widget' has no member 'Vlaue'", "line": 3, "col": 13, "fix": "Did you mean 'Value'?", "file": "/abs/path.ks" }], "written": [] }
```

`fix` is present only when the checker/parser found something concrete enough to say beyond
the message itself — a real "did you mean 'X'?" against an actually-declared name close
enough to be a plausible typo (unknown member/function/identifier/type — `KS4083`/`KS4062`/
`KS4048`/`KS4008`/`KS4076`/`KS4077`), a missing `await` wherever a type mismatch's actual
side is a `task` (assignment, return, argument, base-constructor-argument sites —
`KS4033`/`KS4034`/`KS4035`/`KS4038`/`KS4051`/`KS4059`/`KS4065`/`KS4072`), or a precise,
always-accurate pointer for other specific mistakes: `match` also accepting an enum subject
(`KS4046`), a stray type-parameter list (`KS2023`), an invalid `match` pattern (`KS4088`),
a `string` `match` missing its trailing `_` arm (`KS4087`), awaiting a non-`task` (`KS4054`),
comparing a non-nullable type to `null` (`KS4060`), an override that doesn't match its base
signature (`KS4024`), an `async` function whose return type isn't `task` (`KS4030`),
assigning to a get-only property outside its constructor (`KS4082`), an unsatisfied generic
constraint (`KS4096`), an uninferable generic function type parameter (`KS4100`), and — in
the template compiler — more than one top-level element (`KS5007`). Absent entirely (no empty string) when nothing that
specific applies — most diagnostics still don't have one, and that's expected, not a gap to
fill in for every code; the full diagnostic-code surface (every `.error("KSxxxx", ...)`
call site in `src/*.ts` — `test/diagnostics.test.ts`'s own uniqueness scan is the
authoritative count, since it's checked by a real test, not a number restated by hand
here) has been reviewed with this same bar.

`written` is the absolute paths actually written (`build` only, and only on success — a
failed `build` writes nothing; `.js.map` paths aren't listed separately, one is written next
to each `.js` path in `written`). Exit code is `0` iff `success` is `true`. A missing entry
file yields `{ success: false, diagnostics: [], written: [], error: "cannot find file '...'" }`.

Source maps are statement-level, not column-level (see `src/sourcemap.ts`, hand-rolled VLQ,
zero added dependencies) — a thrown/uncaught error resolves to the right `.ks` line, but a
specific sub-expression within one line isn't separately mapped.

`ks fmt` (`src/printer.ts`) is single-file (lex+parse only, no `using`-graph/type-checking):
2-space indent, K&R braces, no trailing commas, double quotes only. Two things it does NOT
preserve, deliberately: original class-member order (always reprints as fields → properties
→ constructor → methods, since `ClassDecl` buckets members into separate arrays — see
"Classes" below — so any original interleaving is already gone from the AST) and one-line
collapsed bodies (every body always prints as a full multi-line block). Comments survive it.

## File shape

```ks
using "kopular";            // a package's KopScript declarations (see below)
using "./other_file";       // a same-project file, relative to this one
using "../lib/thing";       // 0+ usings, must be a contiguous prefix at the top

// then any number of top-level statements/declarations, in any order
```

A relative `using` resolves `<path>.ks` relative to the current file and pulls its
`public` top-level classes/interfaces/enums/functions/extern-decls into unqualified scope.
**`using` isn't transitive** — each file lists every file (and package) whose names it
uses. No circular `using` (compile error).

A bare package name (`using "kopular";`) resolves `node_modules/<name>/package.json`
(searching parent directories, like Node) and loads the `.ks` file its `"kopscript"` field
names: the package's declarations, normally `extern`s for its own modules (see `extern`
below). They're in scope exactly like a relative `using`, but the declarations file is
never compiled — each consuming file imports just the bindings it references, directly
from each extern's own `from` module. Errors: `KS3013` package not installed, `KS3014` no
`"kopscript"` field, `KS3015` a declarations file containing anything but externs from
package specifiers and interfaces. To make a package usable this way, ship that `.ks` file
and point `"kopscript"` at it.

## Types

| Type | Notes |
|---|---|
| `number` | one numeric type, no int/float split |
| `string` | |
| `bool` | `true` / `false` |
| `void` | function/method return type only |
| `T[]` | array of `T`; `T[][]` etc. nest freely |
| `ClassName` / `InterfaceName` / `EnumName` | any user-declared named type |
| `(T1, T2) => R` | function type — parenthesized param types, arrow, return type |
| `task` / `task<T>` | see Async below |
| `state<T>` | see Reactive state below |
| `T?` | nullable — see Nullable types below |
| `Box<T>` | a user-declared generic class/interface — see Generics below |

**Does not exist**: `any`/`unknown` as a writable annotation, a `let`/`var` keyword (see
Declarations below — there isn't one), type inference for declarations (every
local/`const`/param/field/return type is written out explicitly), union types, tuples,
structural/duck typing (all typing is nominal). Generics exist and cover multiple type
parameters, single-interface constraints, generic inheritance, and free generic functions
— see the Generics section for exactly what's still NOT supported there (variance,
multiple constraints per parameter, generic methods).

## Declarations

```ks
number x = 5;                 // type-first local var (mutable)
const string name = "Joe";    // type-first const
```

Top-level declarations (function/class/interface/enum/extern/raw-string) default to
**exported** (visible via `using`) unless marked `private`. A top-level bare `number x = 5;`
is a plain statement, not exportable — `using` only pulls in types and functions, never
values.

```ks
private void Helper() { }     // file-local, not visible via `using`
void Exported() { }           // default: visible via `using`
```

### Functions

```ks
number Add(number a, number b) {
  return a + b;
}
async task<number> FetchValue() { return 1; }   // see Async
```

### Classes

```ks
class Dog : Animal, IBark {          // 0-1 base class + 0+ interfaces, comma-separated
  private string name;               // field: no initializer syntax — set in constructor
  public static number InstanceCount = 0;   // static field: initializer REQUIRED (no ctor runs for it)

  public string Name { get; set; }   // auto-property, get-only or get+set
  public string ReadOnly { get; }    // settable only within this class's own constructor

  constructor(string name) : base() {   // base ctor args, if any, go inside base(...)
    this.name = name;
  }

  public virtual string Speak() { return "..."; }     // overridable
  public static Dog Create(string name) { return new Dog(name); }
}

class Puppy : Dog {                  // `override` only means anything in a real subclass
  constructor(string name) : base(name) { }
  public override string Speak() { return "Woof"; }
}

Dog d = new Dog("Rex");
```

- Visibility on members: `public` / `private` / `protected` (default when omitted: `public`).
- `virtual`/`override` only on instance methods, not static, not fields/properties.
- No interface-implementation keyword — implementing is structural-by-declaration: list
  the interface in the base list and provide matching public methods, checked at compile time.
- No nested classes/functions.
- No multiple inheritance (at most one class in the base list; the rest must be interfaces).
- `class Box<T> { ... }` / `class Pair<K, V> { ... }` — one or more type parameters are
  allowed; see Generics below for the full (deliberately scoped-down) rules.

### Interfaces

```ks
interface IShape {
  number Area();               // method signatures only — no fields/properties in v1
}
interface INamedShape : IShape { // interfaces can extend other interfaces (not classes)
  string GetName();
}
interface IContainer<T> {      // interfaces take type parameters too — see Generics
  T Get();
}
```

### Enums

```ks
enum Color { Red, Green, Blue }
Color c = Color.Red;
```

No custom values, no methods on enums.

### `match` expression

```ks
string Classify(string input) {
  return match input {
    "cat", "dog" => "animal",     // comma = OR within one arm
    r"^[0-9]+$" => "number",      // regex pattern, r"..."
    _ => "unknown"                // wildcard REQUIRED for a string subject
  };
}
```

`match` is an expression (has a value) over one of two kinds of subject:

- **`string`** — literal/regex patterns only (not type patterns, not destructuring),
  and always requires a trailing `_` arm, exactly as above.
- **an `enum`** — patterns name a member of that same enum (`EnumName.Member`, comma
  for OR within one arm, same as a string match); no regex patterns (there's nothing
  to run a regex against). **Real exhaustiveness**: covering every declared member
  lets the trailing `_` be omitted entirely — there's nothing left for it to catch —
  and a member missing from BOTH the arms and a trailing wildcard is a compile error
  naming exactly which member(s) are missing:

```ks
enum OrderState { Pending, Shipped, Delivered, Cancelled }

string Describe(OrderState s) {
  return match s {
    OrderState.Pending => "placed",
    OrderState.Shipped => "shipped",
    OrderState.Delivered => "delivered",
    OrderState.Cancelled => "cancelled"
    // no `_` needed — every member is covered; add a 5th enum member later
    // without updating this match, and this stops compiling.
  };
}
```

A partial enum match still works exactly like a string match — cover the cases you
care about and fall back with `_`:

```ks
string UrgencyOf(OrderState s) {
  return match s {
    OrderState.Cancelled => "none",
    _ => "normal"
  };
}
```

`match` still doesn't support type patterns or destructuring for either subject kind
— an enum match's patterns are exactly its own members, nothing more general.

## Statements

```ks
if (cond) { } else if (cond2) { } else { }
while (cond) { }
for (number i = 0; i < n; i = i + 1) { }
foreach (number x in items) { }              // arrays only
break; continue; return; return expr;
try { } catch (string e) { } finally { }     // at most one catch clause; param type is your choice
throw "message";                              // throw takes any expression, not just string
```

`throw` accepts any expression (no base exception/error type to conform to), and a
`catch (T e)` param's type `T` is **not checked against what's actually thrown** — it
compiles to a plain JS `catch (e) { ... }` with zero runtime type discrimination. The
type annotation only affects what `e` type-checks as *inside* the catch block. There's
no way to have multiple catch clauses per type (only one `catch` per `try`), and no
built-in exception hierarchy — `catch (string e)` (matching `throw "message";`) is the
conventional shape used throughout this codebase, but any type is legal.

There is **no ternary/conditional expression** (`if` is a statement only). To get a
conditional *value* inline, use a `match` on a `bool`-shaped case set, or (in Kopular UI
code) the `If()` helper — see the Kopular reference.

## Expressions

Precedence, low → high: `=` (right-assoc) → `||` → `&&` → `==` `!=` → `<` `>` `<=` `>=`
→ `+` `-` → `*` `/` `%` → unary `-` `!` → postfix (`.member`, `(call)`, `[index]`) → primary.

```ks
this
new ClassName(args)
obj.Member
obj.Method(args)
arr[index]
await someTask            // only inside an async function/method body
state(0)                  // constructs a state<T> — see below
$"Hello {name}, you are {age} years old"   // interpolated string
r"^[a-z]+$"               // regex literal (only meaningful as a match pattern)
```

A regex literal's contents pass to the real `RegExp` unprocessed — `\s`, `\d`, `\w`, `\.`,
etc. all mean real regex syntax, not string escapes (only `\"` is special, letting a
literal `"` appear before the closing quote). This differs from every other string-like
literal in the language, which do interpret `\n`/`\t`/`\\`/etc.

### Lambdas

```ks
(number x) => x * x                          // expression body
(number x, number y) => { return x + y; }    // block body
() => this.DoThing()
```

**Every lambda parameter type is required — no inference, no bare `(x) => ...`.** A
lambda's own first parameter cannot itself be a function type (a rare v1 parser
limitation, not a semantic one).

**An expression-bodied lambda may discard a non-`void` result where `() => void` is
expected** — the fire-and-forget shape a `setTimeout`/`setInterval` callback needs:
```ks
public async task DoWork() { ... }
SetTimeout(() => this.DoWork(), 300);   // () => task discarded into () => void — fine
```
This is narrower than "anything is assignable to `void`": a `void`-returning
function's own `return expr;` is still a real error, checked separately — this only
relaxes a lambda's own trailing expression value when nothing will ever read it,
matching the same allowance TypeScript gives a `Promise`-returning function passed
where `() => void` is expected.

### Arrays

```ks
number[] xs = [1, 2, 3];
xs.Length
xs.Push(4)                            // NON-mutating — returns a new array, doesn't modify xs
xs.Map((number x) => x * 2)           // -> array of whatever the callback returns
xs.Filter((number x) => x > 1)
xs.ForEach((number x) => { print(x); })
xs.Find((number x) => x > 1)          // -> T?, null if nothing matches
xs.FindIndex((number x) => x > 1)     // -> number, -1 if nothing matches
xs.Includes(2)                        // -> bool
xs.IndexOf(2)                         // -> number, -1 if not present
xs.Sort((number a, number b) => a - b)  // NON-mutating, comparator always required
xs.Reverse()                            // NON-mutating
xs.Slice(1, 2)                        // both bounds always required, no optional params
xs.Concat(ys)                         // ys: same element type
xs.Join(", ")                         // -> string; number[]/string[]/bool[] element types only
xs.Reduce((number acc, number x) => acc + x, 0)  // (acc, initial) — accumulator type = initial's type
```

`Push`/`Sort`/`Reverse` are non-mutating (return a new array) — the one deliberate
departure from real JS, where all three mutate in place; everything else here compiles
straight to its real `Array.prototype` equivalent. `Reduce`'s argument order matches real
JS (`reducer, initial`, not the other way around).

### Strings

```ks
s.Length
s.Contains(other) / s.StartsWith(other) / s.EndsWith(other)
s.Replace(from, to)
s.Split(sep)          // -> string[]
s.Trim() / s.ToUpper() / s.ToLower()
s.CompareTo(other)    // -> number, negative/zero/positive — usable directly as a Sort comparator
"a" + "b"             // + is also string concatenation
```

A string literal is `"double"` or `'single'` quoted, identical in every way (both
interpret `\n`/`\t`/`\\`/etc.) — pick whichever the other quote character isn't, most
commonly inside a template attribute (already `"`-delimited): `*if="Error != ''"`.

### Async

```ks
async task<number> FetchValue() {
  number x = await SomeOtherAsyncFn();
  return x;
}
async task DoThing() { }   // bare task = "async void" with a value you can await
```

`async` requires (and is required by) a `task`/`task<T>` return type. `await` only
legal inside an async function/method body. No async lambdas. No way to construct a
`task` value from outside an async function body.

### Reactive state — `state<T>`

```ks
state<number> count = state(0);
count.Value                          // read/write the current value
count.Value = count.Value + 1;
count.Subscribe((number v) => { ... }); // called on every future .Value assignment
() => void unsubscribe = count.Subscribe((number v) => { ... }); // stop listening later
unsubscribe();                          // no more calls after this
```

`Subscribe` returns an unsubscribe handle — call it to stop receiving that state's future
changes. Only worth keeping if you'll actually need to stop listening before the
subscriber itself goes away (a component built once for the app's whole lifetime, the
common case, never needs to call it). Not a use of user-declared generics (see below) —
`state<T>` is one of exactly two
hardcoded parametrized forms in the type system (the other is `task<T>`), each with only
ever one built-in meaning, unlike a real generic class/interface.

## Generics — one or more type parameters on classes/interfaces

```ks
class Box<T> {
  public T Value;
  constructor(T v) { this.Value = v; }
  public T Get() { return this.Value; }
}

Box<number> nb = new Box<number>(5);   // type argument required on both the type AND `new`
Box<string> sb = new Box<string>("hi");
print(nb.Get());                        // 5

class Pair<K, V> {
  public K First;
  public V Second;
  constructor(K k, V v) { this.First = k; this.Second = v; }
}

Pair<number, string> p = new Pair<number, string>(1, "a");
```

One or more invariant type parameters per class/interface (real *or* `extern` — see the
`extern` section below), comma-separated: `<T>`, `<K, V>`, `<A, B, C>`, however many the
declaration needs — each optionally constrained to a single interface: `<T : IComparable>`,
`<K : IHashable, V>` (mixing constrained and unconstrained params is fine).
Erased at codegen (JS has no generics either; `Box<number>` and `Box<string>` both
compile to the exact same plain `class Box`), so there's no runtime cost and no way to
inspect a type parameter at runtime.

**Rules:**
- The type argument(s) are **required everywhere** a generic type is named — on the
  variable type (`Box<number>`) and separately on `new` (`new Box<number>(...)`); a bare
  `Box`/`Pair` (no arguments) is a compile error, not "any"/inferred. The count must match
  the declaration exactly — `Pair<number>` (too few) and `Pair<number, string, bool>` (too
  many) are both arity errors, not truncated/padded.
- **Invariant, per slot**: `Box<Dog>` is not assignable to `Box<Animal>` even if `Dog :
  Animal`, and `Pair<number, string>` is not assignable to `Pair<string, number>` — every
  type argument must match exactly, in the same position, not just be compatible.
- Nesting works: `Box<Box<number>>`, `Box<number[]>`, `Box<number>?`, `Box<number>?[]`,
  `Pair<number, Pair<string, bool>>`.
- Inside the class/interface's own body, each type parameter is fully abstract — you can
  store it, return it, pass it around, but an **unconstrained** parameter (no `: IFoo`)
  means you **cannot call any member on a bare type-parameter value** (`this.value.Foo()`
  where `value: T` is a compile error: "Cannot access member 'Foo' on type 'T'"). This is
  correct, not a bug — same as an unconstrained type parameter in C#/Java/TypeScript.

**Constraints**: `<T : IComparable>` lifts the "no member calls" restriction — a member
call on a constrained `T` resolves against the constraint interface's own signature:
```ks
interface IComparable { number CompareTo(); }
class Box<T : IComparable> {
  public T Value;
  constructor(T v) { this.Value = v; }
  public number Compare() { return this.Value.CompareTo(); } // legal — T : IComparable
}
Box<Money> b = new Box<Money>(new Money(5));   // Money must implement IComparable
```
A type argument satisfies a constraint by implementing the interface directly, through an
ancestor (`classImplementsInterface` walks the whole superclass chain), or — inside another
generic body — by already being an equally- or more-constrained type parameter of its own
(`class Wrapper<T : IComparable> { Box<T> MakeBox(T v) { return new Box<T>(v); } }` is
legal without ever naming a concrete type). Checked everywhere a type argument is bound: on
`new`, on a bare generic type reference, and on a generic base in a base list.
**v1 limit**: at most one constraint per parameter (`T : IFoo, IBar` isn't supported), and
the constraint must be an interface, never a class.

**Generic inheritance**: a class/interface base list CAN name a generic base, with its own
type arguments, in either direction:
```ks
class IntBox : Box<number> { }        // concrete — threads a fixed type through
class Container<T> : Box<T> {          // generic — threads its OWN T through
  public T GetAgain() { return this.Get(); }
}
interface INumberContainer : IContainer<number> { }   // interfaces work the same way
```
Member resolution (fields, methods, an inherited constructor via `base(...)` or an implicit
default) composes substitutions correctly however many generic-base links up the chain a
member was actually declared — `Container<number>.Get()` (declared on `Box<T>`, two links
away in a longer chain) resolves to `number`, not the abstract `T`. **v1 limit**: at most
one generic entry across a class/interface's *whole* base list (its superclass, or one
implemented/extended interface — never more than one) — `class Foo<T> : Box<T>,
IContainer<T>` is a compile error ("has more than one generic entry in its base list") even
though either half alone would work.

**Generic functions**: only free/top-level functions, never a method inside a class (a
method still only ever uses its enclosing class's own type parameters):
```ks
T Identity<T>(T x) { return x; }
number n = Identity(5);       // T inferred as number, from the argument
string s = Identity("hi");    // T inferred as string, independently
```
**No explicit type argument at a call site** — `Identity<number>(5)` isn't valid syntax;
`T` is always inferred from the actual argument types by structurally unifying each
declared (possibly abstract) parameter type against its argument's real type — including
through a nested generic type (`T Unwrap<T>(Box<T> b)` infers `T` from `Box<number>`) or a
lambda argument's own explicit parameter type (`void UseCallback<T>((T) => void cb)` infers
`T` from `(number n) => ...`). This is a real grammar constraint, not a missing feature:
`new Box<number>(...)` disambiguates `<` from a comparison only because `new` is a distinct
keyword context — a bare call has no such anchor, so `f<T>(x)` would be genuinely ambiguous
with a chained `<`/`>` comparison. If no argument determines a declared type parameter, or
two arguments would bind it to conflicting types, that's a compile error naming the
parameter, not a syntax feature to reach for. A constrained type parameter (`T :
IComparable`) works exactly like it does on a class — the inferred type must satisfy it,
checked after inference succeeds.

**Extern generic free functions**: an `extern` free function can carry its own type
parameter list too, re-describing a real generic free function from another package
(the same shape `extern class Name<T> { ... }` already gives a generic class):
```ks
extern (T) => string? CombineValidators2<T>((T) => string? v1, (T) => string? v2) from "kopular/forms";
(string) => string? combined = CombineValidators2(Required, TooLong);
```
Inference, constraints, and arity all work exactly the same as a real generic
function's — the extern boundary doesn't lose any of it.

**Does not exist (v1 scope cuts, each deliberate)**:
- **Generic methods.** A class method can't introduce its own new type parameter beyond
  its enclosing class's (generic functions are free-function-only — see above).
- **Variance.** No `out`/`in`/covariance/contravariance — see invariance above.

## Nullable types — `T?`

```ks
string? maybeName = null;               // null literal, any type-position postfix `?`
string?[] arrOfNullable = [null, "x"];  // array OF nullable strings
string[]? nullableArr = null;            // NULLABLE array of strings — order matters
```

`?` is a postfix modifier on any type, erased at codegen (JS already has native `null` —
this is purely a compile-time distinction), **except** that `x == null`/`x != null`
specifically compile to loose `==`/`!=` (every other comparison, including `x == y` between
two nullables, stays strict `===`/`!==`) — so real JS `undefined` (a missed `Array.Find`,
an absent field off `JSON.parse`) counts as null too, without a second check. Assignability
is one-directional: a plain `T`
is always usable where `T?` is expected (widening), but a `T?` is **never** usable where
`T` is expected without narrowing first — accessing a member or index on an un-narrowed
`T?` is a compile error:

```ks
void F(string? s) {
  print(s.Length);      // ERROR: possibly-null type 'string?'
  if (s != null) {
    print(s.Length);     // OK — s is string here, not string?
  }
  print(s.Length);        // ERROR again — narrowing doesn't survive past the if
}
```

**Narrowing is recognized in exactly these shapes** (anything else doesn't narrow):
- `if (x != null) { ... }` — narrows `x` inside the then-branch.
- `if (x == null) { ... } else { ... }` — narrows `x` inside the else-branch.
- `cond1 && x.Foo` where `cond1` is `x != null` — narrows `x` for the right operand only.
- `cond1 || x.Foo` where `cond1` is `x == null` — narrows `x` for the right operand only
  (De Morgan's: reaching the right side of `||` means the left was false).
- **A same-operator chain, not just one leading check**: `if (a && x != null && y != null) { use(x); use(y); }`
  narrows both `x` and `y` in the then-branch (every positive `!= null` check anywhere in
  a top-level `&&` chain narrows); `if (a || x == null || y == null) { ... } else { use(x); use(y); }`
  narrows both in the else-branch the same way, by De Morgan's, using every negative
  `== null` check in a top-level `||` chain. Doesn't cross into a different operator: `a &&
  (b || x != null)` never narrows `x` — a check nested under a different operator doesn't
  unconditionally hold the way a same-operator chain does. The `&&`-right-operand and
  `||`-right-operand cases above chain the same way (`x != null && y != null && x.Foo`
  narrows both for the trailing operand).
- Either operand order works (`x != null` and `null != x` both narrow `x`).

**Narrowing does NOT handle** (a common instinct from other languages — don't rely on
these): an early-return guard clause (`if (x == null) { return; } use(x);` — `x` is
*not* narrowed after the `if`, since that needs control-flow reachability analysis, not
just scope); narrowing a member/property path (`if (this.Field != null)` narrows
nothing — only a bare local/parameter identifier narrows); narrowing surviving
reassignment (assigning a new value to a narrowed variable inside the narrowed block
doesn't invalidate or restore narrowing — avoid reassigning a narrowed variable within
the block). `x` stays narrowed for the rest of a narrowed block, including nested blocks
inside it, since narrowing is scope-based.

Comparing a value to `null` when its type **can't** be null is a compile error, not a
silently-always-false comparison — this is a real bug-catching check, not just a style
rule:

```ks
class Dog { }
Dog d = new Dog();
bool x = d == null;   // ERROR: Type 'Dog' can never be null
```

## `extern` — describing existing JS/npm code

Four forms, always terminated with `;` after the closing brace for the class/enum forms:

```ks
extern number ParseFloat(string s) from "npm-package" as "parseFloat"; // `from` before `as`; omit `from` for an ambient global
extern SomeType globalThing as "actualJsName";                          // extern value, ambient
extern class Widget {
  constructor();
  string label { get; set; }         // property sig — get / get+set
  static number Count { get; }
  void Render();
  virtual void Update();              // lets a real subclass `override` it
} from "npm-package";
extern class Panel : Widget { };     // needs its own `;` (no `from` clause to end it)  // : Base inherits Base's members, and is assignable to it — Base must itself be a declared class (KS4104 if not)
extern enum Status { Loading, Success, Failure } from "npm-package"; // real enum in another module
```

Clause order is always `from "<path>"` then `as "<jsName>"` (both optional, `from`
first if both are present). `from "..."` omitted = ambient global (assumed to already
exist at runtime, e.g. `document`). `as "..."` omitted = the JS-side name matches the
KopScript-declared name exactly. Extern class members use real JS member names verbatim
(camelCase, no rename mechanism). `extern class Name : Base { ... }` inherits `Base`'s
members and is assignable to it — `Base` must be a declared class, real or extern
(`KS4104` for an unknown one); two extern classes are otherwise unrelated unless one names
the other this way.

`extern enum` describes a real enum declared in another module (e.g. re-consuming a
package's own `enum AsyncStatus { ... }` from outside it) — `Name.Member` access and
`match` exhaustiveness both work exactly like a locally-declared enum once it's
registered, since it's the same underlying registration either way. Member order must
match the real enum's own declaration order (enum members are plain 0-indexed numbers
under the hood — see "Enums" above); this is trusted, not verified against the actual
JS object, same trust model as every other `extern` (KopScript trusts the declared
shape, same as a TypeScript `.d.ts` file).

`from "<path>"` isn't only for npm packages — a **relative path** (`"./foo"`, `"../foo"`)
names a same-project, hand-written `.js` file (not compiled from `.ks`) the same way
`using` names a same-project `.ks` file, and `.js` is appended automatically if you leave
it off, matching `using`'s own generated imports (a bare package specifier like
`"some-package"` is left untouched — real Node module resolution needs no extension
there). This is the real, working way to reach genuine JS capabilities KopScript's own
syntax has no expression for at all — a dynamic `import()` for real lazy-loading/code-
splitting being the main one: write a tiny hand-written loader, `extern`-declare a
function matching its shape, call it like any other function returning a `task<T>`:
```js
// dogs_page_loader.js — hand-written, not compiled from a .ks file
export async function LoadDogsPage() {
  const { DogsPage } = await import("./dogs_page.js");
  return new DogsPage();
}
```
```ks
extern task<DogsPage> LoadDogsPage() from "./dogs_page_loader";
// DogsPage's own real dogs_page.js chunk is only ever fetched the first time
// LoadDogsPage() is actually called — not eagerly, at startup, with everything else.
DogsPage page = await LoadDogsPage();
```

`extern class` can carry type parameters, optionally constrained (`extern class Box<T :
IComparable> { constructor(T v); T Value { get; } } from "some-package";`) — identical
rules to a real generic class (see "Generics" above: invariant, erased, generic
inheritance and constraints both included), so a generic type from another package
instantiates and type-checks exactly like a local one (`Box<number>`, arity/invariance/
constraint errors included) — and a real KopScript class can extend a generic `extern
class` with a concrete or threaded-through type argument, same as extending a real generic
base.

An extern *free function* (the first form above) can carry its own type parameter list
too, the same place a real generic free function's goes — right after the name:
`extern (T) => string? CombineValidators2<T>((T) => string? v1, (T) => string? v2) from
"kopular/forms";`. Inference/constraints/arity all work identically to a real generic
function (see "Generics" above) — only an extern *value* declaration (the second form)
can never have one, since there's no call site for a type argument to attach to.

**Never write `async` on an extern function/method signature** — declare its return type
as `task`/`task<T>` directly (`task<string> text();`, not `async task<string> text();`).
`async` only means something for a *body* the checker validates (legalizing `await`
inside it, checking `return` against the unwrapped type); an extern signature has no
body, so the keyword is simply invalid there — a parse error, not a no-op. This applies
equally to a top-level `extern` function and an `extern class` method signature.

`jsName` (the `as "..."` string) can be a dotted path, not just a bare identifier — e.g.
`as "JSON.parse"` — since it's spliced directly into `globalThis.<jsName>` for an
ambient binding. This is the supported way to get a *typed* (unchecked, trust-based)
JSON value: describe the shape as its own `extern class`, then declare a parse function
for it: `extern MyShape ParseIt(string json) as "JSON.parse";` — calling `ParseIt(text)`
returns whatever `JSON.parse` actually parsed, typed as `MyShape` with zero runtime
verification (the same trust model as every other `extern`).

An `extern class` reachable from a real npm package — not same-project `using`, which
only resolves relative `.ks` paths — is how KopScript consumes *any* JS dependency,
including another KopScript-authored package published as JS (e.g. Kopular): describe
exactly the members used, from that package's real module specifier. See Kopular's own
`LLM.md`/README for a live example (`Component`, `Router`, `Http`, ...).

## `raw string` — compile-time file embedding

```ks
raw string HeroHtml from "./hero.html";   // reads the file at compile time, type is always string
```

General-purpose only — for a component's actual markup, use `template from` below instead.

## Templates — `template from` (markup files)

```ks
class Counter : Component {
  public state<number> Count;
  constructor() : base() { this.Count = state(0); }
  public void Increment() { this.Count.Value = this.Count.Value + 1; }
  template from "./counter.html";   // replaces a hand-written Render() entirely
}
```

```html
<!-- counter.html -->
<button (click)="Increment()">Count: {{ Count.Value }}</button>
```

- One `template` per class; a class with both `template` and a hand-written `Render()` is a
  compile error.
- Desugars, between parsing and checking, into the exact same `MethodDecl` AST a
  hand-written `Render()` would produce — building a `VElement` tree as data, no runtime
  template engine, no diffing of its own (Kopular's own `Component` base class does that
  against the tree this produces). `{{ }}`/binding contents are real KopScript, parsed and
  type-checked normally.
- Bindings: `{{ expr }}` (text interpolation, → `VElement.TextContent = InterpolatedStringLiteral`),
  `(event)="stmt"` (→ assignment to one of `VElement`'s four named event fields —
  `OnClick`/`OnInput`/`OnBlur`/`OnChange`; any other event name is a compile error),
  `[prop]="expr"` (→ a plain field assignment for `id`/`className`/`value`, or
  `VElement.SetAttr("prop", expr)` for anything else), static `attr="..."` (`class` aliases
  to `className`).
- **Two-way binding**: `[(value)]="Field"` → `[value]="Field"` + an auto-generated
  `(input)="Field = e.target.value"`. `value` only (`KS5017` otherwise — no other
  `VElement` field has an equivalent "user just changed this" event); `Field` must resolve
  to a simple field reference — a bare name, `this.Field`, or a deeper member-access path
  like `Field.Value` (the way to two-way-bind into a `state<T>`'s own settable `.Value`,
  e.g. for a `Computed2` source) — not a method call or other complex expression (`KS5018`
  otherwise, since there'd be nothing sensible to assign back into). A hand-written
  `Render()` has no equivalent shorthand — it already has direct field/handler access, so
  there's nothing to desugar.
- Structural directives: `*if="expr"` (→ real `if`), `*for="Type varName of expr"` (→ real
  `for..in`; the element type is explicit — no inference, same stance as Generics).
  At most one of `*if`/`*for` per element.
- **Embedding a live child component**: `*mount="expr"` → `VElement.Mount(expr)` (Kopular's
  own `Mountable`/`VElement.Mount` mechanism — same one a hand-written `Render()` uses).
  `expr` must already be a fully-built `Mountable` value (a field, a `*for` loop variable) —
  the template doesn't construct it or pass it props/constructor args; however it got built
  is ordinary KopScript elsewhere. Composes with `*if`/`*for` (unlike them, `*mount` isn't
  mutually exclusive with either — `<li *for="Row r of Rows" *mount="r">` mounts one child
  per loop item, the common case). The element's own tag/attrs are discarded except
  `id`/`[id]` (for list-keying, same as any other child); anything else on a `*mount`
  element — other attrs, event/two-way bindings, children — is a compile error, not a
  silent no-op, since none of it has anywhere to go (the mounted child's own `Render()`
  owns all of its content). The underlying check is `KS5020`/`KS5021`, but like every
  template-body error it's reported wrapped as `KS3004` at the `.ks` file's own
  `template from` declaration (see "Diagnostics" below) — don't pattern-match on
  `KS5020`/`KS5021` directly, match on the message text or on `KS3004`.
  ```html
  <p *mount="Badge"></p>
  <li *for="Row r of Rows" *mount="r"></li>
  ```
- **Scoped component styles**: a class-body `styles from "<path>.css";` (independent of
  `template from` — works with a hand-written `Render()` too) scopes that class's own real
  stylesheet so it only matches elements *that class itself* renders, never a sibling's or a
  child's. Every element `template from` builds gets a `data-kop-scope="<id>"` attribute
  automatically (`<id>` is a stable-per-compile, per-class id — the class name plus a short
  hash, not user-controlled); the referenced `.css` file is rewritten at compile time so
  every selector requires that same attribute (`.title { ... }` → `.title[data-kop-scope="Widget-4f2a1c"] { ... }`,
  every compound in a combinator chain, not just the last — `.a .b` → `.a[data-kop-scope="X"] .b[data-kop-scope="X"]`)
  and spliced into the constructor as one `ScopedStyles.Inject(id, rewrittenCss);` call
  (Kopular's own `vdom.ks` — idempotent, injected once per component TYPE into
  `document.head`, not once per instance). A hand-written `Render()` gets no automatic
  attribute injection (same asymmetry as `[(value)]`/`*mount` — template sugar only), but
  still gets a `protected string ScopeId;` field set for it to apply manually:
  `el.SetAttr("data-kop-scope", this.ScopeId);`. `styles from` on a class with no
  constructor is a compile error (`KS3012`) — there's nowhere to splice the injection call.
  CSS parsing (`src/css_scoping.ts`) is a real tokenizer (works on minified/single-line
  input, handles comments/quoted strings correctly), with the same v1 cuts Angular's own
  `ViewEncapsulation.Emulated` accepts: no CSS nesting (`&`), no `:host`, `:not()`/`:is()`/
  `:where()` arguments aren't recursively rewritten, `@keyframes` names aren't renamed
  (global, can collide across components), any at-rule besides `@media`/`@supports` (which
  recurse) or `@keyframes`/`@font-face` (copied verbatim) is copied through untouched.
- **Auto-subscribe**: a `state<T>` field declared on the class gets `Subscribe((v) =>
  this.Update())` wired automatically when the template reads it — directly, or through the
  class's own methods that a render-time binding calls (followed transitively, so
  `*for="Todo t of Visible()"` subscribes to whatever `Visible()` reads). Event-handler
  bindings aren't followed, and state only *assigned* during render isn't subscribed. State
  on another object (`this.SomeService.Count`) still needs a manual `Subscribe`.
- Exactly one top-level element per template (no auto-wrap — hard error). Text and elements
  can mix under one element (`<label>Email <input></label>` — each text run becomes a
  `VElement.Text` leaf). No pipes, no stacked directives.
- Plain HTML works as written: void elements need no `/>` (`<input>`, `<br>`, `<img>`), and
  bare boolean attributes are empty-string attributes (`<form novalidate>`, `required`).
- Events: `(click)`, `(input)`, `(blur)`, `(change)`, `(submit)` (calls `preventDefault()` for
  you, like Angular's `ngSubmit`).
- `ks watch` tracks the referenced `.html` file as well as `.ks` dependencies.
- Layering note: `template from` is kopscript grammar, but what it desugars *to*
  (`VElement.Create`/`.AppendChild`/`.TextContent`/`.SetAttr`/the named `On*` event fields)
  assumes Kopular's `velement.ks` surface specifically — a deliberate, documented coupling,
  not a generic pluggable target.

## Keywords (reserved, lowercase, exact match)

`using extern raw template styles from as const class interface enum constructor public
private protected static virtual override get set return if else while for foreach in break
continue match this base new void true false null task state async await try catch
finally throw`

Anything else (including PascalCase versions of the above, e.g. `If`, `Match`) is a
valid identifier.

## Naming convention used throughout the ecosystem

Types, methods, and properties: **PascalCase** (`Speak()`, `Render()`, `Name`).
Locals/params: **camelCase**. This is convention, not enforced by the compiler.

## Does not exist (don't reach for these)

Generics beyond one or more type parameters (each with at most one interface constraint),
single-generic-base inheritance, and free (never method-level) generic functions with
inference-only call sites (no `T : IFoo, IBar` multi-constraints, no explicit
`Identity<number>(5)` type arguments, no variance, no more than one generic entry per base
list — see Generics above for what *is* supported) · `any`/`unknown` annotations ·
a `let`/`var` keyword (locals are just `Type name = value;`) · decorators/annotations ·
reflection · type inference on declarations · ternary expression · union/tuple types ·
**object-literal syntax** (`{ key: value }` as a value — this doesn't exist *anywhere* in
the grammar, not just as a type; a JS API whose signature needs one — `fetch`'s options
argument, `addEventListener`'s options object, etc. — can't be called directly from
KopScript at all; see Kopular's `http.ks`/`http_runtime.js` for the one established
workaround, a small hand-written JS shim, not a language feature) ·
structural typing · multiple class inheritance ·
nested classes/functions · method overloading · default/optional parameters · varargs ·
static properties with accessors (static fields only) · interface properties (interface
*methods* only) · a real exception hierarchy or multi-type `catch` (one `catch` per
`try`, its param type is unchecked against what's actually thrown) · a package-import
mechanism (`using` is same-project only; cross-package = `extern`).

**Sharp edge, not just "unsupported":** declaring two methods with the same name in one
class is **not a compile error** — the second declaration silently replaces the first in
the checker's method table, so call sites resolve against whichever was declared last
(and its body is the only one ever emitted). If you want to offer more than one
signature, use different names, not overloading. (Nested function/class/interface/enum
declarations, by contrast, *are* a clean compile error: "Nested ... declarations are not
supported".)

## Common mistakes (seeded from real generation failures)

Real, observed cases where a plausible-looking guess was wrong — not hypothetical gotchas:

- **No implicit `this`.** Every reference to a member of the enclosing class — a field, a
  property, a method call — must be `this.X`, always, everywhere, including inside a lambda.
  `Increment()` inside a method body does **not** resolve to `this.Increment()` the way it
  would in Java/C#/Python; it's an undefined-identifier error unless `Increment` is a real
  free function. (Discovered building the template compiler above — templates need a
  dedicated rewriter, `qualifyThis`, specifically because this isn't automatic.)
- **No `let`/`var`, ever.** Every local is `Type name = value;` — writing `let x = 5;` or a
  bare `const x = 5;` (missing the type) is a parse error, not a lenient inferred form.
- **No explicit type argument at a generic function call site.** `Identity<number>(5)`
  doesn't parse — a bare call has no keyword like `new` to disambiguate `<` from a
  comparison, so type arguments are always inferred from the actual arguments instead.
  Just call `Identity(5)`; if inference can't determine a type parameter, restructure the
  call (e.g. an argument that actually mentions the type) rather than reaching for explicit
  syntax that isn't there.
- **Early-return doesn't narrow a nullable type.** `if (x == null) { return; } print(x.Length);`
  still errors on `x.Length` — narrowing is scope-based (an `if`/`else` block), not
  control-flow/reachability-based. Wrap the rest of the logic in the `if (x != null) { ... }`
  block instead of guard-clause-and-continue.
- **Never write `async` on an `extern` signature.** `async task<string> text();` inside an
  `extern class` is a parse error — declare the return type as `task<string> text();`
  directly; `async` only means something for a body the checker validates, and an `extern`
  signature has no body.
- **No object-literal syntax, anywhere, ever.** Reaching for `{ method: "POST", body: x }` to
  call a JS API that expects one (a `fetch`-style options argument) doesn't compile — there's
  no way to construct that value in KopScript at all. See the `extern`/`raw string` sections
  above and Kopular's `http_runtime.js` for the actual workaround (a small hand-written JS
  shim), not a language feature to reach for.
- **No ternary expression.** `cond ? a : b` doesn't parse. Use `match` on a `bool`, or an
  `if`/`else` assigning to a local declared above it.
- **Two methods with the same name in one class don't error** — the second silently replaces
  the first (see "Sharp edge" above). If a generated program seems to call the wrong
  implementation of something, check for an accidental duplicate name before assuming a
  compiler bug.

## Diagnostics

Every compiler error/warning is `{ code, severity, message, line, col }` (1-based).
`code` is `KS` + a number, stable across compiler versions even when `message`'s wording
changes — match on `code` in tooling, not on `message` text. Ranges by pipeline stage,
never reused once assigned: `KS1xxx` lexer, `KS2xxx` parser, `KS3xxx` module/`using`
resolution, `KS4xxx` checker (the large majority of real errors), `KS5xxx` templates.
CLI output format: `` file:line:col - severity code: message `` plus a source line and a
`^` pointer. `DiagnosticBag.hasErrors` gates whether codegen runs at all — a program with
any error produces no output.

**Template-body errors are the one exception to "match on `code`"**: a `.ks` file's own
diagnostic list has exactly one `line`/`col` per entry, but a template lives in a separate
`.html` file — so a real `KS5xxx` error from that file's own lexer/parser/compiler is
re-anchored to the `.ks` file's `template from` declaration and re-wrapped as `KS3003`
(parse) or `KS3004` (compile), with the file, original line/col, and original message text
embedded in `message` itself (`[path/to/file.html:12:3] <original message>`) and the
original `fix` (if any) carried through unchanged. The original `KS5xxx` code is not
preserved — this is deliberate, not a bug, since a wrapped diagnostic still needs exactly
one `code` for the outer bag. Tooling that wants to distinguish *which* template rule
fired should match on `message` text for anything from inside a template, not `code`.
