Skip to main content

lisette_semantics/pattern_analysis/
normalize.rs

1use crate::store::Store;
2use syntax::ast::{Literal, MatchArm, TypedPattern};
3use syntax::program::Definition;
4use syntax::types::Type;
5
6use super::NormalizedPattern::Wildcard;
7use super::inhabitance::{InhabitanceCache, is_inhabited, is_variant_inhabited};
8use super::types::Row;
9use super::types::*;
10
11fn make_type_key(name: &str, type_args: &[Type]) -> String {
12    if type_args.is_empty() {
13        name.to_string()
14    } else {
15        let args = type_args
16            .iter()
17            .map(|t| t.to_string())
18            .collect::<Vec<_>>()
19            .join(", ");
20        format!("{}<{}>", name, args)
21    }
22}
23
24pub struct NormalizationContext<'a> {
25    pub store: &'a Store,
26    pub cache: &'a InhabitanceCache,
27}
28
29pub fn normalize_arm(
30    arm: &MatchArm,
31    unions: &mut UnionTable,
32    ctx: &NormalizationContext,
33) -> Vec<Row> {
34    let typed_pattern = arm
35        .typed_pattern
36        .as_ref()
37        .expect("typed pattern should be populated during inference");
38
39    match typed_pattern {
40        TypedPattern::Or { alternatives } => alternatives
41            .iter()
42            .map(|alt| vec![normalize_typed_pattern(alt, unions, ctx)])
43            .collect(),
44        _ => {
45            vec![vec![normalize_typed_pattern(typed_pattern, unions, ctx)]]
46        }
47    }
48}
49
50pub fn normalize_typed_pattern(
51    typed_pattern: &TypedPattern,
52    unions: &mut UnionTable,
53    ctx: &NormalizationContext,
54) -> NormalizedPattern {
55    match typed_pattern {
56        TypedPattern::Wildcard => Wildcard,
57
58        TypedPattern::Literal(literal) => {
59            if let Literal::Boolean(b) = literal {
60                return normalize_boolean(*b, unions);
61            }
62
63            NormalizedPattern::Literal(literal.clone())
64        }
65
66        TypedPattern::EnumVariant {
67            enum_name,
68            variant_name,
69            fields,
70            type_args,
71            ..
72        } => {
73            let patterns = fields
74                .iter()
75                .map(|f| normalize_typed_pattern(f, unions, ctx))
76                .collect();
77
78            let type_name = make_type_key(enum_name, type_args);
79
80            if unions.get(&type_name).is_none() {
81                let alternatives = match ctx.store.get_definition(enum_name) {
82                    Some(Definition::Enum {
83                        variants, generics, ..
84                    }) => variants
85                        .iter()
86                        .filter(|v| {
87                            is_variant_inhabited(v, type_args, generics, ctx.store, ctx.cache)
88                        })
89                        .map(|v| Constructor {
90                            tag_id: format!("{}.{}", enum_name, v.name),
91                            arity: v.fields.len(),
92                        })
93                        .collect(),
94                    Some(Definition::ValueEnum { variants, .. }) => {
95                        let mut alts: Vec<Constructor> = variants
96                            .iter()
97                            .map(|v| Constructor {
98                                tag_id: format!("{}.{}", enum_name, v.name),
99                                arity: 0,
100                            })
101                            .collect();
102                        alts.push(Constructor {
103                            tag_id: format!("{}.__value_enum_unknown__", enum_name),
104                            arity: 0,
105                        });
106                        alts
107                    }
108                    _ => vec![],
109                };
110
111                unions.insert(type_name.clone(), alternatives);
112            }
113
114            let variant_name = variant_name.rsplit('.').next().unwrap_or(variant_name);
115            let tag = format!("{}.{}", enum_name, variant_name);
116
117            NormalizedPattern::Constructor {
118                type_name,
119                tag,
120                args: patterns,
121            }
122        }
123
124        TypedPattern::EnumStructVariant {
125            enum_name,
126            variant_name,
127            variant_fields,
128            pattern_fields,
129            type_args,
130        } => {
131            let patterns = variant_fields
132                .iter()
133                .map(|f| {
134                    pattern_fields
135                        .iter()
136                        .find_map(|(name, pattern)| {
137                            if *name == f.name {
138                                Some(normalize_typed_pattern(pattern, unions, ctx))
139                            } else {
140                                None
141                            }
142                        })
143                        .unwrap_or(Wildcard)
144                })
145                .collect();
146
147            let type_name = make_type_key(enum_name, type_args);
148
149            if unions.get(&type_name).is_none() {
150                let alternatives = match ctx.store.get_definition(enum_name) {
151                    Some(Definition::Enum {
152                        variants, generics, ..
153                    }) => variants
154                        .iter()
155                        .filter(|v| {
156                            is_variant_inhabited(v, type_args, generics, ctx.store, ctx.cache)
157                        })
158                        .map(|v| Constructor {
159                            tag_id: format!("{}.{}", enum_name, v.name),
160                            arity: v.fields.len(),
161                        })
162                        .collect(),
163                    _ => vec![],
164                };
165
166                unions.insert(type_name.clone(), alternatives);
167            }
168
169            let variant_name = variant_name.rsplit('.').next().unwrap_or(variant_name);
170            let tag = format!("{}.{}", enum_name, variant_name);
171
172            NormalizedPattern::Constructor {
173                type_name,
174                tag,
175                args: patterns,
176            }
177        }
178
179        TypedPattern::Struct {
180            struct_name,
181            struct_fields,
182            pattern_fields,
183            type_args,
184        } => {
185            let patterns = struct_fields
186                .iter()
187                .map(|f| {
188                    pattern_fields
189                        .iter()
190                        .find_map(|(name, pattern)| {
191                            if *name == f.name {
192                                Some(normalize_typed_pattern(pattern, unions, ctx))
193                            } else {
194                                None
195                            }
196                        })
197                        .unwrap_or(Wildcard)
198                })
199                .collect();
200
201            let type_name = make_type_key(struct_name, type_args);
202
203            if unions.get(&type_name).is_none() {
204                let is_inhabited = ctx
205                    .store
206                    .get_definition(struct_name)
207                    .map(|definition| match definition {
208                        Definition::Struct {
209                            generics, fields, ..
210                        } => super::inhabitance::is_struct_inhabited(
211                            fields, type_args, generics, ctx.store, ctx.cache,
212                        ),
213                        _ => true,
214                    })
215                    .unwrap_or(true);
216
217                if is_inhabited {
218                    let constructor = Constructor {
219                        tag_id: struct_name.to_string(),
220                        arity: struct_fields.len(),
221                    };
222                    unions.insert(type_name.clone(), vec![constructor]);
223                } else {
224                    unions.insert(type_name.clone(), vec![]);
225                }
226            }
227
228            NormalizedPattern::Constructor {
229                type_name,
230                tag: struct_name.to_string(),
231                args: patterns,
232            }
233        }
234
235        TypedPattern::Slice {
236            prefix,
237            has_rest,
238            element_type,
239        } => normalize_slice(prefix, *has_rest, element_type, unions, ctx),
240
241        TypedPattern::Tuple { arity, elements } => normalize_tuple(elements, *arity, unions, ctx),
242
243        TypedPattern::Or { .. } => {
244            unreachable!("Or-pattern should be handled by normalize_arm")
245        }
246    }
247}
248
249/// Normalize a slice pattern into nested EmptySlice/NonEmptySlice constructors.
250///
251/// Slice is modeled as a 2-variant type:
252/// - EmptySlice: represents []
253/// - NonEmptySlice(head, tail): represents [head, ..tail]
254///
255/// Examples:
256/// - [] → EmptySlice
257/// - [a] → NonEmptySlice(a, EmptySlice)
258/// - [a, b] → NonEmptySlice(a, NonEmptySlice(b, EmptySlice))
259/// - [a, ..rest] → NonEmptySlice(a, Wildcard)
260/// - [..] → Wildcard (matches any slice)
261fn normalize_slice(
262    prefix: &[TypedPattern],
263    has_rest: bool,
264    element_type: &Type,
265    unions: &mut UnionTable,
266    ctx: &NormalizationContext,
267) -> NormalizedPattern {
268    let type_name = make_type_key("Slice", std::slice::from_ref(element_type));
269    if unions.get(&type_name).is_none() {
270        let element_inhabited = is_inhabited(element_type, ctx.store, ctx.cache);
271
272        let mut constructors = vec![Constructor {
273            tag_id: "EmptySlice".to_string(),
274            arity: 0,
275        }];
276
277        if element_inhabited {
278            constructors.push(Constructor {
279                tag_id: "NonEmptySlice".to_string(),
280                arity: 2, // head and tail
281            });
282        }
283
284        unions.insert(type_name.clone(), constructors);
285    }
286
287    if prefix.is_empty() && has_rest {
288        return Wildcard;
289    }
290
291    if prefix.is_empty() && !has_rest {
292        return NormalizedPattern::Constructor {
293            type_name,
294            tag: "EmptySlice".to_string(),
295            args: vec![],
296        };
297    }
298
299    let tail = if has_rest {
300        Wildcard
301    } else {
302        NormalizedPattern::Constructor {
303            type_name: type_name.clone(),
304            tag: "EmptySlice".to_string(),
305            args: vec![],
306        }
307    };
308
309    let mut result = tail;
310    for element in prefix.iter().rev() {
311        let head = normalize_typed_pattern(element, unions, ctx);
312        result = NormalizedPattern::Constructor {
313            type_name: type_name.clone(),
314            tag: "NonEmptySlice".to_string(),
315            args: vec![head, result],
316        };
317    }
318
319    result
320}
321
322fn normalize_tuple(
323    elements: &[TypedPattern],
324    arity: usize,
325    unions: &mut UnionTable,
326    ctx: &NormalizationContext,
327) -> NormalizedPattern {
328    let type_name = format!("Tuple{}", arity);
329
330    if unions.get(&type_name).is_none() {
331        let constructor = Constructor {
332            tag_id: type_name.clone(),
333            arity,
334        };
335        unions.insert(type_name.clone(), vec![constructor]);
336    }
337
338    let patterns = elements
339        .iter()
340        .map(|e| normalize_typed_pattern(e, unions, ctx))
341        .collect();
342
343    NormalizedPattern::Constructor {
344        type_name: type_name.clone(),
345        tag: type_name,
346        args: patterns,
347    }
348}
349
350fn normalize_boolean(boolean: bool, unions: &mut UnionTable) -> NormalizedPattern {
351    let type_name = "Bool".to_string();
352
353    if unions.get(&type_name).is_none() {
354        let make_alt = |b: bool| Constructor {
355            tag_id: b.to_string(),
356            arity: 0,
357        };
358
359        unions.insert(type_name.clone(), vec![make_alt(true), make_alt(false)]);
360    }
361
362    NormalizedPattern::Constructor {
363        type_name,
364        tag: boolean.to_string(),
365        args: vec![],
366    }
367}