gitql_parser/
type_checker.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
use std::collections::HashMap;

use gitql_ast::expression::CastExpr;
use gitql_ast::expression::Expr;
use gitql_ast::statement::TableSelection;
use gitql_ast::types::any::AnyType;
use gitql_ast::types::base::DataType;
use gitql_ast::types::dynamic::DynamicType;
use gitql_ast::types::varargs::VarargsType;
use gitql_core::environment::Environment;

use crate::diagnostic::Diagnostic;
use crate::token::SourceLocation;

/// Checks if all values has the same type
/// If they have the same type, return it or return None
pub fn check_all_values_are_same_type(arguments: &[Box<dyn Expr>]) -> Option<Box<dyn DataType>> {
    let arguments_count = arguments.len();
    if arguments_count == 0 {
        return Some(Box::new(AnyType));
    }

    let data_type = arguments[0].expr_type();
    for argument in arguments.iter().take(arguments_count).skip(1) {
        let expr_type = argument.expr_type();
        if !data_type.equals(&expr_type) {
            return None;
        }
    }

    Some(data_type)
}

/// Check That function call arguments types are matches the parameter types
/// Return a Diagnostic Error if anything is wrong
pub fn check_function_call_arguments(
    arguments: &mut [Box<dyn Expr>],
    parameters: &[Box<dyn DataType>],
    function_name: String,
    location: SourceLocation,
) -> Result<(), Box<Diagnostic>> {
    let parameters_count = parameters.len();
    let arguments_count = arguments.len();

    let mut has_varargs_parameter = false;
    let mut optional_parameters_count = 0;
    if parameters_count != 0 {
        let last_parameter = parameters.last().unwrap();
        has_varargs_parameter = last_parameter.is_varargs();

        // Count number of optional parameters
        for parameter_type in parameters.iter().take(parameters_count) {
            if parameter_type.is_optional() {
                optional_parameters_count += 1;
            }
        }
    }

    let mut min_arguments_count = parameters_count - optional_parameters_count;
    if has_varargs_parameter {
        min_arguments_count -= 1;
    }

    if arguments_count < min_arguments_count {
        return Err(Diagnostic::error(&format!(
            "Function `{}` expects at least `{}` arguments but got `{}`",
            function_name, min_arguments_count, arguments_count
        ))
        .with_location(location)
        .as_boxed());
    }

    if !has_varargs_parameter && arguments_count > parameters_count {
        return Err(Diagnostic::error(&format!(
            "Function `{}` expects `{}` arguments but got `{}`",
            function_name, arguments_count, parameters_count
        ))
        .with_location(location)
        .as_boxed());
    }

    // Type check the min required arguments
    for index in 0..min_arguments_count {
        let parameter_type =
            resolve_dynamic_data_type(parameters, arguments, parameters.get(index).unwrap());
        let argument = arguments.get(index).unwrap();
        let argument_type = argument.expr_type();

        // Catch undefined arguments
        if argument_type.is_undefined() {
            return Err(Diagnostic::error(&format!(
                "Function `{}` argument number {} has Undefined type",
                function_name, index,
            ))
            .add_help("Make sure you used a correct field name")
            .add_help("Check column names for each table from docs website")
            .with_location(location)
            .as_boxed());
        }

        // Both types are equals
        if parameter_type.equals(&argument_type) {
            continue;
        }

        // Argument exp can be implicit casted to Parameter type
        if parameter_type.has_implicit_cast_from(argument) {
            arguments[index] = Box::new(CastExpr {
                value: argument.clone(),
                result_type: parameter_type.clone(),
            });
            continue;
        }

        // Argument type is not equal and can't be casted to parameter type
        return Err(Diagnostic::error(&format!(
            "Function `{}` argument number {} with type `{}` don't match expected type `{}`",
            function_name,
            index,
            argument_type.literal(),
            parameter_type.literal()
        ))
        .with_location(location)
        .as_boxed());
    }

    // Type check the optional parameters
    let last_optional_param_index = min_arguments_count + optional_parameters_count;
    for index in min_arguments_count..last_optional_param_index {
        if index >= arguments_count {
            return Ok(());
        }

        let parameter_type =
            resolve_dynamic_data_type(parameters, arguments, parameters.get(index).unwrap());
        let argument = arguments.get(index).unwrap();
        let argument_type = argument.expr_type();

        // Catch undefined arguments
        if argument_type.is_undefined() {
            return Err(Diagnostic::error(&format!(
                "Function `{}` argument number {} has Undefined type",
                function_name, index,
            ))
            .add_help("Make sure you used a correct field name")
            .add_help("Check column names for each table from docs website")
            .with_location(location)
            .as_boxed());
        }

        // Both types are equals
        if parameter_type.equals(&argument_type) {
            continue;
        }

        // Argument exp can be implicit casted to Parameter type
        if parameter_type.has_implicit_cast_from(argument) {
            arguments[index] = Box::new(CastExpr {
                value: argument.clone(),
                result_type: parameter_type.clone(),
            });
            continue;
        }

        // Argument type is not equal and can't be casted to parameter type
        return Err(Diagnostic::error(&format!(
            "Function `{}` argument number {} with type `{}` don't match expected type `{}`",
            function_name,
            index,
            argument_type.literal(),
            parameter_type.literal()
        ))
        .with_location(location)
        .as_boxed());
    }

    // Type check the variable parameters if exists
    if has_varargs_parameter {
        let varargs_type =
            resolve_dynamic_data_type(parameters, arguments, parameters.last().unwrap());
        for index in last_optional_param_index..arguments_count {
            let argument = arguments.get(index).unwrap();
            let argument_type = argument.expr_type();

            // Catch undefined arguments
            if argument_type.is_undefined() {
                return Err(Diagnostic::error(&format!(
                    "Function `{}` argument number {} has Undefined type",
                    function_name, index,
                ))
                .add_help("Make sure you used a correct field name")
                .add_help("Check column names for each table from docs website")
                .with_location(location)
                .as_boxed());
            }

            // Both types are equals
            if varargs_type.equals(&argument_type) {
                continue;
            }

            // Argument exp can be implicit casted to Parameter type
            if varargs_type.has_implicit_cast_from(argument) {
                arguments[index] = Box::new(CastExpr {
                    value: argument.clone(),
                    result_type: varargs_type.clone(),
                });
                continue;
            }

            return Err(Diagnostic::error(&format!(
                "Function `{}` argument number {} with type `{}` don't match expected type `{}`",
                function_name,
                index,
                &argument_type.literal(),
                &varargs_type.literal()
            ))
            .with_location(location)
            .as_boxed());
        }
    }

    Ok(())
}

