diff --git a/.vscode/launch.json b/.vscode/launch.json index a349f4776f..3c56fccb62 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,14 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "ST Debug oo test", + "program": "${workspaceFolder}/examples/oo_test", + "args": [], + "cwd": "${workspaceFolder}" + }, { "type": "lldb", "request": "launch", @@ -20,7 +28,8 @@ } }, "args": [ - "target/demo.st" + "examples/oo_test.st", + "-g" ], "cwd": "${workspaceFolder}" }, diff --git a/compiler/plc_ast/src/ast.rs b/compiler/plc_ast/src/ast.rs index 387cc37ad7..00be06ccd4 100644 --- a/compiler/plc_ast/src/ast.rs +++ b/compiler/plc_ast/src/ast.rs @@ -37,6 +37,7 @@ pub struct Pou { pub poly_mode: Option, pub generics: Vec, pub linkage: LinkageType, + pub super_class: Option, } #[derive(Debug, PartialEq, Eq)] diff --git a/examples/oo_test b/examples/oo_test new file mode 100755 index 0000000000..3ca32284cc Binary files /dev/null and b/examples/oo_test differ diff --git a/examples/oo_test.st b/examples/oo_test.st new file mode 100644 index 0000000000..9364f3a4dd --- /dev/null +++ b/examples/oo_test.st @@ -0,0 +1,34 @@ + CLASS MyClass + VAR + x : DINT; + y : DINT; + END_VAR + END_CLASS + + PROGRAM MyProg + VAR_IN_OUT + cls : MyClass; + END_VAR + VAR_OUTPUT + x : DINT; + y : DINT; + END_VAR + x := cls.x; + cls.y := y; + END_PROGRAM + + FUNCTION main : DINT + VAR_TEMP + cls : MyClass; + END_VAR + VAR + x : DINT; + y : DINT; + END_VAR + cls.x := 2; + MyProg.y := 3; + MyProg(cls); + x := MyProg.x; + y := cls.y; + main := x + y; + END_FUNCTION \ No newline at end of file diff --git a/oo_test.st b/oo_test.st new file mode 100755 index 0000000000..d0066ac5b8 Binary files /dev/null and b/oo_test.st differ diff --git a/src/codegen/generators/data_type_generator.rs b/src/codegen/generators/data_type_generator.rs index 5f8d134a7e..2728b624cc 100644 --- a/src/codegen/generators/data_type_generator.rs +++ b/src/codegen/generators/data_type_generator.rs @@ -13,6 +13,7 @@ use crate::{ typesystem::DataType, }; use indexmap::IndexSet; +use inkwell::types::{AnyType, AnyTypeEnum}; use inkwell::{ types::{BasicType, BasicTypeEnum}, values::{BasicValue, BasicValueEnum}, @@ -177,7 +178,20 @@ impl<'ink, 'b> DataTypeGenerator<'ink, 'b> { let members = members .iter() .filter(|it| !it.is_temp() && !it.is_return()) - .map(|m| self.types_index.get_associated_type(m.get_type_name())) + .map(|m| { + //Generate fat pointer if needed + let type_name = self + .index + .find_effective_type_by_name(m.get_type_name()) + .and_then(|it| it.get_type_information().get_inner_pointer_type()) + .map(|it| format!("__fat_pointer_to_{}", it)) + .filter(|it| { + m.get_declaration_type().is_by_ref() && self.index.find_type(it).is_some() + }) + .unwrap_or_else(|| m.get_type_name().to_string()); + + self.types_index.get_associated_type(&type_name) + }) .collect::, Diagnostic>>()?; let struct_type = match source { @@ -215,9 +229,16 @@ impl<'ink, 'b> DataTypeGenerator<'ink, 'b> { if dimensions.iter().any(|dimension| dimension.is_undetermined()) { self.create_type(inner_type_name, self.index.get_type(inner_type_name)?) } else { - self.index - .get_effective_type_by_name(inner_type_name) - .and_then(|inner_type| self.create_type(inner_type_name, inner_type)) + let inner_type = if inner_type_name == "__FUNCTION_POINTER__" { + Ok(self.llvm.context.i32_type().fn_type(&[], false).as_any_type_enum()) + } else { + self.index + .get_effective_type_by_name(inner_type_name) + .and_then(|inner_type| self.create_type(inner_type_name, inner_type)) + .map(|inner_type| inner_type.as_any_type_enum()) + }; + + inner_type .and_then(|inner_type| self.create_nested_array_type(inner_type, dimensions)) .map(|it| it.as_basic_type_enum()) } @@ -422,7 +443,7 @@ impl<'ink, 'b> DataTypeGenerator<'ink, 'b> { /// `arr: ARRAY[0..3] OF INT`. fn create_nested_array_type( &self, - inner_type: BasicTypeEnum<'ink>, + inner_type: AnyTypeEnum<'ink>, dimensions: &[Dimension], ) -> Result, Diagnostic> { let len = dimensions @@ -438,12 +459,14 @@ impl<'ink, 'b> DataTypeGenerator<'ink, 'b> { .ok_or_else(|| Diagnostic::codegen_error("Invalid array dimensions", SourceRange::undefined()))?; let result = match inner_type { - BasicTypeEnum::IntType(ty) => ty.array_type(len), - BasicTypeEnum::FloatType(ty) => ty.array_type(len), - BasicTypeEnum::StructType(ty) => ty.array_type(len), - BasicTypeEnum::ArrayType(ty) => ty.array_type(len), - BasicTypeEnum::PointerType(ty) => ty.array_type(len), - BasicTypeEnum::VectorType(ty) => ty.array_type(len), + AnyTypeEnum::IntType(ty) => ty.array_type(len), + AnyTypeEnum::FloatType(ty) => ty.array_type(len), + AnyTypeEnum::StructType(ty) => ty.array_type(len), + AnyTypeEnum::ArrayType(ty) => ty.array_type(len), + AnyTypeEnum::PointerType(ty) => ty.array_type(len), + AnyTypeEnum::VectorType(ty) => ty.array_type(len), + AnyTypeEnum::FunctionType(ty) => ty.ptr_type(AddressSpace::default()).array_type(len), + AnyTypeEnum::VoidType(_) => unimplemented!() } .as_basic_type_enum(); diff --git a/src/codegen/generators/expression_generator.rs b/src/codegen/generators/expression_generator.rs index f755b0ff5b..06e70d3038 100644 --- a/src/codegen/generators/expression_generator.rs +++ b/src/codegen/generators/expression_generator.rs @@ -21,7 +21,7 @@ use inkwell::{ types::{BasicType, BasicTypeEnum}, values::{ ArrayValue, BasicMetadataValueEnum, BasicValue, BasicValueEnum, FloatValue, IntValue, PointerValue, - StructValue, VectorValue, + StructValue, VectorValue, CallableValue, CallSiteValue, }, AddressSpace, FloatPredicate, IntPredicate, }; @@ -502,15 +502,62 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { self.get_function_context(operator)?, )?; - let function = self - .llvm_index - .find_associated_implementation(implementation_name) // using the non error option to control the output error - .ok_or_else(|| { - Diagnostic::codegen_error( - &format!("No callable implementation associated to {implementation_name:?}"), - operator.get_location(), - ) - })?; + //TODO should check if it is class or FunctionBlock + let function: CallableValue<'_> = if pou.is_method() { + let element = if let AstStatement::QualifiedReference { elements, .. } = operator { + elements.first() + } else { + None + }; + + let StatementAnnotation::Variable { qualified_name, .. } = self.annotations.get(element.unwrap()).unwrap() else { + unimplemented!(); + }; + + //let ptr_to_fp = self.generate_element_pointer(operator)?; + let ptr_to_fp = self.llvm_index.find_loaded_associated_variable_value(qualified_name).unwrap(); + + let vt_arr = + self.llvm.get_member_pointer_from_struct( + ptr_to_fp, + 1, + "name", + &operator.get_location())?; + + let all = self.index.get_pous(); + let mut count = 0; + // populate the array + for (_key, value) in all.elements() { + if value.is_method() && value.get_container().eq(pou.get_container()) { + count += 1; + if _key.eq(&implementation_name.to_lowercase()) { + break; + } + } + } + + + let res = self.llvm.load_array_element( + vt_arr, + &[ + self.llvm.context.i64_type().const_zero(), + self.llvm.context.i64_type().const_int(count-1, true)], + "", + )?; + let res = self.llvm.load_pointer(&res, "").into_pointer_value(); + res.try_into().map_err(|_| Diagnostic::codegen_error("Cannot cast pointer to function", SourceRange::undefined()))? + } else { + let res = self + .llvm_index + .find_associated_implementation(implementation_name) // using the non error option to control the output error + .ok_or_else(|| { + Diagnostic::codegen_error( + &format!("No callable implementation associated to {implementation_name:?}"), + operator.get_location(), + ) + })?; + res.into() + }; // generate the debug statetment for a call self.register_debug_location(operator); @@ -534,7 +581,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { // if the target is a function, declare the struct locally // assign all parameters into the struct values - let call = &self.llvm.builder.build_call(function, &arguments_list, "call"); + let call: CallSiteValue<'_> = self.llvm.builder.build_call(function, &arguments_list, "call"); // so grab either: // - the out-pointer if we generated one in by_ref_func_out @@ -562,7 +609,6 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { }; self.assign_output_values(parameter_struct, implementation_name, parameters_list)? } - value } @@ -876,6 +922,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { return Ok(ptr_value.into()); } + //TODO need to do cast_if_needed() // Generate the element pointer, then... let value = self.generate_element_pointer(argument).or_else::(|_| { // Passed a literal to a byref parameter? @@ -906,6 +953,16 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { )); }; + if target_type_info.is_fat_pointer() { + return Ok(cast_if_needed!( + self, + actual_type, + hint, + value.into(), + self.annotations.get(argument) + )); + }; + // From https://llvm.org/docs/LangRef.html#bitcast-to-instruction: The ‘bitcast’ instruction takes // a value to cast, which must be a **non-aggregate** first class value [...] if !actual_type_info.is_aggregate() && actual_type_info != target_type_info { @@ -1179,7 +1236,9 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { let parameter = self .index .find_parameter(function_name, index) - .and_then(|var| self.index.find_effective_type_by_name(var.get_type_name())) + .and_then(|var: &VariableIndexEntry| { + self.index.find_effective_type_by_name(var.get_type_name()) + }) .map(|var| var.get_type_information()) .unwrap_or_else(|| self.index.get_void_type().get_type_information()); @@ -1194,7 +1253,29 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { })?; builder.build_alloca(temp_type, "empty_varinout").as_basic_value_enum() } else { - self.generate_element_pointer(expression)?.as_basic_value_enum() + //Either Generate a pointer or return the fat pointer by + //Geting expression + //Perform Casting + let value = self.generate_element_pointer(expression)?.as_basic_value_enum(); + + let target_type = self + .annotations + .get_type_hint(param_context.assignment_statement, self.index) + .unwrap(); + let original_type = + self.annotations.get_type_or_void(param_context.assignment_statement, self.index); + + if target_type.is_fat_pointer() { + cast_if_needed!( + self, + original_type, + target_type, + value, + self.annotations.get(param_context.assignment_statement) + ) + } else { + value + } }; builder.build_store(pointer_to_param, generated_exp); } else { @@ -1274,6 +1355,49 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { }) } + fn do_generate_element_pointer_without_deref( + &self, + qualifier: Option>, + reference_statement: &AstStatement, + ) -> Result, Diagnostic> { + match reference_statement { + AstStatement::Reference { name, .. } => self.create_llvm_pointer_value_for_reference( + qualifier.as_ref(), + name.as_str(), + reference_statement, + ), + AstStatement::ArrayAccess { reference, access, .. } => { + let Some(dt) = self.annotations.get_type(reference, self.index) else { + // XXX: will be reachable until we abort codegen on critical errors (e.g. unresolved references) + unreachable!("unresolved reference") + }; + + if dt.get_type_information().is_vla() { + self.generate_element_pointer_for_vla(reference, access) + .map_err(|_| unreachable!("invalid access statement")) + } else { + self.generate_element_pointer_for_array(qualifier.as_ref(), reference, access) + } + } + AstStatement::PointerAccess { reference, .. } => { + self.do_generate_element_pointer(qualifier, reference).map(|it| self.deref(it)) + } + AstStatement::Literal { kind: AstLiteral::String(sv), .. } => if sv.is_wide() { + self.llvm_index.find_utf16_literal_string(sv.value()) + } else { + self.llvm_index.find_utf08_literal_string(sv.value()) + } + .map(|it| it.as_pointer_value()) + .ok_or_else(|| unreachable!("All string literals have to be constants")), + _ => Err(Diagnostic::codegen_error( + &format!("Cannot generate a LValue for {reference_statement:?}"), + reference_statement.get_location(), + )), + } + } + + + fn do_generate_element_pointer( &self, qualifier: Option>, @@ -1313,7 +1437,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { reference_statement.get_location(), )), } - .map(|it| self.auto_deref_if_necessary(it, reference_statement)) + .and_then(|it| self.auto_deref_if_necessary(it, reference_statement)) } /// geneartes a gep for the given reference with an optional qualifier @@ -1321,6 +1445,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { /// - `qualifier` an optional qualifier for a reference (e.g. myStruct.x where myStruct is the qualifier for x) /// - `name` the name of the reference-name (e.g. myStruct.x where 'x' is the reference-name) /// - `context` the statement to obtain the location from when returning an error + /// fn create_llvm_pointer_value_for_reference( &self, qualifier: Option<&PointerValue<'ink>>, @@ -1338,18 +1463,23 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { return Ok(qualifier.to_owned()); } } - Some(StatementAnnotation::Variable { qualified_name, .. }) => { + Some(StatementAnnotation::Variable { qualified_name, accessing_type, .. }) => { + let mut qualifier = *qualifier; + if let Some(accessing_type) = accessing_type { + let base_class_hop = self.find_base_class(1, accessing_type.as_str(), qualified_name); + for _ in 0..base_class_hop { + qualifier = self.llvm.get_member_pointer_from_struct(qualifier, 0, "", offset)? + } + } + let member_location = self .index .find_fully_qualified_variable(qualified_name) .map(VariableIndexEntry::get_location_in_parent) .ok_or_else(|| Diagnostic::unresolved_reference(qualified_name, offset.clone()))?; - let gep = self.llvm.get_member_pointer_from_struct( - *qualifier, - member_location, - name, - offset, - )?; + + let gep = + self.llvm.get_member_pointer_from_struct(qualifier, member_location, name, offset)?; return Ok(gep); } @@ -1379,6 +1509,21 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { self.llvm.load_pointer(&accessor_ptr, "deref").into_pointer_value() } + fn find_base_class(&self, count: u32, accessing_type: &str, qualified_name: &str) -> u32 { + let Some(qualified_class) = qualified_name.split('.').next() else { + return 0; + }; + if let Some(super_class) = self.index.find_pou(accessing_type).and_then(|it| it.get_super_class()) { + if super_class.eq(qualified_class) { + count + } else { + self.find_base_class(count + 1, super_class, qualified_name) + } + } else { + 0 + } + } + pub fn ptr_as_value(&self, ptr: PointerValue<'ink>) -> BasicValueEnum<'ink> { let int_type = self.llvm.context.i64_type(); if ptr.is_const() { @@ -1406,13 +1551,20 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { &self, accessor_ptr: PointerValue<'ink>, statement: &AstStatement, - ) -> PointerValue<'ink> { - if let Some(StatementAnnotation::Variable { is_auto_deref: true, .. }) = - self.annotations.get(statement) - { - self.deref(accessor_ptr) - } else { - accessor_ptr + ) -> Result, Diagnostic> { + match self.annotations.get(statement) { + Some(variable) if variable.is_fat_pointer(self.index) => { + //Create a gep to first struct element + let ptr = self.llvm.get_member_pointer_from_struct( + accessor_ptr, + 0, + "load_fp_member", + &statement.get_location(), + )?; + Ok(self.deref(ptr)) + } + Some(StatementAnnotation::Variable { is_auto_deref: true, .. }) => Ok(self.deref(accessor_ptr)), + _ => Ok(accessor_ptr), } } @@ -2435,7 +2587,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { &self, reference: &AstStatement, access: &AstStatement, - ) -> Result, ()> { + ) -> Result, Diagnostic> { let builder = &self.llvm.builder; // array access is either directly on a reference or on another array access (ARRAY OF ARRAY) @@ -2451,13 +2603,20 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { // if the vla parameter is by-ref, we need to dereference the pointer let struct_ptr = self.auto_deref_if_necessary( - self.llvm_index.find_loaded_associated_variable_value(qualified_name).ok_or(())?, + self.llvm_index.find_loaded_associated_variable_value(qualified_name).ok_or_else(|| { + Diagnostic::codegen_error( + &format!("Could not find associated value for {qualified_name}"), + reference.get_location(), + ) + })?, reference, - ); + )?; // GEPs into the VLA struct, getting an LValue for the array pointer and the dimension array and // dereferences the former - let arr_ptr_gep = self.llvm.builder.build_struct_gep(struct_ptr, 0, "vla_arr_gep")?; + let arr_ptr_gep = self.llvm.builder.build_struct_gep(struct_ptr, 0, "vla_arr_gep").map_err(|_| { + Diagnostic::codegen_error("Error while accessing vla struct", reference.get_location()) + })?; let vla_arr_ptr = builder.build_load(arr_ptr_gep, "vla_arr_ptr").into_pointer_value(); // get pointer to array containing dimension information let dim_arr_gep = builder.build_struct_gep(struct_ptr, 1, "dim_arr").unwrap(); @@ -2480,7 +2639,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { let Some(stmt) = access_statements.get(0) else { unreachable!("Must have exactly 1 access statement") }; - let access_value = self.generate_expression(stmt).map_err(|_| ())?; + let access_value = self.generate_expression(stmt)?; // if start offset is not 0, adjust the access value accordingly let Some(start_offset) = index_offsets.get(0).map(|(start, _)| *start) else { diff --git a/src/codegen/generators/pou_generator.rs b/src/codegen/generators/pou_generator.rs index acc8e41511..5b2f19ab7c 100644 --- a/src/codegen/generators/pou_generator.rs +++ b/src/codegen/generators/pou_generator.rs @@ -37,6 +37,7 @@ use inkwell::{ values::PointerValue, }; use plc_ast::ast::{AstStatement, Implementation, NewLines, PouType, SourceRange}; +use plc_util::convention::qualified_name; pub struct PouGenerator<'ink, 'cg> { llvm: Llvm<'ink>, @@ -102,13 +103,15 @@ pub fn generate_global_constants_for_pou_members<'ink>( }); for implementation in implementations { let type_name = implementation.get_type_name(); - let pou_members = index.get_pou_members(type_name); - let variables = pou_members.iter().filter(|it| it.is_local() || it.is_temp()).filter(|it| { + let pou_members = index.get_all_pou_members_recursively(type_name); + let local_pou_members = pou_members.get(type_name).unwrap(); + let variables = local_pou_members.iter().filter(|it| it.is_local() || it.is_temp()).filter(|it| { let var_type = index.get_effective_type_or_void_by_name(it.get_type_name()).get_type_information(); var_type.is_struct() || var_type.is_array() || var_type.is_string() }); let exp_gen = ExpressionCodeGenerator::new_context_free(llvm, index, annotations, llvm_index); + for variable in variables { let name = index::get_initializer_name(variable.get_qualified_name()); let right_stmt = @@ -163,9 +166,9 @@ impl<'ink, 'cg> PouGenerator<'ink, 'cg> { .enumerate() .map(|(i, p)| match declared_parameters.get(i) { Some(v) - if v.is_in_parameter_by_ref() && - // parameters by ref will always be a pointer - p.into_pointer_type().get_element_type().is_array_type() => + if v.is_in_parameter_by_ref() + && p.is_pointer_type() + && p.into_pointer_type().get_element_type().is_array_type() => { // for array types we will generate a pointer to the arrays element type // not a pointer to array @@ -265,10 +268,22 @@ impl<'ink, 'cg> PouGenerator<'ink, 'cg> { } else { let declared_params = self.index.get_declared_parameters(implementation.get_call_name()); - //find the function's parameters declared_params .iter() - .map(|v| self.llvm_index.get_associated_type(v.get_type_name()).map(Into::into)) + .map(|v| { + //find the function's parameters + let type_name = self + .index + .find_effective_type_by_name(v.get_type_name()) + .and_then(|it| it.get_type_information().get_inner_pointer_type()) + .map(|it| format!("__fat_pointer_to_{}", it)) + .filter(|it| { + v.get_declaration_type().is_by_ref() && self.index.find_type(it).is_some() + }) + .unwrap_or_else(|| v.get_type_name().to_string()); + + self.llvm_index.get_associated_type(&type_name).map(Into::into) + }) .collect::, _>>() } } @@ -356,19 +371,25 @@ impl<'ink, 'cg> PouGenerator<'ink, 'cg> { )?; } { - let pou_members = - self.index.get_pou_members(&implementation.type_name).iter().collect::>(); + let pou_members = self.index.get_all_pou_members_recursively(&implementation.type_name); + let local_pou_members = pou_members.get(implementation.type_name.as_str()).unwrap(); //if this is a function, we need to initilialize the VAR-variables if matches!(implementation.pou_type, PouType::Function | PouType::Method { .. }) { self.generate_initialization_of_local_vars( - &pou_members, + local_pou_members, &local_index, &function_context, debug, )?; } else { //Generate temp variables - let members = pou_members.into_iter().filter(|it| it.is_temp()).collect::>(); + let members = local_pou_members + .iter() + .filter(|it| it.is_temp()) + .collect::>() + .iter() + .map(|&&it| it) + .collect::>(); self.generate_initialization_of_local_vars(&members, &local_index, &function_context, debug)?; } let statement_gen = StatementCodeGenerator::new( @@ -440,12 +461,13 @@ impl<'ink, 'cg> PouGenerator<'ink, 'cg> { function_context: &FunctionContext<'ink, '_>, debug: &DebugBuilderEnum<'ink>, ) -> Result<(), Diagnostic> { - let members = self.index.get_pou_members(type_name); - //Generate reference to parameter + let members = self.index.get_all_pou_members_recursively(type_name); + let local_members = members.get(type_name).unwrap(); + // Generate reference to parameter // cannot use index from members because return and temp variables may not be considered for index in build_struct_gep // eagerly handle the return-variable let mut params_iter = function_context.function.get_param_iter(); - if let Some(ret_v) = members.iter().find(|it| it.is_return()) { + if let Some(ret_v) = local_members.iter().find(|it| it.is_return()) { let return_type = index.get_associated_type(ret_v.get_type_name())?; let return_variable = if self .index @@ -474,7 +496,7 @@ impl<'ink, 'cg> PouGenerator<'ink, 'cg> { } // handle all parameters (without return!) - for m in members.iter().filter(|it| !it.is_return()) { + for m in local_members.iter().filter(|it| !it.is_return()) { let parameter_name = m.get_name(); let (name, variable) = if m.is_parameter() { @@ -482,9 +504,16 @@ impl<'ink, 'cg> PouGenerator<'ink, 'cg> { .next() .ok_or_else(|| Diagnostic::missing_function(m.source_location.source_range.clone()))?; - let ptr = self - .llvm - .create_local_variable(m.get_name(), &index.get_associated_type(m.get_type_name())?); + let type_name = self + .index + .find_effective_type_by_name(m.get_type_name()) + .and_then(|it| it.get_type_information().get_inner_pointer_type()) + .map(|it| format!("__fat_pointer_to_{}", it)) + .filter(|it| m.get_declaration_type().is_by_ref() && self.index.find_type(it).is_some()) + .unwrap_or_else(|| m.get_type_name().to_string()); + + let ptr = + self.llvm.create_local_variable(m.get_name(), &index.get_associated_type(&type_name)?); if let Some(block) = self.llvm.builder.get_insert_block() { debug.add_variable_declaration( @@ -510,7 +539,6 @@ impl<'ink, 'cg> PouGenerator<'ink, 'cg> { index.associate_loaded_local_variable(type_name, name, variable)?; } - Ok(()) } @@ -525,8 +553,8 @@ impl<'ink, 'cg> PouGenerator<'ink, 'cg> { location: &SourceRange, debug: &DebugBuilderEnum<'ink>, ) -> Result<(), Diagnostic> { - let members = self.index.get_pou_members(type_name); - let param_pointer = function_context + let members = self.index.get_all_pou_members_recursively(type_name); + let mut param_pointer = function_context .function .get_nth_param(arg_index) .map(BasicValueEnum::into_pointer_value) @@ -546,28 +574,32 @@ impl<'ink, 'cg> PouGenerator<'ink, 'cg> { } //Generate reference to parameter // cannot use index from members because return and temp variables may not be considered for index in build_struct_gep - let mut var_count = 0; - for m in members.iter() { - let parameter_name = m.get_name(); + for (type_name, pou_members) in members.iter() { + let mut var_count = 0; - let (name, variable) = if m.is_temp() || m.is_return() { - let temp_type = index.get_associated_type(m.get_type_name())?; - (parameter_name, self.llvm.create_local_variable(parameter_name, &temp_type)) - } else { - let ptr = self - .llvm - .builder - .build_struct_gep(param_pointer, var_count as u32, parameter_name) - .expect(INTERNAL_LLVM_ERROR); + for m in pou_members.iter() { + let parameter_name = m.get_name(); - var_count += 1; + let (name, variable) = if m.is_temp() || m.is_return() { + let temp_type = index.get_associated_type(m.get_type_name())?; + (parameter_name, self.llvm.create_local_variable(parameter_name, &temp_type)) + } else { + let ptr = self + .llvm + .builder + .build_struct_gep(param_pointer, var_count as u32, parameter_name) + .expect(INTERNAL_LLVM_ERROR); - (parameter_name, ptr) - }; + var_count += 1; - index.associate_loaded_local_variable(type_name, name, variable)?; - } + (parameter_name, ptr) + }; + index.associate_loaded_local_variable(type_name, name, variable)?; + } + let name = qualified_name(type_name.as_str(), "__super__"); + param_pointer = index.find_loaded_associated_variable_value(&name).unwrap_or(param_pointer); + } Ok(()) } diff --git a/src/codegen/llvm_typesystem.rs b/src/codegen/llvm_typesystem.rs index 6ff51620be..f48edc4dea 100644 --- a/src/codegen/llvm_typesystem.rs +++ b/src/codegen/llvm_typesystem.rs @@ -2,8 +2,9 @@ use inkwell::{ context::Context, types::{FloatType, IntType}, - values::{ArrayValue, BasicValueEnum, FloatValue, IntValue, PointerValue}, + values::{ArrayValue, BasicValueEnum, FloatValue, IntValue, PointerValue, StructValue}, AddressSpace, }; +use plc_ast::ast::PouType; use crate::{ index::Index, @@ -144,11 +145,78 @@ impl<'ctx, 'cast> Castable<'ctx, 'cast> for BasicValueEnum<'ctx> { BasicValueEnum::FloatValue(val) => val.cast(cast_data), BasicValueEnum::PointerValue(val) => val.cast(cast_data), BasicValueEnum::ArrayValue(val) => val.cast(cast_data), + BasicValueEnum::StructValue(val) => val.cast(cast_data), _ => self, } } } +impl<'ctx, 'cast> Castable<'ctx, 'cast> for StructValue<'ctx> { + // generates a fat pointer struct for a Struct (Class) + fn cast(self, cast_data: &CastInstructionData<'ctx, 'cast>) -> BasicValueEnum<'ctx> { + if !cast_data.target_type.is_fat_pointer() { + return self.into(); + } + let builder = &cast_data.llvm.builder; + let zero = cast_data.llvm.i32_type().const_zero(); + + let Ok(associated_type) = cast_data + .llvm_type_index + .get_associated_type(cast_data.target_type.get_name()) else { + unreachable!("Target type of cast instruction does not exist: {}", cast_data.target_type.get_name()) + }; + + // get fat_pointer annotation from POU + let Some(StatementAnnotation::Variable {qualified_name, ..}) = cast_data.annotation else { + unreachable!("Undefined reference: {}", cast_data.value_type.get_name()) + }; + let fat_pointer = cast_data + .llvm_type_index + .find_loaded_associated_variable_value(qualified_name.as_str()) + .unwrap_or_else(|| unreachable!("passed fat_pointer must be in the llvm index")); + + // gep into the original class. result address will be stored in fat_pointer + let class_gep = + unsafe { builder.build_in_bounds_gep(fat_pointer, &[zero, zero], "outer_fat_pointer_gep") }; + + // -- Generate struct with pointer to Class and array of methods -- + let struct_type = associated_type.into_struct_type(); + let fat_pointer_struct = builder.build_alloca(struct_type, "fat_pointer_struct"); + + let Ok(fat_pointer_class_ptr) = builder.build_struct_gep(fat_pointer_struct, 0, "fat_pointer_class_gep") else { + unreachable!("Must have a valid, GEP-able fat-pointer struct at this stage") + }; + + let Ok(fat_pointer_array) = builder.build_struct_gep(fat_pointer_struct, 1, "fat_poitner_array_gep") else { + unreachable!("Must have a valid, GEP-able fat-pointer struct at this stage") + }; + + // --generate array of methods + // let arr = ty.array_type(size as u32); + // let arr_storage = self.llvm.builder.build_alloca(arr, ""); + // for (i, ele) in generated_params.iter().enumerate() { + // let ele_ptr = self.llvm.load_array_element( + // arr_storage, + // &[ + // self.llvm.context.i32_type().const_zero(), + // self.llvm.context.i32_type().const_int(i as u64, true), + // ], + // "", + // )?; + // self.llvm.builder.build_store(ele_ptr, *ele); + // } + + let arr_ptr = builder.build_array_alloca( + cast_data.llvm.context.i64_type(), + cast_data.llvm.context.i32_type().const_int(0, true), + "", + ); + builder.build_store(fat_pointer_array, arr_ptr); + builder.build_store(fat_pointer_class_ptr, class_gep); + builder.build_load(fat_pointer_struct, "") + } +} + impl<'ctx, 'cast> Castable<'ctx, 'cast> for IntValue<'ctx> { fn cast(self, cast_data: &CastInstructionData<'ctx, 'cast>) -> BasicValueEnum<'ctx> { let lsize = cast_data.target_type.get_size_in_bits(cast_data.index); @@ -242,6 +310,71 @@ impl<'ctx, 'cast> Castable<'ctx, 'cast> for PointerValue<'ctx> { cast_data.llvm.builder.build_store(struct_ptr, struct_val); struct_ptr.into() } + DataTypeInformation::Struct { source: StructSource::Pou(PouType::Class), .. } + | DataTypeInformation::Struct { source: StructSource::Pou(PouType::FunctionBlock), .. } => { + let builder = &cast_data.llvm.builder; + let zero = cast_data.llvm.i32_type().const_zero(); + + // get fat_pointer annotation from POU + let Some(StatementAnnotation::Variable {qualified_name, resulting_type, ..}) = cast_data.annotation else { + unreachable!("Undefined reference: {}", cast_data.value_type.get_name()) + }; + let fat_pointer = cast_data + .llvm_type_index + .find_loaded_associated_variable_value(qualified_name.as_str()) + .unwrap_or_else(|| unreachable!("passed fat_pointer must be in the llvm index")); + + // gep into the original class. result address will be stored in fat_pointer + let class_gep = + unsafe { builder.build_in_bounds_gep(fat_pointer, &[zero], "outer_fat_pointer_gep") }; + + let Ok(associated_type) = cast_data + .llvm_type_index + .get_associated_type(cast_data.value_type.get_name()) else { + unreachable!("Target type of cast instruction does not exist: {}", cast_data.value_type.get_name()) + }; + + // -- Generate struct with pointer to Class and array of methods -- + let struct_type = associated_type.into_struct_type(); + let fat_pointer_struct = builder.build_alloca(struct_type, "fat_pointer_struct"); + + let Ok(fat_pointer_class_ptr) = builder.build_struct_gep(fat_pointer_struct, 0, "fat_pointer_class_gep") else { + unreachable!("Must have a valid, GEP-able fat-pointer struct at this stage") + }; + + let Ok(fat_pointer_array) = builder.build_struct_gep(fat_pointer_struct, 1, "fat_poitner_array_gep") else { + unreachable!("Must have a valid, GEP-able fat-pointer struct at this stage") + }; + + // --generate array of methods + // This should be a alloca for the vt array + let all = cast_data.index.get_pous(); + + // populate the array + let mut index = 0; + for (_key, value) in all.elements() { + if value.is_method() && value.get_container().eq(resulting_type) { + let ele_ptr = cast_data.llvm.load_array_element( + fat_pointer_array, + &[cast_data.llvm.context.i64_type().const_int(index as u64, true)], + "", + ).unwrap(); + builder.build_store( + ele_ptr, + cast_data + .llvm_type_index + .find_associated_implementation(value.get_name()) + .unwrap() + .as_global_value() + .as_pointer_value(), + ); + index += 1; + } + } + + builder.build_store(fat_pointer_class_ptr, class_gep); + builder.build_load(fat_pointer_struct, "") + } _ => unreachable!("Cannot cast pointer value to {}", cast_data.target_type.get_name()), } } diff --git a/src/codegen/tests/code_gen_tests.rs b/src/codegen/tests/code_gen_tests.rs index 73ef6e5a1e..9cc1a081b8 100644 --- a/src/codegen/tests/code_gen_tests.rs +++ b/src/codegen/tests/code_gen_tests.rs @@ -3257,3 +3257,225 @@ fn array_of_struct_as_member_of_another_struct_is_initialized() { insta::assert_snapshot!(res); } + +#[test] +fn class_with_super_class() { + let res = codegen( + " + CLASS cls + VAR + x : INT; + y : INT; + END_VAR + END_CLASS + + CLASS cls2 EXTENDS cls + END_CLASS + ", + ); + + insta::assert_snapshot!(res); +} + +#[test] +fn write_to_parent_variable() { + let res = codegen( + " + CLASS cls + VAR + x : INT; + y : INT; + END_VAR + END_CLASS + + CLASS cls2 EXTENDS cls + END_CLASS + + FUNCTION_BLOCK + VAR + myClass : cls2; + END_VAR + myClass.x = 1; + END_FUNCTION_BLOCK + ", + ); + + insta::assert_snapshot!(res); +} + +#[test] +fn write_to_parent_variable_in_function() { + let res = codegen( + " + CLASS cls + VAR + x : INT; + y : INT; + END_VAR + END_CLASS + + CLASS cls2 EXTENDS cls + METHOD myMethod : DINT + x := 33; + END_METHOD + END_CLASS + + FUNCTION_BLOCK + VAR + myClass : cls2; + END_VAR + myClass.myMethod; + END_FUNCTION_BLOCK + ", + ); + + insta::assert_snapshot!(res); +} + +#[test] +fn executes_overridden_method() { + let res = codegen( + " + CLASS cls + VAR + x : INT; + y : INT; + END_VAR + METHOD myMethod : DINT + x := 10; + END_METHOD + END_CLASS + + CLASS cls2 EXTENDS cls + METHOD OVERRIDE myMethod + x := 20; + END_METHOD + END_CLASS + + FUNCTION_BLOCK + VAR + myClass : cls2; + END_VAR + myClass.myMethod(); + END_FUNCTION_BLOCK + ", + ); + + insta::assert_snapshot!(res); +} + +#[test] +//TODO as soon as methods are implemented, this test can be used +fn fat_pointer_struct_method() { + let res = codegen( + " + CLASS cls + VAR x : DINT; END_VAR + + METHOD myMethod : DINT + END_METHOD + METHOD myMethod2 : DINT + END_METHOD + END_CLASS + + FUNCTION_BLOCK myFB + VAR_IN_OUT + callClass : cls; + END_VAR + callClass.x := 2; + callClass.myMethod(); + END_FUNCTION_BLOCK + + PROGRAM main + VAR + callClass : cls; + fb : myFb; + END_VAR + fb(callClass); + END_PROGRAM + ", + ); + + insta::assert_snapshot!(res); +} + +#[test] +fn class_call_by_ref() { + let source = " + CLASS MyClass + VAR + x : INT; + y : INT; + END_VAR + END_CLASS + + PROGRAM MyProg + VAR_IN_OUT + cls : MyClass; + END_VAR + VAR_OUTPUT + x : INT; + y : INT; + END_VAR + x := cls.x; + cls.y := y; + END_PROGRAM + + PROGRAM main + VAR_TEMP + cls : MyClass; + END_VAR + VAR + x : INT; + y : INT; + END_VAR + cls.x := 2; + MyProg.y := 3; + MyProg(cls); + x := MyProg.x; + y := cls.y; + END_PROGRAM + "; + + let res = codegen(source); + insta::assert_snapshot!(res) +} + +#[test] +fn class_call_by_ref_using_func() { + let source = " + CLASS MyClass + VAR + x : INT; + y : INT; + END_VAR + END_CLASS + + FUNCTION MyFunc : DINT + VAR_IN_OUT + cls : MyClass; + END_VAR + VAR_INPUT + y : INT; + END_VAR + MyFunc := cls.x; + cls.y := y; + END_FUNCTION + + PROGRAM main + VAR_TEMP + cls : MyClass; + END_VAR + VAR + x : INT; + y : INT; + END_VAR + cls.x := 2; + x := MyFunc(cls,3); + y := cls.y; + END_PROGRAM + "; + + let res = codegen(source); + insta::assert_snapshot!(res); +} diff --git a/src/codegen/tests/initialization_test/snapshots/rusty__codegen__tests__initialization_test__pou_initializers__class_struct_initialized_in_function.snap b/src/codegen/tests/initialization_test/snapshots/rusty__codegen__tests__initialization_test__pou_initializers__class_struct_initialized_in_function.snap index 9037a62cca..b35cb42b8a 100644 --- a/src/codegen/tests/initialization_test/snapshots/rusty__codegen__tests__initialization_test__pou_initializers__class_struct_initialized_in_function.snap +++ b/src/codegen/tests/initialization_test/snapshots/rusty__codegen__tests__initialization_test__pou_initializers__class_struct_initialized_in_function.snap @@ -6,11 +6,19 @@ expression: function source_filename = "main" %fb = type { i16 } +%__fat_pointer_to_fb = type { %fb*, [0 x i64]* } %main = type { %fb } @__fb__init = unnamed_addr constant %fb { i16 9 } +@____fat_pointer_to_fb__init = unnamed_addr constant %__fat_pointer_to_fb zeroinitializer @main_instance = global %main { %fb { i16 9 } } +define void @fb(%fb* %0) { +entry: + %a = getelementptr inbounds %fb, %fb* %0, i32 0, i32 0 + ret void +} + define i32 @func(%fb %0) { entry: %func = alloca i32, align 4 diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_call_by_ref.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_call_by_ref.snap new file mode 100644 index 0000000000..e02dd74018 --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_call_by_ref.snap @@ -0,0 +1,75 @@ +--- +source: src/codegen/tests/code_gen_tests.rs +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +%MyClass = type { i16, i16 } +%MyProg = type { %__fat_pointer_to_MyClass, i16, i16 } +%__fat_pointer_to_MyClass = type { %MyClass*, [0 x i64]* } +%main = type { i16, i16 } + +@__MyClass__init = unnamed_addr constant %MyClass zeroinitializer +@MyProg_instance = global %MyProg zeroinitializer +@____fat_pointer_to_MyClass__init = unnamed_addr constant %__fat_pointer_to_MyClass zeroinitializer +@main_instance = global %main zeroinitializer + +define void @MyClass(%MyClass* %0) { +entry: + %x = getelementptr inbounds %MyClass, %MyClass* %0, i32 0, i32 0 + %y = getelementptr inbounds %MyClass, %MyClass* %0, i32 0, i32 1 + ret void +} + +define void @MyProg(%MyProg* %0) { +entry: + %cls = getelementptr inbounds %MyProg, %MyProg* %0, i32 0, i32 0 + %x = getelementptr inbounds %MyProg, %MyProg* %0, i32 0, i32 1 + %y = getelementptr inbounds %MyProg, %MyProg* %0, i32 0, i32 2 + %load_fp_member = getelementptr inbounds %__fat_pointer_to_MyClass, %__fat_pointer_to_MyClass* %cls, i32 0, i32 0 + %deref = load %MyClass*, %MyClass** %load_fp_member, align 8 + %x1 = getelementptr inbounds %MyClass, %MyClass* %deref, i32 0, i32 0 + %load_ = load i16, i16* %x1, align 2 + store i16 %load_, i16* %x, align 2 + %load_fp_member2 = getelementptr inbounds %__fat_pointer_to_MyClass, %__fat_pointer_to_MyClass* %cls, i32 0, i32 0 + %deref3 = load %MyClass*, %MyClass** %load_fp_member2, align 8 + %y4 = getelementptr inbounds %MyClass, %MyClass* %deref3, i32 0, i32 1 + %load_y = load i16, i16* %y, align 2 + store i16 %load_y, i16* %y4, align 2 + ret void +} + +define void @main(%main* %0) { +entry: + %cls = alloca %MyClass, align 8 + %x = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %y = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + %1 = bitcast %MyClass* %cls to i8* + call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %1, i8* align 1 bitcast (%MyClass* @__MyClass__init to i8*), i64 ptrtoint (%MyClass* getelementptr (%MyClass, %MyClass* null, i32 1) to i64), i1 false) + %x1 = getelementptr inbounds %MyClass, %MyClass* %cls, i32 0, i32 0 + store i16 2, i16* %x1, align 2 + store i16 3, i16* getelementptr inbounds (%MyProg, %MyProg* @MyProg_instance, i32 0, i32 2), align 2 + %outer_fat_pointer_gep = getelementptr inbounds %MyClass, %MyClass* %cls, i32 0 + %fat_pointer_struct = alloca %__fat_pointer_to_MyClass, align 8 + %fat_pointer_class_gep = getelementptr inbounds %__fat_pointer_to_MyClass, %__fat_pointer_to_MyClass* %fat_pointer_struct, i32 0, i32 0 + %fat_poitner_array_gep = getelementptr inbounds %__fat_pointer_to_MyClass, %__fat_pointer_to_MyClass* %fat_pointer_struct, i32 0, i32 1 + %2 = alloca i64, i32 0, align 8 + store i64* %2, [0 x i64]** %fat_poitner_array_gep, align 8 + store %MyClass* %outer_fat_pointer_gep, %MyClass** %fat_pointer_class_gep, align 8 + %3 = load %__fat_pointer_to_MyClass, %__fat_pointer_to_MyClass* %fat_pointer_struct, align 8 + store %__fat_pointer_to_MyClass %3, %__fat_pointer_to_MyClass* getelementptr inbounds (%MyProg, %MyProg* @MyProg_instance, i32 0, i32 0), align 8 + call void @MyProg(%MyProg* @MyProg_instance) + %load_ = load i16, i16* getelementptr inbounds (%MyProg, %MyProg* @MyProg_instance, i32 0, i32 1), align 2 + store i16 %load_, i16* %x, align 2 + %y2 = getelementptr inbounds %MyClass, %MyClass* %cls, i32 0, i32 1 + %load_3 = load i16, i16* %y2, align 2 + store i16 %load_3, i16* %y, align 2 + ret void +} + +; Function Attrs: argmemonly nofree nounwind willreturn +declare void @llvm.memcpy.p0i8.p0i8.i64(i8* noalias nocapture writeonly, i8* noalias nocapture readonly, i64, i1 immarg) #0 + +attributes #0 = { argmemonly nofree nounwind willreturn } + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_call_by_ref.snap.new b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_call_by_ref.snap.new new file mode 100644 index 0000000000..bfc92473c7 --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_call_by_ref.snap.new @@ -0,0 +1,76 @@ +--- +source: src/codegen/tests/code_gen_tests.rs +assertion_line: 3438 +expression: dbg!(res) +--- +; ModuleID = 'main' +source_filename = "main" + +%MyClass = type { i16, i16 } +%MyProg = type { %__fat_pointer_to_MyClass, i16, i16 } +%__fat_pointer_to_MyClass = type { %MyClass*, [0 x i32 ()*]* } +%main = type { i16, i16 } + +@__MyClass__init = unnamed_addr constant %MyClass zeroinitializer +@MyProg_instance = global %MyProg zeroinitializer +@____fat_pointer_to_MyClass__init = unnamed_addr constant %__fat_pointer_to_MyClass zeroinitializer +@main_instance = global %main zeroinitializer + +define void @MyClass(%MyClass* %0) { +entry: + %x = getelementptr inbounds %MyClass, %MyClass* %0, i32 0, i32 0 + %y = getelementptr inbounds %MyClass, %MyClass* %0, i32 0, i32 1 + ret void +} + +define void @MyProg(%MyProg* %0) { +entry: + %cls = getelementptr inbounds %MyProg, %MyProg* %0, i32 0, i32 0 + %x = getelementptr inbounds %MyProg, %MyProg* %0, i32 0, i32 1 + %y = getelementptr inbounds %MyProg, %MyProg* %0, i32 0, i32 2 + %load_fp_member = getelementptr inbounds %__fat_pointer_to_MyClass, %__fat_pointer_to_MyClass* %cls, i32 0, i32 0 + %deref = load %MyClass*, %MyClass** %load_fp_member, align 8 + %x1 = getelementptr inbounds %MyClass, %MyClass* %deref, i32 0, i32 0 + %load_ = load i16, i16* %x1, align 2 + store i16 %load_, i16* %x, align 2 + %load_fp_member2 = getelementptr inbounds %__fat_pointer_to_MyClass, %__fat_pointer_to_MyClass* %cls, i32 0, i32 0 + %deref3 = load %MyClass*, %MyClass** %load_fp_member2, align 8 + %y4 = getelementptr inbounds %MyClass, %MyClass* %deref3, i32 0, i32 1 + %load_y = load i16, i16* %y, align 2 + store i16 %load_y, i16* %y4, align 2 + ret void +} + +define void @main(%main* %0) { +entry: + %cls = alloca %MyClass, align 8 + %x = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %y = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + %1 = bitcast %MyClass* %cls to i8* + call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %1, i8* align 1 bitcast (%MyClass* @__MyClass__init to i8*), i64 ptrtoint (%MyClass* getelementptr (%MyClass, %MyClass* null, i32 1) to i64), i1 false) + %x1 = getelementptr inbounds %MyClass, %MyClass* %cls, i32 0, i32 0 + store i16 2, i16* %x1, align 2 + store i16 3, i16* getelementptr inbounds (%MyProg, %MyProg* @MyProg_instance, i32 0, i32 2), align 2 + %outer_fat_pointer_gep = getelementptr inbounds %MyClass, %MyClass* %cls, i32 0 + %fat_pointer_struct = alloca %__fat_pointer_to_MyClass, align 8 + %fat_pointer_class_gep = getelementptr inbounds %__fat_pointer_to_MyClass, %__fat_pointer_to_MyClass* %fat_pointer_struct, i32 0, i32 0 + %fat_poitner_array_gep = getelementptr inbounds %__fat_pointer_to_MyClass, %__fat_pointer_to_MyClass* %fat_pointer_struct, i32 0, i32 1 + %2 = alloca i64, align 8 + store i64* %2, [0 x i32 ()*]** %fat_poitner_array_gep, align 8 + store %MyClass* %outer_fat_pointer_gep, %MyClass** %fat_pointer_class_gep, align 8 + %3 = load %__fat_pointer_to_MyClass, %__fat_pointer_to_MyClass* %fat_pointer_struct, align 8 + store %__fat_pointer_to_MyClass %3, %__fat_pointer_to_MyClass* getelementptr inbounds (%MyProg, %MyProg* @MyProg_instance, i32 0, i32 0), align 8 + call void @MyProg(%MyProg* @MyProg_instance) + %load_ = load i16, i16* getelementptr inbounds (%MyProg, %MyProg* @MyProg_instance, i32 0, i32 1), align 2 + store i16 %load_, i16* %x, align 2 + %y2 = getelementptr inbounds %MyClass, %MyClass* %cls, i32 0, i32 1 + %load_3 = load i16, i16* %y2, align 2 + store i16 %load_3, i16* %y, align 2 + ret void +} + +; Function Attrs: argmemonly nofree nounwind willreturn +declare void @llvm.memcpy.p0i8.p0i8.i64(i8* noalias nocapture writeonly, i8* noalias nocapture readonly, i64, i1 immarg) #0 + +attributes #0 = { argmemonly nofree nounwind willreturn } + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_call_by_ref_using_func.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_call_by_ref_using_func.snap new file mode 100644 index 0000000000..204117aa66 --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_call_by_ref_using_func.snap @@ -0,0 +1,76 @@ +--- +source: src/codegen/tests/code_gen_tests.rs +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +%MyClass = type { i16, i16 } +%__fat_pointer_to_MyClass = type { %MyClass*, [0 x i64]* } +%main = type { i16, i16 } + +@__MyClass__init = unnamed_addr constant %MyClass zeroinitializer +@____fat_pointer_to_MyClass__init = unnamed_addr constant %__fat_pointer_to_MyClass zeroinitializer +@main_instance = global %main zeroinitializer + +define void @MyClass(%MyClass* %0) { +entry: + %x = getelementptr inbounds %MyClass, %MyClass* %0, i32 0, i32 0 + %y = getelementptr inbounds %MyClass, %MyClass* %0, i32 0, i32 1 + ret void +} + +define i32 @MyFunc(%__fat_pointer_to_MyClass %0, i16 %1) { +entry: + %MyFunc = alloca i32, align 4 + %cls = alloca %__fat_pointer_to_MyClass, align 8 + store %__fat_pointer_to_MyClass %0, %__fat_pointer_to_MyClass* %cls, align 8 + %y = alloca i16, align 2 + store i16 %1, i16* %y, align 2 + store i32 0, i32* %MyFunc, align 4 + %load_fp_member = getelementptr inbounds %__fat_pointer_to_MyClass, %__fat_pointer_to_MyClass* %cls, i32 0, i32 0 + %deref = load %MyClass*, %MyClass** %load_fp_member, align 8 + %x = getelementptr inbounds %MyClass, %MyClass* %deref, i32 0, i32 0 + %load_ = load i16, i16* %x, align 2 + %2 = sext i16 %load_ to i32 + store i32 %2, i32* %MyFunc, align 4 + %load_fp_member1 = getelementptr inbounds %__fat_pointer_to_MyClass, %__fat_pointer_to_MyClass* %cls, i32 0, i32 0 + %deref2 = load %MyClass*, %MyClass** %load_fp_member1, align 8 + %y3 = getelementptr inbounds %MyClass, %MyClass* %deref2, i32 0, i32 1 + %load_y = load i16, i16* %y, align 2 + store i16 %load_y, i16* %y3, align 2 + %MyFunc_ret = load i32, i32* %MyFunc, align 4 + ret i32 %MyFunc_ret +} + +define void @main(%main* %0) { +entry: + %cls = alloca %MyClass, align 8 + %x = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %y = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + %1 = bitcast %MyClass* %cls to i8* + call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %1, i8* align 1 bitcast (%MyClass* @__MyClass__init to i8*), i64 ptrtoint (%MyClass* getelementptr (%MyClass, %MyClass* null, i32 1) to i64), i1 false) + %x1 = getelementptr inbounds %MyClass, %MyClass* %cls, i32 0, i32 0 + store i16 2, i16* %x1, align 2 + %outer_fat_pointer_gep = getelementptr inbounds %MyClass, %MyClass* %cls, i32 0 + %fat_pointer_struct = alloca %__fat_pointer_to_MyClass, align 8 + %fat_pointer_class_gep = getelementptr inbounds %__fat_pointer_to_MyClass, %__fat_pointer_to_MyClass* %fat_pointer_struct, i32 0, i32 0 + %fat_poitner_array_gep = getelementptr inbounds %__fat_pointer_to_MyClass, %__fat_pointer_to_MyClass* %fat_pointer_struct, i32 0, i32 1 + %2 = alloca i64, i32 0, align 8 + store i64* %2, [0 x i64]** %fat_poitner_array_gep, align 8 + store %MyClass* %outer_fat_pointer_gep, %MyClass** %fat_pointer_class_gep, align 8 + %3 = load %__fat_pointer_to_MyClass, %__fat_pointer_to_MyClass* %fat_pointer_struct, align 8 + %call = call i32 @MyFunc(%__fat_pointer_to_MyClass %3, i16 3) + %4 = trunc i32 %call to i16 + store i16 %4, i16* %x, align 2 + %y2 = getelementptr inbounds %MyClass, %MyClass* %cls, i32 0, i32 1 + %load_ = load i16, i16* %y2, align 2 + store i16 %load_, i16* %y, align 2 + ret void +} + +; Function Attrs: argmemonly nofree nounwind willreturn +declare void @llvm.memcpy.p0i8.p0i8.i64(i8* noalias nocapture writeonly, i8* noalias nocapture readonly, i64, i1 immarg) #0 + +attributes #0 = { argmemonly nofree nounwind willreturn } + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_call_by_ref_using_func.snap.new b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_call_by_ref_using_func.snap.new new file mode 100644 index 0000000000..586f8c9777 --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_call_by_ref_using_func.snap.new @@ -0,0 +1,77 @@ +--- +source: src/codegen/tests/code_gen_tests.rs +assertion_line: 3477 +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +%MyClass = type { i16, i16 } +%__fat_pointer_to_MyClass = type { %MyClass*, [0 x i32 ()*]* } +%main = type { i16, i16 } + +@__MyClass__init = unnamed_addr constant %MyClass zeroinitializer +@____fat_pointer_to_MyClass__init = unnamed_addr constant %__fat_pointer_to_MyClass zeroinitializer +@main_instance = global %main zeroinitializer + +define void @MyClass(%MyClass* %0) { +entry: + %x = getelementptr inbounds %MyClass, %MyClass* %0, i32 0, i32 0 + %y = getelementptr inbounds %MyClass, %MyClass* %0, i32 0, i32 1 + ret void +} + +define i32 @MyFunc(%__fat_pointer_to_MyClass %0, i16 %1) { +entry: + %MyFunc = alloca i32, align 4 + %cls = alloca %__fat_pointer_to_MyClass, align 8 + store %__fat_pointer_to_MyClass %0, %__fat_pointer_to_MyClass* %cls, align 8 + %y = alloca i16, align 2 + store i16 %1, i16* %y, align 2 + store i32 0, i32* %MyFunc, align 4 + %load_fp_member = getelementptr inbounds %__fat_pointer_to_MyClass, %__fat_pointer_to_MyClass* %cls, i32 0, i32 0 + %deref = load %MyClass*, %MyClass** %load_fp_member, align 8 + %x = getelementptr inbounds %MyClass, %MyClass* %deref, i32 0, i32 0 + %load_ = load i16, i16* %x, align 2 + %2 = sext i16 %load_ to i32 + store i32 %2, i32* %MyFunc, align 4 + %load_fp_member1 = getelementptr inbounds %__fat_pointer_to_MyClass, %__fat_pointer_to_MyClass* %cls, i32 0, i32 0 + %deref2 = load %MyClass*, %MyClass** %load_fp_member1, align 8 + %y3 = getelementptr inbounds %MyClass, %MyClass* %deref2, i32 0, i32 1 + %load_y = load i16, i16* %y, align 2 + store i16 %load_y, i16* %y3, align 2 + %MyFunc_ret = load i32, i32* %MyFunc, align 4 + ret i32 %MyFunc_ret +} + +define void @main(%main* %0) { +entry: + %cls = alloca %MyClass, align 8 + %x = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %y = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + %1 = bitcast %MyClass* %cls to i8* + call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %1, i8* align 1 bitcast (%MyClass* @__MyClass__init to i8*), i64 ptrtoint (%MyClass* getelementptr (%MyClass, %MyClass* null, i32 1) to i64), i1 false) + %x1 = getelementptr inbounds %MyClass, %MyClass* %cls, i32 0, i32 0 + store i16 2, i16* %x1, align 2 + %outer_fat_pointer_gep = getelementptr inbounds %MyClass, %MyClass* %cls, i32 0 + %fat_pointer_struct = alloca %__fat_pointer_to_MyClass, align 8 + %fat_pointer_class_gep = getelementptr inbounds %__fat_pointer_to_MyClass, %__fat_pointer_to_MyClass* %fat_pointer_struct, i32 0, i32 0 + %fat_poitner_array_gep = getelementptr inbounds %__fat_pointer_to_MyClass, %__fat_pointer_to_MyClass* %fat_pointer_struct, i32 0, i32 1 + %2 = alloca i64, align 8 + store i64* %2, [0 x i32 ()*]** %fat_poitner_array_gep, align 8 + store %MyClass* %outer_fat_pointer_gep, %MyClass** %fat_pointer_class_gep, align 8 + %3 = load %__fat_pointer_to_MyClass, %__fat_pointer_to_MyClass* %fat_pointer_struct, align 8 + %call = call i32 @MyFunc(%__fat_pointer_to_MyClass %3, i16 3) + %4 = trunc i32 %call to i16 + store i16 %4, i16* %x, align 2 + %y2 = getelementptr inbounds %MyClass, %MyClass* %cls, i32 0, i32 1 + %load_ = load i16, i16* %y2, align 2 + store i16 %load_, i16* %y, align 2 + ret void +} + +; Function Attrs: argmemonly nofree nounwind willreturn +declare void @llvm.memcpy.p0i8.p0i8.i64(i8* noalias nocapture writeonly, i8* noalias nocapture readonly, i64, i1 immarg) #0 + +attributes #0 = { argmemonly nofree nounwind willreturn } + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_member_access_from_method.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_member_access_from_method.snap index 81242441f1..f01d2e9b3d 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_member_access_from_method.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_member_access_from_method.snap @@ -6,9 +6,18 @@ expression: result source_filename = "main" %MyClass = type { i16, i16 } +%__fat_pointer_to_MyClass = type { %MyClass*, [1 x i64]* } %MyClass.testMethod = type { i16, i16 } @__MyClass__init = unnamed_addr constant %MyClass zeroinitializer +@____fat_pointer_to_MyClass__init = unnamed_addr constant %__fat_pointer_to_MyClass zeroinitializer + +define void @MyClass(%MyClass* %0) { +entry: + %x = getelementptr inbounds %MyClass, %MyClass* %0, i32 0, i32 0 + %y = getelementptr inbounds %MyClass, %MyClass* %0, i32 0, i32 1 + ret void +} define void @MyClass.testMethod(%MyClass* %0, %MyClass.testMethod* %1) { entry: diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_method_in_pou.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_method_in_pou.snap index 54fc8fe681..0b3bd92ff6 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_method_in_pou.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_method_in_pou.snap @@ -7,10 +7,19 @@ source_filename = "main" %MyClass = type { i16, i16 } %prg = type { %MyClass, i16 } +%__fat_pointer_to_MyClass = type { %MyClass*, [1 x i64]* } %MyClass.testMethod = type { i16, i16 } @__MyClass__init = unnamed_addr constant %MyClass zeroinitializer @prg_instance = global %prg zeroinitializer +@____fat_pointer_to_MyClass__init = unnamed_addr constant %__fat_pointer_to_MyClass zeroinitializer + +define void @MyClass(%MyClass* %0) { +entry: + %x = getelementptr inbounds %MyClass, %MyClass* %0, i32 0, i32 0 + %y = getelementptr inbounds %MyClass, %MyClass* %0, i32 0, i32 1 + ret void +} define void @MyClass.testMethod(%MyClass* %0, %MyClass.testMethod* %1) { entry: diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_with_super_class.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_with_super_class.snap new file mode 100644 index 0000000000..19778617cf --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_with_super_class.snap @@ -0,0 +1,32 @@ +--- +source: src/codegen/tests/code_gen_tests.rs +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +%cls = type { i16, i16 } +%cls2 = type { %cls } +%__fat_pointer_to_cls = type { %cls*, [0 x i64]* } +%__fat_pointer_to_cls2 = type { %cls2*, [0 x i64]* } + +@__cls__init = unnamed_addr constant %cls zeroinitializer +@__cls2__init = unnamed_addr constant %cls2 zeroinitializer +@____fat_pointer_to_cls__init = unnamed_addr constant %__fat_pointer_to_cls zeroinitializer +@____fat_pointer_to_cls2__init = unnamed_addr constant %__fat_pointer_to_cls2 zeroinitializer + +define void @cls(%cls* %0) { +entry: + %x = getelementptr inbounds %cls, %cls* %0, i32 0, i32 0 + %y = getelementptr inbounds %cls, %cls* %0, i32 0, i32 1 + ret void +} + +define void @cls2(%cls2* %0) { +entry: + %__super__ = getelementptr inbounds %cls2, %cls2* %0, i32 0, i32 0 + %x = getelementptr inbounds %cls, %cls* %__super__, i32 0, i32 0 + %y = getelementptr inbounds %cls, %cls* %__super__, i32 0, i32 1 + ret void +} + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_with_super_class_test.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_with_super_class_test.snap new file mode 100644 index 0000000000..7b4d58caa8 --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__class_with_super_class_test.snap @@ -0,0 +1,39 @@ +--- +source: src/codegen/tests/code_gen_tests.rs +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +%0 = type { %cls2 } +%cls = type { i16, i16 } +%cls2 = type { %cls } + +@__cls__init = unnamed_addr constant %cls zeroinitializer +@__cls2__init = unnamed_addr constant %cls2 zeroinitializer +@____init = unnamed_addr constant %0 zeroinitializer + +define void @cls(%cls* %0) { +entry: + %x = getelementptr inbounds %cls, %cls* %0, i32 0, i32 0 + %y = getelementptr inbounds %cls, %cls* %0, i32 0, i32 1 + ret void +} + +define void @cls2(%cls2* %0) { +entry: + %super = getelementptr inbounds %cls2, %cls2* %0, i32 0, i32 0 + ret void +} + +define void @0(%0* %0) { +entry:member_location + %myClass = getelementptr inbounds %0, %0* %0, i32 0, i32 0 + %super = getelementptr inbounds %cls2, %cls2* %myClass, i32 0, i32 0 + %x = getelementptr inbounds %cls, %cls* %super, i32 0, i32 0 + %load_ = load i16, i16* %x, align 2 + %1 = sext i16 %load_ to i32 + %tmpVar = icmp eq i32 %1, 1 + ret void +} + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__executes_overridden_method.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__executes_overridden_method.snap new file mode 100644 index 0000000000..c02a45019e --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__executes_overridden_method.snap @@ -0,0 +1,61 @@ +--- +source: src/codegen/tests/code_gen_tests.rs +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +%0 = type { %cls2 } +%cls = type { i16, i16 } +%cls2 = type { %cls } +%__fat_pointer_to_cls2 = type { %cls2*, [1 x i64]* } +%__fat_pointer_to_cls = type { %cls*, [1 x i64]* } +%cls.myMethod = type {} +%cls2.myMethod = type {} + +@__cls__init = unnamed_addr constant %cls zeroinitializer +@__cls2__init = unnamed_addr constant %cls2 zeroinitializer +@____init = unnamed_addr constant %0 zeroinitializer +@____fat_pointer_to_cls2__init = unnamed_addr constant %__fat_pointer_to_cls2 zeroinitializer +@____fat_pointer_to_cls__init = unnamed_addr constant %__fat_pointer_to_cls zeroinitializer + +define void @cls(%cls* %0) { +entry: + %x = getelementptr inbounds %cls, %cls* %0, i32 0, i32 0 + %y = getelementptr inbounds %cls, %cls* %0, i32 0, i32 1 + ret void +} + +define void @cls.myMethod(%cls* %0, %cls.myMethod* %1) { +entry: + %x = getelementptr inbounds %cls, %cls* %0, i32 0, i32 0 + %y = getelementptr inbounds %cls, %cls* %0, i32 0, i32 1 + store i16 10, i16* %x, align 2 + ret void +} + +define void @cls2(%cls2* %0) { +entry: + %__super__ = getelementptr inbounds %cls2, %cls2* %0, i32 0, i32 0 + %x = getelementptr inbounds %cls, %cls* %__super__, i32 0, i32 0 + %y = getelementptr inbounds %cls, %cls* %__super__, i32 0, i32 1 + ret void +} + +define void @cls2.myMethod(%cls2* %0, %cls2.myMethod* %1) { +entry: + %__super__ = getelementptr inbounds %cls2, %cls2* %0, i32 0, i32 0 + %x = getelementptr inbounds %cls, %cls* %__super__, i32 0, i32 0 + %y = getelementptr inbounds %cls, %cls* %__super__, i32 0, i32 1 + store i16 20, i16* %x, align 2 + ret void +} + +define void @0(%0* %0) { +entry: + %myClass = getelementptr inbounds %0, %0* %0, i32 0, i32 0 + %cls2.myMethod_instance = alloca %cls2.myMethod, align 8 + call void @cls2.myMethod(%cls2* %myClass, %cls2.myMethod* %cls2.myMethod_instance) + ret void +} + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__fat_pointer_struct.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__fat_pointer_struct.snap new file mode 100644 index 0000000000..530a76bdda --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__fat_pointer_struct.snap @@ -0,0 +1,55 @@ +--- +source: src/codegen/tests/code_gen_tests.rs +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +%0 = type { %cls, %cls2 } +%cls = type {} +%cls2 = type {} +%__fat_pointer_to_cls2 = type { %cls2*, [0 x i64]* } +%__fat_pointer_to_cls = type { %cls*, [2 x i64]* } +%cls.myMethod = type { %cls2* } + +@__cls__init = unnamed_addr constant %cls zeroinitializer +@__cls2__init = unnamed_addr constant %cls2 zeroinitializer +@____fat_pointer_to_cls2__init = unnamed_addr constant %__fat_pointer_to_cls2 zeroinitializer +@____init = unnamed_addr constant %0 zeroinitializer +@____fat_pointer_to_cls__init = unnamed_addr constant %__fat_pointer_to_cls zeroinitializer + +define void @cls(%cls* %0) { +entry: + ret void +} + +define void @cls.myMethod(%cls* %0, %cls.myMethod* %1) { +entry: + %myClass = getelementptr inbounds %cls.myMethod, %cls.myMethod* %1, i32 0, i32 0 + ret void +} + +define void @cls2(%cls2* %0) { +entry: + ret void +} + +define void @0(%0* %0) { +entry: + %callClass = getelementptr inbounds %0, %0* %0, i32 0, i32 0 + %paramClass = getelementptr inbounds %0, %0* %0, i32 0, i32 1 + %cls.myMethod_instance = alloca %cls.myMethod, align 8 + %1 = getelementptr inbounds %cls.myMethod, %cls.myMethod* %cls.myMethod_instance, i32 0, i32 0 + %outer_fat_pointer_gep = getelementptr inbounds %cls2, %cls2* %paramClass, i32 0 + %fat_pointer_struct = alloca %__fat_pointer_to_cls2, align 8 + %fat_pointer_class_gep = getelementptr inbounds %__fat_pointer_to_cls2, %__fat_pointer_to_cls2* %fat_pointer_struct, i32 0, i32 0 + %fat_poitner_array_gep = getelementptr inbounds %__fat_pointer_to_cls2, %__fat_pointer_to_cls2* %fat_pointer_struct, i32 0, i32 1 + %2 = alloca i64, i32 0, align 8 + store i64* %2, [0 x i64]** %fat_poitner_array_gep, align 8 + store %cls2* %outer_fat_pointer_gep, %cls2** %fat_pointer_class_gep, align 8 + %3 = load %__fat_pointer_to_cls2, %__fat_pointer_to_cls2* %fat_pointer_struct, align 8 + store %__fat_pointer_to_cls2 %3, %cls2** %1, align 8 + call void @cls.myMethod(%cls* %callClass, %cls.myMethod* %cls.myMethod_instance) + ret void +} + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__fat_pointer_struct_method.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__fat_pointer_struct_method.snap new file mode 100644 index 0000000000..4cc316b542 --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__fat_pointer_struct_method.snap @@ -0,0 +1,55 @@ +--- +source: src/codegen/tests/code_gen_tests.rs +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +%0 = type { %cls, %cls2 } +%cls = type {} +%cls2 = type {} +%__fat_pointer_to_cls2 = type { %cls2*, [0 x i64]* } +%__fat_pointer_to_cls = type { %cls*, [1 x i64]* } +%cls.myMethod = type { %__fat_pointer_to_cls2 } + +@__cls__init = unnamed_addr constant %cls zeroinitializer +@__cls2__init = unnamed_addr constant %cls2 zeroinitializer +@____fat_pointer_to_cls2__init = unnamed_addr constant %__fat_pointer_to_cls2 zeroinitializer +@____init = unnamed_addr constant %0 zeroinitializer +@____fat_pointer_to_cls__init = unnamed_addr constant %__fat_pointer_to_cls zeroinitializer + +define void @cls(%cls* %0) { +entry: + ret void +} + +define void @cls.myMethod(%cls* %0, %cls.myMethod* %1) { +entry: + %myClass = getelementptr inbounds %cls.myMethod, %cls.myMethod* %1, i32 0, i32 0 + ret void +} + +define void @cls2(%cls2* %0) { +entry: + ret void +} + +define void @0(%0* %0) { +entry: + %callClass = getelementptr inbounds %0, %0* %0, i32 0, i32 0 + %paramClass = getelementptr inbounds %0, %0* %0, i32 0, i32 1 + %cls.myMethod_instance = alloca %cls.myMethod, align 8 + %1 = getelementptr inbounds %cls.myMethod, %cls.myMethod* %cls.myMethod_instance, i32 0, i32 0 + %outer_fat_pointer_gep = getelementptr inbounds %cls2, %cls2* %paramClass, i32 0 + %fat_pointer_struct = alloca %__fat_pointer_to_cls2, align 8 + %fat_pointer_class_gep = getelementptr inbounds %__fat_pointer_to_cls2, %__fat_pointer_to_cls2* %fat_pointer_struct, i32 0, i32 0 + %fat_poitner_array_gep = getelementptr inbounds %__fat_pointer_to_cls2, %__fat_pointer_to_cls2* %fat_pointer_struct, i32 0, i32 1 + %2 = alloca i64, i32 0, align 8 + store i64* %2, [0 x i64]** %fat_poitner_array_gep, align 8 + store %cls2* %outer_fat_pointer_gep, %cls2** %fat_pointer_class_gep, align 8 + %3 = load %__fat_pointer_to_cls2, %__fat_pointer_to_cls2* %fat_pointer_struct, align 8 + store %__fat_pointer_to_cls2 %3, %__fat_pointer_to_cls2* %1, align 8 + call void @cls.myMethod(%cls* %callClass, %cls.myMethod* %cls.myMethod_instance) + ret void +} + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__fat_pointer_struct_method.snap.new b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__fat_pointer_struct_method.snap.new new file mode 100644 index 0000000000..2e053ce28b --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__fat_pointer_struct_method.snap.new @@ -0,0 +1,81 @@ +--- +source: src/codegen/tests/code_gen_tests.rs +assertion_line: 3399 +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +%cls = type { i32 } +%myFB = type { %__fat_pointer_to_cls } +%__fat_pointer_to_cls = type { %cls*, [2 x i32 ()*] } +%main = type { %cls, %myFB } +%cls.myMethod = type {} +%cls.myMethod2 = type {} + +@__cls__init = unnamed_addr constant %cls zeroinitializer +@__myFB__init = unnamed_addr constant %myFB zeroinitializer +@____fat_pointer_to_cls__init = unnamed_addr constant %__fat_pointer_to_cls zeroinitializer +@main_instance = global %main zeroinitializer + +define void @cls(%cls* %0) { +entry: + %x = getelementptr inbounds %cls, %cls* %0, i32 0, i32 0 + ret void +} + +define i32 @cls.myMethod(%cls* %0, %cls.myMethod* %1) { +entry: + %x = getelementptr inbounds %cls, %cls* %0, i32 0, i32 0 + %myMethod = alloca i32, align 4 + store i32 0, i32* %myMethod, align 4 + %cls.myMethod_ret = load i32, i32* %myMethod, align 4 + ret i32 %cls.myMethod_ret +} + +define i32 @cls.myMethod2(%cls* %0, %cls.myMethod2* %1) { +entry: + %x = getelementptr inbounds %cls, %cls* %0, i32 0, i32 0 + %myMethod2 = alloca i32, align 4 + store i32 0, i32* %myMethod2, align 4 + %cls.myMethod2_ret = load i32, i32* %myMethod2, align 4 + ret i32 %cls.myMethod2_ret +} + +define void @myFB(%myFB* %0) { +entry: + %callClass = getelementptr inbounds %myFB, %myFB* %0, i32 0, i32 0 + %load_fp_member = getelementptr inbounds %__fat_pointer_to_cls, %__fat_pointer_to_cls* %callClass, i32 0, i32 0 + %deref = load %cls*, %cls** %load_fp_member, align 8 + %x = getelementptr inbounds %cls, %cls* %deref, i32 0, i32 0 + store i32 2, i32* %x, align 4 + %load_fp_member1 = getelementptr inbounds %__fat_pointer_to_cls, %__fat_pointer_to_cls* %callClass, i32 0, i32 0 + %deref2 = load %cls*, %cls** %load_fp_member1, align 8 + %cls.myMethod_instance = alloca %cls.myMethod, align 8 + %name = getelementptr inbounds %__fat_pointer_to_cls, %__fat_pointer_to_cls* %callClass, i32 0, i32 1 + %1 = getelementptr inbounds [2 x i32 ()*], [2 x i32 ()*]* %name, i64 0, i64 0 + %2 = load i32 ()*, i32 ()** %1, align 8 + %call = call i32 %2(%cls* %deref2, %cls.myMethod* %cls.myMethod_instance) + ret void +} + +define void @main(%main* %0) { +entry: + %callClass = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %fb = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + %1 = getelementptr inbounds %myFB, %myFB* %fb, i32 0, i32 0 + %outer_fat_pointer_gep = getelementptr inbounds %cls, %cls* %callClass, i32 0 + %fat_pointer_struct = alloca %__fat_pointer_to_cls, align 8 + %fat_pointer_class_gep = getelementptr inbounds %__fat_pointer_to_cls, %__fat_pointer_to_cls* %fat_pointer_struct, i32 0, i32 0 + %fat_poitner_array_gep = getelementptr inbounds %__fat_pointer_to_cls, %__fat_pointer_to_cls* %fat_pointer_struct, i32 0, i32 1 + %2 = getelementptr inbounds [2 x i32 ()*], [2 x i32 ()*]* %fat_poitner_array_gep, i64 0 + store i32 (%cls*, %cls.myMethod*)* @cls.myMethod, [2 x i32 ()*]* %2, align 8 + %3 = getelementptr inbounds [2 x i32 ()*], [2 x i32 ()*]* %fat_poitner_array_gep, i64 1 + store i32 (%cls*, %cls.myMethod2*)* @cls.myMethod2, [2 x i32 ()*]* %3, align 8 + store %cls* %outer_fat_pointer_gep, %cls** %fat_pointer_class_gep, align 8 + %4 = load %__fat_pointer_to_cls, %__fat_pointer_to_cls* %fat_pointer_struct, align 8 + store %__fat_pointer_to_cls %4, %__fat_pointer_to_cls* %1, align 8 + call void @myFB(%myFB* %fb) + ret void +} + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__method_codegen_return.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__method_codegen_return.snap index 0c94a3dac2..5af527cd3b 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__method_codegen_return.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__method_codegen_return.snap @@ -6,9 +6,16 @@ expression: result source_filename = "main" %MyClass = type {} +%__fat_pointer_to_MyClass = type { %MyClass*, [1 x i64]* } %MyClass.testMethod = type { i16 } @__MyClass__init = unnamed_addr constant %MyClass zeroinitializer +@____fat_pointer_to_MyClass__init = unnamed_addr constant %__fat_pointer_to_MyClass zeroinitializer + +define void @MyClass(%MyClass* %0) { +entry: + ret void +} define i16 @MyClass.testMethod(%MyClass* %0, %MyClass.testMethod* %1) { entry: diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__method_codegen_void.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__method_codegen_void.snap index 7756903e7b..138ecfb5b4 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__method_codegen_void.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__method_codegen_void.snap @@ -6,9 +6,16 @@ expression: result source_filename = "main" %MyClass = type {} +%__fat_pointer_to_MyClass = type { %MyClass*, [1 x i64]* } %MyClass.testMethod = type { i16, i16 } @__MyClass__init = unnamed_addr constant %MyClass zeroinitializer +@____fat_pointer_to_MyClass__init = unnamed_addr constant %__fat_pointer_to_MyClass zeroinitializer + +define void @MyClass(%MyClass* %0) { +entry: + ret void +} define void @MyClass.testMethod(%MyClass* %0, %MyClass.testMethod* %1) { entry: diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__test.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__test.snap new file mode 100644 index 0000000000..052b0319de --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__test.snap @@ -0,0 +1,28 @@ +--- +source: src/codegen/tests/code_gen_tests.rs +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +%cls = type { i16, i16 } +%__fat_pointer_to_cls = type { %cls*, [1 x i64]* } +%cls.MyMethod = type {} + +@__cls__init = unnamed_addr constant %cls zeroinitializer +@____fat_pointer_to_cls__init = unnamed_addr constant %__fat_pointer_to_cls zeroinitializer + +define void @cls(%cls* %0) { +entry: + %x = getelementptr inbounds %cls, %cls* %0, i32 0, i32 0 + %y = getelementptr inbounds %cls, %cls* %0, i32 0, i32 1 + ret void +} + +define void @cls.MyMethod(%cls* %0, %cls.MyMethod* %1) { +entry: + %x = getelementptr inbounds %cls, %cls* %0, i32 0, i32 0 + %y = getelementptr inbounds %cls, %cls* %0, i32 0, i32 1 + ret void +} + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__write_to_parent_variable.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__write_to_parent_variable.snap new file mode 100644 index 0000000000..6260a3bd6d --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__write_to_parent_variable.snap @@ -0,0 +1,45 @@ +--- +source: src/codegen/tests/code_gen_tests.rs +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +%0 = type { %cls2 } +%cls = type { i16, i16 } +%cls2 = type { %cls } +%__fat_pointer_to_cls2 = type { %cls2*, [0 x i64]* } +%__fat_pointer_to_cls = type { %cls*, [0 x i64]* } + +@__cls__init = unnamed_addr constant %cls zeroinitializer +@__cls2__init = unnamed_addr constant %cls2 zeroinitializer +@____init = unnamed_addr constant %0 zeroinitializer +@____fat_pointer_to_cls2__init = unnamed_addr constant %__fat_pointer_to_cls2 zeroinitializer +@____fat_pointer_to_cls__init = unnamed_addr constant %__fat_pointer_to_cls zeroinitializer + +define void @cls(%cls* %0) { +entry: + %x = getelementptr inbounds %cls, %cls* %0, i32 0, i32 0 + %y = getelementptr inbounds %cls, %cls* %0, i32 0, i32 1 + ret void +} + +define void @cls2(%cls2* %0) { +entry: + %__super__ = getelementptr inbounds %cls2, %cls2* %0, i32 0, i32 0 + %x = getelementptr inbounds %cls, %cls* %__super__, i32 0, i32 0 + %y = getelementptr inbounds %cls, %cls* %__super__, i32 0, i32 1 + ret void +} + +define void @0(%0* %0) { +entry: + %myClass = getelementptr inbounds %0, %0* %0, i32 0, i32 0 + %1 = getelementptr inbounds %cls2, %cls2* %myClass, i32 0, i32 0 + %x = getelementptr inbounds %cls, %cls* %1, i32 0, i32 0 + %load_ = load i16, i16* %x, align 2 + %2 = sext i16 %load_ to i32 + %tmpVar = icmp eq i32 %2, 1 + ret void +} + diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__write_to_parent_variable_in_function.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__write_to_parent_variable_in_function.snap new file mode 100644 index 0000000000..57587ee08a --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__write_to_parent_variable_in_function.snap @@ -0,0 +1,54 @@ +--- +source: src/codegen/tests/code_gen_tests.rs +expression: res +--- +; ModuleID = 'main' +source_filename = "main" + +%0 = type { %cls2 } +%cls = type { i16, i16 } +%cls2 = type { %cls } +%__fat_pointer_to_cls2 = type { %cls2*, [1 x i64]* } +%__fat_pointer_to_cls = type { %cls*, [0 x i64]* } +%cls2.myMethod = type {} + +@__cls__init = unnamed_addr constant %cls zeroinitializer +@__cls2__init = unnamed_addr constant %cls2 zeroinitializer +@____init = unnamed_addr constant %0 zeroinitializer +@____fat_pointer_to_cls2__init = unnamed_addr constant %__fat_pointer_to_cls2 zeroinitializer +@____fat_pointer_to_cls__init = unnamed_addr constant %__fat_pointer_to_cls zeroinitializer + +define void @cls(%cls* %0) { +entry: + %x = getelementptr inbounds %cls, %cls* %0, i32 0, i32 0 + %y = getelementptr inbounds %cls, %cls* %0, i32 0, i32 1 + ret void +} + +define void @cls2(%cls2* %0) { +entry: + %__super__ = getelementptr inbounds %cls2, %cls2* %0, i32 0, i32 0 + %x = getelementptr inbounds %cls, %cls* %__super__, i32 0, i32 0 + %y = getelementptr inbounds %cls, %cls* %__super__, i32 0, i32 1 + ret void +} + +define i32 @cls2.myMethod(%cls2* %0, %cls2.myMethod* %1) { +entry: + %__super__ = getelementptr inbounds %cls2, %cls2* %0, i32 0, i32 0 + %x = getelementptr inbounds %cls, %cls* %__super__, i32 0, i32 0 + %y = getelementptr inbounds %cls, %cls* %__super__, i32 0, i32 1 + %myMethod = alloca i32, align 4 + store i32 0, i32* %myMethod, align 4 + store i16 33, i16* %x, align 2 + %cls2.myMethod_ret = load i32, i32* %myMethod, align 4 + ret i32 %cls2.myMethod_ret +} + +define void @0(%0* %0) { +entry: + %myClass = getelementptr inbounds %0, %0* %0, i32 0, i32 0 + %load_ = load %cls2, %cls2* %myClass, align 2 + ret void +} + diff --git a/src/index.rs b/src/index.rs index 4244458377..5374db8be5 100644 --- a/src/index.rs +++ b/src/index.rs @@ -49,6 +49,8 @@ pub struct VariableIndexEntry { pub source_location: SymbolLocation, /// Variadic information placeholder for the variable, if any varargs: Option, + /// the typename of the pou that accesses this variable, None if it is the same as in the qualified name + accessing_type: Option, } #[derive(Debug, PartialEq, Eq, Clone, Hash)] @@ -106,6 +108,7 @@ impl VariableIndexEntry { argument_type: ArgumentType, location_in_parent: u32, source_location: SymbolLocation, + accessing_type: Option<&str>, ) -> Self { VariableIndexEntry { name: name.to_string(), @@ -119,6 +122,7 @@ impl VariableIndexEntry { binding: None, source_location, varargs: None, + accessing_type: accessing_type.map(|s| s.to_owned()), } } @@ -140,6 +144,7 @@ impl VariableIndexEntry { binding: None, source_location, varargs: None, + accessing_type: None, } } @@ -168,6 +173,11 @@ impl VariableIndexEntry { self } + pub fn set_accessing_type(mut self, accessing_type: Option) -> Self { + self.accessing_type = accessing_type; + self + } + /// 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 { @@ -262,6 +272,10 @@ impl VariableIndexEntry { self.varargs.as_ref() } + pub fn get_accessing_type(&self) -> Option<&str> { + self.accessing_type.as_deref() + } + fn has_parent(&self, context: &str) -> bool { let name = qualified_name(context, &self.name); self.qualified_name.eq_ignore_ascii_case(&name) @@ -383,6 +397,7 @@ pub enum PouIndexEntry { instance_struct_name: String, linkage: LinkageType, location: SymbolLocation, + super_class: Option, }, Function { name: String, @@ -398,6 +413,7 @@ pub enum PouIndexEntry { instance_struct_name: String, linkage: LinkageType, location: SymbolLocation, + super_class: Option, }, Method { name: String, @@ -444,12 +460,14 @@ impl PouIndexEntry { pou_name: &str, linkage: LinkageType, location: SymbolLocation, + super_class: Option<&str>, ) -> PouIndexEntry { PouIndexEntry::FunctionBlock { name: pou_name.into(), instance_struct_name: pou_name.into(), linkage, location, + super_class: super_class.map(|s| s.to_owned()), } } @@ -520,12 +538,14 @@ impl PouIndexEntry { pou_name: &str, linkage: LinkageType, location: SymbolLocation, + super_class: Option, ) -> PouIndexEntry { PouIndexEntry::Class { name: pou_name.into(), instance_struct_name: pou_name.into(), linkage, location, + super_class, } } @@ -563,6 +583,16 @@ impl PouIndexEntry { } } + /// returns the super class of this pou if supported + pub fn get_super_class(&self) -> Option<&str> { + match self { + PouIndexEntry::Class { super_class, .. } | PouIndexEntry::FunctionBlock { super_class, .. } => { + super_class.as_deref() + } + _ => None, + } + } + /// returns the name of the struct-type used to store the POUs state /// (interface-variables) pub fn get_instance_struct_type_name(&self) -> Option<&str> { @@ -687,7 +717,6 @@ pub struct TypeIndex { /// all types (structs, enums, type, POUs, etc.) types: SymbolMap, pou_types: SymbolMap, - void_type: DataType, } @@ -990,7 +1019,11 @@ impl Index { } /// return the `VariableIndexEntry` with the qualified name: `container_name`.`variable_name` - pub fn find_member(&self, container_name: &str, variable_name: &str) -> Option<&VariableIndexEntry> { + pub fn find_local_member( + &self, + container_name: &str, + variable_name: &str, + ) -> Option<&VariableIndexEntry> { self.type_index.find_type(container_name).and_then(|it| it.find_member(variable_name)).or_else(|| { //check qualifier container_name @@ -1000,6 +1033,29 @@ impl Index { }) } + /// Searches for variable name in the given container, if not found, attempts to search for it in super classes + pub fn find_member(&self, container_name: &str, variable_name: &str) -> Option<&VariableIndexEntry> { + // Find pou in index + self.find_local_member(container_name, variable_name).or_else(|| { + if let Some(class) = self.find_pou(container_name).and_then(|it| it.get_super_class()) { + self.find_member(class, variable_name) + } else { + None + } + }) + } + + /// Searches for method names in the given container, if not found, attempts to search for it in super class + pub fn find_method(&self, container_name: &str, method_name: &str) -> Option<&PouIndexEntry> { + if let Some(local_method) = self.find_pou(&qualified_name(container_name, method_name)) { + Some(local_method) + } else if let Some(super_method) = self.find_pou(container_name).and_then(|it| it.get_super_class()) { + self.find_method(super_method, method_name) + } else { + None + } + } + /// return the `VariableIndexEntry` associated with the given fully qualified name using `.` as /// a delimiter. (e.g. "PLC_PRG.x", or "MyClass.MyMethod.x") pub fn find_fully_qualified_variable(&self, fully_qualified_name: &str) -> Option<&VariableIndexEntry> { @@ -1054,6 +1110,18 @@ impl Index { self.type_index.find_type(container_name).map(|it| it.get_members()).unwrap_or_else(|| &[]) } + /// returns all methods of the given POU (e.g. FUNCTION, PROGRAM, CLASS, etc.) + pub fn get_number_of_pou_methods(&self, container_name: &str) -> i64 { + let all = self.get_pous(); + let mut count = 0; + for (_key, value) in all.elements() { + if value.is_method() && value.get_container().eq(container_name) { + count += 1; + } + } + count + } + /// returns all member variables of the given POU (e.g. FUNCTION, PROGRAM, etc.) pub fn get_pou_members(&self, container_name: &str) -> &[VariableIndexEntry] { self.get_pou_types() @@ -1062,6 +1130,28 @@ impl Index { .unwrap_or_else(|| &[]) } + /// returns map of all related POUs and their members + pub fn get_all_pou_members_recursively( + &self, + container_name: &str, + ) -> IndexMap> { + let mut members = IndexMap::new(); + let mut container_name = container_name; + + members.insert( + container_name.to_string(), + self.get_pou_members(container_name).iter().collect::>(), + ); + while let Some(super_class) = self.find_pou(container_name).and_then(|it| it.get_super_class()) { + members.insert( + super_class.to_string(), + self.get_pou_members(super_class).iter().collect::>(), + ); + container_name = super_class; + } + members + } + pub fn find_pou_type(&self, pou_name: &str) -> Option<&DataType> { self.get_pou_types().get(&pou_name.to_lowercase()) } @@ -1326,6 +1416,7 @@ impl Index { variable_type, location, source_location, + None, ) .set_constant(member_info.is_constant) .set_initial_value(initial_value) diff --git a/src/index/tests/index_tests.rs b/src/index/tests/index_tests.rs index b2d0cbc50d..94503b89c1 100644 --- a/src/index/tests/index_tests.rs +++ b/src/index/tests/index_tests.rs @@ -1744,6 +1744,7 @@ fn a_program_pou_is_indexed() { binding: None, source_location: SymbolLocation { source_range: (17..26).into(), line_number: 1 }, varargs: None, + accessing_type: None, } }), index.find_pou("myProgram"), @@ -1768,6 +1769,7 @@ fn a_program_pou_is_indexed() { linkage: LinkageType::Internal, instance_struct_name: "myFunctionBlock".into(), location: SymbolLocation { source_range: (139..154).into(), line_number: 7 }, + super_class: None, }), index.find_pou("myFunctionBlock"), ); @@ -1778,6 +1780,7 @@ fn a_program_pou_is_indexed() { linkage: LinkageType::Internal, instance_struct_name: "myClass".into(), location: SymbolLocation { source_range: (197..204).into(), line_number: 10 }, + super_class: None, }), index.find_pou("myClass"), ); @@ -2012,7 +2015,8 @@ fn internal_vla_struct_type_is_indexed_correctly() { linkage: LinkageType::Internal, binding: None, source_location: SymbolLocation { source_range: (0..0).into(), line_number: 0 }, - varargs: None + varargs: None, + accessing_type: None, }, VariableIndexEntry { name: "dimensions".to_string(), @@ -2025,7 +2029,8 @@ fn internal_vla_struct_type_is_indexed_correctly() { linkage: LinkageType::Internal, binding: None, source_location: SymbolLocation { source_range: (0..0).into(), line_number: 0 }, - varargs: None + varargs: None, + accessing_type: None, } ], source: StructSource::Internal(InternalType::VariableLengthArray { @@ -2059,3 +2064,50 @@ fn string_type_alias_without_size_is_indexed() { let dt = index.find_effective_type_by_name(my_alias).unwrap(); assert_eq!("WSTRING", dt.get_name()); } + +#[test] +fn test_fat_pointer_vt_size() { + let (_, index) = index( + r" + CLASS cls + METHOD foo + END_METHOD + METHOD bar + END_METHOD + METHOD buz + END_METHOD + END_CLASS + + CLASS cls2 + END_CLASS + ", + ); + + let number = index.get_number_of_pou_methods("cls"); + assert_eq!(number, 3); + + let number = index.get_number_of_pou_methods("cls2"); + assert_eq!(number, 0); +} + +#[test] +fn test() { + let (_, index) = index( + r" + CLASS cls + METHOD foo + VAR_IN_OUT + test_class : cls; + END_VAR + END_METHOD + METHOD bar + END_METHOD + METHOD buz + END_METHOD + END_CLASS + ", + ); + + let number = index.get_number_of_pou_methods("cls"); + assert_eq!(number, 3); +} diff --git a/src/index/tests/snapshots/rusty__index__tests__index_tests__fat_pointer_vt_size-2.snap b/src/index/tests/snapshots/rusty__index__tests__index_tests__fat_pointer_vt_size-2.snap new file mode 100644 index 0000000000..9152cd84aa --- /dev/null +++ b/src/index/tests/snapshots/rusty__index__tests__index_tests__fat_pointer_vt_size-2.snap @@ -0,0 +1,29 @@ +--- +source: src/index/tests/index_tests.rs +expression: "index.find_effective_type_by_name(\"__arr_vt_cls\").unwrap()" +--- +DataType { + name: "__arr_vt_cls", + initial_value: None, + information: Array { + name: "__arr_vt_cls", + inner_type_name: "LWORD", + dimensions: [ + Dimension { + start_offset: LiteralInteger( + 0, + ), + end_offset: LiteralInteger( + 2, + ), + }, + ], + }, + nature: Any, + location: SymbolLocation { + line_number: 4294967295, + source_range: SourceRange { + range: 0..0, + }, + }, +} diff --git a/src/index/tests/snapshots/rusty__index__tests__index_tests__fat_pointer_vt_size.snap b/src/index/tests/snapshots/rusty__index__tests__index_tests__fat_pointer_vt_size.snap new file mode 100644 index 0000000000..b07edc2759 --- /dev/null +++ b/src/index/tests/snapshots/rusty__index__tests__index_tests__fat_pointer_vt_size.snap @@ -0,0 +1,65 @@ +--- +source: src/index/tests/index_tests.rs +expression: "index.find_effective_type_by_name(\"__fat_pointer_to_cls\").unwrap()" +--- +DataType { + name: "__fat_pointer_to_cls", + initial_value: None, + information: Struct { + name: "__fat_pointer_to_cls", + members: [ + VariableIndexEntry { + name: "pointer_to_cls", + qualified_name: "__fat_pointer_to_cls.pointer_to_cls", + initial_value: None, + argument_type: ByVal( + Input, + ), + is_constant: false, + data_type_name: "__ptr_to_cls_struct", + location_in_parent: 0, + linkage: Internal, + binding: None, + source_location: SymbolLocation { + line_number: 0, + source_range: SourceRange { + range: 0..0, + }, + }, + varargs: None, + accessing_type: None, + }, + VariableIndexEntry { + name: "pointer_to_cls_vt", + qualified_name: "__fat_pointer_to_cls.pointer_to_cls_vt", + initial_value: None, + argument_type: ByVal( + Input, + ), + is_constant: false, + data_type_name: "__ptr_to_cls_vt", + location_in_parent: 1, + linkage: Internal, + binding: None, + source_location: SymbolLocation { + line_number: 0, + source_range: SourceRange { + range: 0..0, + }, + }, + varargs: None, + accessing_type: None, + }, + ], + source: Internal( + FatPointer, + ), + }, + nature: Derived, + location: SymbolLocation { + line_number: 0, + source_range: SourceRange { + range: 0..0, + }, + }, +} diff --git a/src/index/tests/snapshots/rusty__index__tests__index_tests__fb_parameters_variable_type.snap b/src/index/tests/snapshots/rusty__index__tests__index_tests__fb_parameters_variable_type.snap index 6cccddbb4b..7f1f8ef55d 100644 --- a/src/index/tests/snapshots/rusty__index__tests__index_tests__fb_parameters_variable_type.snap +++ b/src/index/tests/snapshots/rusty__index__tests__index_tests__fb_parameters_variable_type.snap @@ -22,6 +22,7 @@ expression: members }, }, varargs: None, + accessing_type: None, }, VariableIndexEntry { name: "output1", @@ -42,6 +43,7 @@ expression: members }, }, varargs: None, + accessing_type: None, }, VariableIndexEntry { name: "inout1", @@ -62,5 +64,6 @@ expression: members }, }, varargs: None, + accessing_type: None, }, ] diff --git a/src/index/tests/snapshots/rusty__index__tests__index_tests__function_parameters_variable_type.snap b/src/index/tests/snapshots/rusty__index__tests__index_tests__function_parameters_variable_type.snap index 39003520f7..e6d0777433 100644 --- a/src/index/tests/snapshots/rusty__index__tests__index_tests__function_parameters_variable_type.snap +++ b/src/index/tests/snapshots/rusty__index__tests__index_tests__function_parameters_variable_type.snap @@ -22,6 +22,7 @@ expression: members }, }, varargs: None, + accessing_type: None, }, VariableIndexEntry { name: "output1", @@ -42,6 +43,7 @@ expression: members }, }, varargs: None, + accessing_type: None, }, VariableIndexEntry { name: "inout1", @@ -62,6 +64,7 @@ expression: members }, }, varargs: None, + accessing_type: None, }, VariableIndexEntry { name: "foo", @@ -82,5 +85,6 @@ expression: members }, }, varargs: None, + accessing_type: None, }, ] diff --git a/src/index/tests/snapshots/rusty__index__tests__index_tests__program_parameters_variable_type.snap b/src/index/tests/snapshots/rusty__index__tests__index_tests__program_parameters_variable_type.snap index 0c7766b475..43cf915f39 100644 --- a/src/index/tests/snapshots/rusty__index__tests__index_tests__program_parameters_variable_type.snap +++ b/src/index/tests/snapshots/rusty__index__tests__index_tests__program_parameters_variable_type.snap @@ -22,6 +22,7 @@ expression: members }, }, varargs: None, + accessing_type: None, }, VariableIndexEntry { name: "output1", @@ -42,6 +43,7 @@ expression: members }, }, varargs: None, + accessing_type: None, }, VariableIndexEntry { name: "inout1", @@ -62,5 +64,6 @@ expression: members }, }, varargs: None, + accessing_type: None, }, ] diff --git a/src/index/tests/snapshots/rusty__index__tests__index_tests__test.snap b/src/index/tests/snapshots/rusty__index__tests__index_tests__test.snap new file mode 100644 index 0000000000..b07edc2759 --- /dev/null +++ b/src/index/tests/snapshots/rusty__index__tests__index_tests__test.snap @@ -0,0 +1,65 @@ +--- +source: src/index/tests/index_tests.rs +expression: "index.find_effective_type_by_name(\"__fat_pointer_to_cls\").unwrap()" +--- +DataType { + name: "__fat_pointer_to_cls", + initial_value: None, + information: Struct { + name: "__fat_pointer_to_cls", + members: [ + VariableIndexEntry { + name: "pointer_to_cls", + qualified_name: "__fat_pointer_to_cls.pointer_to_cls", + initial_value: None, + argument_type: ByVal( + Input, + ), + is_constant: false, + data_type_name: "__ptr_to_cls_struct", + location_in_parent: 0, + linkage: Internal, + binding: None, + source_location: SymbolLocation { + line_number: 0, + source_range: SourceRange { + range: 0..0, + }, + }, + varargs: None, + accessing_type: None, + }, + VariableIndexEntry { + name: "pointer_to_cls_vt", + qualified_name: "__fat_pointer_to_cls.pointer_to_cls_vt", + initial_value: None, + argument_type: ByVal( + Input, + ), + is_constant: false, + data_type_name: "__ptr_to_cls_vt", + location_in_parent: 1, + linkage: Internal, + binding: None, + source_location: SymbolLocation { + line_number: 0, + source_range: SourceRange { + range: 0..0, + }, + }, + varargs: None, + accessing_type: None, + }, + ], + source: Internal( + FatPointer, + ), + }, + nature: Derived, + location: SymbolLocation { + line_number: 0, + source_range: SourceRange { + range: 0..0, + }, + }, +} diff --git a/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__array_instances_are_repeated.snap b/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__array_instances_are_repeated.snap index d034c04e42..ff1ba8e63b 100644 --- a/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__array_instances_are_repeated.snap +++ b/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__array_instances_are_repeated.snap @@ -30,6 +30,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -62,6 +63,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -115,6 +117,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -168,6 +171,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -200,6 +204,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -267,6 +272,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -334,6 +340,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -366,6 +373,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ] diff --git a/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__array_with_const_instances_are_repeated.snap b/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__array_with_const_instances_are_repeated.snap index 29876c9d17..c75849c3bc 100644 --- a/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__array_with_const_instances_are_repeated.snap +++ b/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__array_with_const_instances_are_repeated.snap @@ -30,6 +30,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -67,6 +68,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -99,6 +101,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -152,6 +155,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -205,6 +209,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ] diff --git a/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__filter_on_variables_are_applied.snap b/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__filter_on_variables_are_applied.snap index ce5827b258..6da51807b3 100644 --- a/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__filter_on_variables_are_applied.snap +++ b/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__filter_on_variables_are_applied.snap @@ -35,6 +35,7 @@ expression: "index.filter_instances(|it, _|\n !it.is_constant()).coll }, }, varargs: None, + accessing_type: None, }, ), ( @@ -64,6 +65,7 @@ expression: "index.filter_instances(|it, _|\n !it.is_constant()).coll }, }, varargs: None, + accessing_type: None, }, ), ( @@ -96,6 +98,7 @@ expression: "index.filter_instances(|it, _|\n !it.is_constant()).coll }, }, varargs: None, + accessing_type: None, }, ), ( @@ -131,6 +134,7 @@ expression: "index.filter_instances(|it, _|\n !it.is_constant()).coll }, }, varargs: None, + accessing_type: None, }, ), ( @@ -166,6 +170,7 @@ expression: "index.filter_instances(|it, _|\n !it.is_constant()).coll }, }, varargs: None, + accessing_type: None, }, ), ] diff --git a/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__global_fb_variables_are_retrieved.snap b/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__global_fb_variables_are_retrieved.snap index 1b07c01dd9..64cec110b8 100644 --- a/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__global_fb_variables_are_retrieved.snap +++ b/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__global_fb_variables_are_retrieved.snap @@ -30,6 +30,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -62,6 +63,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -94,6 +96,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -123,6 +126,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -155,6 +159,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -190,6 +195,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -225,6 +231,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ] diff --git a/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__global_struct_variables_are_retrieved.snap b/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__global_struct_variables_are_retrieved.snap index 97c40939de..9b780b543b 100644 --- a/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__global_struct_variables_are_retrieved.snap +++ b/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__global_struct_variables_are_retrieved.snap @@ -30,6 +30,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -62,6 +63,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -94,6 +96,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -123,6 +126,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -155,6 +159,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -190,6 +195,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -225,6 +231,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ] diff --git a/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__global_vars_are_retrieved.snap b/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__global_vars_are_retrieved.snap index d9d8b109f5..fef006a098 100644 --- a/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__global_vars_are_retrieved.snap +++ b/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__global_vars_are_retrieved.snap @@ -30,6 +30,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -59,6 +60,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ] diff --git a/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__nested_global_struct_variables_are_retrieved.snap b/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__nested_global_struct_variables_are_retrieved.snap index b3644ba0ef..3cf082fb8a 100644 --- a/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__nested_global_struct_variables_are_retrieved.snap +++ b/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__nested_global_struct_variables_are_retrieved.snap @@ -30,6 +30,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -62,6 +63,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -97,6 +99,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -132,6 +135,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -164,6 +168,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -199,6 +204,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -234,6 +240,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -263,6 +270,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -295,6 +303,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -330,6 +339,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -368,6 +378,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -406,6 +417,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -441,6 +453,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -479,6 +492,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -517,6 +531,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ] diff --git a/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__pointer_variables_are_not_retrieved.snap b/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__pointer_variables_are_not_retrieved.snap index 9427d3b1e7..4e7d745f79 100644 --- a/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__pointer_variables_are_not_retrieved.snap +++ b/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__pointer_variables_are_not_retrieved.snap @@ -30,6 +30,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -62,6 +63,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ] diff --git a/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__program_variables_are_retrieved.snap b/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__program_variables_are_retrieved.snap index 82ab0a740e..f5f659eec6 100644 --- a/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__program_variables_are_retrieved.snap +++ b/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__program_variables_are_retrieved.snap @@ -30,6 +30,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -62,6 +63,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ( @@ -94,6 +96,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ] diff --git a/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__programs_are_retrieved.snap b/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__programs_are_retrieved.snap index 302c5137fa..57ca0f6705 100644 --- a/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__programs_are_retrieved.snap +++ b/src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__programs_are_retrieved.snap @@ -30,6 +30,7 @@ expression: "index.find_instances().collect::>>()" }, }, varargs: None, + accessing_type: None, }, ), ] diff --git a/src/index/visitor.rs b/src/index/visitor.rs index 9546fad62c..193c91d5b7 100644 --- a/src/index/visitor.rs +++ b/src/index/visitor.rs @@ -34,15 +34,108 @@ pub fn visit(unit: &CompilationUnit) -> Index { for implementation in &unit.implementations { visit_implementation(&mut index, implementation, &symbol_location_factory); } + + for pou in &unit.units { + visit_and_generate_fat_pointers(&mut index, pou, &symbol_location_factory); + } + index } +pub fn visit_and_generate_fat_pointers( + index: &mut Index, + pou: &Pou, + symbol_location_factory: &SymbolLocationFactory, +) { + let pou_name = &pou.name; + let virtual_table_array = format!("__arr_vt_{pou_name}").to_lowercase(); + let number_of_methods = index.get_number_of_pou_methods(pou_name); + index.register_type(typesystem::DataType { + name: virtual_table_array.clone(), + initial_value: None, + information: DataTypeInformation::Array { + name: virtual_table_array.clone(), + inner_type_name: "__FUNCTION_POINTER__".to_string(), + dimensions: vec![Dimension { + start_offset: TypeSize::LiteralInteger(0), + end_offset: TypeSize::LiteralInteger(number_of_methods - 1), + }], + }, + nature: TypeNature::Any, + location: SymbolLocation::internal(), + }); + + let fat_pointer_variables = vec![ + // pointer to struct + Variable { + name: format!("pointer_to_{pou_name}").to_lowercase(), + data_type_declaration: DataTypeDeclaration::DataTypeDefinition { + data_type: DataType::PointerType { + name: Some(format!("__ptr_to_{pou_name}_struct")), + referenced_type: Box::new(DataTypeDeclaration::DataTypeReference { + referenced_type: pou.name.clone(), + location: SourceRange::undefined(), + }), + }, + location: SourceRange::undefined(), + scope: None, + }, + initializer: None, + address: None, + location: SourceRange::undefined(), + }, + // pointer to array + Variable { + name: format!("{pou_name}_vt").to_lowercase(), + data_type_declaration: DataTypeDeclaration::DataTypeReference { + referenced_type: virtual_table_array, + location: SourceRange::undefined(), + }, + initializer: None, + address: None, + location: SourceRange::undefined(), + }, + ]; + + visit_struct( + format!("__fat_pointer_to_{pou_name}").as_str(), + &fat_pointer_variables, + index, + symbol_location_factory, + &None, + None, + &SourceRange::undefined(), + StructSource::Internal(InternalType::FatPointer), + ); +} + pub fn visit_pou(index: &mut Index, pou: &Pou, symbol_location_factory: &SymbolLocationFactory) { - let mut members = vec![]; + let mut members: Vec = vec![]; //register the pou's member variables - let mut member_varargs = None; + let mut member_varargs: Option = None; let mut count = 0; + + // if class has parent add to struct + if let Some(super_class) = &pou.super_class { + let entry = index.register_member_variable( + MemberInfo { + container_name: &pou.name, + variable_name: "__super__", + variable_linkage: ArgumentType::ByRef(VariableType::Local), + variable_type_name: super_class.as_str(), + is_constant: false, + binding: None, + varargs: None, + }, + None, + symbol_location_factory.create_symbol_location(&SourceRange::undefined()), + count, + ); + members.push(entry); + count += 1; + } + for block in &pou.variable_blocks { let block_type = get_declaration_type_for(block, &pou.pou_type); for var in &block.variables { @@ -158,6 +251,7 @@ pub fn visit_pou(index: &mut Index, pou: &Pou, symbol_location_factory: &SymbolL &pou.name, pou.linkage, symbol_location_factory.create_symbol_location(&pou.name_location), + pou.super_class.clone().as_deref(), )); index.register_pou_type(datatype); } @@ -175,6 +269,7 @@ pub fn visit_pou(index: &mut Index, pou: &Pou, symbol_location_factory: &SymbolL &pou.name, pou.linkage, symbol_location_factory.create_symbol_location(&pou.name_location), + pou.super_class.clone(), )); index.register_pou_type(datatype); } @@ -346,7 +441,8 @@ fn visit_data_type( index, symbol_location_factory, scope, - type_declaration, + type_declaration.initializer.clone(), + &type_declaration.location, StructSource::OriginalDeclaration, ); } @@ -670,14 +766,6 @@ fn visit_variable_length_array( }, ]; - let struct_ty = DataType::StructType { name: Some(struct_name.clone()), variables: variables.clone() }; - let type_dec = UserTypeDeclaration { - data_type: struct_ty, - initializer: None, - location: type_declaration.location.clone(), - scope: type_declaration.scope.clone(), - }; - // visit the internally created struct type to also index its members visit_struct( &struct_name, @@ -685,7 +773,8 @@ fn visit_variable_length_array( index, symbol_location_factory, &type_declaration.scope, - &type_dec, + type_declaration.initializer.clone(), + &type_declaration.location, StructSource::Internal(InternalType::VariableLengthArray { inner_type_name: referenced_type, ndims }), ) } @@ -777,7 +866,8 @@ fn visit_struct( index: &mut Index, symbol_location_factory: &SymbolLocationFactory, scope: &Option, - type_declaration: &UserTypeDeclaration, + initializer: Option, + location: &SourceRange, source: StructSource, ) { let members = variables @@ -830,17 +920,14 @@ fn visit_struct( let nature = source.get_type_nature(); let information = DataTypeInformation::Struct { name: name.to_owned(), members, source }; - let init = index.get_mut_const_expressions().maybe_add_constant_expression( - type_declaration.initializer.clone(), - name, - scope.clone(), - ); + let init = + index.get_mut_const_expressions().maybe_add_constant_expression(initializer, name, scope.clone()); index.register_type(typesystem::DataType { name: name.to_string(), initial_value: init, information, nature, - location: symbol_location_factory.create_symbol_location(&type_declaration.location), + location: symbol_location_factory.create_symbol_location(location), }); //Generate an initializer for the struct let global_struct_name = crate::index::get_initializer_name(name); @@ -848,7 +935,7 @@ fn visit_struct( &global_struct_name, &global_struct_name, name, - symbol_location_factory.create_symbol_location(&type_declaration.location), + symbol_location_factory.create_symbol_location(location), ) .set_initial_value(init) .set_constant(true); diff --git a/src/lexer/tokens.rs b/src/lexer/tokens.rs index b830474e02..02baa2a7e1 100644 --- a/src/lexer/tokens.rs +++ b/src/lexer/tokens.rs @@ -32,6 +32,9 @@ pub enum Token { #[token("ENDCLASS", ignore(case))] KeywordEndClass, + #[token("EXTENDS", ignore(case))] + KeywordExtends, + #[token("VAR_INPUT", ignore(case))] #[token("VARINPUT", ignore(case))] KeywordVarInput, diff --git a/src/parser.rs b/src/parser.rs index 41bd3b13f2..9b7777cac5 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -159,15 +159,12 @@ fn parse_pou( KeywordEndProgram, KeywordEndFunction, KeywordEndFunctionBlock, + KeywordEndClass, ]; let pou = parse_any_in_region(lexer, closing_tokens.clone(), |lexer| { - let poly_mode = match pou_type { - PouType::Class | PouType::FunctionBlock | PouType::Method { .. } => { - // classes and function blocks can be ABSTRACT, FINAL or neither. - parse_polymorphism_mode(lexer, &pou_type) - } - _ => None, - }; + // parse polymorphism mode for all pou types + // check in validator if pou type allows polymorphism + let poly_mode = parse_polymorphism_mode(lexer, &pou_type); let (name, name_location) = parse_identifier(lexer).unwrap_or_else(|| ("".to_string(), SourceRange::undefined())); // parse POU name @@ -176,55 +173,50 @@ fn parse_pou( with_scope(lexer, name.clone(), |lexer| { // TODO: Parse USING directives - // TODO: Parse EXTENDS specifier + let super_class = parse_super_class(lexer); // TODO: Parse IMPLEMENTS specifier - let return_type = if pou_type != PouType::Class { - // parse an optional return type - parse_return_type(lexer, &pou_type) - } else { - // classes do not have a return type - None - }; + // parse an optional return type + // classes do not have a return type (check in validator) + let return_type = parse_return_type(lexer, &pou_type); // parse variable declarations. note that var in/out/inout // blocks are not allowed inside of class declarations. let mut variable_blocks = vec![]; - let allowed_var_types = match pou_type { - PouType::Class => vec![KeywordVar], - _ => vec![KeywordVar, KeywordVarInput, KeywordVarOutput, KeywordVarInOut, KeywordVarTemp], - }; + let allowed_var_types = + vec![KeywordVar, KeywordVarInput, KeywordVarOutput, KeywordVarInOut, KeywordVarTemp]; while allowed_var_types.contains(&lexer.token) { variable_blocks.push(parse_variable_block(lexer, LinkageType::Internal)); } let mut impl_pous = vec![]; let mut implementations = vec![]; - if pou_type == PouType::Class || pou_type == PouType::FunctionBlock { - // classes and function blocks can have methods. methods consist of a Pou part - // and an implementation part. That's why we get another (Pou, Implementation) - // tuple out of parse_method() that has to be added to the list of Pous and - // implementations. Note that function blocks have to start with the method - // declarations before their implementation. - while lexer.token == KeywordMethod { - if let Some((pou, implementation)) = parse_method(lexer, &name, linkage) { - impl_pous.push(pou); - implementations.push(implementation); - } + + // classes and function blocks can have methods. methods consist of a Pou part + // and an implementation part. That's why we get another (Pou, Implementation) + // tuple out of parse_method() that has to be added to the list of Pous and + // implementations. Note that function blocks have to start with the method + // declarations before their implementation. + // all other Pous need to be checked in the validator if they can have methods. + while lexer.token == KeywordMethod { + if let Some((pou, implementation)) = parse_method(lexer, &name, linkage) { + impl_pous.push(pou); + implementations.push(implementation); } } - if pou_type != PouType::Class { - // a class may not contain an implementation - implementations.push(parse_implementation( - lexer, - linkage, - pou_type.clone(), - &name, - &name, - !generics.is_empty(), - name_location.clone(), - )); - } + + // a class may not contain an implementation + // check in validator + implementations.push(parse_implementation( + lexer, + linkage, + pou_type.clone(), + &name, + &name, + !generics.is_empty(), + name_location.clone(), + )); + let mut pous = vec![Pou { name, pou_type, @@ -235,6 +227,7 @@ fn parse_pou( poly_mode, generics, linkage, + super_class, }]; pous.append(&mut impl_pous); @@ -324,6 +317,15 @@ fn parse_polymorphism_mode(lexer: &mut ParseSession, pou_type: &PouType) -> Opti } } +fn parse_super_class(lexer: &mut ParseSession) -> Option { + if lexer.try_consume(&KeywordExtends) { + let (name, _) = parse_identifier(lexer)?; + Some(name) + } else { + None + } +} + fn parse_return_type(lexer: &mut ParseSession, pou_type: &PouType) -> Option { let start_return_type = lexer.range().start; if lexer.try_consume(&KeywordColon) { @@ -420,6 +422,7 @@ fn parse_method( poly_mode, generics, linkage, + super_class: None, }, implementation, )) diff --git a/src/parser/tests/class_parser_tests.rs b/src/parser/tests/class_parser_tests.rs index 1403c193fd..6117cc5529 100644 --- a/src/parser/tests/class_parser_tests.rs +++ b/src/parser/tests/class_parser_tests.rs @@ -12,7 +12,23 @@ fn simple_class_with_defaults_can_be_parsed() { assert_eq!(class.name, "MyClass"); assert_eq!(class.poly_mode, Some(PolymorphismMode::None)); - assert_eq!(unit.implementations.len(), 0); + + // classes have implementation because they are treated as other POUs + assert_eq!(unit.implementations.len(), 1); +} + +#[test] +fn extends_can_be_parsed() { + let src = " + CLASS MyClass + END_CLASS + + CLASS MyClass2 EXTENDS MyClass + END_CLASS + "; + let unit = parse(src).0; + + assert_eq!(&unit.units[1].super_class.clone().unwrap(), "MyClass"); } #[test] @@ -25,7 +41,9 @@ fn simple_class_can_be_parsed() { assert_eq!(class.name, "MyClass"); assert_eq!(class.poly_mode, Some(PolymorphismMode::Abstract)); - assert_eq!(unit.implementations.len(), 0); + + // classes have implementation because they are treated as other POUs + assert_eq!(unit.implementations.len(), 1); } #[test] @@ -38,7 +56,9 @@ fn simple_class2_can_be_parsed() { assert_eq!(class.name, "MyClass2"); assert_eq!(class.poly_mode, Some(PolymorphismMode::Final)); - assert_eq!(unit.implementations.len(), 0); + + // classes have implementation because they are treated as other POUs + assert_eq!(unit.implementations.len(), 1); } #[test] @@ -48,7 +68,9 @@ fn method_with_defaults_can_be_parsed() { let class = &unit.units[0]; assert_eq!(class.pou_type, PouType::Class); - assert_eq!(unit.implementations.len(), 1); + + // classes have implementation because they are treated as other POUs + assert_eq!(unit.implementations.len(), 2); let method_pou = &unit.units[1]; assert_eq!(method_pou.pou_type, PouType::Method { owner_class: "MyClass".into() }); @@ -68,7 +90,9 @@ fn method_can_be_parsed() { let class = &unit.units[0]; assert_eq!(class.pou_type, PouType::Class); - assert_eq!(unit.implementations.len(), 1); + + // classes have implementation because they are treated as other POUs + assert_eq!(unit.implementations.len(), 2); let method_pou = &unit.units[1]; assert_eq!(method_pou.pou_type, PouType::Method { owner_class: "MyClass".into() }); @@ -88,7 +112,9 @@ fn two_methods_can_be_parsed() { let class = &unit.units[0]; assert_eq!(class.pou_type, PouType::Class); - assert_eq!(unit.implementations.len(), 2); + + // classes have implementation because they are treated as other POUs + assert_eq!(unit.implementations.len(), 3); let method1 = &unit.implementations[0]; assert_eq!(method1.name, "MyClass.testMethod2"); @@ -110,7 +136,9 @@ fn method_with_return_type_can_be_parsed() { let method_pou = &unit.units[1]; assert_eq!(method_pou.pou_type, PouType::Method { owner_class: "MyClass".into() }); let method = &unit.implementations[0]; - assert_eq!(unit.implementations.len(), 1); + + // classes have implementation because they are treated as other POUs + assert_eq!(unit.implementations.len(), 2); assert_eq!(method_pou.name, "MyClass.testMethod3"); assert_eq!(method.access, Some(AccessModifier::Private)); @@ -126,7 +154,9 @@ fn class_with_var_default_block() { let class = &unit.units[0]; assert_eq!(class.pou_type, PouType::Class); - assert_eq!(unit.implementations.len(), 0); + + // classes have implementation because they are treated as other POUs + assert_eq!(unit.implementations.len(), 1); let vblock = &class.variable_blocks[0]; assert_eq!(vblock.variables.len(), 0); @@ -144,7 +174,9 @@ fn class_with_var_non_retain_block() { let class = &unit.units[0]; assert_eq!(class.pou_type, PouType::Class); - assert_eq!(unit.implementations.len(), 0); + + // classes have implementation because they are treated as other POUs + assert_eq!(unit.implementations.len(), 1); let vblock = &class.variable_blocks[0]; assert_eq!(vblock.variables.len(), 0); @@ -162,7 +194,9 @@ fn class_with_var_retain_block() { let class = &unit.units[0]; assert_eq!(class.pou_type, PouType::Class); - assert_eq!(unit.implementations.len(), 0); + + // classes have implementation because they are treated as other POUs + assert_eq!(unit.implementations.len(), 1); let vblock = &class.variable_blocks[0]; assert_eq!(vblock.variables.len(), 0); @@ -180,7 +214,9 @@ fn method_with_var_block() { let class = &unit.units[0]; assert_eq!(class.pou_type, PouType::Class); - assert_eq!(unit.implementations.len(), 1); + + // classes have implementation because they are treated as other POUs + assert_eq!(unit.implementations.len(), 2); let method_pou = &unit.units[1]; let vblock = &method_pou.variable_blocks[0]; @@ -209,7 +245,9 @@ fn method_with_var_inout_blocks() { assert_eq!(class.pou_type, PouType::Class); let method_pou = &unit.units[1]; - assert_eq!(unit.implementations.len(), 1); + + // classes have implementation because they are treated as other POUs + assert_eq!(unit.implementations.len(), 2); assert_eq!(method_pou.variable_blocks.len(), 3); let vblock1 = &method_pou.variable_blocks[0]; @@ -237,6 +275,8 @@ fn fb_method_can_be_parsed() { let class = &unit.units[0]; assert_eq!(class.pou_type, PouType::FunctionBlock); + + // classes have implementation because they are treated as other POUs assert_eq!(unit.implementations.len(), 2); let method_pou = &unit.units[1]; @@ -262,6 +302,8 @@ fn fb_two_methods_can_be_parsed() { let class = &unit.units[0]; assert_eq!(class.pou_type, PouType::FunctionBlock); + + // classes have implementation because they are treated as other POUs assert_eq!(unit.implementations.len(), 3); let method1 = &unit.implementations[0]; @@ -288,6 +330,8 @@ fn fb_method_with_return_type_can_be_parsed() { let method_pou = &unit.units[1]; assert_eq!(method_pou.pou_type, PouType::Method { owner_class: "MyShinyFb".into() }); let method = &unit.implementations[0]; + + // classes have implementation because they are treated as other POUs assert_eq!(unit.implementations.len(), 2); assert_eq!(method_pou.name, "MyShinyFb.testMethod3"); diff --git a/src/parser/tests/expressions_parser_tests.rs b/src/parser/tests/expressions_parser_tests.rs index 38867d3eff..20ed8009d5 100644 --- a/src/parser/tests/expressions_parser_tests.rs +++ b/src/parser/tests/expressions_parser_tests.rs @@ -2823,6 +2823,7 @@ fn sized_string_as_function_return() { name_location: SourceRange::undefined(), generics: vec![], linkage: LinkageType::Internal, + super_class: None, }; assert_eq!(format!("{:?}", ast.units[0]), format!("{expected:?}")); @@ -2872,6 +2873,7 @@ fn array_type_as_function_return() { name_location: SourceRange::undefined(), generics: vec![], linkage: LinkageType::Internal, + super_class: None, }; 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 3a864b8710..0bff07fa6e 100644 --- a/src/parser/tests/function_parser_tests.rs +++ b/src/parser/tests/function_parser_tests.rs @@ -242,6 +242,7 @@ fn varargs_parameters_can_be_parsed() { poly_mode: None, generics: vec![], linkage: LinkageType::Internal, + super_class: None, }; assert_eq!(format!("{expected:#?}"), format!("{x:#?}").as_str()); } @@ -311,6 +312,7 @@ fn sized_varargs_parameters_can_be_parsed() { poly_mode: None, generics: vec![], linkage: LinkageType::Internal, + super_class: None, }; 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 75c9294172..51604ac6cf 100644 --- a/src/parser/tests/misc_parser_tests.rs +++ b/src/parser/tests/misc_parser_tests.rs @@ -72,6 +72,7 @@ fn exponent_literals_parsed_as_variables() { name_location: SourceRange::undefined(), generics: vec![], linkage: LinkageType::Internal, + super_class: None, }; assert_eq!(format!("{expected:#?}"), format!("{pou:#?}").as_str()); let implementation = &parse_result.implementations[0]; diff --git a/src/resolver.rs b/src/resolver.rs index fb6c404bb4..e8d4baf3fb 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -269,17 +269,22 @@ pub enum StatementAnnotation { resulting_type: String, }, /// a reference that resolves to a declared variable (e.g. `a` --> `PLC_PROGRAM.a`) + /// a.b + /// + /// a : Myclass Variable { /// the name of the variable's type (e.g. `"INT"`) resulting_type: String, /// the fully qualified name of this variable (e.g. `"MyFB.a"`) qualified_name: String, - /// denotes wheter this variable is declared as a constant + /// denotes whether this variable is declared as a constant constant: bool, - /// denotes the varialbe type of this varialbe, hence whether it is an input, output, etc. + /// denotes the variable type of this variable, hence whether it is an input, output, etc. argument_type: ArgumentType, /// denotes whether this variable-reference should be automatically dereferenced when accessed is_auto_deref: bool, + /// the typename of the pou that accesses this variable, None if it is the same as in the qualified name + accessing_type: Option, }, /// a reference to a function Function { @@ -314,6 +319,19 @@ impl StatementAnnotation { pub fn new_value(type_name: String) -> Self { StatementAnnotation::Value { resulting_type: type_name } } + + pub fn is_auto_deref(&self) -> bool { + matches!(self, StatementAnnotation::Variable { is_auto_deref: true, .. }) + } + + pub fn is_fat_pointer(&self, index: &Index) -> bool { + if let StatementAnnotation::Variable { is_auto_deref: true, resulting_type, .. } = self { + //Get original type + index.find_type(resulting_type).filter(|it| it.is_class()).is_some() + } else { + false + } + } } impl From<&PouIndexEntry> for StatementAnnotation { @@ -421,6 +439,13 @@ pub trait AnnotationMap { } } + // fn get_accessing_type(&self, s: &AstStatement) -> Option<&str> { + // match self.get(s) { + // Some(StatementAnnotation::Variable { accessing_type, ..}) => accessing_type.as_str(), + // _ => None, + // } + // } + fn get_qualified_name(&self, s: &AstStatement) -> Option<&str> { match self.get(s) { Some(StatementAnnotation::Function { qualified_name, .. }) => Some(qualified_name.as_str()), @@ -680,6 +705,7 @@ impl<'i> TypeAnnotator<'i> { fn visit_pou(&mut self, ctx: &VisitorContext, pou: &'i Pou) { self.dependencies.insert(Dependency::Datatype(pou.name.clone())); + //TODO dependency on super class let pou_ctx = ctx.with_pou(pou.name.as_str()); for block in &pou.variable_blocks { for variable in &block.variables { @@ -770,7 +796,7 @@ impl<'i> TypeAnnotator<'i> { { if let Some(v) = self.index.find_member(qualifier, variable_name) { if let Some(target_type) = self.index.find_effective_type_by_name(v.get_type_name()) { - self.annotate(left.as_ref(), to_variable_annotation(v, self.index, false)); + self.annotate(left.as_ref(), to_variable_annotation(v, self.index, false, None)); self.annotation_map.annotate_type_hint( right.as_ref(), StatementAnnotation::value(v.get_type_name()), @@ -953,6 +979,15 @@ impl<'i> TypeAnnotator<'i> { if resolved_names.insert(Dependency::Datatype(datatype.get_name().to_string())) { match datatype.get_type_information() { DataTypeInformation::Struct { members, .. } => { + if let Some(pou) = self.index.find_pou(datatype_name) { + if pou.is_class() { + resolved_names = self.get_datatype_dependencies( + format!("__fat_pointer_to_{datatype_name}").as_str(), + resolved_names, + ); + } + } + for member in members { resolved_names = self.get_datatype_dependencies(member.get_type_name(), resolved_names); @@ -1259,8 +1294,8 @@ impl<'i> TypeAnnotator<'i> { .or_else(|| self.index.find_enum_element(qualifier, name.as_str())) // 3rd try - look for a method qualifier.name .map_or_else( - || self.index.find_pou(&qualified_name(qualifier, name)).map(|it| it.into()), - |v| Some(to_variable_annotation(v, self.index, ctx.constant)), + || self.index.find_method(qualifier, name).map(|it| it.into()), + |v| Some(to_variable_annotation(v, self.index, ctx.constant, Some(qualifier))), ) } else { // if we see no qualifier, we try some strategies ... @@ -1281,8 +1316,9 @@ impl<'i> TypeAnnotator<'i> { Some(m) } }) - .map(|v| to_variable_annotation(v, self.index, ctx.constant)) + .map(|v| to_variable_annotation(v, self.index, ctx.constant, None)) .or_else(|| { + // find parent of super class to start the search // ... then check if we're in a method and we're referencing // a member variable of the corresponding class self.index @@ -1290,7 +1326,7 @@ impl<'i> TypeAnnotator<'i> { .filter(|it| matches!(it, PouIndexEntry::Method { .. })) .and_then(PouIndexEntry::get_instance_struct_type_name) .and_then(|class_name| self.index.find_member(class_name, name)) - .map(|v| to_variable_annotation(v, self.index, ctx.constant)) + .map(|v| to_variable_annotation(v, self.index, ctx.constant, None)) }) .or_else(|| { // try to find a local action with this name @@ -1316,7 +1352,7 @@ impl<'i> TypeAnnotator<'i> { // ... last option is a global variable, where we ignore the current pou's name as a qualifier self.index .find_global_variable(name) - .map(|v| to_variable_annotation(v, self.index, ctx.constant)) + .map(|v| to_variable_annotation(v, self.index, ctx.constant, None)) }) }; if let Some(annotation) = annotation { @@ -1473,7 +1509,9 @@ impl<'i> TypeAnnotator<'i> { unreachable!("must be a reference to a VLA") }; - let Some(argument_type) = self.index.get_pou_members(pou) + let Some(argument_type) = self.index.get_all_pou_members_recursively(pou) + .get(pou) + .unwrap() .iter() .filter(|it| it.get_name() == name) .map(|it| it.get_declaration_type()) @@ -1487,12 +1525,30 @@ impl<'i> TypeAnnotator<'i> { constant: false, argument_type, is_auto_deref: false, + accessing_type: None, }; self.annotation_map.annotate_type_hint(statement, hint_annotation) } } } + fn resolve_fat_pointer(&mut self, m: &VariableIndexEntry) -> String { + let words = m.data_type_name.split('_').collect::>(); + let class_name = words.last().unwrap().to_owned(); + let class_pou = self.index.find_pou(class_name); + if class_pou.is_none() { + m.get_type_name().to_string() + } else if class_pou.unwrap().is_class() { + if m.is_in_parameter_by_ref() { + format!("__fat_pointer_to_{class_name}") + } else { + m.get_type_name().to_string() + } + } else { + m.get_type_name().to_string() + } + } + fn visit_call_statement(&mut self, statement: &AstStatement, ctx: &VisitorContext) { let (operator, parameters_stmt) = if let AstStatement::CallStatement { operator, parameters, .. } = statement { @@ -1530,10 +1586,13 @@ impl<'i> TypeAnnotator<'i> { for m in self.index.get_declared_parameters(operator_qualifier).into_iter() { if let Some(p) = parameters.next() { - let type_name = m.get_type_name(); - if let Some((key, candidate)) = - TypeAnnotator::get_generic_candidate(self.index, &self.annotation_map, type_name, p) - { + let type_name = self.resolve_fat_pointer(m); + if let Some((key, candidate)) = TypeAnnotator::get_generic_candidate( + self.index, + &self.annotation_map, + type_name.as_str(), + p, + ) { generics_candidates .entry(key.to_string()) .or_insert_with(std::vec::Vec::new) @@ -1798,6 +1857,7 @@ fn to_variable_annotation( v: &VariableIndexEntry, index: &Index, constant_override: bool, + accessing_type: Option<&str>, ) -> StatementAnnotation { const AUTO_DEREF: bool = true; const NO_DEREF: bool = false; @@ -1823,6 +1883,7 @@ fn to_variable_annotation( constant: v.is_constant() || constant_override, argument_type: v.get_declaration_type(), is_auto_deref, + accessing_type: accessing_type.map(|it| it.to_string()), } } diff --git a/src/resolver/tests/resolve_expressions_tests.rs b/src/resolver/tests/resolve_expressions_tests.rs index 8f5af9bf55..97735aea1b 100644 --- a/src/resolver/tests/resolve_expressions_tests.rs +++ b/src/resolver/tests/resolve_expressions_tests.rs @@ -92,8 +92,8 @@ fn expt_binary_expression() { let (unit, mut index) = index_with_ids( " PROGRAM PRG - VAR - a,b : DINT; + VAR + a,b : DINT; c,d : REAL; e,f : LREAL; END_VAR @@ -251,10 +251,10 @@ fn complex_expressions_resolves_types_for_literals_directly() { let id_provider = IdProvider::default(); let (unit, mut index) = index_with_ids( "PROGRAM PRG - VAR - a : BYTE; - b : SINT; - c : INT; + VAR + a : BYTE; + b : SINT; + c : INT; END_VAR a := ((b + USINT#7) - c); END_PROGRAM", @@ -457,7 +457,7 @@ fn global_resolves_types() { li : LINT; uli : ULINT; END_VAR - + PROGRAM PRG b; w; @@ -539,7 +539,7 @@ fn resolve_binary_expressions() { li : LINT; uli : ULINT; END_VAR - + PROGRAM PRG b + b; b + w; @@ -578,7 +578,7 @@ fn necessary_promotions_should_be_type_hinted() { b : BYTE; di : DINT; END_VAR - + PROGRAM PRG b + di; b < di; @@ -622,7 +622,7 @@ fn necessary_promotions_between_real_and_literal_should_be_type_hinted() { VAR_GLOBAL f : REAL; END_VAR - + PROGRAM PRG f > 0; END_PROGRAM", @@ -705,10 +705,10 @@ fn pointer_expressions_resolve_types() { b; b^; END_PROGRAM - - TYPE MyInt: INT := 7; END_TYPE - TYPE MyIntRef: REF_TO INT; END_TYPE - TYPE MyAliasRef: REF_TO MyInt; END_TYPE + + TYPE MyInt: INT := 7; END_TYPE + TYPE MyIntRef: REF_TO INT; END_TYPE + TYPE MyAliasRef: REF_TO MyInt; END_TYPE ", id_provider.clone(), @@ -751,10 +751,10 @@ fn array_expressions_resolve_types() { z; z[2]; END_PROGRAM - - TYPE MyInt: INT := 7; END_TYPE - TYPE MyIntArray: ARRAY[0..10] OF INT := 7; END_TYPE - TYPE MyAliasArray: ARRAY[0..10] OF MyInt := 7; END_TYPE + + TYPE MyInt: INT := 7; END_TYPE + TYPE MyIntArray: ARRAY[0..10] OF INT := 7; END_TYPE + TYPE MyAliasArray: ARRAY[0..10] OF MyInt := 7; END_TYPE ", id_provider.clone(), @@ -792,7 +792,7 @@ fn qualified_expressions_resolve_types() { dw : DWORD; lw : LWORD; END_VAR - END_PROGRAM + END_PROGRAM PROGRAM PRG Other.b; @@ -821,7 +821,7 @@ fn pou_expressions_resolve_types() { let (unit, index) = index_with_ids( " PROGRAM OtherPrg - END_PROGRAM + END_PROGRAM FUNCTION OtherFunc : INT END_FUNCTION @@ -915,7 +915,7 @@ fn qualified_expressions_to_structs_resolve_types() { lw : LWORD; END_STRUCT END_TYPE - + TYPE MyStruct: STRUCT b : BYTE; w : WORD; @@ -926,7 +926,7 @@ fn qualified_expressions_to_structs_resolve_types() { END_TYPE PROGRAM PRG - VAR + VAR mys : MyStruct; END_VAR mys; @@ -960,7 +960,7 @@ fn qualified_expressions_to_inlined_structs_resolve_types() { let (unit, index) = index_with_ids( " PROGRAM PRG - VAR + VAR mys : STRUCT b : BYTE; w : WORD; @@ -1030,6 +1030,7 @@ fn function_expression_resolves_to_the_function_itself_not_its_return_type() { resulting_type: "INT".into(), constant: false, is_auto_deref: false, + accessing_type: None, argument_type: ArgumentType::ByVal(VariableType::Return), }), foo_annotation @@ -1111,7 +1112,7 @@ fn shadowed_function_is_annotated_correctly() { FUNCTION foo : DINT END_FUNCTION - PROGRAM prg + PROGRAM prg foo(); END_PROGRAM ", @@ -1137,7 +1138,7 @@ fn qualified_expressions_to_aliased_structs_resolve_types() { lw : LWORD; END_STRUCT END_TYPE - + TYPE MyStruct: STRUCT b : BYTE; w : WORD; @@ -1151,7 +1152,7 @@ fn qualified_expressions_to_aliased_structs_resolve_types() { TYPE AliasedNextStruct : NextStruct; END_TYPE PROGRAM PRG - VAR + VAR mys : AliasedMyStruct; END_VAR mys; @@ -1193,7 +1194,7 @@ fn qualified_expressions_to_fbs_resolve_types() { END_FUNCTION_BLOCK PROGRAM PRG - VAR + VAR fb : MyFb; END_VAR fb; @@ -1221,7 +1222,7 @@ fn qualified_expressions_dont_fallback_to_globals() { " VAR_GLOBAL x : DINT; - END_VAR + END_VAR TYPE MyStruct: STRUCT y : INT; @@ -1246,6 +1247,7 @@ fn qualified_expressions_dont_fallback_to_globals() { resulting_type: "INT".into(), constant: false, is_auto_deref: false, + accessing_type: Some("MyStruct".to_string()), argument_type: ArgumentType::ByVal(VariableType::Input), }), annotations.get(&statements[1]) @@ -1269,7 +1271,7 @@ fn function_parameter_assignments_resolve_types() { PROGRAM PRG foo(x := 3, y => 6); END_PROGRAM - + TYPE MyType: INT; END_TYPE ", id_provider.clone(), @@ -1415,7 +1417,7 @@ fn actions_are_resolved() { prg.foo; END_PROGRAM ACTIONS prg - ACTION foo + ACTION foo END_ACTION END_ACTIONS @@ -1473,11 +1475,12 @@ fn method_references_are_resolved() { resulting_type: "INT".into(), constant: false, is_auto_deref: false, + accessing_type: None, argument_type: ArgumentType::ByVal(VariableType::Return), }), annotation ); - let method_call = &unit.implementations[1].statements[0]; + let method_call = &unit.implementations[2].statements[0]; if let AstStatement::CallStatement { operator, .. } = method_call { assert_eq!( Some(&StatementAnnotation::Function { @@ -1595,7 +1598,7 @@ fn const_flag_is_calculated_when_resolving_simple_references() { VAR_GLOBAL CONSTANT cg : INT := 1; END_VAR - + VAR_GLOBAL g : INT := 1; END_VAR @@ -1605,7 +1608,7 @@ fn const_flag_is_calculated_when_resolving_simple_references() { cl : INT; END_VAR - VAR + VAR l : INT; END_VAR @@ -1644,7 +1647,7 @@ fn const_flag_is_calculated_when_resolving_qualified_variables() { b : BYTE; END_STRUCT END_TYPE - + TYPE MyStruct: STRUCT b : BYTE; next : NextStruct; @@ -1652,10 +1655,10 @@ fn const_flag_is_calculated_when_resolving_qualified_variables() { END_TYPE PROGRAM PRG - VAR + VAR mys : MyStruct; END_VAR - VAR CONSTANT + VAR CONSTANT cmys : MyStruct; END_VAR @@ -1694,7 +1697,7 @@ fn const_flag_is_calculated_when_resolving_qualified_variables_over_prgs() { b : BYTE; END_STRUCT END_TYPE - + TYPE MyStruct: STRUCT b : BYTE; next : NextStruct; @@ -1705,12 +1708,12 @@ fn const_flag_is_calculated_when_resolving_qualified_variables_over_prgs() { other.mys.next.b; other.cmys.next.b; END_PROGRAM - + PROGRAM other - VAR + VAR mys : MyStruct; END_VAR - VAR CONSTANT + VAR CONSTANT cmys : MyStruct; END_VAR @@ -1744,9 +1747,9 @@ fn const_flag_is_calculated_when_resolving_enum_literals() { " TYPE Color: (red, green, yellow); END_TYPE - + PROGRAM other - VAR + VAR state: (OPEN, CLOSE); END_VAR red; @@ -1897,7 +1900,7 @@ fn enum_initialization_is_annotated_correctly() { let id_provider = IdProvider::default(); let (unit, mut index) = index_with_ids( " TYPE MyEnum : BYTE (zero, aa, bb := 7, cc); END_TYPE - + PROGRAM PRG VAR_TEMP x : MyEnum := 1; @@ -2152,7 +2155,7 @@ fn case_conditions_type_hint_test() { let id_provider = IdProvider::default(); let (unit, mut index) = index_with_ids( " - PROGRAM prg + PROGRAM prg VAR x : BYTE; y : BYTE; @@ -2240,9 +2243,9 @@ fn struct_variable_initialization_annotates_initializer() { a: DINT; b: DINT; END_STRUCT END_TYPE - VAR_GLOBAL - a : MyStruct := (a:=3, b:=5); - b : MyStruct := (a:=3); + VAR_GLOBAL + a : MyStruct := (a:=3, b:=5); + b : MyStruct := (a:=3); END_VAR ", id_provider.clone(), @@ -2294,10 +2297,10 @@ fn deep_struct_variable_initialization_annotates_initializer() { v: Point; q: Point; END_STRUCT END_TYPE - VAR_GLOBAL + VAR_GLOBAL a : MyStruct := ( - v := (a := 1, b := 2), - q := (b := 3)); + v := (a := 1, b := 2), + q := (b := 3)); END_VAR ", id_provider.clone(), @@ -2369,7 +2372,7 @@ fn inouts_should_be_annotated_according_to_auto_deref() { let id_provider = IdProvider::default(); let (unit, mut index) = index_with_ids( " - PROGRAM foo + PROGRAM foo VAR_IN_OUT inout : DINT; END_VAR @@ -2394,7 +2397,7 @@ fn action_call_should_be_annotated() { let id_provider = IdProvider::default(); let (unit, mut index) = index_with_ids( " - PROGRAM prg + PROGRAM prg VAR x : DINT; END_VAR @@ -2425,7 +2428,7 @@ fn action_body_gets_resolved() { let id_provider = IdProvider::default(); let (unit, mut index) = index_with_ids( " - PROGRAM prg + PROGRAM prg VAR x : DINT; END_VAR @@ -2453,6 +2456,7 @@ fn action_body_gets_resolved() { resulting_type: "DINT".to_string(), constant: false, is_auto_deref: false, + accessing_type: None, argument_type: ArgumentType::ByVal(VariableType::Local), }), a @@ -2473,7 +2477,7 @@ fn class_method_gets_annotated() { VAR x, y : BYTE; END_VAR - + METHOD testMethod VAR_INPUT myMethodArg : DINT; END_VAR VAR myMethodLocalVar : SINT; END_VAR @@ -2588,7 +2592,7 @@ fn array_accessor_in_struct_array_is_annotated() { data : MyStruct; i : INT; END_VAR - + data.arr1[i]; END_PROGRAM @@ -2622,7 +2626,7 @@ fn type_hint_should_not_hint_to_the_effective_type_but_to_the_original() { PROGRAM Main VAR x : MyInt; - END_VAR + END_VAR x := 7; END_PROGRAM "#, @@ -2652,7 +2656,7 @@ fn null_statement_should_get_a_valid_type_hint() { PROGRAM Main VAR x : POINTER TO BYTE; - END_VAR + END_VAR x := NULL; END_PROGRAM "#, @@ -2732,7 +2736,7 @@ fn string_compare_should_resolve_to_bool() { let id_provider = IdProvider::default(); let (unit, mut index) = index_with_ids( r#" - FUNCTION STRING_EQUAL: BOOL + FUNCTION STRING_EQUAL: BOOL VAR a,b : STRING; END_VAR END_FUNCTION; @@ -2740,7 +2744,7 @@ fn string_compare_should_resolve_to_bool() { PROGRAM Main VAR a,b: STRING; - END_VAR + END_VAR a = b; END_PROGRAM "#, @@ -2763,7 +2767,7 @@ fn assigning_lword_to_ptr_will_annotate_correctly() { VAR a : POINTER TO INT; b : DWORD; - END_VAR + END_VAR b := a; END_PROGRAM "#, @@ -2792,7 +2796,7 @@ fn assigning_ptr_to_lword_will_annotate_correctly() { VAR a : POINTER TO INT; b : DWORD; - END_VAR + END_VAR a := b; END_PROGRAM "#, @@ -2821,7 +2825,7 @@ fn assigning_ptr_to_lword_will_annotate_correctly2() { VAR a : POINTER TO INT; b : DWORD; - END_VAR + END_VAR b := a^; END_PROGRAM "#, @@ -2855,7 +2859,7 @@ fn address_of_is_annotated_correctly() { PROGRAM Main VAR b : INT; - END_VAR + END_VAR &b; END_PROGRAM "#, @@ -2947,7 +2951,7 @@ fn and_statement_of_bools_results_in_bool() { VAR a,b : BOOL; END_VAR - + a AND b; END_PROGRAM ", @@ -2972,7 +2976,7 @@ fn and_statement_of_dints_results_in_dint() { a,b : DINT; c,d : INT; END_VAR - + a AND b; c AND d; END_PROGRAM @@ -3067,9 +3071,9 @@ fn function_block_initialization_test() { END_FUNCTION_BLOCK - PROGRAM main + PROGRAM main VAR - timer : TON := (PT := T#0s); + timer : TON := (PT := T#0s); END_VAR END_PROGRAM ", @@ -3090,7 +3094,8 @@ fn function_block_initialization_test() { qualified_name: "TON.PT".into(), constant: false, argument_type: ArgumentType::ByVal(VariableType::Input), - is_auto_deref: false + is_auto_deref: false, + accessing_type: None, } ) } else { @@ -3110,7 +3115,7 @@ fn undeclared_varargs_type_hint_promoted_correctly() { END_VAR END_FUNCTION - PROGRAM main + PROGRAM main VAR float: REAL := 3.0; double: LREAL := 4.0; @@ -3220,7 +3225,8 @@ fn resolve_return_variable_in_nested_call() { qualified_name: "main.main".to_string(), constant: false, argument_type: ArgumentType::ByVal(VariableType::Return), - is_auto_deref: false + is_auto_deref: false, + accessing_type: None, } ) } @@ -3286,8 +3292,8 @@ fn multiple_pointer_referencing_annotates_correctly() { let (unit, mut index) = index_with_ids( " PROGRAM PRG - VAR - a : BYTE; + VAR + a : BYTE; END_VAR &&a; &&&a; @@ -3317,7 +3323,7 @@ fn multiple_pointer_with_dereference_annotates_and_nests_correctly() { let (unit, mut index) = index_with_ids( " PROGRAM PRG - VAR + VAR a : BYTE; END_VAR (&&a)^; @@ -3353,8 +3359,8 @@ fn multiple_negative_annotates_correctly() { let (unit, mut index) = index_with_ids( " PROGRAM PRG - VAR - a : DINT; + VAR + a : DINT; END_VAR --a; -(-a); @@ -3466,7 +3472,7 @@ fn parameter_down_cast_test() { i : SINT; ii : INT; di : DINT; - li : LINT; + li : LINT; END_VAR foo( ii, // downcast @@ -3658,13 +3664,13 @@ fn action_call_statement_parameters_are_annotated_with_a_type_hint() { VAR var1 : ARRAY[0..10] OF WSTRING; var2 : ARRAY[0..10] OF WSTRING; - END_VAR + END_VAR VAR_INPUT in1 : DINT; in2 : LWORD; - END_VAR + END_VAR END_FUNCTION_BLOCK - + ACTIONS fb_t ACTION foo END_ACTION @@ -4001,7 +4007,7 @@ fn vla_call_statement_with_nested_arrays() { END_VAR foo(arr[1]); END_FUNCTION - + FUNCTION foo : DINT VAR_INPUT vla: ARRAY[*] OF DINT; @@ -4105,3 +4111,404 @@ fn multi_dim_vla_access_assignment_receives_the_correct_type_hint() { // RHS resolves to INT and receives type-hint to DINT assert_type_and_hint!(&annotations, &index, right.as_ref(), "INT", Some("DINT")); } + +#[test] +fn override_is_resolved() { + let id_provider = IdProvider::default(); + let (unit, index) = index_with_ids( + " + CLASS cls + METHOD foo : INT + END_METHOD + METHOD bar : INT + END_METHOD + END_CLASS + + CLASS cls2 EXTENDS cls + METHOD OVERRIDE foo : INT + END_METHOD + END_CLASS + + FUNCTION_BLOCK fb + VAR + myClass : cls2; + END_VAR + + myClass.foo(); + myClass.bar(); + END_FUNCTION_BLOCK + ", + id_provider.clone(), + ); + + let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, id_provider); + let method_call = &unit.implementations[5].statements[0]; + if let AstStatement::CallStatement { operator, .. } = method_call { + assert_eq!( + Some(&StatementAnnotation::Function { + return_type: "INT".to_string(), + qualified_name: "cls2.foo".to_string(), + call_name: None, + }), + annotations.get(operator) + ); + } + let method_call = &unit.implementations[5].statements[1]; + if let AstStatement::CallStatement { operator, .. } = method_call { + assert_eq!( + Some(&StatementAnnotation::Function { + return_type: "INT".to_string(), + qualified_name: "cls.bar".to_string(), + call_name: None, + }), + annotations.get(operator) + ); + } +} + +#[test] +fn override_in_grandparent_is_resolved() { + let id_provider = IdProvider::default(); + let (unit, index) = index_with_ids( + " + CLASS cls + METHOD foo : INT + END_METHOD + METHOD bar : INT + END_METHOD + END_CLASS + + CLASS cls1 EXTENDS cls + METHOD OVERRIDE foo : INT + END_METHOD + END_CLASS + + CLASS cls2 EXTENDS cls1 + METHOD OVERRIDE foo : INT + END_METHOD + END_CLASS + + FUNCTION_BLOCK fb + VAR + myClass : cls2; + END_VAR + + myClass.foo(); + myClass.bar(); + END_FUNCTION_BLOCK + ", + id_provider.clone(), + ); + + let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, id_provider); + let method_call = &unit.implementations[7].statements[0]; + if let AstStatement::CallStatement { operator, .. } = method_call { + assert_eq!( + Some(&StatementAnnotation::Function { + return_type: "INT".to_string(), + qualified_name: "cls2.foo".to_string(), + call_name: None, + }), + annotations.get(operator) + ); + } + let method_call = &unit.implementations[7].statements[1]; + if let AstStatement::CallStatement { operator, .. } = method_call { + assert_eq!( + Some(&StatementAnnotation::Function { + return_type: "INT".to_string(), + qualified_name: "cls.bar".to_string(), + call_name: None, + }), + annotations.get(operator) + ); + } +} + +#[test] +fn annotate_variable_in_parent_class() { + let id_provider = IdProvider::default(); + let (unit, index) = index_with_ids( + " + CLASS cls1 + VAR + LIGHT: BOOL; + END_VAR + END_CLASS + + FUNCTION_BLOCK cls2 EXTENDS cls1 + VAR + Light2 : BOOL; + END_VAR + LIGHT := TRUE; + Light2 := LIGHT; + END_FUNCTION_BLOCK + ", + id_provider.clone(), + ); + let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, id_provider); + + if let AstStatement::Assignment { right, .. } = &unit.implementations[1].statements[1] { + let annotation = annotations.get(right); + assert_eq!( + &StatementAnnotation::Variable { + resulting_type: "BOOL".to_string(), + qualified_name: "cls1.LIGHT".to_string(), + constant: false, + argument_type: ArgumentType::ByVal(VariableType::Local,), + is_auto_deref: false, + accessing_type: None, + }, + annotation.unwrap() + ); + } + if let AstStatement::Assignment { left, .. } = &unit.implementations[1].statements[1] { + let annotation = annotations.get(left); + assert_eq!( + &StatementAnnotation::Variable { + resulting_type: "BOOL".to_string(), + qualified_name: "cls2.Light2".to_string(), + constant: false, + argument_type: ArgumentType::ByVal(VariableType::Local,), + is_auto_deref: false, + accessing_type: None, + }, + annotation.unwrap() + ); + } +} + +#[test] +fn annotate_variable_in_grandparent_class() { + let id_provider = IdProvider::default(); + let (unit, index) = index_with_ids( + " + CLASS cls0 + VAR + LIGHT: BOOL; + END_VAR + END_CLASS + + CLASS cls1 EXTENDS cls0 + END_CLASS + + FUNCTION_BLOCK cls2 EXTENDS cls1 + LIGHT := TRUE; + END_FUNCTION_BLOCK + ", + id_provider.clone(), + ); + let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, id_provider); + if let AstStatement::Assignment { left, .. } = &unit.implementations[2].statements[0] { + let annotation = annotations.get(left); + assert_eq!( + &StatementAnnotation::Variable { + resulting_type: "BOOL".to_string(), + qualified_name: "cls0.LIGHT".to_string(), + constant: false, + argument_type: ArgumentType::ByVal(VariableType::Local,), + is_auto_deref: false, + accessing_type: None, + }, + annotation.unwrap() + ); + } +} + +#[test] +fn annotate_variable_in_field() { + let id_provider = IdProvider::default(); + let (unit, index) = index_with_ids( + " + CLASS cls0 + VAR + LIGHT: BOOL; + END_VAR + END_CLASS + + CLASS cls1 EXTENDS cls0 + END_CLASS + + FUNCTION_BLOCK cls2 EXTENDS cls1 + END_FUNCTION_BLOCK + + PROGRAM prog + VAR + myClass : cls2; + END_VAR + + myClass.LIGHT := TRUE; + END_PROGRAM + ", + id_provider.clone(), + ); + let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, id_provider); + if let AstStatement::Assignment { left, .. } = &unit.implementations[3].statements[0] { + let annotation = annotations.get(left); + assert_eq!( + &StatementAnnotation::Variable { + resulting_type: "BOOL".to_string(), + qualified_name: "cls0.LIGHT".to_string(), + constant: false, + argument_type: ArgumentType::ByVal(VariableType::Local,), + is_auto_deref: false, + accessing_type: Some("cls2".to_string()), + }, + annotation.unwrap() + ); + } +} + +#[test] +fn annotate_method_in_super() { + let id_provider = IdProvider::default(); + let (unit, index) = index_with_ids( + " + CLASS cls0 + VAR + LIGHT: BOOL; + END_VAR + + METHOD meth : DINT + LIGHT := TRUE; + END_METHOD + END_CLASS + + CLASS cls1 EXTENDS cls0 + VAR + LIGHT1: BOOL; + END_VAR + + METHOD meth1 : DINT + LIGHT := TRUE; + LIGHT1 := TRUE; + END_METHOD + END_CLASS + + CLASS cls2 EXTENDS cls1 + VAR + LIGHT2: BOOL; + END_VAR + METHOD meth2 : DINT + LIGHT := TRUE; + LIGHT1 := TRUE; + LIGHT2 := TRUE; + END_METHOD + END_CLASS + ", + id_provider.clone(), + ); + let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, id_provider); + if let AstStatement::Assignment { left, .. } = &unit.implementations[2].statements[0] { + let annotation = annotations.get(left); + assert_eq!( + &StatementAnnotation::Variable { + resulting_type: "BOOL".to_string(), + qualified_name: "cls0.LIGHT".to_string(), + constant: false, + argument_type: ArgumentType::ByVal(VariableType::Local,), + is_auto_deref: false, + accessing_type: None, + }, + annotation.unwrap() + ); + } + if let AstStatement::Assignment { left, .. } = &unit.implementations[2].statements[1] { + let annotation = annotations.get(left); + assert_eq!( + &StatementAnnotation::Variable { + resulting_type: "BOOL".to_string(), + qualified_name: "cls1.LIGHT1".to_string(), + constant: false, + argument_type: ArgumentType::ByVal(VariableType::Local,), + is_auto_deref: false, + accessing_type: None, + }, + annotation.unwrap() + ); + } + if let AstStatement::Assignment { left, .. } = &unit.implementations[4].statements[0] { + let annotation = annotations.get(left); + assert_eq!( + &StatementAnnotation::Variable { + resulting_type: "BOOL".to_string(), + qualified_name: "cls0.LIGHT".to_string(), + constant: false, + argument_type: ArgumentType::ByVal(VariableType::Local,), + is_auto_deref: false, + accessing_type: None, + }, + annotation.unwrap() + ); + } + if let AstStatement::Assignment { left, .. } = &unit.implementations[4].statements[1] { + let annotation = annotations.get(left); + assert_eq!( + &StatementAnnotation::Variable { + resulting_type: "BOOL".to_string(), + qualified_name: "cls1.LIGHT1".to_string(), + constant: false, + argument_type: ArgumentType::ByVal(VariableType::Local,), + is_auto_deref: false, + accessing_type: None, + }, + annotation.unwrap() + ); + } + if let AstStatement::Assignment { left, .. } = &unit.implementations[4].statements[2] { + let annotation = annotations.get(left); + assert_eq!( + &StatementAnnotation::Variable { + resulting_type: "BOOL".to_string(), + qualified_name: "cls2.LIGHT2".to_string(), + constant: false, + argument_type: ArgumentType::ByVal(VariableType::Local,), + is_auto_deref: false, + accessing_type: None, + }, + annotation.unwrap() + ); + } +} + +// foo(cl) +// hint_annotation_ fat pointer +#[test] +fn annotate_fat_pointer() { + let id_provider = IdProvider::default(); + let (unit, index) = index_with_ids( + " + CLASS cls0 + METHOD foo + VAR_IN_OUT + myClass : cls1; + END_VAR + END_METHOD + END_CLASS + + CLASS cls1 + END_CLASS + + PROGRAM prog + VAR + callClass : cls0; + paramClass : cls1; + END_VAR + + callClass.foo(paramClass); + END_PROGRAM + ", + id_provider.clone(), + ); + let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, id_provider); + + let params = + if let AstStatement::CallStatement { parameters, .. } = &unit.implementations[3].statements[0] { + parameters.as_ref().as_ref().map(flatten_expression_list).unwrap_or_default() + } else { + unreachable!(); + }; + + insta::assert_debug_snapshot!(annotations.get_type_hint(params[0], &index)); + insta::assert_debug_snapshot!(annotations.get_type(params[0], &index)); +} diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__annotate_fat_pointer-2.snap b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__annotate_fat_pointer-2.snap new file mode 100644 index 0000000000..9602ecd1fc --- /dev/null +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__annotate_fat_pointer-2.snap @@ -0,0 +1,24 @@ +--- +source: src/resolver/tests/resolve_expressions_tests.rs +expression: "annotations.get_type(params[0], &index)" +--- +Some( + DataType { + name: "cls1", + initial_value: None, + information: Struct { + name: "cls1", + members: [], + source: Pou( + Class, + ), + }, + nature: Any, + location: SymbolLocation { + line_number: 9, + source_range: SourceRange { + range: 154..158, + }, + }, + }, +) diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__annotate_fat_pointer.snap b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__annotate_fat_pointer.snap new file mode 100644 index 0000000000..c7c2ba7621 --- /dev/null +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__annotate_fat_pointer.snap @@ -0,0 +1,67 @@ +--- +source: src/resolver/tests/resolve_expressions_tests.rs +expression: "annotations.get_type_hint(params[0], &index)" +--- +Some( + DataType { + name: "__fat_pointer_to_cls1", + initial_value: None, + information: Struct { + name: "__fat_pointer_to_cls1", + members: [ + VariableIndexEntry { + name: "pointer_to_cls1", + qualified_name: "__fat_pointer_to_cls1.pointer_to_cls1", + initial_value: None, + argument_type: ByVal( + Input, + ), + is_constant: false, + data_type_name: "__ptr_to_cls1_struct", + location_in_parent: 0, + linkage: Internal, + binding: None, + source_location: SymbolLocation { + line_number: 0, + source_range: SourceRange { + range: 0..0, + }, + }, + varargs: None, + accessing_type: None, + }, + VariableIndexEntry { + name: "pointer_to_cls1_vt", + qualified_name: "__fat_pointer_to_cls1.pointer_to_cls1_vt", + initial_value: None, + argument_type: ByVal( + Input, + ), + is_constant: false, + data_type_name: "__ptr_to_cls1_vt", + location_in_parent: 1, + linkage: Internal, + binding: None, + source_location: SymbolLocation { + line_number: 0, + source_range: SourceRange { + range: 0..0, + }, + }, + varargs: None, + accessing_type: None, + }, + ], + source: Internal( + FatPointer, + ), + }, + nature: Derived, + location: SymbolLocation { + line_number: 0, + source_range: SourceRange { + range: 0..0, + }, + }, + }, +) diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__resolve_recursive_function_call.snap b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__resolve_recursive_function_call.snap index 73acc3ce37..8280920bdb 100644 --- a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__resolve_recursive_function_call.snap +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__resolve_recursive_function_call.snap @@ -16,6 +16,7 @@ expression: annotated_types Local, ), is_auto_deref: false, + accessing_type: None, }, 2: Variable { resulting_type: "DINT", @@ -25,6 +26,7 @@ expression: annotated_types Input, ), is_auto_deref: false, + accessing_type: None, }, 6: Variable { resulting_type: "DINT", @@ -34,6 +36,7 @@ expression: annotated_types Local, ), is_auto_deref: false, + accessing_type: None, }, 5: Variable { resulting_type: "DINT", @@ -43,6 +46,7 @@ expression: annotated_types InOut, ), is_auto_deref: true, + accessing_type: None, }, 8: Variable { resulting_type: "DINT", @@ -52,6 +56,7 @@ expression: annotated_types Output, ), is_auto_deref: true, + accessing_type: None, }, 9: Variable { resulting_type: "DINT", @@ -61,6 +66,7 @@ expression: annotated_types Local, ), is_auto_deref: false, + accessing_type: None, }, 12: Value { resulting_type: "DINT", @@ -73,6 +79,7 @@ expression: annotated_types Local, ), is_auto_deref: false, + accessing_type: None, }, 13: Variable { resulting_type: "DINT", @@ -82,5 +89,6 @@ expression: annotated_types Return, ), is_auto_deref: false, + accessing_type: None, }, } diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__resolve_recursive_program_call.snap b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__resolve_recursive_program_call.snap index 520e48e507..702bb076a1 100644 --- a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__resolve_recursive_program_call.snap +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__resolve_recursive_program_call.snap @@ -14,6 +14,7 @@ expression: annotated_types Local, ), is_auto_deref: false, + accessing_type: None, }, 2: Variable { resulting_type: "DINT", @@ -23,6 +24,7 @@ expression: annotated_types Input, ), is_auto_deref: false, + accessing_type: None, }, 6: Variable { resulting_type: "DINT", @@ -32,6 +34,7 @@ expression: annotated_types Local, ), is_auto_deref: false, + accessing_type: None, }, 5: Variable { resulting_type: "DINT", @@ -41,6 +44,7 @@ expression: annotated_types InOut, ), is_auto_deref: true, + accessing_type: None, }, 8: Variable { resulting_type: "DINT", @@ -50,6 +54,7 @@ expression: annotated_types Output, ), is_auto_deref: false, + accessing_type: None, }, 9: Variable { resulting_type: "DINT", @@ -59,5 +64,6 @@ expression: annotated_types Local, ), is_auto_deref: false, + accessing_type: None, }, } diff --git a/src/tests/adr/annotated_ast.rs b/src/tests/adr/annotated_ast.rs index 4560fea01e..d7cb70173e 100644 --- a/src/tests/adr/annotated_ast.rs +++ b/src/tests/adr/annotated_ast.rs @@ -48,7 +48,8 @@ fn references_to_variables_are_annotated() { qualified_name: "prg.a".into(), constant: false, argument_type: ArgumentType::ByVal(VariableType::Local), - is_auto_deref: false + is_auto_deref: false, + accessing_type: None, } ); @@ -60,7 +61,8 @@ fn references_to_variables_are_annotated() { qualified_name: "gX".into(), constant: true, argument_type: ArgumentType::ByVal(VariableType::Global), - is_auto_deref: false + is_auto_deref: false, + accessing_type: None, } ); } @@ -105,6 +107,7 @@ fn different_types_of_annotations() { constant: false, // whether this variable is a constant or not is_auto_deref: false, // whether this pointerType should be automatically dereferenced argument_type: ArgumentType::ByVal(VariableType::Input), // the type of declaration + accessing_type: Some("POINT".to_string()), }) ); @@ -147,6 +150,7 @@ fn different_types_of_annotations() { constant: false, is_auto_deref: false, argument_type: ArgumentType::ByVal(VariableType::Input), + accessing_type: Some("Main".to_string()), }) ); // the qualified statement gets the annotation of the last segment (in this case "Main.in" of type INT) diff --git a/src/tests/adr/pou_adr.rs b/src/tests/adr/pou_adr.rs index 21be3895be..2e9467b778 100644 --- a/src/tests/adr/pou_adr.rs +++ b/src/tests/adr/pou_adr.rs @@ -79,6 +79,7 @@ fn programs_state_is_stored_in_a_struct() { }, }, varargs: None, + accessing_type: None, }, VariableIndexEntry { name: "io", @@ -99,6 +100,7 @@ fn programs_state_is_stored_in_a_struct() { }, }, varargs: None, + accessing_type: None, }, VariableIndexEntry { name: "o", @@ -119,6 +121,7 @@ fn programs_state_is_stored_in_a_struct() { }, }, varargs: None, + accessing_type: None, }, VariableIndexEntry { name: "v", @@ -139,6 +142,7 @@ fn programs_state_is_stored_in_a_struct() { }, }, varargs: None, + accessing_type: None, }, VariableIndexEntry { name: "vt", @@ -159,6 +163,7 @@ fn programs_state_is_stored_in_a_struct() { }, }, varargs: None, + accessing_type: None, }, ], source: Pou( diff --git a/src/tests/adr/vla_adr.rs b/src/tests/adr/vla_adr.rs index eec46da5f6..9b574528b2 100644 --- a/src/tests/adr/vla_adr.rs +++ b/src/tests/adr/vla_adr.rs @@ -54,6 +54,7 @@ fn representation() { }, }, varargs: None, + accessing_type: None, }, VariableIndexEntry { name: "dimensions", @@ -74,6 +75,7 @@ fn representation() { }, }, varargs: None, + accessing_type: None, }, ], source: Internal( @@ -281,6 +283,7 @@ fn pass() { }, }, varargs: None, + accessing_type: None, }, VariableIndexEntry { name: "dimensions", @@ -301,6 +304,7 @@ fn pass() { }, }, varargs: None, + accessing_type: None, }, ], source: Internal( diff --git a/src/typesystem.rs b/src/typesystem.rs index ee0ba3892b..59c646c0f4 100644 --- a/src/typesystem.rs +++ b/src/typesystem.rs @@ -86,6 +86,7 @@ pub const WSTRING_TYPE: &str = "WSTRING"; pub const CHAR_TYPE: &str = "CHAR"; pub const WCHAR_TYPE: &str = "WCHAR"; pub const VOID_TYPE: &str = "VOID"; +pub const FAT_POINTER_TYPE: &str = "FAT_POINTER"; pub const __VLA_TYPE: &str = "__VLA"; #[cfg(test)] @@ -165,6 +166,14 @@ impl DataType { self.get_type_information().is_vla() } + pub fn is_fat_pointer(&self) -> bool { + self.get_type_information().is_fat_pointer() + } + + pub fn is_class(&self) -> bool { + self.get_type_information().is_class() + } + /// returns true if this type is an array, struct or string pub fn is_aggregate_type(&self) -> bool { self.get_type_information().is_aggregate() @@ -334,6 +343,7 @@ impl StructSource { pub enum InternalType { VariableLengthArray { inner_type_name: String, ndims: usize }, __VLA, // used for error-reporting only + FatPointer, } type TypeId = String; @@ -473,6 +483,17 @@ impl DataTypeInformation { ) } + pub fn is_fat_pointer(&self) -> bool { + matches!( + self, + DataTypeInformation::Struct { source: StructSource::Internal(InternalType::FatPointer), .. } + ) + } + + pub fn is_class(&self) -> bool { + matches!(self, DataTypeInformation::Struct { source: StructSource::Pou(PouType::Class), .. }) + } + pub fn is_enum(&self) -> bool { matches!(self, DataTypeInformation::Enum { .. }) } @@ -641,6 +662,8 @@ impl DataTypeInformation { } } + /// Returns the inner type of an array + /// If self is not an array, does not return anything pub fn get_inner_array_type_name(&self) -> Option<&str> { match self { DataTypeInformation::Array { inner_type_name, .. } => Some(inner_type_name), @@ -648,6 +671,15 @@ impl DataTypeInformation { } } + /// Returns the inner type of a pointer + /// If self is not a pointer, does not return anything + pub fn get_inner_pointer_type(&self) -> Option<&str> { + match self { + DataTypeInformation::Pointer { inner_type_name, .. } => Some(inner_type_name), + _ => None, + } + } + pub fn is_compatible_char_and_string(&self, other: &DataTypeInformation) -> bool { match self.get_name() { CHAR_TYPE => matches!(other, DataTypeInformation::String { encoding: StringEncoding::Utf8, .. }), diff --git a/src/validation/pou.rs b/src/validation/pou.rs index 4ee54f01c1..3b42a067f5 100644 --- a/src/validation/pou.rs +++ b/src/validation/pou.rs @@ -1,4 +1,4 @@ -use plc_ast::ast::{Implementation, LinkageType, Pou, PouType}; +use plc_ast::ast::{Implementation, LinkageType, Pou, PouType, VariableBlockType}; use super::{ statement::visit_statement, variable::visit_variable_block, ValidationContext, Validator, Validators, @@ -20,6 +20,12 @@ pub fn visit_implementation( implementation: &Implementation, context: &ValidationContext<'_, T>, ) { + if implementation.pou_type == PouType::Class && !implementation.statements.is_empty() { + validator.push_diagnostic(Diagnostic::syntax_error( + "A class cannot have an implementation", + implementation.location.to_owned(), + )); + } if implementation.linkage != LinkageType::External { validate_action_container(validator, implementation); implementation.statements.iter().for_each(|s| { @@ -32,9 +38,46 @@ fn validate_pou(validator: &mut Validator, pou: &Pou, context: if pou.pou_type == PouType::Function { validate_function(validator, pou, context); }; + if pou.pou_type == PouType::Class { + validate_class(validator, pou, context); + }; + if pou.pou_type == PouType::Program { + validate_program(validator, pou); + } +} + +fn validate_class(validator: &mut Validator, pou: &Pou, context: &ValidationContext) { + // var in/out/inout blocks are not allowed inside of class declaration + if pou.variable_blocks.iter().any(|it| { + matches!( + it.variable_block_type, + VariableBlockType::InOut | VariableBlockType::Input(_) | VariableBlockType::Output + ) + }) { + validator.push_diagnostic(Diagnostic::syntax_error( + "A class cannot have a var in/out/inout blocks", + pou.name_location.to_owned(), + )); + } + + // classes cannot have a return type + if context.index.find_return_type(&pou.name).is_some() { + validator.push_diagnostic(Diagnostic::syntax_error( + "A class cannot have a return type", + pou.name_location.to_owned(), + )); + } } fn validate_function(validator: &mut Validator, pou: &Pou, context: &ValidationContext) { + // functions cannot use EXTENDS + if pou.super_class.is_some() { + validator.push_diagnostic(Diagnostic::syntax_error( + "A function cannot use EXTEND", + pou.name_location.to_owned(), + )); + } + let return_type = context.index.find_return_type(&pou.name); // functions must have a return type if return_type.is_none() { @@ -42,6 +85,16 @@ fn validate_function(validator: &mut Validator, pou: &Pou, con } } +fn validate_program(validator: &mut Validator, pou: &Pou) { + // programs cannot use EXTENDS + if pou.super_class.is_some() { + validator.push_diagnostic(Diagnostic::syntax_error( + "A program cannot use EXTEND", + pou.name_location.to_owned(), + )); + } +} + pub fn validate_action_container(validator: &mut Validator, implementation: &Implementation) { if implementation.pou_type == PouType::Action && implementation.type_name == "__unknown__" { validator.push_diagnostic(Diagnostic::missing_action_container(implementation.location.clone())); diff --git a/src/validation/tests/pou_validation_tests.rs b/src/validation/tests/pou_validation_tests.rs index 3bbb8c46bc..27ee808e09 100644 --- a/src/validation/tests/pou_validation_tests.rs +++ b/src/validation/tests/pou_validation_tests.rs @@ -18,6 +18,140 @@ fn actions_container_no_name() { assert_validation_snapshot!(&diagnostics); } +#[test] +fn class_has_implementation() { + // GIVEN CLASS with an implementation + // WHEN parse_and_validate is done + let diagnostics = parse_and_validate( + " + CLASS myCLASS + VAR + LIGHT: BOOL; + END_VAR + LIGHT := TRUE; + END_CLASS + ", + ); + // THEN there should be one diagnostic -> Class cannot have implementation + assert_validation_snapshot!(&diagnostics); +} + +#[test] +fn program_has_super_class() { + // GIVEN PROGRAM with a super class + // WHEN parse_and_validate is done + let diagnostics = parse_and_validate( + " + CLASS cls + END_CLASS + + PROGRAM prog EXTENDS cls + END_PROGRAM + ", + ); + // THEN there should be one diagnostic -> Program cannot have super class + assert_validation_snapshot!(&diagnostics); +} + +#[test] +fn function_has_super_class() { + // GIVEN FUNCTION with a super class + // WHEN parse_and_validate is done + let diagnostics = parse_and_validate( + " + CLASS cls + END_CLASS + + FUNCTION func EXTENDS cls + END_FUNCTION + ", + ); + // THEN there should be one diagnostic -> Function cannot have super class + assert_validation_snapshot!(&diagnostics); +} + +#[test] +fn class_with_return_type() { + // GIVEN class with a return type + // WHEN parse_and_validate is done + let diagnostics = parse_and_validate( + " + CLASS cls : INT + END_CLASS + ", + ); + // THEN there should be one diagnostic -> Class cannot have a return type + assert_validation_snapshot!(&diagnostics); +} + +#[test] +fn in_out_variable_not_allowed_in_class() { + // GIVEN class with a VAR_IN_OUT + // WHEN parse_and_validate is done + let diagnostics = parse_and_validate( + " + CLASS cls + VAR_IN_OUT + var1 : BOOL; + END_VAR + END_CLASS + ", + ); + // THEN there should be one diagnostic -> Class cannot have a var in/out/inout block + assert_validation_snapshot!(&diagnostics); +} + +#[test] +fn input_variable_not_allowed_in_class() { + // GIVEN class with a VAR_INPUT + // WHEN parse_and_validate is done + let diagnostics = parse_and_validate( + " + CLASS cls + VAR_INPUT + var1 : BOOL; + END_VAR + END_CLASS + ", + ); + // THEN there should be one diagnostic -> Class cannot have a var in/out/inout block + assert_validation_snapshot!(&diagnostics); +} + +#[test] +fn output_variable_not_allowed_in_class() { + // GIVEN class with a VAR_OUTPUT + // WHEN parse_and_validate is done + let diagnostics = parse_and_validate( + " + CLASS cls + VAR_OUTPUT + var1 : BOOL; + END_VAR + END_CLASS + ", + ); + // THEN there should be one diagnostic -> Class cannot have a var in/out/inout block + assert_validation_snapshot!(&diagnostics); +} + +#[test] +fn local_variable_allowed_in_class() { + // GIVEN class with a VAR + // WHEN parse_and_validate is done + let diagnostics = parse_and_validate( + " + CLASS cls + VAR + var1 : BOOL; + END_VAR + END_CLASS + ", + ); + // THEN there should be no diagnostic -> Class can have local var block + assert_validation_snapshot!(&diagnostics); +} + #[test] fn do_not_validate_external() { // GIVEN an external program with a simple assignment diff --git a/src/validation/tests/snapshots/rusty__validation__tests__duplicates_validation_test__duplicate_pous_validation.snap b/src/validation/tests/snapshots/rusty__validation__tests__duplicates_validation_test__duplicate_pous_validation.snap index 0650126bb2..6474357a1b 100644 --- a/src/validation/tests/snapshots/rusty__validation__tests__duplicates_validation_test__duplicate_pous_validation.snap +++ b/src/validation/tests/snapshots/rusty__validation__tests__duplicates_validation_test__duplicate_pous_validation.snap @@ -1,9 +1,18 @@ --- source: src/validation/tests/duplicates_validation_test.rs -expression: make_readable(&diagnostics) +expression: res --- SyntaxError { message: "foo: Ambiguous callable symbol.", range: [SourceRange { range: 25..28 }, SourceRange { range: 74..77 }], err_no: duplicate_symbol } SyntaxError { message: "foo: Ambiguous callable symbol.", range: [SourceRange { range: 74..77 }, SourceRange { range: 25..28 }], err_no: duplicate_symbol } +SyntaxError { message: "__ptr_to_foo_struct can not be used as a name because it is a built-in datatype", range: [SourceRange { range: 0..0 }, SourceRange { range: 0..0 }], err_no: type__invalid_name } +SyntaxError { message: "__ptr_to_foo_struct can not be used as a name because it is a built-in datatype", range: [SourceRange { range: 0..0 }, SourceRange { range: 0..0 }], err_no: type__invalid_name } +SyntaxError { message: "__ptr_to_foo_struct can not be used as a name because it is a built-in datatype", range: [SourceRange { range: 0..0 }, SourceRange { range: 0..0 }], err_no: type__invalid_name } +SyntaxError { message: "__ptr_to_foo_vt can not be used as a name because it is a built-in datatype", range: [SourceRange { range: 0..0 }, SourceRange { range: 0..0 }], err_no: type__invalid_name } +SyntaxError { message: "__ptr_to_foo_vt can not be used as a name because it is a built-in datatype", range: [SourceRange { range: 0..0 }, SourceRange { range: 0..0 }], err_no: type__invalid_name } +SyntaxError { message: "__ptr_to_foo_vt can not be used as a name because it is a built-in datatype", range: [SourceRange { range: 0..0 }, SourceRange { range: 0..0 }], err_no: type__invalid_name } +SyntaxError { message: "__fat_pointer_to_foo can not be used as a name because it is a built-in datatype", range: [SourceRange { range: 0..0 }, SourceRange { range: 0..0 }], err_no: type__invalid_name } +SyntaxError { message: "__fat_pointer_to_foo can not be used as a name because it is a built-in datatype", range: [SourceRange { range: 0..0 }, SourceRange { range: 0..0 }], err_no: type__invalid_name } +SyntaxError { message: "__fat_pointer_to_foo can not be used as a name because it is a built-in datatype", range: [SourceRange { range: 0..0 }, SourceRange { range: 0..0 }], err_no: type__invalid_name } SyntaxError { message: "foo: Duplicate symbol.", range: [SourceRange { range: 25..28 }, SourceRange { range: 74..77 }, SourceRange { range: 116..119 }], err_no: duplicate_symbol } SyntaxError { message: "foo: Duplicate symbol.", range: [SourceRange { range: 74..77 }, SourceRange { range: 25..28 }, SourceRange { range: 116..119 }], err_no: duplicate_symbol } SyntaxError { message: "foo: Duplicate symbol.", range: [SourceRange { range: 116..119 }, SourceRange { range: 25..28 }, SourceRange { range: 74..77 }], err_no: duplicate_symbol } diff --git a/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__class_has_implementation.snap b/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__class_has_implementation.snap new file mode 100644 index 0000000000..3a715e174c --- /dev/null +++ b/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__class_has_implementation.snap @@ -0,0 +1,6 @@ +--- +source: src/validation/tests/pou_validation_tests.rs +expression: res +--- +SyntaxError { message: "A class cannot have an implementation", range: [SourceRange { range: 90..122 }], err_no: syntax__generic_error } + diff --git a/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__class_with_return_type.snap b/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__class_with_return_type.snap new file mode 100644 index 0000000000..0311782cde --- /dev/null +++ b/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__class_with_return_type.snap @@ -0,0 +1,6 @@ +--- +source: src/validation/tests/pou_validation_tests.rs +expression: res +--- +SyntaxError { message: "A class cannot have a return type", range: [SourceRange { range: 15..18 }], err_no: syntax__generic_error } + diff --git a/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__function_has_super_class.snap b/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__function_has_super_class.snap new file mode 100644 index 0000000000..3b92b4e645 --- /dev/null +++ b/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__function_has_super_class.snap @@ -0,0 +1,7 @@ +--- +source: src/validation/tests/pou_validation_tests.rs +expression: res +--- +SyntaxError { message: "A function cannot use EXTEND", range: [SourceRange { range: 55..59 }], err_no: syntax__generic_error } +SyntaxError { message: "Function Return type missing", range: [SourceRange { range: 55..59 }], err_no: pou__missing_return_type } + diff --git a/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__in_out_variable_not_allowed_in_class.snap b/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__in_out_variable_not_allowed_in_class.snap new file mode 100644 index 0000000000..bfb11d1362 --- /dev/null +++ b/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__in_out_variable_not_allowed_in_class.snap @@ -0,0 +1,6 @@ +--- +source: src/validation/tests/pou_validation_tests.rs +expression: res +--- +SyntaxError { message: "A class cannot have a var in/out/inout blocks", range: [SourceRange { range: 15..18 }], err_no: syntax__generic_error } + diff --git a/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__input_variable_not_allowed_in_class.snap b/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__input_variable_not_allowed_in_class.snap new file mode 100644 index 0000000000..bfb11d1362 --- /dev/null +++ b/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__input_variable_not_allowed_in_class.snap @@ -0,0 +1,6 @@ +--- +source: src/validation/tests/pou_validation_tests.rs +expression: res +--- +SyntaxError { message: "A class cannot have a var in/out/inout blocks", range: [SourceRange { range: 15..18 }], err_no: syntax__generic_error } + diff --git a/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__local_variable_allowed_in_class.snap b/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__local_variable_allowed_in_class.snap new file mode 100644 index 0000000000..7a5641a12d --- /dev/null +++ b/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__local_variable_allowed_in_class.snap @@ -0,0 +1,5 @@ +--- +source: src/validation/tests/pou_validation_tests.rs +expression: res +--- + diff --git a/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__output_variable_not_allowed_in_class.snap b/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__output_variable_not_allowed_in_class.snap new file mode 100644 index 0000000000..bfb11d1362 --- /dev/null +++ b/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__output_variable_not_allowed_in_class.snap @@ -0,0 +1,6 @@ +--- +source: src/validation/tests/pou_validation_tests.rs +expression: res +--- +SyntaxError { message: "A class cannot have a var in/out/inout blocks", range: [SourceRange { range: 15..18 }], err_no: syntax__generic_error } + diff --git a/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__program_has_super_class.snap b/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__program_has_super_class.snap new file mode 100644 index 0000000000..3a664911ff --- /dev/null +++ b/src/validation/tests/snapshots/rusty__validation__tests__pou_validation_tests__program_has_super_class.snap @@ -0,0 +1,6 @@ +--- +source: src/validation/tests/pou_validation_tests.rs +expression: res +--- +SyntaxError { message: "A program cannot use EXTEND", range: [SourceRange { range: 54..58 }], err_no: syntax__generic_error } + diff --git a/tests/correctness/classes.rs b/tests/correctness/classes.rs index 00df945960..038a207357 100644 --- a/tests/correctness/classes.rs +++ b/tests/correctness/classes.rs @@ -51,3 +51,356 @@ fn class_reference_in_pou() { let _: i32 = compile_and_run(source, &mut m); assert_eq!(m.x, 10); } + +#[test] +fn access_var_in_super_class() { + #[allow(dead_code)] + #[repr(C)] + struct MainType { + x: i16, + y: i16, + } + + let source = " + CLASS MyClass + VAR + x: INT; + END_VAR + END_CLASS + + CLASS MyClass2 EXTENDS MyCLASS + VAR + y: INT; + END_VAR + END_CLASS + + PROGRAM main + VAR + x : INT := 0; + y : INT := 0; + END_VAR + VAR_TEMP + cl : MyClass2; + END_VAR + cl.y := 2; + cl.x := 1; + x := cl.x; + y := cl.y; + END_PROGRAM + "; + + let mut m = MainType { x: 0, y: 0 }; + let _: i32 = compile_and_run(source, &mut m); + assert_eq!(m.x, 1); + assert_eq!(m.y, 2); +} + +#[test] +fn use_method_to_change_field_in_super() { + #[allow(dead_code)] + #[repr(C)] + struct MainType { + x: i16, + y: i16, + } + + let source = " + CLASS MyClass + VAR + x: INT; + END_VAR + END_CLASS + + CLASS MyClass2 EXTENDS MyCLASS + VAR + y: INT; + END_VAR + + METHOD change_y + y := 55; + END_METHOD + + METHOD change_x + x := 44; + END_METHOD + END_CLASS + + PROGRAM main + VAR + x : INT := 0; + y : INT := 0; + END_VAR + VAR_TEMP + cl : MyClass2; + END_VAR + cl.change_y(); + cl.change_x(); + x := cl.x; + y := cl.y; + END_PROGRAM + "; + + let mut m = MainType { x: 0, y: 0 }; + let _: i32 = compile_and_run(source, &mut m); + assert_eq!(m.x, 44); + assert_eq!(m.y, 55); +} + +#[test] +fn call_method_in_parent() { + #[allow(dead_code)] + #[repr(C)] + struct MainType { + x: i16, + } + + let source = " + CLASS MyClass + VAR + x: INT; + END_VAR + METHOD change_x + x := 10; + END_METHOD + END_CLASS + + CLASS MyClass2 EXTENDS MyCLASS + + END_CLASS + + PROGRAM main + VAR + x : INT := 0; + END_VAR + VAR_TEMP + cl : MyClass2; + END_VAR + cl.change_x(); + x := cl.x; + END_PROGRAM + "; + + let mut m = MainType { x: 0 }; + let _: i32 = compile_and_run(source, &mut m); + assert_eq!(m.x, 10); +} + +#[test] +fn call_overridden_method() { + #[allow(dead_code)] + #[repr(C)] + struct MainType { + x: i16, + } + + let source = " + CLASS MyClass + VAR + x: INT; + END_VAR + METHOD change_x + x := 10; + END_METHOD + END_CLASS + + CLASS MyClass2 EXTENDS MyCLASS + METHOD OVERRIDE change_x + x := 44; + END_METHOD + + END_CLASS + + PROGRAM main + VAR + x : INT := 0; + END_VAR + VAR_TEMP + cl : MyClass2; + END_VAR + cl.change_x(); + x := cl.x; + END_PROGRAM + "; + + let mut m = MainType { x: 0 }; + let _: i32 = compile_and_run(source, &mut m); + assert_eq!(m.x, 44); +} + +#[test] +#[ignore] +//TODO this test can be used once methods are implemented using fp +fn call_method_from_class_given_by_ref() { + #[allow(dead_code)] + #[repr(C)] + struct MainType { + x: i16, + y: i16, + } + + let source = " + CLASS MyClass + VAR + x: INT; + y: INT; + END_VAR + + METHOD change_x + x := 10; + END_METHOD + + METHOD change_y + y := 15; + END_METHOD + END_CLASS + + CLASS MyClass2 EXTENDS MyCLASS + METHOD OVERRIDE change_x + x := 44; + END_METHOD + + METHOD OVERRIDE change_y + y := 55; + END_METHOD + END_CLASS + + CLASS MyClass3 + METHOD callFuncX + VAR_IN_OUT + cls : MyClass; + END_VAR + cls.change_x(); + END_METHOD + + METHOD callFuncY + VAR_IN_OUT + cls : MyClass; + END_VAR + cls.change_y(); + END_METHOD + END_CLASS + + PROGRAM main + VAR + x : INT := 0; + y : INT := 0; + END_VAR + VAR_TEMP + cls2 : MyClass; + cl : MyClass2; + callcls : MyClass3; + END_VAR + + callcls.callFuncX(cl); + x := cl.x; + + callcls.callFuncY(cls2); + y := cls2; + + END_PROGRAM + "; + + let mut m = MainType { x: 0, y: 0 }; + let _: i32 = compile_and_run(source, &mut m); + assert_eq!(m.x, 44); + assert_eq!(m.y, 15); +} + +#[test] +fn access_cls_fields_using_fp() { + #[allow(dead_code)] + #[repr(C)] + struct MainType { + x: i16, + y: i16, + } + + let source = " + CLASS MyClass + VAR + x : INT; + y : INT; + END_VAR + END_CLASS + + PROGRAM MyProg + VAR_IN_OUT + cls : MyClass; + END_VAR + VAR_OUTPUT + x : INT; + y : INT; + END_VAR + x := cls.x; + cls.y := y; + END_PROGRAM + + PROGRAM main + VAR_TEMP + cls : MyClass; + END_VAR + VAR + x : INT; + y : INT; + END_VAR + cls.x := 2; + MyProg.y := 3; + MyProg(cls); + x := MyProg.x; + y := cls.y; + END_PROGRAM + "; + + let mut m = MainType { x: 0, y: 0 }; + let _: i32 = compile_and_run(source, &mut m); + assert_eq!(m.x, 2); + assert_eq!(m.y, 3); +} + +#[test] +fn access_cls_fields_using_fp_via_function() { + #[allow(dead_code)] + #[repr(C)] + struct MainType { + x: i16, + y: i16, + } + + let source = " + CLASS MyClass + VAR + x : INT; + y : INT; + END_VAR + END_CLASS + + FUNCTION MyFunc : DINT + VAR_IN_OUT + cls : MyClass; + END_VAR + VAR_INPUT + y : INT; + END_VAR + MyFunc := cls.x; + cls.y := y; + END_FUNCTION + + PROGRAM main + VAR_TEMP + cls : MyClass; + END_VAR + VAR + x : INT; + y : INT; + END_VAR + cls.x := 2; + x := MyFunc(cls,3); + y := cls.y; + END_PROGRAM + "; + + let mut m = MainType { x: 0, y: 0 }; + let _: i32 = compile_and_run(source, &mut m); + assert_eq!(m.x, 2); + assert_eq!(m.y, 3); +}