Errors & error codes

Every diagnostic code (E1000–E5001) Falx's lexer, parser, runtime, module loader and CLI can produce -49 in total. Each one below was actually triggered against the interpreter to confirm its exact message and behaviour, not inferred from source alone.

Every diagnostic on this page is currently severity error. The registry supports a warning level and reserves a note level, but every code that's actually wired up today uses error -there is no warning-only diagnostic in Falx yet. So "how serious is it" has one answer for almost everything here: encountering any of them stops the program immediately, the same as any other halting error, aside from the handful explicitly marked Reserved or Internal guard below, which can't currently occur at all.

Variables, constants & functions

E1000   Undefined variable Fatal -halts execution

Message shape: undefined variable '<name>'

Something tried to read a variable that was never declared with let or const anywhere in scope -including a simple typo in a name that was declared.

output(doesNotExist);

→ e.flx:1:8: error[E1000]: undefined variable 'doesNotExist'

How to prevent it: Declare the variable before using it, and double-check spelling/case -Falx names are case-sensitive.

E1004   Assign to constant Fatal -halts execution

Message shape: Cannot assign to constant '<name>'

A variable declared with const was reassigned. const only blocks reassignment of the variable itself -see the note below if the value is a container.

const x = 1;
x = 2;

→ e.flx:2:3: error[E1004]: Cannot assign to constant 'x'

How to prevent it: Use let if the variable needs to change. Note that const only protects the binding, not the contents -const arr = [1]; arr.push(2); is perfectly legal, since you're mutating the array, not reassigning arr itself.

E1005   Variable already exists Fatal -halts execution

Message shape: Variable '<name>' already exists

A second let/const declared a name that already exists in the same scope. This is not the same as shadowing in a nested block (see Control flow) -that's allowed. This is specifically re-declaring in the same environment.

let x = 1;
let x = 2;

→ e.flx:2:5: error[E1005]: Variable 'x' already exists

const x = 1;
let x = 2;

→ e.flx:2:5: error[E1005]: Variable 'x' already exists

How to prevent it: Just assign to the existing variable (x = 2;, no let/const) instead of re-declaring it, or pick a different name.

E1031   Function already exists Fatal -halts execution

Message shape: Function '<name>' already exists

The same restriction as E1005, specifically for func declarations: a function name can't be declared twice in the same scope.

func f = define() { return 1; }
func f = define() { return 2; }

→ e.flx:2:6: error[E1031]: Function 'f' already exists

How to prevent it: Give the second function a different name, or remove the duplicate declaration.

E1022   Duplicate parameter Fatal -halts execution

Message shape: Duplicate parameter '<name>'

Two parameters in the same function declaration share a name.

func f = define(a, a) { return a; }

→ e.flx:1:18: error[E1022]: Duplicate parameter 'a'

How to prevent it: Rename one of the parameters -every parameter name in a single define(...) must be unique.

Types, operators & conversions

E1002   Runtime error (generic) Reserved -not currently raised

Message shape: varies

The registry's fallback definition for the base FalxRuntimeException class itself, for whenever a runtime problem doesn't match any of the more specific exceptions below. Verified unreachable: nothing in the current interpreter raises the base class directly -every runtime failure uses one of the specific codes on this page instead.

How to prevent it: Not applicable -you should never actually see this code today. If you do, it likely means a future interpreter change introduced a new, not-yet-specific error path.

E1006   Cannot multiply by value Fatal -halts execution

Message shape: Cannot multiply <value> by <value>.

The * operator was used on a combination of types that don't define multiplication with each other (numbers, and number-by-string repetition, are the only combinations that work -see Arithmetic).

output({} * 2);

→ e.flx:1:9: error[E1006]: Cannot multiply {} by 2.

How to prevent it: Only multiply numbers, or a number by a string (which repeats the string). Maps, arrays, booleans and null don't support *.

E1007   Cannot divide by value Fatal -halts execution

Message shape: Cannot divide <value> by <value>.

The / operator was used where one side isn't a number. This is different from dividing by zero -see E1003 below for that specific case.

output(true / 2);

→ e.flx:1:11: error[E1007]: Cannot divide true by 2.

How to prevent it: Only divide numbers.

E1003   Division by zero Fatal -halts execution

