swamp_analyzer/
pattern.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/swamp/swamp
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5use crate::Analyzer;
6use swamp_semantic::err::ErrorKind;
7use swamp_semantic::{NormalPattern, Pattern, PatternElement};
8use swamp_types::prelude::EnumVariantType as TypesEnumVariantType;
9use swamp_types::prelude::{EnumVariantCommon, TypeKind, TypeRef};
10
11impl Analyzer<'_> {
12    fn find_variant_in_pattern(
13        &mut self,
14        expression_type: &TypeRef,
15        ast_name: &swamp_ast::Node,
16    ) -> TypesEnumVariantType {
17        let enum_type_ref = if let TypeKind::Enum(enum_type_ref) = &*expression_type.kind {
18            enum_type_ref
19        } else {
20            self.add_err(ErrorKind::ExpectedEnumInPattern, ast_name);
21
22            return TypesEnumVariantType {
23                common: EnumVariantCommon {
24                    name: Default::default(),
25                    assigned_name: String::new(),
26                    container_index: 0,
27                },
28                payload_type: self.types().unit(),
29            };
30        };
31
32        let variant_name = self.get_text(ast_name).to_string();
33
34        if let Some(variant) = enum_type_ref.get_variant(&variant_name) {
35            variant.clone()
36        } else {
37            self.add_err(ErrorKind::UnknownEnumVariantTypeInPattern, ast_name);
38            TypesEnumVariantType {
39                common: EnumVariantCommon {
40                    name: Default::default(),
41                    assigned_name: String::new(),
42                    container_index: 0,
43                },
44                payload_type: self.types().unit(),
45            }
46        }
47    }
48
49    pub(crate) fn analyze_pattern(
50        &mut self,
51        ast_pattern: &swamp_ast::Pattern,
52        expected_condition_type: &TypeRef,
53    ) -> (Pattern, bool, bool) {
54        match ast_pattern {
55            swamp_ast::Pattern::Wildcard(node) => {
56                (Pattern::Wildcard(self.to_node(node)), false, false)
57            }
58            swamp_ast::Pattern::ConcretePattern(node, concrete_pattern, maybe_guard) => {
59                let (normal_pattern, was_pushed, wanted_mutable) =
60                    self.analyze_normal_pattern(node, concrete_pattern, expected_condition_type);
61
62                let resolved_guard =
63                    maybe_guard
64                        .as_ref()
65                        .and_then(|guard_clause| match guard_clause {
66                            swamp_ast::GuardClause::Wildcard(_) => None,
67                            swamp_ast::GuardClause::Expression(clause_expr) => {
68                                Some(self.analyze_bool_argument_expression(clause_expr))
69                            }
70                        });
71                (
72                    Pattern::Normal(normal_pattern, resolved_guard),
73                    was_pushed,
74                    wanted_mutable,
75                )
76            }
77        }
78    }
79
80    #[allow(clippy::too_many_lines)]
81    pub(crate) fn analyze_normal_pattern(
82        &mut self,
83        node: &swamp_ast::Node,
84        ast_concrete_pattern: &swamp_ast::ConcretePattern,
85        expected_condition_type: &TypeRef,
86    ) -> (NormalPattern, bool, bool) {
87        let mut anyone_wants_mutable = false;
88        match ast_concrete_pattern {
89            swamp_ast::ConcretePattern::EnumPattern(variant_name, destructuring_pattern) => {
90                let mut scope_was_pushed = false;
91                let enum_variant_type_ref =
92                    self.find_variant_in_pattern(expected_condition_type, variant_name);
93
94                match destructuring_pattern {
95                    swamp_ast::DestructuringPattern::Struct { fields } => {
96                        if !scope_was_pushed {
97                            self.push_block_scope("enum struct");
98                            scope_was_pushed = true;
99                        }
100
101                        let mut resolved_elements = Vec::new();
102
103                        match &*enum_variant_type_ref.payload_type.kind {
104                            TypeKind::AnonymousStruct(anon_struct_type) => {
105                                // For structs, can match any subset of fields in any order
106                                for var in fields {
107                                    let var_name_str = self.get_text(&var.name).to_string();
108                                    // Check if the field exists
109                                    let Some(field_index) = anon_struct_type
110                                        .field_name_sorted_fields
111                                        .get_index(&var_name_str)
112                                    else {
113                                        return (
114                                            NormalPattern::Literal(
115                                                self.create_err(ErrorKind::UnknownField, &var.name),
116                                            ),
117                                            false,
118                                            false,
119                                        );
120                                    };
121
122                                    let Some(field_type) = anon_struct_type
123                                        .field_name_sorted_fields
124                                        .get(&var_name_str)
125                                    else {
126                                        return (
127                                            NormalPattern::Literal(
128                                                self.create_err(ErrorKind::UnknownField, &var.name),
129                                            ),
130                                            false,
131                                            false,
132                                        );
133                                    };
134
135                                    if var.is_mutable.is_some() {
136                                        anyone_wants_mutable = true;
137                                    }
138
139                                    let variable_ref = self.create_local_variable(
140                                        &var.name,
141                                        Option::from(&var.is_mutable),
142                                        &field_type.field_type,
143                                        false,
144                                    );
145
146                                    resolved_elements.push(PatternElement::VariableWithFieldIndex(
147                                        variable_ref,
148                                        field_index,
149                                    ));
150                                }
151                            }
152                            _ => {
153                                return (
154                                    NormalPattern::Literal(
155                                        self.create_err(ErrorKind::CanNotDestructure, variant_name),
156                                    ),
157                                    false,
158                                    false,
159                                );
160                            }
161                        }
162
163                        (
164                            NormalPattern::EnumPattern(
165                                enum_variant_type_ref,
166                                Some(resolved_elements),
167                            ),
168                            scope_was_pushed,
169                            anyone_wants_mutable,
170                        )
171                    }
172                    swamp_ast::DestructuringPattern::Tuple { elements } => {
173                        if !scope_was_pushed {
174                            self.push_block_scope("enum tuple");
175                            scope_was_pushed = true;
176                        }
177                        assert!(elements.len() > 1, "internal error with tuple");
178
179                        let mut resolved_elements = Vec::new();
180
181                        if let TypeKind::Tuple(fields_in_order) =
182                            &*enum_variant_type_ref.payload_type.kind
183                        {
184                            // For tuples, elements must be in order but can be partial
185                            if elements.len() > fields_in_order.len() {
186                                return (
187                                    NormalPattern::Literal(self.create_err(
188                                        ErrorKind::TooManyTupleFields {
189                                            max: fields_in_order.len(),
190                                            got: elements.len(),
191                                        },
192                                        variant_name,
193                                    )),
194                                    false,
195                                    false,
196                                );
197                            }
198
199                            // Only zip with as many fields as we have elements
200                            for (tuple_element_index, (element, field_type)) in
201                                elements.iter().zip(fields_in_order).enumerate()
202                            {
203                                match element {
204                                    swamp_ast::PatternVariableOrWildcard::Variable(var) => {
205                                        if var.is_mutable.is_some() {
206                                            anyone_wants_mutable = true;
207                                        }
208
209                                        let variable_ref = self.create_local_variable(
210                                            &var.name,
211                                            var.is_mutable.as_ref(),
212                                            field_type,
213                                            false,
214                                        );
215
216                                        resolved_elements.push(
217                                            PatternElement::VariableWithFieldIndex(
218                                                variable_ref,
219                                                tuple_element_index,
220                                            ),
221                                        );
222                                    }
223                                    swamp_ast::PatternVariableOrWildcard::Wildcard(node) => {
224                                        resolved_elements
225                                            .push(PatternElement::Wildcard(self.to_node(node)));
226                                    }
227                                }
228                            }
229                            (
230                                NormalPattern::EnumPattern(
231                                    enum_variant_type_ref,
232                                    Some(resolved_elements),
233                                ),
234                                scope_was_pushed,
235                                anyone_wants_mutable,
236                            )
237                        } else {
238                            self.add_err(ErrorKind::ExpectedTupleType, node);
239                            (NormalPattern::PatternList(vec![]), false, false)
240                        }
241                    }
242                    swamp_ast::DestructuringPattern::None { variable } => {
243                        // Single payload variable - capture the entire payload
244                        if !scope_was_pushed {
245                            self.push_block_scope("enum payload");
246                            scope_was_pushed = true;
247                        }
248
249                        if variable.is_mutable.is_some() {
250                            anyone_wants_mutable = true;
251                        }
252
253                        let variable_ref = self.create_local_variable(
254                            &variable.name,
255                            variable.is_mutable.as_ref(),
256                            &enum_variant_type_ref.payload_type,
257                            false,
258                        );
259
260                        let payload_pattern_element = PatternElement::Variable(variable_ref);
261
262                        (
263                            NormalPattern::EnumPattern(
264                                enum_variant_type_ref,
265                                Some(vec![payload_pattern_element]),
266                            ),
267                            scope_was_pushed,
268                            anyone_wants_mutable,
269                        )
270                    }
271                    swamp_ast::DestructuringPattern::Unit => {
272                        // Unit enum variant with no payload - like `Red`, `Green`, `Blue`
273                        (
274                            NormalPattern::EnumPattern(enum_variant_type_ref, None),
275                            scope_was_pushed,
276                            anyone_wants_mutable,
277                        )
278                    }
279                }
280            }
281
282            swamp_ast::ConcretePattern::Literal(ast_literal) => (
283                self.analyze_pattern_literal(node, ast_literal, expected_condition_type),
284                false,
285                anyone_wants_mutable,
286            ),
287        }
288    }
289}