Proposal: Range Patterns on char and byte

Status: Approved Author: Eric (with Claude) Created: 2026-03-05 Approved: 2026-03-05


Summary

Extend range patterns in match to work on char and byte types, enabling concise character classification in pattern matching.

match ch {
    'a'..'z' | 'A'..'Z' -> TokenKind.Ident,
    '0'..'9' -> TokenKind.Number,
    ' ' | '\t' | '\n' -> TokenKind.Whitespace,
    _ -> TokenKind.Symbol,
}

Motivation

The Problem

The spec mentions range patterns in the pattern list (spec 15) and demonstrates 1..10 for integer ranges. But it does not define whether range patterns work on char or byte. This is a critical gap for any text-processing code.

Without char/byte range patterns, classification requires verbose alternatives:

// Current: explicit enumeration or guards
match ch {
    ch if ch >= 'a' && ch <= 'z' -> ...,
    ch if ch >= 'A' && ch <= 'Z' -> ...,
    ch if ch >= '0' && ch <= '9' -> ...,
    _ -> ...,
}

// Or classification methods (if they existed):
match true {
    _ if ch.is_alpha() -> ...,
    _ if ch.is_digit() -> ...,
    _ -> ...,
}

Both defeat the purpose of pattern matching — the pattern should express the classification directly.

What We Want

match byte_val {
    b'a'..b'z' | b'A'..b'Z' | b'_' -> self.read_ident(),
    b'0'..b'9' -> self.read_number(),
    b'"' -> self.read_string(),
    b' ' | b'\t' | b'\n' | b'\r' -> self.skip_whitespace(),
    _ -> self.error("unexpected"),
}

This is the natural pattern-matching style for lexers.

Prior Art

LanguageChar Range PatternSyntax
RustYes'a'..='z'
SwiftYes"a"..."z" (in switch)
ZigNo (uses if chains)
GoNo (no pattern matching)
GleamNo (no range patterns)
HaskellYes['a'..'z'] (list syntax, not pattern)

Design

Range Pattern Syntax

Range patterns use the same syntax as range expressions:

range_pattern = const_pattern ".." const_pattern .       /* exclusive end */
range_pattern = const_pattern "..=" const_pattern .      /* inclusive end */

For char and byte:

match ch {
    'a'..'z' -> ...,     // exclusive: 'a' through 'y'
    'a'..='z' -> ...,    // inclusive: 'a' through 'z'
}

Const Pattern Endpoints

Range pattern endpoints shall be compile-time constants: either literals or $-prefixed constant identifiers:

let $MAX_ASCII = '\x7F';
let $MIN_DIGIT = '0';

match ch {
    $MIN_DIGIT..='9' -> "digit",
    '\x00'..$MAX_ASCII -> "other ascii",
    _ -> "non-ascii",
}

Supported Types

Range patterns are valid for types that implement Comparable with a discrete domain:

TypeExampleOrdering
int0..100Numeric
char'a'..'z'Unicode code point
byteb'0'..b'9'Numeric (0-255)

float is excluded from range patterns due to NaN semantics and precision issues. Use guards for float ranges: x if x >= 0.0 && x < 1.0 -> ....

Semantics

A range pattern lo..hi matches value v when v >= lo && v < hi. A range pattern lo..=hi matches value v when v >= lo && v <= hi.

Both endpoints must be compile-time constants (literals or $ constants).

Exhaustiveness

Range patterns participate in exhaustiveness checking:

match b: byte {
    0..128 -> "ascii",
    128..=255 -> "non-ascii",
}
// Exhaustive: covers 0..=255
match ch: char {
    'a'..='z' -> "lower",
    _ -> "other",           // required: char range is not exhaustive
}

Combining with Or-Patterns

Range patterns compose with |:

match ch {
    'a'..='z' | 'A'..='Z' | '_' -> "ident_start",
    '0'..='9' -> "digit",
    _ -> "other",
}

Combining with At-Patterns

Range patterns compose with @:

match ch {
    c @ 'a'..='z' -> handle_lower(c),
    c @ '0'..='9' -> handle_digit(c),
    _ -> handle_other(),
}

Grammar Changes

// Update pattern production to include range patterns:
pattern = literal_pattern
        | identifier_pattern
        | wildcard_pattern
        | variant_pattern
        | struct_pattern
        | list_pattern
        | or_pattern
        | at_pattern
        | range_pattern .

range_pattern = const_pattern ".." const_pattern
              | const_pattern "..=" const_pattern .

const_pattern = literal | "$" identifier .

The const_pattern production allows both literals and compile-time constant identifiers as range endpoints.


Type Checking

Both endpoints must have the same type

match x {
    'a'..10 -> ...,   // error: cannot mix char and int in range pattern
}

Endpoints must be comparable

The type must implement Comparable. All supported types (int, char, byte) do.

Empty ranges are warnings

match x {
    10..5 -> ...,     // warning: empty range pattern (lo > hi)
    'z'..'a' -> ...,  // warning: empty range pattern
}

Overlap detection

The compiler shall warn about overlapping range patterns:

match x {
    0..10 -> ...,
    5..15 -> ...,     // warning: overlapping range patterns (5..10)
    _ -> ...,
}

Relationship to Existing Roadmap

Range patterns for int are already tracked in plans/roadmap/section-09-match.md. This proposal extends that work to char and byte types, and adds the const_pattern endpoint grammar. The existing 'a'..='z' char range pattern support (parsing and evaluation, done 2026-02-14) is formalized by this proposal.


Migration / Compatibility

  • No breaking changes. Range patterns are a new pattern form.
  • Extends existing range syntax. Uses .. and ..= which already exist as expression operators.
  • Backward compatible: Existing int range patterns (if implemented) are unchanged.

Depends On

  • byte-literals-proposal.md [approved] — uses b'x' syntax in byte range patterns
  • pattern-matching-exhaustiveness-proposal.md [approved] — exhaustiveness algorithm extended

References


Changelog

  • 2026-03-05: Initial draft
  • 2026-03-05: Approved — removed float from supported types; added const_pattern endpoints ($identifier); referenced existing roadmap task; resolved all open questions (half-open ranges deferred, char ordering is code point)