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(enum_variant_type_ref.common.symbol_id.into(), name_node.clone());
99                self.shared.definition_table.refs.add(enum_variant_type_ref.common.symbol_id.into(), name_node);
100
101                match destructuring_pattern {
102                    swamp_ast::DestructuringPattern::Struct { fields } => {
103                        if !scope_was_pushed {
104                            self.push_block_scope("enum struct");
105                            scope_was_pushed = true;
106                        }
107
108                        let mut resolved_elements = Vec::new();
109
110                        match &*enum_variant_type_ref.payload_type.kind {
111                            TypeKind::AnonymousStruct(anon_struct_type) => {
112                                // For structs, can match any subset of fields in any order
113                                for var in fields {
114                                    let var_name_str = self.get_text(&var.name).to_string();
115                                    // Check if the field exists
116                                    let Some(field_index) = anon_struct_type
117                                        .field_name_sorted_fields
118                                        .get_index(&var_name_str)
119                                    else {
120                                        return (
121                                            NormalPattern::Literal(
122                                                self.create_err(ErrorKind::UnknownField, &var.name),
123                                            ),
124                                            false,
125                                            false,
126                                        );
127                                    };
128
129                                    let Some(field_type) = anon_struct_type
130                                        .field_name_sorted_fields
131                                        .get(&var_name_str)
132                                    else {
133                                        return (
134                                            NormalPattern::Literal(
135                                                self.create_err(ErrorKind::UnknownField, &var.name),
136                                            ),
137                                            false,
138                                            false,
139                                        );
140                                    };
141
142                                    if var.is_mutable.is_some() {
143                                        anyone_wants_mutable = true;
144                                    }
145
146                                    let variable_ref = self.create_local_variable_parameter_like(
147                                        &var.name,
148                                        Option::from(&var.is_mutable),
149                                        &field_type.field_type,
150                                        false,
151                                    );
152
153                                    resolved_elements.push(PatternElement::VariableWithFieldIndex(
154                                        variable_ref,
155                                        field_index,
156                                    ));
157                                }
158                            }
159                            _ => {
160                                return (
161                                    NormalPattern::Literal(
162                                        self.create_err(ErrorKind::CanNotDestructure, variant_name),
163                                    ),
164                                    false,
165                                    false,
166                                );
167                            }
168                        }
169
170                        (
171                            NormalPattern::EnumPattern(
172                                enum_variant_type_ref,
173                                Some(resolved_elements),
174                            ),
175                            scope_was_pushed,
176                            anyone_wants_mutable,
177                        )
178                    }
179                    swamp_ast::DestructuringPattern::Tuple { elements } => {
180                        if !scope_was_pushed {
181                            self.push_block_scope("enum tuple");
182                            scope_was_pushed = true;
183                        }
184                        assert!(elements.len() > 1, "internal error with tuple");
185
186                        let mut resolved_elements = Vec::new();
187
188                        if let TypeKind::Tuple(fields_in_order) =
189                            &*enum_variant_type_ref.payload_type.kind
190                        {
191                            // For tuples, elements must be in order but can be partial
192                            if elements.len() > fields_in_order.len() {
193                                return (
194                                    NormalPattern::Literal(self.create_err(
195                                        ErrorKind::TooManyTupleFields {
196                                            max: fields_in_order.len(),
197                                            got: elements.len(),
198                                        },
199                                        variant_name,
200                                    )),
201                                    false,
202                                    false,
203                                );
204                            }
205
206                            // Only zip with as many fields as we have elements
207                            for (tuple_element_index, (element, field_type)) in
208                                elements.iter().zip(fields_in_order).enumerate()
209                            {
210                                match element {
211                                    swamp_ast::PatternVariableOrWildcard::Variable(var) => {
212                                        if var.is_mutable.is_some() {
213                                            anyone_wants_mutable = true;
214                                        }
215
216                                        let variable_ref = self
217                                            .create_local_variable_parameter_like(
218                                                &var.name,
219                                                var.is_mutable.as_ref(),
220                                                field_type,
221                                                false,
222                                            );
223
224                                        resolved_elements.push(
225                                            PatternElement::VariableWithFieldIndex(
226                                                variable_ref,
227                                                tuple_element_index,
228                                            ),
229                                        );
230                                    }
231                                    swamp_ast::PatternVariableOrWildcard::Wildcard(node) => {
232                                        resolved_elements
233                                            .push(PatternElement::Wildcard(self.to_node(node)));
234                                    }
235                                }
236                            }
237                            (
238                                NormalPattern::EnumPattern(
239                                    enum_variant_type_ref,
240                                    Some(resolved_elements),
241                                ),
242                                scope_was_pushed,
243                                anyone_wants_mutable,
244                            )
245                        } else {
246                            self.add_err(ErrorKind::ExpectedTupleType, node);
247                            (NormalPattern::PatternList(vec![]), false, false)
248                        }
249                    }
250                    swamp_ast::DestructuringPattern::None { variable } => {
251                        // Single payload variable - capture the entire payload
252                        if !scope_was_pushed {
253                            self.push_block_scope("enum payload");
254                            scope_was_pushed = true;
255                        }
256
257                        if variable.is_mutable.is_some() {
258                            anyone_wants_mutable = true;
259                        }
260
261                        let variable_ref = self.create_local_variable(
262                            &variable.name,
263                            variable.is_mutable.as_ref(),
264                            &enum_variant_type_ref.payload_type,
265                            false,
266                        );
267
268                        let payload_pattern_element = PatternElement::Variable(variable_ref);
269
270                        (
271                            NormalPattern::EnumPattern(
272                                enum_variant_type_ref,
273                                Some(vec![payload_pattern_element]),
274                            ),
275                            scope_was_pushed,
276                            anyone_wants_mutable,
277                        )
278                    }
279                    swamp_ast::DestructuringPattern::Unit => {
280                        // Unit enum variant with no payload - like `Red`, `Green`, `Blue`
281                        (
282                            NormalPattern::EnumPattern(enum_variant_type_ref, None),
283                            scope_was_pushed,
284                            anyone_wants_mutable,
285                        )
286                    }
287                }
288            }
289
290            swamp_ast::ConcretePattern::Literal(ast_literal) => (
291                self.analyze_pattern_literal(node, ast_literal, expected_condition_type),
292                false,
293                anyone_wants_mutable,
294            ),
295        }
296    }
297}