Skip to main content

lisette_semantics/pattern_analysis/
witness.rs

1use super::NormalizedPattern;
2use syntax::ast::Literal;
3
4pub fn format_witness(pattern: &NormalizedPattern) -> String {
5    match pattern {
6        NormalizedPattern::Wildcard => "_".to_string(),
7
8        // Literals are never produced as witnesses by the exhaustiveness algorithm.
9        // When patterns contain literals, missing coverage is shown as `_` (wildcard).
10        NormalizedPattern::Literal(_) => unreachable!("literals cannot be witnesses"),
11
12        NormalizedPattern::Constructor {
13            type_name,
14            tag,
15            args,
16        } => {
17            if type_name.starts_with("Slice") {
18                return format_slice_pattern(pattern);
19            }
20
21            if type_name.starts_with("Tuple") {
22                let formatted_args = args
23                    .iter()
24                    .map(format_witness)
25                    .collect::<Vec<_>>()
26                    .join(", ");
27                return format!("({})", formatted_args);
28            }
29
30            let display_tag = strip_module_prefix(tag);
31
32            if display_tag == "__value_enum_unknown__" {
33                return "_".to_string();
34            }
35
36            if args.is_empty() {
37                display_tag
38            } else {
39                let formatted_args = args
40                    .iter()
41                    .map(format_witness)
42                    .collect::<Vec<_>>()
43                    .join(", ");
44                format!("{}({})", display_tag, formatted_args)
45            }
46        }
47    }
48}
49
50/// Formats a normalized pattern for display (e.g., in error messages).
51/// Unlike `format_witness()`, this can handle patterns with literal fields,
52/// since user-written patterns may contain literals in struct constructors.
53pub fn format_pattern(pattern: &NormalizedPattern) -> String {
54    match pattern {
55        NormalizedPattern::Wildcard => "_".to_string(),
56
57        // User-written patterns can contain literals in their fields
58        NormalizedPattern::Literal(lit) => format_literal(lit),
59
60        NormalizedPattern::Constructor {
61            type_name,
62            tag,
63            args,
64        } => {
65            if type_name.starts_with("Slice") {
66                return format_slice_pattern_for_display(pattern);
67            }
68
69            if type_name.starts_with("Tuple") {
70                let formatted_args = args
71                    .iter()
72                    .map(format_pattern)
73                    .collect::<Vec<_>>()
74                    .join(", ");
75                return format!("({})", formatted_args);
76            }
77
78            let display_tag = strip_module_prefix(tag);
79
80            if display_tag == "__value_enum_unknown__" {
81                return "_".to_string();
82            }
83
84            if args.is_empty() {
85                display_tag
86            } else {
87                let formatted_args = args
88                    .iter()
89                    .map(format_pattern)
90                    .collect::<Vec<_>>()
91                    .join(", ");
92                format!("{}({})", display_tag, formatted_args)
93            }
94        }
95    }
96}
97
98fn format_literal(lit: &Literal) -> String {
99    match lit {
100        Literal::Integer { text, value } => text.as_ref().unwrap_or(&value.to_string()).clone(),
101        Literal::Float { text, value } => text.as_ref().unwrap_or(&value.to_string()).clone(),
102        Literal::Imaginary(val) => format!("{}i", val),
103        Literal::Boolean(b) => b.to_string(),
104        Literal::String(s) => format!("\"{}\"", s),
105        Literal::Char(c) => format!("'{}'", c),
106        Literal::FormatString(_) => "f\"...\"".to_string(),
107        Literal::Slice(_) => "[...]".to_string(),
108    }
109}
110
111fn strip_module_prefix(tag: &str) -> String {
112    let parts: Vec<&str> = tag.split('.').collect();
113    if parts.len() >= 2 {
114        parts[1..].join(".")
115    } else {
116        tag.to_string()
117    }
118}
119
120fn format_slice_pattern(pattern: &NormalizedPattern) -> String {
121    let mut elements = Vec::new();
122    let mut current = pattern;
123    let mut ends_with_rest = false;
124
125    loop {
126        match current {
127            NormalizedPattern::Constructor { tag, .. } if tag == "EmptySlice" => {
128                break;
129            }
130            NormalizedPattern::Constructor { tag, args, .. } if tag == "NonEmptySlice" => {
131                if args.len() >= 2 {
132                    elements.push(format_witness(&args[0]));
133                    current = &args[1];
134                } else {
135                    break;
136                }
137            }
138            NormalizedPattern::Wildcard => {
139                ends_with_rest = true;
140                break;
141            }
142            _ => {
143                elements.push(format_witness(current));
144                break;
145            }
146        }
147    }
148
149    if ends_with_rest {
150        elements.push("..".to_string());
151    }
152
153    format!("[{}]", elements.join(", "))
154}
155
156fn format_slice_pattern_for_display(pattern: &NormalizedPattern) -> String {
157    let mut elements = Vec::new();
158    let mut current = pattern;
159    let mut ends_with_rest = false;
160
161    loop {
162        match current {
163            NormalizedPattern::Constructor { tag, .. } if tag == "EmptySlice" => {
164                break;
165            }
166            NormalizedPattern::Constructor { tag, args, .. } if tag == "NonEmptySlice" => {
167                if args.len() >= 2 {
168                    elements.push(format_pattern(&args[0]));
169                    current = &args[1];
170                } else {
171                    break;
172                }
173            }
174            NormalizedPattern::Wildcard => {
175                ends_with_rest = true;
176                break;
177            }
178            _ => {
179                elements.push(format_pattern(current));
180                break;
181            }
182        }
183    }
184
185    if ends_with_rest {
186        elements.push("..".to_string());
187    }
188
189    format!("[{}]", elements.join(", "))
190}