Message shape: cannot divide by zero

Both sides of / were numbers, but the divisor was 0 (integer or float -both are caught).

output(5 / 0);

→ e.flx:1:9: error[E1003]: cannot divide by zero

output(1.0 / 0);

→ e.flx:1:11: error[E1003]: cannot divide by zero

How to prevent it: Check the divisor before dividing: if (b != 0) { output(a / b); }.

E1008   Cannot add with value Fatal -halts execution

Message shape: Cannot add <value> to <value>

The + operator was used on a combination that doesn't define addition. Numbers add with numbers; strings concatenate with almost anything (see Concatenation) -but note the message's order: this specifically fires when the left side doesn't know how to combine with the right.

output(true + 5);

→ e.flx:1:14: error[E1008]: Cannot add 5 to true

How to prevent it: Put a string first if you're building text ("" + value), or make sure both sides are numbers for arithmetic.

E1019   Cannot subtract from value Fatal -halts execution

Message shape: Cannot minus <value> from <value>

The - operator (binary subtraction, not unary negation) was used where one side isn't a number.

output(true - 1);

→ e.flx:1:14: error[E1019]: Cannot minus 1 from true

How to prevent it: Only subtract numbers. There's no string/array "remove" meaning for - in Falx.

E1009   Cannot mod with value Fatal -halts execution

Message shape: Cannot mod <value> by <value>

The % operator was used where one side isn't a number.

output(5 % "x");

→ e.flx:1:11: error[E1009]: Cannot mod 5 by x

How to prevent it: Only use % between two numbers.

E1010   Cannot compare to Fatal -halts execution

Message shape: Cannot compare '<type>' with '<value>'

One of < <= > >= was used on a type that doesn't define ordering -only numbers support these. This is a common surprise: unlike ==, ordering comparisons don't work on strings at all. See Comparison.

output("a" < "b");

→ e.flx:1:12: error[E1010]: Cannot compare 'string' with 'b'

output(true > false);

→ e.flx:1:13: error[E1010]: Cannot compare 'boolean' with 'false'

How to prevent it: Only use < <= > >= between two numbers. For strings, compare via a property like .length, or write your own comparison logic.

E1011   Cannot convert to type Fatal -halts execution

Message shape: Cannot convert '<type>' to <target>

Something needed a boolean and the value had no boolean conversion. This is the error you get from while, assert(), and &&/|| when handed a number, array, map, null, tuple, or struct instance -none of those define a boolean conversion. Critically, plain if does not raise this for the same values; see the callout below.

while (5) { output(1); }

→ error[E1011]: Cannot convert 'number' to bool

Why doesn't if (5) {...} raise this? Because if never calls the boolean conversion at all -it only recognises literal true, and silently treats everything else (including a number that would raise E1011 in a while) as false. See Truthiness.

How to prevent it: Give these positions an actual boolean -a comparison or equality check -rather than a raw value. Full explanation, including why if behaves differently from while/&&/|| here, on Truthiness.

E1015   Cannot evaluate value Fatal -halts execution

Message shape: Cannot evaluate '<left>' and '<AND/OR>'.

Raised by &&/|| specifically (logical expressions), when the left-hand side has no boolean conversion -a sibling of E1011 for this one operator pair. The message is a little cryptic: the second quoted part is literally the word AND or OR, not a value.

output(5 && true);

→ e.flx:1:10: error[E1015]: Cannot evaluate '5' and 'AND'.

How to prevent it: Make sure both sides of &&/|| are actual booleans (comparisons, equality checks, or other logical expressions) -see Logical operators.

E1030   Cannot invert value Fatal -halts execution

Message shape: Cannot invert '<value>'

Unary ! was used on a value with no boolean conversion.

output(!5);

→ e.flx:1:8: error[E1030]: Cannot invert '5'

output(!null);

→ e.flx:1:8: error[E1030]: Cannot invert 'null'

How to prevent it: Only negate an actual boolean, or a string that is exactly "true"/"false" (see Truthiness) -not a number, array, map or null.

E1026   Unexpected type Internal guard -not reachable from Falx code

Message shape: varies (internal)

