Errors¶
candela reports errors in two places: while a program is compiled, and while it
runs. This page describes the classes of both, what triggers them, and the
identifiers a catch clause matches on.
How a report is printed¶
Every error is printed with the offending source line, the span underlined, and a message under the underline. The expected thing is coloured blue, the offending expression red. A compile error also carries a second span when there is a related location worth showing, such as the declaration of the function you called.
A message names a type the way you declared it, so a mismatch on a list of your
own enum reads Value[] rather than the bare word enum.
Compilation stops at the first error. Fixing one and recompiling is how you find the next; there is no list.
candela emits no warnings. Everything it reports stops the program.
Parse errors¶
Raised while the file is read, before any type is known.
| Class | Triggered by |
|---|---|
| Unexpected token | A token that cannot appear where it does, including a missing type name, a bad function or variable name, a struct field name that is not an identifier, and null after is or as, where the report points at == null |
| Unexpected end of file | The file ends inside a construct |
| Unknown token | Characters that do not lex, such as a stray symbol |
| Number literal | An int or a float literal whose value is past what the type holds, or an exponent marker with no digits after it, as in 1e |
| Unclosed delimiter | A (, [ or { that is never closed; the report points at the opener |
| Missing semicolon | A statement not ended with ; |
| Missing separator | Array elements, arguments or parameters not separated by , |
Inline if without else |
An if used as an expression must produce a value on every path |
try without catch |
A try block needs at least one catch clause |
match without arms |
A match with no arms, or with only a _ arm |
match arm |
An expression arm not followed by , before the next arm (match_arm_missing_comma), or _ written as one of a pattern's alternatives (match_wildcard_alternative) |
throw call |
throw("kind"), the call spelling: throw is a statement, throw "kind"; (throw_call) |
| Bad import path | An import path whose extension is neither absent nor .cdl, or the removed import std::string; form |
| Import binding | A path whose last segment is not a name, imported with no as (import_name_not_identifier); as and a list of items on one import (import_alias_with_items); an empty list of items (import_items_empty) |
| Constant arithmetic | Integer division or remainder by a literal zero, an integer raised to a negative literal exponent, or a shift by a literal count outside 0 to 63 |
| Nested declaration | A fn declaration written inside a block instead of at the top level |
| Operator method name | An operator symbol after fn in an impl block that a type cannot define, such as > or &&. The report lists the ones it can and says what derives !=, > and >= |
| Nesting too deep | Expressions, blocks or types nested more than 128 levels deep, counting every enclosing level. Move the inner part into a variable or a function |
| Attribute | An @ name other than cfg, an attribute with nothing after it to apply to, or a @cfg condition that is not a flag, key = "value", not(...) with one condition, any(...) or all(...). See conditional compilation |
| Macro | A macro no expander is registered for, a region the file ends before closing, an expander that rejects the region it was given, an expansion that is not a single expression, or macros expanding into one another more than 32 levels deep. See macros |
Compile errors¶
Raised by the type checker once the file parses.
Unknown names. A variable, function, method, type, struct, enum variant or
namespace that does not resolve. These reports suggest the closest name in scope
when there is one. A name or a namespace that a standard library module the
program does not import declares names the import instead, so sqrt(x) points
at import "std/math"; and fs::read(p) at import "std/fs";. A function or
type the file reaches only through an imported module's name names both ways
to write it: parse(s) after import "std/json"; points at json::parse(s)
and at import "std/json" { parse };. A std function that lost its module's
name from its own is reported with the new spelling: assert_eq points at
assert::eq, random_int at random::int.
A method a map no longer has is no_such_method with the spelling that
replaced it: m[key] = value for insert, m.get(key).unwrap_or(fallback)
for get_or. A renamed string or list method is reported the same way:
index_of for a string's find, split for a list's partition, and
trim_start, trim_end, trim_chars, trim_start_chars and trim_end_chars
for trim_left, trim_right, trim_sequence, trim_sequence_left and
trim_sequence_right. A list's find with an argument that is not a function
is find_takes_a_function, and the help names index_of. A namespace a host or dylib block declares resolves like
any other, so a call it has no function for is reported against the function,
naming the namespace it was looked for in. A function a type in the namespace declares
in its impl block is named with the type's path, so set::new() points at
set::Set<T>::new(). A dot call on a block's name, app.rows(id),
is block_dot_call: a block is reached with ::.
Struct and field errors. Reading a field a struct does not declare, building
a struct literal that supplies an unknown field or omits a required one, and
assigning a value of the wrong type to a field. A literal's ..base of another
type is struct_base_type, and the default of a struct with a
field that has neither a declared value nor an empty one, such as an enum field,
struct_field_no_default. A default function in a struct's impl block that
takes parameters or returns another type is struct_default_signature.
Default::default() is unknown_function, and the help names a bare .. in
the literal and S::default(). See types.
Arity and argument errors. Calling a function with too few or too many
arguments, or with an argument whose type the parameter does not accept, whether
that type was declared with name: type or taken from another call. The report
labels the declaration as well as the call. A function that declares -> Type
and returns something else is reported against the annotation. A call has the
type its function declares, so an argument taken from a call is checked against
the declared return type, not against what the body returns. A dot call that
reaches an impl function declared with no parameters is
method_without_receiver: nothing would receive the value in front of the dot,
and the help names the path call, Type::name(). The reverse, a path call that
reaches an impl function declaring a receiver, is path_call_with_receiver,
and the help names the dot call.
Operator errors. An operator applied to operand types it does not accept,
including mixed int and float arithmetic and a non-bool operand of &&,
|| or !. The report names the operator it is complaining about, and where an
operand is a struct or an enum it also names the method that type would define
to accept the expression. == and != take any pair of built-in values and are
never an error there; on a type that defines its own ==, an operand that
method does not accept is. See operators.
Operator method errors. A method named by an operator symbol carries its own
rules, each with an identifier an editor and an embedder read:
operator_method_unknown for a symbol a type cannot define, raised while the
file is read; operator_method_arity for a parameter count the operator does not
have; operator_method_on_builtin for an operator inside impl int, impl list
or another built-in type; operator_method_receiver for a first parameter
annotated as a type other than the one the impl block names; and
operator_method_return_type for a method the operator cannot take a value from,
which is one handing nothing back or an ==, < or <= not answering a bool.
See methods.
Type errors. The general mismatch: an index that is not an int, indexing
or iterating a type that supports neither, a field access on something that is
not a struct, and the condition of an if, an else if, a while or an if
expression whose type is not bool. See
control flow. A value of another type written
into a variable declared with a type, let n: int = "a"; or a later n = 2.0;,
is variable_type_mismatch; see variables.
Collection literal errors. Arrays and maps are homogeneous, so an element or value of a different type is rejected, as is a duplicate map key or a map key that is not a literal.
Control-flow errors. An if used as an expression with no else branch,
and a function that returns a value on some paths and nothing on others, whether
through a bare return; or by reaching the end of its body. See
functions.
Enum and match errors. An unknown variant, a variant pattern with the wrong
number of payload bindings, a pattern that is not a variant when the scrutinee
is an enum, variant patterns on a value that is not one, and a match that does
not cover every variant. The non-exhaustive report lists the variants you left
out. A qualified pattern is also reported when its qualifier names an enum other
than the matched value's, or names nothing at all; the report says which enum
the pattern reaches and which one the match is on. A ? after a value that is
neither an Option nor a Result is propagate_operand, and one in main or
in a function that does not return the same enum, with the same error type for a
Result, is propagate_return_type; see
error handling. A | alternative that binds
other names, or the same names at other types, than the first alternative of
its arm is pattern_bindings_differ. A match used as a value with no _ arm,
on a value that is not an enum, is match_without_wildcard. See
control flow.
Type test and downcast errors. A downcast the operand's static type rules
out, such as 3 as string, is impossible_downcast; the help names the
conversion. A generic type named after is or as without its type arguments
is type_test_needs_arguments, a variant test on a value that is not an enum
is variant_test_not_enum, and s.parse<T>() with a T other than int,
float or bool is parse_type. A removed spelling is reported with the one
that replaced it: is_int(v) and the other is_* functions with v is int,
is_null(v) with v == null, as_int(v) and the other as_* functions with
v as int, str(x) with string(x), the to_int, to_float, to_bool and
to_string methods with int(x), float(x), bool(x) and string(x), and
s.is_int() and s.is_float() with s.parse<int>() and s.parse<float>().
See types.
Declaration errors. Defining a function name twice. The report shows both
definitions. Two impl blocks that define one method for the same instantiated
generic type are reported the same way.
Generic type errors. Type arguments whose count does not match the declaration, type arguments on a name that declares none, type arguments on a built-in method, a name in a declaration that is neither a type nor one of its type parameters, and a generic type whose own fields name a deeper instantiation of itself without end. See generics.
Import and library errors. An import path that cannot be read, an item
named in an import that the module does not declare
(import_item_not_exported, listing the ones it does), an item that collides
with a name already in scope (import_symbol_collision), two modules bound
under one name (import_name_collision), a dylib library that cannot be
opened, a symbol the library does not export, and a dylib signature naming
a type C has no representation for (no_c_representation). candela check
opens no library, so it reports neither a library it cannot open nor a missing
symbol. See
modules and C libraries.
No entry point. A program is entered through main, so a run, a build and
an embedded compile all report a file that declares none. candela check does
not: compiling a file without a main is how a library entry checks. See
functions.
Warnings¶
A warning reports something the compile could not check, without stopping it.
candela check and candela build print each one as a report and still
succeed; an embedding host reads them from Program::warnings, and the language
server shows them in the editor. Each carries a code like an error does.
| Code | Meaning |
|---|---|
unannotated_host_parameter |
A function in the file being built that nothing in the program calls leaves a parameter bare, and the file declares main. A file with no main is a library, whose importers call its functions, so it gets no such warning. A host calling it by name passes that parameter as any, so its body is checked that way. The warning points at the function's name; annotate the parameter with the type the host passes |
no_host_entry_point |
The body of such a function does not compile with its bare parameters typed any. The message carries the error the body raised and the span points at it. A packaged .cdlb has no entry point for the function, so a host call to it fails as an unknown function |
unraisable_catch_kind |
A catch "kind" in the program's own files names a kind that no built-in error carries and no string literal anywhere in the program, a throw or otherwise, spells. Such a clause never runs, which is what a misspelt kind looks like. The check reads literals only: a kind built at run time and caught by a name the program never writes as a literal is warned about |
Runtime errors¶
Raised while the program runs. Each has a kind, which is the string a catch
clause matches and the string bound to the catch variable. See
error handling.
Collections¶
| Kind | Raised by |
|---|---|
index_out_of_bounds |
An array or string index outside the value |
slice_out_of_bounds |
A slice whose bounds fall outside the value, or whose start is past its end. A slice starting where the value ends is in range and produces an empty one |
unknown_map_key |
Reading a map key that is not present with m[k]. m.get(k) answers None instead |
Arithmetic¶
| Kind | Raised by |
|---|---|
division_by_zero |
Integer division by zero |
modulo_by_zero |
Integer remainder by zero |
negative_exponent |
An int raised to a negative int power, which has no int result |
shift_count_out_of_range |
An int shifted by a negative count, or by 64 or more. An int is 64 bits wide, so the count must be between 0 and 63 |
Float division and remainder do not raise; they produce an infinity or NaN,
and both print as such. A float raised to a negative power does not raise
either. The parser rejects the all-literal forms of these before the program
runs, and a shift is refused wherever its count is a literal out of range; the
kinds above cover the cases where a value is only known at run time.
Calls¶
| Kind | Raised by |
|---|---|
call_depth_exceeded |
A call made with 1000000 calls already standing. The report names the call it stopped at. A recursion that never reaches its base case ends here |
Conversion¶
| Kind | Raised by |
|---|---|
invalid_int |
int() on a string that is not an integer |
invalid_float |
float() on a string that is not a number |
invalid_bool |
bool() on a string that is neither true nor false |
bad_downcast |
v as T on a value that is not a T, including a list or a map holding an element, a key or a value of another type, a condition typed any holding anything but a bool, a return of an any value holding a type the function does not declare, and a return of an any value as a function type when the function it holds leaves a parameter unannotated |
not_a_string |
Joining a value onto a string, or comparing it with <, <=, > or >=, when the value is not a string. A variadic host function is the way this happens: it is not signature-checked, so its closure can return a type its host block does not declare |
json_parse_error |
json::parse on text that is not valid JSON; the message names the reason. Objects and arrays nest to a fixed depth, and text past it is rejected the same way |
Files¶
Every function of the fs module maps the operating
system's failure to one kind:
fs_not_found, fs_permission_denied, fs_already_exists,
fs_is_a_directory, fs_not_a_directory, fs_directory_not_empty,
fs_invalid_filename, fs_invalid_data, fs_file_too_large,
fs_storage_full, fs_read_only_filesystem, fs_out_of_memory,
fs_timed_out, fs_interrupted, fs_deadlock, and fs_other for a failure
none of those names.
Two kinds come from what the call was given rather than from the system:
| Kind | Raised by |
|---|---|
fs_not_a_byte |
fs::write_bytes with an element outside 0 to 255 |
fs_unknown_digest |
fs::digest with an algorithm other than md5, sha1 and sha256 |
Dynamic libraries¶
| Kind | Raised by |
|---|---|
null_byte_in_string |
Passing a string containing an interior null byte to a C function |
c_array_return_type_not_supported |
A dylib signature that returns an array; C does not convey the length |
invalid_return_type |
A return type that has no C representation |
Host functions¶
| Kind | Raised by |
|---|---|
host_fn_error |
A function a host block declares failed. The message names the function and repeats what the host reported |
An embedding program decides when this happens: its closure returns an error instead of a value. Catch it like any other kind, or let it end the run. See embedding.
Your own¶
throw "message"; raises an error whose kind is the string you pass, so
catch "message" matches it. Choose short, stable identifiers for anything you
intend to catch.
Catching¶
A try block runs under the innermost catch. The errors listed above are
caught wherever the block raises them: in an operator, a built-in function, a
throw, or inside a function the block calls, at any depth. When no catch
matches, the error is re-raised to the next enclosing try, and an error that
reaches the top of the program is printed and ends it.
import "std/fs";
fn main() {
try {
let text = fs::read("port.txt");
print(int(text) + 1);
} catch "fs_not_found" {
print("no config");
} catch e {
print("failed: " + e);
}
}
The catch variable holds the kind as a string.
An error raised several frames below the try abandons the calls in between and
resumes at the catch, so a failure deep in a helper is handled where the work
was started. This covers your own functions and the standard library modules
written in candela.
exit() with a non-zero status ends a program without being catchable.
Artifact load errors¶
Reported by candela-vm before a .cdlb runs, and printed as a single line
rather than a source report.
| Class | Meaning |
|---|---|
| Bad magic | The file is not a .cdlb |
| Truncated | The file is too short to hold a header |
| Unsupported version | The artifact was built by a different format version |
| Decode failure | The body does not decode |
| Library open failure | A dynamic library the artifact needs cannot be opened; the message gives the name as written and the filename it resolved to |
| Symbol not found | The library opened but does not export a symbol the artifact needs |
| Missing host function | The artifact declares a host block, which only an embedding runtime can supply |
See artifacts.
Exit behaviour¶
candela and candela-vm exit with a non-zero status after reporting any
error, and with zero when the program finishes. Inside a host process, a
candela built with the embed feature unwinds instead of exiting, so the host
survives and receives a structured diagnostic, and the Program it called into
stays usable for the next call. See embedding.