gitql_parser/
type_checker.rs1use 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
15pub 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
34pub 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 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 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 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 if parameter_type.equals(&argument_type) {
100 continue;
101 }
102
103 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 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 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 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 if parameter_type.equals(&argument_type) {
149 continue;
150 }
151
152 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 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 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 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 if varargs_type.equals(&argument_type) {
194 continue;
195 }
196
197 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
221pub 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 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 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
288pub 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#[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 if let Some(dynamic_type) = data_type.as_any().downcast_ref::<DynamicType>() {
328 let mut resolved_data_type = (dynamic_type.function)(parameters);
329
330 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 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}