An internal check inside several built-in string/array/map methods, guarding against the method's this not being the type it expects. It's wired to real call-sites in the interpreter -but every one of those call-sites is only reachable if a method got detached from its owner and rebound to a different type, which isn't something Falx script syntax can actually do (a bound method stays bound to the value it came from, even if you store it in a variable first). Verified unreachable by trying the obvious ways to trigger it.

How to prevent it: Not applicable from normal Falx code today.

Properties, indexing & collections

E1012   Cannot access property Fatal -halts execution

Message shape: '<value>' has no property '<name>'

Dot access read a field or method name that doesn't exist on a struct instance or module. Note this is struct/module-specific behavior -the same situation on a map just returns null instead of raising.

struct P { x; }
let p = P();
output(p.notAField);

→ e.flx:3:10: error[E1012]: 'p' has no property 'notAField'

How to prevent it: Check the struct's field/method names for typos, and remember structs are strict about this while maps are lenient.

E1014   Cannot set property Fatal -halts execution

Message shape: '<value>' has no settable property '<name>'

A dot-assignment tried to set a field that isn't declared on the struct. Unlike maps (where m.newKey = x always works, adding the key), structs only allow assigning to fields that were declared in the struct { ... } body.

struct P { x; }
let p = P();
p.newField = 5;

→ e.flx:3:12: error[E1014]: 'P' has no settable property 'newField'

How to prevent it: Declare every field the struct will ever need up front in its body -you can't add new fields to an instance later the way you can with a map.

E1013   Object is not indexable Fatal -halts execution

Message shape: '<type>' is not indexable

[] was used on a type that has no concept of indexing -only strings, arrays, maps and tuples are indexable.

let x = 5;
output(x[0]);

→ e.flx:2:9: error[E1013]: 'number' is not indexable

let s = "hi";
s[0] = "h";

→ e.flx:2:2: error[E1013]: 'string' is not indexable (raised when assigning to a string index -strings are immutable)

How to prevent it: Only index strings, arrays, maps and tuples -and remember strings reject writing to an index even though reading works (see E1028 below for the same restriction on tuples).

E1016   Invalid index (generic) Reserved -not currently raised

Message shape: Index '<index>' is invalid for structure with size '<size>'

The base definition shared by the array/string/map-specific variants below. Verified unreachable on its own: every actual out-of-range index goes through one of E1017/E1018/E1034 instead, each of which fills in a more specific type name.

How to prevent it: Not applicable -see E1017, E1018 or E1034 instead.

E1017   Invalid index on array Fatal -halts execution

Message shape: Index '<index>' is invalid for array with size '<size>'

An array (or, confusingly, a tuple -see the warning below) was indexed with a number outside its valid range.

let a = [1, 2, 3];
output(a[5]);

→ e.flx:2:9: error[E1017]: Index '5' is invalid for array with size '7'

The size (and sometimes the type name) in this specific message is unreliable -a confirmed bug, not a documentation error. Tracing it down: the size shown is actually len() of the array's printed representation (e.g. "[1,2,3]", 7 characters) rather than its element count, because of how the interpreter re-wraps this specific exception. It also mislabels a tuple as "array" for the same reason, since tuples reuse this array bounds-check internally. The bounds check itself is still correct -only the numbers/type name quoted in the message text are wrong. Trust the location the diagnostic points at, not the size or type it states.

How to prevent it: Check .size before indexing with a computed value, and remember negative indices count from the end (a[-1] is the last element) rather than being automatically out of range.

E1018   Invalid index on string Fatal -halts execution

Message shape: Index '<index>' is invalid for string with size '<size>'

A string was indexed with a number outside its valid range. Unlike E1017, the size quoted here is accurate -strings happen not to trigger the representation bug described above.

let s = "hi";
output(s[10]);

→ e.flx:2:9: error[E1018]: Index '10' is invalid for string with size '2'

How to prevent it: Check .length before indexing with a computed value.

E1034   Invalid index on map Fatal -halts execution

Message shape: Index '<key>' is invalid for map with size '<size>'

Raised specifically by .remove(key) when the key doesn't exist -not by reading a missing key with [] or dot access, both of which just return null (see Maps). This asymmetry is worth remembering: reading a missing key is silent, removing one is not.

let m = {a: 1};
m.remove("nope");

→ e.flx:2:10: error[E1034]: Index 'nope' is invalid for map with size '1'

