Error Handling

This chapter specifies error handling semantics in Clef, including the relationship between compile-time error propagation through tooling and runtime error handling in compiled applications.

Overview

Clef takes a fundamentally different approach to error handling than managed F#:

AspectManaged F#Clef
Optional valuesoption<'T> with null representation for Nonevoption<'T> (ValueOption) with no null
Failure handlingExceptions (raise, try/with)Result<'T, 'E> with explicit propagation
Null valuesPermitted for reference typesNot permitted; null-free by construction
Runtime type errorsInvalidCastException, NullReferenceExceptionCannot occur; prevented by type system

Design Principle: Errors are values, not control flow. The type system encodes fallibility explicitly, making error handling visible and verifiable at compile time.

The Dual Nature of Error Handling

Clef error handling operates at two distinct levels:

  1. Application Runtime: How compiled Fidelity framework applications handle errors during execution
  2. Tooling Integration: How Clef Compiler Service (CCS) propagates errors through the Language Server Protocol to editors like Lattice

These two domains have different requirements and constraints, but must remain coherent.

Application Runtime Error Handling

The Result Type

The Result<'T, 'E> type is the primary mechanism for representing operations that may fail:

type Result<'T, 'E> =
    | Ok of 'T
    | Error of 'E

Operations that can fail return Result values rather than raising exceptions:

// Managed F# style (NOT used in Clef applications)
let divide x y =
    if y = 0 then raise (DivideByZeroException())
    else x / y

// Clef style
let divide x y : Result<int, DivisionError> =
    if y = 0 then Error DivisionByZero
    else Ok (x / y)

Standard Error Types

Clef defines standard error types for common failure modes:

type ArithmeticError =
    | DivisionByZero
    | Overflow
    | Underflow

type IndexError =
    | OutOfBounds of index: int * length: int

type ParseError =
    | InvalidFormat of input: string * expected: string
    | UnexpectedEnd

type IOError =
    | NotFound of path: string
    | PermissionDenied of path: string
    | DeviceError of code: int

Note: The exact set of standard error types is subject to further specification as the standard library matures.

The voption Type

For optional values where absence is not an error, voption<'T> (ValueOption) provides a null-free representation:

type voption<'T> =
    | ValueSome of 'T
    | ValueNone

The voption<'T> type has an explicit discriminator with no null representation.

// Looking up a value that may not exist
let tryFind key (map: Map<'K, 'V>) : voption<'V> =
    match Map.tryFind key map with
    | ValueSome v -> ValueSome v
    | ValueNone -> ValueNone

Result Propagation

Clef provides computation expression syntax for Result propagation:

let result {
    let! x = tryParseInt "42"
    let! y = tryParseInt "17"
    return x + y
}

This is equivalent to explicit binding:

match tryParseInt "42" with
| Error e -> Error e
| Ok x ->
    match tryParseInt "17" with
    | Error e -> Error e
    | Ok y -> Ok (x + y)

Try/With Syntax Compatibility

Clef preserves try/with/finally syntax for compatibility with standard F# tooling:

try
    riskyOperation()
with
| :? SomeException as e -> handleError e

However, the semantics differ:

  • In Clef, try/with may be used for effect handling (delimited continuations) rather than exception catching
  • The exact semantics depend on the effect system specification
  • Code using try/with for exception handling must be migrated to Result-based patterns for native compilation

Tooling Note: Lattice and other editors will parse try/with expressions normally. CCS may emit warnings when exception-style patterns are detected, guiding migration to Result-based alternatives.

Null-Freedom

Clef is null-free by construction. The following are compile-time errors:

let x : string = null           // ERROR: null literal not available
let y = Unchecked.defaultof<_>  // ERROR for reference types in most contexts
 

This eliminates entire classes of runtime errors:

Managed F# Runtime ErrorClef
NullReferenceExceptionCannot occur
InvalidCastExceptionCannot occur (static typing)
ArrayTypeMismatchExceptionCannot occur (no covariant arrays)

Tooling Integration

CCS Error Propagation

Clef Compiler Service (CCS) must propagate errors through the tooling stack in a format compatible with existing F# tooling infrastructure.

Diagnostic Format

CCS diagnostics follow the F# compiler diagnostic format:

filepath(line,col)-(line,col): severity code: message

For example:

src/Main.clef(12,5)-(12,15): error CCS8010: 'null' is not permitted in Clef; use 'ValueNone' for an absent value

Error Codes

CCS uses error codes in the CCS8xxx range to distinguish native-specific diagnostics:

RangeCategory
CCS8000-CCS8099Type system: identity, measures, seals and ranges, null-freedom (CCS8010, Types and Type Constraints), access kinds
CCS8100-CCS8199Memory management (regions, lifetimes)
CCS8200-CCS8299Platform bindings
CCS8300-CCS8399Effect system
CCS8400-CCS8499Code generation

The CCS code table

