Reference

Checking

quillo check app.qwe finds mistakes without running the program, and quillo run does the same first. Types are sets of possible types, and code without annotations is any, so only definite mistakes are reported, and annotations make the checks stricter. Its types are sets of twelve kinds (null bool int float dec str list map fn html response error), lists with what they hold and maps with their fields. It reports unknown names (with suggestions), names used before they are defined, names defined twice, names a file did not take, wrong argument counts and types (for builtins too), operators that can never work, division by a written zero, return and error types, effects, view and component props, component code that cannot run in a browser, route mistakes, and calls like last(xs) where a variable of yours named last, known not to be a function, hides the builtin. Also: a parameter, a record's field, or a local fn or state named twice; a pattern's type the program has not (Usr); a let or a match that cannot match what it is given (let [a, b] = [1], match x { }); a lambda called with the wrong number of arguments, by its let name or given to map, filter, reduce, sort_by and the like; a list whose item is not what list<T> takes ([1, "a"] for list<int>); a field request does not have (request.qurey); top-level code calling a function that reads a global defined further down; a name bound twice in one pattern ([x, x]), a name two tested fields bind used ({ user.id: int, order.id: int } => id), and a route binding one name by two of its path, query and request patterns; and every and schedule as the start reads them, their written-out arguments (every(0, f), a cron field 60, @reboot) and a job or tick named twice where both calls surely run (see Scheduled jobs). When a call to a function of your own named like a builtin (notify, first) has the wrong arguments, the error says so: this notify is the program's own, from line 1, which hides the builtin notify.

Its warnings: an unused variable (name it _x to keep it); a function named like a builtin, or a variable named like a module; a line starting -x that does nothing; ||, &&, !, if or ? : on a value known to be a string or a number; SQL put together from values, and an http.* host from a value (see Standard library); a / result given to an int parameter; a helper with no with that may use an effect its with caller lacks; http.* or net inside a transaction that retries; a live loader fed only by a service; a class a sheet styles that nothing writes; a .qwer function that cannot be exported; sleep given a number written between 0 and 1 (sleep takes milliseconds: [`sleep(0.2)`](/docs/reference/sleep/) waits 0.2 ms; for 0.2 seconds write [`sleep(200)`](/docs/reference/sleep/)); c.query(...) on an untyped parameter in a with function without db; a service whose file is not here; a name in a pattern that takes any value and goes unused, or reads like a type (intt =>); an arm that never runs after one that takes any value, or with a value an arm before it has; xs.push(2), sort(xs) or i + 1 alone, which make a value and drop it; a while whose condition nothing in it changes; "Hello ${name}" in a plain string; index_of(...) == -1, a function used as a condition (if ready {), and a path's untyped parameter added to a number; a misspelt attribute or event (clas, @clik), className, and <card> for a view Card; a name imported as a builtin's; a key written twice in a map ({ a: 1, b: 2, a: 3 }, { a, "a": 2 }: the last value is the one kept; a spread before it is no second key); a function written in a route's answer, which cannot be JSON (this route's answer holds a function at `f`), the program's own or one the route's block makes (fn helper() = 2, let f = (x) => x; a call of it is a value); a job or tick named twice where both calls may run. Warnings never make it fail.

quillo check --policy FILE (quillo check main.qwe --policy host.toml) checks the program as it would run under that policy (and the host's and the program's own, which it always reads): it names the files first (quillo: policy host.toml + quillo.policy (effects: db, http, time; no builds; no private addresses)), then what each would refuse, as errors: a builtin needing an effect the policy does not allow ([`udp.listen`](/docs/reference/udp.listen/) needs the net effect, which host.toml does not allow), a plugin it does not list, a Rust file with no //! effects: line under a policy that narrows effects. See The policy.

What it does not find, so a reader is not surprised: an unknown type name in an annotation (u: Usr, when no type Usr exists; it fails at the first call), division by a variable that is zero, == between values that can never be equal, a match that misses a case (it fails when the value comes), and component code that reaches the database through a helper between them (quillo run refuses that before it serves).

quillo check writes its findings to stderr as file:line: error: ... and file:line: warning: ...; with errors it ends quillo: N errors and exits 1; else it writes main.qwe: ok (12 routes, 40 functions, 9 components, in 18 files) to stdout (with the warnings counted) and exits 0. Its two options, given after the program as quillo run's are, are --strict-null (below) and --policy FILE (above).

After a syntax error that a newer Quillo might accept (an unknown character or escape, an unexpected keyword, an unknown word), quillo check, quillo run and the language server say which quillo they are (note: this is quillo 0.6.0 (f1695a3, 2026-10-29), built 2026-10-27 at ...): code written for a newer Quillo fails to parse in an older one, such as an old build left on the PATH. quillo --version names the commit too: QUILLO_COMMIT when the build sets it (as for a source archive, which has no git), else the checkout's. scripts/doctor.sh and doctor.ps1 point out a quillo on the PATH older than the checkout's own build.

It also knows what lists hold and which fields maps have, from what made them: a list or map literal, an annotation, split, range, merge, set. A lambda given to map, filter, find, sort_by, reduce and the like takes what the list holds, and for x in xs too, so [1, 2] | map((x) => x.name) is an error; map gives a list of what its function returns, first an item or null. A map whose fields are known has no others: { title: "Hi" }.titel is an error (did you mean title?), since a missing key would read as null and nothing would fail. Maps that come from elsewhere (the database, JSON, a parameter without an annotation) and {} are open: any field may be there.

quillo check --strict-null app.qwe also warns where a value may be null and null does not work: an argument str | null for a str parameter, count + 1 with count: int | null, a -> str function that may return null. Checks narrow the type, as in TypeScript: after if x == null { return ... }, in x != null ? f(x) : "", x != null && f(x), if x { ... }, and in the arms of a match after null =>. As in TypeScript, xs[i] is an item, not "or null" past the end. A parameter with a default (page: int = 1) is null only if its default may be. Editors get the same warnings with the language server's initializationOptions: { "strictNull": true }.

Thread
15Useful? Like it, or add to its thread below.

Thread 0 posts

On Checking: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.

    Edit this page on GitHub · made from the user guide · questions on Discord or r/quillo