ferrule documentation[styled mode]
specrfcshome

diagnostics and lints

Status: α1


diagnostics and lints


Principles

Ferrule prioritizes clear, actionable error messages:


Compile-Time Checks

CheckDescription
exhaustivenessmatch covers all variants
effect coveragecalled function effects ⊆ caller effects
capability flowfs effect requires Fs capability
boolean coercionif value rejected, use if value == true
numeric coercionconversions must be explicit
region safetycross-region view misuse flagged
unused bindingswarn on unused const/var
nominal typingincompatible types even with same structure
type inferenceambiguous cases require annotation

Example Diagnostics

Effect Mismatch

error: effect not declared
  ┌─ src/server.fe:12:15

12│   const data = fs.readAll(path);
  │                ^^^^^^^^^^^^^^^^^ function requires effect [fs]

  = note: fs.readAll has effects [fs]
  = help: add 'effects [fs]' to function signature:
          function loadConfig(...) -> Config effects [fs] { ... }

Missing Error Handling

error: unhandled fallible result
  ┌─ src/parser.fe:8:18

 8│   const port = parsePort(input);
  │                ^^^^^^^^^^^^^^^^^^ returns Result<Port, ParseError>

  = note: parsePort can fail with ParseError
  = help: handle the error using one of:
          • check parsePort(input)      -- propagate error
          • match parsePort(input) { ... }  -- handle explicitly

Implicit Boolean Coercion

error: expected Bool, found u32?
  ┌─ src/main.fe:15:8

15│   if count {
  │      ^^^^^ type is u32?, not Bool

  = note: ferrule does not allow implicit boolean coercion
  = help: be explicit about the condition:
          • if count != null { ... }
          • if count != null && count != 0 { ... }

Nominal Type Mismatch

error: type mismatch
  ┌─ src/main.fe:20:18

20│   const post: PostId = user;
  │                        ^^^^ expected PostId, found UserId

  = note: UserId and PostId are different types (nominal typing)
  = note: both have structure { id: u64 } but are not compatible
  = help: use explicit conversion:
          const post: PostId = toPostId(user);

Missing Capability Parameter

error: effect [fs] requires capability
  ┌─ src/io.fe:22:1

22│ function readConfig(path: Path) -> Config error IoError effects [fs] {
  │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  = note: function declares effect [fs] but has no Fs capability parameter
  = help: add capability parameter:
          function readConfig(path: Path, cap fs: Fs) -> Config error IoError effects [fs]

Non-Exhaustive Match

error: non-exhaustive match
  ┌─ src/http.fe:45:3

45│   match response {
  │   ^^^^^ missing coverage for HttpResponse variants

46│     Ok { data } -> process(data);
47│     NotFound -> log.warn("not found");
48│   }

  = note: HttpResponse has 5 variants: Ok, NotFound, Forbidden, ServerError, Timeout
  = help: missing patterns:
          • Forbidden { ... }
          • ServerError { ... }
          • Timeout { ... }
          or add a catch-all: _ -> ...

Region Safety Violation

error: view outlives region
  ┌─ src/buffer.fe:18:10

18│   return buf;
  │          ^^^ view bound to region 'arena' which is disposed at line 19

17│   const buf = arena.alloc<u8>(1024);
  │               ----- region created here
19│   defer arena.dispose();
  │         ----- region disposed here

  = help: either return the region with the view, or copy data to outer region:
          • return { buf: buf, region: arena }
          • return view.copy(buf, to = region.heap())

Type Inference Required

error: cannot infer type
  ┌─ src/main.fe:10:7

10│   const result = compute();
  │         ^^^^^^ type annotation required

  = note: compute() returns a generic type that cannot be inferred
  = help: add type annotation:
          const result: Data = compute();

Warning Levels

LevelBehavior
errorcompilation fails
warningcompilation continues, logged
hintinformational suggestion

Suppressing Warnings

@allow(unused_binding)
const _ignored = someValue;

@allow(deprecated)
oldApi.call();

Lint Configuration

In deps.fe:

.{
  .lints = .{
    .unused_binding = .warn,
    .deprecated = .error,
    .implicit_return = .allow,
  },
}