How to prevent it: Check .contains(key) before calling .remove(key) if the key might not be present.

E1023   Invalid index type Fatal -halts execution

Message shape: Cannot convert '<value>' to int

An array or tuple was indexed with something that isn't (and can't be converted to) an integer -arrays and tuples only accept numeric indices, unlike maps which accept any key type.

let a = [1, 2, 3];
output(a["x"]);

→ e.flx:2:9: error[E1023]: Cannot convert 'x' to int

How to prevent it: Use a number to index an array or tuple. If you want string keys, use a map instead.

E1029   Destructuring count mismatch Fatal -halts execution

Message shape: Cannot destructure a tuple with <n> elements into <m> variables

A tuple destructuring pattern had a different number of variables than the tuple has elements. Falx never truncates or pads -the counts must match exactly.

let (a, b) = (1, 2, 3);

→ e.flx:1:5: error[E1029]: Cannot destructure a tuple with 3 elements into 2 variables

let (a, b, c) = (1, 2);

→ e.flx:1:5: error[E1029]: Cannot destructure a tuple with 2 elements into 3 variables

How to prevent it: Match the pattern's variable count to the tuple's .size exactly, checking it first if it's not a fixed literal.

E1028   Immutable value Fatal -halts execution

Message shape: Cannot modify an immutable tuple

Something tried to write to a tuple -index-assignment is the only way this currently happens, since tuples have no mutating methods to call in the first place.

let t = (1, 2);
t[0] = 9;

→ e.flx:2:3: error[E1028]: Cannot modify an immutable tuple

How to prevent it: Use an array instead of a tuple if you need to change elements after creation -tuples are permanently fixed once built.

E1033   Cannot hash object Fatal -plus a known crash path, see below

Message shape: Cannot hash <type> with value of '<value>'

A mutable, unhashable value (an array or map) was used as a map key. This is only handled cleanly in one of its two code paths -see the warning below.

let m = {a: 1};
m.remove([1, 2]);

→ e.flx:2:10: error[E1033]: Cannot hash array with value of '[1,2]' -clean, via .remove()

Confirmed crash bug: using an array as a key via index-assignment does not raise this diagnostic at all. m[[1,2]] = "x"; crashes with a raw, unhandled Python traceback (TypeError: catching classes that do not inherit from BaseException is not allowed) instead of this clean error, because of a broken exception-handling clause in the map's index-assignment code. .remove([1,2]) on the same map, by contrast, raises E1033 correctly, as shown above. Avoid the index-assignment form entirely with non-string/number keys until this is fixed upstream.

How to prevent it: Never use an array or map as a map key -use a string or number key instead, converting first if necessary.

Functions & calls

E1020   Object not callable Fatal -halts execution

Message shape: '<type>' is not callable

Parentheses () were applied to a value that isn't a function, struct constructor, or other callable.

let x = 5;
x();

→ e.flx:2:2: error[E1020]: 'number' is not callable

How to prevent it: Check the value actually holds a function before calling it -a common cause is a typo that resolves to the wrong variable.

E1021   Invalid argument count Fatal -halts execution

Message shape: Expected <n> arguments, but got <m>

A function was called with the wrong number of arguments. Falx checks this strictly for ordinary functions -no extra arguments are silently dropped, and no missing ones are silently filled with null. Note this is not the same for struct constructors, which are deliberately lenient (see Structs) -this code covers plain function/method calls only.

func add = define(a, b) { return a + b; }
output(add(1));

→ e.flx:2:12: error[E1021]: Expected 2 arguments, but got 1

func add = define(a, b) { return a + b; }
output(add(1, 2, 3));

→ e.flx:2:12: error[E1021]: Expected 2 arguments, but got 3

How to prevent it: Match the call site to the function's declared parameter count, counting any parameters with default values as optional (see default parameters).

E1024   Recursion depth exceeded Fatal -halts execution

Message shape: Maximum recursion depth of <n> exceeded

A function called itself (directly or indirectly) more than the configured limit -currently 500, genuinely tracked and enforced (verified by recursing past it).

func f = define(n) {
    if (n <= 0) { return 0; }
    return f(n - 1);
}
output(f(600));

→ e.flx:3:14: error[E1024]: Maximum recursion depth of 500 exceeded