/// Check that all selected fields types are defined correctly in selected tables
/// Return the columns classified for each table
/// Return a Diagnostic Error if anything is wrong
pub fn type_check_and_classify_selected_fields(
    env: &mut Environment,
    selected_tables: &Vec<String>,
    selected_columns: &Vec<String>,
    location: SourceLocation,
) -> Result<Vec<TableSelection>, Box<Diagnostic>> {
    let mut table_selections: Vec<TableSelection> = vec![];
    let mut table_index: HashMap<String, usize> = HashMap::new();
    for (index, table) in selected_tables.iter().enumerate() {
        table_selections.push(TableSelection {
            table_name: table.to_string(),
            columns_names: vec![],
        });
        table_index.insert(table.to_string(), index);
    }

    for selected_column in selected_columns {
        let mut is_column_resolved = false;
        for table in selected_tables {
            let table_columns = env.schema.tables_fields_names.get(table.as_str()).unwrap();

            // Check if this column name exists in current table
            if table_columns.contains(&selected_column.as_str()) {
                is_column_resolved = true;
                let table_selection_index = *table_index.get(table).unwrap();
                let selection = &mut table_selections[table_selection_index];
                selection.columns_names.push(selected_column.to_string());
                continue;
            }
        }

        if !is_column_resolved {
            // This case for aggregated values or functions
            if let Some(data_type) = env.resolve_type(selected_column) {
                if !data_type.is_undefined() {
                    if table_selections.is_empty() {
                        table_selections.push(TableSelection {
                            table_name: selected_tables
                                .first()
                                .unwrap_or(&"".to_string())
                                .to_string(),
                            columns_names: vec![selected_column.to_string()],
                        });
                    } else {
                        table_selections[0]
                            .columns_names
                            .push(selected_column.to_string());
                    }
                    continue;
                }
            }

            return Err(Diagnostic::error(&format!(
                "Column `{}` not exists in any of the selected tables",
                selected_column
            ))
            .add_help("Check the documentations to see available fields for each tables")
            .with_location(location)
            .as_boxed());
        }
    }

    Ok(table_selections)
}

/// Check that all projection columns are valid for this table name
/// Return a Diagnostic Error if anything is wrong
pub fn type_check_projection_symbols(
    env: &mut Environment,
    selected_tables: &[String],
    projection_names: &[String],
    projection_locations: &[SourceLocation],
) -> Result<(), Box<Diagnostic>> {
    for (index, selected_column) in projection_names.iter().enumerate() {
        let mut is_column_resolved = false;
        for table in selected_tables {
            let table_columns = env.schema.tables_fields_names.get(table.as_str()).unwrap();
            if table_columns.contains(&selected_column.as_str()) {
                is_column_resolved = true;
                break;
            }
        }

        if !is_column_resolved {
            return Err(Diagnostic::error(&format!(
                "Column `{}` not exists in any of the selected tables",
                selected_column
            ))
            .add_help("Check the documentations to see available fields for each tables")
            .with_location(projection_locations[index])
            .as_boxed());
        }
    }

    Ok(())
}

/// Resolve dynamic data type depending on the parameters and arguments types to actual DataType
#[allow(clippy::borrowed_box)]
pub fn resolve_dynamic_data_type(
    parameters: &[Box<dyn DataType>],
    arguments: &[Box<dyn Expr>],
    data_type: &Box<dyn DataType>,
) -> Box<dyn DataType> {
    // Resolve Dynamic type
    if let Some(dynamic_type) = data_type.as_any().downcast_ref::<DynamicType>() {
        let mut resolved_data_type = (dynamic_type.function)(parameters);

        // In Case that data type is Any or Variant [Type1 | Type2...] need to resolve it from arguments types
        // To be able to use it with other expressions
        if !arguments.is_empty() && (resolved_data_type.is_variant() || resolved_data_type.is_any())
        {
            let mut arguments_types: Vec<Box<dyn DataType>> = Vec::with_capacity(arguments.len());
            for argument in arguments {
                arguments_types.push(argument.expr_type());
            }
            resolved_data_type = (dynamic_type.function)(&arguments_types);
        }

        return resolved_data_type;
    }

    // Resolve ...Dynamic to ...<TYPE> recursively
    if let Some(varargs) = data_type.as_any().downcast_ref::<VarargsType>() {
        if varargs
            .base
            .as_any()
            .downcast_ref::<DynamicType>()
            .is_some()
        {
            let base = resolve_dynamic_data_type(parameters, arguments, &varargs.base);
            return Box::new(VarargsType { base });
        }
    }

    data_type.clone()
}