diff --git a/compiler/mesh-codegen/src/mir/lower.rs b/compiler/mesh-codegen/src/mir/lower.rs index 3343690..0b2b0bb 100644 --- a/compiler/mesh-codegen/src/mir/lower.rs +++ b/compiler/mesh-codegen/src/mir/lower.rs @@ -9840,22 +9840,22 @@ impl<'a> Lowerer<'a> { // pattern matching codegen (switch on tag). if name.starts_with(|c: char| c.is_uppercase()) { if let Some(type_name) = find_type_for_variant(&name, self.registry) { - // Verify it's actually a nullary constructor (no fields). - let is_nullary = self + let variant_fields = self .registry .sum_type_defs .get(&type_name) .and_then(|info| info.variants.iter().find(|v| v.name == name)) - .map(|v| v.fields.is_empty()) - .unwrap_or(false); - if is_nullary { - return MirPattern::Constructor { - type_name, - variant: name, - fields: vec![], - bindings: vec![], - }; - } + .map(|v| v.fields.len()) + .unwrap_or(0); + // Nullary constructor: no fields. + // Payload-bearing constructor without explicit binder: treat as + // Constructor(_) -- wildcards cover all fields, bind nothing. + return MirPattern::Constructor { + type_name, + variant: name, + fields: vec![MirPattern::Wildcard; variant_fields], + bindings: vec![], + }; } } diff --git a/compiler/mesh-typeck/src/infer.rs b/compiler/mesh-typeck/src/infer.rs index fce7782..0aa2ede 100644 --- a/compiler/mesh-typeck/src/infer.rs +++ b/compiler/mesh-typeck/src/infer.rs @@ -8732,11 +8732,12 @@ fn ast_pattern_to_abstract(pat: &Pattern, env: &TypeEnv, type_registry: &TypeReg if let Some(_scheme) = env.lookup(&name_text) { // Check if this resolves to a sum type constructor by looking // at the type registry for a variant with this name. - if let Some((sum_info, _variant)) = type_registry.lookup_variant(&name_text) { + if let Some((sum_info, variant)) = type_registry.lookup_variant(&name_text) { + let arity = variant.fields.len(); return AbsPat::Constructor { name: name_text, type_name: sum_info.name.clone(), - args: vec![], + args: vec![AbsPat::Wildcard; arity], }; } } @@ -9960,6 +9961,8 @@ fn infer_pattern( // Check if this identifier is a known nullary variant constructor. // In Mesh, bare uppercase names like `Red`, `None`, `Point` in pattern // position should resolve to constructors, not create fresh bindings. + // Bare payload-bearing constructors like `Ok` or `Err` (no parens) are + // treated as `Ok(_)` / `Err(_)` -- match the constructor, ignore payload. if let Some(scheme) = env.lookup(&name_text) { let candidate = ctx.instantiate(scheme); let resolved = ctx.resolve(candidate.clone()); @@ -9970,6 +9973,14 @@ fn infer_pattern( types.insert(pat.syntax().text_range(), candidate.clone()); return Ok(candidate); } + // Uppercase + function type → payload-bearing constructor used bare. + // Return the constructor's result type; the payload is implicitly wildcarded. + if name_text.starts_with(|c: char| c.is_uppercase()) { + if let Ty::Fun(_, ret) = resolved { + types.insert(pat.syntax().text_range(), (*ret).clone()); + return Ok(*ret); + } + } } // Regular identifier pattern: create a fresh binding. diff --git a/compiler/mesh-typeck/tests/exhaustiveness_integration.rs b/compiler/mesh-typeck/tests/exhaustiveness_integration.rs index 0eeb2e8..35fc051 100644 --- a/compiler/mesh-typeck/tests/exhaustiveness_integration.rs +++ b/compiler/mesh-typeck/tests/exhaustiveness_integration.rs @@ -268,3 +268,35 @@ fn test_or_pattern_covers_all_variants() { ); assert_no_errors(&result); } + +// ── Bare payload-bearing constructor patterns ──────────────────────── + +/// Bare `Ok` / `Err` without explicit binder compiles as sugar for `Ok(_)` / `Err(_)`. +#[test] +fn test_bare_payload_constructor_is_exhaustive() { + let result = check_source( + "let r = Ok(1)\n\ + case r do\n Ok -> 1\n Err -> 0\nend", + ); + assert_no_errors(&result); +} + +/// Bare `Some` without binder is sugar for `Some(_)`. +#[test] +fn test_bare_some_constructor_is_exhaustive() { + let result = check_source( + "let o = Some(42)\n\ + case o do\n Some -> 1\n None -> 0\nend", + ); + assert_no_errors(&result); +} + +/// Mix of bare and explicit binder forms in the same case is valid. +#[test] +fn test_bare_and_explicit_binder_mixed() { + let result = check_source( + "let r = Ok(1)\n\ + case r do\n Ok(_) -> 1\n Err -> 0\nend", + ); + assert_no_errors(&result); +} diff --git a/compiler/meshc/tests/e2e.rs b/compiler/meshc/tests/e2e.rs index a3cf3da..b0936ca 100644 --- a/compiler/meshc/tests/e2e.rs +++ b/compiler/meshc/tests/e2e.rs @@ -7154,3 +7154,38 @@ fn e2e_m032_limit_timer_service_cast() { let output = compile_and_run(M032_TIMER_SERVICE_CAST_MAIN); assert_eq!(output, "0\n"); } + +/// Bare payload-bearing constructor in pattern position (no explicit binder). +/// `Ok` / `Err` without `(_)` must compile and match correctly -- sugar for `Ok(_)`. +#[test] +fn e2e_bare_payload_constructor_pattern() { + let output = compile_and_run( + r#" +fn main() do + let r = Err("boom") + case r do + Ok -> println("ok") + Err -> println("err") + end +end +"#, + ); + assert_eq!(output, "err\n"); +} + +/// Bare `Ok` with actual Ok value hits the Ok arm. +#[test] +fn e2e_bare_payload_constructor_ok_arm() { + let output = compile_and_run( + r#" +fn main() do + let r = Ok(42) + case r do + Ok -> println("ok") + Err -> println("err") + end +end +"#, + ); + assert_eq!(output, "ok\n"); +}