How to prevent it: Restructure deep recursion into a loop where possible, or confirm the recursive call actually has a reachable base case (an infinite-recursion bug is the most common real-world cause). See Recursion.

E1032   Expected value Fatal -halts execution

Message shape: varies -e.g. "Empty seperator on split method"

A built-in method received an argument that was structurally valid (right type) but semantically empty/unusable -currently only raised by "...".split(sep) when sep is an empty string.

output("abc".split(""));

→ e.flx:1:20: error[E1032]: Empty seperator on split method

How to prevent it: Pass a non-empty separator to .split().

Control flow & iteration

E1025   Object not iterable Fatal -halts execution

Message shape: '<value>' is not iterable

foreach was used on something that isn't iterable -only arrays, maps and strings can be iterated (see foreach).

foreach (x: 5) { output(x); }

→ e.flx:1:1: error[E1025]: 'x' is not iterable

How to prevent it: Only use foreach on an array, map, or string.

E1027   Assertion failed Fatal -halts execution

Message shape: Assertion failed<message, if given>

assert(condition, message?)'s condition evaluated to a falsy boolean/string (see Truthiness -assert uses the same strict boolean-conversion rule as while, so a non-boolean/non-string condition raises E1011 instead of this).

assert(1 == 2, "one should equal two");

→ e.flx:1:36: error[E1027]: Assertion failed one should equal two

How to prevent it: This is usually intentional -it's how you assert a program invariant. If it's firing unexpectedly, the condition itself is the thing to debug, not the assert call.

Parser (syntax) errors

E2000   Syntax error (generic) Reserved -not currently raised

Message shape: n/a

Reserved in the code enum, but has no registered definition and no raise-site at all in the current interpreter -even more dormant than the other reserved codes on this page, which at least have a registry entry.

How to prevent it: Not applicable.

E2001   Unexpected token Fatal -halts execution

Message shape: varies, e.g. "Expected variable name.", "Expected ';' after expression"

The most general parser error -the parser expected one specific kind of token next (a name, a symbol, a keyword) and found something else. This is what you get for most everyday typos: a missing semicolon, a missing name, an empty tuple written as () instead of (,,), and so on.

let = 5;

→ e.flx:1:5: error[E2001]: Expected variable name.

How to prevent it: Read the message and the caret position carefully -it names exactly what token type was expected at that location.

E2002   Return outside function Fatal -halts execution

Message shape: 'return' called outside a valid function

A return statement appeared outside any define(...) { ... } body -including at the top level of a script, or lexically inside a loop that isn't itself inside a function.

return 5;

→ e.flx:1:1: error[E2002]: 'return' called outside a valid function

How to prevent it: Only use return inside a function body.

E2003   Continue outside loop Fatal -halts execution

Message shape: 'continue' called outside a valid loop

Same idea as E2004 below, for continue. Also correctly rejects a continue written inside a function that is itself nested inside a loop -the function boundary resets loop context.

continue;

→ e.flx:1:1: error[E2003]: 'continue' called outside a valid loop

How to prevent it: Only use continue directly inside a while or foreach body, not inside a function defined within one.

E2004   Break outside loop Fatal -halts execution

Message shape: 'break' called outside a valid loop

A break appeared outside any loop -this includes a break inside a func that is itself nested inside a while/foreach; the check correctly treats the function boundary as resetting loop context, rather than (incorrectly) breaking the enclosing loop.

break;

→ e.flx:1:1: error[E2004]: 'break' called outside a valid loop

let i = 0;
while (i < 3) {
    func inner = define() { break; }
    inner();
}

→ e.flx:3:33: error[E2004]: 'break' called outside a valid loop -even though textually inside the while

How to prevent it: Only use break directly inside a while or foreach body.

E2005   Invalid assignment target Fatal -halts execution

Message shape: the internal AST node name of the invalid target, e.g. "NumberLiteral"

The left-hand side of = (or a compound assignment) wasn't something assignable -a variable, a property, or an index. The message text is a little rough: it currently prints the parser's internal node type name rather than a friendly phrase.

5 = 10;

→ e.flx:1:3: error[E2005]: NumberLiteral

How to prevent it: Only assign to a plain variable name, a struct field/map key (x.y), or an index (x[0]) -never to a literal or the result of an expression.

