Skip to content

Latest commit

 

History

History
1180 lines (890 loc) · 45.8 KB

errors.md

File metadata and controls

1180 lines (890 loc) · 45.8 KB

error codes

pith diagnostics use stable error codes grouped by compiler phase. codes are never reused — if a code is retired, it stays retired.

use pith check --json <file> or pith lint --json <file> for machine-readable output that includes the error code, location, message, and fix suggestion (if available).


lexing and parsing errors (E24x)

the lexer and parser do not have their own numeric ranges. every diagnostic they produce lands in the E24x block alongside the checker's, and the four codes below are the whole set:

code phase what it covers
E240 parser an expected token was missing
E241 parser the file ended in the middle of a construct
E242 parser the tokens do not form a valid construct here
E243 lexer an invalid token: a stray character, an unterminated string, or a bad indentation change

each is documented in full below. codes in the E0xx and E1xx ranges have never been emitted and are not reserved for these phases.

checker errors (E2xx)

E200 — type mismatch

an expression's type doesn't match what was expected. this is the most common checker error. includes a fix suggestion when the mismatch is in a return type.

error[E200]: type mismatch: expected String, got Int
  3 |   x * 2
      ^^^^^ this is Int, but the function returns String
  fix: change the return type to Int

a bare none reports here too. none is the empty case of an optional and satisfies nothing else, so it is accepted only where the target is already a T? — a binding, an assignment, a return, an argument, a struct field, a collection element, a tuple element, or the other side of == / !=.

error[E200]: return type mismatch: expected Int, got none
  8 |     return none
             ^
  fix: return a value, or declare the return type Int?

E201 — undefined variable

a variable or function name was used but never defined.

error[E201]: undefined variable 'foo'
  5 |   print(foo)
              ^^^

mod.name(...) reaches only what mod itself declares. every module's top-level names live in one shared table, and without this check the lookup falls through to a function some unrelated module declared, then emits a call that resolves to nothing.

a method is an easy way to land here. a method is not a module function and has no free-function spelling: wait on Process is written proc.wait(), never process.wait(proc). when the first argument's type has a method by that name, the error says so.

error[E201]: undefined function: process.wait
  fix: 'wait' is a method on Process; call it as receiver.wait(...)

E202 — undefined type

a type name was used in an annotation but doesn't exist.

error[E202]: undefined type 'Foob'
  1 | fn bar(x: Foob) -> Int:
                ^^^^

E203 — duplicate definition

a name was defined more than once in the same scope.

error[E203]: duplicate definition of 'x'
  3 | x := 10
      ^

not currently emitted. rebinding a name in the same scope is accepted; there is no duplicate-definition check. rebinding it inside a nested block reports E262.

E204 — non-exhaustive match

a match expression doesn't cover all possible values. includes a fix suggestion listing the missing patterns. a guarded arm never counts toward coverage, since its guard can be false: _ if n > 0 => ... alone is not exhaustive. a binding on an optional subject covers the present values only, so the match also needs a none arm.

error[E204]: non-exhaustive match: missing variant 'Circle'
  5 | match shape:
      ^^^^^
  fix: add missing arm: Circle(..)

E205 — unreachable pattern

a match arm can never be reached because an earlier arm without a guard is a wildcard, a binding, or a tuple of those, and matches every value. a binding on an optional subject matches only present values, so a none arm after it is still reachable. a repeated arm that is not a catch-all is not reported.

error[E205]: unreachable match arm: an earlier arm without a guard matches every value
  4 |         1 => 1
              ^
  fix: remove this arm, or move it above the catch-all arm

E206 — missing return type

a function needs a return type annotation but doesn't have one.

not currently emitted. a function with no return type annotation is treated as returning nothing.

E207 — wrong number of arguments

a function call has too many or too few arguments.

error[E207]: expected 2 arguments, got 3
  5 | add(1, 2, 3)
      ^^^^^^^^^^^

E208 — not callable

an expression was used as a function call but its type isn't callable.

E209 — field not found

a field access references a field that doesn't exist on the struct.

error[E209]: field 'z' not found on type 'Point'
  3 | p.z
        ^

the same code covers a missing method, and a method call that writes type arguments on a name the receiver's type does not declare as a method with type parameters — a builtin such as s.len[Int](), or a field.

error[E209]: String has no method 'len' that takes type arguments

E210 — not a struct type

a field access or struct constructor was used on a non-struct type. the same code reports an explicit type argument that is not a type at all, such as Channel[1](2) or Holder[f()](); a type written in expression position may be a name, a parameterized type, an optional, a tuple of those, or an associated type of a type parameter (engine[A.Msg]()).

