Skip to main content

lisette_semantics/pattern_analysis/
witness.rs

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