Diagnostics Reference

Complete reference for all diagnostic codes emitted by the Kit compiler. This includes errors (which prevent compilation), warnings (which indicate potential problems), and suggestions (which recommend improvements).

Overview

Kit uses a structured diagnostic system where every message has a unique code. Codes are grouped by category:

Prefix Category Severity Pipeline Stage
L0xxLexerErrorTokenization
P0xxParserErrorParsing
T0xxType CheckerErrorType checking (kit check)
E0xxEvaluatorErrorInterpretation (kit run)
C0xxCodegenErrorCompilation (kit build)
I0xxImportErrorModule resolution
M0xxMacroErrorMacro expansion
R0xxContractErrorRuntime contract enforcement
W0xxWarningWarningStatic analysis (kit check)
S0xxSuggestionSuggestionStatic analysis (kit check)

Errors halt compilation. Warnings and suggestions are reported by kit check and do not prevent the program from running. Warnings (yellow) flag likely bugs or bad practices. Suggestions (cyan) recommend stylistic improvements.

Lexer Errors (L0xx)

Lexer errors occur during tokenization, the first stage of compilation. They indicate that the source text contains characters or sequences that cannot form valid tokens.

L001 — Invalid character

The source file contains a character that Kit does not recognize as part of any valid token. This often occurs when pasting text from rich-text editors that insert invisible Unicode characters, or when using symbols that are not part of Kit's syntax.