Decision D3 of the dimensional hardening (2026-09-04): every diagnostic the compiler service reports carries a code in the CCS series; the FS prefix is retired. Codes are allocated inside the blocks above and never reassigned. Inherited lexer and parser diagnostics keep their F# number under the CCS prefix (FS0058 becomes CCS0058); the block CCS0000CCS0999 is reserved for that family.

CodeSeverityMeaning
CCS8000ErrorAn operator’s operand is not numeric (Width Inference, the numeric constraint)
CCS8001ErrorThe kind of an operator’s operands cannot be determined at a binding that is not generalisable
CCS8002ErrorA conversion’s source is not numeric
CCS8003ErrorType mismatch
CCS8004ErrorType constructor arity mismatch
CCS8005ErrorInfinite type (a type variable occurs in its own solution)
CCS8006ErrorTuple mismatch (length or struct kind)
CCS8007ErrorByref kind mismatch
CCS8008ErrorThe constructor is not defined
CCS8009ErrorThe value or constructor is not defined
CCS8010ErrorThe null keyword is not permitted (Types and Type Constraints)
CCS8011ErrorAn integer or dimensioned real whose range is unobservable; for a bare real flowing into a dimension, at the dimensioning seam naming the bare source (Width Inference §6, Numeric Selection §6)
CCS8012Warning (error under --warnaserror)A value’s analysed range is not covered by the boundary’s declared representation (Numeric Selection §5)
CCS8013retired“two seals meet”: there are no seals (NTU Types)
CCS8014InfoA declared boundary representation wider than the range requires; the representation the open argmin would select is named
CCS8015retired“sealed arithmetic may wrap”: arithmetic on analysed ranges never overflows
CCS8016Warning (error under --warnaserror)An analysed range exceeds a higher-provenance claim, a library law’s range or a declaration (Numeric Selection §3.4)
CCS8017retired“a conversion cannot hold the range”: there are no conversions
CCS8018ErrorA literal suffix: every width suffix (L, u, uy, s, n, f), I, and any suffix the language does not have (Width Inference §7)
CCS8019Warning (not promoted by --warnaserror during the alias period)A width-named spelling (uint32, byte, float32, …) or a width suffix on a literal (0L, 5u, 1.0f, …) during the CS-12 alias period: the spelling denotes the one kind and the representation it names is an interim declared boundary; retired at step three of the migration, when a spelling is CCS8706 and a suffix CCS8018
CCS8020–CCS8022ErrorAccess kinds (Access Kinds)
CCS8030–CCS8033ErrorPlatform intrinsics (Platform Bindings)
CCS8040–CCS8050ErrorUnits of measure (Units of Measure): mismatch, no integer solution, not in scope, cyclic abbreviation, variable in a literal, sort mismatch, no dimension, unresolved at a non-generalisable binding, rational exponent, parameterised definition, arity
CCS8060Errorobj is not a Clef type
CCS8061ErrorBoxing is not a Clef operation
CCS8062ErrorDynamic invocation is not a Clef operation
CCS8063ErrorQuote expression patterns are not a Clef construct
CCS8064ErrorInstance member patterns (object expressions) are not a Clef construct
CCS8065ErrorExpression splices (%e, %%e) are not a Clef construct: a quotation is compile-time data read whole (Expressions, Quoted Expressions)
CCS8066ErrorA quotation referenced from executed code: a quotation has no run-time value; reported at each reachable reference, or at the quotation when it stands in executed expression position
CCS8080ErrorA BCL type or namespace is not available in Clef
CCS8081ErrorThe System namespace is not available in Clef
CCS8082ErrorThe Microsoft namespace is not available in Clef
CCS8083ErrorUnchecked.defaultof is not available in Clef
CCS8090ErrorInternal invariant violated in the compiler service (reported, never swallowed)
CCS8091WarningA nullable annotation is ignored; native types are null-free by design
CCS8092WarningType arguments applied to a value that is not a type scheme
CCS8100ErrorRegion mismatch (Memory Regions)
CCS8101ErrorLifetime error
CCS8102ErrorA reference escapes its region
CCS8200ErrorPlatform binding error
CCS8201ErrorUnsupported platform operation
CCS8202ErrorPlatform binding undefined
CCS8203ErrorA site needs a width dimension the platform description does not declare (Platform Bindings, NTU Dimensional Architecture §7.1); never a default
CCS8204ErrorA sealed value’s representation is not offered by the platform description, absent or declared unavailable (Numeric Selection §7)
CCS8205InfoA [platform] key the project file carries that the compiler does not read (word_size): width dimensions and representations come from the platform description
CCS8206ErrorAn element of the platform description the compiler cannot read (a field that is not a literal, an element that is not the record its list is declared over, a Core that is neither Some core nor None), reported at the declaration
CCS8207ErrorAn element of the platform description outside its vocabulary (a capability, family or boundary tag not in its closed set, a width or representation of no bits, a name declared twice, a Register width disagreeing with the word size), reported at the declaration
CCS8208ErrorA second platform description of one form among the platform binding’s sources; the first is read, each other is reported at its declaration
CCS8209ErrorMalformed, inconsistent or ambiguous device-access declaration or plan selection
CCS8210ErrorA used MMIO operation lacks established access evidence, including a contradicted or pending required predicate
CCS8300WarningException-style error handling detected; use the Result-based pattern
CCS8400ErrorCode generation error
CCS8401ErrorUnsupported construct in code generation
CCS8500–CCS8505see Interactive DevelopmentInteractive session
CCS8701–CCS8705ErrorRecord field label resolution (Name Resolution)
CCS8706ErrorA type name in an annotation that resolves to nothing (no abbreviation, definition, primitive or built-in constructor), reported at the annotation; the error type it leaves unifies with anything, so this is the one report of the failure
CCS8710ErrorNull constraint is not a Clef constraint
CCS8711ErrorUnsupported constraint

