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