Type narrowing
Narrowing refines a value's type inside a branch by checking it at runtime with typeof, instanceof, or in used as the condition of an if or ?:. When the check passes, the value has a more specific type in that branch. It refines only the positive branch today, not the else side.
Narrowing is how you refine a value's type inside a branch by checking it at runtime. When a check passes, the value has a more specific type in the code that runs under it. CruftScript narrows with three operators — typeof, instanceof, and in — the same runtime checks you would write in JavaScript.
if (typeof x === "string") { /* x is a string here */ }
if (a instanceof Cat) { /* a is a Cat here */ }
if ("kind" in obj) { /* obj has an own property kind here */ }
This is the primary way to use an unknown value or a union value. Neither is usable as a specific type until you have shown, at runtime, what it actually is.
Where narrowing is recognized
The checker refines a value only when one of these operators is the condition of an if or the test of a ?:. Used in those positions, the operator has two faces: it runs as an ordinary JavaScript check at runtime, and the checker narrows the value in the branch where the check is true. The narrowing itself is erased before the program runs. An operator used somewhere the checker does not recognize as a guard does not narrow.
typeof
typeof x narrows x to the named primitive for the tags string, number, boolean, bigint, and symbol. Other tags parse and run but do not refine the type.
function shout(x: string | number): string {
if (typeof x === "string") {
return x.toUpperCase() // x is a string here
}
return "not a string"
}
Narrowing today applies inside the positive branch — the code that runs when the guard is true. The negative/else branch is not refined: CruftScript does not yet subtract the ruled-out case from the value's type in the branch where the check failed, so a value stays at its original union type there. Put each check you rely on in its own positive if branch, and use the narrowed value only inside it.
instanceof
instanceof narrows a value to a class type, but CruftScript does not trust it blindly. It narrows only for a matching class defined in the same program the value could actually be an instance of. This closes a hole that other systems leave open, where a value made in one context can pass an instanceof check against a same-named class from a different context and be wrongly accepted. Here the class identity has to line up for real.
if (a instanceof Cat) {
a.meow() // a is a Cat here
}
in
key in obj checks that a property is present. It narrows, with two conditions worth knowing. The key must be a provably-string key — a dynamic key that the checker cannot prove is a string is refused. And a passing check proves only that the property exists, not what type it holds: the property comes out typed unknown, so you narrow again before you use it.
if ("id" in payload) {
// payload has an id, but its type is unknown
if (typeof payload.id === "number") {
payload.id.toFixed(0) // now it is a number
}
}
An unrecognized guard shape is a compile error rather than a check that quietly does nothing.
Why it works this way
A type you have not verified should be impossible to use as though you had. Narrowing is the honest path from a broad type to a specific one: it makes you run a real check, and it only grants the specific type in the branch where that check passed. There is no compile-time-only assertion that skips the runtime check and no way to force a narrowing the checker cannot back. That is the same discipline behind having no any and no unchecked cast: the type a value claims to have is one a check has actually confirmed.
Limitations
- Only in guard position. Narrowing happens only when the operator is the condition of an
ifor the test of a?:. Elsewhere it does not refine. - Positive branch only. Only the branch where the guard is true is narrowed. The else/negative branch is not refined and exhaustiveness-to-
neveris not derived, so a value keeps its original union type outside the passing branch. typeofcovers five tags. Onlystring,number,boolean,bigint, andsymbolnarrow. Other tags run but do not refine the type.inleaves the propertyunknown. A passingincheck proves the property is present but types itunknown; narrow it again before use. The key must be provably a string.