gitql_parser/
type_checker.rs

1use std::collections::HashMap;
2
3use gitql_ast::expression::CastExpr;
4use gitql_ast::expression::Expr;
5use gitql_ast::statement::TableSelection;
6use gitql_ast::types::any::AnyType;
7use gitql_ast::types::dynamic::DynamicType;
8use gitql_ast::types::varargs::VarargsType;
9use gitql_ast::types::DataType;
10use gitql_core::environment::Environment;
11
12use crate::diagnostic::Diagnostic;
13use crate::token::SourceLocation;
14
15/// Checks if all values has the same type
16/// If they have the same type, return it or return None
17pub fn check_all_values_are_same_type(arguments: &[Box<dyn Expr>]) -> Option<Box<dyn DataType>> {
18    let arguments_count = arguments.len();
19    if arguments_count == 0 {
20        return Some(Box::new(AnyType));
21    }
22
23    let data_type = arguments[0].expr_type();
24    for argument in arguments.iter().take(arguments_count).skip(1) {
25        let expr_type = argument.expr_type();
26        if !data_type.equals(&expr_type) {
27            return None;
28        }
29    }
30
31    Some(data_type)
32}
33
34/// Check That function call arguments types are matches the parameter types
35/// Return a Diagnostic Error if anything is wrong
36pub fn check_function_call_arguments(
37    arguments: &mut [Box<dyn Expr>],
38    parameters: &[Box<dyn DataType>],
39    function_name: String,
40    location: SourceLocation,
41) -> Result<(), Box<Diagnostic>> {
42    let parameters_count = parameters.len();
43    let arguments_count = arguments.len();
44
45    let mut has_varargs_parameter = false;
46    let mut optional_parameters_count = 0;
47    if parameters_count != 0 {
48        let last_parameter = parameters.last().unwrap();
49        has_varargs_parameter = last_parameter.is_varargs();
50
51        // Count number of optional parameters
52        for parameter_type in parameters.iter().take(parameters_count) {
53            if parameter_type.is_optional() {
54                optional_parameters_count += 1;
55            }
56        }
57    }
58
59    let mut min_arguments_count = parameters_count - optional_parameters_count;
60    if has_varargs_parameter {
61        min_arguments_count -= 1;
62    }
63
64    if arguments_count < min_arguments_count {
65        return Err(Diagnostic::error(&format!(
66            "Function `{function_name}` expects at least `{min_arguments_count}` arguments but got `{arguments_count}`",
67        ))
68        .with_location(location)
69        .as_boxed());
70    }
71
72    if !has_varargs_parameter && arguments_count > parameters_count {
73        return Err(Diagnostic::error(&format!(
74            "Function `{function_name}` expects `{parameters_count}` arguments but got `{arguments_count}`",
75        ))
76        .with_location(location)
77        .as_boxed());
78    }
79
80    // Type check the min required arguments
81    for index in 0..min_arguments_count {
82        let parameter_type =
83            resolve_dynamic_data_type(parameters, arguments, parameters.get(index).unwrap());
84        let argument = arguments.get(index).unwrap();
85        let argument_type = argument.expr_type();
86
87        // Catch undefined arguments
88        if argument_type.is_undefined() {
89            return Err(Diagnostic::error(&format!(
90                "Function `{function_name}` argument number {index} has Undefined type",
91            ))
92            .add_help("Make sure you used a correct field name")
93            .add_help("Check column names for each table from docs website")
94            .with_location(location)
95            .as_boxed());
96        }
97
98        // Both types are equals
99        if parameter_type.equals(&argument_type) {
100            continue;
101        }
102
103        // Argument exp can be implicit casted to Parameter type
104        if parameter_type.has_implicit_cast_from(argument) {
105            arguments[index] = Box::new(CastExpr {
106                value: argument.clone(),
107                result_type: parameter_type.clone(),
108            });
109            continue;
110        }
111
112        // Argument type is not equal and can't be casted to parameter type
113        return Err(Diagnostic::error(&format!(
114            "Function `{}` argument number {} with type `{}` don't match expected type `{}`",
115            function_name,
116            index,
117            argument_type.literal(),
118            parameter_type.literal()
119        ))
120        .with_location(location)
121        .as_boxed());
122    }
123
124    // Type check the optional parameters
125    let last_optional_param_index = min_arguments_count + optional_parameters_count;
126    for index in min_arguments_count..last_optional_param_index {
127        if index >= arguments_count {
128            return Ok(());
129        }
130
131        let parameter_type =
132            resolve_dynamic_data_type(parameters, arguments, parameters.get(index).unwrap());
133        let argument = arguments.get(index).unwrap();
134        let argument_type = argument.expr_type();
135
136        // Catch undefined arguments
137        if argument_type.is_undefined() {
138            return Err(Diagnostic::error(&format!(
139                "Function `{function_name}` argument number {index} has Undefined type",
140            ))
141            .add_help("Make sure you used a correct field name")
142            .add_help("Check column names for each table from docs website")
143            .with_location(location)
144            .as_boxed());
145        }
146
147        // Both types are equals
148        if parameter_type.equals(&argument_type) {
149            continue;
150        }
151
152        // Argument exp can be implicit casted to Parameter type
153        if parameter_type.has_implicit_cast_from(argument) {
154            arguments[index] = Box::new(CastExpr {
155                value: argument.clone(),
156                result_type: parameter_type.clone(),
157            });
158            continue;
159        }
160
161        // Argument type is not equal and can't be casted to parameter type
162        return Err(Diagnostic::error(&format!(
163            "Function `{}` argument number {} with type `{}` don't match expected type `{}`",
164            function_name,
165            index,
166            argument_type.literal(),
167            parameter_type.literal()
168        ))
169        .with_location(location)
170        .as_boxed());
171    }
172
173    // Type check the variable parameters if exists
174    if has_varargs_parameter {
175        let varargs_type =
176            resolve_dynamic_data_type(parameters, arguments, parameters.last().unwrap());
177        for index in last_optional_param_index..arguments_count {
178            let argument = arguments.get(index).unwrap();
179            let argument_type = argument.expr_type();
180
181            // Catch undefined arguments
182            if argument_type.is_undefined() {
183                return Err(Diagnostic::error(&format!(
184                    "Function `{function_name}` argument number {index} has Undefined type",
185                ))
186                .add_help("Make sure you used a correct field name")
187                .add_help("Check column names for each table from docs website")
188                .with_location(location)
189                .as_boxed());
190            }
191
192            // Both types are equals
193            if varargs_type.equals(&argument_type) {
194                continue;
195            }
196
197            // Argument exp can be implicit casted to Parameter type
198            if varargs_type.has_implicit_cast_from(argument) {
199                arguments[index] = Box::new(CastExpr {
200                    value: argument.clone(),
201                    result_type: varargs_type.clone(),
202                });
203                continue;
204            }
205
206            return Err(Diagnostic::error(&format!(
207                "Function `{}` argument number {} with type `{}` don't match expected type `{}`",
208                function_name,
209                index,
210                &argument_type.literal(),
211                &varargs_type.literal()
212            ))
213            .with_location(location)
214            .as_boxed());
215        }
216    }
217
218    Ok(())
219}
220
221/// Check that all selected fields types are defined correctly in selected tables
222/// Return the columns classified for each table
223/// Return a Diagnostic Error if anything is wrong
224pub fn type_check_and_classify_selected_fields(
225    env: &mut Environment,
226    selected_tables: &Vec<String>,
227    selected_columns: &Vec<String>,
228    location: SourceLocation,
229) -> Result<Vec<TableSelection>, Box<Diagnostic>> {
230    let mut table_selections: Vec<TableSelection> = vec![];
231    let mut table_index: HashMap<String, usize> = HashMap::new();
232    for (index, table) in selected_tables.iter().enumerate() {
233        table_selections.push(TableSelection {
234            table_name: table.to_string(),
235            columns_names: vec![],
236        });
237        table_index.insert(table.to_string(), index);
238    }
239
240    for selected_column in selected_columns {
241        let mut is_column_resolved = false;
242        for table in selected_tables {
243            let table_columns = env.schema.tables_fields_names.get(table.as_str()).unwrap();
244
245            // Check if this column name exists in current table
246            if table_columns.contains(&selected_column.as_str()) {
247                is_column_resolved = true;
248                let table_selection_index = *table_index.get(table).unwrap();
249                let selection = &mut table_selections[table_selection_index];
250                selection.columns_names.push(selected_column.to_string());
251                continue;
252            }
253        }
254
255        if !is_column_resolved {
256            // This case for aggregated values or functions
257            if let Some(data_type) = env.resolve_type(selected_column) {
258                if !data_type.is_undefined() {
259                    if table_selections.is_empty() {
260                        table_selections.push(TableSelection {
261                            table_name: selected_tables
262                                .first()
263                                .unwrap_or(&"".to_string())
264                                .to_string(),
265                            columns_names: vec![selected_column.to_string()],
266                        });
267                    } else {
268                        table_selections[0]
269                            .columns_names
270                            .push(selected_column.to_string());
271                    }
272                    continue;
273                }
274            }
275
276            return Err(Diagnostic::error(&format!(
277                "Column `{selected_column}` not exists in any of the selected tables",
278            ))
279            .add_help("Check the documentations to see available fields for each tables")
280            .with_location(location)
281            .as_boxed());
282        }
283    }
284
285    Ok(table_selections)
286}
287
288/// Check that all projection columns are valid for this table name
289/// Return a Diagnostic Error if anything is wrong
290pub fn type_check_projection_symbols(
291    env: &mut Environment,
292    selected_tables: &[String],
293    projection_names: &[String],
294    projection_locations: &[SourceLocation],
295) -> Result<(), Box<Diagnostic>> {
296    for (index, selected_column) in projection_names.iter().enumerate() {
297        let mut is_column_resolved = false;
298        for table in selected_tables {
299            let table_columns = env.schema.tables_fields_names.get(table.as_str()).unwrap();
300            if table_columns.contains(&selected_column.as_str()) {
301                is_column_resolved = true;
302                break;
303            }
304        }
305
306        if !is_column_resolved {
307            return Err(Diagnostic::error(&format!(
308                "Column `{selected_column}` not exists in any of the selected tables",
309            ))
310            .add_help("Check the documentations to see available fields for each tables")
311            .with_location(projection_locations[index])
312            .as_boxed());
313        }
314    }
315
316    Ok(())
317}
318
319/// Resolve dynamic data type depending on the parameters and arguments types to actual DataType
320#[allow(clippy::borrowed_box)]
321pub fn resolve_dynamic_data_type(
322    parameters: &[Box<dyn DataType>],
323    arguments: &[Box<dyn Expr>],
324    data_type: &Box<dyn DataType>,
325) -> Box<dyn DataType> {
326    // Resolve Dynamic type
327    if let Some(dynamic_type) = data_type.as_any().downcast_ref::<DynamicType>() {
328        let mut resolved_data_type = (dynamic_type.function)(parameters);
329
330        // In Case that data type is Any or Variant [Type1 | Type2...] need to resolve it from arguments types
331        // To be able to use it with other expressions
332        if !arguments.is_empty() && (resolved_data_type.is_variant() || resolved_data_type.is_any())
333        {
334            let mut arguments_types: Vec<Box<dyn DataType>> = Vec::with_capacity(arguments.len());
335            for argument in arguments {
336                arguments_types.push(argument.expr_type());
337            }
338            resolved_data_type = (dynamic_type.function)(&arguments_types);
339        }
340
341        return resolved_data_type;
342    }
343
344    // Resolve ...Dynamic to ...<TYPE> recursively
345    if let Some(varargs) = data_type.as_any().downcast_ref::<VarargsType>() {
346        if varargs
347            .base
348            .as_any()
349            .downcast_ref::<DynamicType>()
350            .is_some()
351        {
352            let base = resolve_dynamic_data_type(parameters, arguments, &varargs.base);
353            return Box::new(VarargsType { base });
354        }
355    }
356
357    data_type.clone()
358}