# Bad: invisible Unicode character or unsupported symbol
x = 42 ` 3

# Fix: use only supported Kit operators and ASCII identifiers
x = 42 * 3

L002 — Unterminated string literal

A string was opened with a double quote but never closed before the end of the line or file. Kit strings must be closed on the same line they are opened (use string interpolation for multi-expression strings).

# Bad: missing closing quote
name = "hello

# Fix: close the string
name = "hello"

L003 — Invalid number format

A numeric literal is malformed. This can happen with invalid hex digits, multiple decimal points, or other malformed number syntax.

# Bad: invalid hex digit
x = 0xGG

# Bad: multiple decimal points
y = 3.14.15

# Fix: use valid number formats
x = 0xFF
y = 3.1415

Parser Errors (P0xx)

Parser errors occur when the token stream does not form a valid Kit program. The tokens themselves are valid, but their arrangement is not.

P001 — Unexpected token

The parser encountered a token where it was not expected. This is the most general parse error and often indicates a syntax mistake such as a missing operator, extra keyword, or misplaced punctuation.

# Bad: missing operator between expressions
x = 1 2

# Fix: add the intended operator
x = 1 + 2

P002 — Expected expression

The parser expected an expression (a value, function call, or other construct that produces a value) but found something else, such as a keyword or end of file.

# Bad: if with no body
x = if true then

# Fix: provide an expression for the body
x = if true then 42 else 0

P003 — Expected identifier

The parser expected a name (variable, function, or type name) but found a different token. This commonly happens in binding declarations, function parameters, or import statements.

# Bad: number where identifier is expected
42 = x

# Fix: use a valid identifier name
x = 42

P004 — Unclosed parenthesis or bracket

An opening parenthesis (, bracket [, or brace { was never closed with its matching counterpart. Count your delimiters to find the mismatch.

# Bad: unclosed parenthesis
x = (1 + 2

# Fix: close the parenthesis
x = (1 + 2)

P005 — Invalid pattern

A pattern in a match expression, function parameter, or binding is syntactically invalid. Patterns can be literals, identifiers, constructors, lists, records, or wildcards.

# Bad: invalid pattern syntax
match x
| + -> "bad"

# Fix: use a valid pattern
match x
| 0 -> "zero"
| n -> "other: ${n}"

P006 — Do-nothing code

Using () as a branch body or expression. Kit uses no-op as the unit value, not ().

# Bad: () is not Kit's unit value
if true then ()

# Fix: use no-op
if true then no-op

Type Checker Errors (T0xx)

Type checker errors are reported by kit check and occur when the program is syntactically valid but does not pass type analysis. These errors catch bugs before your code ever runs.

T001 — Unbound variable

A variable or function name is used but was never defined in the current scope or any enclosing scope. Check for typos, missing imports, or incorrect scoping.

# Bad: 'y' was never defined
x = y + 1

# Fix: define 'y' first or correct the name
y = 10
x = y + 1

T002 — Type mismatch

Two types that should be the same are not. This is the most common type error and occurs when a function receives the wrong argument type, when match branches return different types, or when an operator is applied to incompatible types.

# Bad: match branches return different types
x = match some-value
  | Ok n -> n + 1          # Int
  | Err e -> "error"       # String — mismatch!

# Fix: ensure both branches return the same type
x = match some-value
  | Ok n -> show (n + 1)   # String
  | Err e -> "error"       # String

T003 — Infinite type (occurs check)

A type variable would need to contain itself, creating an infinite type. This usually happens with accidental recursion in type definitions or when a function is applied to itself incorrectly.

# Bad: 'f' applied to itself creates infinite type
bad = fn(f) => f f

# Fix: restructure to avoid self-application
apply = fn(f, x) => f x

T004 — Invalid field access

Attempting to access a field or method that does not exist on the given type. This can also occur when trying to use TypeName.method() dispatch on a local type whose constructor takes arguments.

# Bad: 'z' is not a field of the record
point = {x: 1, y: 2}
println point.z

# Fix: use a field that exists
println point.x

T005 — Arity mismatch

A function was called with the wrong number of arguments. Kit functions have a fixed arity determined at definition.

# Bad: 'add' expects 2 arguments
add = fn(a, b) => a + b
x = add 1 2 3

# Fix: pass the correct number of arguments
x = add 1 2

T006 — Invalid module name

A module name does not follow Kit's naming conventions. Module names must be in PascalCase (e.g., MyModule, not my_module or myModule).

# Bad: lowercase module name
module my-module

# Fix: use PascalCase
module MyModule

T007 — Invalid identifier name

An identifier violates Kit's naming rules. Bindings and functions use lower-kebab-case in Kit.

# Bad: camelCase or snake_case
myValue = 42
my_value = 42

# Fix: use lower-kebab-case
my-value = 42

T008 — Invalid type name

A type name does not follow the PascalCase convention. All user-defined type names and constructor names must start with an uppercase letter.

# Bad: lowercase type name
type myType = MyVal Int

# Fix: use PascalCase
type MyType = MyVal Int

T009 — Invalid boolean name

A function that returns Bool does not follow the Kit naming convention. Boolean-returning functions must end with ? and typically start with is- or has-.

# Bad: boolean function without '?'
check-valid = fn(x) => x > 0

# Fix: end the name with '?'
is-valid? = fn(x) => x > 0

T010 — Missing module declaration

A file that is imported as a module does not have a module declaration at the top. Every Kit file that is imported by other files must declare its module name.

# Bad: file imported as module but has no declaration
export add = fn(a, b) => a + b

# Fix: add module declaration at the top
module Math
export add = fn(a, b) => a + b

T011 — Incorrect module or type casing

A module or type name has incorrect casing. Module and type names must use PascalCase. A common trigger is using result as a variable name, which Kit interprets as a reference to the Result type module.

# Bad: 'result' conflicts with the Result type
result = parse-int "42"

# Fix: use a different variable name
parsed = parse-int "42"

T012 — Module requires import

A module is used without being imported first. Add an import statement at the top of your file.

# Bad: using JSON without importing it
data = JSON.parse text

# Fix: import the module first
import Encoding.JSON
data = JSON.parse text

Evaluator Errors (E0xx)

Evaluator errors occur at runtime when using the interpreter (kit run). These represent errors that cannot be caught at compile time.

E001 — Unbound variable

A variable is referenced at runtime that has not been defined. This can occur in dynamically constructed code or when type checking is bypassed.

E002 — Type mismatch

A runtime type error occurred. For example, attempting arithmetic on a string, or calling a non-function value. The interpreter checks types dynamically and reports this when an operation receives an unexpected type.

# This would cause E002 at runtime:
# trying to add a string and an int without conversion

E003 — Runtime error

A general runtime error that does not fit other categories. This includes panic calls, failed unwraps, and other unrecoverable runtime conditions.

# Triggers E003:
x = None |> Option.unwrap   # unwrapping None

# Fix: handle the None case
x = None ?? "default"

E004 — Division by zero

An integer or float division by zero was attempted at runtime.

# Triggers E004:
x = 10 / 0

# Fix: check for zero before dividing
safe-div = fn(a, b) =>
  if b == 0 then Err "division by zero"
  else Ok (a / b)

E005 — Index out of bounds

An array or list access used an index outside the valid range.

# Triggers E005:
items = [1, 2, 3]
x = List.nth 10 items   # only indices 0-2 are valid

# Fix: check bounds or use safe access
x = List.nth 0 items    # valid index

Codegen Errors (C0xx)

Codegen errors occur during kit build when compiling Kit to a native binary. They indicate that valid Kit code cannot be translated to the target backend.

C001 — Unsupported feature for compilation

A language feature that works in the interpreter (kit run) is not yet supported by the native code generator. This is rare but can occur with very dynamic features.

C002 — C compilation failed

The underlying Zig/C compilation step failed. This typically happens when FFI code has errors, when native libraries are missing, or when the generated Zig code encounters a platform-specific issue. The error message will include details from the Zig compiler.

C003 — Unsafe FFI usage

The program uses extern-c or extern-zig FFI declarations but was compiled with the --safe flag, which disallows foreign function calls.

# This code requires FFI access:
extern-c puts(s: CString) : CInt from "stdio.h"

# Compile without --safe, or remove the FFI code
# kit build myfile.kit -o myfile

C004 — Missing capability

The program uses a capability (file system, network, etc.) that was not granted. Kit uses an object-capability model to control access to system resources. Use the --allow flag to grant the required capability.

# Code that requires file system capability:
contents = File.read "data.txt"

# Compile with the required capability:
# kit build --allow=file myfile.kit -o myfile

Import Errors (I0xx)

Import errors occur when the module system cannot resolve or load an imported module.

I001 — Import file not found

The file referenced by an import statement does not exist. Check the import path, ensure the package is installed (kit install), and verify the module name follows the correct casing.

# Bad: module doesn't exist or isn't installed
import NonExistent.Module

# Fix: verify the module path and install if needed
# kit install
import Encoding.JSON

I002 — Lexer error in imported file

An imported module has a lexer error (see L0xx errors). Fix the error in the imported file.

I003 — Parser error in imported file

An imported module has a parser error (see P0xx errors). Fix the error in the imported file.

I004 — Circular import detected

Two or more modules import each other, forming a cycle. Kit does not support circular imports. Restructure your modules to break the cycle, typically by extracting shared types into a separate module.

# Bad: a.kit imports b.kit, b.kit imports a.kit

# Fix: extract shared types into a third module
# shared.kit - types used by both a.kit and b.kit
# a.kit - imports shared.kit
# b.kit - imports shared.kit

I005 — Package entry point not found

The entry_point field in a package's kit.toml references a file that does not exist. Verify the entry_point path in the package manifest.

Macro Errors (M0xx)

M001 — Macro expansion error

An error occurred during macro expansion. The macro may have received invalid arguments or produced output that is not valid Kit syntax.

Contract Errors (R0xx)

Contract errors occur at runtime when a function's precondition or postcondition is violated. Contracts are defined using Kit's contracts system.

R001 — Precondition failed

A function's @pre precondition evaluated to false at runtime. The caller passed arguments that do not satisfy the function's input contract.

# Function with a precondition
@pre(x >= 0, "x must be non-negative")
square-root = fn(x) => Math.sqrt x

# Triggers R001:
square-root (-1)

# Fix: ensure the argument satisfies the contract
square-root 4   # Ok: 2.0

R002 — Postcondition failed

A function's @post postcondition evaluated to false at runtime. The function's return value does not satisfy its output contract. This indicates a bug in the function implementation.

# Function with a postcondition
@post(result >= 0, "result must be non-negative")
absolute-value = fn(x) =>
  if x >= 0 then x else 0 - x

Warnings (W0xx)

Warnings are reported by kit check and flag potential problems in your code. They do not prevent compilation or execution but should generally be addressed. Warnings are displayed in yellow.

W001 — Missing defer for resource cleanup

A resource was acquired but there is no corresponding defer to ensure cleanup. For example, a memory-mapped file opened with File.mmap needs a matching File.munmap. Functions annotated with @defer-required also trigger this warning.

# Warning: memory-mapped file without cleanup
mapped = File.mmap "data.bin"

# Fix: use defer to ensure cleanup
mapped = File.mmap "data.bin"
defer File.munmap "data.bin"

W002 — Unused binding

A variable or binding is defined but never referenced. This may indicate dead code or a typo. Prefix the name with - to mark it as intentionally unused, or remove it entirely.

# Warning: 'temp' is defined but never used
temp = compute-something
final = 42

# Fix option 1: remove the unused binding
final = 42

# Fix option 2: prefix with - to mark as intentionally unused
-temp = compute-something
final = 42

# Fix option 3: use the binding as a bare expression (for side effects)
compute-something
final = 42

W003 — Shadowed binding

A binding in an inner scope has the same name as a binding in an outer scope, making the outer binding inaccessible. This can cause subtle bugs when you accidentally reference the wrong variable.

# Warning: inner 'x' shadows outer 'x'
x = 10
process = fn(x) => x + 1   # shadows outer x

# Fix: use a different name
x = 10
process = fn(val) => val + 1

W004 — Unused import

A module was imported but none of its exports are used. Remove the import to keep your dependency list clean.

# Warning: JSON is imported but never used
import Encoding.JSON

main = fn => println "hello"
main

# Fix: remove the unused import
main = fn => println "hello"
main

W005 — Missing else branch

An if expression is used in a value context (its result is assigned or returned) but has no else branch. Without else, the expression has no value when the condition is false.

# Warning: if used as value without else
x = if condition then 42

# Fix: add an else branch
x = if condition then 42 else 0

W006 — Boolean naming convention violation

A function that returns Bool does not follow Kit's naming convention. Boolean-returning functions should use is-*? or has-*? naming patterns.

# Warning: boolean function doesn't end with '?'
check-empty = fn(list) => List.length list == 0

# Fix: follow the naming convention
is-empty? = fn(list) => List.length list == 0

W007 — Unreachable pattern after wildcard

A match branch appears after a wildcard pattern (_ or a bare identifier) that already matches everything. The later branch can never execute.

# Warning: the second branch is unreachable
match x
| _ -> "anything"
| 42 -> "forty-two"   # never reached!

# Fix: put specific patterns before wildcards
match x
| 42 -> "forty-two"
| _ -> "anything"

W008 — Unused function parameter

A function parameter is declared but never used in the function body. Prefix it with - to mark it as intentionally unused, or use _ as a wildcard.

# Warning: parameter 'y' is never used
add-x = fn(x, y) => x + 1

# Fix option 1: prefix with -
add-x = fn(x, -y) => x + 1

# Fix option 2: use wildcard
add-x = fn(x, _) => x + 1

W009 — Self comparison

An expression compares a value to itself (e.g., x == x). This is always true for == and always false for !=, which is almost certainly a bug.

# Warning: self comparison is always true
is-same? = x == x

# Fix: compare against the intended value
is-same? = x == y

W010 — Empty collection as condition

An empty list or map literal is used as a condition in an if expression. Empty collections are falsy, so the condition is always false.

# Warning: [] is always falsy
if [] then "yes" else "no"

# Fix: use the actual condition you intend
if List.length items > 0 then "yes" else "no"

W011 — Constant in string interpolation

A literal value is used inside string interpolation ${...}. Since the value is constant, the interpolation is unnecessary.

# Warning: constant doesn't need interpolation
msg = "value is ${42}"

# Fix: embed the literal directly in the string
msg = "value is 42"

W012 — Piped value not used

A value is piped with |> into a function, but the piped value is not used by the right-hand side. The pipe has no effect.

# Warning: piped value 'x' is not used by println "hello"
x |> println "hello"

# Fix: use the piped value or remove the pipe
x |> println
# or just:
println "hello"

W013 — Deprecated function usage

The code calls a function that has been marked as deprecated with the @deprecated attribute. The deprecation note will suggest what to use instead.

# Warning: 'old-api' is deprecated, use 'new-api' instead
old-api data

# Fix: switch to the recommended replacement
new-api data

W014 — Non-exhaustive pattern match

A match expression does not cover all possible cases for the matched value. This can lead to runtime errors if an unmatched case occurs. Add a wildcard _ branch or cover all constructors.

# Warning: not all Option variants are covered
match maybe-value
| Some x -> x + 1
# Missing: None branch

# Fix: cover all cases
match maybe-value
| Some x -> x + 1
| None -> 0

W015 — Recursive binding in own definition

A binding references itself in its own definition. This creates unintentional infinite recursion. If recursion is intended, use rec.

# Warning: 'x' references itself
x = x + 1

# Fix: use a different name or make recursion explicit
x = 10
y = x + 1

W016 — Expression has no effect

An expression is evaluated but its result is not used. The expression does nothing unless it has side effects. If the expression has side effects, assign the result to a binding prefixed with -.

# Warning: expression result is discarded
1 + 2

# Fix: use the result or mark as intentionally discarded
x = 1 + 2

W017 — Empty print statement

Calling print "" produces no output and has no effect. Remove the call or use println "" if you intended to print a blank line.

# Warning: print "" does nothing
print ""

# Fix: remove it, or use println for a blank line
println ""

W018 — Empty println statement

Calling println "" only prints a newline. This is valid but the analyzer flags it so you can consider merging it with adjacent print calls.

# Warning: println "" just prints a newline
println "Header"
println ""
println "Body"

# Consider: use \n in the string if you want spacing
println "Header\n"
println "Body"

W019 — TODO comment

The source contains a TODO comment (case-insensitive). This is a reminder that there is unfinished work. Address the TODO or remove it when done.

# Warning: TODO comment found
# TODO: implement error handling
process = fn(x) => x

# Fix: complete the TODO
process = fn(x) =>
  match validate x
  | Ok v -> v
  | Err e -> panic "invalid: ${e}"

Suggestions (S0xx)

Suggestions recommend code improvements but are even less urgent than warnings. They are displayed in cyan and focus on code style and clarity.

S001 — Wildcard import could be selective

A wildcard import (import Module.*) brings all exports into scope, but only a few are actually used. A selective import makes dependencies explicit and avoids name collisions.

# Suggestion: only 'encode' and 'decode' are used
import Encoding.JSON.*

data = encode value
text = decode input

# Better: import only what you use
import Encoding.JSON.{encode, decode}

data = encode value
text = decode input

S002 — Ignored binding can be a bare expression

A binding is prefixed with - to mark it as unused, but the expression could simply be written as a bare statement. This is simpler and more idiomatic.

# Suggestion: ignored binding is unnecessary
-ignore = println "hello"

# Better: use a bare expression for side effects
println "hello"

Suppressing Diagnostics

Sometimes a warning or suggestion is intentional and you want to suppress it. Kit supports two suppression mechanisms: inline comments and project-level configuration.

Only warnings (W0xx) and suggestions (S0xx) can be suppressed. Errors (L0xx, P0xx, T0xx, E0xx, C0xx, I0xx, M0xx, R0xx) cannot be suppressed — they must be fixed.

Inline suppression

Add # ignore: CODE either as a trailing comment on the same line, or on the line immediately above the diagnostic. Only W0xx and S0xx codes are accepted.

# Suppress a specific warning on one line
x = 42  # ignore: W002

# Suppress multiple codes
temp = 10  # ignore: W002, W003

# Suppress on the preceding line
# ignore: S001
import Encoding.JSON.*

# Suppress all warnings and suggestions on a line
x = 42  # ignore

Project-level suppression

To suppress warnings or suggestions across an entire project, add an [lint] section to your kit.toml. Only W0xx and S0xx codes are accepted here.

# kit.toml
[lint]
ignore = ["W003", "W019"]

This suppresses the specified codes globally for all files in the project.

Tip

Prefer inline suppression over project-level suppression. Inline comments document why a specific diagnostic is acceptable at that exact location, while project-wide suppression can hide real issues elsewhere.