From 5614400a7adb524bf54d278378228b3b5bca8561 Mon Sep 17 00:00:00 2001 From: Chad Peppers Date: Sat, 11 Jul 2026 00:56:49 -0500 Subject: [PATCH] feat(checker): declare builtin UnhandledMatchError (Error subclass) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A `match` expression with no `default` arm throws `UnhandledMatchError` at the implicit no-match point, and codegen materializes that class reference — but it was not among elephc's declared builtin throwables, so every default-less `match` failed type resolution with "Undefined class: UnhandledMatchError" (ward-sse DatastarEventSerializer and other match-dispatch classes). Declare `UnhandledMatchError extends Error` alongside TypeError/ValueError/ ArithmeticError in `inject_builtin_throwables`, and add it to the autoload builtin-class list. Like its siblings it is a nominal marker subclass — constructor/getMessage/etc. are inherited from Error transitively. Whole-codebase survey 852 -> 854 OK (80.4%), zero regressions: error_tests byte-identical to baseline, full codegen sweep unchanged at the pre-existing 440 failures, a default-less `match` now compiles and runs natively. Adds regression test test_match_without_default_compiles. Refs #605. Co-Authored-By: Claude Opus 4.8 --- src/autoload/mod.rs | 1 + .../checker/builtin_types/declarations.rs | 20 ++++++++++++++++ .../control_flow/branches_and_loops.rs | 23 +++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/src/autoload/mod.rs b/src/autoload/mod.rs index 151f3928e7..21b81df38a 100644 --- a/src/autoload/mod.rs +++ b/src/autoload/mod.rs @@ -45,6 +45,7 @@ const BUILTIN_CLASS_LIKE_NAMES: &[&str] = &[ "EmptyIterator", "Error", "ArithmeticError", + "UnhandledMatchError", "Exception", "Fiber", "FiberError", diff --git a/src/types/checker/builtin_types/declarations.rs b/src/types/checker/builtin_types/declarations.rs index ab09a37f66..1438483759 100644 --- a/src/types/checker/builtin_types/declarations.rs +++ b/src/types/checker/builtin_types/declarations.rs @@ -72,6 +72,7 @@ pub(crate) fn inject_builtin_throwables( "TypeError", "ValueError", "ArithmeticError", + "UnhandledMatchError", "Exception", "RuntimeException", "JsonException", @@ -246,6 +247,25 @@ pub(crate) fn inject_builtin_throwables( used_traits: Vec::new(), }, ); + // Thrown by a `match` expression when no arm matches and no `default` arm is present. Codegen + // materializes it at the implicit no-match throw, so it must be a declared builtin subclass of + // Error for the checker to resolve the type reference. + class_map.insert( + "UnhandledMatchError".to_string(), + FlattenedClass { + name: "UnhandledMatchError".to_string(), + extends: Some("Error".to_string()), + implements: Vec::new(), + is_abstract: false, + is_final: false, + is_readonly_class: false, + properties: Vec::new(), + methods: Vec::new(), + attributes: Vec::new(), + constants: Vec::new(), + used_traits: Vec::new(), + }, + ); // Fiber: cooperative coroutine class. Methods are placeholders here — the // codegen intercepts every Fiber operation (`new Fiber(...)`, instance diff --git a/tests/codegen/control_flow/branches_and_loops.rs b/tests/codegen/control_flow/branches_and_loops.rs index 4fb7681c7e..ff421f7a7f 100644 --- a/tests/codegen/control_flow/branches_and_loops.rs +++ b/tests/codegen/control_flow/branches_and_loops.rs @@ -250,3 +250,26 @@ fn test_while_null_no_loop() { } // --- Ternary operator --- + +// --- match without default (UnhandledMatchError) --- + +/// Regression: a `match` with no `default` arm references the builtin `UnhandledMatchError` class +/// (thrown at the implicit no-match point), so that class must be a declared builtin subclass of +/// `Error`. Before it was declared, any default-less `match` failed with +/// "Undefined class: UnhandledMatchError". The happy path (a matching arm) compiles and runs. +#[test] +fn test_match_without_default_compiles() { + let out = compile_and_run( + r#" "one", + 2 => "two", + }; +} +echo classify(1), classify(2); +"#, + ); + assert_eq!(out, "onetwo"); +}