E211 — not an enum type

an enum variant pattern was used on a non-enum type.

not currently emitted. matching an enum pattern against a non-enum reports E228 instead.

E212 — unknown variant

an enum variant name doesn't exist on the enum type.

E213 — wrong field count in pattern

a pattern has the wrong number of fields for the enum variant.

E214 — reserved

reserved for future checker diagnostics. not currently emitted.

E215 — break/continue outside loop

a break or continue statement was used outside of a loop body.

E216 — assignment to immutable binding

a variable was assigned to but wasn't declared with mut. includes a fix suggestion.

error[E216]: cannot assign to immutable variable 'x'
  3 | x = 10
      ^
  fix: declare with 'mut': mut x := ...

E217 — invalid operand types

an operator was used with types that don't support it.

error[E217]: operator '+' not supported for types Bool and Bool
  2 | true + false
      ^^^^^^^^^^

E218 — match guard must be Bool

the if guard on a match arm doesn't evaluate to Bool.

E219 — argument type mismatch

a function argument has the wrong type.

error[E219]: argument type mismatch: expected String, got Int
  3 | greet(42)
            ^^

a bare none passed to a parameter that is not an optional reports here.

error[E219]: argument type mismatch: expected Int, got none
  11 |     print(takes_int(none).to_string())
                 ^

the reverse is fine: a plain value passed to an optional parameter widens to Some(v), so f(3) against fn f(x: Int?) is accepted. it does not run backwards — an Int? argument to an Int parameter still reports here, and so does an inner type that does not match (f("three") against Int?). a builtin container position that looks a value up rather than stores it — xs.contains(3), m.contains_key(3), a map key — reports here too, because a freshly built Some(3) would not compare equal to the one the container holds; see docs/limitations.md.

E220 — pipe operator error

the right side of a pipe operator (|) is not a valid function.

not currently emitted. a bad right-hand side to | reports E208 instead.

E221 — generic type argument count mismatch

a generic type was used with the wrong number of type arguments. the channel constructor counts too: a channel carries exactly one payload type, and Channel[Int, String](1) used to build a Channel[Int] silently, dropping the rest. send a struct when one message should carry several fields.

error[E221]: Channel expects 1 type argument, got 2

type arguments written at a method call, x.describe[Int](v), count against the method's own bracket list the same way; a method that declares no type parameters expects zero.

error[E221]: describe expects 1 type arguments, got 2

E222 — generic type inference failure

the compiler couldn't infer the type arguments for a generic type.

E223 — collection type inference error

the compiler couldn't determine the element type of a collection literal, or the elements disagree. a none element is folded in rather than compared — [none, 1] is a List[Int?] — but a set element and a map key are hashed and compared, so neither accepts none.

error[E223]: set element cannot be none
  4 |     zs: Set[Int] := {none}
                           ^

E224 — invalid unwrap/try target

the ? (unwrap) operator was used on a non-optional type, or the ! (try) operator was used on a non-result type. includes a fix suggestion.

error[E224]: try requires a result type, got Int
  3 | x!
      ^^
  fix: use ? for unwrapping optional types

E225 — branch type mismatch

if/elif/else branches, match arms or select arms produce different types when the whole is used as an expression. a none beside a value is not a mismatch: the branches settle on the value's optional, so match v: none => none; x => x is a T?, and a value beside its own optional settles on the optional the same way.

E226 — interface constraint violation

a type doesn't satisfy the interface bounds required by a generic parameter.

E227 — method not found

a method call references a method that doesn't exist on the type.

not currently emitted. a missing method reports E209 instead, which covers both fields and methods.

E228 — pattern type mismatch

a pattern in a match arm doesn't match the type being matched on. a bare name that equals a variant of the matched enum is also E228: a bare name is a binding that matches every value, so the variant has to be written qualified.

error[E228]: 'Red' is a variant of Color, but a bare name in a pattern is a binding that matches every value
  8 |         Red => 1
              ^
  fix: write Color.Red to match the variant

E229 — invalid self usage

self was used outside of a method body. includes a fix suggestion.

error[E229]: 'self' can only be used inside a method body
  1 | fn foo(): self.x
                ^^^^
  fix: define methods inside an 'impl' block with 'self' as the first parameter

E230 — missing type annotation

a type annotation is required but wasn't provided.

E231 — return outside function

a return statement was used outside of a function body.

error[E231]: return statement outside of function
  1 | return 42
      ^^^^^^
  fix: 'return' can only be used inside a function body

E232 — spawn/await type error

a spawn or await expression has a type error.

