swamp_script_analyzer/
pattern.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/swamp/script
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5
6use crate::Analyzer;
7use crate::err::{Error, ErrorKind};
8use swamp_script_semantic::{NormalPattern, Pattern, PatternElement};
9use swamp_script_types::prelude::*;
10
11impl Analyzer<'_> {
12    fn find_variant_in_pattern(
13        &self,
14        expression_type: &Type,
15        ast_name: &swamp_script_ast::Node,
16    ) -> Result<EnumVariantType, Error> {
17        let enum_type_ref = match expression_type {
18            Type::Enum(enum_type_ref) => enum_type_ref,
19            _ => Err(self.create_err(ErrorKind::ExpectedEnumInPattern, ast_name))?,
20        };
21
22        let variant_name = self.get_text(ast_name).to_string();
23
24        enum_type_ref.get_variant(&variant_name).map_or_else(
25            || Err(self.create_err(ErrorKind::UnknownEnumVariantTypeInPattern, ast_name)),
26            |found_variant| Ok(found_variant.clone()),
27        )
28    }
29
30    pub(crate) fn analyze_pattern(
31        &mut self,
32        ast_pattern: &swamp_script_ast::Pattern,
33        expected_condition_type: &Type,
34    ) -> Result<(Pattern, bool, bool), Error> {
35        match ast_pattern {
36            swamp_script_ast::Pattern::Wildcard(node) => {
37                Ok((Pattern::Wildcard(self.to_node(node)), false, false))
38            }
39            swamp_script_ast::Pattern::NormalPattern(node, normal_pattern, maybe_guard) => {
40                let (normal_pattern, was_pushed, wanted_mutable) =
41                    self.analyze_normal_pattern(node, normal_pattern, expected_condition_type)?;
42
43                let resolved_guard = if let Some(guard_clause) = maybe_guard {
44                    match guard_clause {
45                        swamp_script_ast::GuardClause::Wildcard(_) => None,
46                        swamp_script_ast::GuardClause::Expression(clause_expr) => {
47                            Some(self.analyze_bool_argument_expression(clause_expr)?)
48                        }
49                    }
50                } else {
51                    None
52                };
53                Ok((
54                    Pattern::Normal(normal_pattern, resolved_guard),
55                    was_pushed,
56                    wanted_mutable,
57                ))
58            }
59        }
60    }
61
62    #[allow(clippy::too_many_lines)]
63    pub(crate) fn analyze_normal_pattern(
64        &mut self,
65        node: &swamp_script_ast::Node,
66        ast_normal_pattern: &swamp_script_ast::NormalPattern,
67        expected_condition_type: &Type,
68    ) -> Result<(NormalPattern, bool, bool), Error> {
69        let mut anyone_wants_mutable = false;
70        match ast_normal_pattern {
71            swamp_script_ast::NormalPattern::PatternList(elements) => {
72                let mut resolved_elements = Vec::new();
73                let mut scope_is_pushed = false;
74                for element in elements {
75                    match element {
76                        swamp_script_ast::PatternElement::Variable(var) => {
77                            if !scope_is_pushed {
78                                self.push_block_scope("pattern_list one variable");
79                                scope_is_pushed = true;
80                            }
81                            if var.is_mutable.is_some() {
82                                anyone_wants_mutable = true;
83                            }
84                            let variable_ref = self.create_local_variable(
85                                &var.name,
86                                Option::from(&var.is_mutable),
87                                expected_condition_type,
88                            )?;
89                            resolved_elements.push(PatternElement::Variable(variable_ref));
90                        }
91                        swamp_script_ast::PatternElement::Expression(expr) => {
92                            return Err(self.create_err(
93                                ErrorKind::ExpressionsNotAllowedInLetPattern,
94                                &expr.node,
95                            ));
96                        }
97                        swamp_script_ast::PatternElement::Wildcard(node) => {
98                            resolved_elements.push(PatternElement::Wildcard(self.to_node(node)));
99                        }
100                    }
101                }
102                Ok((
103                    NormalPattern::PatternList(resolved_elements),
104                    scope_is_pushed,
105                    anyone_wants_mutable,
106                ))
107            }
108
109            swamp_script_ast::NormalPattern::EnumPattern(variant_name, maybe_elements) => {
110                let mut scope_was_pushed = false;
111                let enum_variant_type_ref =
112                    self.find_variant_in_pattern(expected_condition_type, variant_name)?;
113
114                if let Some(elements) = maybe_elements {
115                    let mut resolved_elements = Vec::new();
116                    match enum_variant_type_ref {
117                        EnumVariantType::Tuple(ref tuple_type) => {
118                            // For tuples, elements must be in order but can be partial
119                            if elements.len() > tuple_type.fields_in_order.len() {
120                                return Err(self.create_err(
121                                    ErrorKind::TooManyTupleFields {
122                                        max: tuple_type.fields_in_order.len(),
123                                        got: elements.len(),
124                                    },
125                                    variant_name,
126                                ));
127                            }
128
129                            if !scope_was_pushed {
130                                self.push_block_scope("enum tuple");
131                                scope_was_pushed = true;
132                            }
133
134                            // Only zip with as many fields as we have elements
135                            for (element, field_type) in
136                                elements.iter().zip(&tuple_type.fields_in_order)
137                            {
138                                match element {
139                                    swamp_script_ast::PatternElement::Variable(var) => {
140                                        if var.is_mutable.is_some() {
141                                            anyone_wants_mutable = true;
142                                        }
143                                        let variable_ref = self.create_local_variable(
144                                            &var.name,
145                                            var.is_mutable.as_ref(),
146                                            field_type,
147                                        )?;
148                                        resolved_elements
149                                            .push(PatternElement::Variable(variable_ref));
150                                    }
151                                    swamp_script_ast::PatternElement::Wildcard(node) => {
152                                        resolved_elements
153                                            .push(PatternElement::Wildcard(self.to_node(node)));
154                                    }
155                                    swamp_script_ast::PatternElement::Expression(expr) => {
156                                        return Err(self.create_err(
157                                            ErrorKind::ExpressionsNotAllowedInLetPattern,
158                                            &expr.node,
159                                        ));
160                                    }
161                                }
162                            }
163                        }
164                        EnumVariantType::Struct(ref struct_type) => {
165                            if !scope_was_pushed {
166                                self.push_block_scope("enum struct");
167                                scope_was_pushed = true;
168                            }
169                            // For structs, can match any subset of fields in any order
170                            for element in elements {
171                                match element {
172                                    swamp_script_ast::PatternElement::Variable(var) => {
173                                        let var_name_str = self.get_text(&var.name).to_string();
174                                        // Check if the field exists
175                                        let field_index = struct_type
176                                            .anon_struct
177                                            .field_name_sorted_fields
178                                            .get_index(&var_name_str)
179                                            .ok_or_else(|| {
180                                                self.create_err(ErrorKind::UnknownField, &var.name)
181                                            })?;
182
183                                        let field_type = struct_type
184                                            .anon_struct
185                                            .field_name_sorted_fields
186                                            .get(&var_name_str)
187                                            .ok_or_else(|| {
188                                                self.create_err(ErrorKind::UnknownField, &var.name)
189                                            })?;
190
191                                        if var.is_mutable.is_some() {
192                                            anyone_wants_mutable = true;
193                                        }
194
195                                        let variable_ref = self.create_local_variable(
196                                            &var.name,
197                                            Option::from(&var.is_mutable),
198                                            &field_type.field_type,
199                                        )?;
200
201                                        resolved_elements.push(
202                                            PatternElement::VariableWithFieldIndex(
203                                                variable_ref,
204                                                field_index,
205                                            ),
206                                        );
207                                    }
208                                    swamp_script_ast::PatternElement::Wildcard(node) => {
209                                        resolved_elements
210                                            .push(PatternElement::Wildcard(self.to_node(node)));
211                                    }
212                                    swamp_script_ast::PatternElement::Expression(expr) => {
213                                        return Err(self.create_err(
214                                            ErrorKind::ExpressionsNotAllowedInLetPattern,
215                                            &expr.node,
216                                        ));
217                                    }
218                                }
219                            }
220                        }
221                        EnumVariantType::Nothing(_) => {
222                            if !elements.is_empty() {
223                                return Err(self
224                                    .create_err(ErrorKind::EnumVariantHasNoFields, variant_name));
225                            }
226                        }
227                    }
228
229                    Ok((
230                        NormalPattern::EnumPattern(enum_variant_type_ref, Some(resolved_elements)),
231                        scope_was_pushed,
232                        anyone_wants_mutable,
233                    ))
234                } else {
235                    Ok((
236                        NormalPattern::EnumPattern(enum_variant_type_ref, None),
237                        false,
238                        anyone_wants_mutable,
239                    ))
240                }
241            }
242
243            swamp_script_ast::NormalPattern::Literal(ast_literal) => Ok((
244                self.analyze_pattern_literal(node, ast_literal, expected_condition_type)?,
245                false,
246                anyone_wants_mutable,
247            )),
248        }
249    }
250}