Type predicates
A type predicate is a function whose return type is written param is T; calling it in a guard narrows the argument to T where it returns true. Unlike TypeScript, CruftScript admits a predicate only when its body actually proves the claim, through a primitive type tag or a cast that runs a real shape check.
A type predicate is a function whose return type is written param is T. Calling it in a guard narrows the argument to T in the branch where the call returns true, so you can package a reusable check as a named function.
function isString(v: unknown): v is string { return typeof v === "string" }
if (isString(raw)) { /* raw is a string here */ }
If you have used TypeScript this looks familiar, but there is one large difference. In TypeScript the body of a predicate is taken on faith: you can write param is T over a function that returns true for everything, and the checker believes you. CruftScript does not. A predicate here only narrows when its body actually proves what it claims.
The two kinds the checker admits
The checker accepts a predicate in exactly two forms, and rejects anything else.
A primitive target, backed by the engine's real type tag. When the target is a primitive, the narrowing rests on a check the runtime can genuinely perform, so the body is trusted to do it.
function isNumber(v: unknown): v is number { return typeof v === "number" }
Note a current gap here: while typeof v === "number" works as an if guard, it is not yet accepted as the returned expression of a predicate body. Written as above, the checker rejects typeof v as an unsupported body expression, so this primitive predicate does not compile as written today. The shape describes the intended admitted form; the returned-typeof route is not executable yet.
A non-primitive target that has a real runtime check, written in the canonical witnessing shape. The target must be a type that has a backing check that runs at runtime, and the body must be written so that check is actually exercised:
function isUser(v: unknown): v is User {
const local: User = v as User // this cast runs a real shape check
return true
}
The as User here is not a free assertion. For a target that has a runtime check, the cast inserts that check, so the body cannot reach return true unless the value really passed. The narrowing is honest because the code proves it.
What is rejected
Two shapes are compile errors rather than accepted predicates.
A lying predicate is not expressible. A body that just returns true without running a real check does not narrow. There is no way to write a predicate that grants a type it never verified.
function isUser(v: unknown): v is User { return true } // error: nothing proves it
A non-primitive target with no real runtime check cannot be a predicate at all. If the target type has no backing check that could run, there is nothing to witness, so the checker refuses it. A non-witnessing body over an otherwise valid target is refused for the same reason: the proof is missing.
Why it works this way
A predicate hands out a specific type on the strength of a function's say-so. If the function can lie, the type it grants is worthless, and the failure shows up far from where the bad value entered. CruftScript closes that by requiring the body to carry a real check: a primitive tag the engine can read, or a runtime shape check the cast actually runs. Either the predicate proves its claim or it is not a predicate. This is the same rule that governs casts and narrowing throughout the language, applied to the checks you write yourself.
Limitations
- Only two admitted kinds. A primitive backed by the engine's type tag, or a non-primitive whose target has a real runtime check written in the witnessing shape. Nothing else narrows.
- The body must witness the claim. A non-primitive predicate needs the canonical
const local: T = param as T; return true;shape, where the cast runs a real check. A non-witnessing body is a compile error. - No runtime check, no predicate. A non-primitive target with no backing runtime check cannot be made into a predicate.
- The returned-
typeofprimitive form is not executable yet. A primitive predicate whose body isreturn typeof v === "..."is rejected in a runnable body today, even though the same comparison narrows correctly as anifguard. Treat the primitive-predicate examples as the intended admitted shape, not runnable code.