diff --git a/compiler/plc_ast/src/ast.rs b/compiler/plc_ast/src/ast.rs index 20ce2912328..f851a8fd31f 100644 --- a/compiler/plc_ast/src/ast.rs +++ b/compiler/plc_ast/src/ast.rs @@ -487,9 +487,7 @@ pub enum DataType { PointerType { name: Option, referenced_type: Box, - auto_deref: bool, - /// Denotes whether the variable was declared as `REFERENCE TO`, e.g. `foo : REFERENCE TO DINT` - is_reference_to: bool, + auto_deref: Option, }, StringType { name: Option, @@ -507,6 +505,18 @@ pub enum DataType { }, } +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum AutoDerefType { + /// A plain pointer variable with the auto-deref trait, e.g. VAR_IN_OUT or VAR_INPUT{ref} variables + Default, + + /// An alias pointer variable, e.g. `foo AT bar : DINT` + Alias, + + /// A reference pointer variable, e.g. `foo : REFERENCE TO DINT;` + Reference, +} + impl DataType { pub fn set_name(&mut self, new_name: String) { match self { @@ -1295,8 +1305,11 @@ impl AstFactory { } /// creates a new Identifier - pub fn create_identifier(name: &str, location: &SourceLocation, id: AstId) -> AstNode { - AstNode::new(AstStatement::Identifier(name.to_string()), id, location.clone()) + pub fn create_identifier(name: &str, location: T, id: AstId) -> AstNode + where + T: Into, + { + AstNode::new(AstStatement::Identifier(name.to_string()), id, location.into()) } pub fn create_unary_expression( @@ -1441,18 +1454,21 @@ impl AstFactory { } } - pub fn create_call_statement( + pub fn create_call_statement( operator: AstNode, parameters: Option, id: usize, - location: SourceLocation, - ) -> AstNode { + location: T, + ) -> AstNode + where + T: Into, + { AstNode { stmt: AstStatement::CallStatement(CallStatement { operator: Box::new(operator), parameters: parameters.map(Box::new), }), - location, + location: location.into(), id, } } diff --git a/compiler/plc_diagnostics/src/diagnostics.rs b/compiler/plc_diagnostics/src/diagnostics.rs index 669ffa7b2e5..1aa1441df2d 100644 --- a/compiler/plc_diagnostics/src/diagnostics.rs +++ b/compiler/plc_diagnostics/src/diagnostics.rs @@ -234,7 +234,10 @@ impl Diagnostic { .with_location(location) } - pub fn invalid_assignment(right_type: &str, left_type: &str, location: SourceLocation) -> Diagnostic { + pub fn invalid_assignment(right_type: &str, left_type: &str, location: T) -> Diagnostic + where + T: Into, + { Diagnostic::new(format!("Invalid assignment: cannot assign '{right_type}' to '{left_type}'")) .with_error_code("E037") .with_location(location) diff --git a/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs b/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs index 874f1c4eb1f..f3db285d771 100644 --- a/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs +++ b/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs @@ -201,6 +201,7 @@ lazy_static! { E097, Error, include_str!("./error_codes/E097.md"), // Invalid Array Range E098, Error, include_str!("./error_codes/E098.md"), // Invalid `REF=` assignment E099, Error, include_str!("./error_codes/E099.md"), // Invalid `REFERENCE TO` declaration + E100, Error, include_str!("./error_codes/E100.md"), // Immutable variable address ); } diff --git a/compiler/plc_diagnostics/src/diagnostics/error_codes/E099.md b/compiler/plc_diagnostics/src/diagnostics/error_codes/E099.md index d327378250e..f78cc784484 100644 --- a/compiler/plc_diagnostics/src/diagnostics/error_codes/E099.md +++ b/compiler/plc_diagnostics/src/diagnostics/error_codes/E099.md @@ -3,6 +3,4 @@ `REFERENCE TO` variable declarations are considered valid if the referenced type is not of the following form * `foo : REFERENCE TO REFERENCE TO (* ... *)` * `foo : ARRAY[...] OF REFERENCE TO (* ... *)` -* `foo : REF_TO REFERENCE TO (* ... *)` - -Furthermore `REFERENCE_TO` variables must not be initialized in their declaration, e.g. `foo : REFERENCE TO DINT := bar`. \ No newline at end of file +* `foo : REF_TO REFERENCE TO (* ... *)` \ No newline at end of file diff --git a/compiler/plc_diagnostics/src/diagnostics/error_codes/E100.md b/compiler/plc_diagnostics/src/diagnostics/error_codes/E100.md new file mode 100644 index 00000000000..5d9e852ac4c --- /dev/null +++ b/compiler/plc_diagnostics/src/diagnostics/error_codes/E100.md @@ -0,0 +1,15 @@ +# Immutable Variable Address + +Alias variables are immutable with regards to their pointer address, thus re-assigning an address will return an error. For example the following code will not compile +```ST +FUNCTION main + VAR + foo AT bar : DINT; + bar : DINT; + baz : DINT; + END_VAR + + foo := baz; // Valid, because we are changing the pointers dereferenced value + foo REF= baz; // Invalid, `foo` is immutable with regards to it's pointer address +END_FUNCTION +``` \ No newline at end of file diff --git a/src/codegen/generators/expression_generator.rs b/src/codegen/generators/expression_generator.rs index b9a1941bb5c..09039ab0b36 100644 --- a/src/codegen/generators/expression_generator.rs +++ b/src/codegen/generators/expression_generator.rs @@ -844,8 +844,9 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { .get(i) .map(|it| { let name = it.get_type_name(); - if let Some(DataTypeInformation::Pointer { inner_type_name, auto_deref: true, .. }) = - self.index.find_effective_type_info(name) + if let Some(DataTypeInformation::Pointer { + inner_type_name, auto_deref: Some(_), .. + }) = self.index.find_effective_type_info(name) { // for auto_deref pointers (VAR_INPUT {ref}, VAR_IN_OUT) we call generate_argument_by_ref() // we need the inner_type and not pointer to type otherwise we would generate a double pointer @@ -1145,7 +1146,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { } fn get_parameter_type(&self, parameter: &VariableIndexEntry) -> String { - if let Some(DataTypeInformation::Pointer { inner_type_name, auto_deref: true, .. }) = + if let Some(DataTypeInformation::Pointer { inner_type_name, auto_deref: Some(_), .. }) = self.index.find_effective_type_info(parameter.get_type_name()) { inner_type_name.into() @@ -1253,7 +1254,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { .map(|var| var.get_type_information()) .unwrap_or_else(|| self.index.get_void_type().get_type_information()); - if let DataTypeInformation::Pointer { auto_deref: true, inner_type_name, .. } = parameter { + if let DataTypeInformation::Pointer { auto_deref: Some(_), inner_type_name, .. } = parameter { //this is a VAR_IN_OUT assignment, so don't load the value, assign the pointer //expression may be empty -> generate a local variable for it let generated_exp = if expression.is_empty_statement() { @@ -1297,13 +1298,12 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { // don't generate param assignments for empty statements, with the exception // of VAR_IN_OUT params - they need an address to point to - let is_auto_deref = matches!( - self.index - .find_effective_type_by_name(parameter.get_type_name()) - .map(|var| var.get_type_information()) - .unwrap_or_else(|| self.index.get_void_type().get_type_information()), - DataTypeInformation::Pointer { auto_deref: true, .. } - ); + let is_auto_deref = self + .index + .find_effective_type_by_name(parameter.get_type_name()) + .map(|var| var.get_type_information()) + .unwrap_or(self.index.get_void_type().get_type_information()) + .is_auto_deref(); if !right.is_empty_statement() || is_auto_deref { self.generate_call_struct_argument_assignment(&CallParameterAssignment { assignment: right, @@ -1419,9 +1419,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { accessor_ptr: PointerValue<'ink>, statement: &AstNode, ) -> PointerValue<'ink> { - if let Some(StatementAnnotation::Variable { is_auto_deref: true, .. }) = - self.annotations.get(statement) - { + if self.annotations.get(statement).is_some_and(|opt| opt.is_auto_deref()) { self.deref(accessor_ptr) } else { accessor_ptr @@ -1976,7 +1974,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { } } } - DataTypeInformation::Pointer { inner_type_name, auto_deref: true, .. } => { + DataTypeInformation::Pointer { inner_type_name, auto_deref: Some(_), .. } => { let inner_type = self.index.get_type_information_or_void(inner_type_name); self.generate_string_literal_for_type(inner_type, value, location) } diff --git a/src/codegen/llvm_typesystem.rs b/src/codegen/llvm_typesystem.rs index d84bf1bbe1f..50823654527 100644 --- a/src/codegen/llvm_typesystem.rs +++ b/src/codegen/llvm_typesystem.rs @@ -114,7 +114,7 @@ impl<'ctx, 'cast> CastInstructionData<'ctx, 'cast> { let value_type = index.get_intrinsic_type_by_name(value_type.get_name()).get_type_information(); let target_type = - if let DataTypeInformation::Pointer { auto_deref: true, inner_type_name, .. } = target_type { + if let DataTypeInformation::Pointer { auto_deref: Some(_), inner_type_name, .. } = target_type { // Deref auto-deref pointers before casting index.get_intrinsic_type_by_name(inner_type_name.as_str()).get_type_information() } else { diff --git a/src/codegen/tests/statement_codegen_test.rs b/src/codegen/tests/statement_codegen_test.rs index 9f8a4db6d0e..5b627712f30 100644 --- a/src/codegen/tests/statement_codegen_test.rs +++ b/src/codegen/tests/statement_codegen_test.rs @@ -1,3 +1,5 @@ +use insta::assert_snapshot; + // Copyright (c) 2020 Ghaith Hachem and Mathias Rieder use crate::test_utils::tests::codegen; @@ -308,3 +310,60 @@ fn reference_to_string_assignment() { attributes #0 = { argmemonly nofree nounwind willreturn } "###); } + +#[test] +#[ignore = "Not working because of REF(...) initializer; should be resolved with https://github.com/PLC-lang/rusty/pull/1259"] +fn alias_dint() { + let content = codegen( + r#" + FUNCTION main + VAR + foo AT bar : DINT; + bar : DINT; + END_VAR + END_FUNCTION + "#, + ); + + assert_snapshot!(content, @r""); +} + +#[test] +#[ignore = "Not working because of REF(...) initializer; should be resolved with https://github.com/PLC-lang/rusty/pull/1259"] +fn alias_string() { + let content = codegen( + r#" + FUNCTION main + VAR + foo AT bar : STRING; + bar : STRING; + END_VAR + END_FUNCTION + "#, + ); + + assert_snapshot!(content, @r""); +} + +#[test] +#[ignore = "Not working because of REF(...) initializer; should be resolved with https://github.com/PLC-lang/rusty/pull/1259"] +fn alias_struct() { + let content = codegen( + r#" + TYPE Node : STRUCT + id : DINT; + child : REF_TO Node; + parent : REF_TO Node; + END_STRUCT END_TYPE + + FUNCTION main + VAR + foo AT bar : STRING; + bar : STRING; + END_VAR + END_FUNCTION + "#, + ); + + assert_snapshot!(content, @r""); +} diff --git a/src/index/tests/index_tests.rs b/src/index/tests/index_tests.rs index 2e8eb9dacb3..7b1d2e4d081 100644 --- a/src/index/tests/index_tests.rs +++ b/src/index/tests/index_tests.rs @@ -1,7 +1,8 @@ // Copyright (c) 2020 Ghaith Hachem and Mathias Rieder use insta::assert_debug_snapshot; use plc_ast::ast::{ - pre_process, AstFactory, DataType, GenericBinding, LinkageType, Operator, TypeNature, UserTypeDeclaration, + pre_process, AstFactory, AutoDerefType, DataType, GenericBinding, LinkageType, Operator, TypeNature, + UserTypeDeclaration, }; use plc_ast::provider::IdProvider; use plc_source::source_location::{SourceLocation, SourceLocationFactory}; @@ -1252,8 +1253,7 @@ fn pointer_and_in_out_pointer_should_not_conflict() { &DataTypeInformation::Pointer { name: "__main_x".to_string(), inner_type_name: "INT".to_string(), - auto_deref: false, - is_reference_to: false, + auto_deref: None, } ); @@ -1264,8 +1264,7 @@ fn pointer_and_in_out_pointer_should_not_conflict() { &DataTypeInformation::Pointer { name: "__auto_pointer_to_INT".to_string(), inner_type_name: "INT".to_string(), - auto_deref: true, - is_reference_to: false, + auto_deref: Some(AutoDerefType::Default), } ); } @@ -1304,8 +1303,7 @@ fn pointer_and_in_out_pointer_should_not_conflict_2() { &DataTypeInformation::Pointer { name: "__main_x".to_string(), inner_type_name: "INT".to_string(), - auto_deref: false, - is_reference_to: false, + auto_deref: None, } ); @@ -1316,8 +1314,7 @@ fn pointer_and_in_out_pointer_should_not_conflict_2() { &DataTypeInformation::Pointer { name: "__auto_pointer_to_INT".to_string(), inner_type_name: "INT".to_string(), - auto_deref: true, - is_reference_to: false, + auto_deref: Some(AutoDerefType::Default), } ); } diff --git a/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_inline_pointer_to_pointer-2.snap b/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_inline_pointer_to_pointer-2.snap index e3895d6f10f..dd220acec73 100644 --- a/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_inline_pointer_to_pointer-2.snap +++ b/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_inline_pointer_to_pointer-2.snap @@ -10,8 +10,7 @@ UserTypeDeclaration { referenced_type: DataTypeReference { referenced_type: "__foo_inline_pointer_", }, - auto_deref: false, - is_reference_to: false, + auto_deref: None, }, initializer: None, scope: Some( diff --git a/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_inline_pointer_to_pointer.snap b/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_inline_pointer_to_pointer.snap index 8acc34ac464..3ce97d31658 100644 --- a/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_inline_pointer_to_pointer.snap +++ b/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_inline_pointer_to_pointer.snap @@ -10,8 +10,7 @@ UserTypeDeclaration { referenced_type: DataTypeReference { referenced_type: "INT", }, - auto_deref: false, - is_reference_to: false, + auto_deref: None, }, initializer: None, scope: Some( diff --git a/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_inline_pointers.snap b/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_inline_pointers.snap index be433f8b032..582c409f4e3 100644 --- a/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_inline_pointers.snap +++ b/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_inline_pointers.snap @@ -10,8 +10,7 @@ UserTypeDeclaration { referenced_type: DataTypeReference { referenced_type: "INT", }, - auto_deref: false, - is_reference_to: false, + auto_deref: None, }, initializer: None, scope: Some( diff --git a/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_pointer_to_pointer_type-2.snap b/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_pointer_to_pointer_type-2.snap index 6bd5e157f63..b88ea5be2b0 100644 --- a/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_pointer_to_pointer_type-2.snap +++ b/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_pointer_to_pointer_type-2.snap @@ -10,8 +10,7 @@ UserTypeDeclaration { referenced_type: DataTypeReference { referenced_type: "__pointer_to_pointer", }, - auto_deref: false, - is_reference_to: false, + auto_deref: None, }, initializer: None, scope: None, diff --git a/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_pointer_to_pointer_type.snap b/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_pointer_to_pointer_type.snap index f8a2939b474..bbcd573ad6b 100644 --- a/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_pointer_to_pointer_type.snap +++ b/src/index/tests/snapshots/rusty__index__tests__index_tests__pre_processing_generates_pointer_to_pointer_type.snap @@ -10,8 +10,7 @@ UserTypeDeclaration { referenced_type: DataTypeReference { referenced_type: "INT", }, - auto_deref: false, - is_reference_to: false, + auto_deref: None, }, initializer: None, scope: None, diff --git a/src/index/visitor.rs b/src/index/visitor.rs index f0ecd18a1ef..04f146031e6 100644 --- a/src/index/visitor.rs +++ b/src/index/visitor.rs @@ -3,9 +3,9 @@ use super::{HardwareBinding, PouIndexEntry, VariableIndexEntry, VariableType}; use crate::index::{ArgumentType, Index, MemberInfo}; use crate::typesystem::{self, *}; use plc_ast::ast::{ - self, ArgumentProperty, Assignment, AstFactory, AstNode, AstStatement, CompilationUnit, DataType, - DataTypeDeclaration, Implementation, Pou, PouType, RangeStatement, TypeNature, UserTypeDeclaration, - Variable, VariableBlock, VariableBlockType, + self, ArgumentProperty, Assignment, AstFactory, AstNode, AstStatement, AutoDerefType, CompilationUnit, + DataType, DataTypeDeclaration, Implementation, Pou, PouType, RangeStatement, TypeNature, + UserTypeDeclaration, Variable, VariableBlock, VariableBlockType, }; use plc_ast::literals::AstLiteral; use plc_diagnostics::diagnostics::Diagnostic; @@ -272,8 +272,7 @@ fn register_byref_pointer_type_for(index: &mut Index, inner_type_name: &str) -> information: DataTypeInformation::Pointer { name: type_name.clone(), inner_type_name: inner_type_name.to_string(), - auto_deref: true, - is_reference_to: false, + auto_deref: Some(AutoDerefType::Default), }, nature: TypeNature::Any, location: SourceLocation::internal(), @@ -401,13 +400,12 @@ fn visit_data_type(index: &mut Index, type_declaration: &UserTypeDeclaration) { DataType::ArrayType { name: Some(name), bounds, referenced_type, .. } => { visit_array(bounds, index, scope, referenced_type, name, type_declaration); } - DataType::PointerType { name: Some(name), referenced_type, auto_deref, is_reference_to } => { + DataType::PointerType { name: Some(name), referenced_type, auto_deref: kind } => { let inner_type_name = referenced_type.get_name().expect("named datatype"); let information = DataTypeInformation::Pointer { name: name.clone(), inner_type_name: inner_type_name.into(), - auto_deref: *auto_deref, - is_reference_to: *is_reference_to, + auto_deref: *kind, }; let init = index.get_mut_const_expressions().maybe_add_constant_expression( @@ -574,8 +572,7 @@ fn visit_variable_length_array( referenced_type: dummy_array_name, location: SourceLocation::undefined(), }), - auto_deref: false, - is_reference_to: false, + auto_deref: None, }, location: SourceLocation::undefined(), scope: None, diff --git a/src/parser.rs b/src/parser.rs index f113e445490..c554853729d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4,8 +4,8 @@ use std::ops::Range; use plc_ast::{ ast::{ - AccessModifier, ArgumentProperty, AstFactory, AstNode, AstStatement, CompilationUnit, DataType, - DataTypeDeclaration, DirectAccessType, GenericBinding, HardwareAccessType, Implementation, + AccessModifier, ArgumentProperty, AstFactory, AstNode, AstStatement, AutoDerefType, CompilationUnit, + DataType, DataTypeDeclaration, DirectAccessType, GenericBinding, HardwareAccessType, Implementation, LinkageType, PolymorphismMode, Pou, PouType, ReferenceAccess, ReferenceExpr, TypeNature, UserTypeDeclaration, Variable, VariableBlock, VariableBlockType, }, @@ -23,7 +23,10 @@ use plc_util::convention::qualified_name; use crate::{ expect_token, - lexer::{self, ParseSession, Token, Token::*}, + lexer::{ + self, ParseSession, + Token::{self, *}, + }, typesystem::DINT_TYPE, }; @@ -676,9 +679,9 @@ fn parse_data_type_definition( if expect_keyword_to(lexer).is_some() { lexer.advance(); } - parse_pointer_definition(lexer, name, start_pos, false) + parse_pointer_definition(lexer, name, start_pos, None) } else if lexer.try_consume(&KeywordRef) { - parse_pointer_definition(lexer, name, lexer.last_range.start, false) + parse_pointer_definition(lexer, name, lexer.last_range.start, None) } else if lexer.try_consume(&KeywordParensOpen) { //enum without datatype parse_enum_type_definition(lexer, name) @@ -701,17 +704,12 @@ fn parse_pointer_definition( lexer: &mut ParseSession, name: Option, start_pos: usize, - is_reference_to: bool, + auto_deref: Option, ) -> Option<(DataTypeDeclaration, Option)> { parse_data_type_definition(lexer, None).map(|(decl, initializer)| { ( DataTypeDeclaration::DataTypeDefinition { - data_type: DataType::PointerType { - name, - referenced_type: Box::new(decl), - auto_deref: is_reference_to, - is_reference_to, - }, + data_type: DataType::PointerType { name, referenced_type: Box::new(decl), auto_deref }, location: lexer.source_range_factory.create_range(start_pos..lexer.last_range.end), scope: lexer.scope.clone(), }, @@ -739,7 +737,11 @@ fn parse_type_reference_type_definition( }; let initial_value = - if lexer.try_consume(&KeywordAssignment) { Some(parse_expression(lexer)) } else { None }; + if lexer.try_consume(&KeywordAssignment) || lexer.try_consume(&KeywordReferenceAssignment) { + Some(parse_expression(lexer)) + } else { + None + }; let end = lexer.last_range.end; if name.is_some() || bounds.is_some() { @@ -1070,6 +1072,37 @@ fn parse_variable_list(lexer: &mut ParseSession) -> Vec { variables } +fn parse_aliasing(lexer: &mut ParseSession, names: &(String, Range)) -> Option { + let reference = parse_reference(lexer); + if !lexer.try_consume(&KeywordColon) { + lexer.accept_diagnostic(Diagnostic::missing_token( + format!("{KeywordColon:?}").as_str(), + lexer.location(), + )); + } + + let start = &lexer.location().get_span().to_range().unwrap_or(lexer.last_range.clone()).start; + let datatype = parse_pointer_definition(lexer, None, *start, Some(AutoDerefType::Alias)); + if !lexer.try_consume(&KeywordSemicolon) { + lexer.accept_diagnostic(Diagnostic::missing_token( + format!("{KeywordSemicolon:?}").as_str(), + lexer.location(), + )); + } + + if let Some((data_type, _)) = datatype { + return Some(Variable { + name: names.0.clone(), + data_type_declaration: data_type, + location: lexer.source_range_factory.create_range(names.1.clone()), + initializer: Some(reference), + address: None, + }); + } + + None +} + fn parse_variable_line(lexer: &mut ParseSession) -> Vec { // read in a comma separated list of variable names let mut var_names: Vec<(String, Range)> = vec![]; @@ -1092,16 +1125,22 @@ fn parse_variable_line(lexer: &mut ParseSession) -> Vec { } //See if there's an AT keyword - let address = if lexer.try_consume(&KeywordAt) { - //Look for a hardware address - if let HardwareAccess((direction, access_type)) = lexer.token { - parse_hardware_access(lexer, direction, access_type) - } else { - lexer.accept_diagnostic(Diagnostic::missing_token("Hardware Access", lexer.location())); - None + let mut address: Option = None; + if lexer.try_consume(&KeywordAt) { + match lexer.token { + HardwareAccess((direction, access_type)) => { + address = parse_hardware_access(lexer, direction, access_type) + } + + Identifier => return vec![parse_aliasing(lexer, &var_names[0]).unwrap()], + + _ => { + lexer.accept_diagnostic(Diagnostic::missing_token( + "hardware access or identifier", + lexer.location(), + )); + } } - } else { - None }; // colon has to come before the data type @@ -1116,7 +1155,7 @@ fn parse_variable_line(lexer: &mut ParseSession) -> Vec { let mut variables = vec![]; let parse_definition_opt = if lexer.try_consume(&KeywordReferenceTo) { - parse_pointer_definition(lexer, None, lexer.last_range.start, true) + parse_pointer_definition(lexer, None, lexer.last_range.start, Some(AutoDerefType::Reference)) } else { parse_full_data_type_definition(lexer, None) }; diff --git a/src/parser/expressions_parser.rs b/src/parser/expressions_parser.rs index c9760451ff7..ac41e0223fe 100644 --- a/src/parser/expressions_parser.rs +++ b/src/parser/expressions_parser.rs @@ -316,7 +316,7 @@ fn parse_atomic_leaf_expression(lexer: &mut ParseSession<'_>) -> Option } fn parse_identifier(lexer: &mut ParseSession<'_>) -> AstNode { - AstFactory::create_identifier(&lexer.slice_and_advance(), &lexer.last_location(), lexer.next_id()) + AstFactory::create_identifier(&lexer.slice_and_advance(), lexer.last_location(), lexer.next_id()) } fn parse_vla_range(lexer: &mut ParseSession) -> Option { @@ -481,7 +481,7 @@ fn parse_direct_access(lexer: &mut ParseSession, access: DirectAccessType) -> Op Identifier => { let location = lexer.location(); Some(AstFactory::create_member_reference( - AstFactory::create_identifier(lexer.slice_and_advance().as_str(), &location, lexer.next_id()), + AstFactory::create_identifier(lexer.slice_and_advance().as_str(), location, lexer.next_id()), None, lexer.next_id(), )) diff --git a/src/parser/tests.rs b/src/parser/tests.rs index f9879edf508..5d3b2f9800d 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -23,7 +23,7 @@ mod variable_parser_tests; /// helper function to create references pub fn ref_to(name: &str) -> AstNode { AstFactory::create_member_reference( - AstFactory::create_identifier(name, &SourceLocation::undefined(), 0), + AstFactory::create_identifier(name, SourceLocation::undefined(), 0), None, 0, ) diff --git a/src/parser/tests/expressions_parser_tests.rs b/src/parser/tests/expressions_parser_tests.rs index 7eec8b4575f..72f4edde749 100644 --- a/src/parser/tests/expressions_parser_tests.rs +++ b/src/parser/tests/expressions_parser_tests.rs @@ -734,7 +734,7 @@ fn literal_real_test() { fn cast(data_type: &str, value: AstNode) -> AstNode { AstFactory::create_cast_statement( AstFactory::create_member_reference( - AstFactory::create_identifier(data_type, &SourceLocation::undefined(), 0), + AstFactory::create_identifier(data_type, SourceLocation::undefined(), 0), None, 0, ), diff --git a/src/parser/tests/parse_errors/parse_error_statements_tests.rs b/src/parser/tests/parse_errors/parse_error_statements_tests.rs index 635dd037928..65d2419ec02 100644 --- a/src/parser/tests/parse_errors/parse_error_statements_tests.rs +++ b/src/parser/tests/parse_errors/parse_error_statements_tests.rs @@ -1128,8 +1128,7 @@ fn pointer_type_without_to_test() { referenced_type: "INT".to_string(), location: SourceLocation::undefined(), }), - auto_deref: false, - is_reference_to: false, + auto_deref: None, }, location: SourceLocation::undefined(), initializer: None, @@ -1156,8 +1155,7 @@ fn pointer_type_with_wrong_keyword_to_test() { referenced_type: "tu".to_string(), location: SourceLocation::undefined(), }), - auto_deref: false, - is_reference_to: false, + auto_deref: None, }, location: SourceLocation::undefined(), initializer: None, diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__global_pointer_declaration-2.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__global_pointer_declaration-2.snap index b7f30760038..670cab7d36f 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__global_pointer_declaration-2.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__global_pointer_declaration-2.snap @@ -10,8 +10,7 @@ Variable { referenced_type: DataTypeReference { referenced_type: "INT", }, - auto_deref: false, - is_reference_to: false, + auto_deref: None, }, }, } diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__global_pointer_declaration.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__global_pointer_declaration.snap index fcdb7bc49cc..01eb109c7bc 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__global_pointer_declaration.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__global_pointer_declaration.snap @@ -10,8 +10,7 @@ Variable { referenced_type: DataTypeReference { referenced_type: "INT", }, - auto_deref: false, - is_reference_to: false, + auto_deref: None, }, }, } diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__pointer_type_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__pointer_type_test.snap index 576dcbd1160..03c86b8ad31 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__pointer_type_test.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__pointer_type_test.snap @@ -10,8 +10,7 @@ UserTypeDeclaration { referenced_type: DataTypeReference { referenced_type: "INT", }, - auto_deref: false, - is_reference_to: false, + auto_deref: None, }, initializer: None, scope: None, diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__ref_type_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__ref_type_test.snap index 332f3018175..366d52a65bb 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__ref_type_test.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__ref_type_test.snap @@ -10,8 +10,7 @@ UserTypeDeclaration { referenced_type: DataTypeReference { referenced_type: "INT", }, - auto_deref: false, - is_reference_to: false, + auto_deref: None, }, initializer: None, scope: None, diff --git a/src/parser/tests/statement_parser_tests.rs b/src/parser/tests/statement_parser_tests.rs index c1b1c51d6e1..81d2848f38b 100644 --- a/src/parser/tests/statement_parser_tests.rs +++ b/src/parser/tests/statement_parser_tests.rs @@ -1,6 +1,6 @@ use crate::{ parser::tests::{empty_stmt, ref_to}, - test_utils::tests::{parse, parse_and_validate_buffered}, + test_utils::tests::parse, typesystem::DINT_TYPE, }; use insta::assert_snapshot; @@ -38,7 +38,7 @@ fn empty_statements_are_parsed_before_a_statement() { empty_stmt(), empty_stmt(), AstFactory::create_member_reference( - AstFactory::create_identifier("x", &SourceLocation::undefined(), 0), + AstFactory::create_identifier("x", SourceLocation::undefined(), 0), None, 0 ), @@ -59,7 +59,7 @@ fn empty_statements_are_ignored_after_a_statement() { let expected_ast = format!( "{:#?}", AstFactory::create_member_reference( - AstFactory::create_identifier("x", &SourceLocation::undefined(), 0), + AstFactory::create_identifier("x", SourceLocation::undefined(), 0), None, 0 ) @@ -322,8 +322,9 @@ fn reference_to_declaration() { referenced_type: DataTypeReference { referenced_type: "DINT", }, - auto_deref: true, - is_reference_to: true, + auto_deref: Some( + Reference, + ), }, }, }, @@ -338,3 +339,49 @@ fn reference_to_declaration() { } "###); } + +#[test] +fn aliasing_dint_variable() { + let (result, diagnostics) = parse( + " + FUNCTION main + VAR + a AT b : DINT; // equivalent to `a : REFERENCE TO DINT REF= b` + END_VAR + END_FUNCTION + ", + ); + + assert_eq!(diagnostics, vec![]); + insta::assert_debug_snapshot!(result.units[0].variable_blocks[0], @r###" + VariableBlock { + variables: [ + Variable { + name: "a", + data_type: DataTypeDefinition { + data_type: PointerType { + name: None, + referenced_type: DataTypeReference { + referenced_type: "DINT", + }, + auto_deref: Some( + Alias, + ), + }, + }, + initializer: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "b", + }, + ), + base: None, + }, + ), + }, + ], + variable_block_type: Local, + } + "###); +} diff --git a/src/resolver.rs b/src/resolver.rs index 2038bc223f4..21ca9b9f926 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -10,7 +10,7 @@ use std::hash::Hash; use plc_ast::{ ast::{ - self, flatten_expression_list, Assignment, AstFactory, AstId, AstNode, AstStatement, + self, flatten_expression_list, Assignment, AstFactory, AstId, AstNode, AstStatement, AutoDerefType, BinaryExpression, CompilationUnit, DataType, DataTypeDeclaration, DirectAccessType, JumpStatement, Operator, Pou, ReferenceAccess, ReferenceExpr, TypeNature, UserTypeDeclaration, Variable, }, @@ -391,10 +391,8 @@ pub enum StatementAnnotation { constant: bool, /// 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, - /// denotes whether this variable is declared as `REFERENCE TO` (e.g. `foo : REFERENCE TO DINT`) - is_reference_to: bool, + /// denotes wheter this variable has the auto-deref trait and if so what type + auto_deref: Option, }, /// a reference to a function Function { @@ -435,12 +433,12 @@ impl StatementAnnotation { } } - pub fn is_auto_deref(&self) -> bool { - matches!(self, StatementAnnotation::Variable { is_auto_deref: true, .. }) + pub fn is_alias(&self) -> bool { + matches!(self, StatementAnnotation::Variable { auto_deref: Some(AutoDerefType::Alias), .. }) } - pub fn is_reference_to(&self) -> bool { - matches!(self, StatementAnnotation::Variable { is_reference_to: true, .. }) + pub fn is_auto_deref(&self) -> bool { + matches!(self, StatementAnnotation::Variable { auto_deref: Some(_), .. }) } pub fn data_type(type_name: &str) -> Self { @@ -1024,19 +1022,64 @@ impl<'i> TypeAnnotator<'i> { fn type_hint_for_variable_initializer( &mut self, initializer: &AstNode, - ty: &typesystem::DataType, + variable_ty: &typesystem::DataType, ctx: &VisitorContext, ) { if let AstStatement::ParenExpression(expr) = &initializer.stmt { - self.type_hint_for_variable_initializer(expr, ty, ctx); + self.type_hint_for_variable_initializer(expr, variable_ty, ctx); self.inherit_annotations(initializer, expr); return; } - self.annotation_map.annotate_type_hint(initializer, StatementAnnotation::value(ty.get_name())); - self.update_expected_types(ty, initializer); + self.replace_reference_pointer_initializer(variable_ty, initializer, ctx); + + self.annotation_map.annotate_type_hint(initializer, StatementAnnotation::value(&variable_ty.name)); + self.update_expected_types(variable_ty, initializer); - self.type_hint_for_array_of_structs(ty, initializer, ctx); + self.type_hint_for_array_of_structs(variable_ty, initializer, ctx); + } + + /// Wraps the initializer of a reference- or alias-pointer in a `REF` function call. + /// + /// Initializers already wrapped in a `REF` (or similiar) function call are however excluded, as we do + /// not want something like `REF(REF(...))`. + fn replace_reference_pointer_initializer( + &mut self, + variable_ty: &typesystem::DataType, + initializer: &AstNode, + ctx: &VisitorContext, + ) { + let variable_is_auto_deref_pointer = { + variable_ty.get_type_information().is_alias() + || variable_ty.get_type_information().is_reference_to() + }; + + let initializer_is_not_wrapped_in_ref_call = { + !(initializer.is_call() + && self.annotation_map.get_type(initializer, self.index).is_some_and(|opt| opt.is_pointer())) + }; + + if variable_is_auto_deref_pointer && initializer_is_not_wrapped_in_ref_call { + debug_assert!(builtins::get_builtin("REF").is_some(), "REF must exist for this use-case"); + + let mut id_provider = ctx.id_provider.clone(); + let location = &initializer.location; + + let ref_ident = AstFactory::create_identifier("REF", location, id_provider.next_id()); + let fn_name = AstFactory::create_member_reference(ref_ident, None, id_provider.next_id()); + let fn_arg = initializer; + self.visit_statement(ctx, &fn_name); + self.visit_statement(ctx, fn_arg); + + let fn_call = AstFactory::create_call_statement( + fn_name, + Some(fn_arg.clone()), + id_provider.next_id(), + location, + ); + self.visit_statement(ctx, &fn_call); + self.annotate(initializer, StatementAnnotation::ReplacementAst { statement: fn_call }); + } } fn type_hint_for_array_of_structs( @@ -1553,7 +1596,7 @@ impl<'i> TypeAnnotator<'i> { } } (ReferenceAccess::Deref, _) => { - if let Some(DataTypeInformation::Pointer { inner_type_name, auto_deref: false, .. }) = base + if let Some(DataTypeInformation::Pointer { inner_type_name, auto_deref: None, .. }) = base .map(|base| self.annotation_map.get_type_or_void(base, self.index)) .map(|it| it.get_type_information()) { @@ -1638,7 +1681,7 @@ impl<'i> TypeAnnotator<'i> { else { unreachable!("expected a vla reference, but got {statement:#?}"); }; - if let DataTypeInformation::Pointer { inner_type_name, is_reference_to, .. } = &self + if let DataTypeInformation::Pointer { inner_type_name, auto_deref: kind, .. } = &self .index .get_effective_type_or_void_by_name( members.first().expect("internal VLA struct ALWAYS has this member").get_type_name(), @@ -1673,8 +1716,7 @@ impl<'i> TypeAnnotator<'i> { qualified_name: qualified_name.to_string(), constant: false, argument_type, - is_auto_deref: false, - is_reference_to: *is_reference_to, + auto_deref: *kind, }; self.annotation_map.annotate_type_hint(statement, hint_annotation) } @@ -1886,10 +1928,9 @@ pub(crate) fn add_pointer_type(index: &mut Index, inner_type_name: String) -> St initial_value: None, nature: TypeNature::Any, information: DataTypeInformation::Pointer { - auto_deref: false, - inner_type_name, name: new_type_name.clone(), - is_reference_to: false, + inner_type_name, + auto_deref: None, }, location: SourceLocation::internal(), }); @@ -1925,22 +1966,21 @@ fn to_variable_annotation( index: &Index, constant_override: bool, ) -> StatementAnnotation { - const AUTO_DEREF: bool = true; - const NO_DEREF: bool = false; let v_type = index.get_effective_type_or_void_by_name(v.get_type_name()); //see if this is an auto-deref variable - let (effective_type_name, is_auto_deref) = match (v_type.get_type_information(), v.is_return()) { + let (effective_type_name, kind) = match (v_type.get_type_information(), v.is_return()) { (_, true) if v_type.is_aggregate_type() => { // treat a return-aggregate variable like an auto-deref pointer since it got // passed by-ref - (v_type.get_name().to_string(), AUTO_DEREF) + let kind = v_type.get_type_information().get_auto_deref_type().unwrap_or(AutoDerefType::Default); + (v_type.get_name().to_string(), Some(kind)) } - (DataTypeInformation::Pointer { inner_type_name, auto_deref: true, .. }, _) => { + (DataTypeInformation::Pointer { inner_type_name, auto_deref: Some(deref), .. }, _) => { // real auto-deref pointer - (inner_type_name.clone(), AUTO_DEREF) + (inner_type_name.clone(), Some(*deref)) } - _ => (v_type.get_name().to_string(), NO_DEREF), + _ => (v_type.get_name().to_string(), None), }; StatementAnnotation::Variable { @@ -1948,8 +1988,7 @@ fn to_variable_annotation( resulting_type: effective_type_name, constant: v.is_constant() || constant_override, argument_type: v.get_declaration_type(), - is_auto_deref, - is_reference_to: v_type.get_type_information().is_reference_to(), + auto_deref: kind, } } diff --git a/src/resolver/generics.rs b/src/resolver/generics.rs index 6d7861b7e48..509c321ab11 100644 --- a/src/resolver/generics.rs +++ b/src/resolver/generics.rs @@ -201,7 +201,7 @@ impl<'i> TypeAnnotator<'i> { .unwrap_or_else(|| member_name) .to_string() } - Some(DataTypeInformation::Pointer { name, inner_type_name, auto_deref: true, .. }) => { + Some(DataTypeInformation::Pointer { name, inner_type_name, auto_deref: Some(kind), .. }) => { // This is an auto deref pointer (VAR_IN_OUT or VAR_INPUT {ref}) that points to a // generic. We first resolve the generic type, then create a new pointer type of // the combination @@ -210,8 +210,7 @@ impl<'i> TypeAnnotator<'i> { let new_type_info = DataTypeInformation::Pointer { name: name.clone(), inner_type_name, - auto_deref: true, - is_reference_to: false, + auto_deref: Some(*kind), }; // Registers a new pointer type to the index diff --git a/src/resolver/tests.rs b/src/resolver/tests.rs index 15f0039de29..0813304d6bd 100644 --- a/src/resolver/tests.rs +++ b/src/resolver/tests.rs @@ -1,4 +1,5 @@ mod const_resolver_tests; +mod lowering; mod resolve_control_statments; mod resolve_expressions_tests; mod resolve_generic_calls; diff --git a/src/resolver/tests/lowering.rs b/src/resolver/tests/lowering.rs new file mode 100644 index 00000000000..ca69f098048 --- /dev/null +++ b/src/resolver/tests/lowering.rs @@ -0,0 +1,240 @@ +//! Contains tests related to lowering, i.e. (for now) anything that uses the `ReplacementAst`. + +use insta::assert_debug_snapshot; +use plc_ast::provider::IdProvider; + +use crate::{ + resolver::AnnotationMap, + test_utils::tests::{annotate_with_ids, index_with_ids}, +}; + +#[test] +fn initializer_with_ref_call_annotated_as_pointer() { + let id_provider = IdProvider::default(); + let (unit, mut index) = index_with_ids( + " + FUNCTION main + VAR + foo : DINT; + bar : REFERENCE TO DINT := REF(foo); + END_VAR + END_FUNCTION + ", + id_provider.clone(), + ); + + let annotations = annotate_with_ids(&unit, &mut index, id_provider); + let initializer_bar = unit.units[0].variable_blocks[0].variables[1].initializer.as_ref().unwrap(); + let initializer_bar_annotation = annotations.get(initializer_bar).unwrap(); + + assert_debug_snapshot!((initializer_bar, initializer_bar_annotation), @r###" + ( + CallStatement { + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "REF", + }, + ), + base: None, + }, + parameters: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "foo", + }, + ), + base: None, + }, + ), + }, + Value { + resulting_type: "__POINTER_TO_DINT", + }, + ) + "###); +} + +#[test] +fn initializer_with_refassignment_annotated_with_replacementast() { + let id_provider = IdProvider::default(); + let (unit, mut index) = index_with_ids( + " + FUNCTION main + VAR + foo : DINT; + bar : REFERENCE TO DINT REF= foo; + END_VAR + END_FUNCTION + ", + id_provider.clone(), + ); + + let annotations = annotate_with_ids(&unit, &mut index, id_provider); + let initializer_bar = unit.units[0].variable_blocks[0].variables[1].initializer.as_ref().unwrap(); + let initializer_bar_annotation = annotations.get(initializer_bar).unwrap(); + + assert_debug_snapshot!((initializer_bar, initializer_bar_annotation), @r###" + ( + ReferenceExpr { + kind: Member( + Identifier { + name: "foo", + }, + ), + base: None, + }, + ReplacementAst { + statement: CallStatement { + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "REF", + }, + ), + base: None, + }, + parameters: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "foo", + }, + ), + base: None, + }, + ), + }, + }, + ) + "###); +} + +#[test] +fn initializer_of_alias_annotated_with_replacementast() { + let id_provider = IdProvider::default(); + let (unit, mut index) = index_with_ids( + " + FUNCTION main + VAR + foo : DINT; + bar AT foo : DINT; + END_VAR + END_FUNCTION + ", + id_provider.clone(), + ); + + let annotations = annotate_with_ids(&unit, &mut index, id_provider); + let initializer_bar = unit.units[0].variable_blocks[0].variables[1].initializer.as_ref().unwrap(); + let initializer_bar_annotation = annotations.get(initializer_bar).unwrap(); + + assert_debug_snapshot!((initializer_bar, initializer_bar_annotation), @r###" + ( + ReferenceExpr { + kind: Member( + Identifier { + name: "foo", + }, + ), + base: None, + }, + ReplacementAst { + statement: CallStatement { + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "REF", + }, + ), + base: None, + }, + parameters: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "foo", + }, + ), + base: None, + }, + ), + }, + }, + ) + "###); +} + +#[test] +fn initializer_of_alias_annotated_with_replacementast_array() { + let id_provider = IdProvider::default(); + let (unit, mut index) = index_with_ids( + " + FUNCTION main + VAR + foo : ARRAY[1..5] OF DINT; + bar AT foo[1] : DINT; + END_VAR + END_FUNCTION + ", + id_provider.clone(), + ); + + let annotations = annotate_with_ids(&unit, &mut index, id_provider); + let initializer_bar = unit.units[0].variable_blocks[0].variables[1].initializer.as_ref().unwrap(); + let initializer_bar_annotation = annotations.get(initializer_bar).unwrap(); + + assert_debug_snapshot!((initializer_bar, initializer_bar_annotation), @r###" + ( + ReferenceExpr { + kind: Index( + LiteralInteger { + value: 1, + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "foo", + }, + ), + base: None, + }, + ), + }, + ReplacementAst { + statement: CallStatement { + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "REF", + }, + ), + base: None, + }, + parameters: Some( + ReferenceExpr { + kind: Index( + LiteralInteger { + value: 1, + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "foo", + }, + ), + base: None, + }, + ), + }, + ), + }, + }, + ) + "###); +} diff --git a/src/resolver/tests/resolve_expressions_tests.rs b/src/resolver/tests/resolve_expressions_tests.rs index 44872aec2bd..10c9a14479d 100644 --- a/src/resolver/tests/resolve_expressions_tests.rs +++ b/src/resolver/tests/resolve_expressions_tests.rs @@ -162,8 +162,7 @@ fn cast_expressions_of_enum_with_resolves_types() { qualified_name: "MyEnum.a".to_string(), constant: true, argument_type: ArgumentType::ByVal(VariableType::Global), - is_auto_deref: false, - is_reference_to: false, + auto_deref: None, }) ); @@ -179,8 +178,7 @@ fn cast_expressions_of_enum_with_resolves_types() { qualified_name: "MyEnum.b".to_string(), constant: true, argument_type: ArgumentType::ByVal(VariableType::Global), - is_auto_deref: false, - is_reference_to: false, + auto_deref: None, }) ); } @@ -1313,9 +1311,8 @@ fn function_expression_resolves_to_the_function_itself_not_its_return_type() { qualified_name: "foo.foo".into(), resulting_type: "INT".into(), constant: false, - is_auto_deref: false, argument_type: ArgumentType::ByVal(VariableType::Return), - is_reference_to: false, + auto_deref: None, }), foo_annotation ); @@ -1575,9 +1572,8 @@ fn qualified_expressions_dont_fallback_to_globals() { qualified_name: "MyStruct.y".into(), resulting_type: "INT".into(), constant: false, - is_auto_deref: false, argument_type: ArgumentType::ByVal(VariableType::Input), - is_reference_to: false, + auto_deref: None, }), annotations.get(&statements[1]) ); @@ -1817,9 +1813,8 @@ fn method_references_are_resolved() { qualified_name: "cls.foo.foo".into(), resulting_type: "INT".into(), constant: false, - is_auto_deref: false, argument_type: ArgumentType::ByVal(VariableType::Return), - is_reference_to: false, + auto_deref: None, }), annotation ); @@ -2453,8 +2448,7 @@ fn struct_member_explicit_initialization_test() { qualified_name: "myStruct.var1".to_string(), constant: false, argument_type: ArgumentType::ByVal(VariableType::Input), - is_auto_deref: false, - is_reference_to: false, + auto_deref: None, }), annotations.get(left) ); @@ -2468,8 +2462,7 @@ fn struct_member_explicit_initialization_test() { qualified_name: "myStruct.var2".to_string(), constant: false, argument_type: ArgumentType::ByVal(VariableType::Input), - is_auto_deref: false, - is_reference_to: false, + auto_deref: None, }), annotations.get(left) ); @@ -2949,9 +2942,8 @@ fn action_body_gets_resolved() { qualified_name: "prg.x".to_string(), resulting_type: "DINT".to_string(), constant: false, - is_auto_deref: false, argument_type: ArgumentType::ByVal(VariableType::Local), - is_reference_to: false, + auto_deref: None, }), a ); @@ -3393,10 +3385,9 @@ fn address_of_is_annotated_correctly() { if let Some(&StatementAnnotation::Value { resulting_type }) = annotations.get(s).as_ref() { assert_eq!( Some(&DataTypeInformation::Pointer { - is_reference_to: false, - auto_deref: false, inner_type_name: "INT".to_string(), name: "__POINTER_TO_INT".to_string(), + auto_deref: None, }), index.find_effective_type_info(resulting_type), ); @@ -3477,8 +3468,7 @@ fn call_explicit_parameter_name_is_resolved() { qualified_name: "fb.b".to_string(), constant: false, argument_type: ArgumentType::ByVal(VariableType::Input), - is_auto_deref: false, - is_reference_to: false, + auto_deref: None, }), annotations.get(b.as_ref()) ); @@ -3489,8 +3479,7 @@ fn call_explicit_parameter_name_is_resolved() { qualified_name: "fb.a".to_string(), constant: false, argument_type: ArgumentType::ByVal(VariableType::Input), - is_auto_deref: false, - is_reference_to: false, + auto_deref: None, }), annotations.get(a) ); @@ -3712,8 +3701,7 @@ fn function_block_initialization_test() { qualified_name: "TON.PT".into(), constant: false, argument_type: ArgumentType::ByVal(VariableType::Input), - is_auto_deref: false, - is_reference_to: false, + auto_deref: None, } ) } else { @@ -3848,8 +3836,7 @@ 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_reference_to: false, + auto_deref: None, } ) } @@ -5093,8 +5080,7 @@ fn annotate_variable_in_parent_class() { qualified_name: "cls1.LIGHT".to_string(), constant: false, argument_type: ArgumentType::ByVal(VariableType::Local,), - is_auto_deref: false, - is_reference_to: false, + auto_deref: None, }, annotation.unwrap() ); @@ -5109,8 +5095,7 @@ fn annotate_variable_in_parent_class() { qualified_name: "cls2.Light2".to_string(), constant: false, argument_type: ArgumentType::ByVal(VariableType::Local,), - is_auto_deref: false, - is_reference_to: false, + auto_deref: None, }, annotation.unwrap() ); @@ -5148,8 +5133,7 @@ fn annotate_variable_in_grandparent_class() { qualified_name: "cls0.LIGHT".to_string(), constant: false, argument_type: ArgumentType::ByVal(VariableType::Local,), - is_auto_deref: false, - is_reference_to: false, + auto_deref: None, }, annotation.unwrap() ); @@ -5194,8 +5178,7 @@ fn annotate_variable_in_field() { qualified_name: "cls0.LIGHT".to_string(), constant: false, argument_type: ArgumentType::ByVal(VariableType::Local,), - is_auto_deref: false, - is_reference_to: false, + auto_deref: None, }, annotation.unwrap() ); @@ -5252,8 +5235,7 @@ fn annotate_method_in_super() { qualified_name: "cls0.LIGHT".to_string(), constant: false, argument_type: ArgumentType::ByVal(VariableType::Local,), - is_auto_deref: false, - is_reference_to: false, + auto_deref: None, }, annotation.unwrap() ); @@ -5268,8 +5250,7 @@ fn annotate_method_in_super() { qualified_name: "cls1.LIGHT1".to_string(), constant: false, argument_type: ArgumentType::ByVal(VariableType::Local,), - is_auto_deref: false, - is_reference_to: false, + auto_deref: None, }, annotation.unwrap() ); @@ -5284,8 +5265,7 @@ fn annotate_method_in_super() { qualified_name: "cls0.LIGHT".to_string(), constant: false, argument_type: ArgumentType::ByVal(VariableType::Local,), - is_auto_deref: false, - is_reference_to: false, + auto_deref: None, }, annotation.unwrap() ); @@ -5300,8 +5280,7 @@ fn annotate_method_in_super() { qualified_name: "cls1.LIGHT1".to_string(), constant: false, argument_type: ArgumentType::ByVal(VariableType::Local,), - is_auto_deref: false, - is_reference_to: false, + auto_deref: None, }, annotation.unwrap() ); @@ -5316,8 +5295,7 @@ fn annotate_method_in_super() { qualified_name: "cls2.LIGHT2".to_string(), constant: false, argument_type: ArgumentType::ByVal(VariableType::Local,), - is_auto_deref: false, - is_reference_to: false, + auto_deref: None, }, annotation.unwrap() ); diff --git a/src/resolver/tests/resolve_generic_calls.rs b/src/resolver/tests/resolve_generic_calls.rs index 706f8e525c1..f517aa5157a 100644 --- a/src/resolver/tests/resolve_generic_calls.rs +++ b/src/resolver/tests/resolve_generic_calls.rs @@ -716,7 +716,7 @@ fn auto_pointer_of_generic_resolved() { let member = index.find_member("LEFT_EXT__DINT", "IN").unwrap(); let dt = index.find_effective_type_info(&member.data_type_name).unwrap(); - if let DataTypeInformation::Pointer { inner_type_name, auto_deref: true, .. } = dt { + if let DataTypeInformation::Pointer { inner_type_name, auto_deref: Some(_), .. } = dt { assert_eq!(inner_type_name, "DINT") } else { panic!("Expecting a pointer to dint, found {dt:?}") @@ -767,7 +767,7 @@ fn string_ref_as_generic_resolved() { let member = index.find_member("LEFT_EXT__STRING", "IN").unwrap(); let dt = index.find_effective_type_info(&member.data_type_name).unwrap(); - if let DataTypeInformation::Pointer { inner_type_name, auto_deref: true, .. } = dt { + if let DataTypeInformation::Pointer { inner_type_name, auto_deref: Some(_), .. } = dt { assert_eq!(inner_type_name, STRING_TYPE) } else { panic!("Expecting auto deref pointer to string, found {dt:?}") diff --git a/src/resolver/tests/resolve_literals_tests.rs b/src/resolver/tests/resolve_literals_tests.rs index 42d39cd54db..30636c1c701 100644 --- a/src/resolver/tests/resolve_literals_tests.rs +++ b/src/resolver/tests/resolve_literals_tests.rs @@ -312,8 +312,7 @@ fn enum_literals_target_are_annotated() { qualified_name: "Color.Red".into(), constant: true, argument_type: ArgumentType::ByVal(crate::index::VariableType::Global), - is_auto_deref: false, - is_reference_to: false, + auto_deref: None, }), annotations.get(target) ); diff --git a/src/tests/adr/annotated_ast_adr.rs b/src/tests/adr/annotated_ast_adr.rs index 6845bd21633..6bc6ffa1f0d 100644 --- a/src/tests/adr/annotated_ast_adr.rs +++ b/src/tests/adr/annotated_ast_adr.rs @@ -49,8 +49,7 @@ fn references_to_variables_are_annotated() { qualified_name: "prg.a".into(), constant: false, argument_type: ArgumentType::ByVal(VariableType::Local), - is_auto_deref: false, - is_reference_to: false, + auto_deref: None, } ); @@ -62,8 +61,7 @@ fn references_to_variables_are_annotated() { qualified_name: "gX".into(), constant: true, argument_type: ArgumentType::ByVal(VariableType::Global), - is_auto_deref: false, - is_reference_to: false, + auto_deref: None, } ); } @@ -106,9 +104,8 @@ fn different_types_of_annotations() { qualified_name: "POINT.x".into(), // the qualified name of the target element resulting_type: "SINT".into(), // the variable's type constant: false, // whether this variable is a constant or not - is_auto_deref: false, // whether this pointerType should be automatically dereferenced - is_reference_to: false, // whether this pointerType was declared as `REFERENCE TO`, making it auto-deref by default argument_type: ArgumentType::ByVal(VariableType::Input), // the type of declaration + auto_deref: None, }) ); @@ -156,9 +153,8 @@ fn different_types_of_annotations() { qualified_name: "Main.in".into(), resulting_type: "INT".into(), constant: false, - is_auto_deref: false, - is_reference_to: false, argument_type: ArgumentType::ByVal(VariableType::Input), + auto_deref: None, }) ); @@ -169,9 +165,8 @@ fn different_types_of_annotations() { qualified_name: "Main.in".into(), resulting_type: "INT".into(), constant: false, - is_auto_deref: false, - is_reference_to: false, argument_type: ArgumentType::ByVal(VariableType::Input), + auto_deref: None, }) ); } diff --git a/src/tests/adr/vla_adr.rs b/src/tests/adr/vla_adr.rs index 29c9816894b..38d3a8eb83c 100644 --- a/src/tests/adr/vla_adr.rs +++ b/src/tests/adr/vla_adr.rs @@ -103,8 +103,7 @@ fn representation() { information: Pointer { name: "__ptr_to___arr_vla_1_dint", inner_type_name: "__arr_vla_1_dint", - auto_deref: false, - is_reference_to: false, + auto_deref: None, }, nature: Any, location: SourceLocation { diff --git a/src/typesystem.rs b/src/typesystem.rs index 3e19f5665d2..b915271d7dd 100644 --- a/src/typesystem.rs +++ b/src/typesystem.rs @@ -6,7 +6,7 @@ use std::{ }; use plc_ast::{ - ast::{AstNode, Operator, PouType, TypeNature}, + ast::{AstNode, AutoDerefType, Operator, PouType, TypeNature}, literals::{AstLiteral, StringValue}, }; use plc_source::source_location::SourceLocation; @@ -390,9 +390,7 @@ pub enum DataTypeInformation { Pointer { name: TypeId, inner_type_name: TypeId, - auto_deref: bool, - /// Denotes whether the variable was declared as `REFERENCE TO`, e.g. `foo : REFERENCE TO DINT` - is_reference_to: bool, + auto_deref: Option, }, Integer { name: TypeId, @@ -445,6 +443,13 @@ impl DataTypeInformation { } } + pub fn get_inner_name(&self) -> &str { + match self { + DataTypeInformation::Pointer { inner_type_name, .. } => inner_type_name, + _ => self.get_name(), + } + } + pub fn is_void(&self) -> bool { matches!(self, DataTypeInformation::Void) } @@ -563,7 +568,16 @@ impl DataTypeInformation { /// Returns true if the variable was declared as `REFERENCE TO`, e.g. `foo : REFERENCE TO DINT`. pub fn is_reference_to(&self) -> bool { - matches!(self, DataTypeInformation::Pointer { is_reference_to: true, .. }) + matches!(self, DataTypeInformation::Pointer { auto_deref: Some(AutoDerefType::Reference), .. }) + } + + /// Returns true if the variable was declared as `REFERENCE TO`, e.g. `foo : REFERENCE TO DINT`. + pub fn is_alias(&self) -> bool { + matches!(self, DataTypeInformation::Pointer { auto_deref: Some(AutoDerefType::Alias), .. }) + } + + pub fn is_auto_deref(&self) -> bool { + matches!(self, DataTypeInformation::Pointer { auto_deref: Some(_), .. }) } pub fn is_aggregate(&self) -> bool { @@ -575,6 +589,14 @@ impl DataTypeInformation { ) } + pub fn get_auto_deref_type(&self) -> Option { + if let DataTypeInformation::Pointer { auto_deref: kind, .. } = self { + return *kind; + } + + None + } + pub fn is_date_or_time_type(&self) -> bool { matches!(self.get_name(), DATE_TYPE | DATE_AND_TIME_TYPE | TIME_OF_DAY_TYPE | TIME_TYPE) } diff --git a/src/validation/array.rs b/src/validation/array.rs index f87e7a367c9..596dc3ff0b4 100644 --- a/src/validation/array.rs +++ b/src/validation/array.rs @@ -194,7 +194,7 @@ impl<'a> StatementWrapper<'a> { where T: AnnotationMap, { - match self { + let ty = match self { StatementWrapper::Statement(statement) => { let AstNode { stmt: AstStatement::Assignment(data), .. } = statement else { return None }; context.annotations.get_type(&data.left, context.index).map(|it| it.get_type_information()) @@ -204,6 +204,11 @@ impl<'a> StatementWrapper<'a> { .data_type_declaration .get_referenced_type() .and_then(|it| context.index.find_effective_type_info(&it)), + }?; + + match ty { + DataTypeInformation::Pointer { .. } => Some(context.index.find_elementary_pointer_type(ty)), + _ => Some(ty), } } } diff --git a/src/validation/statement.rs b/src/validation/statement.rs index 21159721809..8c2aa880093 100644 --- a/src/validation/statement.rs +++ b/src/validation/statement.rs @@ -100,6 +100,8 @@ pub fn visit_statement( visit_statement(validator, &data.right, context); validate_ref_assignment(context, validator, data, &statement.location); + validate_alias_assignment(validator, context, statement); + validate_array_assignment(validator, context, statement); } AstStatement::CallStatement(data) => { validate_call(validator, &data.operator, data.parameters.as_deref(), &context.set_is_call()); @@ -769,6 +771,46 @@ fn validate_call_by_ref(validator: &mut Validator, param: &VariableIndexEntry, a } } +pub fn validate_pointer_assignment( + context: &ValidationContext, + validator: &mut Validator, + type_lhs: &DataType, + type_rhs: &DataType, + assignment_location: &SourceLocation, +) where + T: AnnotationMap, +{ + let type_info_lhs = context.index.find_elementary_pointer_type(type_lhs.get_type_information()); + let type_info_rhs = context.index.find_elementary_pointer_type(type_rhs.get_type_information()); + + if type_info_lhs.is_array() && type_info_rhs.is_array() { + let len_lhs = type_info_lhs.get_array_length(context.index).unwrap_or_default(); + let len_rhs = type_info_rhs.get_array_length(context.index).unwrap_or_default(); + + let inner_ty_name_lhs = type_info_lhs.get_inner_array_type_name().unwrap_or(VOID_TYPE); + let inner_ty_name_rhs = type_info_rhs.get_inner_array_type_name().unwrap_or(VOID_TYPE); + let inner_ty_lhs = context.index.find_effective_type_by_name(inner_ty_name_lhs); + let inner_ty_rhs = context.index.find_effective_type_by_name(inner_ty_name_rhs); + + if len_lhs != len_rhs || inner_ty_lhs != inner_ty_rhs { + validator.push_diagnostic(Diagnostic::invalid_assignment( + &get_datatype_name_or_slice(validator.context, type_rhs), + &get_datatype_name_or_slice(validator.context, type_lhs), + assignment_location, + )); + } + } else if type_info_lhs != type_info_rhs { + let type_name_lhs = get_datatype_name_or_slice(validator.context, type_lhs); + let type_name_rhs = get_datatype_name_or_slice(validator.context, type_rhs); + + validator.push_diagnostic(Diagnostic::invalid_assignment( + &type_name_rhs, + &type_name_lhs, + assignment_location, + )); + } +} + /// Checks if `REF=` assignments are correct, specifically if the left-hand side is a reference declared /// as `REFERENCE TO` and the right hand side is a lvalue of the same type that is being referenced. fn validate_ref_assignment( @@ -777,11 +819,9 @@ fn validate_ref_assignment( assignment: &Assignment, assignment_location: &SourceLocation, ) { + let annotation_lhs = context.annotations.get(&assignment.left); let type_lhs = context.annotations.get_type_or_void(&assignment.left, context.index); let type_rhs = context.annotations.get_type_or_void(&assignment.right, context.index); - let type_info_lhs = context.index.find_elementary_pointer_type(type_lhs.get_type_information()); - let type_info_rhs = context.index.find_elementary_pointer_type(type_rhs.get_type_information()); - let annotation_lhs = context.annotations.get(&assignment.left); // Assert that the right-hand side is a reference if !assignment.right.is_reference() { @@ -793,7 +833,7 @@ fn validate_ref_assignment( } // Assert that the left-hand side is a valid pointer-reference - if !annotation_lhs.is_some_and(StatementAnnotation::is_reference_to) && !type_lhs.is_pointer() { + if !type_lhs.is_pointer() && !annotation_lhs.is_some_and(|opt| opt.is_auto_deref()) { validator.push_diagnostic( Diagnostic::new("Invalid assignment, expected a pointer reference") .with_location(&assignment.left.location) @@ -801,46 +841,26 @@ fn validate_ref_assignment( ) } - if type_info_lhs.is_array() && type_info_rhs.is_array() { - let mut messages = Vec::new(); - - let len_lhs = type_info_lhs.get_array_length(context.index).unwrap_or_default(); - let len_rhs = type_info_rhs.get_array_length(context.index).unwrap_or_default(); - - if len_lhs < len_rhs { - messages.push(format!("Invalid assignment, array lengths {len_lhs} and {len_rhs} differ")); - } - - let inner_ty_name_lhs = type_info_lhs.get_inner_array_type_name().unwrap_or(VOID_TYPE); - let inner_ty_name_rhs = type_info_rhs.get_inner_array_type_name().unwrap_or(VOID_TYPE); - let inner_ty_lhs = context.index.find_effective_type_by_name(inner_ty_name_lhs); - let inner_ty_rhs = context.index.find_effective_type_by_name(inner_ty_name_rhs); - - if inner_ty_lhs != inner_ty_rhs { - messages.push(format!( - "Invalid assignment, array types {inner_ty_name_lhs} and {inner_ty_name_rhs} differ" - )); - } + validate_pointer_assignment(context, validator, type_lhs, type_rhs, assignment_location); +} - for message in messages { +/// Returns a diagnostic if an alias declared variables address is re-assigned in the POU body. +fn validate_alias_assignment( + validator: &mut Validator, + context: &ValidationContext, + ref_assignment: &AstNode, +) { + if let AstStatement::RefAssignment(Assignment { left, .. }) = ref_assignment.get_stmt() { + if context.annotations.get(left).is_some_and(|opt| opt.is_alias()) { validator.push_diagnostic( - Diagnostic::new(message).with_location(assignment_location).with_error_code("E098"), + Diagnostic::new(format!( + "{} is an immutable alias variable, can not change the address", + validator.context.slice(&left.location) + )) + .with_location(&ref_assignment.location) + .with_error_code("E100"), ) } - - return; - } - - if type_info_lhs != type_info_rhs { - validator.push_diagnostic( - Diagnostic::new(format!( - "Invalid assignment, types {} and {} differ", - get_datatype_name_or_slice(validator.context, type_lhs), - get_datatype_name_or_slice(validator.context, type_rhs), - )) - .with_location(assignment_location) - .with_error_code("E098"), - ); } } @@ -909,7 +929,7 @@ fn validate_assignment( if let (Some(right_type), Some(left_type)) = (right_type, left_type) { // implicit call parameter assignments are annotated to auto_deref pointers for ´ByRef` parameters // we need the inner type - let left_type = if let DataTypeInformation::Pointer { inner_type_name, auto_deref: true, .. } = + let left_type = if let DataTypeInformation::Pointer { inner_type_name, auto_deref: Some(_), .. } = left_type.get_type_information() { context.index.get_effective_type_or_void_by_name(inner_type_name) @@ -938,11 +958,7 @@ fn validate_assignment( .with_location(location), ); } else { - validator.push_diagnostic(Diagnostic::invalid_assignment( - &get_datatype_name_or_slice(validator.context, right_type), - &get_datatype_name_or_slice(validator.context, left_type), - location.clone(), - )); + validate_pointer_assignment(context, validator, left_type, right_type, location); } } else { validate_assignment_type_sizes(validator, left_type, right, context) @@ -1137,7 +1153,7 @@ fn is_invalid_pointer_assignment( return !typesystem::is_same_type_class(left_type, right_type, index); } //check if Datatype can hold a Pointer (u64) - else if right_type.is_pointer() + else if (right_type.is_pointer() && !right_type.is_auto_deref()) && !left_type.is_pointer() && left_type.get_size_in_bits(index) < POINTER_SIZE { @@ -1595,7 +1611,7 @@ fn validate_argument_count( } } -mod helper { +pub(crate) mod helper { use std::ops::Range; use plc_ast::ast::{AstNode, DirectAccessType}; @@ -1633,7 +1649,7 @@ mod helper { pub fn get_datatype_name_or_slice(context: &GlobalContext, dt: &DataType) -> String { if dt.is_internal() { - return dt.get_type_information().get_name().to_string(); + return dt.get_type_information().get_inner_name().to_string(); } context.slice(&dt.location) diff --git a/src/validation/tests/assignment_validation_tests.rs b/src/validation/tests/assignment_validation_tests.rs index 219dc6872d7..ddf4ce82f63 100644 --- a/src/validation/tests/assignment_validation_tests.rs +++ b/src/validation/tests/assignment_validation_tests.rs @@ -1,6 +1,6 @@ -use insta::assert_snapshot; +use insta::{assert_debug_snapshot, assert_snapshot}; -use crate::test_utils::tests::parse_and_validate_buffered; +use crate::test_utils::tests::{parse_and_validate, parse_and_validate_buffered}; #[test] fn constant_assignment_validation() { @@ -1261,11 +1261,11 @@ fn ref_assignments() { 17 │ localINT REF= localDINT; │ ^^^^^^^^ Invalid assignment, expected a pointer reference - error[E098]: Invalid assignment, types INT and DINT differ + error[E037]: Invalid assignment: cannot assign 'DINT' to 'INT' ┌─ :17:13 │ 17 │ localINT REF= localDINT; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment, types INT and DINT differ + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment: cannot assign 'DINT' to 'INT' error[E098]: Invalid assignment, expected a reference ┌─ :18:38 @@ -1279,17 +1279,17 @@ fn ref_assignments() { 19 │ localReferenceTo REF= 1; │ ^ Invalid assignment, expected a reference - error[E098]: Invalid assignment, types DINT and INT differ + error[E037]: Invalid assignment: cannot assign 'INT' to 'DINT' ┌─ :21:13 │ 21 │ localReferenceTo REF= localINT; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment, types DINT and INT differ + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment: cannot assign 'INT' to 'DINT' - error[E098]: Invalid assignment, types DINT and STRING differ + error[E037]: Invalid assignment: cannot assign 'STRING' to 'DINT' ┌─ :22:13 │ 22 │ localReferenceTo REF= localSTRING; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment, types DINT and STRING differ + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment: cannot assign 'STRING' to 'DINT' error[E098]: Invalid assignment, expected a reference ┌─ :23:38 @@ -1297,11 +1297,11 @@ fn ref_assignments() { 23 │ localReferenceTo REF= 'howdy'; │ ^^^^^^^ Invalid assignment, expected a reference - error[E098]: Invalid assignment, types DINT and STRING differ + error[E037]: Invalid assignment: cannot assign 'STRING' to 'DINT' ┌─ :23:13 │ 23 │ localReferenceTo REF= 'howdy'; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment, types DINT and STRING differ + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment: cannot assign 'STRING' to 'DINT' "###); } @@ -1355,23 +1355,17 @@ fn ref_assignment_with_global_local_variables_and_aliased_types() { 18 │ invalidB : REFERENCE TO fooGlobal; │ ^^^^^^^^^^^^^^^^^^^^^^ REFERENCE TO variables can not reference other variables - error[E099]: Initializations of REFERENCE TO variables are disallowed - ┌─ :19:49 - │ - 19 │ invalidC : REFERENCE TO DINT := 5; - │ ^ Initializations of REFERENCE TO variables are disallowed - - error[E098]: Invalid assignment, types DINT and INT differ + error[E037]: Invalid assignment: cannot assign 'INT' to 'DINT' ┌─ :28:13 │ 28 │ referenceToFooFirstOfHisName REF= intLocal; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment, types DINT and INT differ + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment: cannot assign 'INT' to 'DINT' - error[E098]: Invalid assignment, types DINT and STRING differ + error[E037]: Invalid assignment: cannot assign 'STRING' to 'DINT' ┌─ :29:13 │ 29 │ referenceToFooFirstOfHisName REF= stringLocal; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment, types DINT and STRING differ + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment: cannot assign 'STRING' to 'DINT' "###); } @@ -1394,17 +1388,11 @@ fn ref_assignment_with_reference_to_array_variable() { ); assert_snapshot!(diagnostics, @r###" - error[E098]: Invalid assignment, array lengths 5 and 6 differ - ┌─ :10:13 - │ - 10 │ arrReferenceDINT REF= arrSTRING; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment, array lengths 5 and 6 differ - - error[E098]: Invalid assignment, array types DINT and STRING differ + error[E037]: Invalid assignment: cannot assign 'ARRAY[1..6] OF STRING' to 'REFERENCE TO ARRAY[1..5] OF DINT' ┌─ :10:13 │ 10 │ arrReferenceDINT REF= arrSTRING; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment, array types DINT and STRING differ + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment: cannot assign 'ARRAY[1..6] OF STRING' to 'REFERENCE TO ARRAY[1..5] OF DINT' "###); } @@ -1429,17 +1417,17 @@ fn ref_assignment_with_reference_to_string_variable() { ); assert_snapshot!(diagnostics, @r###" - error[E098]: Invalid assignment, types STRING and CHAR differ + error[E037]: Invalid assignment: cannot assign 'CHAR' to 'STRING' ┌─ :10:13 │ 10 │ referenceToString REF= localCHAR; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment, types STRING and CHAR differ + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment: cannot assign 'CHAR' to 'STRING' - error[E098]: Invalid assignment, types STRING and WSTRING differ + error[E037]: Invalid assignment: cannot assign 'WSTRING' to 'STRING' ┌─ :12:13 │ 12 │ referenceToString REF= localWSTRING; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment, types STRING and WSTRING differ + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment: cannot assign 'WSTRING' to 'STRING' "###); } @@ -1491,3 +1479,103 @@ fn invalid_reference_to_declaration() { "###); } + +#[test] +fn alias_variable_type_check() { + let diagnostics = parse_and_validate( + r" + FUNCTION foo + VAR + dintVar : DINT; + sintVar : SINT; + stringVar : STRING; + arrayDintVar : ARRAY[1..5] OF DINT; + + dintVarRefA AT dintVar : DINT; // Valid + dintVarRefB AT sintVar : DINT; // Invalid + dintVarRefC AT stringVar : DINT; // Invalid + dintVarRefD AT arrayDintVar : DINT; // Invalid + + sintVarRefA AT dintVar : SINT; // Invalid + sintVarRefB AT sintVar : SINT; // Valid + sintVarRefC AT stringVar : SINT; // Invalid + sintVarRefD AT arrayDintVar : SINT; // Invalid + + stringVarRefA AT dintVar : STRING; // Invalid + stringVarRefB AT sintVar : STRING; // Invalid + stringVarRefC AT stringVar : STRING; // Valid + stringVarRefD AT arrayDintVar : STRING; // Invalid + + arrayDintVarRefA AT dintVar : ARRAY[1..5] OF DINT; // Invalid + arrayDintVarRefB AT sintVar : ARRAY[1..5] OF DINT; // Invalid + arrayDintVarRefC AT stringVar : ARRAY[1..5] OF DINT; // Invalid + arrayDintVarRefD AT arrayDintVar : ARRAY[1..5] OF DINT; // Valid + END_VAR + END_FUNCTION + ", + ); + + // Note: This assertion must fail once init functions are implemented and can then also be deleted + assert!(diagnostics.iter().any(|diagnostics| diagnostics.get_error_code() == "E033")); + + // TODO: Use parse_and_validate_buffered once above assertion is deleted + let diagnostics_messages_without_const_error = diagnostics + .iter() + .filter(|diagnostic| diagnostic.get_error_code() != "E033") + .map(|diagnostic| diagnostic.get_message()) + .collect::>(); + + assert_eq!(diagnostics_messages_without_const_error.len(), 12); + assert_debug_snapshot!(diagnostics_messages_without_const_error, @r###" + [ + "Invalid assignment: cannot assign 'SINT' to 'DINT'", + "Invalid assignment: cannot assign 'STRING' to 'DINT'", + "Invalid assignment: cannot assign '__foo_arrayDintVar' to 'DINT'", + "Invalid assignment: cannot assign 'DINT' to 'SINT'", + "Invalid assignment: cannot assign 'STRING' to 'SINT'", + "Invalid assignment: cannot assign '__foo_arrayDintVar' to 'SINT'", + "Invalid assignment: cannot assign 'DINT' to 'STRING'", + "Invalid assignment: cannot assign 'SINT' to 'STRING'", + "Invalid assignment: cannot assign '__foo_arrayDintVar' to 'STRING'", + "Invalid assignment: cannot assign 'DINT' to 'ARRAY[1..5] OF DINT'", + "Invalid assignment: cannot assign 'SINT' to 'ARRAY[1..5] OF DINT'", + "Invalid assignment: cannot assign 'STRING' to 'ARRAY[1..5] OF DINT'", + ] + "###); +} + +#[test] +fn reassignment_of_alias_variables_is_disallowed() { + let diagnostics = parse_and_validate( + r" + FUNCTION main + VAR + foo AT bar : DINT; + bar : DINT; + baz : DINT; + END_VAR + + foo := bar; // Valid, the dereferenced value of `foo` is being changed + foo := baz; // Valid, same reason as above + foo REF= bar; // Invalid, the address of `foo` is being changed + END_FUNCTION + ", + ); + + // Note: This assertion must fail once init functions are implemented and can then also be deleted + assert!(diagnostics.iter().any(|diagnostics| diagnostics.get_error_code() == "E033")); + + // TODO: Use parse_and_validate_buffered once above assertion is deleted + let diagnostics_messages_without_const_error = diagnostics + .iter() + .filter(|diagnostic| diagnostic.get_error_code() != "E033") + .map(|diagnostic| diagnostic.get_message()) + .collect::>(); + + assert_eq!(diagnostics_messages_without_const_error.len(), 1); + assert_debug_snapshot!(diagnostics_messages_without_const_error, @r###" + [ + "foo is an immutable alias variable, can not change the address", + ] + "###); +} diff --git a/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__pointer_assignment_validation.snap b/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__pointer_assignment_validation.snap index 3318e16d805..812104a4a62 100644 --- a/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__pointer_assignment_validation.snap +++ b/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__pointer_assignment_validation.snap @@ -26,11 +26,11 @@ error[E037]: Invalid assignment: cannot assign 'REF_TO INT' to 'WORD' 29 │ v_word := v_ptr_int; // INVALID │ ^^^^^^^^^^^^^^^^^^^ Invalid assignment: cannot assign 'REF_TO INT' to 'WORD' -warning[E090]: Pointers REF_TO INT and __POINTER_TO_REAL have different types +warning[E090]: Pointers REF_TO INT and REAL have different types ┌─ :31:5 │ 31 │ v_ptr_int := REF(v_real); // INVALID -> TODO: should be valid - │ ^^^^^^^^^^^^^^^^^^^^^^^^ Pointers REF_TO INT and __POINTER_TO_REAL have different types + │ ^^^^^^^^^^^^^^^^^^^^^^^^ Pointers REF_TO INT and REAL have different types warning[E067]: Implicit downcast from 'REAL' to 'INT'. ┌─ :32:19 @@ -62,11 +62,11 @@ warning[E067]: Implicit downcast from 'WORD' to 'INT'. 40 │ v_ptr_int^ := v_word; // valid │ ^^^^^^ Implicit downcast from 'WORD' to 'INT'. -warning[E090]: Pointers REF_TO INT and __POINTER_TO_STRING have different types +warning[E090]: Pointers REF_TO INT and STRING have different types ┌─ :41:5 │ 41 │ v_ptr_int := REF(v_string); // INVALID - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ Pointers REF_TO INT and __POINTER_TO_STRING have different types + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ Pointers REF_TO INT and STRING have different types error[E037]: Invalid assignment: cannot assign 'STRING' to 'INT' ┌─ :42:5 diff --git a/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__struct_assignment_validation.snap b/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__struct_assignment_validation.snap index 8e77f342fee..1eeb0996e84 100644 --- a/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__struct_assignment_validation.snap +++ b/src/validation/tests/snapshots/rusty__validation__tests__assignment_validation_tests__struct_assignment_validation.snap @@ -32,20 +32,20 @@ error[E037]: Invalid assignment: cannot assign 'STRUCT2' to 'STRUCT1' 56 │ myFB(var_inout_struct1 := v_struct2); // INVALID │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment: cannot assign 'STRUCT2' to 'STRUCT1' -warning[E090]: Pointers REF_TO STRUCT1 and __POINTER_TO_REAL have different types +warning[E090]: Pointers REF_TO STRUCT1 and REAL have different types ┌─ :66:5 │ 66 │ v_ref_to_struct1 := REF(v_real); // INVALID - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Pointers REF_TO STRUCT1 and __POINTER_TO_REAL have different types + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Pointers REF_TO STRUCT1 and REAL have different types -warning[E090]: Pointers REF_TO STRUCT1 and __POINTER_TO_STRING have different types +warning[E090]: Pointers REF_TO STRUCT1 and STRING have different types ┌─ :67:5 │ 67 │ v_ref_to_struct1 := REF(v_string); // INVALID - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Pointers REF_TO STRUCT1 and __POINTER_TO_STRING have different types + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Pointers REF_TO STRUCT1 and STRING have different types -warning[E090]: Pointers REF_TO STRUCT1 and __POINTER_TO_CHAR have different types +warning[E090]: Pointers REF_TO STRUCT1 and CHAR have different types ┌─ :68:5 │ 68 │ v_ref_to_struct1 := REF(v_char); // INVALID - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Pointers REF_TO STRUCT1 and __POINTER_TO_CHAR have different types + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Pointers REF_TO STRUCT1 and CHAR have different types diff --git a/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__reference_to_reference_assignments_in_function_arguments.snap b/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__reference_to_reference_assignments_in_function_arguments.snap index 46c02e259f3..35513a3c8eb 100644 --- a/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__reference_to_reference_assignments_in_function_arguments.snap +++ b/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__reference_to_reference_assignments_in_function_arguments.snap @@ -2,20 +2,20 @@ source: src/validation/tests/statement_validation_tests.rs expression: "&diagnostics" --- -warning[E090]: Pointers REF_TO STRUCT_params and __POINTER_TO_INT have different types +warning[E090]: Pointers REF_TO STRUCT_params and INT have different types ┌─ :47:13 │ 47 │ input1 := REF(global4), - │ ^^^^^^^^^^^^^^^^^^^^^^ Pointers REF_TO STRUCT_params and __POINTER_TO_INT have different types + │ ^^^^^^^^^^^^^^^^^^^^^^ Pointers REF_TO STRUCT_params and INT have different types -warning[E090]: Pointers REF_TO STRUCT_params and __POINTER_TO_REAL have different types +warning[E090]: Pointers REF_TO STRUCT_params and REAL have different types ┌─ :48:13 │ 48 │ input2 := REF(global5), - │ ^^^^^^^^^^^^^^^^^^^^^^ Pointers REF_TO STRUCT_params and __POINTER_TO_REAL have different types + │ ^^^^^^^^^^^^^^^^^^^^^^ Pointers REF_TO STRUCT_params and REAL have different types -warning[E090]: Pointers REF_TO STRUCT_params and __POINTER_TO_STRING have different types +warning[E090]: Pointers REF_TO STRUCT_params and STRING have different types ┌─ :49:13 │ 49 │ input3 := REF(global6), - │ ^^^^^^^^^^^^^^^^^^^^^^ Pointers REF_TO STRUCT_params and __POINTER_TO_STRING have different types + │ ^^^^^^^^^^^^^^^^^^^^^^ Pointers REF_TO STRUCT_params and STRING have different types diff --git a/src/validation/variable.rs b/src/validation/variable.rs index ed074817340..174454483af 100644 --- a/src/validation/variable.rs +++ b/src/validation/variable.rs @@ -3,7 +3,7 @@ use plc_diagnostics::diagnostics::Diagnostic; use super::{ array::validate_array_assignment, - statement::{validate_enum_variant_assignment, visit_statement}, + statement::{validate_enum_variant_assignment, validate_pointer_assignment, visit_statement}, types::{data_type_is_fb_or_class_instance, visit_data_type_declaration}, ValidationContext, Validator, Validators, }; @@ -210,7 +210,7 @@ fn validate_variable( } } -/// Returns a diagnostic if a `REFERENCE TO` variable is incorrectly declared (or initialized). +/// Returns a diagnostic if a `REFERENCE TO` variable is incorrectly declared. fn validate_reference_to_declaration( validator: &mut Validator, context: &ValidationContext, @@ -221,7 +221,8 @@ fn validate_reference_to_declaration( return; }; - if !variable_ty.get_type_information().is_reference_to() { + if !variable_ty.get_type_information().is_reference_to() && !variable_ty.get_type_information().is_alias() + { return; } @@ -229,17 +230,6 @@ fn validate_reference_to_declaration( unreachable!("`REFERENCE TO` is defined as a pointer, hence this must exist") }; - // Assert that no initializers are present in the `REFERENCE TO` declaration - if let Some(ref initializer) = variable.initializer { - if variable_ty.get_type_information().is_reference_to() { - validator.push_diagnostic( - Diagnostic::new("Initializations of REFERENCE TO variables are disallowed") - .with_location(&initializer.location) - .with_error_code("E099"), - ); - } - } - // Assert that the referenced type is no variable reference let qualifier = context.qualifier.unwrap_or_default(); let inner_ty_is_local_var = context.index.find_member(qualifier, inner_ty_name).is_some(); @@ -252,6 +242,13 @@ fn validate_reference_to_declaration( .with_error_code("E099"), ); } + + if let Some(ref initializer) = variable.initializer { + let type_lhs = context.index.find_type(inner_ty_name).unwrap(); + let type_rhs = context.annotations.get_type(initializer, context.index).unwrap(); + + validate_pointer_assignment(context, validator, type_lhs, type_rhs, &initializer.location); + } } #[cfg(test)] diff --git a/tests/integration/cfc/resolver_tests.rs b/tests/integration/cfc/resolver_tests.rs index dd11728f656..76165b9798f 100644 --- a/tests/integration/cfc/resolver_tests.rs +++ b/tests/integration/cfc/resolver_tests.rs @@ -98,8 +98,7 @@ fn function_block_calls_are_annotated_correctly() { argument_type: ByVal( Local, ), - is_auto_deref: false, - is_reference_to: false, + auto_deref: None, } "###); } diff --git a/tests/integration/cfc/snapshots/tests__integration__cfc__resolver_tests__action_variables_annotated-3.snap b/tests/integration/cfc/snapshots/tests__integration__cfc__resolver_tests__action_variables_annotated-3.snap index 5fdbebac50d..ab63eb8fa6c 100644 --- a/tests/integration/cfc/snapshots/tests__integration__cfc__resolver_tests__action_variables_annotated-3.snap +++ b/tests/integration/cfc/snapshots/tests__integration__cfc__resolver_tests__action_variables_annotated-3.snap @@ -10,7 +10,6 @@ Some( argument_type: ByVal( Local, ), - is_auto_deref: false, - is_reference_to: false, + auto_deref: None, }, ) diff --git a/tests/integration/cfc/snapshots/tests__integration__cfc__resolver_tests__action_variables_annotated-4.snap b/tests/integration/cfc/snapshots/tests__integration__cfc__resolver_tests__action_variables_annotated-4.snap index 2d8cbaeaf2f..1c4ab20701f 100644 --- a/tests/integration/cfc/snapshots/tests__integration__cfc__resolver_tests__action_variables_annotated-4.snap +++ b/tests/integration/cfc/snapshots/tests__integration__cfc__resolver_tests__action_variables_annotated-4.snap @@ -10,7 +10,6 @@ Some( argument_type: ByVal( Local, ), - is_auto_deref: false, - is_reference_to: false, + auto_deref: None, }, ) diff --git a/tests/lit/single/pointer/alias_array.ignore b/tests/lit/single/pointer/alias_array.ignore new file mode 100644 index 00000000000..7cd997cb82e --- /dev/null +++ b/tests/lit/single/pointer/alias_array.ignore @@ -0,0 +1,14 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s +FUNCTION main: DINT + VAR + refArr AT arr : ARRAY[1..3] OF DINT; + arr : ARRAY[1..3] OF DINT; + END_VAR + + arr[1] := 3; + arr[2] := 2; + arr[3] := 1; + + // CHECK: 1, 2, 3 + printf('%d, %d, %d$N', refArr[3], refArr[2], refArr[1]); +END_FUNCTION \ No newline at end of file diff --git a/tests/lit/single/pointer/alias_autoderef.ignore b/tests/lit/single/pointer/alias_autoderef.ignore new file mode 100644 index 00000000000..eb9be37b0c5 --- /dev/null +++ b/tests/lit/single/pointer/alias_autoderef.ignore @@ -0,0 +1,13 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s +FUNCTION main: DINT + VAR + foo AT bar : DINT; + bar : DINT; + END_VAR + + bar := 1; + + // CHECK: 2 + bar := bar + foo; // bar + bar => 1 + 1 + printf('%d$N', foo); +END_FUNCTION \ No newline at end of file diff --git a/tests/lit/single/pointer/alias_struct.ignore b/tests/lit/single/pointer/alias_struct.ignore new file mode 100644 index 00000000000..c0c5f41512e --- /dev/null +++ b/tests/lit/single/pointer/alias_struct.ignore @@ -0,0 +1,22 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s +TYPE Transaction : STRUCT + id : DINT; + amount : DINT; + message : STRING; +END_STRUCT END_TYPE + +FUNCTION main : DINT + VAR + txn : Transaction := (id := 1, amount := 5, message := 'whats up'); + refTxn AT txn : Transaction; + END_VAR + + // CHECK: 1 + printf('%d$N', refTxn.id); + + // CHECK: 5 + printf('%d$N', refTxn.amount); + + // CHECK: whats up + printf('%s$N', REF(refTxn.message)); +END_FUNCTION \ No newline at end of file