Skip to content

Compiler diagnostics

Every compile error has a stable code. A code never changes meaning once released. Each entry below gives the cause, an example that produces it, and a fix. The same text is built into the compiler:

Terminal window
lucid explain E0303
lucid explain --json E0303

The ranges are: E00xx files, E01xx lexer, E02xx parser, E03xx names and types, E09xx compiler limits and features not built yet. Codes never change meaning once released.

The examples on this page are wrong on purpose: each one produces its own code.

The path does not name a file that can be read, or names a directory with no lucid.toml at or above it.

Fix: check the path, and run from the directory that the path is relative to.

Source must be UTF-8. The file holds bytes that are not, often from an editor that saved it in a legacy encoding.

Fix: save the file as UTF-8.

Indentation is spaces only, so that every tool sees the same depth.

Fix: replace each tab at the start of a line with 4 spaces. lucid fix does this.

Each block is exactly 4 spaces deeper than the line that opens it.

if true
print "x"

Fix: indent the block by 4 spaces more than its opening line.

The character starts no token in Lucid.

let a = 1 $ 2

Fix: remove the character, or put it inside text.

A " starts text, and the line ends before the closing ".

print "abc

Fix: close the text on the same line, or use """ for text over many lines.

Text knows only the escapes \n, \t, \r, \\, and \".

print "a\qb"

Fix: write \\ for a backslash, or use one of the known escapes.

{ and } in text mark interpolation, so a lone } is ambiguous.

print "a}b"

Fix: write }} for a literal }, and {{ for a literal {. lucid fix writes }}.

A number is digits, with _ only between digits, an optional fraction, and an optional exponent. A prefix such as 0x needs digits after it.

let a = 0x

Fix: complete the number, as in 0xff, or remove the stray part.

Text over many lines starts on the line after """.

print """abc
"""

