Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions compiler/mesh-codegen/src/mir/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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![],
};
}
}

Expand Down
15 changes: 13 additions & 2 deletions compiler/mesh-typeck/src/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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],
};
}
}
Expand Down Expand Up @@ -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());
Expand All @@ -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.
Expand Down
32 changes: 32 additions & 0 deletions compiler/mesh-typeck/tests/exhaustiveness_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
35 changes: 35 additions & 0 deletions compiler/meshc/tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Loading