diff --git a/compiler/plc_ast/src/ast.rs b/compiler/plc_ast/src/ast.rs index e7353608c2..20ce291232 100644 --- a/compiler/plc_ast/src/ast.rs +++ b/compiler/plc_ast/src/ast.rs @@ -487,6 +487,9 @@ 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, }, StringType { name: Option, @@ -596,11 +599,14 @@ pub struct AstNode { #[derive(Debug, Clone, PartialEq)] pub enum AstStatement { EmptyStatement(EmptyStatement), - // a placeholder that indicates a default value of a datatype + + // A placeholder which indicates a default value of a datatype DefaultValue(DefaultValue), + // Literals Literal(AstLiteral), MultipliedStatement(MultipliedStatement), + // Expressions ReferenceExpr(ReferenceExpr), Identifier(String), @@ -612,15 +618,17 @@ pub enum AstStatement { ParenExpression(Box), RangeStatement(RangeStatement), VlaRangeStatement, - // Assignment + + // TODO: Merge these variants with a `kind` field? + // Assignments Assignment(Assignment), - // OutputAssignment OutputAssignment(Assignment), - //Call Statement + RefAssignment(Assignment), + CallStatement(CallStatement), + // Control Statements ControlStatement(AstControlStatement), - CaseCondition(Box), ExitStatement(()), ContinueStatement(()), @@ -661,6 +669,9 @@ impl Debug for AstNode { AstStatement::OutputAssignment(Assignment { left, right }) => { f.debug_struct("OutputAssignment").field("left", left).field("right", right).finish() } + AstStatement::RefAssignment(Assignment { left, right }) => { + f.debug_struct("ReferenceAssignment").field("left", left).field("right", right).finish() + } AstStatement::CallStatement(CallStatement { operator, parameters }) => f .debug_struct("CallStatement") .field("operator", operator) @@ -1319,6 +1330,19 @@ impl AstFactory { ) } + // TODO: Merge `create_assignment`, `create_output_assignment` and `create_ref_assignment` + // once the the Assignment AstStatements have been merged and a `kind` field is available + // I.e. something like `AstStatement::Assignment { data, kind: AssignmentKind { Normal, Output, Reference } } + // and then fn create_assignment(kind: AssignmentKind, ...) + pub fn create_ref_assignment(left: AstNode, right: AstNode, id: AstId) -> AstNode { + let location = left.location.span(&right.location); + AstNode::new( + AstStatement::RefAssignment(Assignment { left: Box::new(left), right: Box::new(right) }), + id, + location, + ) + } + pub fn create_member_reference(member: AstNode, base: Option, id: AstId) -> AstNode { let location = base .as_ref() diff --git a/compiler/plc_ast/src/visitor.rs b/compiler/plc_ast/src/visitor.rs index 0aab9108ea..c50b7c3c1b 100644 --- a/compiler/plc_ast/src/visitor.rs +++ b/compiler/plc_ast/src/visitor.rs @@ -312,6 +312,15 @@ pub trait AstVisitor: Sized { stmt.walk(self) } + /// Visits an `RefAssignment` node. + /// Make sure to call `walk` on the `Assignment` node to visit its children. + /// # Arguments + /// * `stmt` - The unwraped, typed `Assignment` node to visit. + /// * `node` - The wrapped `AstNode` node to visit. Offers access to location information and AstId + fn visit_ref_assignment(&mut self, stmt: &Assignment, _node: &AstNode) { + stmt.walk(self) + } + /// Visits a `CallStatement` node. /// Make sure to call `walk` on the `CallStatement` node to visit its children. /// # Arguments @@ -556,6 +565,7 @@ impl Walker for AstNode { AstStatement::VlaRangeStatement => visitor.visit_vla_range_statement(node), AstStatement::Assignment(stmt) => visitor.visit_assignment(stmt, node), AstStatement::OutputAssignment(stmt) => visitor.visit_output_assignment(stmt, node), + AstStatement::RefAssignment(stmt) => visitor.visit_ref_assignment(stmt, node), AstStatement::CallStatement(stmt) => visitor.visit_call_statement(stmt, node), AstStatement::ControlStatement(stmt) => visitor.visit_control_statement(stmt, node), AstStatement::CaseCondition(stmt) => visitor.visit_case_condition(stmt, node), diff --git a/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs b/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs index f076bdf045..874f1c4eb1 100644 --- a/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs +++ b/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs @@ -199,6 +199,8 @@ lazy_static! { E095, Error, include_str!("./error_codes/E095.md"), // Action call without `()` E096, Warning, include_str!("./error_codes/E096.md"), // Integer Condition 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 ); } diff --git a/compiler/plc_diagnostics/src/diagnostics/error_codes/E098.md b/compiler/plc_diagnostics/src/diagnostics/error_codes/E098.md new file mode 100644 index 0000000000..f550271947 --- /dev/null +++ b/compiler/plc_diagnostics/src/diagnostics/error_codes/E098.md @@ -0,0 +1,19 @@ +# Invalid REF= assignment + +`REF=` assignments are considered valid if the left-hand side of the assignment is a pointer variable +and the right-hand side is a variable of the type that is being referenced. + +For example assignments such as the following are invalid + +```smalltalk +VAR + foo : DINT; + bar : DINT; + qux : SINT; + refFoo : REFERENCE TO DINT; +END_VAR + +refFoo REF= 5; // `5` is not a variable +foo REF= bar; // `foo` is not a pointer +refFoo REF= qux; // `refFoo` and `qux` have different types, DINT vs SINT +``` \ No newline at end of file diff --git a/compiler/plc_diagnostics/src/diagnostics/error_codes/E099.md b/compiler/plc_diagnostics/src/diagnostics/error_codes/E099.md new file mode 100644 index 0000000000..d327378250 --- /dev/null +++ b/compiler/plc_diagnostics/src/diagnostics/error_codes/E099.md @@ -0,0 +1,8 @@ +# Invalid `REFERENCE TO` declaration + +`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 diff --git a/src/codegen/generators/expression_generator.rs b/src/codegen/generators/expression_generator.rs index 3ad0a9abf7..b9a1941bb5 100644 --- a/src/codegen/generators/expression_generator.rs +++ b/src/codegen/generators/expression_generator.rs @@ -2507,14 +2507,13 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { /// - `access` the ReferenceAccess of the reference to generate /// - `base` the "previous" segment of an optional qualified reference-access /// - `original_expression` the original ast-statement used to report Diagnostics - fn generate_reference_expression( + pub(crate) fn generate_reference_expression( &self, access: &ReferenceAccess, base: Option<&AstNode>, original_expression: &AstNode, ) -> Result, Diagnostic> { match (access, base) { - // expressions like `base.member`, or just `member` (ReferenceAccess::Member(member), base) => { let base_value = base.map(|it| self.generate_expression_value(it)).transpose()?; diff --git a/src/codegen/generators/statement_generator.rs b/src/codegen/generators/statement_generator.rs index d9edbd130c..7c053ddf93 100644 --- a/src/codegen/generators/statement_generator.rs +++ b/src/codegen/generators/statement_generator.rs @@ -125,7 +125,9 @@ impl<'a, 'b> StatementCodeGenerator<'a, 'b> { AstStatement::Assignment(data, ..) => { self.generate_assignment_statement(&data.left, &data.right)?; } - + AstStatement::RefAssignment(data, ..) => { + self.generate_ref_assignment(&data.left, &data.right)?; + } AstStatement::ControlStatement(ctl_statement, ..) => { self.generate_control_statement(ctl_statement)? } @@ -234,6 +236,31 @@ impl<'a, 'b> StatementCodeGenerator<'a, 'b> { } } + /// Generates IR for a `REF=` assignment, which is syntactic sugar for an assignment where the + /// right-hand side is wrapped in a `REF(...)` call. Specifically `foo REF= bar` and + /// `foo := REF(bar)` are the same. + /// + /// Note: Although somewhat similar to the [`generate_assignment_statement`] function, we can't + /// apply the code here because the left side of a `REF=` assignment is flagged as auto-deref. + /// For `REF=` assignments we don't want (and can't) deref without generating incorrect IR. + pub fn generate_ref_assignment(&self, left: &AstNode, right: &AstNode) -> Result<(), Diagnostic> { + let exp = self.create_expr_generator(); + let ref_builtin = self.index.get_builtin_function("REF").expect("REF must exist"); + + let AstStatement::ReferenceExpr(data) = &left.stmt else { + unreachable!("should be covered by a validation") + }; + + let left_ptr_val = { + let expr = exp.generate_reference_expression(&data.access, data.base.as_deref(), left)?; + expr.get_basic_value_enum().into_pointer_value() + }; + let right_expr_val = ref_builtin.codegen(&exp, &[&right], right.get_location())?; + + self.llvm.builder.build_store(left_ptr_val, right_expr_val.get_basic_value_enum()); + Ok(()) + } + /// generates an assignment statement _left_ := _right_ /// /// `left_statement` the left side of the assignment diff --git a/src/codegen/tests/statement_codegen_test.rs b/src/codegen/tests/statement_codegen_test.rs index b4dd2e88ca..9f8a4db6d0 100644 --- a/src/codegen/tests/statement_codegen_test.rs +++ b/src/codegen/tests/statement_codegen_test.rs @@ -184,3 +184,127 @@ fn floating_point_type_casting() { insta::assert_snapshot!(result); } + +#[test] +fn ref_assignment() { + let result = codegen( + r#" + FUNCTION main + VAR + a : REF_TO DINT; + b : DINT; + END_VAR + a REF= b; + END_PROGRAM + "#, + ); + + insta::assert_snapshot!(result, @r###" + ; ModuleID = 'main' + source_filename = "main" + + define void @main() section "fn-$RUSTY$main:v" { + entry: + %a = alloca i32*, align 8 + %b = alloca i32, align 4 + store i32* null, i32** %a, align 8 + store i32 0, i32* %b, align 4 + store i32* %b, i32** %a, align 8 + ret void + } + "###); +} + +#[test] +fn reference_to_assignment() { + let auto_deref = codegen( + r#" + FUNCTION main + VAR + a : REFERENCE TO DINT; + END_VAR + a := 5; + END_FUNCTION + "#, + ); + + let manual_deref = codegen( + r#" + FUNCTION main + VAR + a : REF_TO DINT; + END_VAR + a^ := 5; + END_FUNCTION + "#, + ); + + // We want to assert that `a := 5` and `a^ := 5` yield identical IR + assert_eq!(auto_deref, manual_deref); + + insta::assert_snapshot!(auto_deref, @r###" + ; ModuleID = 'main' + source_filename = "main" + + define void @main() section "fn-$RUSTY$main:v" { + entry: + %a = alloca i32*, align 8 + store i32* null, i32** %a, align 8 + %deref = load i32*, i32** %a, align 8 + store i32 5, i32* %deref, align 4 + ret void + } + "###); +} + +#[test] +fn reference_to_string_assignment() { + let auto_deref = codegen( + r#" + FUNCTION main + VAR + a : REFERENCE TO STRING; + END_VAR + + a := 'hello'; + END_FUNCTION + "#, + ); + + let manual_deref = codegen( + r#" + FUNCTION main + VAR + a : REF_TO STRING; + END_VAR + + a^ := 'hello'; + END_FUNCTION + "#, + ); + + // We want to assert that `a := 'hello'` and `a^ := 'hello'` yield identical IR + assert_eq!(auto_deref, manual_deref); + + insta::assert_snapshot!(auto_deref, @r###" + ; ModuleID = 'main' + source_filename = "main" + + @utf08_literal_0 = private unnamed_addr constant [6 x i8] c"hello\00" + + define void @main() section "fn-$RUSTY$main:v" { + entry: + %a = alloca [81 x i8]*, align 8 + store [81 x i8]* null, [81 x i8]** %a, align 8 + %deref = load [81 x i8]*, [81 x i8]** %a, align 8 + %0 = bitcast [81 x i8]* %deref to i8* + call void @llvm.memcpy.p0i8.p0i8.i32(i8* align 1 %0, i8* align 1 getelementptr inbounds ([6 x i8], [6 x i8]* @utf08_literal_0, i32 0, i32 0), i32 6, i1 false) + ret void + } + + ; Function Attrs: argmemonly nofree nounwind willreturn + declare void @llvm.memcpy.p0i8.p0i8.i32(i8* noalias nocapture writeonly, i8* noalias nocapture readonly, i32, i1 immarg) #0 + + attributes #0 = { argmemonly nofree nounwind willreturn } + "###); +} diff --git a/src/index.rs b/src/index.rs index cb0fdd9837..474217b662 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1130,6 +1130,7 @@ impl Index { if segments.is_empty() { return None; } + //For the first element, if the context does not contain that element, it is possible that the element is also a global variable let init = match context { Some(context) => self diff --git a/src/index/tests/index_tests.rs b/src/index/tests/index_tests.rs index b9dc72dc62..2e8eb9dacb 100644 --- a/src/index/tests/index_tests.rs +++ b/src/index/tests/index_tests.rs @@ -1253,6 +1253,7 @@ fn pointer_and_in_out_pointer_should_not_conflict() { name: "__main_x".to_string(), inner_type_name: "INT".to_string(), auto_deref: false, + is_reference_to: false, } ); @@ -1264,6 +1265,7 @@ fn pointer_and_in_out_pointer_should_not_conflict() { name: "__auto_pointer_to_INT".to_string(), inner_type_name: "INT".to_string(), auto_deref: true, + is_reference_to: false, } ); } @@ -1303,6 +1305,7 @@ fn pointer_and_in_out_pointer_should_not_conflict_2() { name: "__main_x".to_string(), inner_type_name: "INT".to_string(), auto_deref: false, + is_reference_to: false, } ); @@ -1314,6 +1317,7 @@ fn pointer_and_in_out_pointer_should_not_conflict_2() { name: "__auto_pointer_to_INT".to_string(), inner_type_name: "INT".to_string(), auto_deref: true, + is_reference_to: false, } ); } 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 16bd9f9481..e3895d6f10 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,6 +10,8 @@ UserTypeDeclaration { referenced_type: DataTypeReference { referenced_type: "__foo_inline_pointer_", }, + auto_deref: false, + is_reference_to: false, }, 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 493df6f2fd..8acc34ac46 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,6 +10,8 @@ UserTypeDeclaration { referenced_type: DataTypeReference { referenced_type: "INT", }, + auto_deref: false, + is_reference_to: false, }, 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 e603f07e5d..be433f8b03 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,6 +10,8 @@ UserTypeDeclaration { referenced_type: DataTypeReference { referenced_type: "INT", }, + auto_deref: false, + is_reference_to: false, }, 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 df5d0ae77a..6bd5e157f6 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,6 +10,8 @@ UserTypeDeclaration { referenced_type: DataTypeReference { referenced_type: "__pointer_to_pointer", }, + auto_deref: false, + is_reference_to: false, }, 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 f5332a5730..f8a2939b47 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,6 +10,8 @@ UserTypeDeclaration { referenced_type: DataTypeReference { referenced_type: "INT", }, + auto_deref: false, + is_reference_to: false, }, initializer: None, scope: None, diff --git a/src/index/visitor.rs b/src/index/visitor.rs index cff5e49e47..f0ecd18a1e 100644 --- a/src/index/visitor.rs +++ b/src/index/visitor.rs @@ -273,6 +273,7 @@ fn register_byref_pointer_type_for(index: &mut Index, inner_type_name: &str) -> name: type_name.clone(), inner_type_name: inner_type_name.to_string(), auto_deref: true, + is_reference_to: false, }, nature: TypeNature::Any, location: SourceLocation::internal(), @@ -400,12 +401,13 @@ 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, .. } => { + DataType::PointerType { name: Some(name), referenced_type, auto_deref, is_reference_to } => { 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: false, + auto_deref: *auto_deref, + is_reference_to: *is_reference_to, }; let init = index.get_mut_const_expressions().maybe_add_constant_expression( @@ -572,6 +574,8 @@ fn visit_variable_length_array( referenced_type: dummy_array_name, location: SourceLocation::undefined(), }), + auto_deref: false, + is_reference_to: false, }, location: SourceLocation::undefined(), scope: None, diff --git a/src/lexer/tokens.rs b/src/lexer/tokens.rs index 02baa2a7e1..d7c72a5394 100644 --- a/src/lexer/tokens.rs +++ b/src/lexer/tokens.rs @@ -159,6 +159,9 @@ pub enum Token { #[token("=>")] KeywordOutputAssignment, + #[token("REF=", ignore(case))] + KeywordReferenceAssignment, + #[token("(")] KeywordParensOpen, @@ -252,6 +255,9 @@ pub enum Token { #[token("REFTO", ignore(case))] KeywordRef, + #[token("REFERENCE TO", ignore(case))] + KeywordReferenceTo, + #[token("ARRAY", ignore(case))] KeywordArray, diff --git a/src/parser.rs b/src/parser.rs index 385322fd28..f113e44549 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -676,9 +676,9 @@ fn parse_data_type_definition( if expect_keyword_to(lexer).is_some() { lexer.advance(); } - parse_pointer_definition(lexer, name, start_pos) + parse_pointer_definition(lexer, name, start_pos, false) } else if lexer.try_consume(&KeywordRef) { - parse_pointer_definition(lexer, name, lexer.last_range.start) + parse_pointer_definition(lexer, name, lexer.last_range.start, false) } else if lexer.try_consume(&KeywordParensOpen) { //enum without datatype parse_enum_type_definition(lexer, name) @@ -701,11 +701,17 @@ fn parse_pointer_definition( lexer: &mut ParseSession, name: Option, start_pos: usize, + is_reference_to: bool, ) -> Option<(DataTypeDeclaration, Option)> { parse_data_type_definition(lexer, None).map(|(decl, initializer)| { ( DataTypeDeclaration::DataTypeDefinition { - data_type: DataType::PointerType { name, referenced_type: Box::new(decl) }, + data_type: DataType::PointerType { + name, + referenced_type: Box::new(decl), + auto_deref: is_reference_to, + is_reference_to, + }, location: lexer.source_range_factory.create_range(start_pos..lexer.last_range.end), scope: lexer.scope.clone(), }, @@ -1108,7 +1114,16 @@ fn parse_variable_line(lexer: &mut ParseSession) -> Vec { // create variables with the same data type for each of the names let mut variables = vec![]; - if let Some((data_type, initializer)) = parse_full_data_type_definition(lexer, None) { + + let parse_definition_opt = if lexer.try_consume(&KeywordReferenceTo) { + parse_pointer_definition(lexer, None, lexer.last_range.start, true) + } else { + parse_full_data_type_definition(lexer, None) + }; + + lexer.try_consume(&KeywordSemicolon); + + if let Some((data_type, initializer)) = parse_definition_opt { for (name, range) in var_names { variables.push(Variable { name, @@ -1119,6 +1134,7 @@ fn parse_variable_line(lexer: &mut ParseSession) -> Vec { }); } } + variables } diff --git a/src/parser/expressions_parser.rs b/src/parser/expressions_parser.rs index 798de92813..c9760451ff 100644 --- a/src/parser/expressions_parser.rs +++ b/src/parser/expressions_parser.rs @@ -213,17 +213,21 @@ fn parse_leaf_expression(lexer: &mut ParseSession) -> AstNode { }; match literal_parse_result { - Some(statement) => { - if lexer.token == KeywordAssignment { + Some(statement) => match lexer.token { + KeywordAssignment => { lexer.advance(); AstFactory::create_assignment(statement, parse_range_statement(lexer), lexer.next_id()) - } else if lexer.token == KeywordOutputAssignment { + } + KeywordOutputAssignment => { lexer.advance(); AstFactory::create_output_assignment(statement, parse_range_statement(lexer), lexer.next_id()) - } else { - statement } - } + KeywordReferenceAssignment => { + lexer.advance(); + AstFactory::create_ref_assignment(statement, parse_range_statement(lexer), lexer.next_id()) + } + _ => statement, + }, None => { let statement = AstFactory::create_empty_statement( lexer.diagnostics.last().map_or(SourceLocation::undefined(), |d| d.get_location()), 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 ca29b7abba..635dd03792 100644 --- a/src/parser/tests/parse_errors/parse_error_statements_tests.rs +++ b/src/parser/tests/parse_errors/parse_error_statements_tests.rs @@ -1128,6 +1128,8 @@ fn pointer_type_without_to_test() { referenced_type: "INT".to_string(), location: SourceLocation::undefined(), }), + auto_deref: false, + is_reference_to: false, }, location: SourceLocation::undefined(), initializer: None, @@ -1154,6 +1156,8 @@ fn pointer_type_with_wrong_keyword_to_test() { referenced_type: "tu".to_string(), location: SourceLocation::undefined(), }), + auto_deref: false, + is_reference_to: false, }, 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 f430f66293..b7f3076003 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,6 +10,8 @@ Variable { referenced_type: DataTypeReference { referenced_type: "INT", }, + auto_deref: false, + is_reference_to: false, }, }, } 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 f7426bc0ee..fcdb7bc49c 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,6 +10,8 @@ Variable { referenced_type: DataTypeReference { referenced_type: "INT", }, + auto_deref: false, + is_reference_to: false, }, }, } 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 1bd1d207de..576dcbd116 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,6 +10,8 @@ UserTypeDeclaration { referenced_type: DataTypeReference { referenced_type: "INT", }, + auto_deref: false, + is_reference_to: false, }, 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 9402582b6e..332f301817 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,6 +10,8 @@ UserTypeDeclaration { referenced_type: DataTypeReference { referenced_type: "INT", }, + auto_deref: false, + is_reference_to: false, }, initializer: None, scope: None, diff --git a/src/parser/tests/statement_parser_tests.rs b/src/parser/tests/statement_parser_tests.rs index ffddd7578f..c1b1c51d6e 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, + test_utils::tests::{parse, parse_and_validate_buffered}, typesystem::DINT_TYPE, }; use insta::assert_snapshot; @@ -262,3 +262,79 @@ fn empty_parameter_assignments_in_call_statement() { let ast_string = format!("{:#?}", &result); insta::assert_snapshot!(ast_string); } + +#[test] +fn ref_assignment() { + let result = &parse("PROGRAM main x REF= y END_PROGRAM").0.implementations[0]; + insta::assert_debug_snapshot!(result.statements, @r###" + [ + ReferenceAssignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + }, + ] + "###) +} + +#[test] +fn reference_to_declaration() { + let (result, diagnostics) = parse( + r" + FUNCTION foo + VAR + bar : DINT; + baz : REFERENCE TO DINT; + qux : DINT; + END_VAR + END_FUNCTION + ", + ); + + assert!(diagnostics.is_empty()); + insta::assert_debug_snapshot!(result.units[0].variable_blocks[0], @r###" + VariableBlock { + variables: [ + Variable { + name: "bar", + data_type: DataTypeReference { + referenced_type: "DINT", + }, + }, + Variable { + name: "baz", + data_type: DataTypeDefinition { + data_type: PointerType { + name: None, + referenced_type: DataTypeReference { + referenced_type: "DINT", + }, + auto_deref: true, + is_reference_to: true, + }, + }, + }, + Variable { + name: "qux", + data_type: DataTypeReference { + referenced_type: "DINT", + }, + }, + ], + variable_block_type: Local, + } + "###); +} diff --git a/src/resolver.rs b/src/resolver.rs index 58186c0a8c..2038bc223f 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -393,6 +393,8 @@ pub enum StatementAnnotation { 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, }, /// a reference to a function Function { @@ -433,6 +435,14 @@ impl StatementAnnotation { } } + pub fn is_auto_deref(&self) -> bool { + matches!(self, StatementAnnotation::Variable { is_auto_deref: true, .. }) + } + + pub fn is_reference_to(&self) -> bool { + matches!(self, StatementAnnotation::Variable { is_reference_to: true, .. }) + } + pub fn data_type(type_name: &str) -> Self { StatementAnnotation::Type { type_name: type_name.into() } } @@ -1419,7 +1429,7 @@ impl<'i> TypeAnnotator<'i> { AstStatement::RangeStatement(data, ..) => { visit_all_statements!(self, ctx, &data.start, &data.end); } - AstStatement::Assignment(data, ..) => { + AstStatement::Assignment(data, ..) | AstStatement::RefAssignment(data, ..) => { self.visit_statement(&ctx.enter_control(), &data.right); if let Some(lhs) = ctx.lhs { //special context for left hand side @@ -1628,7 +1638,7 @@ impl<'i> TypeAnnotator<'i> { else { unreachable!("expected a vla reference, but got {statement:#?}"); }; - if let DataTypeInformation::Pointer { inner_type_name, .. } = &self + if let DataTypeInformation::Pointer { inner_type_name, is_reference_to, .. } = &self .index .get_effective_type_or_void_by_name( members.first().expect("internal VLA struct ALWAYS has this member").get_type_name(), @@ -1664,6 +1674,7 @@ impl<'i> TypeAnnotator<'i> { constant: false, argument_type, is_auto_deref: false, + is_reference_to: *is_reference_to, }; self.annotation_map.annotate_type_hint(statement, hint_annotation) } @@ -1749,7 +1760,12 @@ impl<'i> TypeAnnotator<'i> { } pub(crate) fn annotate_parameters(&mut self, p: &AstNode, type_name: &str) { - if !matches!(p.get_stmt(), AstStatement::Assignment(..) | AstStatement::OutputAssignment(..)) { + if !matches!( + p.get_stmt(), + AstStatement::Assignment(..) + | AstStatement::OutputAssignment(..) + | AstStatement::RefAssignment(..) + ) { if let Some(effective_member_type) = self.index.find_effective_type_by_name(type_name) { //update the type hint self.annotation_map @@ -1869,10 +1885,11 @@ pub(crate) fn add_pointer_type(index: &mut Index, inner_type_name: String) -> St name: new_type_name.clone(), initial_value: None, nature: TypeNature::Any, - information: crate::typesystem::DataTypeInformation::Pointer { + information: DataTypeInformation::Pointer { auto_deref: false, inner_type_name, name: new_type_name.clone(), + is_reference_to: false, }, location: SourceLocation::internal(), }); @@ -1932,6 +1949,7 @@ fn to_variable_annotation( 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(), } } diff --git a/src/resolver/generics.rs b/src/resolver/generics.rs index 379460b346..6d7861b7e4 100644 --- a/src/resolver/generics.rs +++ b/src/resolver/generics.rs @@ -201,14 +201,18 @@ 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: true, .. }) => { // 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 let inner_type_name = self.find_or_create_datatype(inner_type_name, generics); let name = format!("{name}__{inner_type_name}"); // TODO: Naming convention (see plc_util/src/convention.rs) - let new_type_info = - DataTypeInformation::Pointer { name: name.clone(), inner_type_name, auto_deref: true }; + let new_type_info = DataTypeInformation::Pointer { + name: name.clone(), + inner_type_name, + auto_deref: true, + is_reference_to: false, + }; // Registers a new pointer type to the index self.annotation_map.new_index.register_type(DataType { diff --git a/src/resolver/tests/resolve_expressions_tests.rs b/src/resolver/tests/resolve_expressions_tests.rs index 44d1537f19..44872aec2b 100644 --- a/src/resolver/tests/resolve_expressions_tests.rs +++ b/src/resolver/tests/resolve_expressions_tests.rs @@ -162,7 +162,8 @@ 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_auto_deref: false, + is_reference_to: false, }) ); @@ -178,7 +179,8 @@ 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_auto_deref: false, + is_reference_to: false, }) ); } @@ -1313,6 +1315,7 @@ fn function_expression_resolves_to_the_function_itself_not_its_return_type() { constant: false, is_auto_deref: false, argument_type: ArgumentType::ByVal(VariableType::Return), + is_reference_to: false, }), foo_annotation ); @@ -1574,6 +1577,7 @@ fn qualified_expressions_dont_fallback_to_globals() { constant: false, is_auto_deref: false, argument_type: ArgumentType::ByVal(VariableType::Input), + is_reference_to: false, }), annotations.get(&statements[1]) ); @@ -1815,6 +1819,7 @@ fn method_references_are_resolved() { constant: false, is_auto_deref: false, argument_type: ArgumentType::ByVal(VariableType::Return), + is_reference_to: false, }), annotation ); @@ -2448,7 +2453,8 @@ 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_auto_deref: false, + is_reference_to: false, }), annotations.get(left) ); @@ -2462,7 +2468,8 @@ 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_auto_deref: false, + is_reference_to: false, }), annotations.get(left) ); @@ -2944,6 +2951,7 @@ fn action_body_gets_resolved() { constant: false, is_auto_deref: false, argument_type: ArgumentType::ByVal(VariableType::Local), + is_reference_to: false, }), a ); @@ -3385,6 +3393,7 @@ 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(), @@ -3468,7 +3477,8 @@ 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_auto_deref: false, + is_reference_to: false, }), annotations.get(b.as_ref()) ); @@ -3479,7 +3489,8 @@ 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_auto_deref: false, + is_reference_to: false, }), annotations.get(a) ); @@ -3701,7 +3712,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, + is_reference_to: false, } ) } else { @@ -3836,7 +3848,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, + is_reference_to: false, } ) } @@ -5081,6 +5094,7 @@ fn annotate_variable_in_parent_class() { constant: false, argument_type: ArgumentType::ByVal(VariableType::Local,), is_auto_deref: false, + is_reference_to: false, }, annotation.unwrap() ); @@ -5096,6 +5110,7 @@ fn annotate_variable_in_parent_class() { constant: false, argument_type: ArgumentType::ByVal(VariableType::Local,), is_auto_deref: false, + is_reference_to: false, }, annotation.unwrap() ); @@ -5134,6 +5149,7 @@ fn annotate_variable_in_grandparent_class() { constant: false, argument_type: ArgumentType::ByVal(VariableType::Local,), is_auto_deref: false, + is_reference_to: false, }, annotation.unwrap() ); @@ -5179,6 +5195,7 @@ fn annotate_variable_in_field() { constant: false, argument_type: ArgumentType::ByVal(VariableType::Local,), is_auto_deref: false, + is_reference_to: false, }, annotation.unwrap() ); @@ -5236,6 +5253,7 @@ fn annotate_method_in_super() { constant: false, argument_type: ArgumentType::ByVal(VariableType::Local,), is_auto_deref: false, + is_reference_to: false, }, annotation.unwrap() ); @@ -5251,6 +5269,7 @@ fn annotate_method_in_super() { constant: false, argument_type: ArgumentType::ByVal(VariableType::Local,), is_auto_deref: false, + is_reference_to: false, }, annotation.unwrap() ); @@ -5266,6 +5285,7 @@ fn annotate_method_in_super() { constant: false, argument_type: ArgumentType::ByVal(VariableType::Local,), is_auto_deref: false, + is_reference_to: false, }, annotation.unwrap() ); @@ -5281,6 +5301,7 @@ fn annotate_method_in_super() { constant: false, argument_type: ArgumentType::ByVal(VariableType::Local,), is_auto_deref: false, + is_reference_to: false, }, annotation.unwrap() ); @@ -5296,6 +5317,7 @@ fn annotate_method_in_super() { constant: false, argument_type: ArgumentType::ByVal(VariableType::Local,), is_auto_deref: false, + is_reference_to: false, }, annotation.unwrap() ); diff --git a/src/resolver/tests/resolve_literals_tests.rs b/src/resolver/tests/resolve_literals_tests.rs index 559da46845..42d39cd54d 100644 --- a/src/resolver/tests/resolve_literals_tests.rs +++ b/src/resolver/tests/resolve_literals_tests.rs @@ -312,7 +312,8 @@ 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_auto_deref: false, + is_reference_to: false, }), annotations.get(target) ); diff --git a/src/tests/adr/annotated_ast_adr.rs b/src/tests/adr/annotated_ast_adr.rs index bdfc003e7c..6845bd2163 100644 --- a/src/tests/adr/annotated_ast_adr.rs +++ b/src/tests/adr/annotated_ast_adr.rs @@ -49,7 +49,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, + is_reference_to: false, } ); @@ -61,7 +62,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, + is_reference_to: false, } ); } @@ -105,6 +107,7 @@ fn different_types_of_annotations() { 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 }) ); @@ -154,6 +157,7 @@ fn different_types_of_annotations() { resulting_type: "INT".into(), constant: false, is_auto_deref: false, + is_reference_to: false, argument_type: ArgumentType::ByVal(VariableType::Input), }) ); @@ -166,6 +170,7 @@ fn different_types_of_annotations() { resulting_type: "INT".into(), constant: false, is_auto_deref: false, + is_reference_to: false, argument_type: ArgumentType::ByVal(VariableType::Input), }) ); diff --git a/src/tests/adr/vla_adr.rs b/src/tests/adr/vla_adr.rs index e4ba8d7512..29c9816894 100644 --- a/src/tests/adr/vla_adr.rs +++ b/src/tests/adr/vla_adr.rs @@ -104,6 +104,7 @@ fn representation() { name: "__ptr_to___arr_vla_1_dint", inner_type_name: "__arr_vla_1_dint", auto_deref: false, + is_reference_to: false, }, nature: Any, location: SourceLocation { diff --git a/src/typesystem.rs b/src/typesystem.rs index eaead48113..3e19f5665d 100644 --- a/src/typesystem.rs +++ b/src/typesystem.rs @@ -96,7 +96,7 @@ mod tests; #[derive(Debug, Clone)] pub struct DataType { pub name: String, - /// the initial value defined on the TYPE-declration + /// the initial value defined on the TYPE-declaration pub initial_value: Option, pub information: DataTypeInformation, pub nature: TypeNature, @@ -391,6 +391,8 @@ pub enum DataTypeInformation { 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, }, Integer { name: TypeId, @@ -559,6 +561,11 @@ 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, .. }) + } + pub fn is_aggregate(&self) -> bool { matches!( self, diff --git a/src/validation/statement.rs b/src/validation/statement.rs index eb91f49d83..2115972180 100644 --- a/src/validation/statement.rs +++ b/src/validation/statement.rs @@ -1,6 +1,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use std::mem::discriminant; +use plc_ast::ast::Assignment; use plc_ast::control_statements::ForLoopStatement; use plc_ast::{ ast::{ @@ -13,7 +14,9 @@ use plc_ast::{ use plc_diagnostics::diagnostics::Diagnostic; use plc_source::source_location::SourceLocation; +use super::{array::validate_array_assignment, ValidationContext, Validator, Validators}; use crate::index::ImplementationType; +use crate::typesystem::VOID_TYPE; use crate::validation::statement::helper::{get_datatype_name_or_slice, get_literal_int_or_const_expr_value}; use crate::{ builtins::{self, BuiltIn}, @@ -26,8 +29,6 @@ use crate::{ }, }; -use super::{array::validate_array_assignment, ValidationContext, Validator, Validators}; - macro_rules! visit_all_statements { ($validator:expr, $context:expr, $last:expr ) => { visit_statement($validator, $last, $context); @@ -85,14 +86,20 @@ pub fn visit_statement( visit_statement(validator, &data.left, context); visit_statement(validator, &data.right, context); - validate_assignment(validator, &data.right, Some(&data.left), &statement.get_location(), context); + validate_assignment(validator, &data.right, Some(&data.left), &statement.location, context); validate_array_assignment(validator, context, statement); } AstStatement::OutputAssignment(data) => { visit_statement(validator, &data.left, context); visit_statement(validator, &data.right, context); - validate_assignment(validator, &data.right, Some(&data.left), &statement.get_location(), context); + validate_assignment(validator, &data.right, Some(&data.left), &statement.location, context); + } + AstStatement::RefAssignment(data) => { + visit_statement(validator, &data.left, context); + visit_statement(validator, &data.right, context); + + validate_ref_assignment(context, validator, data, &statement.location); } AstStatement::CallStatement(data) => { validate_call(validator, &data.operator, data.parameters.as_deref(), &context.set_is_call()); @@ -762,6 +769,81 @@ fn validate_call_by_ref(validator: &mut Validator, param: &VariableIndexEntry, a } } +/// 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( + context: &ValidationContext, + validator: &mut Validator, + assignment: &Assignment, + assignment_location: &SourceLocation, +) { + 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() { + validator.push_diagnostic( + Diagnostic::new("Invalid assignment, expected a reference") + .with_location(&assignment.right.location) + .with_error_code("E098"), + ); + } + + // 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() { + validator.push_diagnostic( + Diagnostic::new("Invalid assignment, expected a pointer reference") + .with_location(&assignment.left.location) + .with_error_code("E098"), + ) + } + + 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" + )); + } + + for message in messages { + validator.push_diagnostic( + Diagnostic::new(message).with_location(assignment_location).with_error_code("E098"), + ) + } + + 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"), + ); + } +} + fn validate_assignment( validator: &mut Validator, right: &AstNode, diff --git a/src/validation/tests/assignment_validation_tests.rs b/src/validation/tests/assignment_validation_tests.rs index 40da9e7fc4..219dc6872d 100644 --- a/src/validation/tests/assignment_validation_tests.rs +++ b/src/validation/tests/assignment_validation_tests.rs @@ -1217,3 +1217,277 @@ fn void_assignment_validation() { "###) } + +#[test] +fn ref_assignments() { + let diagnostics = parse_and_validate_buffered( + " + FUNCTION main + VAR + localINT : INT; + localDINT : DINT; + localSTRING : STRING; + + localRefTo : REF_TO DINT; + localReferenceTo : REFERENCE TO DINT; + END_VAR + + localRefTo REF= localDINT; + localReferenceTo REF= localDINT; + + // The following are invalid + 1 REF= localDINT; + localINT REF= localDINT; + localRefTo REF= 1; + localReferenceTo REF= 1; + + localReferenceTo REF= localINT; + localReferenceTo REF= localSTRING; + localReferenceTo REF= 'howdy'; + END_FUNCTION + ", + ); + + assert_snapshot!(diagnostics, @r###" + error[E098]: Invalid assignment, expected a pointer reference + ┌─ :16:13 + │ + 16 │ 1 REF= localDINT; + │ ^ Invalid assignment, expected a pointer reference + + error[E098]: Invalid assignment, expected a pointer reference + ┌─ :17:13 + │ + 17 │ localINT REF= localDINT; + │ ^^^^^^^^ Invalid assignment, expected a pointer reference + + error[E098]: Invalid assignment, types INT and DINT differ + ┌─ :17:13 + │ + 17 │ localINT REF= localDINT; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment, types INT and DINT differ + + error[E098]: Invalid assignment, expected a reference + ┌─ :18:38 + │ + 18 │ localRefTo REF= 1; + │ ^ Invalid assignment, expected a reference + + error[E098]: Invalid assignment, expected a reference + ┌─ :19:38 + │ + 19 │ localReferenceTo REF= 1; + │ ^ Invalid assignment, expected a reference + + error[E098]: Invalid assignment, types DINT and INT differ + ┌─ :21:13 + │ + 21 │ localReferenceTo REF= localINT; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment, types DINT and INT differ + + error[E098]: Invalid assignment, types DINT and STRING differ + ┌─ :22:13 + │ + 22 │ localReferenceTo REF= localSTRING; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment, types DINT and STRING differ + + error[E098]: Invalid assignment, expected a reference + ┌─ :23:38 + │ + 23 │ localReferenceTo REF= 'howdy'; + │ ^^^^^^^ Invalid assignment, expected a reference + + error[E098]: Invalid assignment, types DINT and STRING differ + ┌─ :23:13 + │ + 23 │ localReferenceTo REF= 'howdy'; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment, types DINT and STRING differ + + "###); +} + +#[test] +fn ref_assignment_with_global_local_variables_and_aliased_types() { + let diagnostics = parse_and_validate_buffered( + " + VAR_GLOBAL + fooGlobal : DINT; + END_VAR + + FUNCTION main + VAR + fooLocal : DINT; + referenceToFooFirstOfHisName : REFERENCE TO DINT; + referenceToFooSecondOfHisName : REFERENCE TO DINT; + referenceToAlias : REFERENCE TO AliasedDINT; + + intLocal : INT; + stringLocal : STRING; + + // Invalid, types should be referenced rather than literals or variables + invalidA : REFERENCE TO fooLocal; + invalidB : REFERENCE TO fooGlobal; + invalidC : REFERENCE TO DINT := 5; + END_VAR + + referenceToFooFirstOfHisName REF= fooLocal; + referenceToFooFirstOfHisName REF= fooGlobal; + referenceToFooFirstOfHisName REF= referenceToFooFirstOfHisName; // Valid, albeit questionable + referenceToFooFirstOfHisName REF= referenceToFooSecondOfHisName; + + // Invalid, type mismatch + referenceToFooFirstOfHisName REF= intLocal; + referenceToFooFirstOfHisName REF= stringLocal; + END_FUNCTION + ", + ); + + assert_snapshot!(diagnostics, @r###" + error[E099]: REFERENCE TO variables can not reference other variables + ┌─ :17:28 + │ + 17 │ invalidA : REFERENCE TO fooLocal; + │ ^^^^^^^^^^^^^^^^^^^^^ REFERENCE TO variables can not reference other variables + + error[E099]: REFERENCE TO variables can not reference other variables + ┌─ :18:28 + │ + 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 + ┌─ :28:13 + │ + 28 │ referenceToFooFirstOfHisName REF= intLocal; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment, types DINT and INT differ + + error[E098]: Invalid assignment, types DINT and STRING differ + ┌─ :29:13 + │ + 29 │ referenceToFooFirstOfHisName REF= stringLocal; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment, types DINT and STRING differ + + "###); +} + +#[test] +fn ref_assignment_with_reference_to_array_variable() { + let diagnostics = parse_and_validate_buffered( + " + FUNCTION main + VAR + arrSTRING : ARRAY[1..6] OF STRING; + arrDINT : ARRAY[1..5] OF DINT; + arrReferenceDINT : REFERENCE TO ARRAY[1..5] OF DINT; + END_VAR + + arrReferenceDINT REF= arrDINT; + arrReferenceDINT REF= arrSTRING; + END_FUNCTION + ", + ); + + 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 + ┌─ :10:13 + │ + 10 │ arrReferenceDINT REF= arrSTRING; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment, array types DINT and STRING differ + + "###); +} + +#[test] +fn ref_assignment_with_reference_to_string_variable() { + let diagnostics = parse_and_validate_buffered( + " + FUNCTION main + VAR + localCHAR : CHAR; + localSTRING : STRING; + localWSTRING : WSTRING; + referenceToString : REFERENCE TO STRING; + END_VAR + + referenceToString REF= localCHAR; + referenceToString REF= localSTRING; + referenceToString REF= localWSTRING; + END_FUNCTION + ", + ); + + assert_snapshot!(diagnostics, @r###" + error[E098]: Invalid assignment, types STRING and CHAR differ + ┌─ :10:13 + │ + 10 │ referenceToString REF= localCHAR; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment, types STRING and CHAR differ + + error[E098]: Invalid assignment, types STRING and WSTRING differ + ┌─ :12:13 + │ + 12 │ referenceToString REF= localWSTRING; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment, types STRING and WSTRING differ + + "###); +} + +// TODO(volsa): Improve the error messages here; these are the default messages returned by the parser +// without any modifications. +#[test] +fn invalid_reference_to_declaration() { + let diagnostics = parse_and_validate_buffered( + r" + FUNCTION foo + VAR + bar : ARRAY[1..5] OF REFERENCE TO DINT; + baz : REFERENCE TO REFERENCE TO DINT; + qux : REF_TO REFERENCE TO DINT; + END_VAR + END_FUNCTION + ", + ); + + insta::assert_snapshot!(diagnostics, @r###" + error[E007]: Unexpected token: expected DataTypeDefinition but found KeywordReferenceTo + ┌─ :4:38 + │ + 4 │ bar : ARRAY[1..5] OF REFERENCE TO DINT; + │ ^^^^^^^^^^^^ Unexpected token: expected DataTypeDefinition but found KeywordReferenceTo + + error[E007]: Unexpected token: expected KeywordSemicolon but found 'REFERENCE TO DINT' + ┌─ :4:38 + │ + 4 │ bar : ARRAY[1..5] OF REFERENCE TO DINT; + │ ^^^^^^^^^^^^^^^^^ Unexpected token: expected KeywordSemicolon but found 'REFERENCE TO DINT' + + error[E007]: Unexpected token: expected DataTypeDefinition but found KeywordReferenceTo + ┌─ :5:36 + │ + 5 │ baz : REFERENCE TO REFERENCE TO DINT; + │ ^^^^^^^^^^^^ Unexpected token: expected DataTypeDefinition but found KeywordReferenceTo + + error[E007]: Unexpected token: expected KeywordEndVar but found 'REFERENCE TO DINT; + qux : REF_TO REFERENCE TO DINT;' + ┌─ :5:36 + │ + 5 │ baz : REFERENCE TO REFERENCE TO DINT; + │ ╭────────────────────────────────────^ + 6 │ │ qux : REF_TO REFERENCE TO DINT; + │ ╰───────────────────────────────────────────────^ Unexpected token: expected KeywordEndVar but found 'REFERENCE TO DINT; + qux : REF_TO REFERENCE TO DINT;' + + "###); +} diff --git a/src/validation/tests/snapshots/rusty__validation__tests__variable_validation_tests__constant_fb_instances_are_illegal.snap b/src/validation/tests/snapshots/rusty__validation__tests__variable_validation_tests__constant_fb_instances_are_illegal.snap index 52d6277b77..a6062071b6 100644 --- a/src/validation/tests/snapshots/rusty__validation__tests__variable_validation_tests__constant_fb_instances_are_illegal.snap +++ b/src/validation/tests/snapshots/rusty__validation__tests__variable_validation_tests__constant_fb_instances_are_illegal.snap @@ -2,16 +2,14 @@ source: src/validation/tests/variable_validation_tests.rs expression: "&diagnostics" --- -error[E035]: Invalid constant y - Functionblock- and Class-instances cannot be delcared constant +error[E035]: Invalid constant y, FUNCTION_BLOCK- and CLASS-instances cannot be declared constant ┌─ :15:13 │ 15 │ y : MyFb; - │ ^ Invalid constant y - Functionblock- and Class-instances cannot be delcared constant + │ ^ Invalid constant y, FUNCTION_BLOCK- and CLASS-instances cannot be declared constant -error[E035]: Invalid constant z - Functionblock- and Class-instances cannot be delcared constant +error[E035]: Invalid constant z, FUNCTION_BLOCK- and CLASS-instances cannot be declared constant ┌─ :16:13 │ 16 │ z : cls; - │ ^ Invalid constant z - Functionblock- and Class-instances cannot be delcared constant - - + │ ^ Invalid constant z, FUNCTION_BLOCK- and CLASS-instances cannot be declared constant diff --git a/src/validation/variable.rs b/src/validation/variable.rs index f841b0001f..ed07481734 100644 --- a/src/validation/variable.rs +++ b/src/validation/variable.rs @@ -1,15 +1,15 @@ use plc_ast::ast::{ArgumentProperty, Pou, PouType, Variable, VariableBlock, VariableBlockType}; use plc_diagnostics::diagnostics::Diagnostic; -use crate::typesystem::DataTypeInformation; -use crate::{index::const_expressions::ConstExpression, resolver::AnnotationMap}; - use super::{ array::validate_array_assignment, statement::{validate_enum_variant_assignment, visit_statement}, types::{data_type_is_fb_or_class_instance, visit_data_type_declaration}, ValidationContext, Validator, Validators, }; +use crate::index::VariableIndexEntry; +use crate::typesystem::DataTypeInformation; +use crate::{index::const_expressions::ConstExpression, resolver::AnnotationMap}; pub fn visit_variable_block( validator: &mut Validator, @@ -143,6 +143,8 @@ fn validate_variable( validate_array_ranges(validator, variable, context); if let Some(v_entry) = context.index.find_variable(context.qualifier, &[&variable.name]) { + validate_reference_to_declaration(validator, context, variable, v_entry); + if let Some(initializer) = &variable.initializer { // Assume `foo : ARRAY[1..5] OF DINT := [...]`, here the first function call validates the // assignment as a whole whereas the second function call (`visit_statement`) validates the @@ -198,7 +200,7 @@ fn validate_variable( { validator.push_diagnostic( Diagnostic::new(format!( - "Invalid constant {} - Functionblock- and Class-instances cannot be delcared constant", + "Invalid constant {}, FUNCTION_BLOCK- and CLASS-instances cannot be declared constant", v_entry.get_name() )) .with_error_code("E035") @@ -208,6 +210,50 @@ fn validate_variable( } } +/// Returns a diagnostic if a `REFERENCE TO` variable is incorrectly declared (or initialized). +fn validate_reference_to_declaration( + validator: &mut Validator, + context: &ValidationContext, + variable: &Variable, + variable_entry: &VariableIndexEntry, +) { + let Some(variable_ty) = context.index.find_effective_type_by_name(variable_entry.get_type_name()) else { + return; + }; + + if !variable_ty.get_type_information().is_reference_to() { + return; + } + + let Some(inner_ty_name) = variable_ty.get_type_information().get_inner_pointer_type_name() else { + 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(); + let inner_ty_is_global_var = context.index.find_global_variable(inner_ty_name).is_some(); + + if inner_ty_is_local_var || inner_ty_is_global_var { + validator.push_diagnostic( + Diagnostic::new("REFERENCE TO variables can not reference other variables") + .with_location(&variable_ty.location) + .with_error_code("E099"), + ); + } +} + #[cfg(test)] mod variable_validator_tests { use insta::assert_snapshot; diff --git a/tests/integration/cfc/resolver_tests.rs b/tests/integration/cfc/resolver_tests.rs index f11c866739..dd11728f65 100644 --- a/tests/integration/cfc/resolver_tests.rs +++ b/tests/integration/cfc/resolver_tests.rs @@ -99,6 +99,7 @@ fn function_block_calls_are_annotated_correctly() { Local, ), is_auto_deref: false, + is_reference_to: false, } "###); } 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 5d46308218..5fdbebac50 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 @@ -11,5 +11,6 @@ Some( Local, ), is_auto_deref: false, + is_reference_to: false, }, ) 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 1cb6d633ba..2d8cbaeaf2 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 @@ -11,5 +11,6 @@ Some( Local, ), is_auto_deref: false, + is_reference_to: false, }, ) diff --git a/tests/lit/single/pointer/ref_assignment_operator.st b/tests/lit/single/pointer/ref_assignment_operator.st new file mode 100644 index 0000000000..72f5a9f97c --- /dev/null +++ b/tests/lit/single/pointer/ref_assignment_operator.st @@ -0,0 +1,11 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s +FUNCTION main : DINT + VAR + a : REF_TO DINT; + b : DINT := 5; + END_VAR + a REF= b; + + // CHECK: 5 + printf('%d$N', a^); +END_FUNCTION \ No newline at end of file diff --git a/tests/lit/single/pointer/referenceto_variable_autoderef.st b/tests/lit/single/pointer/referenceto_variable_autoderef.st new file mode 100644 index 0000000000..400b2ffc56 --- /dev/null +++ b/tests/lit/single/pointer/referenceto_variable_autoderef.st @@ -0,0 +1,28 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s +FUNCTION main: DINT + VAR + foo : REFERENCE TO DINT; + bar : DINT; + baz : DINT; + qux : DINT; + END_VAR + + foo REF= bar; + bar := 1; + baz := 2; + qux := 2; + + // CHECK: 2 + bar := bar + foo; // bar + bar => 1 + 1 + printf('%d$N', foo); + + // CHECK: 4 + baz := baz + foo; // baz + foo => baz + bar => 2 + 2 + foo REF= baz; + printf('%d$N', foo); + + // CHECK: 6 + qux := qux + foo; // qux + foo => qux + baz => 2 + 4 + foo REF= qux; + printf('%d$N', foo); +END_FUNCTION \ No newline at end of file diff --git a/tests/lit/single/pointer/referenceto_variable_referencing_array.st b/tests/lit/single/pointer/referenceto_variable_referencing_array.st new file mode 100644 index 0000000000..f3e255a388 --- /dev/null +++ b/tests/lit/single/pointer/referenceto_variable_referencing_array.st @@ -0,0 +1,15 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s +FUNCTION main: DINT + VAR + arr : ARRAY[1..3] OF DINT; + refArr : REFERENCE TO ARRAY[1..3] OF DINT; + END_VAR + + refArr REF= arr; + 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/referenceto_variable_referencing_itself.st b/tests/lit/single/pointer/referenceto_variable_referencing_itself.st new file mode 100644 index 0000000000..c108e8917e --- /dev/null +++ b/tests/lit/single/pointer/referenceto_variable_referencing_itself.st @@ -0,0 +1,17 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s +FUNCTION main : DINT + VAR + foo : REFERENCE TO DINT; + bar : REFERENCE TO DINT; + qux : DINT; + END_VAR + + foo REF= bar; + bar REF= qux; + + bar REF= bar; + qux := 5; + + // CHECK: 5 + printf('%d$N', bar); // bar (-> bar) -> qux +END_FUNCTION \ No newline at end of file diff --git a/tests/lit/single/pointer/referenceto_variable_referencing_other_referenceto_variable.st b/tests/lit/single/pointer/referenceto_variable_referencing_other_referenceto_variable.st new file mode 100644 index 0000000000..9b739965c9 --- /dev/null +++ b/tests/lit/single/pointer/referenceto_variable_referencing_other_referenceto_variable.st @@ -0,0 +1,15 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s +FUNCTION main : DINT + VAR + foo : REFERENCE TO DINT; + bar : REFERENCE TO DINT; + qux : DINT; + END_VAR + + bar REF= qux; + foo REF= bar; + qux := 5; + + // CHECK: 5 + printf('%d$N', foo); // foo -> bar -> qux +END_FUNCTION diff --git a/tests/lit/single/pointer/referenceto_variable_referencing_struct.st b/tests/lit/single/pointer/referenceto_variable_referencing_struct.st new file mode 100644 index 0000000000..77e6502ce3 --- /dev/null +++ b/tests/lit/single/pointer/referenceto_variable_referencing_struct.st @@ -0,0 +1,24 @@ +// 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 : REFERENCE TO Transaction; + END_VAR + + refTxn REF= txn; + + // 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