E2006   Unexpected end of input Fatal -halts execution

Message shape: the specific token that was still expected, e.g. "}"

The file ended while the parser was still expecting more -almost always a missing closing }, ), or ] somewhere earlier in the file.

func f = define() {
    let x = 1;

→ e.flx:2:15: error[E2006]: }

How to prevent it: Count your braces/parens/brackets -an editor with bracket-matching helps a lot here, since Falx has no official syntax highlighting yet (see the roadmap).

E2007   Invalid default value creation Fatal -halts execution

Message shape: Cannot have non default parameter <name> after default parameters.

A function parameter without a default appeared after one that has a default -see default parameters.

func f = define(a = 1, b) { return a; }

→ e.flx:1:24: error[E2007]: Cannot have non default parameter b after default parameters.

How to prevent it: Order parameters so every parameter with a default comes after every parameter without one.

Lexer errors

E3000   Invalid character Fatal -halts execution

Message shape: Invalid character '<char>'.

The lexer found a character that isn't part of any valid Falx token -most often a stray symbol like @, $, or # that isn't one of Falx's operators.

let x = 5;
output(x @ 2);

→ e.flx:2:10: error[E3000]: Invalid character '@'.

How to prevent it: Remove the stray character -often the result of a typo, a copy-paste artifact, or an operator from another language that Falx doesn't support (like ** for exponentiation -see Operators).

E3001   Unterminated string Fatal -halts execution

Message shape: Unterminated string.

A string literal opened with " but never found a matching closing quote before the end of the line/file.

let x = "hello;

→ e.flx:1:15: error[E3001]: Unterminated string.

How to prevent it: Add the missing closing ". If the string needs to contain a literal quote character, escape it as \" rather than ending the string early.

E3002   Invalid escape sequence Fatal -halts execution

Message shape: Invalid escape character: <char>

A backslash inside a string was followed by a character that isn't one of Falx's supported escapes.

let x = "bad \q escape";

→ e.flx:1:15: error[E3002]: Invalid escape character: q

How to prevent it: Only use the supported escapes: \n, \t, \r, \f, \b, \", \\ -see Strings. For a literal backslash followed by another character, escape the backslash itself.

Module errors

E4000   Module not found Fatal -halts execution

Message shape: Cannot find module '<name>' in path '<resolved path>'

require(...) couldn't resolve the given name to a real .flx file -including if the resolved path falls outside the project directory. See path resolution.

let x = require("doesnotexist");

→ e.flx:1:31: error[E4000]: Cannot find module 'doesnotexist' in path '/.../doesnotexist.flx'

How to prevent it: Check the spelling of the module name, and remember it's resolved relative to the requiring file (not the entry script) with .flx appended automatically -don't include the extension yourself.

E4001   Circular module dependency detected Fatal -halts execution

Message shape: Circular module dependency detected: <chain>

Two or more modules require each other, directly or through a chain, so none of them could finish loading first.

// a.flx
let b = require("b");

// b.flx (in the same folder)
let a = require("a");

→ a.flx:2:20: error[E4001]: Circular module dependency detected: b -> a -> b (when running a.flx)

How to prevent it: Break the cycle -usually by moving the shared code both files need into a third module that neither of the original two depends on.

CLI errors

E5000   Missing filename Reserved -not currently raised

Message shape: n/a

Registered for when run/--file/-f is used with no file argument. Verified unreachable: the actual CLI (built on Python's argparse) already rejects a missing file argument itself, with its own generic usage: ... message, before Falx's own diagnostic system ever gets a chance to raise this.

(running: python -m src.Main run    -with no file argument)

→ usage: falx run [-h] file
falx run: error: the following arguments are required: file

How to prevent it: Always pass a file: python -m src.Main run script.flx.

E5001   Invalid file type Reserved -not currently raised

Message shape: n/a

Registered for running a non-.flx file. Verified unreachable: the CLI does not currently check the file extension at all -a .txt file containing valid Falx source runs exactly the same as a .flx file.

# script.txt, containing:
output("works fine");

→ $ python -m src.Main run script.txt
works fine (no error, despite the extension)

How to prevent it: Not enforced today, but name your scripts .flx anyway for clarity and future-proofing -the check may be added later.