Fix: end the line after the opening """, and start the text on the next line.

The parser expected one kind of token and found another. The message says what it expected.

let = 1

Fix: write what the message expects at that place.

A note names the usual cause in four places: a { in text that starts an interpolation, where {{ gives a literal brace; a match arm with no ->; a nested match written after -> on the same line, which needs its own indented block; and a , inside (), since there are no tuples.

Only a name, a field, or an index can take a new value.

1 + 2 = 3

Fix: assign to a name, as in let a = 1 + 2.

E0203: match used as a value has a block arm

Section titled “E0203: match used as a value has a block arm”

A match that gives a value needs one expression in each arm.

let b = 1
let a = match b
_ ->
print "x"

Fix: make each arm one expression, or use a match statement that assigns to a var.

a < b < c reads differently in different languages, so Lucid rejects it.

let a = 1 < 2 < 3

Fix: join comparisons with and, as in 1 < 2 and 2 < 3.

E0205: Nesting over 128 levels, or over 1024 operators in one expression

Section titled “E0205: Nesting over 128 levels, or over 1024 operators in one expression”

The compiler limits depth so that deep input cannot overflow its stack.

Fix: split the expression or the blocks into smaller tasks or named values.

E0206: Declaration only allowed at top level

Section titled “E0206: Declaration only allowed at top level”

use, type, choice, trait, test, and extern tasks sit at top level only.

if true
use std.fs

Fix: move the declaration to the top level of the file.

No name in scope has this spelling. A note names a close match, if one exists.

let total = 1
print totl

Fix: correct the spelling, or define the name before its use. If one name in sight is a close spelling, lucid fix renames to it.

No type in scope has this name.

let a: Thing = 1

Fix: correct the spelling, declare the type, or use the module that has it.

A value has one type where another is needed. The message names both.

let a: int = "one"

Fix: give a value of the needed type, or convert it, as in text.to_int() or int.to_text().

A call that gives no value, such as push, send, close, or update, used as a value is E0303 too, and so is .pop() or .receive() inside a larger expression. Make the call a statement, or the whole value of let, var, return, or an assignment. The test is channel_form.

E0304: Name is already defined in this scope

Section titled “E0304: Name is already defined in this scope”

Two declarations in one scope use the same name.

let a = 1
let a = 2

Fix: rename one of them, or use var and assign the new value.

A type named like a built-in type or trait, such as Order or int, also gets this error. The message names the built-in it clashes with.

The target of an assignment or change is a let, a parameter that is not change, or a slice.

let a = 1
a = 2

Fix: declare it with var, or mark the parameter change.

A task sees top-level let values, but not var ones, so that every change shows in a signature.

var count = 0
task f() -> int
return count

Fix: pass the value as a parameter, with change if the task changes it.

E0307: Arguments or parameters are in the wrong form

Section titled “E0307: Arguments or parameters are in the wrong form”

A call has missing, extra, repeated, or misnamed arguments, or a parameter has no default after one that has one.

task f(a: int) -> int
return a
print f()

Fix: match the call to the parameters, by position or by name.

Only a task or a task value can be called.

let a = 1
print a(2)

Fix: call a task, or remove the parentheses.

A task with a result type must return on every path.

task f(a: bool) -> int
if a
return 1

Fix: add a return at the end, or an else branch that returns.

Top-level code is not a task, so it has nothing to return from.

return

Fix: remove the return, or move the code into a task.

These only work inside while or for, in the same task.

break

Fix: remove it, or move it into a loop.

A literal is outside the range of its type.

let a = 9223372036854775808

Fix: use a smaller number, or a type with a larger range, such as u64 or float.

A parameter or field default must be a literal value, or an empty [] or {}, so that a signature tells everything.

task f(a: int = 1 + 1)
return

Fix: write the value as one literal, as in a: int = 2.

E0314: Empty list or map has no type to take

Section titled “E0314: Empty list or map has no type to take”

[] and {} need a type from an annotation or from where they go.

let a = []

Fix: annotate it, as in let a: list[int] = [].

E0315: Alias is defined by itself, or a type holds itself with no way to end

Section titled “E0315: Alias is defined by itself, or a type holds itself with no way to end”

A recursive type needs a variant or an optional field that ends it.

type Loop
next: Loop

Fix: make the field optional, as in next: Loop?, or add a choice variant with no fields.

Every variant or value must have an arm, so that adding a variant shows each place to update.

choice Light
Red
Green
let light = Red
match light
Red -> print "stop"

Fix: add the missing arms, or a final _ arm.

An earlier arm already covers everything this arm would.

let a = 1
match a
_ -> print "any"
1 -> print "one"

Fix: remove the arm, or move it above the arm that covers it.

The pattern names an unknown variant, has the wrong number of names, binds a type or variant name, holds {} in text, or names a type that the value never has.

choice Shape
Circle(radius: float)
Square(side: float)
let shape = Square(side: 1.0)
match shape
Circl(r) -> print r
_ -> print "other"

Fix: spell the variant as declared, with one name per field.

E0319: Type argument cannot be told from the values

Section titled “E0319: Type argument cannot be told from the values”

A generic task must find each type parameter from its arguments, or be given it.

task make[T]() -> list[T]
return []
let a = make()

Fix: give the type in brackets, as in make[int]().

E0320: Type does not have a trait that a bound or operator needs

Section titled “E0320: Type does not have a trait that a bound or operator needs”

An operator or a bound needs a trait, such as Equal for == and in, or Order for <.

task has[T](value: T, items: list[T]) -> bool
return value in items

Fix: add the bound, as in [T: Equal], or use a type that has the trait.

A generic name takes a fixed number of type arguments.

task plain(a: int)
return
plain[int](1)

Fix: give as many type arguments as the declaration has parameters.

E0322: Type form where a value belongs, or a value where a type belongs

Section titled “E0322: Type form where a value belongs, or a value where a type belongs”

A type such as int? was used as a value, or a value was used in type brackets.

let a = 1
print a?

Fix: put a type only in an annotation or in brackets, and a value only in an expression.

E0323: Task named after a derived task or operator has the wrong form

Section titled “E0323: Task named after a derived task or operator has the wrong form”

A hand-written show, equal, compare, or hash replaces a derived one, so it must have that task’s parameters and result.

type Point
x: int
task show(self: Point) -> int
return self.x

Fix: use the form of the derived task, here task show(self: Point) -> text, or pick another name.

E0324: Lambda or channel has no type to take from its context, or a lambda has try and no result type from it

Section titled “E0324: Lambda or channel has no type to take from its context, or a lambda has try and no result type from it”

A lambda takes its parameter types from where it goes, such as a parameter of task type.

let double = n => n * 2

Fix: annotate the name, as in let double: task(int) -> int = n => n * 2, or write a nested task.

channel(16) with no type and no context to take one from is E0324 too. Fix: write channel[int](16).

E0325: Lambda or nested task captures a name that can change

Section titled “E0325: Lambda or nested task captures a name that can change”

A capture copies a value, so capturing a var would hide later changes.

var n = 1
let add: task(int) -> int = m => m + n

Fix: copy the value into a let first, and capture that.

E0326: A value is used while something changes it

Section titled “E0326: A value is used while something changes it”

Two change arguments overlap, a statement uses what it changes, a for body changes what it visits, or a value is read while a for change loop holds it.

task swap(change a: int, change b: int)
let c = a
a = b
b = c
var x = 1
swap(change x, change x)

Fix: pass separate values, or copy one into a new name first.

E0327: try or else on a value with no error type or none to act on, or with nothing left after it

Section titled “E0327: try or else on a value with no error type or none to act on, or with nothing left after it”

try and else act on a value whose type has an error or none in it.

let a = 1 else 2

Fix: remove the try or else, since the value cannot fail.

A result that may be an error must be handled, so a failure is never lost.

use std.fs
fs.write_text("out.txt", "hi")

Fix: handle it with try, else, or a match.

A test name is plain text, and new in its file, so that a report names one test.

test "adds"
expect 1 + 1 == 2
test "adds"
expect 2 + 2 == 4

Fix: give each test its own name, with no {}.

E0330: A call to an extern task or .pointer() is outside an unsafe block

Section titled “E0330: A call to an extern task or .pointer() is outside an unsafe block”

Foreign code can break every guarantee, so each use is marked.

unsafe extern task strlen(text: pointer[byte]) -> int
let bytes = "abc".bytes()
print strlen(bytes.pointer())

Fix: put the call in an unsafe block with a comment that says why it is safe.

E0331: An extern task has a form that C cannot take

Section titled “E0331: An extern task has a form that C cannot take”

An extern task takes and gives only numbers, bool, and pointer[byte], sits at top level, is not used as a value, and has one form in the whole program. .pointer() works only on a list[byte] name.

unsafe extern task strlen(text: text) -> int

Fix: use a type that C can take, such as pointer[byte] from .pointer().

The comment on the line before an unsafe block says why the code in it is safe.

unsafe extern task abs(n: i32) -> i32
unsafe
print abs(-1)

Fix: add a # comment on the line before unsafe.

E0333: A private name or task with self is used from another module

Section titled “E0333: A private name or task with self is used from another module”

A module shares only what is marked public.

Fix: mark the declaration public, or use what the module already shares.

E0334: The file of a use does not exist, or is spelled with other letter case

Section titled “E0334: The file of a use does not exist, or is spelled with other letter case”

A use ./a.b reads a/b.lcd from the directory of the file. Letter case must match the disk, so the program works on every system.

Fix: correct the path, or match the letter case of the file on disk.

E0335: Modules import each other in a cycle

Section titled “E0335: Modules import each other in a cycle”

Imports must form a tree, so a module can load with no partial state.

Fix: move what both modules need into a third module that both import.

E0336: An imported module holds something other than declarations and literal let values

Section titled “E0336: An imported module holds something other than declarations and literal let values”

Only the entry file runs code, so importing a module never has an effect.

Fix: move the statements to the entry file, or into a task.

E0337: A use with no ./ names something that is not a dependency of the file’s package

Section titled “E0337: A use with no ./ names something that is not a dependency of the file’s package”

use name means std or a dependency in the package’s own lucid.toml.

Fix: add the dependency to lucid.toml, or write ./ for a local file.

The manifest has bad syntax, a form outside the supported TOML, an unknown key, a bad name or version, or a files or env item that is empty or holds a control character, such as the line break that "\n" makes.

Fix: correct the line the message names. A manifest holds [package] with name and version, and [dependencies].

E0339: A package has no entry file or two, or a dependency is wrong

Section titled “E0339: A package has no entry file or two, or a dependency is wrong”

A package has src/main.lcd or src/lib.lcd, not both. A use ./ cannot reach a file under another lucid.toml, since that file would have the other package’s permissions. A dependency must exist, have the name it is given, be a library, and not share a name with another package. lucid run cannot run a library.

Fix: follow the message: add or remove an entry file, or correct the dependency’s path or name.

E0340: A package uses a permission it does not hold

Section titled “E0340: A package uses a permission it does not hold”

A package’s use of a std module, its unsafe block or extern, or a dependency’s permissions go beyond what its lucid.toml grants. A dependency may hold only what its allow entry names, a package may only allow what it holds, and a dependency’s files and env lists must fall inside its parent’s. See “Package permissions” in docs/security.md.

For example, use std.fs in a package whose lucid.toml has no files permission. The tests are in tests/package/perm_*, since they need a manifest.

Fix: add the permission to [permissions] in the package’s lucid.toml, as files = ["./notes"], or to the allow entry of the dependency that needs it.

E0341: spawn is not given a call of a task, or is given a change argument

Section titled “E0341: spawn is not given a call of a task, or is given a change argument”

spawn runs one call of a task on a new thread, by decision 171. A built-in task, such as .len(), is not a task call, and a change argument would let two threads hold one variable.

let job = spawn 1 + 2

Fix: spawn a call of a task, as spawn add(1, 2). For a change argument, pass the value, and return the new one, which .wait() gives.

E0901: Parses, but cannot run at this milestone

Section titled “E0901: Parses, but cannot run at this milestone”

The feature is in the language, but not built yet, or not built yet for lucid build --target web. lucid check does not report this code.

let a = [1, 2]
let b = {1: 2}[0..1]

Fix: see docs/roadmap.md for when it arrives, and use a built feature until then.

E0902: Task frame is too large for the stack check

Section titled “E0902: Task frame is too large for the stack check”

A task’s frame is over 512 KiB, or over half the stack for top-level code. Only code generation finds this, so lucid check does not report it.

Fix: split the task into smaller tasks.

Over 20,000 instances, or a type argument of over 256 parts, usually from a generic task that calls itself with a larger type.

task grow[T](value: T) -> int
return grow([value])
print grow(1)

Fix: call the task with the same type it was given, or remove the type growth.

An extern task must be in the C runtime or kernel32.

unsafe extern task no_such_symbol_anywhere(x: int) -> int

Fix: correct the name, or use a task that one of those libraries has.

A user extern task or .pointer() is in a program built with lucid build --target web. WebAssembly in a page reaches no C library, by decision 162. Only code generation for the web finds this, so lucid check and lucid run do not report it. The test is web_rejects_foreign_code.

Fix: build the program without --target web, or move the foreign code out of what the page uses.

A program that uses std.dom ran with lucid run or lucid test, or was built without --target web. A page needs a browser, by decision 162. lucid check does not report this code.

use std.dom
print "page"

Fix: build it with lucid build --target web, and open the page that loads the .js file.

E0907: spawn, std.net, or std.http cannot run on the web

Section titled “E0907: spawn, std.net, or std.http cannot run on the web”

A program built with lucid build --target web uses spawn, use std.net, or use std.http. Threads in WebAssembly need shared memory, which browsers allow only on pages with cross-origin isolation headers, and a page cannot open a socket, by decision 176. Only a build for the web finds this, so lucid check and lucid run do not report it. The test is web_rejects_spawn.

Fix: build the program without --target web, or do the work in one thread. A page talks to a server with the browser’s own requests.