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