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 Ionide

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 (see Effects)
  • Code using try/with for exception handling must be migrated to Result-based patterns for native compilation

Tooling Note: Ionide 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.fs(12,5)-(12,15): error FS8100: Cannot use 'null' in Clef; use 'ValueNone' for optional values

Error Codes

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

RangeCategory
FS8000-FS8099Type system (null-freedom, access kinds)
FS8100-FS8199Memory management (regions, lifetimes)
FS8200-FS8299Platform bindings
FS8300-FS8399Effect system
FS8400-FS8499Code generation

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., “Replace null with ValueNone”) are provided via textDocument/codeAction
  3. Hover Information: Type information displays native types

Ionide Integration Model

Ionide currently supports multiple F# compilation targets:

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

Clef follows this model:

Ionide ←→ LSP ←→ CCS ←→ Firefly Compiler ←→ MLIR/LLVM

Extension Points

CCS provides extension points for Ionide integration:

  1. Project Recognition: .fidproj files identify Clef projects
  2. Target Selection: Ionide 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

CodeSeverityMessage
FS8100ErrorCannot use ’null’ in Clef; use ‘ValueNone’ for optional values
FS8101ErrorCannot use ’null’ in Clef; all values must be initialized
FS8102WarningException-style error handling detected; consider Result-based pattern
FS8103ErrorType does not support ’null’ in Clef
FS8104WarningUnchecked.defaultof<‘T> produces undefined behavior for reference types

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 Ionide/FSAC to support CCS

See Also