LSP Compatibility

CCS implements the Language Server Protocol for editor integration. Key considerations:

  1. Diagnostic Publishing: Errors are published via textDocument/publishDiagnostics in standard LSP format
  2. Code Actions: Quick fixes (e.g., “Insert the explicit conversion”, “Add the measure annotation”) are provided via textDocument/codeAction
  3. Hover Information: Type information displays native types

Lattice Integration Model

The multi-target editor model is well established in the F# ecosystem: Ionide routes a single editing experience across several F# compilation backends.

TargetIntegration Point
.NETFSharp.Compiler.Service
FableFable.Compiler (JavaScript output)
WebSharperWebSharper.Compiler

Lattice, the Clef editor tooling, follows this model:

Lattice ←→ LSP ←→ CCS ←→ Composer Compiler ←→ MLIR/LLVM

Extension Points

CCS provides extension points for Lattice integration:

  1. Project Recognition: .fidproj files identify Clef projects
  2. Target Selection: Lattice can route to CCS when native compilation is detected
  3. Shared Parsing: Syntax parsing uses standard F# lexer/parser for compatibility
  4. Semantic Divergence: Type checking and code generation use native semantics

Compatibility Considerations

To maintain compatibility with the broader F# ecosystem:

  1. Syntax Compatibility: Clef code parses as valid F# syntax
  2. Type Notation: Types are expressed using standard F# type notation
  3. Error Format: Diagnostics follow F# compiler conventions
  4. Incremental Adoption: Projects can mix managed and native targets during migration

Editor Experience

The design-time experience for Clef should be consistent with managed F#:

FeatureBehavior
Syntax highlightingStandard F# highlighting
Error underliningRed squiggles for errors, yellow for warnings
Hover typesShows native type representations
AutocompleteSuggests native library members
Go to definitionNavigates to native library source
Quick fixesOffers native-appropriate fixes

Error Handling Patterns

Railway-Oriented Programming

Clef encourages railway-oriented programming with Result:

let processOrder orderId =
    orderId
    |> validateOrderId
    |> Result.bind fetchOrder
    |> Result.bind validateInventory
    |> Result.bind processPayment
    |> Result.bind shipOrder

Error Aggregation

For operations that may produce multiple errors:

type ValidationErrors = ValidationErrors of ValidationError list

let validateAll validators input =
    validators
    |> List.map (fun v -> v input)
    |> List.fold aggregateErrors (Ok input)

Partial Success

For operations where partial results are meaningful:

type PartialResult<'T, 'E> =
    | Complete of 'T
    | Partial of 'T * 'E list
    | Failed of 'E list

Grammar

result-type := Result < type , type >

voption-type := voption < type >

result-expr :=
    Ok expr
    Error expr

voption-expr :=
    ValueSome expr
    ValueNone

result-bind := let! pattern = expr in expr

result-return := return expr

Diagnostics

Null-freedom is by construction and has exactly one diagnostic, CCS8010 (the null keyword is not permitted, Types and Type Constraints). There is no second null diagnostic: no type “supports null”, no value is uninitialised, and Unchecked.defaultof is BCL surface rejected as such. The codes this chapter contributes:

CodeSeverityMessage
CCS8100ErrorRegion mismatch: a handle of region ‘{r1}’ where region ‘{r2}’ is required (Memory Regions)
CCS8300WarningException-style error handling detected; use the Result-based pattern

A warning is promoted to an error under the --warnaserror policy, the rule every warning in the framework follows (the FPGA timing budget’s CCS0100 is the reference case).

Areas Requiring Further Specification

The following areas require additional design work:

  1. Effect System Integration: How Result interacts with algebraic effects and delimited continuations
  2. Async/Concurrent Errors: Error propagation in concurrent and asynchronous contexts
  3. Interop Boundaries: Error translation at FFI boundaries with C libraries
  4. Panic vs. Error: Distinction between recoverable errors (Result) and unrecoverable panics
  5. Stack Traces: Diagnostic information for debugging without managed exception infrastructure
  6. Tooling PR Strategy: Concrete changes needed for Lattice/FSAC to support CCS