a spawn's arguments and a method call's receiver are evaluated on the new task, so ! among them has no function to return from. bind the value before the spawn:

value := load()!
t := spawn work(value)

E233 — type nesting too deep

type declarations are nested too deeply (exceeds the compiler's recursion limit). this usually happens with deeply nested generic types or recursive type definitions.

error[E233]: type nesting too deep

E234 — invalid fail target

the fail statement was used in a function that does not return a result type.

E235 — import cycle detected

two or more modules import each other, forming a cycle. break the cycle by restructuring the code or extracting shared types into a third module.

E236 — imported name not found

a from ... import refers to a name that doesn't exist in the module.

error[E236]: name 'subtract' not found in the imported module
  1 | from math import subtract
                       ^^^^^^^^

not currently emitted. importing a name a module does not declare reports E246 instead.

E237 — imported name is not public

a from ... import refers to a name that exists but isn't marked pub.

error[E237]: 'secret' is not public in the imported module
  1 | from math import secret
                       ^^^^^^

not currently emitted, and there is no check behind it: from mod import name currently succeeds whether or not name is pub. only the mod.name(...) call form is gated, by E251.

E238 — invalid unwrap context

the ? operator was used outside a function that returns a result type. ? propagates none as a fail, so the enclosing function must be able to carry that error. Either change the function to return T!, or replace ? with match/unwrap_or for an in-place default.

error[E238]: unwrap (?) can only be used in functions that return a result type
  3 | print("got: {value?}")
                  ^^^^^^
  fix: wrap main or the enclosing fn return in T! and propagate, or replace ? with `match`/`unwrap_or` for an in-place default

E239 — invalid try context

the ! operator was used inside a function that does not itself return a result type.

E240 — expected token

the parser expected a specific token but encountered a different one.

E241 — unexpected end of file

the parser reached the end of the file before a required token or block terminator appeared.

E242 — invalid syntax

the parser encountered a token sequence that does not form a valid expression, pattern, or declaration.

E243 — lexer error

the lexer produced an invalid token — an unterminated string literal, a stray character that starts no valid token, or a bad indentation change. every lexer error is reported, even one that lands between two otherwise valid tokens (1 € + 2), so an invalid character can never be silently dropped.

E245 — invalid if let / while let pattern

if let and while let take a variant pattern (which destructures like a match arm) or a bare binding (which unwraps an optional subject). anything else — a literal pattern, or a bare binding against a non-optional — is rejected.

error[E245]: if let with a bare binding needs an optional subject, got Int

E246 — import of an undeclared name

a from import must name something its source module actually declares at the top level. a wrong module name in an import otherwise compiles clean and dies much later as a silent unknown call in the backend.

error[E246]: 'imaginary_helper' is not declared by module 'helper'

E247 — cannot find module

an import or from ... import names a module whose file cannot be found. standard-library modules are imported with the std. prefix (import std.fs as fs, not import fs); local modules resolve relative to the importing file.

error[E247]: cannot find module 'fs'
  1 | import fs
             ^

E248 — collection shared with another task

a spawned task runs concurrently with the scope that spawned it, on a pool of os threads. reference counts are atomic, but a list, map, or set is an unsynchronized buffer behind a handle, so passing one the current scope still holds lets both mutate the same buffer. the same holds for a value sent through a channel: the receiver gets the handle, not a copy, so send and try_send are checked like a spawn's arguments. hand the task a copy, build the value in the call, or use an atomic cell for a shared scalar. a fresh collection — a literal, a copy, a call result — is not shared and is allowed.

error[E248]: a spawned task cannot take a list this scope still holds; both threads would mutate the same buffer
  6 |     t := spawn worker(data)
                            ^

a struct argument is refused the same way when one of its fields reaches a list, a map or a set outside a guard block, directly or through a nested struct or optional struct field. move the field into a guard block, or build the struct in the spawn's arguments, or the send's, so the other task holds the only reference.

error[E248]: a spawned task cannot take this Account: its field 'inbox.messages' is a collection outside a `guard` block, and both threads would reach the same buffer
  16 |     t := spawn count(acct)
                            ^
  fix: move the field into a `guard` block, or hand the task its own copy

E249 — weak field must be an optional struct

error[E249]: weak field 'parent' must be an optional struct type (T?)

a weak field points at a struct without keeping it alive, so it must be able to read back as "gone" once the target is reclaimed — which means it has to be an optional struct type, T?. weak parent: Node is rejected; write weak parent: Node?. weak is for breaking reference cycles (a parent/child back pointer, a doubly-linked list's back link); see docs/ownership.md.

E250 — builtin type name

a struct or enum cannot reuse the name of a builtin type (Channel, Task, List, Map, Mutex, WaitGroup, Semaphore, AtomicInt, or a primitive like Int). the declaration would shadow the builtin and silently miscompile anything that uses it — a struct Channel, for instance, corrupts every Channel[T] in scope, so an imported module's own channels stop working with no error. rename the type.

error[E250]: 'Channel' is a builtin type name and cannot be used as a struct name
  2 |     n: Int
                ^

E251 — function is not public in that module

a module function declared with a bare fn belongs to the module that declared it; only a pub fn can be called through an import. the two live in one shared name table, so before this check the lookup for mod.helper would fall through to a bare-name match and return something the caller had no business holding.

mark the function pub if it is meant to be part of the module's surface, or call the public entry point that wraps it.

error[E251]: 'frame_message' is not public in module 'grpc'
 11 |     framed := grpc.frame_message(bytes.from_string_utf8("hi"))
                                                                    ^

E252 — this file has no main function

pith build and pith run make an executable, and an executable needs an entry point. a file without a top-level fn main is a library: it can be imported, checked and linted, but not built on its own. before this check the build sailed through to the system linker, which answered with an undefined-reference dump instead of a diagnosis.

error[E252]: this file has no main function
  fix: `pith build` and `pith run` make an executable. a module without main can be imported, checked and linted, but not built on its own

E253 — catch block must leave

the block form of catch runs on the error path and produces no value, so falling out of its end would leave the surrounding binding with nothing. end the block with return, fail, continue or break.

error[E253]: a catch block must end with return, fail, continue or break; it produces no value for the surrounding expression

E254 — unhashable set element or map key

a Set element or a Map key must hash, and a set and a map hash exactly three flavors: the int family, String, and Bytes, which is hashed and compared by content. every other type used to fall into the string flavor anyway and misbehave silently — an optional's shell and a list's allocation header were read as a c-string, collapsing distinct values into one entry; a struct compared raw memory bytes; a plain enum inserted nothing at all; a float crashed the process. the type is rejected wherever it is formed: a written annotation, an inferred literal, an empty literal's first typed store, and a generic instantiation. store List[T?] instead of Set[T?], and key by a string, integer or bytes encoding of the value otherwise (an id field for a struct, a serialized form for a tuple). an optional map value stays legal; only the key is hashed.

error[E254]: an optional cannot be a set element type; store List[T?] or key by the payload instead
error[E254]: a list cannot be a map key type; a set element and a map key hash int, string and bytes flavors only

E260 — invalid defer or errdefer

defer and errdefer appear outside a function, or the deferred statement is not a plain side effect. a deferred statement may not return, fail, break, continue, use ! or ?, bind a name, or nest another defer — move the control flow into a helper and defer the call to it. errdefer additionally requires a function that returns a result (T!), since there is no error case for it to run on otherwise.

error[E260]: errdefer is only meaningful in a function returning a result (`T!`)
  fix: use `defer` for cleanup that must always run, or give the function a `T!` return type

see defer.md for the full ordering rules.

E261 — invalid weak binding

a weak binding broke one of its restrictions. a weak binding cannot be mut, cannot carry a type annotation (it takes its type from its value), must hold a struct value, cannot be declared inside a closure, and cannot reuse a name already bound in the same function.

error[E261]: a weak binding cannot be 'mut'

see ownership.md for why each restriction exists.

E262 — generic enum instance cannot be inferred

a payload-free variant of a generic enum carries nothing to infer the type argument from. bound without an annotation, the value would have no concrete type — and a later match on it could not be checked. annotate the binding; a constructor with a payload argument (Opt.Some(5)) infers its instance and needs no annotation.

error[E262]: cannot infer the type arguments of generic enum 'Opt' from this constructor; annotate the binding

the same code reports a binding in a nested block that reuses a name an enclosing block of the same function already bound. a function has one slot per name, so the inner binding would overwrite the outer value instead of shadowing it. sibling blocks may reuse a name.

error[E262]: 'x' is already bound in this function; a nested binding overwrites it rather than shadowing it

E263 — duplicate method across impl blocks

two impl blocks gave the same declaration the same method name with the same kind of declaration, so one body would silently shadow the other and which one answered depended on module order. remove or rename one of the two. an interface impl re-declaring a method the inherent impl also has is not a duplicate — that pair is the conformance idiom std/io uses — and an impl in another module adding new method names to a type it can see stays legal too.

error[E263]: duplicate method 'read' on 'FileStream': another impl block already declares it

E264 — where clause names an undeclared type parameter

a where clause put a bound on a name that is not one of the function's declared type parameters. the clause form and the inline form ([T: Display]) are two spellings of the same bounds, so a clause can only constrain names the bracket list declares. declare the parameter, or fix the spelling.

error[E264]: where clause names 'U', which is not a type parameter of 'f'

E265 — generic method reuses its owner's type parameter name

a method of a generic type declared a type parameter with the same name as one of the type's own. the two lists compose by name — the type's parameters are fixed by the receiver's instance, the method's by the call — so a method parameter spelled T on a Box[T] would resolve to the box's T everywhere it is written, never to the call's type. give the method's parameter a name of its own.

the code used to refuse every generic method on a generic type; that shape is supported now (docs/generics.md), and only the name clash is reported.

error[E265]: generic method 'describe' on generic type 'Box' reuses its owner's type parameter name 'T'; a method's own type parameters need names of their own

E266 — module global read before a local of the same name

a function's storage is flat: one slot per name for the whole body. a local, a parameter or a match / if let payload therefore takes its name for the entire function, and the module global of that name is unreachable from inside it. that is legal on its own (the linter mentions it as E308). what has no answer is a body that wants both — reads the global somewhere above and binds a local of the same name below.

rename the binding, or move the reads of the global into a function that does not bind the name.

error[E266]: 'items' names a module global that is read earlier in this function, and this binding takes the name for the whole function
 10 |     mut items: List[Int] := [7]
          ^
  fix: rename the binding, or move the reads of the global into a function that does not bind the name

a for variable is exempt. it is the one binding whose storage is scoped to a block rather than the function, so the global is still reachable on either side of the loop and nothing is ambiguous.

E267 — a generic instantiated at more than 64 distinct type sets

a generic body is checked once per distinct set of concrete types the program asks for (see docs/generics.md). a generic that calls itself at a larger type (fn grow[T](x: T) calling grow([x])) asks for T, then List[T], then List[List[T]], without end. the checker stops queueing the declaration after 64 sets and reports it once.

error[E267]: 'grow' is instantiated at more than 64 distinct type sets; a generic that calls itself at a larger type never stops

not reported when the check is switched off (PITH_CHECK_GENERIC_BODIES=off or silent); the declaration is then left unchecked past the cap, as every generic body was before the check existed.

E268 — invalid Drop implementation

impl Drop for T asks the compiler to call T's drop method from the destructor it attaches to every T it builds, when the last value naming the struct goes away (see docs/ownership.md, "destructors"). the impl has to be a shape that destructor can call: T must be a struct, declared in the module the impl is written in, and not generic — the destructor is generated per declaration and spells the method symbol from the declaring module, and a generic struct's destructor is generated per instance with no place for the call. the impl must declare fn drop() with no parameters and no return value, since the runtime hands it the bare pointer and reads nothing back, and nothing else beside it.

error[E268]: impl Drop for Guard: a generic struct cannot implement Drop
error[E268]: impl Drop for Shape: only a struct can implement Drop
error[E268]: impl Drop for Slot: drop() takes no parameters
error[E268]: impl Drop for Slot: the impl may only declare drop(), not 'close'
error[E268]: impl Drop for Slot: the impl must declare fn drop()

Drop is a language-level interface: nothing declares it and nothing imports it. put the type's other methods in an impl T: block of their own, and have drop call the explicit closer so the two stay idempotent.

E269 — assignment to a captured variable

a closure captures the outer variables it uses by value, when it is created. an assignment to one inside the closure would change only the closure's copy: mut n := 0; c := fn() -> Int: n = n + 1; return n returned 1 on every call and n never changed. the checker rejects the assignment. return the new value from the closure and assign it outside, or keep the state in a struct field or a collection, whose handle the closure shares with the caller. a module global is not captured, and a closure can assign its own locals.

error[E269]: cannot assign to 'n' inside a closure: a closure captures a copy, so the variable outside would not change
  fix: return the new value from the closure, or keep the state in a struct field or a collection the closure can update

E270 — missing return

a function that declares a return type reaches the end of its body on some path without returning a value. that path used to return a zero: 0 for an Int, [] for a list, none for an optional, a failed result with no message for T!, and a null struct that crashed on its first field read. there is no implicit none or ok, so -> T? and -> T! follow the same rule.

a path returns when it ends in return or fail, in a call to exit, or in a while true loop with no break for it. an if returns when it has an else and every branch returns, and a match when every arm does. a match that is the last statement of the body returns its value: an inline arm (Color.Red => "red") returns its expression, and a block arm has to return on its own. a closure with a block body follows the same rule when it declares a return type or returns a value on some path. pith lint reports the same code as a warning.

error[E270]: 'mk' declares a return type, but a path through its body reaches the end without returning a value
  fix: end every path with return or fail
error[E270]: this closure returns a value, but a path through its body reaches the end without returning one

E271 — guarded data without its lock

data declared in a guard block can only be read or written with its mutex held: inside a locked block for it, or in a function marked holding it. a read counts, since reading a map while another task inserts into it is as unsafe as the write. a struct field under guard pick: needs pick held on the same value, so p.next needs p.pick, and the value has to be reached through a path the checker can compare: a variable, self, or a field read off one. a call to a function marked holding m needs m held at the call; a path rooted at self is read off the receiver, and one rooted at a parameter off that argument. a closure body runs when it is called, so the locks held where it is made do not count inside it. code outside a function runs at module init, before any task, and needs no lock.

error[E271]: 'cache' is guarded by cache_mu, which is not held here
  fix: wrap the access in `locked cache_mu:`, or mark the function `holding cache_mu`
error[E271]: 'p.next' is guarded by p.pick, which is not held here
error[E271]: 'evict' must be called holding cache_mu, which is not held here

E272 — a locked block's mutex

locked m: takes m on entry and releases it at every exit from the block, which means evaluating m again at each exit. so m has to be a path that names the same Mutex every time: a variable, self, a field read off one, or a list element indexed by a variable or a literal. the body may not assign the path, a part of it, or an index variable in it, and may not rebind one of those names, since the unlock would then reach a different mutex. a locked block outside a function is rejected the same way.

error[E272]: locked needs a Mutex, got Int
error[E272]: a locked block needs a variable, a field or an indexed element that holds a Mutex
  fix: bind the mutex to a name first: `m := pick_lock()`, then `locked m:`
error[E272]: the body of this locked block changes 'i', so the unlock at its exit could reach a different mutex
  fix: change 'i' outside the locked block

E273 — locking a mutex the block already holds

a Mutex is not reentrant. a locked block nested inside one that already holds the same mutex would park the task on itself for good, so the checker rejects it. the check compares the two paths as written, inside one function or closure body: it does not follow a call into a function that takes the same lock.

error[E273]: 'm' is already held by an enclosing locked block, and locking it again would deadlock
  fix: remove the inner locked block; the outer one already holds the mutex

E274 — a guard block's shape

a module guard block holds only mut globals, and none of them may be pub, since another module could not take the mutex. a sharded guard, guard m[n]:, needs a count of at least 1 and is only allowed on module globals. a guard block is not allowed in a generic struct.

error[E274]: a guarded global cannot be pub, since another module could not take its mutex
error[E274]: a guard block holds only `mut` globals
error[E274]: a sharded guard is only allowed on module globals
error[E274]: a guard block is not allowed in a generic struct

lint errors (E3xx)

reported by pith lint. all of them are warnings: pith lint reports them without failing a build.

E300 — snake_case required (warning)

function names, variable names, and method names must use snake_case.

error[E300]: function name 'GetUser' should be snake_case
  1 | fn GetUser():
       ^^^^^^^

E301 — PascalCase required (warning)

type names (structs, enums, interfaces, type aliases) must use PascalCase.

error[E301]: struct name 'my_point' should be PascalCase
  1 | struct my_point:
            ^^^^^^^^

E304 — missing doc comment (warning)

a public function or method has no doc comment. every pub fn should have a # comment on the line directly above it explaining its purpose. there is no /// form — that is a parse error.

warning[E304]: public function 'serve' is missing a doc comment
  5 | pub fn serve():
            ^^^^^

E305 — deep nesting (warning)

code is indented more than 4 levels deep. consider extracting a helper function to reduce complexity.

warning[E305]: indentation depth 5 exceeds maximum of 4
  12 |                     if x > 0:
                            ^^

E306 — strong reference cycle (warning)

a struct holds a strong reference back to itself, directly (next: Node?) or through a chain of fields across other structs in the same module. a cycle of strong references keeps its own count above zero, so it is never reclaimed; mark the back edge weak (see ownership.md). a weak field is not an edge, and neither is a function-typed field. a cycle that runs through another module's structs is not visible to the linter and passes.

warning[E306]: struct 'Node' holds a strong reference to itself through field 'next'; a cycle of strong references is never reclaimed
  3 |     next: Node?
          ^
  fix: mark the back edge with `weak` (`weak next: ...`), see docs/ownership.md

E307 — resource never closed (warning)

a local binding holds a resource its caller owns and nothing in the function closes it. reference counting reclaims the handle's memory but not the registry slot behind it, so the slot is held until the process exits with no other diagnostic. pair the build with a defer close (see defer.md), or close it explicitly.

the constructor list is empty at the moment. it held the three std.net.tls config builders, and a tls config now implements Drop (docs/ownership.md, "destructors"): a config built and never closed gives its slot back when the last value naming it goes away, so the shape this rule reported is no longer a leak. the rule stays for the std types that still close by hand, which docs/destructors_roadmap.md lists; an entry is added when one of them is audited onto the list rather than the destructor.

the rule is deliberately narrow, because a false positive on a resource that is closed elsewhere is worse than no rule. it reports only when every later mention of the name is the receiver of a method that is not a closer. passing the value as an argument, returning it, assigning it, storing it, reading a field off it, or capturing it in a lambda all silence the rule — each of those can hand the resource to something the linter cannot see. it also follows ! and a chain of builder methods back to the constructor, so tls.client_config()!.with_alpn(["h2"]) is recognized.

the constructor list is short on purpose. an entry has to be a call whose result the caller owns, on a type with no method that consumes the receiver instead of closing it — bytes.ByteBuffer is excluded for exactly that reason, since take_bytes() frees the buffer as it takes its contents. the survey behind the list is in destructors_roadmap.md.

the message names the constructor, as it did for a tls config before the destructor:

warning[E307]: 'config' is built by tls.client_config() and never closed; the resource behind the handle is not reclaimed when the binding goes out of scope
  7 |     config := tls.client_config()!
                                       ^
  fix: close it on every exit -- `defer config.close()` next to the build; see docs/destructors_roadmap.md

E308 — binding shadows a module global (warning)

a local, a parameter or a match / if let payload is spelled like one of the module's globals. the binding reads its own storage, so the global is unreachable for the rest of that function — every mention of the name inside is the binding, whichever was declared first. a for variable is exempt: its storage lasts the loop and the global is still reachable on either side of it.

that is legal and sometimes deliberate, which is why it is a warning. it is worth a look anyway: a reader cannot tell a function that means to read the global from one that means to hold its own value, and the two are one rename apart.

it used to be a miscompile rather than a readability problem. the ir namespace is flat and the consumer resolved a bare load/store operand to a global's data slot whenever one existed, so the binding wrote the GLOBAL's storage: the global's own next read came back as whatever the binding had left there, and a container global reached list indexing on invalid list handle once the binding's cleanup freed what the slot still named. globals now carry a storage symbol of their own (__g_<name>) and a binding keeps its written name, so the two can no longer meet.

warning[E308]: binding 'items' shadows the module global declared on line 1; the global is unreachable for the rest of this function
  4 |     mut items: List[Int] := [7, 8, 9]
          ^
  fix: rename the binding, or drop the global if the binding is what you meant

E309 — an unguarded shared global in a program that spawns

a mut module global that holds a list, a map, a set or a struct is one buffer every task can reach, and two tasks writing it at once corrupt it. in a program that spawns tasks, pith check, build, run and test refuse each such global that is written after module init and is neither declared in a guard block nor threadlocal, and pith lint reports it as well. the error names one spawn the program reaches.

the rule covers the whole program the file loads, so a library global is reported when a program that spawns uses it. a program spawns when a function in the entry file holds a spawn, or calls one that does. a call is resolved to its module's function, its import or, for a method, its receiver's type; a method on a receiver whose type does not say whose method it is, such as an interface, counts every method of that name. a call through a function value is not followed. a write is an assignment rooted at the global (g = x, g[k] = x, g.f = x) or a call of a mutating method on it (push, insert, remove, clear and the like). a write made through a function that takes the collection as an argument is not seen.

a global that is built once and only read afterwards needs no lock. build it in its own initializer, catalog := seeded(), rather than in a function called from main, and drop the mut.

error[E309]: 'hits' is a mut module global holding a shared buffer, written after module init, in a program that spawns tasks (app.pith:7)
  1 | mut hits: Map[String, Int] := {}
                                     ^
  fix: declare it in a `guard` block, mark it `threadlocal`, or stop writing it after init

E310 — a call that parks while a mutex is held (warning)

a task that parks inside a locked block, or anywhere in a holding function, keeps the mutex for as long as it is parked, and every task that needs the mutex waits for the park too. pith lint reports these calls when the mutex is held:

  • await
  • a select with no default arm
  • sleep and time.delay
  • a channel recv
  • fs.read and the other std.fs reads
  • tcp.accept, tcp.connect and the std.net.tcp reads
  • the reads, receives and accepts of tls.Conn, tls.Listener and websocket.Conn

a channel send is not reported. it parks only when the channel is full, and sending under the lock is how a writer keeps its frames in order. a closure or a spawn inside the block runs later or on another task, so neither is checked, and a call to a function that parks somewhere inside it is not followed.

warning[E310]: a channel `recv` runs with a mutex held, so the task parks holding it and every task that needs the mutex waits for the park too
  7 |         job := jobs.recv()
                     ^
  fix: park before taking the lock or after releasing it, and copy what the call needs while the lock is held

E311 — two mutexes taken in opposite orders (warning)

a task that holds mutex A and waits for B, while another task holds B and waits for A, deadlocks both. pith lint records every place in the program a mutex is taken while another is held and reports a pair taken in both orders. a mutex taken inside a function called while another is held counts, through calls the checker resolved; a call through a function value is not followed, and a closure or a spawn inside the block is not walked.

a mutex is named by where it lives: a module global by its module and name, a struct field by the struct's type and the field, and every element of a sharded guard by the guard's name. two values of one struct share a name, so nesting them, as a transfer between two accounts does, is not reported, and a mutex reached through a local or a parameter of type Mutex is skipped.

warning[E311]: 'ledger.b_mu' is taken here while 'ledger.a_mu' is held, and ledger.pith:16 takes 'ledger.a_mu' while 'ledger.b_mu' is held; two tasks taking these in opposite orders can deadlock
  10 |         locked b_mu:
                      ^
  fix: take the mutexes in one fixed order everywhere, or release the first before taking the second

E312 — a call that takes a mutex already held (warning)

a mutex is not reentrant, so a task that holds one and calls a function that takes it again waits for itself forever. E273 refuses a locked block nested in a block on the same path; pith lint reports the same mistake made through a call. a module global is matched through the callee and everything it calls, except a sharded guard, whose elements share one name. a struct's mutex is matched when a method called on the receiver whose mutex is held locks that field on self, so self.encode() inside locked self.mu: is reported when encode takes self.mu, while a method called on a different value of the same struct is not.

warning[E312]: this call takes 'self.mu', which is already held here; a mutex is not reentrant, so the task deadlocks
  21 |             self.bump()
                   ^
  fix: call a version that expects the lock held, marked `holding`, or release the mutex before the call

E313: assignment into an index of a String or Bytes

a String and a Bytes value are immutable, so s[i] = v and b[i] += 1 are refused. build a new value instead, or keep bytes that change in a ByteBuffer.

error[E313]: cannot assign into an index of a Bytes: the value is immutable
  3 |     b[0] = 70
             ^
  fix: build a new value, or use a ByteBuffer for bytes that change

E314: an argument label that does not match its parameter

arguments bind by position. a label on an argument (scale(by: 2)) is checked against the name of the parameter in that position, so a label that names a different parameter, or none, is refused rather than bound to the wrong one. a call through a function value takes no labels, since the value carries no parameter names.

error[E314]: argument label 'b' is in the position of parameter 'a'; arguments bind by position
  5 |     print("{scaled(b: 1, a: 5)}")
                         ^

E315: an interface used as a value type

an interface bounds a type parameter. it has no values of its own, so it cannot be the type of a parameter, a return or a field. take a type parameter bounded by the interface instead.

error[E315]: interface 'Shape' is not a value type
  11 | fn show(x: Shape) -> Int:
                  ^
  fix: take a type parameter bounded by it, as in `fn f[T: Shape](x: T)`

E275 — a frozen field is changed

a field declared frozen is set when its struct is built and never changed after (see docs/ownership.md, "frozen fields"). reassigning it, assigning to one of its elements, or calling a method that changes it in place (push, insert, remove and the like) through a path that runs through it is refused. build a new struct with the changed value, or drop frozen and keep the field in a guard block.

error[E275]: 'routes' is frozen, so it cannot be changed in place
  8 |     r.routes.push("/admin")
          ^
  fix: build a new value with the change, or drop `frozen` and keep the field in a guard block

E276 — a guarded value leaves its lock

a guarded field or global holding a collection, or a struct that reaches one outside a guard block, is reached safely only while its mutex is held. inside locked, its value may not be stored in a variable declared outside the block, returned from the block, or captured by a closure made in the block: each keeps a handle to the buffer after the lock is released.

a block that moves the value out and gives the field a new one, out = self.items then self.items = [], is a take and is allowed, since the guard no longer reaches the old buffer. a value that never changes after it is built belongs in a frozen field instead of a guard.

error[E276]: 'messages' is guarded, and this returns its value out of the block, so the buffer is reached after the lock is released
  11 |         return inbox.messages
                      ^
  fix: copy what is needed inside the block, or declare the field `frozen` outside the guard block if it is never changed