use match_lang::{Arm, Checker, Model, Pat, Signature};
#[derive(Clone, Debug)]
enum Ty {
U8,
Str,
Bool,
Shape,
Point,
Slice(Box<Ty>),
}
struct Lang;
impl Model for Lang {
type Ty = Ty;
fn signature(&self, ty: &Ty) -> Signature<Ty> {
match ty {
Ty::U8 => Signature::Int { min: 0, max: 255 },
Ty::Str => Signature::Opaque,
Ty::Bool => Signature::Bool,
Ty::Shape => Signature::Sum { count: 3 },
Ty::Point => Signature::Product,
Ty::Slice(elem) => Signature::Slice {
elem: (**elem).clone(),
},
}
}
fn fields(&self, ty: &Ty, variant: u32, out: &mut Vec<Ty>) {
match (ty, variant) {
(Ty::Shape, 0) => out.push(Ty::U8),
(Ty::Shape, 1) | (Ty::Point, _) => out.extend([Ty::U8, Ty::U8]),
_ => {}
}
}
fn variant_name(&self, ty: &Ty, variant: u32) -> Option<&str> {
match (ty, variant) {
(Ty::Shape, 0) => Some("Circle"),
(Ty::Shape, 1) => Some("Rect"),
(Ty::Shape, 2) => Some("Empty"),
(Ty::Point, _) => Some("Point"),
_ => None,
}
}
}
enum Src {
Wild,
Bool(bool),
Int(i128, i128, &'static str),
Str(&'static str),
Variant(u32, Vec<Src>, &'static str),
Struct(Vec<Src>, &'static str),
Or(Vec<Src>),
Slice(Vec<Src>, Option<Vec<Src>>, &'static str),
}
impl Src {
fn lower(&self, strings: &mut Vec<&'static str>) -> Pat {
match self {
Src::Wild => Pat::wild(),
Src::Bool(b) => Pat::bool(*b),
Src::Int(lo, hi, _) => Pat::range(*lo, *hi),
Src::Str(text) => {
let id = strings.iter().position(|s| s == text).unwrap_or_else(|| {
strings.push(text);
strings.len() - 1
});
Pat::lit(id as u64)
}
Src::Variant(v, fields, _) => Pat::variant(*v, fields.iter().map(|f| f.lower(strings))),
Src::Struct(fields, _) => Pat::product(fields.iter().map(|f| f.lower(strings))),
Src::Or(alts) => Pat::or(alts.iter().map(|a| a.lower(strings))),
Src::Slice(prefix, None, _) => Pat::slice(prefix.iter().map(|p| p.lower(strings))),
Src::Slice(prefix, Some(suffix), _) => {
let prefix: Vec<Pat> = prefix.iter().map(|p| p.lower(strings)).collect();
let suffix: Vec<Pat> = suffix.iter().map(|p| p.lower(strings)).collect();
Pat::slice_rest(prefix, suffix)
}
}
}
fn text(&self) -> String {
match self {
Src::Wild => "_".into(),
Src::Bool(b) => b.to_string(),
Src::Int(_, _, t) | Src::Variant(_, _, t) | Src::Struct(_, t) | Src::Slice(_, _, t) => {
(*t).into()
}
Src::Str(t) => format!("{t:?}"),
Src::Or(alts) => alts.iter().map(Src::text).collect::<Vec<_>>().join(" | "),
}
}
fn children(&self) -> Vec<&Src> {
match self {
Src::Variant(_, kids, _) | Src::Struct(kids, _) | Src::Or(kids) => {
kids.iter().collect()
}
Src::Slice(prefix, suffix, _) => prefix.iter().chain(suffix.iter().flatten()).collect(),
_ => Vec::new(),
}
}
fn node(&self, index: usize) -> Option<&Src> {
let mut stack = vec![self];
let mut seen = 0;
while let Some(node) = stack.pop() {
if seen == index {
return Some(node);
}
seen += 1;
stack.extend(node.children().into_iter().rev());
}
None
}
}
fn int(v: i128, text: &'static str) -> Src {
Src::Int(v, v, text)
}
fn report(checker: &mut Checker<Ty>, name: &str, ty: Ty, arms: &[(Src, bool)]) {
let mut strings = Vec::new();
let lowered: Vec<Arm> = arms
.iter()
.map(|(src, guarded)| {
let pat = src.lower(&mut strings);
if *guarded {
Arm::guarded(pat)
} else {
Arm::new(pat)
}
})
.collect();
println!("match {name}:");
match checker.check(&Lang, &ty, &lowered) {
Err(err) => println!(" error: {err}"),
Ok(report) => {
for w in report.missing() {
println!(" error: pattern `{}` not covered", w.display(&Lang));
}
for &arm in report.unreachable() {
println!(
" warning: arm {arm} `{}` is unreachable",
arms[arm].0.text()
);
}
for r in report.redundant() {
let text = arms[r.arm]
.0
.node(r.node)
.map_or_else(String::new, Src::text);
println!(
" warning: in arm {}, alternative `{text}` is unreachable",
r.arm
);
}
if report.is_exhaustive()
&& report.unreachable().is_empty()
&& report.redundant().is_empty()
{
println!(" ok");
}
}
}
}
fn main() {
let mut checker = Checker::new().with_witness_limit(4);
report(
&mut checker,
"shape",
Ty::Shape,
&[
(Src::Variant(0, vec![int(0, "0")], "Circle(0)"), false),
(Src::Variant(0, vec![Src::Wild], "Circle(_)"), false),
(
Src::Variant(1, vec![Src::Wild, Src::Wild], "Rect(w, h)"),
true,
),
],
);
report(
&mut checker,
"point",
Ty::Point,
&[
(
Src::Struct(vec![int(0, "0"), Src::Wild], "Point { x: 0, .. }"),
false,
),
(
Src::Struct(
vec![
Src::Int(1, 9, "1..=9"),
Src::Or(vec![int(7, "7"), int(7, "7")]),
],
"Point { x: 1..=9, y: 7 | 7 }",
),
false,
),
(Src::Wild, false),
],
);
report(
&mut checker,
"command",
Ty::Str,
&[
(Src::Or(vec![Src::Str("go"), Src::Str("stop")]), false),
(Src::Str("go"), false),
],
);
report(
&mut checker,
"flags",
Ty::Slice(Box::new(Ty::Bool)),
&[
(Src::Slice(vec![], None, "[]"), false),
(
Src::Slice(vec![Src::Bool(true)], Some(vec![]), "[true, ..]"),
false,
),
(
Src::Slice(vec![], Some(vec![Src::Bool(false)]), "[.., false]"),
false,
),
],
);
}