diff --git a/Cargo.toml b/Cargo.toml index 734d4e6020..610a9eedfa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "rusty" version = "0.2.0" authors = ["Ghaith Hachem ", "Mathias Rieder "] -edition = "2018" +edition = "2021" readme = "README.md" repository = "https://github.com/ghaith/rusty/" license = "LGPL-3.0-or-later" diff --git a/src/ast.rs b/src/ast.rs index 9fac0079e7..5197e9fc78 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -10,6 +10,12 @@ mod pre_processor; pub type AstId = usize; +#[derive(Clone, Debug, PartialEq)] +pub struct GenericBinding { + pub name: String, + pub nature: TypeNature, +} + #[derive(PartialEq)] pub struct Pou { pub name: String, @@ -18,6 +24,7 @@ pub struct Pou { pub return_type: Option, pub location: SourceRange, pub poly_mode: Option, + pub generics: Vec, } #[derive(Debug, PartialEq)] @@ -35,6 +42,95 @@ pub enum DirectAccessType { DWord, } +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum TypeNature { + Any, + Derived, + Elementary, + Magnitude, + Num, + Real, + Int, + Signed, + Unsigned, + Duration, + Bit, + Chars, + String, + Char, + Date, +} + +impl TypeNature { + pub fn derives(self, other: TypeNature) -> bool { + if other == self { + true + } else { + match self { + TypeNature::Any => true, + TypeNature::Derived => matches!(other, TypeNature::Any), + TypeNature::Elementary => matches!(other, TypeNature::Any), + TypeNature::Magnitude => matches!(other, TypeNature::Elementary | TypeNature::Any), + TypeNature::Num => matches!( + other, + TypeNature::Magnitude | TypeNature::Elementary | TypeNature::Any + ), + TypeNature::Real => matches!( + other, + TypeNature::Num + | TypeNature::Magnitude + | TypeNature::Elementary + | TypeNature::Any + ), + TypeNature::Int => matches!( + other, + TypeNature::Num + | TypeNature::Magnitude + | TypeNature::Elementary + | TypeNature::Any + ), + TypeNature::Signed => matches!( + other, + TypeNature::Int + | TypeNature::Num + | TypeNature::Magnitude + | TypeNature::Elementary + | TypeNature::Any + ), + TypeNature::Unsigned => matches!( + other, + TypeNature::Int + | TypeNature::Num + | TypeNature::Magnitude + | TypeNature::Elementary + | TypeNature::Any + ), + TypeNature::Duration => matches!( + other, + TypeNature::Num + | TypeNature::Magnitude + | TypeNature::Elementary + | TypeNature::Any + ), + TypeNature::Bit => matches!( + other, + TypeNature::Magnitude | TypeNature::Elementary | TypeNature::Any + ), + TypeNature::Chars => matches!(other, TypeNature::Elementary | TypeNature::Any), + TypeNature::String => matches!( + other, + TypeNature::Chars | TypeNature::Elementary | TypeNature::Any + ), + TypeNature::Char => matches!( + other, + TypeNature::Chars | TypeNature::Elementary | TypeNature::Any + ), + TypeNature::Date => matches!(other, TypeNature::Elementary | TypeNature::Any), + } + } + } +} + impl DirectAccessType { /// Returns true if the current index is in the range for the given type pub fn is_in_range(&self, index: u64, data_type: &DataTypeInformation) -> bool { @@ -64,12 +160,15 @@ impl DirectAccessType { impl Debug for Pou { fn fmt(&self, f: &mut Formatter<'_>) -> Result { - f.debug_struct("POU") - .field("name", &self.name) + let mut str = f.debug_struct("POU"); + str.field("name", &self.name) .field("variable_blocks", &self.variable_blocks) .field("pou_type", &self.pou_type) - .field("return_type", &self.return_type) - .finish() + .field("return_type", &self.return_type); + if !self.generics.is_empty() { + str.field("generics", &self.generics); + } + str.finish() } } @@ -335,7 +434,7 @@ impl Debug for UserTypeDeclaration { } } -#[derive(Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub enum DataType { StructType { name: Option, //maybe None for inline structs @@ -367,88 +466,36 @@ pub enum DataType { VarArgs { referenced_type: Option>, }, -} - -impl Debug for DataType { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - match self { - DataType::StructType { name, variables } => f - .debug_struct("StructType") - .field("name", name) - .field("variables", variables) - .finish(), - DataType::EnumType { name, elements } => f - .debug_struct("EnumType") - .field("name", name) - .field("elements", elements) - .finish(), - DataType::SubRangeType { - name, - referenced_type, - bounds, - } => f - .debug_struct("SubRangeType") - .field("name", name) - .field("referenced_type", referenced_type) - .field("bounds", bounds) - .finish(), - DataType::ArrayType { - name, - bounds, - referenced_type, - } => f - .debug_struct("ArrayType") - .field("name", name) - .field("bounds", bounds) - .field("referenced_type", referenced_type) - .finish(), - DataType::PointerType { - name, - referenced_type, - } => f - .debug_struct("PointerType") - .field("name", name) - .field("referenced_type", referenced_type) - .finish(), - DataType::StringType { - name, - is_wide, - size, - } => f - .debug_struct("StringType") - .field("name", name) - .field("is_wide", is_wide) - .field("size", size) - .finish(), - DataType::VarArgs { referenced_type } => f - .debug_struct("VarArgs") - .field("referenced_type", referenced_type) - .finish(), - } - } + GenericType { + name: String, + generic_symbol: String, + nature: TypeNature, + }, } impl DataType { pub fn set_name(&mut self, new_name: String) { match self { - DataType::StructType { name, variables: _ } => *name = Some(new_name), - DataType::EnumType { name, elements: _ } => *name = Some(new_name), - DataType::SubRangeType { name, .. } => *name = Some(new_name), - DataType::ArrayType { name, .. } => *name = Some(new_name), - DataType::PointerType { name, .. } => *name = Some(new_name), - DataType::StringType { name, .. } => *name = Some(new_name), + DataType::StructType { name, .. } + | DataType::EnumType { name, .. } + | DataType::SubRangeType { name, .. } + | DataType::ArrayType { name, .. } + | DataType::PointerType { name, .. } + | DataType::StringType { name, .. } => *name = Some(new_name), + DataType::GenericType { name, .. } => *name = new_name, DataType::VarArgs { .. } => {} //No names on varargs } } pub fn get_name(&self) -> Option<&str> { - match self { - DataType::StructType { name, variables: _ } => name.as_ref().map(|x| x.as_str()), - DataType::EnumType { name, elements: _ } => name.as_ref().map(|x| x.as_str()), - DataType::ArrayType { name, .. } => name.as_ref().map(|x| x.as_str()), - DataType::PointerType { name, .. } => name.as_ref().map(|x| x.as_str()), - DataType::StringType { name, .. } => name.as_ref().map(|x| x.as_str()), - DataType::SubRangeType { name, .. } => name.as_ref().map(|x| x.as_str()), + match &self { + DataType::StructType { name, .. } + | DataType::EnumType { name, .. } + | DataType::ArrayType { name, .. } + | DataType::PointerType { name, .. } + | DataType::StringType { name, .. } + | DataType::SubRangeType { name, .. } => name.as_ref().map(|x| x.as_str()), + DataType::GenericType { name, .. } => Some(name.as_str()), DataType::VarArgs { .. } => None, } } diff --git a/src/ast/pre_processor.rs b/src/ast/pre_processor.rs index a7d3263c3f..dd19417bd2 100644 --- a/src/ast/pre_processor.rs +++ b/src/ast/pre_processor.rs @@ -1,14 +1,20 @@ // Copyright (c) 2020 Ghaith Hachem and Mathias Rieder +use crate::ast::DataTypeDeclaration; + use super::{ - super::ast::{CompilationUnit, DataType, DataTypeDeclaration, UserTypeDeclaration, Variable}, + super::ast::{CompilationUnit, DataType, UserTypeDeclaration, Variable}, Pou, SourceRange, }; -use std::vec; +use std::{collections::HashMap, vec}; pub fn pre_process(unit: &mut CompilationUnit) { //process all local variables from POUs for mut pou in unit.units.iter_mut() { + //Find all generic types in that pou + let generic_types = preprocess_generic_structs(&mut pou); + unit.types.extend(generic_types); + let all_variables = pou .variable_blocks .iter_mut() @@ -39,7 +45,9 @@ pub fn pre_process(unit: &mut CompilationUnit) { for dt in unit.types.iter_mut() { { match &mut dt.data_type { - DataType::StructType { name, variables } => { + DataType::StructType { + name, variables, .. + } => { let name: &str = name.as_ref().map(|it| it.as_str()).unwrap_or("undefined"); variables .iter_mut() @@ -88,6 +96,38 @@ pub fn pre_process(unit: &mut CompilationUnit) { unit.types.append(&mut new_types); } +fn preprocess_generic_structs(pou: &mut Pou) -> Vec { + let mut generic_types = HashMap::new(); + let mut types = vec![]; + for binding in &pou.generics { + let new_name = format!("__{}__{}", pou.name, binding.name); + //Generate a type for the generic + let data_type = UserTypeDeclaration { + data_type: DataType::GenericType { + name: new_name.clone(), + generic_symbol: binding.name.clone(), + nature: binding.nature, + }, + initializer: None, + scope: Some(pou.name.clone()), + location: pou.location.clone(), + }; + types.push(data_type); + generic_types.insert(binding.name.clone(), new_name); + } + for var in pou + .variable_blocks + .iter_mut() + .flat_map(|it| it.variables.iter_mut()) + { + replace_generic_type_name(&mut var.data_type, &generic_types); + } + if let Some(datatype) = pou.return_type.as_mut() { + replace_generic_type_name(datatype, &generic_types); + }; + types +} + fn preprocess_return_type(pou: &mut Pou, types: &mut Vec) { if let Some(return_type) = &pou.return_type { if should_generate_implicit(return_type) { @@ -185,3 +225,24 @@ fn add_nested_datatypes( }); } } + +fn replace_generic_type_name(dt: &mut DataTypeDeclaration, generics: &HashMap) { + match dt { + DataTypeDeclaration::DataTypeDefinition { data_type, .. } => match data_type { + DataType::ArrayType { + referenced_type, .. + } + | DataType::PointerType { + referenced_type, .. + } => replace_generic_type_name(referenced_type.as_mut(), generics), + _ => {} + }, + DataTypeDeclaration::DataTypeReference { + referenced_type, .. + } => { + if let Some(type_name) = generics.get(referenced_type) { + *referenced_type = type_name.clone(); + } + } + } +} diff --git a/src/codegen.rs b/src/codegen.rs index 80423fe659..65a5250b0e 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -88,8 +88,12 @@ impl<'ink> CodeGen<'ink> { //Generate the POU stubs in the first go to make sure they can be referenced. for implementation in &unit.implementations { - //Don't generate external functions - if implementation.linkage != LinkageType::External { + //Don't generate external or generic functions + if implementation.linkage != LinkageType::External + && !global_index + .get_type_information_or_void(&implementation.type_name) + .is_generic() + { pou_generator.generate_implementation(implementation)?; } } diff --git a/src/codegen/generators/data_type_generator.rs b/src/codegen/generators/data_type_generator.rs index 49a4a35669..3a2ecee7ce 100644 --- a/src/codegen/generators/data_type_generator.rs +++ b/src/codegen/generators/data_type_generator.rs @@ -54,12 +54,24 @@ pub fn generate_data_types<'ink>( types_index: LlvmTypedIndex::default(), }; - let types = generator.index.get_types(); - let pou_types = generator.index.get_pou_types(); + let types = generator + .index + .get_types() + .iter() + .filter(|(_, it)| !it.get_type_information().is_generic()) + .map(|(a, b)| (a.as_str(), b)) + .collect::>(); + let pou_types = generator + .index + .get_pou_types() + .iter() + .filter(|(_, it)| !it.get_type_information().is_generic()) + .map(|(a, b)| (a.as_str(), b)) + .collect::>(); // first create all STUBs for struct types (empty structs) // and associate them in the llvm index - for (name, user_type) in types { + for (name, user_type) in &types { if let DataTypeInformation::Struct { name: struct_name, .. } = user_type.get_type_information() @@ -70,7 +82,7 @@ pub fn generate_data_types<'ink>( } } // pou_types will always be struct - for (name, user_type) in pou_types { + for (name, user_type) in &pou_types { if let DataTypeInformation::Struct { name: struct_name, .. } = user_type.get_type_information() @@ -81,18 +93,18 @@ pub fn generate_data_types<'ink>( } } // now create all other types (enum's, arrays, etc.) - for (name, user_type) in types { + for (name, user_type) in &types { let gen_type = generator.create_type(name, user_type)?; generator.types_index.associate_type(name, gen_type)? } - for (name, user_type) in pou_types { + for (name, user_type) in &pou_types { let gen_type = generator.create_type(name, user_type)?; generator.types_index.associate_pou_type(name, gen_type)? } // now since all types should be available in the llvm index, we can think about constructing and associating // initial values for the types - for (name, user_type) in types { + for (name, user_type) in &types { generator.expand_opaque_types(user_type)?; if let Some(init_value) = generator.generate_initial_value(user_type)? { generator @@ -100,7 +112,7 @@ pub fn generate_data_types<'ink>( .associate_initial_value(name, init_value)?; } } - for (name, user_type) in pou_types { + for (name, user_type) in &pou_types { generator.expand_opaque_types(user_type)?; if let Some(init_value) = generator.generate_initial_value(user_type)? { generator @@ -209,6 +221,9 @@ impl<'ink, 'b> DataTypeGenerator<'ink, 'b> { self.create_type(inner_type_name, self.index.get_type(inner_type_name)?)?; Ok(inner_type.ptr_type(AddressSpace::Generic).into()) } + DataTypeInformation::Generic { .. } => { + unreachable!("Generic types should not be generated") + } } } diff --git a/src/codegen/generators/expression_generator.rs b/src/codegen/generators/expression_generator.rs index 0e32d402d3..2583eb2ced 100644 --- a/src/codegen/generators/expression_generator.rs +++ b/src/codegen/generators/expression_generator.rs @@ -5,7 +5,7 @@ use crate::{ diagnostics::{Diagnostic, INTERNAL_LLVM_ERROR}, index::{ImplementationIndexEntry, ImplementationType, Index, VariableIndexEntry}, resolver::{AnnotationMap, StatementAnnotation}, - typesystem::{is_same_type_nature, Dimension, StringEncoding, INT_SIZE, INT_TYPE, LINT_TYPE}, + typesystem::{is_same_type_class, Dimension, StringEncoding, INT_SIZE, INT_TYPE, LINT_TYPE}, }; use inkwell::{ basic_block::BasicBlock, @@ -1176,7 +1176,7 @@ impl<'a, 'b> ExpressionCodeGenerator<'a, 'b> { ) -> Result, Diagnostic> { let type_hint = self.get_type_hint_for(stmt)?; let actual_type = self.annotations.get_type_or_void(stmt, self.index); - let literal_type = if is_same_type_nature( + let literal_type = if is_same_type_class( type_hint.get_type_information(), actual_type.get_type_information(), self.index, diff --git a/src/codegen/generators/pou_generator.rs b/src/codegen/generators/pou_generator.rs index 50c5a05360..f7c6fa5f6a 100644 --- a/src/codegen/generators/pou_generator.rs +++ b/src/codegen/generators/pou_generator.rs @@ -50,8 +50,11 @@ pub fn generate_implementation_stubs<'ink>( let mut llvm_index = LlvmTypedIndex::default(); let pou_generator = PouGenerator::new(llvm, index, annotations, types_index); for (name, implementation) in index.get_implementations() { - let curr_f = pou_generator.generate_implementation_stub(implementation, module)?; - llvm_index.associate_implementation(name, curr_f)?; + let type_info = index.get_type_information_or_void(implementation.get_type_name()); + if !type_info.is_generic() { + let curr_f = pou_generator.generate_implementation_stub(implementation, module)?; + llvm_index.associate_implementation(name, curr_f)?; + } } Ok(llvm_index) diff --git a/src/codegen/tests.rs b/src/codegen/tests.rs index 9554e293ee..6729b52587 100644 --- a/src/codegen/tests.rs +++ b/src/codegen/tests.rs @@ -2,6 +2,7 @@ mod code_gen_tests; mod codegen_error_messages_tests; mod expression_tests; +mod generics_test; mod statement_codegen_test; mod string_tests; mod typesystem_test; diff --git a/src/codegen/tests/generics_test.rs b/src/codegen/tests/generics_test.rs new file mode 100644 index 0000000000..a4003e063d --- /dev/null +++ b/src/codegen/tests/generics_test.rs @@ -0,0 +1,32 @@ +use crate::test_utils::tests::codegen; + +#[test] +fn generic_function_has_no_declaration() { + let prg = codegen( + r" + FUNCTION MAX : T VAR_INPUT in1, in2 : T END_VAR END_FUNCTION + ", + ); + + insta::assert_snapshot!(prg); +} + +#[test] +fn generic_function_call_generates_real_type_call() { + let prg = codegen( + r" + @EXTERNAL FUNCTION MAX : T VAR_INPUT in1, in2 : T END_VAR END_FUNCTION + + PROGRAM prg + VAR + a, b : INT; + END_VAR + + MAX(1,2); + MAX(a,b); + END_PROGRAM + ", + ); + + insta::assert_snapshot!(prg); +} diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__generics_test__generic_function_call_generates_real_type_call.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__generics_test__generic_function_call_generates_real_type_call.snap new file mode 100644 index 0000000000..bdf7d2eaed --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__generics_test__generic_function_call_generates_real_type_call.snap @@ -0,0 +1,63 @@ +--- +source: src/codegen/tests/generics_test.rs +expression: prg + +--- +; ModuleID = 'main' +source_filename = "main" + +%prg_interface = type { i16, i16 } +%MAX__DINT_interface = type { i32, i32 } +%MAX__INT_interface = type { i16, i16 } + +@prg_instance = global %prg_interface zeroinitializer + +define void @prg(%prg_interface* %0) { +entry: + %a = getelementptr inbounds %prg_interface, %prg_interface* %0, i32 0, i32 0 + %b = getelementptr inbounds %prg_interface, %prg_interface* %0, i32 0, i32 1 + %MAX__DINT_instance = alloca %MAX__DINT_interface, align 8 + br label %input + +input: ; preds = %entry + %1 = getelementptr inbounds %MAX__DINT_interface, %MAX__DINT_interface* %MAX__DINT_instance, i32 0, i32 0 + store i32 1, i32* %1, align 4 + %2 = getelementptr inbounds %MAX__DINT_interface, %MAX__DINT_interface* %MAX__DINT_instance, i32 0, i32 1 + store i32 2, i32* %2, align 4 + br label %call + +call: ; preds = %input + %call1 = call i32 @MAX__DINT(%MAX__DINT_interface* %MAX__DINT_instance) + br label %output + +output: ; preds = %call + br label %continue + +continue: ; preds = %output + %MAX__INT_instance = alloca %MAX__INT_interface, align 8 + br label %input2 + +input2: ; preds = %continue + %3 = getelementptr inbounds %MAX__INT_interface, %MAX__INT_interface* %MAX__INT_instance, i32 0, i32 0 + %load_a = load i16, i16* %a, align 2 + store i16 %load_a, i16* %3, align 2 + %4 = getelementptr inbounds %MAX__INT_interface, %MAX__INT_interface* %MAX__INT_instance, i32 0, i32 1 + %load_b = load i16, i16* %b, align 2 + store i16 %load_b, i16* %4, align 2 + br label %call3 + +call3: ; preds = %input2 + %call6 = call i16 @MAX__INT(%MAX__INT_interface* %MAX__INT_instance) + br label %output4 + +output4: ; preds = %call3 + br label %continue5 + +continue5: ; preds = %output4 + ret void +} + +declare i32 @MAX__DINT(%MAX__DINT_interface*) + +declare i16 @MAX__INT(%MAX__INT_interface*) + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__generics_test__generic_function_has_no_declaration.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__generics_test__generic_function_has_no_declaration.snap new file mode 100644 index 0000000000..c2d6f8c989 --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__generics_test__generic_function_has_no_declaration.snap @@ -0,0 +1,8 @@ +--- +source: src/codegen/tests/generics_test.rs +expression: prg + +--- +; ModuleID = 'main' +source_filename = "main" + diff --git a/src/diagnostics.rs b/src/diagnostics.rs index a57af9895b..5cc5585c6c 100644 --- a/src/diagnostics.rs +++ b/src/diagnostics.rs @@ -68,6 +68,9 @@ pub enum ErrNo { type__incompatible_directaccess_variable, type__incompatible_directaccess_range, type__expected_literal, + type__invalid_nature, + type__unknown_nature, + type__unresolved_generic, //codegen related codegen__general, @@ -180,6 +183,21 @@ impl Diagnostic { } } + pub fn unresolved_generic_type( + symbol: &str, + nature: &str, + location: SourceRange, + ) -> Diagnostic { + Diagnostic::SyntaxError { + message: format!( + "Could not resolve generic type {} with nature {}", + symbol, nature + ), + range: location, + err_no: ErrNo::type__unresolved_generic, + } + } + pub fn unknown_type(type_name: &str, location: SourceRange) -> Diagnostic { Diagnostic::SyntaxError { message: format!("Unknown type: {:}", type_name), @@ -401,6 +419,25 @@ impl Diagnostic { } } + pub fn invalid_type_nature(type_name: &str, nature: &str, location: SourceRange) -> Diagnostic { + Diagnostic::SyntaxError { + message: format!( + "Invalid type nature for generic argument. {} is no {}.", + type_name, nature + ), + range: location, + err_no: ErrNo::type__invalid_nature, + } + } + + pub fn unknown_type_nature(nature: &str, location: SourceRange) -> Diagnostic { + Diagnostic::SyntaxError { + message: format!("Unknown type nature {}.", nature), + range: location, + err_no: ErrNo::type__unknown_nature, + } + } + pub fn get_message(&self) -> &str { match self { Diagnostic::SyntaxError { message, .. } diff --git a/src/index.rs b/src/index.rs index 60ce1e71b8..3f00128c88 100644 --- a/src/index.rs +++ b/src/index.rs @@ -2,7 +2,7 @@ use indexmap::IndexMap; use crate::{ - ast::{Implementation, PouType, SourceRange}, + ast::{Implementation, PouType, SourceRange, TypeNature}, diagnostics::Diagnostic, typesystem::*, }; @@ -43,6 +43,16 @@ pub struct MemberInfo<'b> { } impl VariableIndexEntry { + /// Creates a new VariableIndexEntry from the current entry with a new container and type + /// This is used to create new entries from previously generic entries + pub fn into_typed(&self, container: &str, new_type: &str) -> Self { + VariableIndexEntry { + qualified_name: format!("{}.{}", container, self.name), + data_type_name: new_type.to_string(), + ..self.to_owned() + } + } + pub fn get_name(&self) -> &str { self.name.as_str() } @@ -131,11 +141,11 @@ pub enum ImplementationType { Method, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct ImplementationIndexEntry { - call_name: String, - type_name: String, - associated_class: Option, + pub(crate) call_name: String, + pub(crate) type_name: String, + pub(crate) associated_class: Option, pub(crate) implementation_type: ImplementationType, } @@ -182,6 +192,7 @@ impl From<&PouType> for ImplementationType { /// the TypeIndex carries all types. /// it is extracted into its seaprate struct so it can be /// internally borrowed individually from the other maps +#[derive(Debug)] pub struct TypeIndex { /// all types (structs, enums, type, POUs, etc.) types: IndexMap, @@ -199,6 +210,7 @@ impl TypeIndex { name: VOID_TYPE.into(), initial_value: None, information: DataTypeInformation::Void, + nature: TypeNature::Any, }, } } @@ -249,7 +261,7 @@ impl TypeIndex { /// The global index of the rusty-compiler /// /// The index contains information about all referencable elements. -#[derive()] +#[derive(Debug)] pub struct Index { /// all global variables global_variables: IndexMap, @@ -663,11 +675,6 @@ impl Index { let variable_linkage = member_info.variable_linkage; let variable_type_name = member_info.variable_type_name; - let members = self - .member_variables - .entry(container_name.to_lowercase()) - .or_insert_with(IndexMap::new); - let qualified_name = format!("{}.{}", container_name, variable_name); let entry = VariableIndexEntry { @@ -680,7 +687,14 @@ impl Index { is_constant: member_info.is_constant, location_in_parent: location, }; - members.insert(variable_name.to_lowercase(), entry); + self.register_member_entry(container_name, entry); + } + pub fn register_member_entry(&mut self, container_name: &str, entry: VariableIndexEntry) { + let members = self + .member_variables + .entry(container_name.to_lowercase()) + .or_insert_with(IndexMap::new); + members.insert(entry.name.to_lowercase(), entry); } pub fn register_enum_element( @@ -752,36 +766,16 @@ impl Index { .insert(association_name.to_lowercase(), entry); } - pub fn register_type( - &mut self, - type_name: &str, - initial_value: Option, - information: DataTypeInformation, - ) { - let index_entry = DataType { - name: type_name.into(), - initial_value, - information, - }; + pub fn register_type(&mut self, datatype: DataType) { self.type_index .types - .insert(type_name.to_lowercase(), index_entry); + .insert(datatype.get_name().to_lowercase(), datatype); } - pub fn register_pou_type( - &mut self, - type_name: &str, - initial_value: Option, - information: DataTypeInformation, - ) { - let index_entry = DataType { - name: type_name.into(), - initial_value, - information, - }; + pub fn register_pou_type(&mut self, datatype: DataType) { self.type_index .pou_types - .insert(type_name.to_lowercase(), index_entry); + .insert(datatype.get_name().to_lowercase(), datatype); } pub fn find_callable_instance_variable( diff --git a/src/index/tests.rs b/src/index/tests.rs index 4b21ec698c..b52e138c46 100644 --- a/src/index/tests.rs +++ b/src/index/tests.rs @@ -1,2 +1,3 @@ // Copyright (c) 2020 Ghaith Hachem and Mathias Rieder +mod generic_tests; mod index_tests; diff --git a/src/index/tests/generic_tests.rs b/src/index/tests/generic_tests.rs new file mode 100644 index 0000000000..7a09114a21 --- /dev/null +++ b/src/index/tests/generic_tests.rs @@ -0,0 +1,27 @@ +use pretty_assertions::assert_eq; + +use crate::{ast::GenericBinding, test_utils::tests::index, typesystem::DataTypeInformation}; + +#[test] +fn generics_saved_in_index() { + let (_, index) = index( + r" + FUNCTION foo : T; END_FUNCTION + ", + ); + + let foo_info = index.find_effective_type_info("foo").unwrap(); + assert!(foo_info.is_generic()); + if let DataTypeInformation::Struct { generics, .. } = foo_info { + let t = &generics[0]; + assert_eq!( + &GenericBinding { + name: "T".into(), + nature: crate::ast::TypeNature::Any, + }, + t + ); + } else { + panic!("{:#?} not a struct", foo_info); + } +} diff --git a/src/index/tests/index_tests.rs b/src/index/tests/index_tests.rs index 21cf7c21cd..099bd0fcb6 100644 --- a/src/index/tests/index_tests.rs +++ b/src/index/tests/index_tests.rs @@ -705,7 +705,7 @@ fn pre_processing_generates_inline_structs_global() { }, location: (54..55).into(), initializer: None, - }] + },], }, new_struct_type ); @@ -794,7 +794,7 @@ fn pre_processing_generates_inline_structs() { }, location: (67..68).into(), initializer: None, - }] + }], }, new_struct_type ); @@ -1349,6 +1349,109 @@ fn pre_processing_generates_inline_array_of_array_of_array() { ); } +#[test] +fn pre_processing_generates_generic_types() { + // GIVEN a function with a generic type G: ANY + let src = " + FUNCTION myFunc : G + VAR_INPUT + in1 : G; + in2 : INT; + END_VAR + END_FUNCTION + "; + let (mut ast, ..) = parse(src); + + // WHEN the AST ist pre-processed + crate::ast::pre_process(&mut ast); + + assert_eq!(1, ast.types.len()); + //A type __myFunc__G is created + let expected = UserTypeDeclaration { + data_type: DataType::GenericType { + name: "__myFunc__G".into(), + generic_symbol: "G".into(), + nature: TypeNature::Any, + }, + initializer: None, + location: SourceRange::undefined(), + scope: Some("myFunc".into()), + }; + + assert_eq!(format!("{:?}", expected), format!("{:?}", ast.types[0])); + + //The variables with type G now have type __myFunc__G + let pou = &ast.units[0]; + assert_eq!( + pou.variable_blocks[0].variables[0] + .data_type + .get_name() + .unwrap(), + "__myFunc__G" + ); + assert_eq!( + pou.variable_blocks[0].variables[1] + .data_type + .get_name() + .unwrap(), + "INT" + ); + assert_eq!( + pou.return_type.as_ref().unwrap().get_name().unwrap(), + "__myFunc__G" + ); +} + +#[test] +fn pre_processing_generates_nested_generic_types() { + // GIVEN a function with a generic type G: ANY + let src = " + FUNCTION myFunc : REF_TO G + VAR_INPUT + in1 : ARRAY[0..1] OF G; + in2 : INT; + END_VAR + END_FUNCTION + "; + let (mut ast, ..) = parse(src); + + // WHEN the AST ist pre-processed + crate::ast::pre_process(&mut ast); + + //A type __myFunc__G is created + let expected = UserTypeDeclaration { + data_type: DataType::GenericType { + name: "__myFunc__G".into(), + generic_symbol: "G".into(), + nature: TypeNature::Any, + }, + initializer: None, + location: SourceRange::undefined(), + scope: Some("myFunc".into()), + }; + + assert_eq!(format!("{:?}", expected), format!("{:?}", ast.types[0])); + //Additional types created + assert_eq!(3, ast.types.len()); + //referenced types of additional types are the new type + if let DataType::ArrayType { + referenced_type, .. + } = &ast.types[1].data_type + { + assert_eq!(referenced_type.get_name().unwrap(), "__myFunc__G"); + } else { + panic!("expected array"); + } + if let DataType::PointerType { + referenced_type, .. + } = &ast.types[2].data_type + { + assert_eq!(referenced_type.get_name().unwrap(), "__myFunc__G"); + } else { + panic!("expected pointer"); + } +} + #[test] fn sub_range_boundaries_are_registered_at_the_index() { // GIVEN a Subrange INT from 7 to 1000 @@ -1630,6 +1733,32 @@ fn string_dimensions_are_stored_in_the_const_expression_arena() { } } +#[test] +fn generic_datatypes_indexed() { + let source = "FUNCTION gen : INT END_FUNCTION"; + let (_, index) = index(source); + + //Expecting a datatype entry for G and a datatype entry for X + let g = index.get_type("__gen__G").unwrap(); + assert_eq!( + g.get_type_information(), + &DataTypeInformation::Generic { + name: "__gen__G".into(), + generic_symbol: "G".into(), + nature: TypeNature::Any, + } + ); + let g = index.get_type("__gen__X").unwrap(); + assert_eq!( + g.get_type_information(), + &DataTypeInformation::Generic { + name: "__gen__X".into(), + generic_symbol: "X".into(), + nature: TypeNature::Bit, + } + ); +} + #[test] fn function_name_equals_return_type() { // GIVEN function with the same name as the return type diff --git a/src/index/visitor.rs b/src/index/visitor.rs index c6ad200018..bc96ce80c4 100644 --- a/src/index/visitor.rs +++ b/src/index/visitor.rs @@ -2,7 +2,7 @@ use super::VariableType; use crate::ast::{ self, AstStatement, CompilationUnit, DataType, DataTypeDeclaration, Implementation, Pou, - PouType, SourceRange, UserTypeDeclaration, VariableBlock, VariableBlockType, + PouType, SourceRange, TypeNature, UserTypeDeclaration, VariableBlock, VariableBlockType, }; use crate::diagnostics::Diagnostic; use crate::index::{Index, MemberInfo}; @@ -15,11 +15,7 @@ pub fn visit(unit: &CompilationUnit, mut id_provider: IdProvider) -> Index { //Create the typesystem let builtins = get_builtin_types(); for data_type in builtins { - index.register_type( - data_type.get_name(), - data_type.initial_value, - data_type.clone_type_information(), - ); + index.register_type(data_type); } //Create user defined datatypes @@ -132,16 +128,19 @@ pub fn visit_pou(index: &mut Index, pou: &Pou) { ) } - index.register_pou_type( - &pou.name, - None, - DataTypeInformation::Struct { + let datatype = typesystem::DataType { + name: pou.name.to_string(), + initial_value: None, + information: DataTypeInformation::Struct { name: interface_name, member_names, varargs, source: StructSource::Pou(pou.pou_type.clone()), + generics: pou.generics.clone(), }, - ); + nature: TypeNature::Any, + }; + index.register_pou_type(datatype); } fn visit_implementation(index: &mut Index, implementation: &Implementation) { @@ -154,14 +153,16 @@ fn visit_implementation(index: &mut Index, implementation: &Implementation) { ); //if we are registing an action, also register a datatype for it if pou_type == &PouType::Action { - index.register_pou_type( - &implementation.name, - None, - DataTypeInformation::Alias { + let datatype = typesystem::DataType { + name: implementation.name.to_string(), + initial_value: None, + information: DataTypeInformation::Alias { name: implementation.name.clone(), referenced_type: implementation.type_name.clone(), }, - ); + nature: TypeNature::Derived, + }; + index.register_pou_type(datatype); } } @@ -170,15 +171,16 @@ fn register_inout_pointer_type_for(index: &mut Index, inner_type_name: &str) -> let type_name = format!("pointer_to_{}", inner_type_name); //generate a pointertype for the variable - index.register_type( - &type_name, - None, - DataTypeInformation::Pointer { + index.register_type(typesystem::DataType { + name: type_name.clone(), + initial_value: None, + information: DataTypeInformation::Pointer { name: type_name.clone(), inner_type_name: inner_type_name.to_string(), auto_deref: true, }, - ); + nature: TypeNature::Any, + }); type_name } @@ -234,6 +236,7 @@ fn visit_data_type( member_names, varargs: None, source: StructSource::OriginalDeclaration, + generics: vec![], }; let init = index @@ -243,7 +246,12 @@ fn visit_data_type( type_name.as_str(), scope.clone(), ); - index.register_type(name, init, information); + index.register_type(typesystem::DataType { + name: name.to_string(), + initial_value: init, + information, + nature: TypeNature::Derived, + }); for (count, var) in variables.iter().enumerate() { if let DataTypeDeclaration::DataTypeDefinition { data_type, scope, .. @@ -302,7 +310,12 @@ fn visit_data_type( enum_name, scope.clone(), ); - index.register_type(enum_name, init, information); + index.register_type(typesystem::DataType { + name: enum_name.to_string(), + initial_value: init, + information, + nature: TypeNature::Int, + }); elements.iter().enumerate().for_each(|(i, v)| { let enum_literal = ast::AstStatement::LiteralInteger { @@ -346,7 +359,12 @@ fn visit_data_type( name, scope.clone(), ); - index.register_type(name, init, information) + index.register_type(typesystem::DataType { + name: name.to_string(), + initial_value: init, + information, + nature: TypeNature::Int, + }); } DataType::ArrayType { name: Some(name), @@ -398,7 +416,12 @@ fn visit_data_type( name, scope.clone(), ); - index.register_type(name, init, information) + index.register_type(typesystem::DataType { + name: name.to_string(), + initial_value: init, + information, + nature: TypeNature::Any, + }); } DataType::PointerType { name: Some(name), @@ -419,7 +442,12 @@ fn visit_data_type( name, scope.clone(), ); - index.register_type(name, init, information) + index.register_type(typesystem::DataType { + name: name.to_string(), + initial_value: init, + information, + nature: TypeNature::Any, + }); } DataType::StringType { name: Some(name), @@ -469,9 +497,32 @@ fn visit_data_type( type_name, scope.clone(), ); - index.register_type(name, init, information) + index.register_type(typesystem::DataType { + name: name.to_string(), + initial_value: init, + information, + nature: TypeNature::String, + }); + } + DataType::VarArgs { .. } => {} //Varargs are not indexed, + DataType::GenericType { + name, + generic_symbol, + nature, + } => { + let information = DataTypeInformation::Generic { + name: name.clone(), + generic_symbol: generic_symbol.clone(), + nature: *nature, + }; + index.register_type(typesystem::DataType { + name: name.to_string(), + initial_value: None, + information, + nature: TypeNature::Any, + }); } - DataType::VarArgs { .. } => {} //Varargs are not indexed + _ => { /* unnamed datatypes are ignored */ } }; } diff --git a/src/lib.rs b/src/lib.rs index 67a1399a08..2169b10c64 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -358,7 +358,8 @@ pub fn compile_module<'c, T: SourceContainer>( } // ### PHASE 1.1 resolve constant literal values - let (full_index, _unresolvables) = resolver::const_evaluator::evaluate_constants(full_index); + let (mut full_index, _unresolvables) = + resolver::const_evaluator::evaluate_constants(full_index); // ### PHASE 2 ### // annotation & validation everything @@ -377,6 +378,9 @@ pub fn compile_module<'c, T: SourceContainer>( all_annotations.import(annotations); } + //Merge the new indices with the full index + full_index.import(std::mem::take(&mut all_annotations.new_index)); + // ### PHASE 3 ### // - codegen let code_generator = codegen::CodeGen::new(context, "main"); diff --git a/src/parser.rs b/src/parser.rs index f57e3d7981..f8332832ea 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -150,6 +150,8 @@ fn parse_pou( let name = parse_identifier(lexer).unwrap_or_else(|| "".to_string()); // parse POU name + let generics = parse_generics(lexer); + with_scope(lexer, name.clone(), |lexer| { // TODO: Parse USING directives // TODO: Parse EXTENDS specifier @@ -216,6 +218,7 @@ fn parse_pou( return_type, location: SourceRange::new(start..lexer.range().end), poly_mode, + generics, }]; pous.append(&mut impl_pous); @@ -234,6 +237,59 @@ fn parse_pou( pou } +fn parse_generics(lexer: &mut ParseSession) -> Vec { + if lexer.allow(&Token::OperatorLess) { + parse_any_in_region(lexer, vec![Token::OperatorGreater], |lexer| { + let mut generics = vec![]; + loop { + //identifier + if let Some(name) = parse_identifier(lexer) { + lexer.consume_or_report(Token::KeywordColon); + + //Expect a type nature + if let Some(nature) = + parse_identifier(lexer).map(|it| parse_type_nature(lexer, &it)) + { + generics.push(GenericBinding { name, nature }); + } + } + + if !lexer.allow(&Token::KeywordComma) || lexer.allow(&Token::OperatorGreater) { + break; + } + } + + generics + }) + } else { + vec![] + } +} + +fn parse_type_nature(lexer: &mut ParseSession, nature: &str) -> TypeNature { + match nature { + "ANY" => TypeNature::Any, + "ANY_DERIVED" => TypeNature::Derived, + "ANY_ELEMENTARY" => TypeNature::Elementary, + "ANY_MAGNITUDE" => TypeNature::Magnitude, + "ANY_NUM" => TypeNature::Num, + "ANY_REAL" => TypeNature::Real, + "ANY_INT" => TypeNature::Int, + "ANY_SIGNED" => TypeNature::Signed, + "ANY_UNSIGNED" => TypeNature::Unsigned, + "ANY_DURATION" => TypeNature::Duration, + "ANY_BIT" => TypeNature::Bit, + "ANY_CHARS" => TypeNature::Chars, + "ANY_STRING" => TypeNature::String, + "ANY_CHAR" => TypeNature::Char, + "ANY_DATE" => TypeNature::Date, + _ => { + lexer.accept_diagnostic(Diagnostic::unknown_type_nature(nature, lexer.location())); + TypeNature::Any + } + } +} + fn parse_polymorphism_mode( lexer: &mut ParseSession, pou_type: &PouType, @@ -318,6 +374,7 @@ fn parse_method( let poly_mode = parse_polymorphism_mode(lexer, &pou_type); let overriding = lexer.allow(&KeywordOverride); let name = parse_identifier(lexer)?; + let generics = parse_generics(lexer); let return_type = parse_return_type(lexer, &pou_type); let mut variable_blocks = vec![]; @@ -361,6 +418,7 @@ fn parse_method( return_type, location: SourceRange::new(method_start..method_end), poly_mode, + generics, }, implementation, )) diff --git a/src/parser/tests.rs b/src/parser/tests.rs index 78596eb7fa..f800509747 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -9,6 +9,7 @@ mod function_parser_tests; mod initializer_parser_tests; mod misc_parser_tests; mod parse_errors; +mod parse_generics; mod program_parser_tests; mod statement_parser_tests; mod type_parser_tests; diff --git a/src/parser/tests/expressions_parser_tests.rs b/src/parser/tests/expressions_parser_tests.rs index 7846ebbe0d..b8e1c241e7 100644 --- a/src/parser/tests/expressions_parser_tests.rs +++ b/src/parser/tests/expressions_parser_tests.rs @@ -2579,6 +2579,7 @@ fn sized_string_as_function_return() { }), variable_blocks: vec![], location: SourceRange::undefined(), + generics: vec![], }; assert_eq!(format!("{:?}", ast.units[0]), format!("{:?}", expected)); @@ -2624,6 +2625,7 @@ fn array_type_as_function_return() { }), variable_blocks: vec![], location: SourceRange::undefined(), + generics: vec![], }; assert_eq!(format!("{:?}", ast.units[0]), format!("{:?}", expected)); diff --git a/src/parser/tests/function_parser_tests.rs b/src/parser/tests/function_parser_tests.rs index 3308914b9d..e66cac6674 100644 --- a/src/parser/tests/function_parser_tests.rs +++ b/src/parser/tests/function_parser_tests.rs @@ -158,6 +158,7 @@ fn varargs_parameters_can_be_parsed() { }], location: SourceRange::undefined(), poly_mode: None, + generics: vec![], }; assert_eq!(format!("{:#?}", expected), format!("{:#?}", x).as_str()); } diff --git a/src/parser/tests/misc_parser_tests.rs b/src/parser/tests/misc_parser_tests.rs index 4b4fc0137b..ab2d392bf9 100644 --- a/src/parser/tests/misc_parser_tests.rs +++ b/src/parser/tests/misc_parser_tests.rs @@ -59,6 +59,7 @@ fn exponent_literals_parsed_as_variables() { }], }], location: SourceRange::undefined(), + generics: vec![], }; assert_eq!(format!("{:#?}", expected), format!("{:#?}", pou).as_str()); let implementation = &parse_result.implementations[0]; diff --git a/src/parser/tests/parse_generics.rs b/src/parser/tests/parse_generics.rs new file mode 100644 index 0000000000..279c2ab297 --- /dev/null +++ b/src/parser/tests/parse_generics.rs @@ -0,0 +1,236 @@ +use crate::ast::{DataTypeDeclaration, GenericBinding, TypeNature, Variable}; +use crate::test_utils::tests::parse; +use crate::SourceRange; + +#[test] +fn generic_markers_on_pou_added() { + let src = "FUNCTION test< + A: ANY, + B : ANY_DERIVED, + C : ANY_ELEMENTARY, + D: ANY_MAGNITUDE, + E: ANY_NUM, + F : ANY_REAL, + G : ANY_INT, + H : ANY_SIGNED, + I : ANY_UNSIGNED, + J : ANY_DURATION, + K : ANY_BIT, + L : ANY_CHARS, + M : ANY_STRING, + N : ANY_CHAR, + O : ANY_DATE> : INT END_FUNCTION"; + let (parse_result, _) = parse(src); + let function = &parse_result.units[0]; + //Make sure the function has the generic parametes T: ANY, R : ANY_NUMBER + let generics = &function.generics; + assert!(!generics.is_empty()); + let t = &generics[0]; + assert_eq!( + &GenericBinding { + name: "A".into(), + nature: TypeNature::Any, + }, + t + ); + let r = &generics[1]; + assert_eq!( + &GenericBinding { + name: "B".into(), + nature: TypeNature::Derived, + }, + r + ); + let t = &generics[2]; + assert_eq!( + &GenericBinding { + name: "C".into(), + nature: TypeNature::Elementary, + }, + t + ); + let r = &generics[3]; + assert_eq!( + &GenericBinding { + name: "D".into(), + nature: TypeNature::Magnitude, + }, + r + ); + let t = &generics[4]; + assert_eq!( + &GenericBinding { + name: "E".into(), + nature: TypeNature::Num, + }, + t + ); + let r = &generics[5]; + assert_eq!( + &GenericBinding { + name: "F".into(), + nature: TypeNature::Real, + }, + r + ); + let t = &generics[6]; + assert_eq!( + &GenericBinding { + name: "G".into(), + nature: TypeNature::Int, + }, + t + ); + let r = &generics[7]; + assert_eq!( + &GenericBinding { + name: "H".into(), + nature: TypeNature::Signed, + }, + r + ); + let t = &generics[8]; + assert_eq!( + &GenericBinding { + name: "I".into(), + nature: TypeNature::Unsigned, + }, + t + ); + let r = &generics[9]; + assert_eq!( + &GenericBinding { + name: "J".into(), + nature: TypeNature::Duration, + }, + r + ); + let t = &generics[10]; + assert_eq!( + &GenericBinding { + name: "K".into(), + nature: TypeNature::Bit, + }, + t + ); + let r = &generics[11]; + assert_eq!( + &GenericBinding { + name: "L".into(), + nature: TypeNature::Chars, + }, + r + ); + let t = &generics[12]; + assert_eq!( + &GenericBinding { + name: "M".into(), + nature: TypeNature::String, + }, + t + ); + let r = &generics[13]; + assert_eq!( + &GenericBinding { + name: "N".into(), + nature: TypeNature::Char, + }, + r + ); + let t = &generics[14]; + assert_eq!( + &GenericBinding { + name: "O".into(), + nature: TypeNature::Date, + }, + t + ); +} + +#[test] +fn generic_markers_on_method_added() { + let src = "CLASS xx METHOD test : INT END_METHOD END_CLASS"; + let (parse_result, _) = parse(src); + let function = &parse_result.units[1]; + //Make sure the function has the generic parametes T: ANY, R : ANY_NUMBER + let generics = &function.generics; + assert!(!generics.is_empty()); + let t = &generics[0]; + assert_eq!( + &GenericBinding { + name: "T".into(), + nature: TypeNature::Any, + }, + t + ); + let r = &generics[1]; + assert_eq!( + &GenericBinding { + name: "R".into(), + nature: TypeNature::Num, + }, + r + ); +} + +#[test] +fn generic_parameters_are_datatypes() { + let src = "FUNCTION test : R VAR_INPUT x : T; y : R; END_VAR END_FUNCTION"; + let (parse_result, _) = parse(src); + let function = &parse_result.units[0]; + let variables = &function.variable_blocks[0].variables; + assert_eq!( + &vec![ + Variable { + name: "x".into(), + data_type: DataTypeDeclaration::DataTypeReference { + referenced_type: "T".into(), + location: SourceRange::new(53..54), + }, + initializer: None, + location: SourceRange::new(49..50), + }, + Variable { + name: "y".into(), + data_type: DataTypeDeclaration::DataTypeReference { + referenced_type: "R".into(), + location: SourceRange::new(60..61), + }, + initializer: None, + location: SourceRange::new(56..57), + }, + ], + variables + ); +} + +#[test] +fn generic_method_parameters_are_datatypes() { + let src = "CLASS cls METHOD test : R VAR_INPUT x : T; y : R; END_VAR END_METHOD END_CLASS"; + let (parse_result, _) = parse(src); + let function = &parse_result.units[1]; + let variables = &function.variable_blocks[0].variables; + assert_eq!( + &vec![ + Variable { + name: "x".into(), + data_type: DataTypeDeclaration::DataTypeReference { + referenced_type: "T".into(), + location: SourceRange::new(61..62), + }, + initializer: None, + location: SourceRange::new(57..58), + }, + Variable { + name: "y".into(), + data_type: DataTypeDeclaration::DataTypeReference { + referenced_type: "R".into(), + location: SourceRange::new(68..69), + }, + initializer: None, + location: SourceRange::new(64..65), + }, + ], + variables + ); +} diff --git a/src/resolver.rs b/src/resolver.rs index 350bf0b577..c2acb0335d 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -5,14 +5,16 @@ //! Recursively visits all statements and expressions of a `CompilationUnit` and //! records all resulting types associated with the statement's id. +use std::collections::HashMap; + use indexmap::IndexMap; pub mod const_evaluator; use crate::{ ast::{ - self, AstId, AstStatement, CompilationUnit, DataType, DataTypeDeclaration, Operator, Pou, - UserTypeDeclaration, Variable, + self, AstId, AstStatement, CompilationUnit, DataType, DataTypeDeclaration, GenericBinding, + Operator, Pou, TypeNature, UserTypeDeclaration, Variable, }, index::{ImplementationIndexEntry, ImplementationType, Index, VariableIndexEntry}, typesystem::{ @@ -95,7 +97,6 @@ impl<'s> VisitorContext<'s> { pub struct TypeAnnotator<'i> { index: &'i Index, annotation_map: AnnotationMap, - //context: VisitorContext<'i>, } #[derive(Debug, Clone, PartialEq)] @@ -128,6 +129,7 @@ impl StatementAnnotation { } } +#[derive(Default)] pub struct AnnotationMap { /// maps a statement to the type it resolves to type_map: IndexMap, @@ -140,20 +142,24 @@ pub struct AnnotationMap { /// if the type-hint is equal to the actual type, or there is no /// useful type-hint to resolve, there is no mapping in this map type_hint_map: IndexMap, + + /// A map from a call to the generic function name of that call + generic_nature_map: IndexMap, + + //An index of newly created types + pub new_index: Index, } impl AnnotationMap { /// creates a new empty AnnotationMap pub fn new() -> Self { - AnnotationMap { - type_map: IndexMap::new(), - type_hint_map: IndexMap::new(), - } + Default::default() } pub fn import(&mut self, other: AnnotationMap) { self.type_map.extend(other.type_map); self.type_hint_map.extend(other.type_hint_map); + self.new_index.import(other.new_index); } /// annotates the given statement (using it's `get_id()`) with the given type-name @@ -165,6 +171,11 @@ impl AnnotationMap { self.type_hint_map.insert(s.get_id(), annotation); } + /// Annotates the ast statement with its original generic nature + pub fn add_generic_nature(&mut self, s: &AstStatement, nature: TypeNature) { + self.generic_nature_map.insert(s.get_id(), nature); + } + pub fn get(&self, s: &AstStatement) -> Option<&StatementAnnotation> { self.type_map.get(&s.get_id()) } @@ -252,11 +263,9 @@ impl AnnotationMap { pub fn has_type_annotation(&self, id: &usize) -> bool { self.type_map.contains_key(id) } -} -impl Default for AnnotationMap { - fn default() -> Self { - Self::new() + pub fn get_generic_nature(&self, s: &AstStatement) -> Option<&TypeNature> { + self.generic_nature_map.get(&s.get_id()) } } @@ -273,7 +282,6 @@ impl<'i> TypeAnnotator<'i> { /// Returns an AnnotationMap with the resulting types for all visited Statements. See `AnnotationMap` pub fn visit_unit(index: &Index, unit: &'i CompilationUnit) -> AnnotationMap { let mut visitor = TypeAnnotator::new(index); - let ctx = &VisitorContext { pou: None, qualifier: None, @@ -879,10 +887,10 @@ impl<'i> TypeAnnotator<'i> { if let Some(lhs) = ctx.call { //special context for left hand side self.visit_statement(&ctx.with_pou(lhs), left); - // give a type hint that we want the right side to be stored in the left's type } else { self.visit_statement(ctx, left); } + // give a type hint that we want the right side to be stored in the left's type self.update_right_hand_side_expected_type(left, right); } AstStatement::OutputAssignment { left, right, .. } => { @@ -901,62 +909,71 @@ impl<'i> TypeAnnotator<'i> { .. } => { self.visit_statement(ctx, operator); - if let Some(s) = parameters.as_ref() { - let operator_qualifier = self - .annotation_map - .get_annotation(operator) - .and_then(|it| match it { - StatementAnnotation::Function { qualified_name, .. } => { - Some(qualified_name.clone()) - } - StatementAnnotation::Program { qualified_name } => { - Some(qualified_name.clone()) - } - StatementAnnotation::Variable { resulting_type, .. } => { - //lets see if this is a FB - if let Some(implementation) = - self.index.find_implementation(resulting_type.as_str()) + let operator_qualifier = self + .annotation_map + .get_annotation(operator) + .and_then(|it| match it { + StatementAnnotation::Function { qualified_name, .. } => { + Some(qualified_name.clone()) + } + StatementAnnotation::Program { qualified_name } => { + Some(qualified_name.clone()) + } + StatementAnnotation::Variable { resulting_type, .. } => { + //lets see if this is a FB + if let Some(implementation) = + self.index.find_implementation(resulting_type.as_str()) + { + if let ImplementationType::FunctionBlock {} = + implementation.get_implementation_type() { - if let ImplementationType::FunctionBlock {} = - implementation.get_implementation_type() - { - return Some(resulting_type.clone()); - } + return Some(resulting_type.clone()); } - None } - _ => None, - }) - .unwrap_or_else(|| VOID_TYPE.to_string()); - let ctx = ctx.with_call(operator_qualifier.as_str()); - //need to clone the qualifier string because of borrow checker :-( - //todo look into this + None + } + _ => None, + }) + .unwrap_or_else(|| VOID_TYPE.to_string()); + let ctx = ctx.with_call(operator_qualifier.as_str()); + let mut generics_candidates: HashMap<&str, Vec<&str>> = HashMap::new(); + if let Some(s) = parameters.as_ref() { self.visit_statement(&ctx, s); - - let parameters = ast::flatten_expression_list(s); - let all_members = self - .index - .get_container_members(operator_qualifier.as_str()); - let members = all_members.iter().filter(|it| it.is_parameter()); - - for (i, m) in members.enumerate() { - if let Some(p) = parameters.get(i) { - if !matches!(p, AstStatement::Assignment { .. }) { - if let Some(effective_member_type) = - self.index.find_effective_type(m.get_type_name()) - { - //update the type hint - self.annotation_map.annotate_type_hint( - p, - StatementAnnotation::value( - effective_member_type.get_name(), - ), - ) + if let Some(s) = parameters.as_ref() { + let parameters = ast::flatten_expression_list(s); + let all_members = self.index.get_container_members(&operator_qualifier); + let members = all_members.iter().filter(|it| it.is_parameter()); + for (i, m) in members.enumerate() { + if let Some(p) = parameters.get(i) { + //Add a possible generic candidate + if let Some((key, candidate)) = TypeAnnotator::get_generic_candidate( + self.index, + &self.annotation_map, + m, + p, + ) { + generics_candidates + .entry(key) + .or_insert_with(std::vec::Vec::new) + .push(candidate); + } else { + //Don't do this for generics + self.annotate_parameters(*p, m.get_type_name()); } } } } } + //Attempt to resolve the generic signature here + self.update_generic_call_statement( + generics_candidates, + &operator_qualifier, + operator, + parameters, + ctx, + ); + if let Some(StatementAnnotation::Function { return_type, .. }) = self.annotation_map.get(operator) { @@ -1000,6 +1017,278 @@ impl<'i> TypeAnnotator<'i> { } } } + // Returns a possible generic for the current statement + fn get_generic_candidate<'idx>( + index: &'idx Index, + annotation_map: &AnnotationMap, + member: &VariableIndexEntry, + statement: &AstStatement, + ) -> Option<(&'idx str, &'idx str)> { + //If generic add a generic annotation + if let Some(DataTypeInformation::Generic { generic_symbol, .. }) = + index.find_effective_type_info(member.get_type_name()) + { + let statement = match statement { + //The right side of the assignment is the source of truth + AstStatement::Assignment { right, .. } => right, + _ => statement, + }; + //Find the statement's type + annotation_map + .get_type(statement, index) + .map(|it| it.get_name()) + .map(|name| (generic_symbol.as_str(), name)) + } else { + None + } + } + + /// Updates the generic information of a function call + /// It collects all candidates for a generic function + /// Then chooses the best fitting function signature + /// And reannotates the function with the found information + fn update_generic_call_statement( + &mut self, + generics_candidates: HashMap<&str, Vec<&str>>, + implementation_name: &str, + operator: &AstStatement, + parameters: &Option, + ctx: VisitorContext, + ) { + let operator_type = self + .index + .get_effective_type_by_name(implementation_name) + .get_type_information(); + if let DataTypeInformation::Struct { generics, .. } = operator_type { + if !generics.is_empty() { + let generic_map = &self.derive_generic_types(generics, generics_candidates); + //Annotate the statement with the new function call + if let Some(StatementAnnotation::Function { + qualified_name, + return_type, + }) = self.annotation_map.get_annotation(operator) + { + //Figure out the new name for the call + let (name, annotation) = self.get_generic_function_annotation( + generics, + qualified_name, + return_type, + generic_map, + ); + //Create a new pou and implementation for the function + if let Some((pou, implementation)) = self + .index + .find_effective_type(qualified_name) + .zip(self.index.find_implementation(qualified_name)) + { + self.annotation_map.new_index.register_implementation( + &name, + &name, + implementation.get_associated_class_name(), + implementation.get_implementation_type().clone(), + ); + self.index_generic_type(pou, &name, generic_map); + } + self.annotation_map.annotate(operator, annotation); + } + //Adjust annotations on the inner statement + if let Some(s) = parameters.as_ref() { + self.visit_statement(&ctx, s); + self.update_generic_function_parameters(s, implementation_name, generic_map); + } + } + } + } + + /// Only works for Structs + pub fn index_generic_type( + &mut self, + generic_type: &typesystem::DataType, + specific_name: &str, + generics: &HashMap, + ) { + let information = if let DataTypeInformation::Struct { + member_names, + source, + varargs, + .. + } = &generic_type.get_type_information() + { + let interface_name = format!("{}_interface", specific_name); + let information = DataTypeInformation::Struct { + name: interface_name, + member_names: member_names.clone(), + varargs: varargs.clone(), + source: source.clone(), + generics: vec![], + }; + for member in member_names { + if let Some(generic_entry) = self.index.find_member(generic_type.get_name(), member) + { + let new_name = if let Some(old_type) = self + .index + .find_effective_type(generic_entry.get_type_name()) + { + match old_type.get_type_information() { + DataTypeInformation::Generic { generic_symbol, .. } => generics + .get(generic_symbol) + .map(String::as_str) + .unwrap_or_else(|| old_type.get_name()), + _ => old_type.get_name(), + } + } else { + unreachable!("Member not indexed") + }; + let entry = generic_entry.into_typed(specific_name, new_name); + self.annotation_map + .new_index + .register_member_entry(specific_name, entry); + } + } + information + } else { + unreachable!("Not a struct"); + }; + let datatype = typesystem::DataType { + name: specific_name.to_string(), + initial_value: generic_type.initial_value, + nature: generic_type.nature, + information, + }; + self.annotation_map.new_index.register_pou_type(datatype); + } + + fn update_generic_function_parameters( + &mut self, + s: &AstStatement, + function_name: &str, + generic_map: &HashMap, + ) { + /// An internal struct used to hold the type and nature of a generic parameter + struct TypeAndNature<'a> { + datatype: &'a typesystem::DataType, + nature: TypeNature, + } + + // Map the input or output parameters of the function into a list of Index Entry with an optional generic type discription + let parameters = ast::flatten_expression_list(s); + let all_members = self.index.get_container_members(function_name); + let members: Vec<(&VariableIndexEntry, Option)> = all_members + .iter() + .filter(|it| it.is_parameter()) + .copied() + .map(|it| { + //if the member is generic + if let Some(DataTypeInformation::Generic { + generic_symbol, + nature, + .. + }) = self.index.find_effective_type_info(it.get_type_name()) + { + let real_type = generic_map + .get(generic_symbol) + .and_then(|it| self.index.find_effective_type(it)) + .map(|datatype| TypeAndNature { + datatype, + nature: *nature, + }); + (it, real_type) + } else { + (it, None) + } + }) + .collect(); + + //See if parameters have assignments, as they need to be treated differently + if parameters.iter().any(|it| { + matches!( + it, + AstStatement::Assignment { .. } | AstStatement::OutputAssignment { .. } + ) + }) { + for p in parameters { + match p { + AstStatement::Assignment { left, right, .. } + | AstStatement::OutputAssignment { left, right, .. } => { + if let AstStatement::Reference { name, .. } = &**left { + //Find the member with that name + if let Some((_, Some(TypeAndNature { datatype, nature }))) = + members.iter().find(|(it, _)| it.get_name() == name) + { + self.annotation_map.add_generic_nature(p, *nature); + self.annotation_map.annotate( + left, + StatementAnnotation::value(datatype.get_name()), + ); + self.update_right_hand_side_expected_type(left, right); + } + } + } + _ => { /*do nothing*/ } + } + } + } else { + for (i, (_, dt)) in members.iter().enumerate() { + if let Some(p) = parameters.get(i) { + if let Some(TypeAndNature { datatype, nature }) = dt { + self.annotation_map.add_generic_nature(p, *nature); + self.annotation_map + .annotate_type_hint(p, StatementAnnotation::value(datatype.get_name())); + } + } + } + } + } + + fn get_generic_function_annotation( + &self, + generics: &[GenericBinding], + qualified_name: &str, + return_type: &str, + generic_map: &HashMap, + ) -> (String, StatementAnnotation) { + let generic_name = generics + .iter() + .map(|it| { + generic_map + .get(&it.name) + .map(String::as_str) + .unwrap_or_else(|| it.name.as_str()) + }) + .collect::>() + .join("__"); + let function_name = format!("{}__{}", qualified_name, generic_name); + let return_type = if let DataTypeInformation::Generic { generic_symbol, .. } = + self.index.get_type_information_or_void(return_type) + { + generic_map + .get(generic_symbol) + .map(String::as_str) + .unwrap_or(return_type) + } else { + return_type + } + .to_string(); + ( + function_name.clone(), + StatementAnnotation::Function { + qualified_name: function_name, + return_type, + }, + ) + } + + fn annotate_parameters(&mut self, p: &AstStatement, type_name: &str) { + if !matches!(p, AstStatement::Assignment { .. }) { + if let Some(effective_member_type) = self.index.find_effective_type(type_name) { + //update the type hint + self.annotation_map.annotate_type_hint( + p, + StatementAnnotation::value(effective_member_type.get_name()), + ) + } + } + } /// annotate a literal statement fn visit_statement_literals(&mut self, ctx: &VisitorContext, statement: &AstStatement) { @@ -1059,6 +1348,42 @@ impl<'i> TypeAnnotator<'i> { _ => {} } } + + fn derive_generic_types( + &self, + generics: &[GenericBinding], + generics_candidates: HashMap<&str, Vec<&str>>, + ) -> HashMap { + let mut generic_map: HashMap = HashMap::new(); + for GenericBinding { name, .. } in generics { + //Get the current binding + if let Some(candidates) = generics_candidates.get(name.as_str()) { + //Find the best suiting type + let winner = candidates + .iter() + .fold( + None, + |previous_type: Option<&DataTypeInformation>, current| { + let current_type = self + .index + .find_effective_type_info(current) + .map(|it| self.index.find_intrinsic_type(it)); + //Find bigger + if let Some((previous, current)) = previous_type.zip(current_type) { + Some(typesystem::get_bigger_type(previous, current, self.index)) + } else { + current_type + } + }, + ) + .map(DataTypeInformation::get_name); + if let Some(winner) = winner { + generic_map.insert(name.into(), winner.into()); + } + } + } + generic_map + } } fn find_implementation_annotation(name: &str, index: &Index) -> Option { diff --git a/src/resolver/tests.rs b/src/resolver/tests.rs index 871c2ea41c..a67bad62f2 100644 --- a/src/resolver/tests.rs +++ b/src/resolver/tests.rs @@ -1,8 +1,5 @@ -#[cfg(test)] mod const_resolver_tests; -#[cfg(test)] mod resolve_control_statments; -#[cfg(test)] mod resolve_expressions_tests; -#[cfg(test)] +mod resolve_generic_calls; mod resolve_literals_tests; diff --git a/src/resolver/tests/resolve_expressions_tests.rs b/src/resolver/tests/resolve_expressions_tests.rs index 25b93d19ce..5187831c52 100644 --- a/src/resolver/tests/resolve_expressions_tests.rs +++ b/src/resolver/tests/resolve_expressions_tests.rs @@ -2547,7 +2547,6 @@ fn string_compare_should_resolve_to_bool() { // THEN we want the hint for 'NULL' to be POINTER TO BYTE let annotations = annotate(&unit, &index); let a_eq_b = &unit.implementations[1].statements[0]; - dbg!(a_eq_b); assert_eq!( Some(&StatementAnnotation::value("BOOL")), annotations.get_annotation(a_eq_b), diff --git a/src/resolver/tests/resolve_generic_calls.rs b/src/resolver/tests/resolve_generic_calls.rs new file mode 100644 index 0000000000..487854cbee --- /dev/null +++ b/src/resolver/tests/resolve_generic_calls.rs @@ -0,0 +1,455 @@ +use crate::{ + assert_type_and_hint, + ast::{self, AstStatement}, + resolver::TypeAnnotator, + test_utils::tests::index, + typesystem::{BYTE_TYPE, DINT_TYPE, INT_TYPE, REAL_TYPE}, +}; + +#[test] +fn resolved_generic_call_added_to_index() { + let (unit, index) = index( + " + FUNCTION myFunc : G + VAR_INPUT + x : G; + END_VAR + END_FUNCTION + + PROGRAM PRG + VAR + a : INT; + END_VAR + myFunc(x := a); + myFunc(6); + myFunc(1.0); + END_PROGRAM", + ); + let annotations = TypeAnnotator::visit_unit(&index, &unit); + //The implementations are added to the index + let implementations = annotations.new_index.get_implementations(); + assert_eq!(3, implementations.len()); + assert!(implementations.contains_key("myfunc__int")); + assert!(implementations.contains_key("myfunc__dint")); + assert!(implementations.contains_key("myfunc__real")); + + //The pous are added to the index + let pous = annotations.new_index.get_pou_types(); + assert_eq!(3, pous.len()); + assert!(pous.contains_key("myfunc__int")); + assert!(pous.contains_key("myfunc__dint")); + assert!(pous.contains_key("myfunc__real")); + + //Each POU has members + assert_eq!( + "INT", + annotations + .new_index + .find_member("myfunc__int", "x") + .unwrap() + .get_type_name() + ); + assert_eq!( + "DINT", + annotations + .new_index + .find_member("myfunc__dint", "x") + .unwrap() + .get_type_name() + ); + assert_eq!( + "REAL", + annotations + .new_index + .find_member("myfunc__real", "x") + .unwrap() + .get_type_name() + ); +} + +#[test] +fn generic_call_annotated_with_correct_type() { + let (unit, index) = index( + " + FUNCTION myFunc : G + VAR_INPUT + x : G; + END_VAR + END_FUNCTION + + PROGRAM PRG + VAR + a : INT; + END_VAR + myFunc(x := a); + myFunc(6); + myFunc(1.0); + END_PROGRAM", + ); + let annotations = TypeAnnotator::visit_unit(&index, &unit); + let call = &unit.implementations[1].statements[0]; + + //The return type should have the correct type + assert_type_and_hint!(&annotations, &index, call, INT_TYPE, None); + + if let AstStatement::CallStatement { + operator, + parameters, + .. + } = call + { + //The call name should nave the correct type + assert_eq!(Some("myFunc__INT"), annotations.get_call_name(operator)); + //parameters should have the correct type + if let Some(AstStatement::Assignment { left, right, .. }) = &**parameters { + assert_type_and_hint!(&annotations, &index, left, INT_TYPE, None); + assert_type_and_hint!(&annotations, &index, right, INT_TYPE, Some(INT_TYPE)); + } else { + unreachable!("Not an assignment"); + } + } else { + unreachable!("Not a call statement"); + } + + let call = &unit.implementations[1].statements[1]; + + //The return type should have the correct type + assert_type_and_hint!(&annotations, &index, call, DINT_TYPE, None); + + if let AstStatement::CallStatement { + operator, + parameters, + .. + } = call + { + //The call name should nave the correct type + assert_eq!(Some("myFunc__DINT"), annotations.get_call_name(operator)); + if let Some(parameter) = &**parameters { + //parameters should have the correct type + assert_type_and_hint!(&annotations, &index, parameter, DINT_TYPE, Some(DINT_TYPE)); + } else { + unreachable!("No Parameters"); + } + } else { + unreachable!("Not a call statement"); + } + + let call = &unit.implementations[1].statements[2]; + + //The return type should have the correct type + assert_type_and_hint!(&annotations, &index, call, REAL_TYPE, None); + + if let AstStatement::CallStatement { + operator, + parameters, + .. + } = call + { + //The call name should nave the correct type + assert_eq!(Some("myFunc__REAL"), annotations.get_call_name(operator)); + if let Some(parameter) = &**parameters { + //parameters should have the correct type + assert_type_and_hint!(&annotations, &index, parameter, REAL_TYPE, Some(REAL_TYPE)); + } else { + unreachable!("No Parameters"); + } + } else { + unreachable!("Not a call statement"); + } +} + +#[test] +fn generic_call_multi_params_annotated_with_correct_type() { + let (unit, index) = index( + " + FUNCTION myFunc : G + VAR_INPUT + x,y : G; + z : F; + END_VAR + END_FUNCTION + + PROGRAM PRG + VAR + a : INT; + b : DINT; + c : INT; + END_VAR + myFunc(x := a, y := b, z := c); + myFunc(a,b,c); + myFunc(1.0, 2, BYTE#2); + END_PROGRAM", + ); + let annotations = TypeAnnotator::visit_unit(&index, &unit); + + let call = &unit.implementations[1].statements[0]; + + //The return type should have the correct type + assert_type_and_hint!(&annotations, &index, call, DINT_TYPE, None); + + if let AstStatement::CallStatement { + operator, + parameters, + .. + } = call + { + //The call name should nave the correct type + assert_eq!( + Some("myFunc__DINT__INT"), + annotations.get_call_name(operator) + ); + //parameters should have the correct type + if let Some(parameters) = &**parameters { + if let [x, y, z] = ast::flatten_expression_list(parameters)[..] { + if let AstStatement::Assignment { left, right, .. } = x { + assert_type_and_hint!(&annotations, &index, left, DINT_TYPE, None); + assert_type_and_hint!(&annotations, &index, right, INT_TYPE, Some(DINT_TYPE)); + } else { + unreachable!("Not an assignment"); + } + + if let AstStatement::Assignment { left, right, .. } = y { + assert_type_and_hint!(&annotations, &index, left, DINT_TYPE, None); + assert_type_and_hint!(&annotations, &index, right, DINT_TYPE, Some(DINT_TYPE)); + } else { + unreachable!("Not an assignment"); + } + + if let AstStatement::Assignment { left, right, .. } = z { + assert_type_and_hint!(&annotations, &index, left, INT_TYPE, None); + assert_type_and_hint!(&annotations, &index, right, INT_TYPE, Some(INT_TYPE)); + } else { + unreachable!("Not an assignment"); + } + } else { + unreachable!("Wrong parameters {:?}", parameters) + } + } else { + unreachable!("No parameters") + } + } + let call = &unit.implementations[1].statements[1]; + + //The return type should have the correct type + assert_type_and_hint!(&annotations, &index, call, DINT_TYPE, None); + + if let AstStatement::CallStatement { + operator, + parameters, + .. + } = call + { + //The call name should nave the correct type + assert_eq!( + Some("myFunc__DINT__INT"), + annotations.get_call_name(operator) + ); + //parameters should have the correct type + if let Some(parameters) = &**parameters { + if let [x, y, z] = ast::flatten_expression_list(parameters)[..] { + assert_type_and_hint!(&annotations, &index, x, INT_TYPE, Some(DINT_TYPE)); + assert_type_and_hint!(&annotations, &index, y, DINT_TYPE, Some(DINT_TYPE)); + assert_type_and_hint!(&annotations, &index, z, INT_TYPE, Some(INT_TYPE)); + } else { + unreachable!("Wrong parameters {:?}", parameters) + } + } else { + unreachable!("No parameters") + } + } + + let call = &unit.implementations[1].statements[2]; + + //The return type should have the correct type + assert_type_and_hint!(&annotations, &index, call, REAL_TYPE, None); + + if let AstStatement::CallStatement { + operator, + parameters, + .. + } = call + { + //The call name should nave the correct type + assert_eq!( + Some("myFunc__REAL__BYTE"), + annotations.get_call_name(operator) + ); + //parameters should have the correct type + if let Some(parameters) = &**parameters { + if let [x, y, z] = ast::flatten_expression_list(parameters)[..] { + assert_type_and_hint!(&annotations, &index, x, REAL_TYPE, Some(REAL_TYPE)); + assert_type_and_hint!(&annotations, &index, y, DINT_TYPE, Some(REAL_TYPE)); + assert_type_and_hint!(&annotations, &index, z, BYTE_TYPE, Some(BYTE_TYPE)); + } else { + unreachable!("Wrong parameters {:?}", parameters) + } + } else { + unreachable!("No parameters") + } + } +} + +#[test] +fn call_order_of_parameters_does_not_change_annotations() { + let (unit, index) = index( + " + FUNCTION myFunc : INT + VAR_INPUT + x,y : DINT; + z : INT; + END_VAR + END_FUNCTION + + PROGRAM PRG + VAR + a : INT; + b : DINT; + c : INT; + END_VAR + myFunc(x := a, y := b, z := c); + myFunc(y := b, x := a, z := c); + myFunc(z := c, y := b, x := a); + END_PROGRAM", + ); + let annotations = TypeAnnotator::visit_unit(&index, &unit); + + fn get_parameter_with_name<'a>( + parameters_list: &[&'a AstStatement], + expected_name: &str, + ) -> &'a AstStatement { + parameters_list.iter().find(|it| matches!(it, AstStatement::Assignment { left, .. } + if { matches!(&**left, AstStatement::Reference{name, ..} if {name == expected_name})})).unwrap() + } + + // all three call-statements should give the exact same annotations + // the order of the parameters should not matter + for call in &unit.implementations[1].statements { + if let AstStatement::CallStatement { + operator, + parameters, + .. + } = call + { + //The call name should nave the correct type + assert_eq!(Some("myFunc"), annotations.get_call_name(operator)); + //parameters should have the correct type + if let Some(parameters) = &**parameters { + let parameters_list = ast::flatten_expression_list(parameters); + let [x, y, z] = [ + get_parameter_with_name(¶meters_list, "x"), + get_parameter_with_name(¶meters_list, "y"), + get_parameter_with_name(¶meters_list, "z"), + ]; + if let [AstStatement::Assignment { + left: x, right: a, .. + }, AstStatement::Assignment { + left: y, right: b, .. + }, AstStatement::Assignment { + left: z, right: c, .. + }] = [x, y, z] + { + assert_type_and_hint!(&annotations, &index, x, DINT_TYPE, None); + assert_type_and_hint!(&annotations, &index, a, INT_TYPE, Some(DINT_TYPE)); + + assert_type_and_hint!(&annotations, &index, y, DINT_TYPE, None); + assert_type_and_hint!(&annotations, &index, b, DINT_TYPE, Some(DINT_TYPE)); + + assert_type_and_hint!(&annotations, &index, z, INT_TYPE, None); + assert_type_and_hint!(&annotations, &index, c, INT_TYPE, Some(INT_TYPE)); + } else { + unreachable!("Not an assignment"); + } + } else { + unreachable!("No parameters") + } + } + } +} + +#[test] +fn call_order_of_generic_parameters_does_not_change_annotations() { + let (unit, index) = index( + " + FUNCTION myFunc : G + VAR_INPUT + x,y : G; + z : F; + END_VAR + END_FUNCTION + + PROGRAM PRG + VAR + a : INT; + b : DINT; + c : INT; + END_VAR + myFunc(x := a, y := b, z := c); + myFunc(y := b, x := a, z := c); + myFunc(z := c, y := b, x := a); + END_PROGRAM", + ); + let annotations = TypeAnnotator::visit_unit(&index, &unit); + + fn get_parameter_with_name<'a>( + parameters_list: &[&'a AstStatement], + expected_name: &str, + ) -> &'a AstStatement { + parameters_list + .iter() + .find(|it| { + matches!(it, AstStatement::Assignment { left, .. } + if { matches!(&**left, AstStatement::Reference{name, ..} + if {name == expected_name} + )} + ) + }) + .unwrap() + } + + // all three call-statements should give the exact same annotations + // the order of the parameters should not matter + for call in &unit.implementations[1].statements { + if let AstStatement::CallStatement { + operator, + parameters, + .. + } = call + { + //The call name should nave the correct type + assert_eq!( + Some("myFunc__DINT__INT"), + annotations.get_call_name(operator) + ); + //parameters should have the correct type + if let Some(parameters) = &**parameters { + let parameters_list = ast::flatten_expression_list(parameters); + let [x, y, z] = [ + get_parameter_with_name(¶meters_list, "x"), + get_parameter_with_name(¶meters_list, "y"), + get_parameter_with_name(¶meters_list, "z"), + ]; + if let [AstStatement::Assignment { + left: x, right: a, .. + }, AstStatement::Assignment { + left: y, right: b, .. + }, AstStatement::Assignment { + left: z, right: c, .. + }] = [x, y, z] + { + assert_type_and_hint!(&annotations, &index, x, DINT_TYPE, None); + assert_type_and_hint!(&annotations, &index, a, INT_TYPE, Some(DINT_TYPE)); + + assert_type_and_hint!(&annotations, &index, y, DINT_TYPE, None); + assert_type_and_hint!(&annotations, &index, b, DINT_TYPE, Some(DINT_TYPE)); + + assert_type_and_hint!(&annotations, &index, z, INT_TYPE, None); + assert_type_and_hint!(&annotations, &index, c, INT_TYPE, Some(INT_TYPE)); + } else { + unreachable!("Not an assignment"); + } + } else { + unreachable!("No parameters") + } + } + } +} diff --git a/src/test_utils.rs b/src/test_utils.rs index 3bb23e306f..6ee6f0cbe8 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -41,8 +41,9 @@ pub mod tests { pub fn codegen_without_unwrap(src: &str) -> Result { let (unit, index) = index(src); - let (index, ..) = evaluate_constants(index); - let annotations = TypeAnnotator::visit_unit(&index, &unit); + let (mut index, ..) = evaluate_constants(index); + let mut annotations = TypeAnnotator::visit_unit(&index, &unit); + index.import(std::mem::take(&mut annotations.new_index)); let context = inkwell::context::Context::create(); let code_generator = crate::codegen::CodeGen::new(&context, "main"); diff --git a/src/typesystem.rs b/src/typesystem.rs index d91e1ddc93..d7e2499270 100644 --- a/src/typesystem.rs +++ b/src/typesystem.rs @@ -2,7 +2,7 @@ use std::{mem::size_of, ops::Range}; use crate::{ - ast::{AstStatement, Operator, PouType}, + ast::{AstStatement, GenericBinding, Operator, PouType, TypeNature}, index::{const_expressions::ConstId, Index}, }; @@ -69,13 +69,16 @@ pub const CONST_WSTRING_TYPE: &str = "___CONST_WSTRING"; pub const VOID_TYPE: &str = "VOID"; -#[derive(Debug, PartialEq)] +#[cfg(test)] +mod tests; + +#[derive(Debug, Clone, PartialEq)] pub struct DataType { pub name: String, /// the initial value defined on the TYPE-declration pub initial_value: Option, pub information: DataTypeInformation, - //TODO : Add location information + pub nature: TypeNature, } impl DataType { @@ -90,6 +93,11 @@ impl DataType { pub fn clone_type_information(&self) -> DataTypeInformation { self.information.clone() } + + pub fn has_nature(&self, nature: TypeNature, index: &Index) -> bool { + let type_nature = index.get_intrinsic_type_by_name(self.get_name()).nature; + type_nature.derives(nature) + } } type VarArgs = Option; @@ -161,6 +169,7 @@ pub enum DataTypeInformation { member_names: Vec, varargs: Option, source: StructSource, + generics: Vec, }, Array { name: String, @@ -198,17 +207,26 @@ pub enum DataTypeInformation { name: String, referenced_type: String, }, + Generic { + name: String, + generic_symbol: String, + nature: TypeNature, + }, Void, } impl DataTypeInformation { pub fn get_name(&self) -> &str { match self { - DataTypeInformation::Struct { name, .. } => name, - DataTypeInformation::Array { name, .. } => name, - DataTypeInformation::Pointer { name, .. } => name, - DataTypeInformation::Integer { name, .. } => name, - DataTypeInformation::Float { name, .. } => name, + DataTypeInformation::Struct { name, .. } + | DataTypeInformation::Array { name, .. } + | DataTypeInformation::Pointer { name, .. } + | DataTypeInformation::Integer { name, .. } + | DataTypeInformation::Float { name, .. } + | DataTypeInformation::SubRange { name, .. } + | DataTypeInformation::Alias { name, .. } + | DataTypeInformation::Enum { name, .. } + | DataTypeInformation::Generic { name, .. } => name, DataTypeInformation::String { encoding: StringEncoding::Utf8, .. @@ -217,10 +235,7 @@ impl DataTypeInformation { encoding: StringEncoding::Utf16, .. } => "WSTRING", - DataTypeInformation::SubRange { name, .. } => name, DataTypeInformation::Void => "VOID", - DataTypeInformation::Alias { name, .. } => name, - DataTypeInformation::Enum { name, .. } => name, } } @@ -286,6 +301,14 @@ impl DataTypeInformation { } } + pub fn is_generic(&self) -> bool { + match self { + DataTypeInformation::Struct { generics, .. } => !generics.is_empty(), + DataTypeInformation::Generic { .. } => true, + _ => false, + } + } + pub fn get_size(&self) -> u32 { match self { DataTypeInformation::Integer { size, .. } => *size, @@ -298,6 +321,7 @@ impl DataTypeInformation { DataTypeInformation::Alias { .. } => unimplemented!("alias"), DataTypeInformation::Void => 0, DataTypeInformation::Enum { .. } => DINT_SIZE, + DataTypeInformation::Generic { .. } => unimplemented!("generics"), } } @@ -324,12 +348,35 @@ impl Dimension { } } +pub trait DataTypeInformationProvider<'a>: Into<&'a DataTypeInformation> { + fn get_type_information(&self) -> &DataTypeInformation; +} + +impl<'a> DataTypeInformationProvider<'a> for &'a DataTypeInformation { + fn get_type_information(&self) -> &'a DataTypeInformation { + self + } +} + +impl<'a> From<&'a DataType> for &'a DataTypeInformation { + fn from(dt: &'a DataType) -> Self { + dt.get_type_information() + } +} + +impl<'a> DataTypeInformationProvider<'a> for &'a DataType { + fn get_type_information(&self) -> &DataTypeInformation { + DataType::get_type_information(self) + } +} + pub fn get_builtin_types() -> Vec { vec![ DataType { name: "__VOID".into(), initial_value: None, information: DataTypeInformation::Void, + nature: TypeNature::Any, }, DataType { name: BOOL_TYPE.into(), @@ -339,6 +386,7 @@ pub fn get_builtin_types() -> Vec { signed: false, size: BOOL_SIZE, }, + nature: TypeNature::Bit, }, DataType { name: BYTE_TYPE.into(), @@ -348,6 +396,7 @@ pub fn get_builtin_types() -> Vec { signed: false, size: BYTE_SIZE, }, + nature: TypeNature::Bit, }, DataType { name: SINT_TYPE.into(), @@ -357,6 +406,7 @@ pub fn get_builtin_types() -> Vec { signed: true, size: SINT_SIZE, }, + nature: TypeNature::Signed, }, DataType { name: USINT_TYPE.into(), @@ -366,6 +416,7 @@ pub fn get_builtin_types() -> Vec { signed: false, size: SINT_SIZE, }, + nature: TypeNature::Unsigned, }, DataType { name: WORD_TYPE.into(), @@ -375,6 +426,7 @@ pub fn get_builtin_types() -> Vec { signed: false, size: INT_SIZE, }, + nature: TypeNature::Bit, }, DataType { name: INT_TYPE.into(), @@ -384,6 +436,7 @@ pub fn get_builtin_types() -> Vec { signed: true, size: INT_SIZE, }, + nature: TypeNature::Signed, }, DataType { name: UINT_TYPE.into(), @@ -393,6 +446,7 @@ pub fn get_builtin_types() -> Vec { signed: false, size: INT_SIZE, }, + nature: TypeNature::Unsigned, }, DataType { name: DWORD_TYPE.into(), @@ -402,6 +456,7 @@ pub fn get_builtin_types() -> Vec { signed: false, size: DINT_SIZE, }, + nature: TypeNature::Bit, }, DataType { name: DINT_TYPE.into(), @@ -411,6 +466,7 @@ pub fn get_builtin_types() -> Vec { signed: true, size: DINT_SIZE, }, + nature: TypeNature::Signed, }, DataType { name: UDINT_TYPE.into(), @@ -420,6 +476,7 @@ pub fn get_builtin_types() -> Vec { signed: false, size: DINT_SIZE, }, + nature: TypeNature::Unsigned, }, DataType { name: LWORD_TYPE.into(), @@ -429,6 +486,7 @@ pub fn get_builtin_types() -> Vec { signed: false, size: LINT_SIZE, }, + nature: TypeNature::Bit, }, DataType { name: LINT_TYPE.into(), @@ -438,6 +496,7 @@ pub fn get_builtin_types() -> Vec { signed: true, size: LINT_SIZE, }, + nature: TypeNature::Signed, }, DataType { name: DATE_TYPE.into(), @@ -447,6 +506,7 @@ pub fn get_builtin_types() -> Vec { signed: true, size: DATE_TIME_SIZE, }, + nature: TypeNature::Date, }, DataType { name: TIME_TYPE.into(), @@ -456,6 +516,7 @@ pub fn get_builtin_types() -> Vec { signed: true, size: DATE_TIME_SIZE, }, + nature: TypeNature::Duration, }, DataType { name: DATE_AND_TIME_TYPE.into(), @@ -465,6 +526,7 @@ pub fn get_builtin_types() -> Vec { signed: true, size: DATE_TIME_SIZE, }, + nature: TypeNature::Date, }, DataType { name: TIME_OF_DAY_TYPE.into(), @@ -474,6 +536,7 @@ pub fn get_builtin_types() -> Vec { signed: true, size: DATE_TIME_SIZE, }, + nature: TypeNature::Date, }, DataType { name: ULINT_TYPE.into(), @@ -483,6 +546,7 @@ pub fn get_builtin_types() -> Vec { signed: false, size: LINT_SIZE, }, + nature: TypeNature::Unsigned, }, DataType { name: REAL_TYPE.into(), @@ -491,6 +555,7 @@ pub fn get_builtin_types() -> Vec { name: REAL_TYPE.into(), size: REAL_SIZE, }, + nature: TypeNature::Real, }, DataType { name: LREAL_TYPE.into(), @@ -499,6 +564,7 @@ pub fn get_builtin_types() -> Vec { name: LREAL_TYPE.into(), size: LREAL_SIZE, }, + nature: TypeNature::Real, }, DataType { name: STRING_TYPE.into(), @@ -507,6 +573,7 @@ pub fn get_builtin_types() -> Vec { size: TypeSize::from_literal(DEFAULT_STRING_LEN + 1), encoding: StringEncoding::Utf8, }, + nature: TypeNature::String, }, DataType { name: WSTRING_TYPE.into(), @@ -515,6 +582,7 @@ pub fn get_builtin_types() -> Vec { size: TypeSize::from_literal(DEFAULT_STRING_LEN + 1), encoding: StringEncoding::Utf16, }, + nature: TypeNature::String, }, DataType { name: CONST_STRING_TYPE.into(), @@ -523,6 +591,7 @@ pub fn get_builtin_types() -> Vec { size: TypeSize::from_literal(u16::MAX as u32), encoding: StringEncoding::Utf8, }, + nature: TypeNature::String, }, DataType { name: CONST_WSTRING_TYPE.into(), @@ -531,6 +600,7 @@ pub fn get_builtin_types() -> Vec { size: TypeSize::from_literal(u16::MAX as u32), encoding: StringEncoding::Utf16, }, + nature: TypeNature::String, }, DataType { name: SHORT_DATE_AND_TIME_TYPE.into(), @@ -539,6 +609,7 @@ pub fn get_builtin_types() -> Vec { name: SHORT_DATE_AND_TIME_TYPE.into(), referenced_type: DATE_AND_TIME_TYPE.into(), }, + nature: TypeNature::Date, }, DataType { name: SHORT_DATE_TYPE.into(), @@ -547,6 +618,7 @@ pub fn get_builtin_types() -> Vec { name: SHORT_DATE_TYPE.into(), referenced_type: DATE_TYPE.into(), }, + nature: TypeNature::Date, }, DataType { name: SHORT_TIME_OF_DAY_TYPE.into(), @@ -555,6 +627,7 @@ pub fn get_builtin_types() -> Vec { name: SHORT_TIME_OF_DAY_TYPE.into(), referenced_type: TIME_OF_DAY_TYPE.into(), }, + nature: TypeNature::Date, }, DataType { name: SHORT_TIME_TYPE.into(), @@ -563,6 +636,7 @@ pub fn get_builtin_types() -> Vec { name: SHORT_TIME_TYPE.into(), referenced_type: TIME_TYPE.into(), }, + nature: TypeNature::Duration, }, DataType { name: CHAR_TYPE.into(), @@ -572,6 +646,7 @@ pub fn get_builtin_types() -> Vec { signed: false, size: 8, }, + nature: TypeNature::Char, }, DataType { name: WCHAR_TYPE.into(), @@ -581,6 +656,7 @@ pub fn get_builtin_types() -> Vec { signed: false, size: 16, }, + nature: TypeNature::Char, }, ] } @@ -595,11 +671,17 @@ fn get_rank(type_information: &DataTypeInformation) -> u32 { } } DataTypeInformation::Float { size, .. } => size + 1000, - _ => unreachable!(), + DataTypeInformation::String { size, .. } => match size { + TypeSize::LiteralInteger(size) => *size, + TypeSize::ConstExpression(_) => todo!("String rank with CONSTANTS"), + }, + _ => todo!("{:?}", type_information), } } -pub fn is_same_type_nature( +/// Returns true if provided types have the same type nature +/// i.e. Both are numeric or both are floats +pub fn is_same_type_class( ltype: &DataTypeInformation, rtype: &DataTypeInformation, index: &Index, @@ -609,31 +691,41 @@ pub fn is_same_type_nature( match ltype { DataTypeInformation::Integer { .. } => matches!(rtype, DataTypeInformation::Integer { .. }), DataTypeInformation::Float { .. } => matches!(rtype, DataTypeInformation::Float { .. }), + DataTypeInformation::String { encoding: lenc, .. } => { + matches!(rtype, DataTypeInformation::String { encoding, .. } if encoding == lenc) + } _ => ltype == rtype, } } -pub fn get_bigger_type<'t>( - left_type: &'t DataType, - right_type: &'t DataType, +/// Returns the bigger of the two provided types +pub fn get_bigger_type< + 't, + T: DataTypeInformationProvider<'t> + std::convert::From<&'t DataType>, +>( + left_type: T, + right_type: T, index: &'t Index, -) -> &'t DataType { +) -> T { let lt = left_type.get_type_information(); let rt = right_type.get_type_information(); - if is_same_type_nature(lt, rt, index) { + if is_same_type_class(lt, rt, index) { if get_rank(lt) < get_rank(rt) { right_type } else { left_type } - } else { + } else if lt.is_numerical() && rt.is_numerical() { let real_type = index.get_type_or_panic(REAL_TYPE); let real_size = real_type.get_type_information().get_size(); if lt.get_size() > real_size || rt.get_size() > real_size { - index.get_type_or_panic(LREAL_TYPE) + index.get_type_or_panic(LREAL_TYPE).into() } else { - real_type + real_type.into() } + } else { + //Return the first + left_type } } @@ -677,86 +769,3 @@ pub fn get_equals_function_name_for(type_name: &str, operator: &Operator) -> Opt suffix.map(|suffix| format!("{}_{}", type_name, suffix)) } - -#[cfg(test)] -mod tests { - use crate::{ - ast::{CompilationUnit, Operator}, - index::visitor::visit, - lexer::IdProvider, - typesystem::{ - get_equals_function_name_for, get_signed_type, BYTE_TYPE, DINT_TYPE, DWORD_TYPE, - INT_TYPE, LINT_TYPE, LWORD_TYPE, SINT_TYPE, STRING_TYPE, UDINT_TYPE, UINT_TYPE, - ULINT_TYPE, USINT_TYPE, WORD_TYPE, - }, - }; - - macro_rules! assert_signed_type { - ($expected:expr, $actual:expr, $index:expr) => { - assert_eq!( - $index.find_effective_type_info($expected), - get_signed_type($index.find_effective_type_info($actual).unwrap(), &$index) - ); - }; - } - - #[test] - pub fn signed_types_tests() { - // Given an initialized index - let index = visit(&CompilationUnit::default(), IdProvider::default()); - assert_signed_type!(SINT_TYPE, BYTE_TYPE, index); - assert_signed_type!(SINT_TYPE, USINT_TYPE, index); - assert_signed_type!(INT_TYPE, WORD_TYPE, index); - assert_signed_type!(INT_TYPE, UINT_TYPE, index); - assert_signed_type!(DINT_TYPE, DWORD_TYPE, index); - assert_signed_type!(DINT_TYPE, UDINT_TYPE, index); - assert_signed_type!(LINT_TYPE, ULINT_TYPE, index); - assert_signed_type!(LINT_TYPE, LWORD_TYPE, index); - - assert_eq!( - Some( - index - .find_effective_type(STRING_TYPE) - .as_ref() - .unwrap() - .get_type_information() - ), - get_signed_type( - index - .find_effective_type(STRING_TYPE) - .as_ref() - .unwrap() - .get_type_information(), - &index - ) - ); - } - - #[test] - pub fn equal_method_function_names() { - assert_eq!( - Some("STRING_EQUAL".to_string()), - get_equals_function_name_for("STRING", &Operator::Equal) - ); - assert_eq!( - Some("MY_TYPE_EQUAL".to_string()), - get_equals_function_name_for("MY_TYPE", &Operator::Equal) - ); - assert_eq!( - Some("STRING_LESS".to_string()), - get_equals_function_name_for("STRING", &Operator::Less) - ); - assert_eq!( - Some("MY_TYPE_LESS".to_string()), - get_equals_function_name_for("MY_TYPE", &Operator::Less) - ); - assert_eq!( - Some("STRING_GREATER".to_string()), - get_equals_function_name_for("STRING", &Operator::Greater) - ); - assert_eq!( - Some("MY_TYPE_GREATER".to_string()), - get_equals_function_name_for("MY_TYPE", &Operator::Greater) - ); - } -} diff --git a/src/typesystem/tests.rs b/src/typesystem/tests.rs new file mode 100644 index 0000000000..f94154238b --- /dev/null +++ b/src/typesystem/tests.rs @@ -0,0 +1,612 @@ +use crate::{ + ast::{CompilationUnit, Operator, TypeNature}, + index::{visitor::visit, Index}, + lexer::IdProvider, + typesystem::{ + self, get_equals_function_name_for, get_signed_type, Dimension, BOOL_TYPE, BYTE_TYPE, + CHAR_TYPE, DATE_AND_TIME_TYPE, DATE_TYPE, DINT_TYPE, DWORD_TYPE, INT_TYPE, LINT_TYPE, + LREAL_TYPE, LWORD_TYPE, REAL_TYPE, SINT_TYPE, STRING_TYPE, TIME_OF_DAY_TYPE, TIME_TYPE, + UDINT_TYPE, UINT_TYPE, ULINT_TYPE, USINT_TYPE, WCHAR_TYPE, WORD_TYPE, WSTRING_TYPE, + }, +}; + +use super::TypeSize; + +macro_rules! assert_signed_type { + ($expected:expr, $actual:expr, $index:expr) => { + assert_eq!( + $index.find_effective_type_info($expected), + get_signed_type($index.find_effective_type_info($actual).unwrap(), &$index) + ); + }; +} + +#[test] +pub fn signed_types_tests() { + // Given an initialized index + let index = visit(&CompilationUnit::default(), IdProvider::default()); + assert_signed_type!(SINT_TYPE, BYTE_TYPE, index); + assert_signed_type!(SINT_TYPE, USINT_TYPE, index); + assert_signed_type!(INT_TYPE, WORD_TYPE, index); + assert_signed_type!(INT_TYPE, UINT_TYPE, index); + assert_signed_type!(DINT_TYPE, DWORD_TYPE, index); + assert_signed_type!(DINT_TYPE, UDINT_TYPE, index); + assert_signed_type!(LINT_TYPE, ULINT_TYPE, index); + assert_signed_type!(LINT_TYPE, LWORD_TYPE, index); + + assert_eq!( + Some( + index + .find_effective_type(STRING_TYPE) + .as_ref() + .unwrap() + .get_type_information() + ), + get_signed_type( + index + .find_effective_type(STRING_TYPE) + .as_ref() + .unwrap() + .get_type_information(), + &index + ) + ); +} + +#[test] +pub fn equal_method_function_names() { + assert_eq!( + Some("STRING_EQUAL".to_string()), + get_equals_function_name_for("STRING", &Operator::Equal) + ); + assert_eq!( + Some("MY_TYPE_EQUAL".to_string()), + get_equals_function_name_for("MY_TYPE", &Operator::Equal) + ); + assert_eq!( + Some("STRING_LESS".to_string()), + get_equals_function_name_for("STRING", &Operator::Less) + ); + assert_eq!( + Some("MY_TYPE_LESS".to_string()), + get_equals_function_name_for("MY_TYPE", &Operator::Less) + ); + assert_eq!( + Some("STRING_GREATER".to_string()), + get_equals_function_name_for("STRING", &Operator::Greater) + ); + assert_eq!( + Some("MY_TYPE_GREATER".to_string()), + get_equals_function_name_for("MY_TYPE", &Operator::Greater) + ); +} + +#[test] +fn get_bigger_size_integers_test() { + // Given an initialized index + let index = visit(&CompilationUnit::default(), IdProvider::default()); + //Given integer types + let sint_type = index.get_type_or_panic(SINT_TYPE); + let int_type = index.get_type_or_panic(INT_TYPE); + let dint_type = index.get_type_or_panic(DINT_TYPE); + let lint_type = index.get_type_or_panic(LINT_TYPE); + //Unsigned + let usint_type = index.get_type_or_panic(USINT_TYPE); + let uint_type = index.get_type_or_panic(UINT_TYPE); + let udint_type = index.get_type_or_panic(UDINT_TYPE); + let ulint_type = index.get_type_or_panic(ULINT_TYPE); + + //The bigger type is the one with the bigger size + assert_eq!( + int_type, + typesystem::get_bigger_type(sint_type, int_type, &index) + ); + assert_eq!( + dint_type, + typesystem::get_bigger_type(int_type, dint_type, &index) + ); + assert_eq!( + lint_type, + typesystem::get_bigger_type(lint_type, dint_type, &index) + ); + assert_eq!( + uint_type, + typesystem::get_bigger_type(usint_type, uint_type, &index) + ); + assert_eq!( + udint_type, + typesystem::get_bigger_type(uint_type, udint_type, &index) + ); + assert_eq!( + ulint_type, + typesystem::get_bigger_type(ulint_type, udint_type, &index) + ); +} + +#[test] +fn get_bigger_size_integers_mix_test() { + // Given an initialized index + let index = visit(&CompilationUnit::default(), IdProvider::default()); + //Given integer types + let sint_type = index.get_type_or_panic(SINT_TYPE); + let int_type = index.get_type_or_panic(INT_TYPE); + let dint_type = index.get_type_or_panic(DINT_TYPE); + let lint_type = index.get_type_or_panic(LINT_TYPE); + //Unsigned + let usint_type = index.get_type_or_panic(USINT_TYPE); + let uint_type = index.get_type_or_panic(UINT_TYPE); + let udint_type = index.get_type_or_panic(UDINT_TYPE); + let ulint_type = index.get_type_or_panic(ULINT_TYPE); + + assert_eq!( + int_type, + typesystem::get_bigger_type(sint_type, int_type, &index) + ); + assert_eq!( + dint_type, + typesystem::get_bigger_type(int_type, dint_type, &index) + ); + assert_eq!( + lint_type, + typesystem::get_bigger_type(lint_type, dint_type, &index) + ); + assert_eq!( + uint_type, + typesystem::get_bigger_type(usint_type, uint_type, &index) + ); + assert_eq!( + udint_type, + typesystem::get_bigger_type(uint_type, udint_type, &index) + ); + assert_eq!( + ulint_type, + typesystem::get_bigger_type(ulint_type, udint_type, &index) + ); + //The bigger type is the signed + assert_eq!( + sint_type, + typesystem::get_bigger_type(sint_type, usint_type, &index) + ); + assert_eq!( + int_type, + typesystem::get_bigger_type(int_type, uint_type, &index) + ); + assert_eq!( + dint_type, + typesystem::get_bigger_type(dint_type, udint_type, &index) + ); +} + +#[test] +fn get_bigger_size_real_test() { + // Given an initialized index + let index = visit(&CompilationUnit::default(), IdProvider::default()); + //Given two float numbers (REAL/LREAL) + let real_type = index.get_type_or_panic(REAL_TYPE); + let lreal_type = index.get_type_or_panic(LREAL_TYPE); + //LREAL is bigger than REAL + assert_eq!( + lreal_type, + typesystem::get_bigger_type(lreal_type, real_type, &index) + ); +} + +#[test] +fn get_bigger_size_numeric_test() { + // Given an initialized index + let index = visit(&CompilationUnit::default(), IdProvider::default()); + //Given a float and an int + //integer types + let int_type = index.get_type_or_panic(INT_TYPE); + let dint_type = index.get_type_or_panic(DINT_TYPE); + let lint_type = index.get_type_or_panic(LINT_TYPE); + + //Float types + let real_type = index.get_type_or_panic(REAL_TYPE); + let lreal_type = index.get_type_or_panic(LREAL_TYPE); + //The bigger type is the float + assert_eq!( + real_type, + typesystem::get_bigger_type(real_type, int_type, &index) + ); + assert_eq!( + real_type, + typesystem::get_bigger_type(real_type, dint_type, &index) + ); + //Given an int that is bigger than a float in size (LINT) + //The bigger type is an LREAL + assert_eq!( + lreal_type, + typesystem::get_bigger_type(lint_type, real_type, &index) + ); +} + +#[test] +fn get_bigger_size_string_test() { + // Given an initialized index + let index = visit(&CompilationUnit::default(), IdProvider::default()); + //Given two STRING + let string_1024 = typesystem::DataType { + name: "STRING_1024".into(), + initial_value: None, + information: typesystem::DataTypeInformation::String { + size: TypeSize::LiteralInteger(1024), + encoding: typesystem::StringEncoding::Utf8, + }, + + nature: TypeNature::String, + }; + let string_30 = typesystem::DataType { + name: "STRING_30".into(), + initial_value: None, + information: typesystem::DataTypeInformation::String { + size: TypeSize::LiteralInteger(30), + encoding: typesystem::StringEncoding::Utf8, + }, + nature: TypeNature::String, + }; + //The string with the bigger length is the bigger string + assert_eq!( + &string_1024, + typesystem::get_bigger_type(&string_1024, &string_30, &index) + ); + assert_eq!( + &string_1024, + typesystem::get_bigger_type(&string_30, &string_1024, &index) + ); + + //TODO : Strings with constant sizes +} + +#[test] +fn get_bigger_size_array_test_returns_first() { + // Given an initialized index + let index = visit(&CompilationUnit::default(), IdProvider::default()); + //Given two ARRAY of the same type and dimensions + let array_1024 = typesystem::DataType { + name: "ARRAY_1024".into(), + initial_value: None, + information: typesystem::DataTypeInformation::Array { + name: "ARRAY_1024".into(), + inner_type_name: "INT".into(), + dimensions: vec![Dimension { + start_offset: TypeSize::LiteralInteger(0), + end_offset: TypeSize::LiteralInteger(1023), + }], + }, + nature: TypeNature::Any, + }; + let array_30 = typesystem::DataType { + name: "ARRAY_30".into(), + initial_value: None, + information: typesystem::DataTypeInformation::Array { + name: "ARRAY_30".into(), + inner_type_name: "INT".into(), + dimensions: vec![Dimension { + start_offset: TypeSize::LiteralInteger(1), + end_offset: TypeSize::LiteralInteger(30), + }], + }, + nature: TypeNature::Any, + }; + //The array with the most elements is bigger + assert_eq!( + &array_1024, + typesystem::get_bigger_type(&array_1024, &array_30, &index) + ); + assert_eq!( + &array_30, + typesystem::get_bigger_type(&array_30, &array_1024, &index) + ); +} + +#[test] +fn get_bigger_size_mixed_test_no_() { + // Given an initialized index + let index = visit(&CompilationUnit::default(), IdProvider::default()); + //Int + let int_type = index.get_type_or_panic(INT_TYPE); + //String + let string_1024 = typesystem::DataType { + name: "STRING_1024".into(), + initial_value: None, + information: typesystem::DataTypeInformation::String { + size: TypeSize::LiteralInteger(1024), + encoding: typesystem::StringEncoding::Utf8, + }, + nature: TypeNature::String, + }; + let wstring_1024 = typesystem::DataType { + name: "WSTRING_1024".into(), + initial_value: None, + information: typesystem::DataTypeInformation::String { + size: TypeSize::LiteralInteger(1024), + encoding: typesystem::StringEncoding::Utf16, + }, + nature: TypeNature::String, + }; + //Array of string + let array_string_30 = typesystem::DataType { + name: "ARRAY_STRING_30".into(), + initial_value: None, + information: typesystem::DataTypeInformation::Array { + name: "ARRAY_STRING_30".into(), + inner_type_name: "STRING".into(), + dimensions: vec![Dimension { + start_offset: TypeSize::LiteralInteger(1), + end_offset: TypeSize::LiteralInteger(30), + }], + }, + nature: TypeNature::Any, + }; + //Array of int + let array_30 = typesystem::DataType { + name: "ARRAY_30".into(), + initial_value: None, + information: typesystem::DataTypeInformation::Array { + name: "ARRAY_30".into(), + inner_type_name: "INT".into(), + dimensions: vec![Dimension { + start_offset: TypeSize::LiteralInteger(1), + end_offset: TypeSize::LiteralInteger(30), + }], + }, + nature: TypeNature::Any, + }; + //2-dim array of int + let array_30_30 = typesystem::DataType { + name: "ARRAY_30_30".into(), + initial_value: None, + information: typesystem::DataTypeInformation::Array { + name: "ARRAY_30_30".into(), + inner_type_name: "INT".into(), + dimensions: vec![ + Dimension { + start_offset: TypeSize::LiteralInteger(1), + end_offset: TypeSize::LiteralInteger(30), + }, + Dimension { + start_offset: TypeSize::LiteralInteger(1), + end_offset: TypeSize::LiteralInteger(30), + }, + ], + }, + nature: TypeNature::Any, + }; + + //Given two incompatible types + //The first given type is returned + assert_eq!( + &array_30, + typesystem::get_bigger_type(&array_30, &array_30_30, &index) + ); + assert_eq!( + &string_1024, + typesystem::get_bigger_type(&string_1024, &array_30, &index) + ); + assert_eq!( + &string_1024, + typesystem::get_bigger_type(&string_1024, &wstring_1024, &index) + ); + assert_eq!( + &wstring_1024, + typesystem::get_bigger_type(&wstring_1024, &string_1024, &index) + ); + assert_eq!( + &array_string_30, + typesystem::get_bigger_type(&array_string_30, &array_30, &index) + ); + assert_eq!( + int_type, + typesystem::get_bigger_type(int_type, &array_30, &index) + ); +} + +fn get_index() -> Index { + let mut index = Index::default(); + for t in typesystem::get_builtin_types() { + index.register_type(t) + } + index +} + +#[test] +fn any_signed_type_test() { + let index = get_index(); + let sint = index.get_type_or_panic(SINT_TYPE); + let int = index.get_type_or_panic(INT_TYPE); + let dint = index.get_type_or_panic(DINT_TYPE); + let lint = index.get_type_or_panic(LINT_TYPE); + + assert!(sint.has_nature(TypeNature::Signed, &index)); + assert!(int.has_nature(TypeNature::Signed, &index)); + assert!(dint.has_nature(TypeNature::Signed, &index)); + assert!(lint.has_nature(TypeNature::Signed, &index)); + + assert!(sint.has_nature(TypeNature::Int, &index)); + assert!(int.has_nature(TypeNature::Int, &index)); + assert!(dint.has_nature(TypeNature::Int, &index)); + assert!(lint.has_nature(TypeNature::Int, &index)); + + assert!(sint.has_nature(TypeNature::Num, &index)); + assert!(int.has_nature(TypeNature::Num, &index)); + assert!(dint.has_nature(TypeNature::Num, &index)); + assert!(lint.has_nature(TypeNature::Num, &index)); + + assert!(sint.has_nature(TypeNature::Magnitude, &index)); + assert!(int.has_nature(TypeNature::Magnitude, &index)); + assert!(dint.has_nature(TypeNature::Magnitude, &index)); + assert!(lint.has_nature(TypeNature::Magnitude, &index)); + + assert!(sint.has_nature(TypeNature::Elementary, &index)); + assert!(int.has_nature(TypeNature::Elementary, &index)); + assert!(dint.has_nature(TypeNature::Elementary, &index)); + assert!(lint.has_nature(TypeNature::Elementary, &index)); + + assert!(sint.has_nature(TypeNature::Any, &index)); + assert!(int.has_nature(TypeNature::Any, &index)); + assert!(dint.has_nature(TypeNature::Any, &index)); + assert!(lint.has_nature(TypeNature::Any, &index)); +} + +#[test] +fn any_unsigned_type_test() { + let index = get_index(); + let usint = index.get_type_or_panic(USINT_TYPE); + let uint = index.get_type_or_panic(UINT_TYPE); + let udint = index.get_type_or_panic(UDINT_TYPE); + let ulint = index.get_type_or_panic(ULINT_TYPE); + + assert!(usint.has_nature(TypeNature::Unsigned, &index)); + assert!(uint.has_nature(TypeNature::Unsigned, &index)); + assert!(udint.has_nature(TypeNature::Unsigned, &index)); + assert!(ulint.has_nature(TypeNature::Unsigned, &index)); + + assert!(usint.has_nature(TypeNature::Int, &index)); + assert!(uint.has_nature(TypeNature::Int, &index)); + assert!(udint.has_nature(TypeNature::Int, &index)); + assert!(ulint.has_nature(TypeNature::Int, &index)); + + assert!(usint.has_nature(TypeNature::Num, &index)); + assert!(uint.has_nature(TypeNature::Num, &index)); + assert!(udint.has_nature(TypeNature::Num, &index)); + assert!(ulint.has_nature(TypeNature::Num, &index)); + + assert!(usint.has_nature(TypeNature::Magnitude, &index)); + assert!(uint.has_nature(TypeNature::Magnitude, &index)); + assert!(udint.has_nature(TypeNature::Magnitude, &index)); + assert!(ulint.has_nature(TypeNature::Magnitude, &index)); + + assert!(usint.has_nature(TypeNature::Elementary, &index)); + assert!(uint.has_nature(TypeNature::Elementary, &index)); + assert!(udint.has_nature(TypeNature::Elementary, &index)); + assert!(ulint.has_nature(TypeNature::Elementary, &index)); + + assert!(usint.has_nature(TypeNature::Any, &index)); + assert!(uint.has_nature(TypeNature::Any, &index)); + assert!(udint.has_nature(TypeNature::Any, &index)); + assert!(ulint.has_nature(TypeNature::Any, &index)); +} + +#[test] +fn any_real_type_test() { + let index = get_index(); + let real = index.get_type_or_panic(REAL_TYPE); + let lreal = index.get_type_or_panic(LREAL_TYPE); + + assert!(real.has_nature(TypeNature::Real, &index)); + assert!(lreal.has_nature(TypeNature::Real, &index)); + + assert!(real.has_nature(TypeNature::Num, &index)); + assert!(lreal.has_nature(TypeNature::Num, &index)); + + assert!(real.has_nature(TypeNature::Magnitude, &index)); + assert!(lreal.has_nature(TypeNature::Magnitude, &index)); + + assert!(real.has_nature(TypeNature::Elementary, &index)); + assert!(lreal.has_nature(TypeNature::Elementary, &index)); + + assert!(real.has_nature(TypeNature::Any, &index)); + assert!(lreal.has_nature(TypeNature::Any, &index)); +} + +#[test] +fn any_duration_type_test() { + let index = get_index(); + let time = index.get_type_or_panic(TIME_TYPE); + // let ltime = index.get_type_or_panic(LTIME_TYTE); + + assert!(time.has_nature(TypeNature::Duration, &index)); + + assert!(time.has_nature(TypeNature::Magnitude, &index)); + + assert!(time.has_nature(TypeNature::Elementary, &index)); + + assert!(time.has_nature(TypeNature::Any, &index)); +} + +#[test] +fn any_bit_type_test() { + let index = get_index(); + let bool_type = index.get_type_or_panic(BOOL_TYPE); + let byte = index.get_type_or_panic(BYTE_TYPE); + let word = index.get_type_or_panic(WORD_TYPE); + let dword = index.get_type_or_panic(DWORD_TYPE); + let lword = index.get_type_or_panic(LWORD_TYPE); + + assert!(bool_type.has_nature(TypeNature::Bit, &index)); + assert!(byte.has_nature(TypeNature::Bit, &index)); + assert!(word.has_nature(TypeNature::Bit, &index)); + assert!(dword.has_nature(TypeNature::Bit, &index)); + assert!(lword.has_nature(TypeNature::Bit, &index)); + + assert!(bool_type.has_nature(TypeNature::Elementary, &index)); + assert!(byte.has_nature(TypeNature::Elementary, &index)); + assert!(word.has_nature(TypeNature::Elementary, &index)); + assert!(dword.has_nature(TypeNature::Elementary, &index)); + assert!(lword.has_nature(TypeNature::Elementary, &index)); + + assert!(bool_type.has_nature(TypeNature::Any, &index)); + assert!(byte.has_nature(TypeNature::Any, &index)); + assert!(word.has_nature(TypeNature::Any, &index)); + assert!(dword.has_nature(TypeNature::Any, &index)); + assert!(lword.has_nature(TypeNature::Any, &index)); +} + +#[test] +fn any_string_type_test() { + let index = get_index(); + let string = index.get_type_or_panic(STRING_TYPE); + let wstring = index.get_type_or_panic(WSTRING_TYPE); + + assert!(string.has_nature(TypeNature::Chars, &index)); + assert!(wstring.has_nature(TypeNature::Chars, &index)); + + assert!(string.has_nature(TypeNature::String, &index)); + assert!(wstring.has_nature(TypeNature::String, &index)); + + assert!(string.has_nature(TypeNature::Elementary, &index)); + assert!(wstring.has_nature(TypeNature::Elementary, &index)); + + assert!(string.has_nature(TypeNature::Any, &index)); + assert!(wstring.has_nature(TypeNature::Any, &index)); +} + +#[test] +fn any_char_type_test() { + let index = get_index(); + let char = index.get_type_or_panic(CHAR_TYPE); + let wchar = index.get_type_or_panic(WCHAR_TYPE); + + assert!(char.has_nature(TypeNature::Chars, &index)); + assert!(wchar.has_nature(TypeNature::Chars, &index)); + + assert!(char.has_nature(TypeNature::Char, &index)); + assert!(wchar.has_nature(TypeNature::Char, &index)); + + assert!(char.has_nature(TypeNature::Elementary, &index)); + assert!(wchar.has_nature(TypeNature::Elementary, &index)); + + assert!(char.has_nature(TypeNature::Any, &index)); + assert!(wchar.has_nature(TypeNature::Any, &index)); +} + +#[test] +fn any_date_type_test() { + let index = get_index(); + let date = index.get_type_or_panic(DATE_TYPE); + let date_time = index.get_type_or_panic(DATE_AND_TIME_TYPE); + let tod = index.get_type_or_panic(TIME_OF_DAY_TYPE); + + assert!(date.has_nature(TypeNature::Date, &index)); + assert!(date_time.has_nature(TypeNature::Date, &index)); + assert!(tod.has_nature(TypeNature::Date, &index)); + + assert!(date.has_nature(TypeNature::Elementary, &index)); + assert!(date_time.has_nature(TypeNature::Elementary, &index)); + assert!(tod.has_nature(TypeNature::Elementary, &index)); + + assert!(date.has_nature(TypeNature::Any, &index)); + assert!(date_time.has_nature(TypeNature::Any, &index)); + assert!(tod.has_nature(TypeNature::Any, &index)); +} diff --git a/src/validation/stmt_validator.rs b/src/validation/stmt_validator.rs index 8eaabf6167..baa60391d9 100644 --- a/src/validation/stmt_validator.rs +++ b/src/validation/stmt_validator.rs @@ -205,6 +205,38 @@ impl StatementValidator { }, _ => (), } + self.validate_type_nature(statement, context); + } + + /// Validates that the assigned type and type hint are compatible with the nature for this + /// statement + fn validate_type_nature(&mut self, statement: &AstStatement, context: &ValidationContext) { + if let Some(statement_type) = context + .ast_annotation + .get_type_hint(statement, context.index) + .or_else(|| context.ast_annotation.get_type(statement, context.index)) + { + if let DataTypeInformation::Generic { + generic_symbol, + nature, + .. + } = statement_type.get_type_information() + { + self.diagnostics.push(Diagnostic::unresolved_generic_type( + generic_symbol, + &format!("{:?}", nature), + statement.get_location(), + )) + } else if let Some(nature) = context.ast_annotation.get_generic_nature(statement) { + if !statement_type.has_nature(*nature, context.index) { + self.diagnostics.push(Diagnostic::invalid_type_nature( + statement_type.get_name(), + format!("{:?}", nature).as_str(), + statement.get_location(), + )); + } + } + } } fn validate_access_index( diff --git a/src/validation/tests.rs b/src/validation/tests.rs index 0d4009e080..58d6937417 100644 --- a/src/validation/tests.rs +++ b/src/validation/tests.rs @@ -5,3 +5,5 @@ mod reference_resolve_tests; mod variable_validation_tests; mod statement_validation_tests; + +mod generic_validation_tests; diff --git a/src/validation/tests/generic_validation_tests.rs b/src/validation/tests/generic_validation_tests.rs new file mode 100644 index 0000000000..01b356cdfc --- /dev/null +++ b/src/validation/tests/generic_validation_tests.rs @@ -0,0 +1,217 @@ +use crate::{test_utils::tests::parse_and_validate, Diagnostic}; + +#[test] +fn any_allows_all_natures() { + let src = r" + TYPE str STRUCT x : INT; END_STRUCT END_TYPE + FUNCTION test : INT VAR_INPUT x : T; END_VAR END_FUNCTION + FUNCTION func : INT VAR x : INT; END_VAR test(x); END_FUNCTION + FUNCTION func2 : INT VAR x : UINT; END_VAR test(x); END_FUNCTION + FUNCTION func3 : INT VAR x : BYTE; END_VAR test(x); END_FUNCTION + FUNCTION func4 : INT VAR x : REAL; END_VAR test(x); END_FUNCTION + FUNCTION func5 : INT VAR x : STRING; END_VAR test(x); END_FUNCTION + FUNCTION func6 : INT VAR x : WSTRING; END_VAR test(x); END_FUNCTION + FUNCTION func7 : INT VAR x : str; END_VAR test(x); END_FUNCTION + "; + + let diagnostics = parse_and_validate(src); + assert_eq!(diagnostics, vec![]); +} + +#[test] +fn any_number_allows_ints_reals_bits() { + let src = r" + FUNCTION test : INT VAR_INPUT x : T; END_VAR END_FUNCTION + FUNCTION func : INT VAR x : INT; END_VAR test(x); END_FUNCTION + FUNCTION func1 : INT VAR x : UINT; END_VAR test(x); END_FUNCTION + FUNCTION func2 : INT VAR x : REAL; END_VAR test(x); END_FUNCTION + FUNCTION func3 : INT VAR x : BYTE; END_VAR test(x); END_FUNCTION + "; + + let diagnostics = parse_and_validate(src); + assert!(diagnostics.is_empty()); +} + +#[test] +fn any_number_does_not_allow_string() { + let src = r" + FUNCTION test : INT VAR_INPUT x : T; END_VAR END_FUNCTION + FUNCTION func : INT VAR x : STRING; END_VAR test(x); END_FUNCTION + FUNCTION func1 : INT VAR x : WSTRING; END_VAR test(x); END_FUNCTION + "; + + let diagnostics = parse_and_validate(src); + assert_eq!( + diagnostics, + vec![ + Diagnostic::invalid_type_nature("STRING", "Num", (138..139).into()), + Diagnostic::invalid_type_nature("WSTRING", "Num", (215..216).into()), + ] + ); +} + +#[test] +fn any_int_allow_int_signed_unsigned_bit() { + let src = r" + FUNCTION test : INT VAR_INPUT x : T; END_VAR END_FUNCTION + FUNCTION func : INT VAR x : INT; END_VAR test(x); END_FUNCTION + FUNCTION func1 : INT VAR x : UINT; END_VAR test(x); END_FUNCTION + "; + + let diagnostics = parse_and_validate(src); + assert!(diagnostics.is_empty()); +} + +#[test] +fn any_int_does_not_allow_real() { + let src = r" + FUNCTION test : INT VAR_INPUT x : T; END_VAR END_FUNCTION + FUNCTION func : INT VAR x : REAL; END_VAR test(x); END_FUNCTION + FUNCTION func1 : INT VAR x : LREAL; END_VAR test(x); END_FUNCTION + "; + + let diagnostics = parse_and_validate(src); + assert_eq!( + diagnostics, + vec![ + Diagnostic::invalid_type_nature("REAL", "Int", (136..137).into()), + Diagnostic::invalid_type_nature("LREAL", "Int", (211..212).into()), + ] + ); +} + +#[test] +fn any_int_does_not_allow_string() { + let src = r" + FUNCTION test : INT VAR_INPUT x : T; END_VAR END_FUNCTION + FUNCTION func : INT VAR x : STRING; END_VAR test(x); END_FUNCTION + FUNCTION func1 : INT VAR x : WSTRING; END_VAR test(x); END_FUNCTION + "; + + let diagnostics = parse_and_validate(src); + assert_eq!( + diagnostics, + vec![ + Diagnostic::invalid_type_nature("STRING", "Int", (138..139).into()), + Diagnostic::invalid_type_nature("WSTRING", "Int", (215..216).into()), + ] + ); +} + +#[test] +fn any_real_allow_real_lreal() { + let src = r" + FUNCTION test : INT VAR_INPUT x : T; END_VAR END_FUNCTION + FUNCTION func : INT VAR x : REAL; END_VAR test(x); END_FUNCTION + FUNCTION func1 : INT VAR x : LREAL; END_VAR test(x); END_FUNCTION + "; + + let diagnostics = parse_and_validate(src); + assert!(diagnostics.is_empty()); +} + +#[test] +fn any_real_does_not_allow_ints() { + let src = r" + FUNCTION test : INT VAR_INPUT x : T; END_VAR END_FUNCTION + FUNCTION func : INT VAR x : INT; END_VAR test(x); END_FUNCTION + FUNCTION func1 : INT VAR x : UINT; END_VAR test(x); END_FUNCTION + FUNCTION func2 : INT VAR x : BYTE; END_VAR test(x); END_FUNCTION + "; + + let diagnostics = parse_and_validate(src); + assert_eq!( + diagnostics, + vec![ + Diagnostic::invalid_type_nature("INT", "Real", (136..137).into()), + Diagnostic::invalid_type_nature("UINT", "Real", (210..211).into()), + Diagnostic::invalid_type_nature("BYTE", "Real", (284..285).into()), + ] + ); +} + +#[test] +fn any_real_does_not_allow_string() { + let src = r" + FUNCTION test : INT VAR_INPUT x : T; END_VAR END_FUNCTION + FUNCTION func : INT VAR x : STRING; END_VAR test(x); END_FUNCTION + FUNCTION func1 : INT VAR x : WSTRING; END_VAR test(x); END_FUNCTION + "; + + let diagnostics = parse_and_validate(src); + assert_eq!( + diagnostics, + vec![ + Diagnostic::invalid_type_nature("STRING", "Real", (139..140).into()), + Diagnostic::invalid_type_nature("WSTRING", "Real", (216..217).into()), + ] + ); +} + +#[test] +fn any_string_allow_string_wstring() { + let src = r" + FUNCTION test : INT VAR_INPUT x : T; END_VAR END_FUNCTION + FUNCTION func : INT VAR x : STRING; END_VAR test(x); END_FUNCTION + FUNCTION func2 : INT VAR x : WSTRING; END_VAR test(x); END_FUNCTION + "; + + let diagnostics = parse_and_validate(src); + assert!(diagnostics.is_empty()); +} + +#[test] +fn any_string_does_not_allow_ints() { + let src = r" + FUNCTION test : INT VAR_INPUT x : T; END_VAR END_FUNCTION + FUNCTION func : INT VAR x : INT; END_VAR test(x); END_FUNCTION + FUNCTION func1 : INT VAR x : UINT; END_VAR test(x); END_FUNCTION + FUNCTION func2 : INT VAR x : BYTE; END_VAR test(x); END_FUNCTION + "; + + let diagnostics = parse_and_validate(src); + assert_eq!( + diagnostics, + vec![ + Diagnostic::invalid_type_nature("INT", "String", (138..139).into()), + Diagnostic::invalid_type_nature("UINT", "String", (212..213).into()), + Diagnostic::invalid_type_nature("BYTE", "String", (286..287).into()), + ] + ); +} + +#[test] +fn any_string_does_not_allow_real() { + let src = r" + FUNCTION test : INT VAR_INPUT x : T; END_VAR END_FUNCTION + FUNCTION func : INT VAR x : REAL; END_VAR test(x); END_FUNCTION + FUNCTION func1 : INT VAR x : LREAL; END_VAR test(x); END_FUNCTION + "; + + let diagnostics = parse_and_validate(src); + assert_eq!( + diagnostics, + vec![ + Diagnostic::invalid_type_nature("REAL", "String", (139..140).into()), + Diagnostic::invalid_type_nature("LREAL", "String", (214..215).into()), + ] + ); +} + +#[test] +fn non_resolved_generics_reported() { + let src = r" + FUNCTION test : T END_VAR END_FUNCTION + FUNCTION func : INT test(); END_FUNCTION + "; + + let diagnostics = parse_and_validate(src); + assert_eq!( + diagnostics, + vec![Diagnostic::unresolved_generic_type( + "T", + "String", + (94..101).into() + ),] + ); +} diff --git a/tests/correctness/generic_functions.rs b/tests/correctness/generic_functions.rs new file mode 100644 index 0000000000..932e422d52 --- /dev/null +++ b/tests/correctness/generic_functions.rs @@ -0,0 +1,72 @@ +use super::super::*; +use inkwell::targets::{InitializationConfig, Target}; + +#[allow(dead_code)] +#[repr(C)] +struct TimesTwoTypeInt { + val: i16, +} + +#[allow(dead_code)] +#[repr(C)] +struct TimesTwoTypeReal { + val: f32, +} + +#[allow(dead_code)] +#[repr(C)] +struct MainType { + a: i16, + b: f32, +} + +extern "C" fn times_two_int(param: &TimesTwoTypeInt) -> i16 { + param.val * 2 +} + +extern "C" fn times_two_real(param: &TimesTwoTypeReal) -> f32 { + param.val * 2.0f32 +} + +#[test] +fn test_external_function_called() { + //Given some external function. + let prog = " + @EXTERNAL FUNCTION times_two : T + VAR_INPUT + val : T; + END_VAR + END_FUNCTION + + FUNCTION main : DINT + VAR + a : INT; + b : REAL; + END_VAR + a := times_two(INT#100); + b := times_two(2.5); + END_FUNCTION + "; + + Target::initialize_native(&InitializationConfig::default()).unwrap(); + let context: Context = Context::create(); + let source = SourceCode { + path: "external_test.st".to_string(), + source: prog.to_string(), + }; + let code_gen = compile_module(&context, vec![source], None, Diagnostician::default()).unwrap(); + let exec_engine = code_gen + .module + .create_jit_execution_engine(inkwell::OptimizationLevel::None) + .unwrap(); + + let fn_value = code_gen.module.get_function("times_two__INT").unwrap(); + exec_engine.add_global_mapping(&fn_value, times_two_int as usize); + let fn_value = code_gen.module.get_function("times_two__REAL").unwrap(); + exec_engine.add_global_mapping(&fn_value, times_two_real as usize); + + let mut main_type = MainType { a: 0, b: 0.0f32 }; + let _: i32 = run(&exec_engine, "main", &mut main_type); + assert_eq!(main_type.a, 200); + assert_eq!(main_type.b, 5.0f32); +} diff --git a/tests/tests.rs b/tests/tests.rs index 0a5c640e8f..94add1737f 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -30,6 +30,7 @@ mod correctness { mod expressions; mod external_functions; mod functions; + mod generic_functions; mod global_variables; mod initial_values; mod methods;