From 198de5b86df2af08309474ce9d45a77b1a1ec1f7 Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Fri, 12 Sep 2025 12:11:27 +0200 Subject: [PATCH 01/27] fix builtins --- src/builtins.rs | 60 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/src/builtins.rs b/src/builtins.rs index 6c4ff42533e..37457d34939 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -213,20 +213,25 @@ lazy_static! { generic_name_resolver: no_generic_name_resolver, code: |generator, params, location| { if let &[g,in0,in1] = params { + // Handle named arguments by extracting actual parameters + let actual_g = extract_actual_parameter(g); + let actual_in0 = extract_actual_parameter(in0); + let actual_in1 = extract_actual_parameter(in1); + // evaluate the parameters - let cond = expression_generator::to_i1(generator.generate_expression(g)?.into_int_value(), &generator.llvm.builder); + let cond = expression_generator::to_i1(generator.generate_expression(actual_g)?.into_int_value(), &generator.llvm.builder); // for aggregate types we need a ptr to perform memcpy // use generate_expression_value(), this will return a gep // generate_expression() would load the ptr - let in0 = if generator.annotations.get_type(in0,generator.index).map(|it| it.get_type_information().is_aggregate()).unwrap_or_default() { - generator.generate_expression_value(in0)?.get_basic_value_enum() + let in0 = if generator.annotations.get_type(actual_in0,generator.index).map(|it| it.get_type_information().is_aggregate()).unwrap_or_default() { + generator.generate_expression_value(actual_in0)?.get_basic_value_enum() } else { - generator.generate_expression(in0)? + generator.generate_expression(actual_in0)? }; - let in1 = if generator.annotations.get_type(in1,generator.index).map(|it| it.get_type_information().is_aggregate()).unwrap_or_default() { - generator.generate_expression_value(in1)?.get_basic_value_enum() + let in1 = if generator.annotations.get_type(actual_in1,generator.index).map(|it| it.get_type_information().is_aggregate()).unwrap_or_default() { + generator.generate_expression_value(actual_in1)?.get_basic_value_enum() } else { - generator.generate_expression(in1)? + generator.generate_expression(actual_in1)? }; // generate an llvm select instruction let sel = generator.llvm.builder.build_select(cond, in1, in0, ""); @@ -256,7 +261,9 @@ lazy_static! { generic_name_resolver: no_generic_name_resolver, code : |generator, params, location| { if params.len() == 1 { - generator.generate_expression(params[0]).map(ExpressionValue::RValue) + // Handle named arguments by extracting the actual parameter + let actual_param = extract_actual_parameter(params[0]); + generator.generate_expression(actual_param).map(ExpressionValue::RValue) } else { Err(Diagnostic::codegen_error("MOVE expects exactly one parameter", location)) } @@ -276,9 +283,18 @@ lazy_static! { generic_name_resolver: no_generic_name_resolver, code : |generator, params, location| { if let [reference] = params { + // Handle named arguments by extracting the actual parameter from Assignment nodes + let actual_param = if let AstStatement::Assignment(assignment) = reference.get_stmt() { + // For named arguments like SIZEOF(in := foo), extract the right side (foo) + assignment.right.as_ref() + } else { + // For positional arguments like SIZEOF(foo), use the parameter directly + reference + }; + // get name of datatype behind reference let type_name = generator.annotations - .get_type(reference, generator.index) + .get_type(actual_param, generator.index) .map(|it| generator.index.get_effective_type_or_void_by_name(it.get_name())) .unwrap() .get_name(); @@ -853,6 +869,19 @@ fn validate_argument_count( } } +/// Helper function to extract the actual parameter from Assignment nodes when dealing with named arguments +/// For named arguments like `func(param := value)`, the AST contains an Assignment node where we need +/// to extract the right-hand side (the actual value). For positional arguments, use the parameter directly. +fn extract_actual_parameter(param: &AstNode) -> &AstNode { + if let AstStatement::Assignment(assignment) = param.get_stmt() { + // Named argument: extract the actual value from the right side of the assignment + assignment.right.as_ref() + } else { + // Positional argument: use the parameter directly + param + } +} + /// Generates the code for the LOWER- AND UPPER_BOUND built-in functions, returning an error if the function /// arguments are incorrect. fn generate_variable_length_array_bound_function<'ink>( @@ -863,8 +892,13 @@ fn generate_variable_length_array_bound_function<'ink>( ) -> Result, Diagnostic> { let llvm = generator.llvm; let builder = &generator.llvm.builder; + + // Handle named arguments by extracting the actual parameter + let actual_first_param = extract_actual_parameter(params[0]); + let actual_second_param = extract_actual_parameter(params[1]); + let data_type_information = - generator.annotations.get_type_or_void(params[0], generator.index).get_type_information(); + generator.annotations.get_type_or_void(actual_first_param, generator.index).get_type_information(); // TODO: most of the codegen errors should already be caught during validation. // once we abort codegen on critical errors, revisit and change to unreachable where possible @@ -875,10 +909,10 @@ fn generate_variable_length_array_bound_function<'ink>( )); }; - let vla = generator.generate_lvalue(params[0]).unwrap(); + let vla = generator.generate_lvalue(actual_first_param).unwrap(); let dim = builder.build_struct_gep(vla, 1, "dim").unwrap(); - let accessor = match params[1].get_stmt() { + let accessor = match actual_second_param.get_stmt() { // e.g. LOWER_BOUND(arr, 1) AstStatement::Literal(kind) => { let AstLiteral::Integer(value) = kind else { @@ -897,7 +931,7 @@ fn generate_variable_length_array_bound_function<'ink>( } // e.g. LOWER_BOUND(arr, idx + 3) _ => { - let expression_value = generator.generate_expression(params[1])?; + let expression_value = generator.generate_expression(actual_second_param)?; if !expression_value.is_int_value() { todo!() }; From a7874bf941097115ac6dab1f0f1ba15e4de67459 Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Mon, 22 Sep 2025 10:04:59 +0000 Subject: [PATCH 02/27] fix tests --- src/builtins.rs | 243 ++++++++++++++++-- ...call_lower_bound_with_named_arguments.snap | 48 ++++ ...unction_call_move_with_named_argument.snap | 21 ++ ...function_call_sel_with_named_argument.snap | 24 ++ ...ction_call_sizeof_with_named_argument.snap | 20 ++ ..._call_upper_bound_with_named_argument.snap | 48 ++++ src/validation/statement.rs | 3 +- .../single/builtin-named-arguments/main.st | 96 +++++++ 8 files changed, 477 insertions(+), 26 deletions(-) create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_lower_bound_with_named_arguments.snap create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_move_with_named_argument.snap create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sel_with_named_argument.snap create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sizeof_with_named_argument.snap create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_upper_bound_with_named_argument.snap create mode 100644 tests/lit/single/builtin-named-arguments/main.st diff --git a/src/builtins.rs b/src/builtins.rs index 37457d34939..095cab8d640 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -6,8 +6,8 @@ use inkwell::{ use lazy_static::lazy_static; use plc_ast::{ ast::{ - self, flatten_expression_list, pre_process, AstFactory, AstNode, AstStatement, CompilationUnit, - GenericBinding, LinkageType, Operator, TypeNature, + self, flatten_expression_list, pre_process, Assignment, AstFactory, AstNode, AstStatement, + CompilationUnit, GenericBinding, LinkageType, Operator, TypeNature, }, literals::AstLiteral, provider::IdProvider, @@ -50,6 +50,7 @@ lazy_static! { generic_name_resolver: no_generic_name_resolver, code: |generator, params, location| { if let [reference] = params { + let reference = extract_actual_parameter(reference); // Return the pointer value of a function when dealing with them, e.g. `ADR(MyFb.myMethod)` match generator.annotations.get(reference) { Some(StatementAnnotation::Function { qualified_name, .. }) => { @@ -74,7 +75,7 @@ lazy_static! { .map(|it| ExpressionValue::RValue(it.as_basic_value_enum())) } else { Err(Diagnostic::codegen_error( - "Expected exactly one parameter for REF", + "Expected exactly one parameter for ADR", location, )) } @@ -97,19 +98,24 @@ lazy_static! { let Some(params) = parameters else { return; }; // Get the input and annotate it with a pointer type let input = flatten_expression_list(params); - let Some(input) = input.first() else { return; }; + let actual_input = extract_actual_parameter(input.first().expect("Exactly one parameter required")); let input_type = annotator.annotation_map - .get_type_or_void(input, annotator.index) - .get_type_information() - .get_name() - .to_owned(); + .get_type_or_void(actual_input, annotator.index) + .get_type_information() + .get_name() + .to_owned(); let ptr_type = resolver::add_pointer_type( &mut annotator.annotation_map.new_index, - input_type, + input_type.clone(), true, ); + if input.first().is_some_and(|it| { + matches!(it.get_stmt(), AstStatement::Assignment(_)) + }){ + annotator.annotation_map.annotate_type_hint(actual_input, StatementAnnotation::value(input_type)); + } annotator.annotate( operator, resolver::StatementAnnotation::Function { return_type: ptr_type, qualified_name: "REF".to_string(), generic_name: None, call_name: None @@ -122,6 +128,7 @@ lazy_static! { generic_name_resolver: no_generic_name_resolver, code: |generator, params, location| { if let [reference] = params { + let reference = extract_actual_parameter(reference); // Return the pointer value of a function when dealing with them, e.g. `ADR(MyFb.myMethod)` if let Some(StatementAnnotation::Function { qualified_name, .. }) = generator.annotations.get(reference) { if let Some(fn_value) = generator.llvm_index.find_associated_implementation(qualified_name) { @@ -217,7 +224,7 @@ lazy_static! { let actual_g = extract_actual_parameter(g); let actual_in0 = extract_actual_parameter(in0); let actual_in1 = extract_actual_parameter(in1); - + // evaluate the parameters let cond = expression_generator::to_i1(generator.generate_expression(actual_g)?.into_int_value(), &generator.llvm.builder); // for aggregate types we need a ptr to perform memcpy @@ -291,7 +298,7 @@ lazy_static! { // For positional arguments like SIZEOF(foo), use the parameter directly reference }; - + // get name of datatype behind reference let type_name = generator.annotations .get_type(actual_param, generator.index) @@ -779,9 +786,9 @@ fn annotate_variable_length_array_bound_function( // caught during validation return; }; - + let vla_param = extract_actual_parameter(params[0]); // if the VLA parameter is a VLA struct, annotate it as such - let vla_type = annotator.annotation_map.get_type_or_void(vla, annotator.index); + let vla_type = annotator.annotation_map.get_type_or_void(vla_param, annotator.index); let vla_type_name = if vla_type.get_nature() == TypeNature::__VLA { vla_type.get_name() } else { @@ -789,7 +796,101 @@ fn annotate_variable_length_array_bound_function( typesystem::__VLA_TYPE }; - annotator.annotation_map.annotate_type_hint(vla, StatementAnnotation::value(vla_type_name)); + annotator.annotation_map.annotate_type_hint(vla_param, StatementAnnotation::value(vla_type_name)); + // if params.first().is_some_and(|it| matches!(it.get_stmt(), AstStatement::Assignment(_))) { + // } + if params.len() == 2 { + let dim_param = extract_actual_parameter(params[1]); + let dim_type = annotator.annotation_map.get_type_or_void(dim_param, annotator.index); + if dim_type.get_name() != typesystem::VOID_TYPE { + // Use the actual type of the dimension parameter + annotator + .annotation_map + .annotate_type_hint(dim_param, StatementAnnotation::value(dim_type.get_name())); + } else { + // Fallback to a default integer type if no type is available + annotator.annotation_map.annotate_type_hint(dim_param, StatementAnnotation::value("DINT")); + } + } + + // let (vla_param, dim_param) = if params.len() == 2 { + // let first_is_assignment = matches!(params[0].get_stmt(), AstStatement::Assignment(_)); + // let second_is_assignment = matches!(params[1].get_stmt(), AstStatement::Assignment(_)); + // + // // FIXME: review code and implement for remaining bultins + // // + // // if we get two assignments then we know we have named arguments + // if first_is_assignment && second_is_assignment { + // let mut vla_expr = None; + // let mut dim_expr = None; + // + // for param in ¶ms { + // if let AstStatement::Assignment(assignment) = param.get_stmt() { + // if let AstStatement::ReferenceExpr(ref_expr) = assignment.left.get_stmt() { + // if let ast::ReferenceAccess::Member(identifier) = &ref_expr.access { + // if let Some(identifier_name) = identifier.get_flat_reference_name() { + // match identifier_name { + // "arr" => vla_expr = Some(assignment.right.as_ref()), + // "dim" => dim_expr = Some(assignment.right.as_ref()), + // _ => {} + // } + // } + // } + // } + // } + // } + // + // match (vla_expr, dim_expr) { + // (Some(vla), Some(dim)) => (vla, dim), + // _ => return, // Invalid structure - caught during validation + // } + // } else { + // (extract_actual_parameter(params[0]), extract_actual_parameter(params[1])) + // } + // } else if params.len() == 1 { + // // Only one parameter provided - still annotate the VLA parameter but skip dim + // let vla = extract_actual_parameter(params[0]); + // let vla_type = annotator.annotation_map.get_type_or_void(vla, annotator.index); + // let vla_type_name = if vla_type.get_nature() == TypeNature::__VLA { + // vla_type.get_name() + // } else { + // typesystem::__VLA_TYPE + // }; + // + // // Annotate just the VLA parameter and return early + // if matches!(params[0].get_stmt(), AstStatement::Assignment(_)) { + // annotator.annotation_map.annotate_type_hint(vla, StatementAnnotation::value(vla_type_name)); + // } + // return; + // } else { + // // Invalid parameter count - caught during validation + // return; + // }; + // + // // if the VLA parameter is a VLA struct, annotate it as such + // let vla_type = annotator.annotation_map.get_type_or_void(vla_param, annotator.index); + // let vla_type_name = if vla_type.get_nature() == TypeNature::__VLA { + // vla_type.get_name() + // } else { + // // otherwise annotate it with an internal, reserved VLA type + // typesystem::__VLA_TYPE + // }; + // + // if params.first().is_some_and(|it| matches!(it.get_stmt(), AstStatement::Assignment(_))) { + // annotator.annotation_map.annotate_type_hint(vla_param, StatementAnnotation::value(vla_type_name)); + // } + // + // // Also annotate the dimension parameter to resolve its generic type T: ANY_INT + // let dim_type = annotator.annotation_map.get_type_or_void(dim_param, annotator.index); + // if dim_type.get_name() != typesystem::VOID_TYPE { + // // Use the actual type of the dimension parameter + // annotator + // .annotation_map + // .annotate_type_hint(dim_param, StatementAnnotation::value(dim_type.get_name())); + // } else { + // // Fallback to a default integer type if no type is available + // annotator.annotation_map.annotate_type_hint(dim_param, StatementAnnotation::value("DINT")); + // } } fn validate_variable_length_array_bound_function( @@ -812,9 +913,62 @@ fn validate_variable_length_array_bound_function( validator.push_diagnostic(Diagnostic::invalid_argument_count(2, params.len(), operator)); } + // match (params.first()) { + // Some(_vla) => { + // let actual_vla = if params.len() > 0 { + // let first_is_assignment = matches!(params[0].get_stmt(), AstStatement::Assignment(_)); + // if first_is_assignment { + // extract_actual_parameter(params[0]) + // } else { + // extract_actual_parameter(params[0]) + // } + // }; + // } + // _ => { + // todo!("todo") + // } + // } match (params.first(), params.get(1)) { - (Some(vla), Some(idx)) => { - let idx_type = annotations.get_type_or_void(idx, index); + (Some(_vla), Some(_idx)) => { + let (actual_vla, actual_idx) = if params.len() == 2 { + let first_is_assignment = matches!(params[0].get_stmt(), AstStatement::Assignment(_)); + let second_is_assignment = matches!(params[1].get_stmt(), AstStatement::Assignment(_)); + + if first_is_assignment && second_is_assignment { + // Named arguments - extract by parameter name + let mut vla_expr = None; + let mut dim_expr = None; + + for param in ¶ms { + if let AstStatement::Assignment(assignment) = param.get_stmt() { + if let AstStatement::ReferenceExpr(ref_expr) = assignment.left.get_stmt() { + if let ast::ReferenceAccess::Member(identifier) = &ref_expr.access { + if let Some(identifier_name) = identifier.get_flat_reference_name() { + match identifier_name { + "arr" => vla_expr = Some(assignment.right.as_ref()), + "dim" => dim_expr = Some(assignment.right.as_ref()), + _ => {} + } + } + } + } + } + } + + match (vla_expr, dim_expr) { + (Some(vla), Some(dim)) => (vla, dim), + _ => return, // Invalid structure - caught during validation + } + } else { + // TODO: is return correct here? + (extract_actual_parameter(params[0]), extract_actual_parameter(params[1])) + } + } else { + // TODO: is return correct here? + return; // Invalid parameter count - caught during validation + }; + + let idx_type = annotations.get_type_or_void(actual_idx, index); if !idx_type.has_nature(TypeNature::Int, index) { validator.push_diagnostic( @@ -824,16 +978,18 @@ fn validate_variable_length_array_bound_function( TypeNature::Int )) .with_error_code("E062") - .with_location(*idx), + .with_location(actual_idx), ) } // TODO: consider adding validation for consts and enums once https://github.com/PLC-lang/rusty/issues/847 has been implemented - if let AstStatement::Literal(AstLiteral::Integer(dimension_idx)) = idx.get_stmt() { + if let AstStatement::Literal(AstLiteral::Integer(dimension_idx)) = actual_idx.get_stmt() { let dimension_idx = *dimension_idx as usize; - let Some(n_dimensions) = - annotations.get_type_or_void(vla, index).get_type_information().get_dimension_count() + let Some(n_dimensions) = annotations + .get_type_or_void(actual_vla, index) + .get_type_information() + .get_dimension_count() else { // not a vla, validated via type nature return; @@ -892,11 +1048,48 @@ fn generate_variable_length_array_bound_function<'ink>( ) -> Result, Diagnostic> { let llvm = generator.llvm; let builder = &generator.llvm.builder; - - // Handle named arguments by extracting the actual parameter - let actual_first_param = extract_actual_parameter(params[0]); - let actual_second_param = extract_actual_parameter(params[1]); - + + let (actual_first_param, actual_second_param) = if params.len() == 2 { + let first_is_assignment = matches!(params[0].get_stmt(), AstStatement::Assignment(_)); + let second_is_assignment = matches!(params[1].get_stmt(), AstStatement::Assignment(_)); + + if first_is_assignment && second_is_assignment { + // Named arguments - extract by parameter name + let mut vla_expr = None; + let mut dim_expr = None; + + for param in params.iter() { + if let AstStatement::Assignment(assignment) = param.get_stmt() { + if let AstStatement::ReferenceExpr(ref_expr) = assignment.left.get_stmt() { + if let ast::ReferenceAccess::Member(identifier) = &ref_expr.access { + if let Some(identifier_name) = identifier.get_flat_reference_name() { + match identifier_name { + "arr" => vla_expr = Some(assignment.right.as_ref()), + "dim" => dim_expr = Some(assignment.right.as_ref()), + _ => {} + } + } + } + } + } + } + + match (vla_expr, dim_expr) { + (Some(vla), Some(dim)) => (vla, dim), + _ => { + return Err(Diagnostic::codegen_error("Invalid structure for named arguments", location)) + } + } + } else { + (extract_actual_parameter(params[0]), extract_actual_parameter(params[1])) + } + } else { + return Err(Diagnostic::codegen_error( + format!("Invalid parameter count. Expected 2 but got {}", params.len()), + location, + )); + }; + let data_type_information = generator.annotations.get_type_or_void(actual_first_param, generator.index).get_type_information(); diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_lower_bound_with_named_arguments.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_lower_bound_with_named_arguments.snap new file mode 100644 index 00000000000..057045aaf37 --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_lower_bound_with_named_arguments.snap @@ -0,0 +1,48 @@ +--- +source: src/codegen/tests/expression_tests.rs +expression: result +--- +; ModuleID = '' +source_filename = "" +target datalayout = "[filtered]" +target triple = "[filtered]" + +%main = type { [2 x i32], i32 } +%__foo_vla = type { i32*, [2 x i32] } + +@main_instance = global %main zeroinitializer +@____foo_vla__init = unnamed_addr constant %__foo_vla zeroinitializer + +define void @main(%main* %0) { +entry: + %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + %auto_deref = load [2 x i32], [2 x i32]* %a, align 4 + %outer_arr_gep = getelementptr inbounds [2 x i32], [2 x i32]* %a, i32 0, i32 0 + %vla_struct = alloca %__foo_vla, align 8 + %vla_array_gep = getelementptr inbounds %__foo_vla, %__foo_vla* %vla_struct, i32 0, i32 0 + %vla_dimensions_gep = getelementptr inbounds %__foo_vla, %__foo_vla* %vla_struct, i32 0, i32 1 + store [2 x i32] [i32 0, i32 1], [2 x i32]* %vla_dimensions_gep, align 4 + store i32* %outer_arr_gep, i32** %vla_array_gep, align 8 + %1 = load %__foo_vla, %__foo_vla* %vla_struct, align 8 + %vla_struct_ptr = alloca %__foo_vla, align 8 + store %__foo_vla %1, %__foo_vla* %vla_struct_ptr, align 8 + %call = call i32 @foo(%__foo_vla* %vla_struct_ptr) + store i32 %call, i32* %b, align 4 + ret void +} + +define i32 @foo(%__foo_vla* %0) { +entry: + %foo = alloca i32, align 4 + %vla = alloca %__foo_vla*, align 8 + store %__foo_vla* %0, %__foo_vla** %vla, align 8 + store i32 0, i32* %foo, align 4 + %deref = load %__foo_vla*, %__foo_vla** %vla, align 8 + %dim = getelementptr inbounds %__foo_vla, %__foo_vla* %deref, i32 0, i32 1 + %1 = getelementptr inbounds [2 x i32], [2 x i32]* %dim, i32 0, i32 0 + %2 = load i32, i32* %1, align 4 + store i32 %2, i32* %foo, align 4 + %foo_ret = load i32, i32* %foo, align 4 + ret i32 %foo_ret +} diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_move_with_named_argument.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_move_with_named_argument.snap new file mode 100644 index 00000000000..63ea222a11b --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_move_with_named_argument.snap @@ -0,0 +1,21 @@ +--- +source: src/codegen/tests/expression_tests.rs +expression: result +--- +; ModuleID = '' +source_filename = "" +target datalayout = "[filtered]" +target triple = "[filtered]" + +%main = type { i32, i32 } + +@main_instance = global %main zeroinitializer + +define void @main(%main* %0) { +entry: + %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + %load_b = load i32, i32* %b, align 4 + store i32 %load_b, i32* %a, align 4 + ret void +} diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sel_with_named_argument.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sel_with_named_argument.snap new file mode 100644 index 00000000000..86692757aac --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sel_with_named_argument.snap @@ -0,0 +1,24 @@ +--- +source: src/codegen/tests/expression_tests.rs +expression: result +--- +; ModuleID = '' +source_filename = "" +target datalayout = "[filtered]" +target triple = "[filtered]" + +%main = type { i32, i32, i32 } + +@main_instance = global %main zeroinitializer + +define void @main(%main* %0) { +entry: + %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + %c = getelementptr inbounds %main, %main* %0, i32 0, i32 2 + %load_b = load i32, i32* %b, align 4 + %load_c = load i32, i32* %c, align 4 + %1 = select i1 true, i32 %load_c, i32 %load_b + store i32 %1, i32* %a, align 4 + ret void +} diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sizeof_with_named_argument.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sizeof_with_named_argument.snap new file mode 100644 index 00000000000..bd46100bc0e --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sizeof_with_named_argument.snap @@ -0,0 +1,20 @@ +--- +source: src/codegen/tests/expression_tests.rs +expression: result +--- +; ModuleID = '' +source_filename = "" +target datalayout = "[filtered]" +target triple = "[filtered]" + +%main = type { i32, i64 } + +@main_instance = global %main zeroinitializer + +define void @main(%main* %0) { +entry: + %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + store i32 ptrtoint (i64* getelementptr (i64, i64* null, i32 1) to i32), i32* %a, align 4 + ret void +} diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_upper_bound_with_named_argument.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_upper_bound_with_named_argument.snap new file mode 100644 index 00000000000..e966e18d20b --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_upper_bound_with_named_argument.snap @@ -0,0 +1,48 @@ +--- +source: src/codegen/tests/expression_tests.rs +expression: result +--- +; ModuleID = '' +source_filename = "" +target datalayout = "[filtered]" +target triple = "[filtered]" + +%main = type { [2 x i32], i32 } +%__foo_vla = type { i32*, [2 x i32] } + +@main_instance = global %main zeroinitializer +@____foo_vla__init = unnamed_addr constant %__foo_vla zeroinitializer + +define void @main(%main* %0) { +entry: + %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + %auto_deref = load [2 x i32], [2 x i32]* %a, align 4 + %outer_arr_gep = getelementptr inbounds [2 x i32], [2 x i32]* %a, i32 0, i32 0 + %vla_struct = alloca %__foo_vla, align 8 + %vla_array_gep = getelementptr inbounds %__foo_vla, %__foo_vla* %vla_struct, i32 0, i32 0 + %vla_dimensions_gep = getelementptr inbounds %__foo_vla, %__foo_vla* %vla_struct, i32 0, i32 1 + store [2 x i32] [i32 0, i32 1], [2 x i32]* %vla_dimensions_gep, align 4 + store i32* %outer_arr_gep, i32** %vla_array_gep, align 8 + %1 = load %__foo_vla, %__foo_vla* %vla_struct, align 8 + %vla_struct_ptr = alloca %__foo_vla, align 8 + store %__foo_vla %1, %__foo_vla* %vla_struct_ptr, align 8 + %call = call i32 @foo(%__foo_vla* %vla_struct_ptr) + store i32 %call, i32* %b, align 4 + ret void +} + +define i32 @foo(%__foo_vla* %0) { +entry: + %foo = alloca i32, align 4 + %vla = alloca %__foo_vla*, align 8 + store %__foo_vla* %0, %__foo_vla** %vla, align 8 + store i32 0, i32* %foo, align 4 + %deref = load %__foo_vla*, %__foo_vla** %vla, align 8 + %dim = getelementptr inbounds %__foo_vla, %__foo_vla* %deref, i32 0, i32 1 + %1 = getelementptr inbounds [2 x i32], [2 x i32]* %dim, i32 0, i32 1 + %2 = load i32, i32* %1, align 4 + store i32 %2, i32* %foo, align 4 + %foo_ret = load i32, i32* %foo, align 4 + ret i32 %foo_ret +} diff --git a/src/validation/statement.rs b/src/validation/statement.rs index 8e40e9f1f91..ba24e27f3a3 100644 --- a/src/validation/statement.rs +++ b/src/validation/statement.rs @@ -1651,7 +1651,8 @@ fn validate_type_nature( if let DataTypeInformation::Generic { generic_symbol, nature, .. } = type_hint.get_type_information() { // we might be validating an identifier of a formal parameter assignment (FOO(x := 0)) - if let AstStatement::Identifier(_) = statement.get_stmt() { + // This includes both Identifier and ReferenceExpr nodes for named arguments + if let AstStatement::Identifier(_) | AstStatement::ReferenceExpr(_) = statement.get_stmt() { return; } validator.push_diagnostic( diff --git a/tests/lit/single/builtin-named-arguments/main.st b/tests/lit/single/builtin-named-arguments/main.st new file mode 100644 index 00000000000..ba51cb6e43a --- /dev/null +++ b/tests/lit/single/builtin-named-arguments/main.st @@ -0,0 +1,96 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s +// +// Test cases for builtin functions with both named and positional arguments +// +// NOTE: `MUX` does currently not support named arguments +// +// This test verifies that builtin functions work correctly with: +// - MOVE: Both named (IN := x) and positional arguments +// - SEL: Both named (G := TRUE, IN0 := b, IN1 := c) and positional (TRUE, b, c) arguments +// - SIZEOF: Both named (in := myarray) and positional (myarray) arguments +// - UPPER_BOUND: Both named (arr := vla, dim := x) and positional (vla, x) arguments +// - LOWER_BOUND: Both named (arr := vla, dim := x) and positional (vla, x) arguments +// - ADR: Both named (in := variable) and positional (variable) arguments +// - REF: Both named (in := variable) and positional (variable) arguments +// +// The test ensures that generic type resolution works properly for both calling conventions. + +FUNCTION main : DINT +VAR + result : ULINT; + x : DINT := 9; + y : DINT := 7331; + + a : DINT := 0; + b : DINT := 3; + c : DINT := 4; + + piAddress1: REF_TO INT; + iVar1 : INT := 5; + + myarray : ARRAY [0..9] OF BYTE := [0,1,2,3,4,5,6,7,8,9]; +END_VAR + +printf('%d$N', y); // CHECK: 7331 +y := MOVE(IN := x); +printf('%d$N', y); // CHECK: 9 +x := MOVE(y); +printf('%d$N', y); // CHECK: 9 + + +a := SEL(G := TRUE, IN0 := b, IN1 := c); +printf('%d$N', a); // CHECK: 4 +a := 0; +a := SEL(TRUE, b, c); +printf('%d$N', a); // CHECK: 4 + +result := MUX(1, a, b, c); + +result := SIZEOF(in := myarray); +printf('%d$N', result); // CHECK: 10 + +result := SIZEOF(myarray); +printf('%d$N', result); // CHECK: 10 + +piAddress1 := ADR(iVar1); +piAddress1 := ADR(IN := iVar1); + +// Test REF builtin with both positional and named arguments +piAddress1 := REF(iVar1); // positional +piAddress1 := REF(in := iVar1); // named argument + +test_bounds(myarray); + +main := 0; + +END_FUNCTION + +FUNCTION test_bounds : DINT +VAR_IN_OUT + vla : ARRAY [*] OF BYTE; +END_VAR +VAR + result : DINT; + x : DINT := 1; +END_VAR + +// check with positional arguments +result := UPPER_BOUND( vla, x); +printf('%d$N', result); // CHECK: 9 + +result := 999; + +// and with named arguments +result := UPPER_BOUND(arr := vla, dim := x); +printf('%d$N', result); // CHECK: 9 + +// check with positional arguments +result := LOWER_BOUND( vla, x); +printf('%d$N', result); // CHECK: 0 + +result := 999; + +// and with named arguments +result := LOWER_BOUND(arr := vla, dim := x); +printf('%d$N', result); // CHECK: 0 +END_FUNCTION From 1d63f426e74e7ebbda3dc5e5eb2f6c0b3a2f0a75 Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Mon, 22 Sep 2025 10:05:17 +0000 Subject: [PATCH 03/27] add codegen tests --- src/codegen/tests/expression_tests.rs | 91 +++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/src/codegen/tests/expression_tests.rs b/src/codegen/tests/expression_tests.rs index c31329085ed..7fc31a739c2 100644 --- a/src/codegen/tests/expression_tests.rs +++ b/src/codegen/tests/expression_tests.rs @@ -355,6 +355,20 @@ fn builtin_function_call_sel() { filtered_assert_snapshot!(result); } +#[test] +fn builtin_function_call_sel_with_named_argument() { + let result = codegen( + "PROGRAM main + VAR + a,b,c : DINT; + END_VAR + a := SEL(G := TRUE, IN0 := b, IN1 := c); + END_PROGRAM", + ); + + filtered_assert_snapshot!(result); +} + #[test] fn builtin_function_call_sel_as_expression() { let result = codegen( @@ -383,6 +397,20 @@ fn builtin_function_call_move() { filtered_assert_snapshot!(result); } +#[test] +fn builtin_function_call_move_with_named_argument() { + let result = codegen( + "PROGRAM main + VAR + a,b : DINT; + END_VAR + a := MOVE(IN := b); + END_PROGRAM", + ); + + filtered_assert_snapshot!(result); +} + #[test] fn builtin_function_call_sizeof() { let result = codegen( @@ -398,6 +426,21 @@ fn builtin_function_call_sizeof() { filtered_assert_snapshot!(result); } +#[test] +fn builtin_function_call_sizeof_with_named_argument() { + let result = codegen( + "PROGRAM main + VAR + a: DINT; + b: LINT; + END_VAR + a := SIZEOF(IN := b); + END_PROGRAM", + ); + + filtered_assert_snapshot!(result); +} + #[test] fn builtin_function_call_lower_bound() { let result = codegen( @@ -422,6 +465,54 @@ fn builtin_function_call_lower_bound() { filtered_assert_snapshot!(result); } +#[test] +fn builtin_function_call_lower_bound_with_named_arguments() { + let result = codegen( + "PROGRAM main + VAR + a: ARRAY[0..1] OF DINT; + b: DINT; + END_VAR + b := foo(a); + END_PROGRAM + + FUNCTION foo : DINT + VAR_IN_OUT + vla: ARRAY[*] OF DINT; + END_VAR + foo := LOWER_BOUND(arr := vla, dim := 1); + END_VAR + END_FUNCTION + ", + ); + + filtered_assert_snapshot!(result); +} + +#[test] +fn builtin_function_call_upper_bound_with_named_argument() { + let result = codegen( + "PROGRAM main + VAR + a: ARRAY[0..1] OF DINT; + b: DINT; + END_VAR + b := foo(a); + END_PROGRAM + + FUNCTION foo : DINT + VAR_IN_OUT + vla: ARRAY[*] OF DINT; + END_VAR + foo := UPPER_BOUND(arr := vla, dim := 1); + END_VAR + END_FUNCTION + ", + ); + + filtered_assert_snapshot!(result); +} + #[test] fn builtin_function_call_upper_bound() { let result = codegen( From b4099664fea0ecd2b22c2b79a1a5e0abec2148b1 Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Mon, 22 Sep 2025 10:06:05 +0000 Subject: [PATCH 04/27] add correctness tests --- tests/correctness/functions.rs | 59 ++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/correctness/functions.rs b/tests/correctness/functions.rs index c1b75d9672b..f25574b7aba 100644 --- a/tests/correctness/functions.rs +++ b/tests/correctness/functions.rs @@ -1172,6 +1172,28 @@ fn sel_string_literal() { assert_eq!(main.res, "world\0".as_bytes()); } +#[test] +fn sel_string_literal_with_named_arguments() { + #[repr(C)] + #[derive(Default)] + struct MainType { + res: [u8; 6], + } + + let function = r#" + PROGRAM main + VAR + str1 : STRING[5]; + END_VAR + str1 := SEL(G := TRUE, IN0 := 'hello', IN1 := 'world'); // world + END_PROGRAM + "#; + + let mut main = MainType::default(); + let _: i32 = compile_and_run(function.to_string(), &mut main); + assert_eq!(main.res, "world\0".as_bytes()); +} + #[test] fn move_test() { let function = r#" @@ -1188,6 +1210,22 @@ fn move_test() { assert_eq!(res, 4) } +#[test] +fn move_test_with_named_argument() { + let function = r#" + FUNCTION main : DINT + VAR a : DINT; END_VAR + a := 4; + main := MOVE(in := a); //Result is 4 + END_FUNCTION + "#; + + let context = CodegenContext::create(); + let module = compile(&context, function); + let res: i32 = module.run_no_param("main"); + assert_eq!(res, 4) +} + #[test] fn sizeof_test() { #[derive(Debug, Default, PartialEq)] @@ -1255,6 +1293,27 @@ fn sizeof_test() { assert_eq!(expected, maintype); } +#[test] +fn sizeof_minimal_test_with_argument() { + let function = r#" + PROGRAM main + VAR + s1 : INT; + a : INT; + END_VAR + s1 := SIZEOF(in := a); // a is INT, so SIZEOF(a) = 2 + END_PROGRAM + "#; + + let mut maintype = MainType::default(); + let context = CodegenContext::create(); + let module = compile(&context, function); + let mut res = 0; + let _: i32 = module.run("main", &mut res); + + assert_eq!(2, res); +} + #[test] #[ignore = "variable sized arrays not yet implemented"] fn sizeof_len() { From 3325e393c11193056acf7e3574df4e1b411026ce Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Mon, 22 Sep 2025 11:45:46 +0000 Subject: [PATCH 05/27] improve lit test --- .../single/builtin-named-arguments/main.st | 120 ++++++++++-------- 1 file changed, 67 insertions(+), 53 deletions(-) diff --git a/tests/lit/single/builtin-named-arguments/main.st b/tests/lit/single/builtin-named-arguments/main.st index ba51cb6e43a..4e3114f46ca 100644 --- a/tests/lit/single/builtin-named-arguments/main.st +++ b/tests/lit/single/builtin-named-arguments/main.st @@ -17,51 +17,64 @@ FUNCTION main : DINT VAR - result : ULINT; - x : DINT := 9; - y : DINT := 7331; + result : ULINT; + x : DINT := 9; + y : DINT := 7331; - a : DINT := 0; - b : DINT := 3; - c : DINT := 4; + a : DINT := 0; + b : DINT := 3; + c : DINT := 4; - piAddress1: REF_TO INT; - iVar1 : INT := 5; + piAddress1 : REF_TO INT; + piAddress2 : REF_TO INT; + iVar1 : INT := 5; myarray : ARRAY [0..9] OF BYTE := [0,1,2,3,4,5,6,7,8,9]; END_VAR -printf('%d$N', y); // CHECK: 7331 -y := MOVE(IN := x); -printf('%d$N', y); // CHECK: 9 -x := MOVE(y); -printf('%d$N', y); // CHECK: 9 - - -a := SEL(G := TRUE, IN0 := b, IN1 := c); -printf('%d$N', a); // CHECK: 4 -a := 0; -a := SEL(TRUE, b, c); -printf('%d$N', a); // CHECK: 4 - -result := MUX(1, a, b, c); - -result := SIZEOF(in := myarray); -printf('%d$N', result); // CHECK: 10 - -result := SIZEOF(myarray); -printf('%d$N', result); // CHECK: 10 - -piAddress1 := ADR(iVar1); -piAddress1 := ADR(IN := iVar1); - -// Test REF builtin with both positional and named arguments -piAddress1 := REF(iVar1); // positional -piAddress1 := REF(in := iVar1); // named argument - -test_bounds(myarray); - -main := 0; + // MOVE + printf('%d$N', y); // CHECK: 7331 + y := MOVE(IN := x); + printf('%d$N', y); // CHECK: 9 + y := 7331; + x := MOVE(y); + printf('%d$N', y); // CHECK: 7331 + + // SEL + a := SEL(G := TRUE, IN0 := b, IN1 := c); + printf('%d$N', a); // CHECK: 4 + a := 0; + a := SEL(TRUE, b, c); + printf('%d$N', a); // CHECK: 4 + + result := MUX(1, a, b, c); + printf('%d$N', result); // CHECK: 3 + + // SIZEOF + result := SIZEOF(in := myarray); + printf('%d$N', result); // CHECK: 10 + result := SIZEOF(myarray); + printf('%d$N', result); // CHECK: 10 + + // ADR + result := 0; + printf('%d$N', result); // CHECK: 0 + piAddress1 := ADR(iVar1); + piAddress2 := ADR(IN := iVar1); + result := (piAddress1 = piAddress2); + printf('%d$N', result); // CHECK: 1 + + // REF + result := 0; + printf('%d$N', result); // CHECK: 0 + piAddress1 := REF(iVar1); // positional + piAddress2 := REF(in := iVar1); // named argument + result := (piAddress1 = piAddress2); + printf('%d$N', result); // CHECK: 1 + + test_bounds(myarray); + + main := 0; END_FUNCTION @@ -74,23 +87,24 @@ VAR x : DINT := 1; END_VAR -// check with positional arguments -result := UPPER_BOUND( vla, x); -printf('%d$N', result); // CHECK: 9 + // check with positional arguments + result := UPPER_BOUND(vla, x); + printf('%d$N', result); // CHECK: 9 + + result := 999; -result := 999; + // and with named arguments + result := UPPER_BOUND(arr := vla, dim := x); + printf('%d$N', result); // CHECK: 9 -// and with named arguments -result := UPPER_BOUND(arr := vla, dim := x); -printf('%d$N', result); // CHECK: 9 + // check with positional arguments + result := LOWER_BOUND(vla, x); + printf('%d$N', result); // CHECK: 0 -// check with positional arguments -result := LOWER_BOUND( vla, x); -printf('%d$N', result); // CHECK: 0 + result := 999; -result := 999; + // and with named arguments + result := LOWER_BOUND(arr := vla, dim := x); + printf('%d$N', result); // CHECK: 0 -// and with named arguments -result := LOWER_BOUND(arr := vla, dim := x); -printf('%d$N', result); // CHECK: 0 END_FUNCTION From 454f9b5f21924c283d307b83725b303150e7ccc1 Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Mon, 22 Sep 2025 12:18:18 +0000 Subject: [PATCH 06/27] add more tests --- src/codegen/tests/expression_tests.rs | 39 +++++++++++++++++++ ...function_call_adr_with_named_argument.snap | 20 ++++++++++ ...function_call_ref_with_named_argument.snap | 20 ++++++++++ ...uiltin_function_call_upper_bound_expr.snap | 6 ++- 4 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_adr_with_named_argument.snap create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_ref_with_named_argument.snap diff --git a/src/codegen/tests/expression_tests.rs b/src/codegen/tests/expression_tests.rs index 7fc31a739c2..13d26102ef2 100644 --- a/src/codegen/tests/expression_tests.rs +++ b/src/codegen/tests/expression_tests.rs @@ -294,6 +294,25 @@ fn builtin_function_call_adr() { filtered_assert_snapshot!(result); } +#[test] +fn builtin_function_call_adr_with_named_argument() { + // GIVEN some nested call statements + let result = codegen( + " + PROGRAM main + VAR + a : REF_TO DINT; + b : DINT; + END_VAR + a := ADR(IN := b); + END_PROGRAM + ", + ); + // WHEN compiled + // We expect the same behaviour as if REF was called, due to the assignee being a pointer + filtered_assert_snapshot!(result); +} + #[test] fn builtin_function_call_ref() { // GIVEN some nested call statements @@ -313,6 +332,25 @@ fn builtin_function_call_ref() { filtered_assert_snapshot!(result); } +#[test] +fn builtin_function_call_ref_with_named_argument() { + // GIVEN some nested call statements + let result = codegen( + " + PROGRAM main + VAR + a : REF_TO DINT; + b : DINT; + END_VAR + a := REF(IN := b); + END_PROGRAM + ", + ); + // WHEN compiled + // We expect a direct conversion and subsequent assignment to pointer(no call) + filtered_assert_snapshot!(result); +} + #[test] fn builtin_function_call_mux() { let result = codegen( @@ -558,6 +596,7 @@ fn builtin_function_call_upper_bound_expr() { END_VAR // upper bound of 4th dimension => 8th element in dimension array foo := UPPER_BOUND(vla, MY_CONST - (2 * 3)); + foo := UPPER_BOUND(arr := vla, dim := MY_CONST - (2 * 3)); END_VAR END_FUNCTION ", diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_adr_with_named_argument.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_adr_with_named_argument.snap new file mode 100644 index 00000000000..4df20e09689 --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_adr_with_named_argument.snap @@ -0,0 +1,20 @@ +--- +source: src/codegen/tests/expression_tests.rs +expression: result +--- +; ModuleID = '' +source_filename = "" +target datalayout = "[filtered]" +target triple = "[filtered]" + +%main = type { i32*, i32 } + +@main_instance = global %main zeroinitializer + +define void @main(%main* %0) { +entry: + %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + store i32* %b, i32** %a, align 8 + ret void +} diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_ref_with_named_argument.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_ref_with_named_argument.snap new file mode 100644 index 00000000000..4df20e09689 --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_ref_with_named_argument.snap @@ -0,0 +1,20 @@ +--- +source: src/codegen/tests/expression_tests.rs +expression: result +--- +; ModuleID = '' +source_filename = "" +target datalayout = "[filtered]" +target triple = "[filtered]" + +%main = type { i32*, i32 } + +@main_instance = global %main zeroinitializer + +define void @main(%main* %0) { +entry: + %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + store i32* %b, i32** %a, align 8 + ret void +} diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_upper_bound_expr.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_upper_bound_expr.snap index a4a48f79b9d..074c65ee680 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_upper_bound_expr.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_upper_bound_expr.snap @@ -1,7 +1,6 @@ --- source: src/codegen/tests/expression_tests.rs expression: result -snapshot_kind: text --- ; ModuleID = '' source_filename = "" @@ -45,6 +44,11 @@ entry: %1 = getelementptr inbounds [2 x i32], [2 x i32]* %dim, i32 0, i32 7 %2 = load i32, i32* %1, align 4 store i32 %2, i32* %foo, align 4 + %deref1 = load %__foo_vla*, %__foo_vla** %vla, align 8 + %dim2 = getelementptr inbounds %__foo_vla, %__foo_vla* %deref1, i32 0, i32 1 + %3 = getelementptr inbounds [2 x i32], [2 x i32]* %dim2, i32 0, i32 7 + %4 = load i32, i32* %3, align 4 + store i32 %4, i32* %foo, align 4 %foo_ret = load i32, i32* %foo, align 4 ret i32 %foo_ret } From ed037f49f6894de731f6f78c54769f45f0912f2f Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Mon, 22 Sep 2025 12:24:02 +0000 Subject: [PATCH 07/27] cleanup --- src/builtins.rs | 104 ++------------------------------- src/validation/statement.rs | 2 +- tests/correctness/functions.rs | 1 - 3 files changed, 5 insertions(+), 102 deletions(-) diff --git a/src/builtins.rs b/src/builtins.rs index 095cab8d640..b440f49e877 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -6,8 +6,8 @@ use inkwell::{ use lazy_static::lazy_static; use plc_ast::{ ast::{ - self, flatten_expression_list, pre_process, Assignment, AstFactory, AstNode, AstStatement, - CompilationUnit, GenericBinding, LinkageType, Operator, TypeNature, + self, flatten_expression_list, pre_process, AstFactory, AstNode, AstStatement, CompilationUnit, + GenericBinding, LinkageType, Operator, TypeNature, }, literals::AstLiteral, provider::IdProvider, @@ -786,7 +786,7 @@ fn annotate_variable_length_array_bound_function( // caught during validation return; }; - let vla_param = extract_actual_parameter(params[0]); + let vla_param = extract_actual_parameter(vla); // if the VLA parameter is a VLA struct, annotate it as such let vla_type = annotator.annotation_map.get_type_or_void(vla_param, annotator.index); let vla_type_name = if vla_type.get_nature() == TypeNature::__VLA { @@ -795,10 +795,8 @@ fn annotate_variable_length_array_bound_function( // otherwise annotate it with an internal, reserved VLA type typesystem::__VLA_TYPE }; - annotator.annotation_map.annotate_type_hint(vla_param, StatementAnnotation::value(vla_type_name)); - // if params.first().is_some_and(|it| matches!(it.get_stmt(), AstStatement::Assignment(_))) { - // } + if params.len() == 2 { let dim_param = extract_actual_parameter(params[1]); let dim_type = annotator.annotation_map.get_type_or_void(dim_param, annotator.index); @@ -812,85 +810,6 @@ fn annotate_variable_length_array_bound_function( annotator.annotation_map.annotate_type_hint(dim_param, StatementAnnotation::value("DINT")); } } - - // let (vla_param, dim_param) = if params.len() == 2 { - // let first_is_assignment = matches!(params[0].get_stmt(), AstStatement::Assignment(_)); - // let second_is_assignment = matches!(params[1].get_stmt(), AstStatement::Assignment(_)); - // - // // FIXME: review code and implement for remaining bultins - // // - // // if we get two assignments then we know we have named arguments - // if first_is_assignment && second_is_assignment { - // let mut vla_expr = None; - // let mut dim_expr = None; - // - // for param in ¶ms { - // if let AstStatement::Assignment(assignment) = param.get_stmt() { - // if let AstStatement::ReferenceExpr(ref_expr) = assignment.left.get_stmt() { - // if let ast::ReferenceAccess::Member(identifier) = &ref_expr.access { - // if let Some(identifier_name) = identifier.get_flat_reference_name() { - // match identifier_name { - // "arr" => vla_expr = Some(assignment.right.as_ref()), - // "dim" => dim_expr = Some(assignment.right.as_ref()), - // _ => {} - // } - // } - // } - // } - // } - // } - // - // match (vla_expr, dim_expr) { - // (Some(vla), Some(dim)) => (vla, dim), - // _ => return, // Invalid structure - caught during validation - // } - // } else { - // (extract_actual_parameter(params[0]), extract_actual_parameter(params[1])) - // } - // } else if params.len() == 1 { - // // Only one parameter provided - still annotate the VLA parameter but skip dim - // let vla = extract_actual_parameter(params[0]); - // let vla_type = annotator.annotation_map.get_type_or_void(vla, annotator.index); - // let vla_type_name = if vla_type.get_nature() == TypeNature::__VLA { - // vla_type.get_name() - // } else { - // typesystem::__VLA_TYPE - // }; - // - // // Annotate just the VLA parameter and return early - // if matches!(params[0].get_stmt(), AstStatement::Assignment(_)) { - // annotator.annotation_map.annotate_type_hint(vla, StatementAnnotation::value(vla_type_name)); - // } - // return; - // } else { - // // Invalid parameter count - caught during validation - // return; - // }; - // - // // if the VLA parameter is a VLA struct, annotate it as such - // let vla_type = annotator.annotation_map.get_type_or_void(vla_param, annotator.index); - // let vla_type_name = if vla_type.get_nature() == TypeNature::__VLA { - // vla_type.get_name() - // } else { - // // otherwise annotate it with an internal, reserved VLA type - // typesystem::__VLA_TYPE - // }; - // - // if params.first().is_some_and(|it| matches!(it.get_stmt(), AstStatement::Assignment(_))) { - // annotator.annotation_map.annotate_type_hint(vla_param, StatementAnnotation::value(vla_type_name)); - // } - // - // // Also annotate the dimension parameter to resolve its generic type T: ANY_INT - // let dim_type = annotator.annotation_map.get_type_or_void(dim_param, annotator.index); - // if dim_type.get_name() != typesystem::VOID_TYPE { - // // Use the actual type of the dimension parameter - // annotator - // .annotation_map - // .annotate_type_hint(dim_param, StatementAnnotation::value(dim_type.get_name())); - // } else { - // // Fallback to a default integer type if no type is available - // annotator.annotation_map.annotate_type_hint(dim_param, StatementAnnotation::value("DINT")); - // } } fn validate_variable_length_array_bound_function( @@ -913,21 +832,6 @@ fn validate_variable_length_array_bound_function( validator.push_diagnostic(Diagnostic::invalid_argument_count(2, params.len(), operator)); } - // match (params.first()) { - // Some(_vla) => { - // let actual_vla = if params.len() > 0 { - // let first_is_assignment = matches!(params[0].get_stmt(), AstStatement::Assignment(_)); - // if first_is_assignment { - // extract_actual_parameter(params[0]) - // } else { - // extract_actual_parameter(params[0]) - // } - // }; - // } - // _ => { - // todo!("todo") - // } - // } match (params.first(), params.get(1)) { (Some(_vla), Some(_idx)) => { let (actual_vla, actual_idx) = if params.len() == 2 { diff --git a/src/validation/statement.rs b/src/validation/statement.rs index ba24e27f3a3..16b1c28342d 100644 --- a/src/validation/statement.rs +++ b/src/validation/statement.rs @@ -1652,7 +1652,7 @@ fn validate_type_nature( { // we might be validating an identifier of a formal parameter assignment (FOO(x := 0)) // This includes both Identifier and ReferenceExpr nodes for named arguments - if let AstStatement::Identifier(_) | AstStatement::ReferenceExpr(_) = statement.get_stmt() { + if let AstStatement::Identifier(_) = statement.get_stmt() { return; } validator.push_diagnostic( diff --git a/tests/correctness/functions.rs b/tests/correctness/functions.rs index f25574b7aba..70db80e8fc2 100644 --- a/tests/correctness/functions.rs +++ b/tests/correctness/functions.rs @@ -1305,7 +1305,6 @@ fn sizeof_minimal_test_with_argument() { END_PROGRAM "#; - let mut maintype = MainType::default(); let context = CodegenContext::create(); let module = compile(&context, function); let mut res = 0; From 35488260bf5b3318b0b939a9fa5a9ebb7f3d182c Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Mon, 22 Sep 2025 12:49:04 +0000 Subject: [PATCH 08/27] fix and cleanup --- src/builtins.rs | 51 +++---------------------------------- src/validation/statement.rs | 2 +- 2 files changed, 5 insertions(+), 48 deletions(-) diff --git a/src/builtins.rs b/src/builtins.rs index b440f49e877..eff6b0e3be0 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -290,15 +290,7 @@ lazy_static! { generic_name_resolver: no_generic_name_resolver, code : |generator, params, location| { if let [reference] = params { - // Handle named arguments by extracting the actual parameter from Assignment nodes - let actual_param = if let AstStatement::Assignment(assignment) = reference.get_stmt() { - // For named arguments like SIZEOF(in := foo), extract the right side (foo) - assignment.right.as_ref() - } else { - // For positional arguments like SIZEOF(foo), use the parameter directly - reference - }; - + let actual_param = extract_actual_parameter(reference); // get name of datatype behind reference let type_name = generator.annotations .get_type(actual_param, generator.index) @@ -833,44 +825,9 @@ fn validate_variable_length_array_bound_function( } match (params.first(), params.get(1)) { - (Some(_vla), Some(_idx)) => { - let (actual_vla, actual_idx) = if params.len() == 2 { - let first_is_assignment = matches!(params[0].get_stmt(), AstStatement::Assignment(_)); - let second_is_assignment = matches!(params[1].get_stmt(), AstStatement::Assignment(_)); - - if first_is_assignment && second_is_assignment { - // Named arguments - extract by parameter name - let mut vla_expr = None; - let mut dim_expr = None; - - for param in ¶ms { - if let AstStatement::Assignment(assignment) = param.get_stmt() { - if let AstStatement::ReferenceExpr(ref_expr) = assignment.left.get_stmt() { - if let ast::ReferenceAccess::Member(identifier) = &ref_expr.access { - if let Some(identifier_name) = identifier.get_flat_reference_name() { - match identifier_name { - "arr" => vla_expr = Some(assignment.right.as_ref()), - "dim" => dim_expr = Some(assignment.right.as_ref()), - _ => {} - } - } - } - } - } - } - - match (vla_expr, dim_expr) { - (Some(vla), Some(dim)) => (vla, dim), - _ => return, // Invalid structure - caught during validation - } - } else { - // TODO: is return correct here? - (extract_actual_parameter(params[0]), extract_actual_parameter(params[1])) - } - } else { - // TODO: is return correct here? - return; // Invalid parameter count - caught during validation - }; + (Some(vla), Some(idx)) => { + let actual_vla = extract_actual_parameter(vla); + let actual_idx = extract_actual_parameter(idx); let idx_type = annotations.get_type_or_void(actual_idx, index); diff --git a/src/validation/statement.rs b/src/validation/statement.rs index 16b1c28342d..ba24e27f3a3 100644 --- a/src/validation/statement.rs +++ b/src/validation/statement.rs @@ -1652,7 +1652,7 @@ fn validate_type_nature( { // we might be validating an identifier of a formal parameter assignment (FOO(x := 0)) // This includes both Identifier and ReferenceExpr nodes for named arguments - if let AstStatement::Identifier(_) = statement.get_stmt() { + if let AstStatement::Identifier(_) | AstStatement::ReferenceExpr(_) = statement.get_stmt() { return; } validator.push_diagnostic( From f8e29873d482d3616ef87ddc49dd123598502112 Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Tue, 23 Sep 2025 10:37:36 +0200 Subject: [PATCH 09/27] refactor --- src/builtins.rs | 102 +++++++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 57 deletions(-) diff --git a/src/builtins.rs b/src/builtins.rs index bd333c76d4f..0f6cd34e1f2 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -770,27 +770,23 @@ fn annotate_variable_length_array_bound_function( parameters: Option<&AstNode>, ) { let Some(parameters) = parameters else { - // caught during validation return; }; let params = ast::flatten_expression_list(parameters); - let Some(vla) = params.first() else { - // caught during validation - return; - }; - let vla_param = extract_actual_parameter(vla); - // if the VLA parameter is a VLA struct, annotate it as such - let vla_type = annotator.annotation_map.get_type_or_void(vla_param, annotator.index); - let vla_type_name = if vla_type.get_nature() == TypeNature::__VLA { - vla_type.get_name() - } else { - // otherwise annotate it with an internal, reserved VLA type - typesystem::__VLA_TYPE - }; - annotator.annotation_map.annotate_type_hint(vla_param, StatementAnnotation::value(vla_type_name)); - - if params.len() == 2 { - let dim_param = extract_actual_parameter(params[1]); + if let Some(vla) = params.first() { + let vla_param = extract_actual_parameter(vla); + // if the VLA parameter is a VLA struct, annotate it as such + let vla_type = annotator.annotation_map.get_type_or_void(vla_param, annotator.index); + let vla_type_name = if vla_type.get_nature() == TypeNature::__VLA { + vla_type.get_name() + } else { + // otherwise annotate it with an internal, reserved VLA type + typesystem::__VLA_TYPE + }; + annotator.annotation_map.annotate_type_hint(vla_param, StatementAnnotation::value(vla_type_name)); + } + if let Some(dim) = params.get(1) { + let dim_param = extract_actual_parameter(dim); let dim_type = annotator.annotation_map.get_type_or_void(dim_param, annotator.index); if dim_type.get_name() != typesystem::VOID_TYPE { // Use the actual type of the dimension parameter @@ -820,51 +816,43 @@ fn validate_variable_length_array_bound_function( let params = ast::flatten_expression_list(parameters); - if params.len() > 2 { - validator.push_diagnostic(Diagnostic::invalid_argument_count(2, params.len(), operator)); - } + if let &[vla, dim] = params.as_slice() { + let actual_vla = extract_actual_parameter(vla); + let actual_idx = extract_actual_parameter(dim); + + let idx_type = annotations.get_type_or_void(actual_idx, index); + + if !idx_type.has_nature(TypeNature::Int, index) { + validator.push_diagnostic( + Diagnostic::new(format!( + "Invalid type nature for generic argument. {} is no {}", + idx_type.get_name(), + TypeNature::Int + )) + .with_error_code("E062") + .with_location(actual_idx), + ) + } - match (params.first(), params.get(1)) { - (Some(vla), Some(idx)) => { - let actual_vla = extract_actual_parameter(vla); - let actual_idx = extract_actual_parameter(idx); + // TODO: consider adding validation for consts and enums once https://github.com/PLC-lang/rusty/issues/847 has been implemented + if let AstStatement::Literal(AstLiteral::Integer(dimension_idx)) = actual_idx.get_stmt() { + let dimension_idx = *dimension_idx as usize; - let idx_type = annotations.get_type_or_void(actual_idx, index); + let Some(n_dimensions) = + annotations.get_type_or_void(actual_vla, index).get_type_information().get_dimension_count() + else { + // not a vla, validated via type nature + return; + }; - if !idx_type.has_nature(TypeNature::Int, index) { + if dimension_idx < 1 || dimension_idx > n_dimensions { validator.push_diagnostic( - Diagnostic::new(format!( - "Invalid type nature for generic argument. {} is no {}", - idx_type.get_name(), - TypeNature::Int - )) - .with_error_code("E062") - .with_location(actual_idx), + Diagnostic::new("Index out of bound").with_error_code("E046").with_location(operator), ) } - - // TODO: consider adding validation for consts and enums once https://github.com/PLC-lang/rusty/issues/847 has been implemented - if let AstStatement::Literal(AstLiteral::Integer(dimension_idx)) = actual_idx.get_stmt() { - let dimension_idx = *dimension_idx as usize; - - let Some(n_dimensions) = annotations - .get_type_or_void(actual_vla, index) - .get_type_information() - .get_dimension_count() - else { - // not a vla, validated via type nature - return; - }; - - if dimension_idx < 1 || dimension_idx > n_dimensions { - validator.push_diagnostic( - Diagnostic::new("Index out of bound").with_error_code("E046").with_location(operator), - ) - } - }; - } - (Some(_), None) => validator.push_diagnostic(Diagnostic::invalid_argument_count(2, 1, operator)), - _ => unreachable!(), + }; + } else { + validator.push_diagnostic(Diagnostic::invalid_argument_count(2, params.len(), operator)); } } From bfc95e28bb047b0e98cef816429414c2ec8bc7e6 Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Tue, 23 Sep 2025 10:46:51 +0200 Subject: [PATCH 10/27] more refactor --- src/builtins.rs | 155 +++++++++++++++++++----------------------------- 1 file changed, 60 insertions(+), 95 deletions(-) diff --git a/src/builtins.rs b/src/builtins.rs index 0f6cd34e1f2..0239ae62061 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -898,108 +898,73 @@ fn generate_variable_length_array_bound_function<'ink>( let llvm = generator.llvm; let builder = &generator.llvm.builder; - let (actual_first_param, actual_second_param) = if params.len() == 2 { - let first_is_assignment = matches!(params[0].get_stmt(), AstStatement::Assignment(_)); - let second_is_assignment = matches!(params[1].get_stmt(), AstStatement::Assignment(_)); - - if first_is_assignment && second_is_assignment { - // Named arguments - extract by parameter name - let mut vla_expr = None; - let mut dim_expr = None; + if let &[vla, dim] = params { + let actual_vla = extract_actual_parameter(vla); + let actual_dim = extract_actual_parameter(dim); + + let data_type_information = + generator.annotations.get_type_or_void(actual_vla, generator.index).get_type_information(); + + // TODO: most of the codegen errors should already be caught during validation. + // once we abort codegen on critical errors, revisit and change to unreachable where possible + if !data_type_information.is_vla() { + return Err(Diagnostic::codegen_error( + format!("Expected VLA type, received {}", data_type_information.get_name()), + location, + )); + }; - for param in params.iter() { - if let AstStatement::Assignment(assignment) = param.get_stmt() { - if let AstStatement::ReferenceExpr(ref_expr) = assignment.left.get_stmt() { - if let ast::ReferenceAccess::Member(identifier) = &ref_expr.access { - if let Some(identifier_name) = identifier.get_flat_reference_name() { - match identifier_name { - "arr" => vla_expr = Some(assignment.right.as_ref()), - "dim" => dim_expr = Some(assignment.right.as_ref()), - _ => {} - } - } - } - } - } - } + let vla = generator.generate_lvalue(actual_vla).unwrap(); + let dim = builder.build_struct_gep(vla, 1, "dim").unwrap(); - match (vla_expr, dim_expr) { - (Some(vla), Some(dim)) => (vla, dim), - _ => { - return Err(Diagnostic::codegen_error("Invalid structure for named arguments", location)) - } + let accessor = match actual_dim.get_stmt() { + // e.g. LOWER_BOUND(arr, 1) + AstStatement::Literal(kind) => { + let AstLiteral::Integer(value) = kind else { + let Some(type_name) = get_literal_actual_signed_type_name(kind, false) else { + unreachable!("type cannot be VOID") + }; + return Err(Diagnostic::codegen_error( + format!("Invalid literal type. Expected INT type, received {type_name} type"), + location, + )); + }; + // array offset start- and end-values are adjacent values in a flattened array -> 2 values per dimension, so in order + // to read the correct values, the given index needs to be doubled. Additionally, the value is adjusted for 0-indexing. + let offset = if is_lower { (value - 1) as u64 * 2 } else { (value - 1) as u64 * 2 + 1 }; + llvm.i32_type().const_int(offset, false) } - } else { - (extract_actual_parameter(params[0]), extract_actual_parameter(params[1])) - } - } else { - return Err(Diagnostic::codegen_error( - format!("Invalid parameter count. Expected 2 but got {}", params.len()), - location, - )); - }; - - let data_type_information = - generator.annotations.get_type_or_void(actual_first_param, generator.index).get_type_information(); - - // TODO: most of the codegen errors should already be caught during validation. - // once we abort codegen on critical errors, revisit and change to unreachable where possible - if !data_type_information.is_vla() { - return Err(Diagnostic::codegen_error( - format!("Expected VLA type, received {}", data_type_information.get_name()), - location, - )); - }; - - let vla = generator.generate_lvalue(actual_first_param).unwrap(); - let dim = builder.build_struct_gep(vla, 1, "dim").unwrap(); - - let accessor = match actual_second_param.get_stmt() { - // e.g. LOWER_BOUND(arr, 1) - AstStatement::Literal(kind) => { - let AstLiteral::Integer(value) = kind else { - let Some(type_name) = get_literal_actual_signed_type_name(kind, false) else { - unreachable!("type cannot be VOID") + // e.g. LOWER_BOUND(arr, idx + 3) + _ => { + let expression_value = generator.generate_expression(actual_dim)?; + if !expression_value.is_int_value() { + todo!() }; - return Err(Diagnostic::codegen_error( - format!("Invalid literal type. Expected INT type, received {type_name} type"), - location, - )); - }; - // array offset start- and end-values are adjacent values in a flattened array -> 2 values per dimension, so in order - // to read the correct values, the given index needs to be doubled. Additionally, the value is adjusted for 0-indexing. - let offset = if is_lower { (value - 1) as u64 * 2 } else { (value - 1) as u64 * 2 + 1 }; - llvm.i32_type().const_int(offset, false) - } - // e.g. LOWER_BOUND(arr, idx + 3) - _ => { - let expression_value = generator.generate_expression(actual_second_param)?; - if !expression_value.is_int_value() { - todo!() - }; - // this operation mirrors the offset calculation of literal ints, but at runtime - let offset = builder.build_int_mul( - llvm.i32_type().const_int(2, false), - builder.build_int_sub( - expression_value.into_int_value(), - llvm.i32_type().const_int(1, false), + // this operation mirrors the offset calculation of literal ints, but at runtime + let offset = builder.build_int_mul( + llvm.i32_type().const_int(2, false), + builder.build_int_sub( + expression_value.into_int_value(), + llvm.i32_type().const_int(1, false), + "", + ), "", - ), - "", - ); - if !is_lower { - builder.build_int_add(offset, llvm.i32_type().const_int(1, false), "") - } else { - offset + ); + if !is_lower { + builder.build_int_add(offset, llvm.i32_type().const_int(1, false), "") + } else { + offset + } } - } - }; - - let gep_bound = - unsafe { llvm.builder.build_in_bounds_gep(dim, &[llvm.i32_type().const_zero(), accessor], "") }; - let bound = llvm.builder.build_load(gep_bound, ""); + }; + let gep_bound = + unsafe { llvm.builder.build_in_bounds_gep(dim, &[llvm.i32_type().const_zero(), accessor], "") }; + let bound = llvm.builder.build_load(gep_bound, ""); - Ok(ExpressionValue::RValue(bound)) + Ok(ExpressionValue::RValue(bound)) + } else { + Err(Diagnostic::codegen_error("Invalid signature for LOWER_BOUND/UPPER_BOUND", location)) + } } type AnnotationFunction = fn(&mut TypeAnnotator, &AstNode, &AstNode, Option<&AstNode>, VisitorContext); From 9263a529a9d37422b15c25b0eb660d81ec037a5b Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Tue, 23 Sep 2025 11:01:42 +0200 Subject: [PATCH 11/27] support div and sub --- src/builtins.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/builtins.rs b/src/builtins.rs index 0239ae62061..e9dbd94347d 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -623,7 +623,8 @@ fn validate_types( ) { let Some(params) = parameters else { return }; - let types = flatten_expression_list(params); + let types: Vec<_> = + flatten_expression_list(params).into_iter().map(|it| extract_actual_parameter(it).clone()).collect(); let mut types = types.iter().peekable(); while let Some(left) = types.next() { @@ -722,7 +723,10 @@ fn annotate_arithmetic_function( ctx: VisitorContext, operation: Operator, ) { - let params_flattened = flatten_expression_list(parameters); + let params_flattened: Vec<_> = flatten_expression_list(parameters) + .into_iter() + .map(|it| extract_actual_parameter(it).clone()) + .collect(); if params_flattened.iter().any(|it| { !annotator .annotation_map From e6886eb663a455b8fb7fcf1bf2c770c77cae5001 Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Tue, 23 Sep 2025 11:04:10 +0200 Subject: [PATCH 12/27] more tests --- .../tests/statement_validation_tests.rs | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/validation/tests/statement_validation_tests.rs b/src/validation/tests/statement_validation_tests.rs index bb44fccae86..ba7b7562212 100644 --- a/src/validation/tests/statement_validation_tests.rs +++ b/src/validation/tests/statement_validation_tests.rs @@ -723,6 +723,84 @@ fn ref_builtin_function_reports_invalid_param_count() { assert_snapshot!(diagnostics); } +#[test] +fn builtin_functions_named_arguments_invalid_parameter_names() { + let diagnostics = parse_and_validate_buffered( + " + FUNCTION main : DINT + VAR + arr: ARRAY[0..5] OF INT; + a, b: INT; + sel: BOOL; + END_VAR + // SEL with wrong parameter names + a := SEL(WRONG := sel, IN0 := a, IN1 := b); + a := SEL(G := sel, INVALID := a, IN1 := b); + + // MOVE with wrong parameter names + MOVE(SOURCE := arr, INVALID := arr); + MOVE(WRONG := arr, DEST := arr); + + // SIZEOF with wrong parameter name + a := SIZEOF(INVALID := arr); + + // ADR with wrong parameter name + ADR(WRONG := arr); + + // REF with wrong parameter name + REF(INVALID := arr); + + // UPPER_BOUND with wrong parameter names + a := UPPER_BOUND(INVALID := arr, DIM := 1); + a := UPPER_BOUND(ARR := arr, WRONG := 1); + + // LOWER_BOUND with wrong parameter names + a := LOWER_BOUND(WRONG := arr, DIM := 1); + a := LOWER_BOUND(ARR := arr, INVALID := 1); + END_FUNCTION + ", + ); + + assert_snapshot!(diagnostics); +} + +#[test] +fn builtin_functions_named_arguments_type_mismatches() { + let diagnostics = parse_and_validate_buffered( + " + FUNCTION main : DINT + VAR + arr: ARRAY[0..5] OF INT; + a, b: INT; + sel: BOOL; + str_val: STRING; + END_VAR + // SEL with type mismatches + a := SEL(G := str_val, IN0 := a, IN1 := b); // wrong type for selector + + // MOVE with type mismatches + MOVE(IN := a, OUT := str_val); // incompatible array types + + // SIZEOF - should work with any type, so no type mismatch possible + + // ADR - should work with any type, so no type mismatch possible + + // REF - should work with any type, so no type mismatch possible + + // UPPER_BOUND with type mismatches + a := UPPER_BOUND(ARR := a, DIM := 1); // not an array + a := UPPER_BOUND(ARR := arr, DIM := str_val); // wrong type for dimension + + // LOWER_BOUND with type mismatches + a := LOWER_BOUND(ARR := str_val, DIM := 1); // not an array + a := LOWER_BOUND(ARR := arr, DIM := sel); // wrong type for dimension + END_FUNCTION + ", + ); + + assert_snapshot!(diagnostics); +} + #[test] fn address_of_operations() { let diagnostics = parse_and_validate_buffered( From b96b01a5e97e03e232c1a789c6c9e7dde898e391 Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Tue, 23 Sep 2025 12:38:01 +0200 Subject: [PATCH 13/27] even more refactor --- src/builtins.rs | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/builtins.rs b/src/builtins.rs index e9dbd94347d..e086f6f0652 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -723,11 +723,27 @@ fn annotate_arithmetic_function( ctx: VisitorContext, operation: Operator, ) { - let params_flattened: Vec<_> = flatten_expression_list(parameters) + let params_extracted_flattened: Vec<_> = flatten_expression_list(parameters) .into_iter() .map(|it| extract_actual_parameter(it).clone()) .collect(); - if params_flattened.iter().any(|it| { + + let params = flatten_expression_list(parameters); + // loop params to add type hints for named arguments + for (original_param, actual_param) in params.iter().zip(params_extracted_flattened.iter()) { + // check if it's a named argument + if matches!(original_param.get_stmt(), AstStatement::Assignment(_)) { + let param_type = annotator + .annotation_map + .get_type_or_void(actual_param, annotator.index) + .get_type_information() + .get_name() + .to_owned(); + annotator.annotation_map.annotate_type_hint(actual_param, StatementAnnotation::value(param_type)); + } + } + + if params_extracted_flattened.iter().any(|it| { !annotator .annotation_map .get_type_or_void(it, annotator.index) @@ -742,11 +758,12 @@ fn annotate_arithmetic_function( // find biggest type to later annotate it as type hint. this is done in a closure to avoid a borrow-checker tantrum later on due to // mutable and immutable borrow of TypeAnnotator let find_biggest_param_type_name = |annotator: &TypeAnnotator| { - let mut bigger = annotator - .annotation_map - .get_type_or_void(params_flattened.first().expect("must have this parameter"), annotator.index); + let mut bigger = annotator.annotation_map.get_type_or_void( + params_extracted_flattened.first().expect("must have this parameter"), + annotator.index, + ); - for param in params_flattened.iter().skip(1) { + for param in params_extracted_flattened.iter().skip(1) { let right_type = annotator.annotation_map.get_type_or_void(param, annotator.index); bigger = get_bigger_type(bigger, right_type, annotator.index); } @@ -758,8 +775,8 @@ fn annotate_arithmetic_function( // create nested AstStatement::BinaryExpression for each parameter, such that // ADD(a, b, c, d) ends up as (((a + b) + c) + d) - let left = (*params_flattened.first().expect("Must exist")).clone(); - let new_statement = params_flattened.into_iter().skip(1).fold(left, |left, right| { + let left = (*params_extracted_flattened.first().expect("Must exist")).clone(); + let new_statement = params_extracted_flattened.into_iter().skip(1).fold(left, |left, right| { AstFactory::create_binary_expression(left, operation, right.clone(), ctx.id_provider.next_id()) }); From b68663643093826f3536fa67df71f7cf7695a924 Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Tue, 23 Sep 2025 10:52:42 +0000 Subject: [PATCH 14/27] add tests for sub/div --- src/codegen/tests/expression_tests.rs | 34 +++++++++++++++++++ ...sts__builtin_div_with_named_arguments.snap | 23 +++++++++++++ ...sts__builtin_sub_with_named_arguments.snap | 23 +++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_div_with_named_arguments.snap create mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_sub_with_named_arguments.snap diff --git a/src/codegen/tests/expression_tests.rs b/src/codegen/tests/expression_tests.rs index 13d26102ef2..8e82d4e1275 100644 --- a/src/codegen/tests/expression_tests.rs +++ b/src/codegen/tests/expression_tests.rs @@ -878,6 +878,40 @@ fn builtin_div_mixed() { filtered_assert_snapshot!(res); } +#[test] +fn builtin_div_with_named_arguments() { + let src = r#" + FUNCTION main : DINT + VAR + x : DINT := 20; + y : DINT := 4; + END_VAR + DIV(IN1 := x, IN2 := y); + END_FUNCTION + "#; + + let res = codegen(src); + + filtered_assert_snapshot!(res); +} + +#[test] +fn builtin_sub_with_named_arguments() { + let src = r#" + FUNCTION main : DINT + VAR + x : DINT := 20; + y : DINT := 4; + END_VAR + SUB(IN1 := x, IN2 := y); + END_FUNCTION + "#; + + let res = codegen(src); + + filtered_assert_snapshot!(res); +} + #[test] fn global_namespace_operator() { let src = r#" diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_div_with_named_arguments.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_div_with_named_arguments.snap new file mode 100644 index 00000000000..6fb2ea21bee --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_div_with_named_arguments.snap @@ -0,0 +1,23 @@ +--- +source: src/codegen/tests/expression_tests.rs +expression: res +--- +; ModuleID = '' +source_filename = "" +target datalayout = "[filtered]" +target triple = "[filtered]" + +define i32 @main() { +entry: + %main = alloca i32, align 4 + %x = alloca i32, align 4 + %y = alloca i32, align 4 + store i32 20, i32* %x, align 4 + store i32 4, i32* %y, align 4 + store i32 0, i32* %main, align 4 + %load_x = load i32, i32* %x, align 4 + %load_y = load i32, i32* %y, align 4 + %tmpVar = sdiv i32 %load_x, %load_y + %main_ret = load i32, i32* %main, align 4 + ret i32 %main_ret +} diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_sub_with_named_arguments.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_sub_with_named_arguments.snap new file mode 100644 index 00000000000..da09e11f83a --- /dev/null +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_sub_with_named_arguments.snap @@ -0,0 +1,23 @@ +--- +source: src/codegen/tests/expression_tests.rs +expression: res +--- +; ModuleID = '' +source_filename = "" +target datalayout = "[filtered]" +target triple = "[filtered]" + +define i32 @main() { +entry: + %main = alloca i32, align 4 + %x = alloca i32, align 4 + %y = alloca i32, align 4 + store i32 20, i32* %x, align 4 + store i32 4, i32* %y, align 4 + store i32 0, i32* %main, align 4 + %load_x = load i32, i32* %x, align 4 + %load_y = load i32, i32* %y, align 4 + %tmpVar = sub i32 %load_x, %load_y + %main_ret = load i32, i32* %main, align 4 + ret i32 %main_ret +} From 3ce4de59875460c7f9653b85d4b5c90626585f22 Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Tue, 23 Sep 2025 10:53:23 +0000 Subject: [PATCH 15/27] another refactor --- src/builtins.rs | 76 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 25 deletions(-) diff --git a/src/builtins.rs b/src/builtins.rs index e086f6f0652..a4fe185cb7e 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -723,30 +723,29 @@ fn annotate_arithmetic_function( ctx: VisitorContext, operation: Operator, ) { - let params_extracted_flattened: Vec<_> = flatten_expression_list(parameters) - .into_iter() - .map(|it| extract_actual_parameter(it).clone()) - .collect(); - let params = flatten_expression_list(parameters); - // loop params to add type hints for named arguments - for (original_param, actual_param) in params.iter().zip(params_extracted_flattened.iter()) { - // check if it's a named argument - if matches!(original_param.get_stmt(), AstStatement::Assignment(_)) { + let params_extracted: Vec<_> = + params.iter().map(|param| extract_actual_parameter(param).clone()).collect(); + + // Add type hints (only named arguments) + params + .iter() + .zip(¶ms_extracted) + .filter(|(it, _)| matches!(it.get_stmt(), AstStatement::Assignment(_))) + .for_each(|(_, extracted)| { let param_type = annotator .annotation_map - .get_type_or_void(actual_param, annotator.index) + .get_type_or_void(extracted, annotator.index) .get_type_information() .get_name() .to_owned(); - annotator.annotation_map.annotate_type_hint(actual_param, StatementAnnotation::value(param_type)); - } - } + annotator.annotation_map.annotate_type_hint(extracted, StatementAnnotation::value(param_type)); + }); - if params_extracted_flattened.iter().any(|it| { + if params_extracted.iter().any(|param| { !annotator .annotation_map - .get_type_or_void(it, annotator.index) + .get_type_or_void(param, annotator.index) .has_nature(TypeNature::Num, annotator.index) }) { // we are trying to call this function with a non-numerical type, so we redirect back to the resolver @@ -758,12 +757,11 @@ fn annotate_arithmetic_function( // find biggest type to later annotate it as type hint. this is done in a closure to avoid a borrow-checker tantrum later on due to // mutable and immutable borrow of TypeAnnotator let find_biggest_param_type_name = |annotator: &TypeAnnotator| { - let mut bigger = annotator.annotation_map.get_type_or_void( - params_extracted_flattened.first().expect("must have this parameter"), - annotator.index, - ); + let mut bigger = annotator + .annotation_map + .get_type_or_void(params_extracted.first().expect("must have this parameter"), annotator.index); - for param in params_extracted_flattened.iter().skip(1) { + for param in params_extracted.iter().skip(1) { let right_type = annotator.annotation_map.get_type_or_void(param, annotator.index); bigger = get_bigger_type(bigger, right_type, annotator.index); } @@ -775,8 +773,8 @@ fn annotate_arithmetic_function( // create nested AstStatement::BinaryExpression for each parameter, such that // ADD(a, b, c, d) ends up as (((a + b) + c) + d) - let left = (*params_extracted_flattened.first().expect("Must exist")).clone(); - let new_statement = params_extracted_flattened.into_iter().skip(1).fold(left, |left, right| { + let left = (*params_extracted.first().expect("Must exist")).clone(); + let new_statement = params_extracted.into_iter().skip(1).fold(left, |left, right| { AstFactory::create_binary_expression(left, operation, right.clone(), ctx.id_provider.next_id()) }); @@ -895,9 +893,37 @@ fn validate_argument_count( } } -/// Helper function to extract the actual parameter from Assignment nodes when dealing with named arguments -/// For named arguments like `func(param := value)`, the AST contains an Assignment node where we need -/// to extract the right-hand side (the actual value). For positional arguments, use the parameter directly. +/// Extracts the actual parameter value from either named or positional arguments. +/// +/// This function is essential for supporting named arguments in builtin functions. When a function +/// is called with named arguments like `SEL(G := TRUE, IN0 := a, IN1 := b)`, the AST parser creates +/// Assignment nodes where the left side is the parameter name and the right side is the actual value. +/// For positional arguments, the parameter node directly contains the value. +/// +/// # Parameters +/// * `param` - The parameter node from the AST, which can be either: +/// - An Assignment node for named arguments (e.g., `param := value`) +/// - A direct expression node for positional arguments +/// +/// # Returns +/// * For named arguments: Returns the right-hand side of the assignment (the actual value) +/// * For positional arguments: Returns the parameter node directly +/// +/// # Examples +/// ```rust +/// // For named argument: func(param := 42) +/// // param points to Assignment { left: "param", right: "42" } +/// // extract_actual_parameter(param) returns pointer to "42" +/// +/// // For positional argument: func(42) +/// // param points directly to "42" +/// // extract_actual_parameter(param) returns pointer to "42" +/// ``` +/// +/// # Usage in Builtin Functions +/// All builtin functions that support named arguments should use this function to extract +/// parameter values before processing them. This ensures consistent handling of both named +/// and positional argument styles. fn extract_actual_parameter(param: &AstNode) -> &AstNode { if let AstStatement::Assignment(assignment) = param.get_stmt() { // Named argument: extract the actual value from the right side of the assignment From a04564783b638fb02633e017f67394efbe497fa2 Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Tue, 23 Sep 2025 10:54:47 +0000 Subject: [PATCH 16/27] add lit test for sub/div --- tests/lit/single/builtin-named-arguments/main.st | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/lit/single/builtin-named-arguments/main.st b/tests/lit/single/builtin-named-arguments/main.st index 4e3114f46ca..33c9713d994 100644 --- a/tests/lit/single/builtin-named-arguments/main.st +++ b/tests/lit/single/builtin-named-arguments/main.st @@ -30,6 +30,10 @@ VAR iVar1 : INT := 5; myarray : ARRAY [0..9] OF BYTE := [0,1,2,3,4,5,6,7,8,9]; + + dividend : DINT := 20; + divisor : DINT := 4; + subtrahend : DINT := 7; END_VAR // MOVE @@ -72,6 +76,18 @@ END_VAR result := (piAddress1 = piAddress2); printf('%d$N', result); // CHECK: 1 + // DIV + result := DIV(IN1 := dividend, IN2 := divisor); + printf('%d$N', result); // CHECK: 5 + result := DIV(dividend, divisor); + printf('%d$N', result); // CHECK: 5 + + // SUB + result := SUB(IN1 := dividend, IN2 := subtrahend); + printf('%d$N', result); // CHECK: 13 + result := SUB(dividend, subtrahend); + printf('%d$N', result); // CHECK: 13 + test_bounds(myarray); main := 0; From 3cac9f64b04ee46bd48766a2fc298698d889eaf8 Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Wed, 24 Sep 2025 05:57:34 +0000 Subject: [PATCH 17/27] add missing snapshots --- ...med_arguments_invalid_parameter_names.snap | 177 ++++++++++++++++++ ...tions_named_arguments_type_mismatches.snap | 81 ++++++++ 2 files changed, 258 insertions(+) create mode 100644 src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__builtin_functions_named_arguments_invalid_parameter_names.snap create mode 100644 src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__builtin_functions_named_arguments_type_mismatches.snap diff --git a/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__builtin_functions_named_arguments_invalid_parameter_names.snap b/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__builtin_functions_named_arguments_invalid_parameter_names.snap new file mode 100644 index 00000000000..9a640fa75ca --- /dev/null +++ b/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__builtin_functions_named_arguments_invalid_parameter_names.snap @@ -0,0 +1,177 @@ +--- +source: src/validation/tests/statement_validation_tests.rs +expression: diagnostics +--- +error[E089]: Invalid call parameters + ┌─ :9:22 + │ +9 │ a := SEL(WRONG := sel, IN0 := a, IN1 := b); + │ ^^^^^^^^^^^^ Invalid call parameters + +error[E048]: Could not resolve reference to WRONG + ┌─ :9:22 + │ +9 │ a := SEL(WRONG := sel, IN0 := a, IN1 := b); + │ ^^^^^ Could not resolve reference to WRONG + +error[E089]: Invalid call parameters + ┌─ :10:32 + │ +10 │ a := SEL(G := sel, INVALID := a, IN1 := b); + │ ^^^^^^^^^^^^ Invalid call parameters + +error[E048]: Could not resolve reference to INVALID + ┌─ :10:32 + │ +10 │ a := SEL(G := sel, INVALID := a, IN1 := b); + │ ^^^^^^^ Could not resolve reference to INVALID + +error[E089]: Invalid call parameters + ┌─ :13:18 + │ +13 │ MOVE(SOURCE := arr, INVALID := arr); + │ ^^^^^^^^^^^^^ Invalid call parameters + +error[E048]: Could not resolve reference to SOURCE + ┌─ :13:18 + │ +13 │ MOVE(SOURCE := arr, INVALID := arr); + │ ^^^^^^ Could not resolve reference to SOURCE + +error[E089]: Invalid call parameters + ┌─ :14:18 + │ +14 │ MOVE(WRONG := arr, DEST := arr); + │ ^^^^^^^^^^^^ Invalid call parameters + +error[E048]: Could not resolve reference to WRONG + ┌─ :14:18 + │ +14 │ MOVE(WRONG := arr, DEST := arr); + │ ^^^^^ Could not resolve reference to WRONG + +error[E089]: Invalid call parameters + ┌─ :17:25 + │ +17 │ a := SIZEOF(INVALID := arr); + │ ^^^^^^^^^^^^^^ Invalid call parameters + +error[E048]: Could not resolve reference to INVALID + ┌─ :17:25 + │ +17 │ a := SIZEOF(INVALID := arr); + │ ^^^^^^^ Could not resolve reference to INVALID + +warning[E067]: Implicit downcast from 'ULINT' to 'INT'. + ┌─ :17:18 + │ +17 │ a := SIZEOF(INVALID := arr); + │ ^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'ULINT' to 'INT'. + +error[E089]: Invalid call parameters + ┌─ :20:17 + │ +20 │ ADR(WRONG := arr); + │ ^^^^^^^^^^^^ Invalid call parameters + +error[E048]: Could not resolve reference to WRONG + ┌─ :20:17 + │ +20 │ ADR(WRONG := arr); + │ ^^^^^ Could not resolve reference to WRONG + +error[E089]: Invalid call parameters + ┌─ :23:17 + │ +23 │ REF(INVALID := arr); + │ ^^^^^^^^^^^^^^ Invalid call parameters + +error[E048]: Could not resolve reference to INVALID + ┌─ :23:17 + │ +23 │ REF(INVALID := arr); + │ ^^^^^^^ Could not resolve reference to INVALID + +error[E089]: Invalid call parameters + ┌─ :26:30 + │ +26 │ a := UPPER_BOUND(INVALID := arr, DIM := 1); + │ ^^^^^^^^^^^^^^ Invalid call parameters + +error[E048]: Could not resolve reference to INVALID + ┌─ :26:30 + │ +26 │ a := UPPER_BOUND(INVALID := arr, DIM := 1); + │ ^^^^^^^ Could not resolve reference to INVALID + +warning[E067]: Implicit downcast from 'DINT' to 'INT'. + ┌─ :26:18 + │ +26 │ a := UPPER_BOUND(INVALID := arr, DIM := 1); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. + +error[E037]: Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' + ┌─ :27:30 + │ +27 │ a := UPPER_BOUND(ARR := arr, WRONG := 1); + │ ^^^^^^^^^^ Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' + +error[E089]: Invalid call parameters + ┌─ :27:42 + │ +27 │ a := UPPER_BOUND(ARR := arr, WRONG := 1); + │ ^^^^^^^^^^ Invalid call parameters + +error[E048]: Could not resolve reference to WRONG + ┌─ :27:42 + │ +27 │ a := UPPER_BOUND(ARR := arr, WRONG := 1); + │ ^^^^^ Could not resolve reference to WRONG + +warning[E067]: Implicit downcast from 'DINT' to 'INT'. + ┌─ :27:18 + │ +27 │ a := UPPER_BOUND(ARR := arr, WRONG := 1); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. + +error[E089]: Invalid call parameters + ┌─ :30:30 + │ +30 │ a := LOWER_BOUND(WRONG := arr, DIM := 1); + │ ^^^^^^^^^^^^ Invalid call parameters + +error[E048]: Could not resolve reference to WRONG + ┌─ :30:30 + │ +30 │ a := LOWER_BOUND(WRONG := arr, DIM := 1); + │ ^^^^^ Could not resolve reference to WRONG + +warning[E067]: Implicit downcast from 'DINT' to 'INT'. + ┌─ :30:18 + │ +30 │ a := LOWER_BOUND(WRONG := arr, DIM := 1); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. + +error[E037]: Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' + ┌─ :31:30 + │ +31 │ a := LOWER_BOUND(ARR := arr, INVALID := 1); + │ ^^^^^^^^^^ Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' + +error[E089]: Invalid call parameters + ┌─ :31:42 + │ +31 │ a := LOWER_BOUND(ARR := arr, INVALID := 1); + │ ^^^^^^^^^^^^ Invalid call parameters + +error[E048]: Could not resolve reference to INVALID + ┌─ :31:42 + │ +31 │ a := LOWER_BOUND(ARR := arr, INVALID := 1); + │ ^^^^^^^ Could not resolve reference to INVALID + +warning[E067]: Implicit downcast from 'DINT' to 'INT'. + ┌─ :31:18 + │ +31 │ a := LOWER_BOUND(ARR := arr, INVALID := 1); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. diff --git a/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__builtin_functions_named_arguments_type_mismatches.snap b/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__builtin_functions_named_arguments_type_mismatches.snap new file mode 100644 index 00000000000..11c1b658c84 --- /dev/null +++ b/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__builtin_functions_named_arguments_type_mismatches.snap @@ -0,0 +1,81 @@ +--- +source: src/validation/tests/statement_validation_tests.rs +expression: diagnostics +--- +error[E037]: Invalid assignment: cannot assign 'STRING' to 'BOOL' + ┌─ :10:22 + │ +10 │ a := SEL(G := str_val, IN0 := a, IN1 := b); // wrong type for selector + │ ^^^^^^^^^^^^ Invalid assignment: cannot assign 'STRING' to 'BOOL' + +error[E089]: Invalid call parameters + ┌─ :13:27 + │ +13 │ MOVE(IN := a, OUT := str_val); // incompatible array types + │ ^^^^^^^^^^^^^^ Invalid call parameters + +error[E048]: Could not resolve reference to OUT + ┌─ :13:27 + │ +13 │ MOVE(IN := a, OUT := str_val); // incompatible array types + │ ^^^ Could not resolve reference to OUT + +error[E037]: Invalid assignment: cannot assign 'INT' to 'VARIABLE LENGTH ARRAY' + ┌─ :22:30 + │ +22 │ a := UPPER_BOUND(ARR := a, DIM := 1); // not an array + │ ^^^^^^^^ Invalid assignment: cannot assign 'INT' to 'VARIABLE LENGTH ARRAY' + +warning[E067]: Implicit downcast from 'DINT' to 'INT'. + ┌─ :22:18 + │ +22 │ a := UPPER_BOUND(ARR := a, DIM := 1); // not an array + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. + +error[E062]: Invalid type nature for generic argument. STRING is no ANY_INT + ┌─ :23:49 + │ +23 │ a := UPPER_BOUND(ARR := arr, DIM := str_val); // wrong type for dimension + │ ^^^^^^^ Invalid type nature for generic argument. STRING is no ANY_INT + +error[E037]: Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' + ┌─ :23:30 + │ +23 │ a := UPPER_BOUND(ARR := arr, DIM := str_val); // wrong type for dimension + │ ^^^^^^^^^^ Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' + +warning[E067]: Implicit downcast from 'DINT' to 'INT'. + ┌─ :23:18 + │ +23 │ a := UPPER_BOUND(ARR := arr, DIM := str_val); // wrong type for dimension + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. + +error[E037]: Invalid assignment: cannot assign 'STRING' to 'VARIABLE LENGTH ARRAY' + ┌─ :26:30 + │ +26 │ a := LOWER_BOUND(ARR := str_val, DIM := 1); // not an array + │ ^^^^^^^^^^^^^^ Invalid assignment: cannot assign 'STRING' to 'VARIABLE LENGTH ARRAY' + +warning[E067]: Implicit downcast from 'DINT' to 'INT'. + ┌─ :26:18 + │ +26 │ a := LOWER_BOUND(ARR := str_val, DIM := 1); // not an array + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. + +error[E062]: Invalid type nature for generic argument. BOOL is no ANY_INT + ┌─ :27:49 + │ +27 │ a := LOWER_BOUND(ARR := arr, DIM := sel); // wrong type for dimension + │ ^^^ Invalid type nature for generic argument. BOOL is no ANY_INT + +error[E037]: Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' + ┌─ :27:30 + │ +27 │ a := LOWER_BOUND(ARR := arr, DIM := sel); // wrong type for dimension + │ ^^^^^^^^^^ Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' + +warning[E067]: Implicit downcast from 'DINT' to 'INT'. + ┌─ :27:18 + │ +27 │ a := LOWER_BOUND(ARR := arr, DIM := sel); // wrong type for dimension + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. From c8777f379dc69fda9b6a01d60c6a72e99f6d6b12 Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Wed, 24 Sep 2025 06:50:31 +0000 Subject: [PATCH 18/27] add/modify comments --- src/builtins.rs | 35 +++-------------------------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/src/builtins.rs b/src/builtins.rs index a4fe185cb7e..a1e6af9d919 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -268,7 +268,6 @@ lazy_static! { generic_name_resolver: no_generic_name_resolver, code : |generator, params, location| { if params.len() == 1 { - // Handle named arguments by extracting the actual parameter let actual_param = extract_actual_parameter(params[0]); generator.generate_expression(actual_param).map(ExpressionValue::RValue) } else { @@ -893,37 +892,9 @@ fn validate_argument_count( } } -/// Extracts the actual parameter value from either named or positional arguments. -/// -/// This function is essential for supporting named arguments in builtin functions. When a function -/// is called with named arguments like `SEL(G := TRUE, IN0 := a, IN1 := b)`, the AST parser creates -/// Assignment nodes where the left side is the parameter name and the right side is the actual value. -/// For positional arguments, the parameter node directly contains the value. -/// -/// # Parameters -/// * `param` - The parameter node from the AST, which can be either: -/// - An Assignment node for named arguments (e.g., `param := value`) -/// - A direct expression node for positional arguments -/// -/// # Returns -/// * For named arguments: Returns the right-hand side of the assignment (the actual value) -/// * For positional arguments: Returns the parameter node directly -/// -/// # Examples -/// ```rust -/// // For named argument: func(param := 42) -/// // param points to Assignment { left: "param", right: "42" } -/// // extract_actual_parameter(param) returns pointer to "42" -/// -/// // For positional argument: func(42) -/// // param points directly to "42" -/// // extract_actual_parameter(param) returns pointer to "42" -/// ``` -/// -/// # Usage in Builtin Functions -/// All builtin functions that support named arguments should use this function to extract -/// parameter values before processing them. This ensures consistent handling of both named -/// and positional argument styles. +/// Helper function to extract the actual parameter from Assignment nodes when dealing with named arguments +/// For named arguments like `func(param := value)`, the AST contains an Assignment node where we need +/// to extract the right-hand side (the actual value). For positional arguments, use the parameter directly. fn extract_actual_parameter(param: &AstNode) -> &AstNode { if let AstStatement::Assignment(assignment) = param.get_stmt() { // Named argument: extract the actual value from the right side of the assignment From 18053d87c45a0b1fc337ee2200d2ad327eee75e7 Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Wed, 24 Sep 2025 10:37:58 +0200 Subject: [PATCH 19/27] implement feedback --- src/builtins.rs | 1 + src/codegen/tests/expression_tests.rs | 432 +++++++++++++++++- ...sion_tests__builtin_function_call_adr.snap | 21 - ...function_call_adr_with_named_argument.snap | 20 - ...sion_tests__builtin_function_call_ref.snap | 21 - ...function_call_ref_with_named_argument.snap | 20 - ...med_arguments_invalid_parameter_names.snap | 177 ------- ...tions_named_arguments_type_mismatches.snap | 81 ---- .../tests/statement_validation_tests.rs | 369 ++++++++++++++- 9 files changed, 768 insertions(+), 374 deletions(-) delete mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_adr.snap delete mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_adr_with_named_argument.snap delete mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_ref.snap delete mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_ref_with_named_argument.snap delete mode 100644 src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__builtin_functions_named_arguments_invalid_parameter_names.snap delete mode 100644 src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__builtin_functions_named_arguments_type_mismatches.snap diff --git a/src/builtins.rs b/src/builtins.rs index a1e6af9d919..a9aaf00332f 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -727,6 +727,7 @@ fn annotate_arithmetic_function( params.iter().map(|param| extract_actual_parameter(param).clone()).collect(); // Add type hints (only named arguments) + // TODO: cant we just annotate all parameters? params .iter() .zip(¶ms_extracted) diff --git a/src/codegen/tests/expression_tests.rs b/src/codegen/tests/expression_tests.rs index 8e82d4e1275..e3695ea4bbf 100644 --- a/src/codegen/tests/expression_tests.rs +++ b/src/codegen/tests/expression_tests.rs @@ -291,7 +291,24 @@ fn builtin_function_call_adr() { ); // WHEN compiled // We expect the same behaviour as if REF was called, due to the assignee being a pointer - filtered_assert_snapshot!(result); + filtered_assert_snapshot!(result, @r#" + ; ModuleID = '' + source_filename = "" + target datalayout = "[filtered]" + target triple = "[filtered]" + + %main = type { i32*, i32 } + + @main_instance = global %main zeroinitializer + + define void @main(%main* %0) { + entry: + %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + store i32* %b, i32** %a, align 8 + ret void + } + "#); } #[test] @@ -310,7 +327,24 @@ fn builtin_function_call_adr_with_named_argument() { ); // WHEN compiled // We expect the same behaviour as if REF was called, due to the assignee being a pointer - filtered_assert_snapshot!(result); + filtered_assert_snapshot!(result, @r#" + ; ModuleID = '' + source_filename = "" + target datalayout = "[filtered]" + target triple = "[filtered]" + + %main = type { i32*, i32 } + + @main_instance = global %main zeroinitializer + + define void @main(%main* %0) { + entry: + %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + store i32* %b, i32** %a, align 8 + ret void + } + "#); } #[test] @@ -329,7 +363,24 @@ fn builtin_function_call_ref() { ); // WHEN compiled // We expect a direct conversion and subsequent assignment to pointer(no call) - filtered_assert_snapshot!(result); + filtered_assert_snapshot!(result, @r#" + ; ModuleID = '' + source_filename = "" + target datalayout = "[filtered]" + target triple = "[filtered]" + + %main = type { i32*, i32 } + + @main_instance = global %main zeroinitializer + + define void @main(%main* %0) { + entry: + %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + store i32* %b, i32** %a, align 8 + ret void + } + "#); } #[test] @@ -348,7 +399,24 @@ fn builtin_function_call_ref_with_named_argument() { ); // WHEN compiled // We expect a direct conversion and subsequent assignment to pointer(no call) - filtered_assert_snapshot!(result); + filtered_assert_snapshot!(result, @r#" + ; ModuleID = '' + source_filename = "" + target datalayout = "[filtered]" + target triple = "[filtered]" + + %main = type { i32*, i32 } + + @main_instance = global %main zeroinitializer + + define void @main(%main* %0) { + entry: + %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + store i32* %b, i32** %a, align 8 + ret void + } + "#); } #[test] @@ -390,7 +458,28 @@ fn builtin_function_call_sel() { END_PROGRAM", ); - filtered_assert_snapshot!(result); + filtered_assert_snapshot!(result, @r#" + ; ModuleID = '' + source_filename = "" + target datalayout = "[filtered]" + target triple = "[filtered]" + + %main = type { i32, i32, i32 } + + @main_instance = global %main zeroinitializer + + define void @main(%main* %0) { + entry: + %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + %c = getelementptr inbounds %main, %main* %0, i32 0, i32 2 + %load_b = load i32, i32* %b, align 4 + %load_c = load i32, i32* %c, align 4 + %1 = select i1 true, i32 %load_c, i32 %load_b + store i32 %1, i32* %a, align 4 + ret void + } + "#); } #[test] @@ -404,7 +493,28 @@ fn builtin_function_call_sel_with_named_argument() { END_PROGRAM", ); - filtered_assert_snapshot!(result); + filtered_assert_snapshot!(result, @r#" + ; ModuleID = '' + source_filename = "" + target datalayout = "[filtered]" + target triple = "[filtered]" + + %main = type { i32, i32, i32 } + + @main_instance = global %main zeroinitializer + + define void @main(%main* %0) { + entry: + %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + %c = getelementptr inbounds %main, %main* %0, i32 0, i32 2 + %load_b = load i32, i32* %b, align 4 + %load_c = load i32, i32* %c, align 4 + %1 = select i1 true, i32 %load_c, i32 %load_b + store i32 %1, i32* %a, align 4 + ret void + } + "#); } #[test] @@ -432,7 +542,25 @@ fn builtin_function_call_move() { END_PROGRAM", ); - filtered_assert_snapshot!(result); + filtered_assert_snapshot!(result, @r#" + ; ModuleID = '' + source_filename = "" + target datalayout = "[filtered]" + target triple = "[filtered]" + + %main = type { i32, i32 } + + @main_instance = global %main zeroinitializer + + define void @main(%main* %0) { + entry: + %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + %load_b = load i32, i32* %b, align 4 + store i32 %load_b, i32* %a, align 4 + ret void + } + "#); } #[test] @@ -446,7 +574,25 @@ fn builtin_function_call_move_with_named_argument() { END_PROGRAM", ); - filtered_assert_snapshot!(result); + filtered_assert_snapshot!(result, @r#" + ; ModuleID = '' + source_filename = "" + target datalayout = "[filtered]" + target triple = "[filtered]" + + %main = type { i32, i32 } + + @main_instance = global %main zeroinitializer + + define void @main(%main* %0) { + entry: + %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + %load_b = load i32, i32* %b, align 4 + store i32 %load_b, i32* %a, align 4 + ret void + } + "#); } #[test] @@ -461,7 +607,24 @@ fn builtin_function_call_sizeof() { END_PROGRAM", ); - filtered_assert_snapshot!(result); + filtered_assert_snapshot!(result, @r#" + ; ModuleID = '' + source_filename = "" + target datalayout = "[filtered]" + target triple = "[filtered]" + + %main = type { i32, i64 } + + @main_instance = global %main zeroinitializer + + define void @main(%main* %0) { + entry: + %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + store i32 ptrtoint (i64* getelementptr (i64, i64* null, i32 1) to i32), i32* %a, align 4 + ret void + } + "#); } #[test] @@ -476,7 +639,24 @@ fn builtin_function_call_sizeof_with_named_argument() { END_PROGRAM", ); - filtered_assert_snapshot!(result); + filtered_assert_snapshot!(result, @r#" + ; ModuleID = '' + source_filename = "" + target datalayout = "[filtered]" + target triple = "[filtered]" + + %main = type { i32, i64 } + + @main_instance = global %main zeroinitializer + + define void @main(%main* %0) { + entry: + %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + store i32 ptrtoint (i64* getelementptr (i64, i64* null, i32 1) to i32), i32* %a, align 4 + ret void + } + "#); } #[test] @@ -500,7 +680,52 @@ fn builtin_function_call_lower_bound() { ", ); - filtered_assert_snapshot!(result); + filtered_assert_snapshot!(result, @r#" + ; ModuleID = '' + source_filename = "" + target datalayout = "[filtered]" + target triple = "[filtered]" + + %main = type { [2 x i32], i32 } + %__foo_vla = type { i32*, [2 x i32] } + + @main_instance = global %main zeroinitializer + @____foo_vla__init = unnamed_addr constant %__foo_vla zeroinitializer + + define void @main(%main* %0) { + entry: + %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + %auto_deref = load [2 x i32], [2 x i32]* %a, align 4 + %outer_arr_gep = getelementptr inbounds [2 x i32], [2 x i32]* %a, i32 0, i32 0 + %vla_struct = alloca %__foo_vla, align 8 + %vla_array_gep = getelementptr inbounds %__foo_vla, %__foo_vla* %vla_struct, i32 0, i32 0 + %vla_dimensions_gep = getelementptr inbounds %__foo_vla, %__foo_vla* %vla_struct, i32 0, i32 1 + store [2 x i32] [i32 0, i32 1], [2 x i32]* %vla_dimensions_gep, align 4 + store i32* %outer_arr_gep, i32** %vla_array_gep, align 8 + %1 = load %__foo_vla, %__foo_vla* %vla_struct, align 8 + %vla_struct_ptr = alloca %__foo_vla, align 8 + store %__foo_vla %1, %__foo_vla* %vla_struct_ptr, align 8 + %call = call i32 @foo(%__foo_vla* %vla_struct_ptr) + store i32 %call, i32* %b, align 4 + ret void + } + + define i32 @foo(%__foo_vla* %0) { + entry: + %foo = alloca i32, align 4 + %vla = alloca %__foo_vla*, align 8 + store %__foo_vla* %0, %__foo_vla** %vla, align 8 + store i32 0, i32* %foo, align 4 + %deref = load %__foo_vla*, %__foo_vla** %vla, align 8 + %dim = getelementptr inbounds %__foo_vla, %__foo_vla* %deref, i32 0, i32 1 + %1 = getelementptr inbounds [2 x i32], [2 x i32]* %dim, i32 0, i32 0 + %2 = load i32, i32* %1, align 4 + store i32 %2, i32* %foo, align 4 + %foo_ret = load i32, i32* %foo, align 4 + ret i32 %foo_ret + } + "#); } #[test] @@ -524,7 +749,52 @@ fn builtin_function_call_lower_bound_with_named_arguments() { ", ); - filtered_assert_snapshot!(result); + filtered_assert_snapshot!(result, @r#" + ; ModuleID = '' + source_filename = "" + target datalayout = "[filtered]" + target triple = "[filtered]" + + %main = type { [2 x i32], i32 } + %__foo_vla = type { i32*, [2 x i32] } + + @main_instance = global %main zeroinitializer + @____foo_vla__init = unnamed_addr constant %__foo_vla zeroinitializer + + define void @main(%main* %0) { + entry: + %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + %auto_deref = load [2 x i32], [2 x i32]* %a, align 4 + %outer_arr_gep = getelementptr inbounds [2 x i32], [2 x i32]* %a, i32 0, i32 0 + %vla_struct = alloca %__foo_vla, align 8 + %vla_array_gep = getelementptr inbounds %__foo_vla, %__foo_vla* %vla_struct, i32 0, i32 0 + %vla_dimensions_gep = getelementptr inbounds %__foo_vla, %__foo_vla* %vla_struct, i32 0, i32 1 + store [2 x i32] [i32 0, i32 1], [2 x i32]* %vla_dimensions_gep, align 4 + store i32* %outer_arr_gep, i32** %vla_array_gep, align 8 + %1 = load %__foo_vla, %__foo_vla* %vla_struct, align 8 + %vla_struct_ptr = alloca %__foo_vla, align 8 + store %__foo_vla %1, %__foo_vla* %vla_struct_ptr, align 8 + %call = call i32 @foo(%__foo_vla* %vla_struct_ptr) + store i32 %call, i32* %b, align 4 + ret void + } + + define i32 @foo(%__foo_vla* %0) { + entry: + %foo = alloca i32, align 4 + %vla = alloca %__foo_vla*, align 8 + store %__foo_vla* %0, %__foo_vla** %vla, align 8 + store i32 0, i32* %foo, align 4 + %deref = load %__foo_vla*, %__foo_vla** %vla, align 8 + %dim = getelementptr inbounds %__foo_vla, %__foo_vla* %deref, i32 0, i32 1 + %1 = getelementptr inbounds [2 x i32], [2 x i32]* %dim, i32 0, i32 0 + %2 = load i32, i32* %1, align 4 + store i32 %2, i32* %foo, align 4 + %foo_ret = load i32, i32* %foo, align 4 + ret i32 %foo_ret + } + "#); } #[test] @@ -548,7 +818,52 @@ fn builtin_function_call_upper_bound_with_named_argument() { ", ); - filtered_assert_snapshot!(result); + filtered_assert_snapshot!(result, @r#" + ; ModuleID = '' + source_filename = "" + target datalayout = "[filtered]" + target triple = "[filtered]" + + %main = type { [2 x i32], i32 } + %__foo_vla = type { i32*, [2 x i32] } + + @main_instance = global %main zeroinitializer + @____foo_vla__init = unnamed_addr constant %__foo_vla zeroinitializer + + define void @main(%main* %0) { + entry: + %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + %auto_deref = load [2 x i32], [2 x i32]* %a, align 4 + %outer_arr_gep = getelementptr inbounds [2 x i32], [2 x i32]* %a, i32 0, i32 0 + %vla_struct = alloca %__foo_vla, align 8 + %vla_array_gep = getelementptr inbounds %__foo_vla, %__foo_vla* %vla_struct, i32 0, i32 0 + %vla_dimensions_gep = getelementptr inbounds %__foo_vla, %__foo_vla* %vla_struct, i32 0, i32 1 + store [2 x i32] [i32 0, i32 1], [2 x i32]* %vla_dimensions_gep, align 4 + store i32* %outer_arr_gep, i32** %vla_array_gep, align 8 + %1 = load %__foo_vla, %__foo_vla* %vla_struct, align 8 + %vla_struct_ptr = alloca %__foo_vla, align 8 + store %__foo_vla %1, %__foo_vla* %vla_struct_ptr, align 8 + %call = call i32 @foo(%__foo_vla* %vla_struct_ptr) + store i32 %call, i32* %b, align 4 + ret void + } + + define i32 @foo(%__foo_vla* %0) { + entry: + %foo = alloca i32, align 4 + %vla = alloca %__foo_vla*, align 8 + store %__foo_vla* %0, %__foo_vla** %vla, align 8 + store i32 0, i32* %foo, align 4 + %deref = load %__foo_vla*, %__foo_vla** %vla, align 8 + %dim = getelementptr inbounds %__foo_vla, %__foo_vla* %deref, i32 0, i32 1 + %1 = getelementptr inbounds [2 x i32], [2 x i32]* %dim, i32 0, i32 1 + %2 = load i32, i32* %1, align 4 + store i32 %2, i32* %foo, align 4 + %foo_ret = load i32, i32* %foo, align 4 + ret i32 %foo_ret + } + "#); } #[test] @@ -572,7 +887,52 @@ fn builtin_function_call_upper_bound() { ", ); - filtered_assert_snapshot!(result); + filtered_assert_snapshot!(result, @r#" + ; ModuleID = '' + source_filename = "" + target datalayout = "[filtered]" + target triple = "[filtered]" + + %main = type { [2 x i32], i32 } + %__foo_vla = type { i32*, [2 x i32] } + + @main_instance = global %main zeroinitializer + @____foo_vla__init = unnamed_addr constant %__foo_vla zeroinitializer + + define void @main(%main* %0) { + entry: + %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 + %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 + %auto_deref = load [2 x i32], [2 x i32]* %a, align 4 + %outer_arr_gep = getelementptr inbounds [2 x i32], [2 x i32]* %a, i32 0, i32 0 + %vla_struct = alloca %__foo_vla, align 8 + %vla_array_gep = getelementptr inbounds %__foo_vla, %__foo_vla* %vla_struct, i32 0, i32 0 + %vla_dimensions_gep = getelementptr inbounds %__foo_vla, %__foo_vla* %vla_struct, i32 0, i32 1 + store [2 x i32] [i32 0, i32 1], [2 x i32]* %vla_dimensions_gep, align 4 + store i32* %outer_arr_gep, i32** %vla_array_gep, align 8 + %1 = load %__foo_vla, %__foo_vla* %vla_struct, align 8 + %vla_struct_ptr = alloca %__foo_vla, align 8 + store %__foo_vla %1, %__foo_vla* %vla_struct_ptr, align 8 + %call = call i32 @foo(%__foo_vla* %vla_struct_ptr) + store i32 %call, i32* %b, align 4 + ret void + } + + define i32 @foo(%__foo_vla* %0) { + entry: + %foo = alloca i32, align 4 + %vla = alloca %__foo_vla*, align 8 + store %__foo_vla* %0, %__foo_vla** %vla, align 8 + store i32 0, i32* %foo, align 4 + %deref = load %__foo_vla*, %__foo_vla** %vla, align 8 + %dim = getelementptr inbounds %__foo_vla, %__foo_vla* %deref, i32 0, i32 1 + %1 = getelementptr inbounds [2 x i32], [2 x i32]* %dim, i32 0, i32 1 + %2 = load i32, i32* %1, align 4 + store i32 %2, i32* %foo, align 4 + %foo_ret = load i32, i32* %foo, align 4 + ret i32 %foo_ret + } + "#); } #[test] @@ -892,7 +1252,27 @@ fn builtin_div_with_named_arguments() { let res = codegen(src); - filtered_assert_snapshot!(res); + filtered_assert_snapshot!(res, @r#" + ; ModuleID = '' + source_filename = "" + target datalayout = "[filtered]" + target triple = "[filtered]" + + define i32 @main() { + entry: + %main = alloca i32, align 4 + %x = alloca i32, align 4 + %y = alloca i32, align 4 + store i32 20, i32* %x, align 4 + store i32 4, i32* %y, align 4 + store i32 0, i32* %main, align 4 + %load_x = load i32, i32* %x, align 4 + %load_y = load i32, i32* %y, align 4 + %tmpVar = sdiv i32 %load_x, %load_y + %main_ret = load i32, i32* %main, align 4 + ret i32 %main_ret + } + "#); } #[test] @@ -909,7 +1289,27 @@ fn builtin_sub_with_named_arguments() { let res = codegen(src); - filtered_assert_snapshot!(res); + filtered_assert_snapshot!(res, @r#" + ; ModuleID = '' + source_filename = "" + target datalayout = "[filtered]" + target triple = "[filtered]" + + define i32 @main() { + entry: + %main = alloca i32, align 4 + %x = alloca i32, align 4 + %y = alloca i32, align 4 + store i32 20, i32* %x, align 4 + store i32 4, i32* %y, align 4 + store i32 0, i32* %main, align 4 + %load_x = load i32, i32* %x, align 4 + %load_y = load i32, i32* %y, align 4 + %tmpVar = sub i32 %load_x, %load_y + %main_ret = load i32, i32* %main, align 4 + ret i32 %main_ret + } + "#); } #[test] diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_adr.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_adr.snap deleted file mode 100644 index 3d833a7b34b..00000000000 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_adr.snap +++ /dev/null @@ -1,21 +0,0 @@ ---- -source: src/codegen/tests/expression_tests.rs -expression: result -snapshot_kind: text ---- -; ModuleID = '' -source_filename = "" -target datalayout = "[filtered]" -target triple = "[filtered]" - -%main = type { i32*, i32 } - -@main_instance = global %main zeroinitializer - -define void @main(%main* %0) { -entry: - %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 - %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 - store i32* %b, i32** %a, align 8 - ret void -} diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_adr_with_named_argument.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_adr_with_named_argument.snap deleted file mode 100644 index 4df20e09689..00000000000 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_adr_with_named_argument.snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: src/codegen/tests/expression_tests.rs -expression: result ---- -; ModuleID = '' -source_filename = "" -target datalayout = "[filtered]" -target triple = "[filtered]" - -%main = type { i32*, i32 } - -@main_instance = global %main zeroinitializer - -define void @main(%main* %0) { -entry: - %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 - %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 - store i32* %b, i32** %a, align 8 - ret void -} diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_ref.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_ref.snap deleted file mode 100644 index 3d833a7b34b..00000000000 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_ref.snap +++ /dev/null @@ -1,21 +0,0 @@ ---- -source: src/codegen/tests/expression_tests.rs -expression: result -snapshot_kind: text ---- -; ModuleID = '' -source_filename = "" -target datalayout = "[filtered]" -target triple = "[filtered]" - -%main = type { i32*, i32 } - -@main_instance = global %main zeroinitializer - -define void @main(%main* %0) { -entry: - %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 - %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 - store i32* %b, i32** %a, align 8 - ret void -} diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_ref_with_named_argument.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_ref_with_named_argument.snap deleted file mode 100644 index 4df20e09689..00000000000 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_ref_with_named_argument.snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: src/codegen/tests/expression_tests.rs -expression: result ---- -; ModuleID = '' -source_filename = "" -target datalayout = "[filtered]" -target triple = "[filtered]" - -%main = type { i32*, i32 } - -@main_instance = global %main zeroinitializer - -define void @main(%main* %0) { -entry: - %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 - %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 - store i32* %b, i32** %a, align 8 - ret void -} diff --git a/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__builtin_functions_named_arguments_invalid_parameter_names.snap b/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__builtin_functions_named_arguments_invalid_parameter_names.snap deleted file mode 100644 index 9a640fa75ca..00000000000 --- a/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__builtin_functions_named_arguments_invalid_parameter_names.snap +++ /dev/null @@ -1,177 +0,0 @@ ---- -source: src/validation/tests/statement_validation_tests.rs -expression: diagnostics ---- -error[E089]: Invalid call parameters - ┌─ :9:22 - │ -9 │ a := SEL(WRONG := sel, IN0 := a, IN1 := b); - │ ^^^^^^^^^^^^ Invalid call parameters - -error[E048]: Could not resolve reference to WRONG - ┌─ :9:22 - │ -9 │ a := SEL(WRONG := sel, IN0 := a, IN1 := b); - │ ^^^^^ Could not resolve reference to WRONG - -error[E089]: Invalid call parameters - ┌─ :10:32 - │ -10 │ a := SEL(G := sel, INVALID := a, IN1 := b); - │ ^^^^^^^^^^^^ Invalid call parameters - -error[E048]: Could not resolve reference to INVALID - ┌─ :10:32 - │ -10 │ a := SEL(G := sel, INVALID := a, IN1 := b); - │ ^^^^^^^ Could not resolve reference to INVALID - -error[E089]: Invalid call parameters - ┌─ :13:18 - │ -13 │ MOVE(SOURCE := arr, INVALID := arr); - │ ^^^^^^^^^^^^^ Invalid call parameters - -error[E048]: Could not resolve reference to SOURCE - ┌─ :13:18 - │ -13 │ MOVE(SOURCE := arr, INVALID := arr); - │ ^^^^^^ Could not resolve reference to SOURCE - -error[E089]: Invalid call parameters - ┌─ :14:18 - │ -14 │ MOVE(WRONG := arr, DEST := arr); - │ ^^^^^^^^^^^^ Invalid call parameters - -error[E048]: Could not resolve reference to WRONG - ┌─ :14:18 - │ -14 │ MOVE(WRONG := arr, DEST := arr); - │ ^^^^^ Could not resolve reference to WRONG - -error[E089]: Invalid call parameters - ┌─ :17:25 - │ -17 │ a := SIZEOF(INVALID := arr); - │ ^^^^^^^^^^^^^^ Invalid call parameters - -error[E048]: Could not resolve reference to INVALID - ┌─ :17:25 - │ -17 │ a := SIZEOF(INVALID := arr); - │ ^^^^^^^ Could not resolve reference to INVALID - -warning[E067]: Implicit downcast from 'ULINT' to 'INT'. - ┌─ :17:18 - │ -17 │ a := SIZEOF(INVALID := arr); - │ ^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'ULINT' to 'INT'. - -error[E089]: Invalid call parameters - ┌─ :20:17 - │ -20 │ ADR(WRONG := arr); - │ ^^^^^^^^^^^^ Invalid call parameters - -error[E048]: Could not resolve reference to WRONG - ┌─ :20:17 - │ -20 │ ADR(WRONG := arr); - │ ^^^^^ Could not resolve reference to WRONG - -error[E089]: Invalid call parameters - ┌─ :23:17 - │ -23 │ REF(INVALID := arr); - │ ^^^^^^^^^^^^^^ Invalid call parameters - -error[E048]: Could not resolve reference to INVALID - ┌─ :23:17 - │ -23 │ REF(INVALID := arr); - │ ^^^^^^^ Could not resolve reference to INVALID - -error[E089]: Invalid call parameters - ┌─ :26:30 - │ -26 │ a := UPPER_BOUND(INVALID := arr, DIM := 1); - │ ^^^^^^^^^^^^^^ Invalid call parameters - -error[E048]: Could not resolve reference to INVALID - ┌─ :26:30 - │ -26 │ a := UPPER_BOUND(INVALID := arr, DIM := 1); - │ ^^^^^^^ Could not resolve reference to INVALID - -warning[E067]: Implicit downcast from 'DINT' to 'INT'. - ┌─ :26:18 - │ -26 │ a := UPPER_BOUND(INVALID := arr, DIM := 1); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. - -error[E037]: Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' - ┌─ :27:30 - │ -27 │ a := UPPER_BOUND(ARR := arr, WRONG := 1); - │ ^^^^^^^^^^ Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' - -error[E089]: Invalid call parameters - ┌─ :27:42 - │ -27 │ a := UPPER_BOUND(ARR := arr, WRONG := 1); - │ ^^^^^^^^^^ Invalid call parameters - -error[E048]: Could not resolve reference to WRONG - ┌─ :27:42 - │ -27 │ a := UPPER_BOUND(ARR := arr, WRONG := 1); - │ ^^^^^ Could not resolve reference to WRONG - -warning[E067]: Implicit downcast from 'DINT' to 'INT'. - ┌─ :27:18 - │ -27 │ a := UPPER_BOUND(ARR := arr, WRONG := 1); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. - -error[E089]: Invalid call parameters - ┌─ :30:30 - │ -30 │ a := LOWER_BOUND(WRONG := arr, DIM := 1); - │ ^^^^^^^^^^^^ Invalid call parameters - -error[E048]: Could not resolve reference to WRONG - ┌─ :30:30 - │ -30 │ a := LOWER_BOUND(WRONG := arr, DIM := 1); - │ ^^^^^ Could not resolve reference to WRONG - -warning[E067]: Implicit downcast from 'DINT' to 'INT'. - ┌─ :30:18 - │ -30 │ a := LOWER_BOUND(WRONG := arr, DIM := 1); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. - -error[E037]: Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' - ┌─ :31:30 - │ -31 │ a := LOWER_BOUND(ARR := arr, INVALID := 1); - │ ^^^^^^^^^^ Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' - -error[E089]: Invalid call parameters - ┌─ :31:42 - │ -31 │ a := LOWER_BOUND(ARR := arr, INVALID := 1); - │ ^^^^^^^^^^^^ Invalid call parameters - -error[E048]: Could not resolve reference to INVALID - ┌─ :31:42 - │ -31 │ a := LOWER_BOUND(ARR := arr, INVALID := 1); - │ ^^^^^^^ Could not resolve reference to INVALID - -warning[E067]: Implicit downcast from 'DINT' to 'INT'. - ┌─ :31:18 - │ -31 │ a := LOWER_BOUND(ARR := arr, INVALID := 1); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. diff --git a/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__builtin_functions_named_arguments_type_mismatches.snap b/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__builtin_functions_named_arguments_type_mismatches.snap deleted file mode 100644 index 11c1b658c84..00000000000 --- a/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__builtin_functions_named_arguments_type_mismatches.snap +++ /dev/null @@ -1,81 +0,0 @@ ---- -source: src/validation/tests/statement_validation_tests.rs -expression: diagnostics ---- -error[E037]: Invalid assignment: cannot assign 'STRING' to 'BOOL' - ┌─ :10:22 - │ -10 │ a := SEL(G := str_val, IN0 := a, IN1 := b); // wrong type for selector - │ ^^^^^^^^^^^^ Invalid assignment: cannot assign 'STRING' to 'BOOL' - -error[E089]: Invalid call parameters - ┌─ :13:27 - │ -13 │ MOVE(IN := a, OUT := str_val); // incompatible array types - │ ^^^^^^^^^^^^^^ Invalid call parameters - -error[E048]: Could not resolve reference to OUT - ┌─ :13:27 - │ -13 │ MOVE(IN := a, OUT := str_val); // incompatible array types - │ ^^^ Could not resolve reference to OUT - -error[E037]: Invalid assignment: cannot assign 'INT' to 'VARIABLE LENGTH ARRAY' - ┌─ :22:30 - │ -22 │ a := UPPER_BOUND(ARR := a, DIM := 1); // not an array - │ ^^^^^^^^ Invalid assignment: cannot assign 'INT' to 'VARIABLE LENGTH ARRAY' - -warning[E067]: Implicit downcast from 'DINT' to 'INT'. - ┌─ :22:18 - │ -22 │ a := UPPER_BOUND(ARR := a, DIM := 1); // not an array - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. - -error[E062]: Invalid type nature for generic argument. STRING is no ANY_INT - ┌─ :23:49 - │ -23 │ a := UPPER_BOUND(ARR := arr, DIM := str_val); // wrong type for dimension - │ ^^^^^^^ Invalid type nature for generic argument. STRING is no ANY_INT - -error[E037]: Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' - ┌─ :23:30 - │ -23 │ a := UPPER_BOUND(ARR := arr, DIM := str_val); // wrong type for dimension - │ ^^^^^^^^^^ Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' - -warning[E067]: Implicit downcast from 'DINT' to 'INT'. - ┌─ :23:18 - │ -23 │ a := UPPER_BOUND(ARR := arr, DIM := str_val); // wrong type for dimension - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. - -error[E037]: Invalid assignment: cannot assign 'STRING' to 'VARIABLE LENGTH ARRAY' - ┌─ :26:30 - │ -26 │ a := LOWER_BOUND(ARR := str_val, DIM := 1); // not an array - │ ^^^^^^^^^^^^^^ Invalid assignment: cannot assign 'STRING' to 'VARIABLE LENGTH ARRAY' - -warning[E067]: Implicit downcast from 'DINT' to 'INT'. - ┌─ :26:18 - │ -26 │ a := LOWER_BOUND(ARR := str_val, DIM := 1); // not an array - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. - -error[E062]: Invalid type nature for generic argument. BOOL is no ANY_INT - ┌─ :27:49 - │ -27 │ a := LOWER_BOUND(ARR := arr, DIM := sel); // wrong type for dimension - │ ^^^ Invalid type nature for generic argument. BOOL is no ANY_INT - -error[E037]: Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' - ┌─ :27:30 - │ -27 │ a := LOWER_BOUND(ARR := arr, DIM := sel); // wrong type for dimension - │ ^^^^^^^^^^ Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' - -warning[E067]: Implicit downcast from 'DINT' to 'INT'. - ┌─ :27:18 - │ -27 │ a := LOWER_BOUND(ARR := arr, DIM := sel); // wrong type for dimension - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. diff --git a/src/validation/tests/statement_validation_tests.rs b/src/validation/tests/statement_validation_tests.rs index ba7b7562212..89199eaa652 100644 --- a/src/validation/tests/statement_validation_tests.rs +++ b/src/validation/tests/statement_validation_tests.rs @@ -723,6 +723,99 @@ fn ref_builtin_function_reports_invalid_param_count() { assert_snapshot!(diagnostics); } +#[test] +fn builtin_functions_named_arguments_mixed_with_positional_arguments() { + let diagnostics = parse_and_validate_buffered( + " + FUNCTION main : DINT + VAR + arr: ARRAY[0..5] OF INT; + a, b: INT; + sel: BOOL; + END_VAR + // SEL with mixed named and positional parameter + a := SEL(G := sel, IN0 := a, b); + + // SIZEOF only has one parameter so no mixing possible + + // MOVE only has one parameter so no mixing possible + + // ADR only has one parameter so no mixing possible + + // REF only has one parameter so no mixing possible + + // UPPER_BOUND with mixed named and positional parameter + a := UPPER_BOUND(arr := arr, 1); + + // LOWER_BOUND with mixed named and positional parameter + a := LOWER_BOUND(arr := arr, 1); + + // LOWER_BOUND with mixed named and positional parameter + a := DIV(IN1 := a, b); + + // LOWER_BOUND with mixed named and positional parameter + a := SUB(IN1 := a, b); + END_FUNCTION + ", + ); + + assert_snapshot!(diagnostics, @r" + error[E031]: Cannot mix implicit and explicit call parameters! + ┌─ :9:42 + │ + 9 │ a := SEL(G := sel, IN0 := a, b); + │ ^ Cannot mix implicit and explicit call parameters! + + error[E037]: Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' + ┌─ :20:30 + │ + 20 │ a := UPPER_BOUND(arr := arr, 1); + │ ^^^^^^^^^^ Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' + + error[E031]: Cannot mix implicit and explicit call parameters! + ┌─ :20:42 + │ + 20 │ a := UPPER_BOUND(arr := arr, 1); + │ ^ Cannot mix implicit and explicit call parameters! + + warning[E067]: Implicit downcast from 'DINT' to 'INT'. + ┌─ :20:18 + │ + 20 │ a := UPPER_BOUND(arr := arr, 1); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. + + error[E037]: Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' + ┌─ :23:30 + │ + 23 │ a := LOWER_BOUND(arr := arr, 1); + │ ^^^^^^^^^^ Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' + + error[E031]: Cannot mix implicit and explicit call parameters! + ┌─ :23:42 + │ + 23 │ a := LOWER_BOUND(arr := arr, 1); + │ ^ Cannot mix implicit and explicit call parameters! + + warning[E067]: Implicit downcast from 'DINT' to 'INT'. + ┌─ :23:18 + │ + 23 │ a := LOWER_BOUND(arr := arr, 1); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. + + error[E031]: Cannot mix implicit and explicit call parameters! + ┌─ :26:32 + │ + 26 │ a := DIV(IN1 := a, b); + │ ^ Cannot mix implicit and explicit call parameters! + + error[E031]: Cannot mix implicit and explicit call parameters! + ┌─ :29:32 + │ + 29 │ a := SUB(IN1 := a, b); + │ ^ Cannot mix implicit and explicit call parameters! + "); +} + #[test] fn builtin_functions_named_arguments_invalid_parameter_names() { let diagnostics = parse_and_validate_buffered( @@ -730,30 +823,30 @@ fn builtin_functions_named_arguments_invalid_parameter_names() { FUNCTION main : DINT VAR arr: ARRAY[0..5] OF INT; + arr2: ARRAY[0..5] OF INT; a, b: INT; sel: BOOL; END_VAR // SEL with wrong parameter names a := SEL(WRONG := sel, IN0 := a, IN1 := b); a := SEL(G := sel, INVALID := a, IN1 := b); - - // MOVE with wrong parameter names - MOVE(SOURCE := arr, INVALID := arr); - MOVE(WRONG := arr, DEST := arr); - + + // MOVE with wrong parameter name + arr2 := MOVE(SOURCE := arr); + // SIZEOF with wrong parameter name a := SIZEOF(INVALID := arr); - + // ADR with wrong parameter name ADR(WRONG := arr); - + // REF with wrong parameter name REF(INVALID := arr); - + // UPPER_BOUND with wrong parameter names a := UPPER_BOUND(INVALID := arr, DIM := 1); a := UPPER_BOUND(ARR := arr, WRONG := 1); - + // LOWER_BOUND with wrong parameter names a := LOWER_BOUND(WRONG := arr, DIM := 1); a := LOWER_BOUND(ARR := arr, INVALID := 1); @@ -761,7 +854,169 @@ fn builtin_functions_named_arguments_invalid_parameter_names() { ", ); - assert_snapshot!(diagnostics); + assert_snapshot!(diagnostics, @r" + error[E089]: Invalid call parameters + ┌─ :10:22 + │ + 10 │ a := SEL(WRONG := sel, IN0 := a, IN1 := b); + │ ^^^^^^^^^^^^ Invalid call parameters + + error[E048]: Could not resolve reference to WRONG + ┌─ :10:22 + │ + 10 │ a := SEL(WRONG := sel, IN0 := a, IN1 := b); + │ ^^^^^ Could not resolve reference to WRONG + + error[E089]: Invalid call parameters + ┌─ :11:32 + │ + 11 │ a := SEL(G := sel, INVALID := a, IN1 := b); + │ ^^^^^^^^^^^^ Invalid call parameters + + error[E048]: Could not resolve reference to INVALID + ┌─ :11:32 + │ + 11 │ a := SEL(G := sel, INVALID := a, IN1 := b); + │ ^^^^^^^ Could not resolve reference to INVALID + + error[E089]: Invalid call parameters + ┌─ :14:26 + │ + 14 │ arr2 := MOVE(SOURCE := arr); + │ ^^^^^^^^^^^^^ Invalid call parameters + + error[E048]: Could not resolve reference to SOURCE + ┌─ :14:26 + │ + 14 │ arr2 := MOVE(SOURCE := arr); + │ ^^^^^^ Could not resolve reference to SOURCE + + error[E089]: Invalid call parameters + ┌─ :17:25 + │ + 17 │ a := SIZEOF(INVALID := arr); + │ ^^^^^^^^^^^^^^ Invalid call parameters + + error[E048]: Could not resolve reference to INVALID + ┌─ :17:25 + │ + 17 │ a := SIZEOF(INVALID := arr); + │ ^^^^^^^ Could not resolve reference to INVALID + + warning[E067]: Implicit downcast from 'ULINT' to 'INT'. + ┌─ :17:18 + │ + 17 │ a := SIZEOF(INVALID := arr); + │ ^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'ULINT' to 'INT'. + + error[E089]: Invalid call parameters + ┌─ :20:17 + │ + 20 │ ADR(WRONG := arr); + │ ^^^^^^^^^^^^ Invalid call parameters + + error[E048]: Could not resolve reference to WRONG + ┌─ :20:17 + │ + 20 │ ADR(WRONG := arr); + │ ^^^^^ Could not resolve reference to WRONG + + error[E089]: Invalid call parameters + ┌─ :23:17 + │ + 23 │ REF(INVALID := arr); + │ ^^^^^^^^^^^^^^ Invalid call parameters + + error[E048]: Could not resolve reference to INVALID + ┌─ :23:17 + │ + 23 │ REF(INVALID := arr); + │ ^^^^^^^ Could not resolve reference to INVALID + + error[E089]: Invalid call parameters + ┌─ :26:30 + │ + 26 │ a := UPPER_BOUND(INVALID := arr, DIM := 1); + │ ^^^^^^^^^^^^^^ Invalid call parameters + + error[E048]: Could not resolve reference to INVALID + ┌─ :26:30 + │ + 26 │ a := UPPER_BOUND(INVALID := arr, DIM := 1); + │ ^^^^^^^ Could not resolve reference to INVALID + + warning[E067]: Implicit downcast from 'DINT' to 'INT'. + ┌─ :26:18 + │ + 26 │ a := UPPER_BOUND(INVALID := arr, DIM := 1); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. + + error[E037]: Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' + ┌─ :27:30 + │ + 27 │ a := UPPER_BOUND(ARR := arr, WRONG := 1); + │ ^^^^^^^^^^ Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' + + error[E089]: Invalid call parameters + ┌─ :27:42 + │ + 27 │ a := UPPER_BOUND(ARR := arr, WRONG := 1); + │ ^^^^^^^^^^ Invalid call parameters + + error[E048]: Could not resolve reference to WRONG + ┌─ :27:42 + │ + 27 │ a := UPPER_BOUND(ARR := arr, WRONG := 1); + │ ^^^^^ Could not resolve reference to WRONG + + warning[E067]: Implicit downcast from 'DINT' to 'INT'. + ┌─ :27:18 + │ + 27 │ a := UPPER_BOUND(ARR := arr, WRONG := 1); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. + + error[E089]: Invalid call parameters + ┌─ :30:30 + │ + 30 │ a := LOWER_BOUND(WRONG := arr, DIM := 1); + │ ^^^^^^^^^^^^ Invalid call parameters + + error[E048]: Could not resolve reference to WRONG + ┌─ :30:30 + │ + 30 │ a := LOWER_BOUND(WRONG := arr, DIM := 1); + │ ^^^^^ Could not resolve reference to WRONG + + warning[E067]: Implicit downcast from 'DINT' to 'INT'. + ┌─ :30:18 + │ + 30 │ a := LOWER_BOUND(WRONG := arr, DIM := 1); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. + + error[E037]: Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' + ┌─ :31:30 + │ + 31 │ a := LOWER_BOUND(ARR := arr, INVALID := 1); + │ ^^^^^^^^^^ Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' + + error[E089]: Invalid call parameters + ┌─ :31:42 + │ + 31 │ a := LOWER_BOUND(ARR := arr, INVALID := 1); + │ ^^^^^^^^^^^^ Invalid call parameters + + error[E048]: Could not resolve reference to INVALID + ┌─ :31:42 + │ + 31 │ a := LOWER_BOUND(ARR := arr, INVALID := 1); + │ ^^^^^^^ Could not resolve reference to INVALID + + warning[E067]: Implicit downcast from 'DINT' to 'INT'. + ┌─ :31:18 + │ + 31 │ a := LOWER_BOUND(ARR := arr, INVALID := 1); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. + "); } #[test] @@ -777,28 +1032,106 @@ fn builtin_functions_named_arguments_type_mismatches() { END_VAR // SEL with type mismatches a := SEL(G := str_val, IN0 := a, IN1 := b); // wrong type for selector - + // MOVE with type mismatches MOVE(IN := a, OUT := str_val); // incompatible array types - + // SIZEOF - should work with any type, so no type mismatch possible - + // ADR - should work with any type, so no type mismatch possible - + // REF - should work with any type, so no type mismatch possible - + // UPPER_BOUND with type mismatches a := UPPER_BOUND(ARR := a, DIM := 1); // not an array a := UPPER_BOUND(ARR := arr, DIM := str_val); // wrong type for dimension - - // LOWER_BOUND with type mismatches + + // LOWER_BOUND with type mismatches a := LOWER_BOUND(ARR := str_val, DIM := 1); // not an array a := LOWER_BOUND(ARR := arr, DIM := sel); // wrong type for dimension END_FUNCTION ", ); - assert_snapshot!(diagnostics); + assert_snapshot!(diagnostics, @r" + error[E037]: Invalid assignment: cannot assign 'STRING' to 'BOOL' + ┌─ :10:22 + │ + 10 │ a := SEL(G := str_val, IN0 := a, IN1 := b); // wrong type for selector + │ ^^^^^^^^^^^^ Invalid assignment: cannot assign 'STRING' to 'BOOL' + + error[E089]: Invalid call parameters + ┌─ :13:27 + │ + 13 │ MOVE(IN := a, OUT := str_val); // incompatible array types + │ ^^^^^^^^^^^^^^ Invalid call parameters + + error[E048]: Could not resolve reference to OUT + ┌─ :13:27 + │ + 13 │ MOVE(IN := a, OUT := str_val); // incompatible array types + │ ^^^ Could not resolve reference to OUT + + error[E037]: Invalid assignment: cannot assign 'INT' to 'VARIABLE LENGTH ARRAY' + ┌─ :22:30 + │ + 22 │ a := UPPER_BOUND(ARR := a, DIM := 1); // not an array + │ ^^^^^^^^ Invalid assignment: cannot assign 'INT' to 'VARIABLE LENGTH ARRAY' + + warning[E067]: Implicit downcast from 'DINT' to 'INT'. + ┌─ :22:18 + │ + 22 │ a := UPPER_BOUND(ARR := a, DIM := 1); // not an array + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. + + error[E062]: Invalid type nature for generic argument. STRING is no ANY_INT + ┌─ :23:49 + │ + 23 │ a := UPPER_BOUND(ARR := arr, DIM := str_val); // wrong type for dimension + │ ^^^^^^^ Invalid type nature for generic argument. STRING is no ANY_INT + + error[E037]: Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' + ┌─ :23:30 + │ + 23 │ a := UPPER_BOUND(ARR := arr, DIM := str_val); // wrong type for dimension + │ ^^^^^^^^^^ Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' + + warning[E067]: Implicit downcast from 'DINT' to 'INT'. + ┌─ :23:18 + │ + 23 │ a := UPPER_BOUND(ARR := arr, DIM := str_val); // wrong type for dimension + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. + + error[E037]: Invalid assignment: cannot assign 'STRING' to 'VARIABLE LENGTH ARRAY' + ┌─ :26:30 + │ + 26 │ a := LOWER_BOUND(ARR := str_val, DIM := 1); // not an array + │ ^^^^^^^^^^^^^^ Invalid assignment: cannot assign 'STRING' to 'VARIABLE LENGTH ARRAY' + + warning[E067]: Implicit downcast from 'DINT' to 'INT'. + ┌─ :26:18 + │ + 26 │ a := LOWER_BOUND(ARR := str_val, DIM := 1); // not an array + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. + + error[E062]: Invalid type nature for generic argument. BOOL is no ANY_INT + ┌─ :27:49 + │ + 27 │ a := LOWER_BOUND(ARR := arr, DIM := sel); // wrong type for dimension + │ ^^^ Invalid type nature for generic argument. BOOL is no ANY_INT + + error[E037]: Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' + ┌─ :27:30 + │ + 27 │ a := LOWER_BOUND(ARR := arr, DIM := sel); // wrong type for dimension + │ ^^^^^^^^^^ Invalid assignment: cannot assign 'ARRAY[0..5] OF INT' to 'VARIABLE LENGTH ARRAY' + + warning[E067]: Implicit downcast from 'DINT' to 'INT'. + ┌─ :27:18 + │ + 27 │ a := LOWER_BOUND(ARR := arr, DIM := sel); // wrong type for dimension + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Implicit downcast from 'DINT' to 'INT'. + "); } #[test] From a0acc536dd618b52376040eecf55617c304da518 Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Thu, 25 Sep 2025 08:04:02 +0000 Subject: [PATCH 20/27] seperate tests for named and positional arguments into two lit tests --- .../single/builtin-named-arguments/main.st | 72 +++--------- .../builtin-positional-arguments/main.st | 104 ++++++++++++++++++ 2 files changed, 117 insertions(+), 59 deletions(-) create mode 100644 tests/lit/single/builtin-positional-arguments/main.st diff --git a/tests/lit/single/builtin-named-arguments/main.st b/tests/lit/single/builtin-named-arguments/main.st index 33c9713d994..6d65288a71e 100644 --- a/tests/lit/single/builtin-named-arguments/main.st +++ b/tests/lit/single/builtin-named-arguments/main.st @@ -1,19 +1,20 @@ // RUN: (%COMPILE %s && %RUN) | %CHECK %s // -// Test cases for builtin functions with both named and positional arguments +// Test cases for builtin functions with named arguments // // NOTE: `MUX` does currently not support named arguments +// `ADR()` and `REF()` with the positional arguments test // -// This test verifies that builtin functions work correctly with: -// - MOVE: Both named (IN := x) and positional arguments -// - SEL: Both named (G := TRUE, IN0 := b, IN1 := c) and positional (TRUE, b, c) arguments -// - SIZEOF: Both named (in := myarray) and positional (myarray) arguments -// - UPPER_BOUND: Both named (arr := vla, dim := x) and positional (vla, x) arguments -// - LOWER_BOUND: Both named (arr := vla, dim := x) and positional (vla, x) arguments -// - ADR: Both named (in := variable) and positional (variable) arguments -// - REF: Both named (in := variable) and positional (variable) arguments +// This test verifies that builtin functions work correctly with named arguments: +// - MOVE: named (IN := x) arguments +// - SEL: named (G := TRUE, IN0 := b, IN1 := c) arguments +// - SIZEOF: named (in := myarray) arguments +// - UPPER_BOUND: named (arr := vla, dim := x) arguments +// - LOWER_BOUND: named (arr := vla, dim := x) arguments +// - DIV: named (IN1 := dividend, IN2 := divisor) arguments +// - SUB: named (IN1 := dividend, IN2 := subtrahend) arguments // -// The test ensures that generic type resolution works properly for both calling conventions. +// For ADR and REF, both calling conventions are tested to verify they produce equivalent results. FUNCTION main : DINT VAR @@ -25,10 +26,6 @@ VAR b : DINT := 3; c : DINT := 4; - piAddress1 : REF_TO INT; - piAddress2 : REF_TO INT; - iVar1 : INT := 5; - myarray : ARRAY [0..9] OF BYTE := [0,1,2,3,4,5,6,7,8,9]; dividend : DINT := 20; @@ -40,53 +37,22 @@ END_VAR printf('%d$N', y); // CHECK: 7331 y := MOVE(IN := x); printf('%d$N', y); // CHECK: 9 - y := 7331; - x := MOVE(y); - printf('%d$N', y); // CHECK: 7331 // SEL a := SEL(G := TRUE, IN0 := b, IN1 := c); printf('%d$N', a); // CHECK: 4 - a := 0; - a := SEL(TRUE, b, c); - printf('%d$N', a); // CHECK: 4 - - result := MUX(1, a, b, c); - printf('%d$N', result); // CHECK: 3 // SIZEOF result := SIZEOF(in := myarray); printf('%d$N', result); // CHECK: 10 - result := SIZEOF(myarray); - printf('%d$N', result); // CHECK: 10 - - // ADR - result := 0; - printf('%d$N', result); // CHECK: 0 - piAddress1 := ADR(iVar1); - piAddress2 := ADR(IN := iVar1); - result := (piAddress1 = piAddress2); - printf('%d$N', result); // CHECK: 1 - - // REF - result := 0; - printf('%d$N', result); // CHECK: 0 - piAddress1 := REF(iVar1); // positional - piAddress2 := REF(in := iVar1); // named argument - result := (piAddress1 = piAddress2); - printf('%d$N', result); // CHECK: 1 // DIV result := DIV(IN1 := dividend, IN2 := divisor); printf('%d$N', result); // CHECK: 5 - result := DIV(dividend, divisor); - printf('%d$N', result); // CHECK: 5 // SUB result := SUB(IN1 := dividend, IN2 := subtrahend); printf('%d$N', result); // CHECK: 13 - result := SUB(dividend, subtrahend); - printf('%d$N', result); // CHECK: 13 test_bounds(myarray); @@ -103,23 +69,11 @@ VAR x : DINT := 1; END_VAR - // check with positional arguments - result := UPPER_BOUND(vla, x); - printf('%d$N', result); // CHECK: 9 - - result := 999; - - // and with named arguments + // UPPER_BOUND result := UPPER_BOUND(arr := vla, dim := x); printf('%d$N', result); // CHECK: 9 - // check with positional arguments - result := LOWER_BOUND(vla, x); - printf('%d$N', result); // CHECK: 0 - - result := 999; - - // and with named arguments + // LOWER_BOUND result := LOWER_BOUND(arr := vla, dim := x); printf('%d$N', result); // CHECK: 0 diff --git a/tests/lit/single/builtin-positional-arguments/main.st b/tests/lit/single/builtin-positional-arguments/main.st new file mode 100644 index 00000000000..b8b2a7101e9 --- /dev/null +++ b/tests/lit/single/builtin-positional-arguments/main.st @@ -0,0 +1,104 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s +// +// Test cases for builtin functions with positional arguments +// +// This test verifies that builtin functions work correctly with positional arguments: +// - MOVE: positional arguments +// - SEL: positional (TRUE, b, c) arguments +// - MUX: positional arguments (NOTE: MUX does not support named arguments) +// - SIZEOF: positional (myarray) arguments +// - UPPER_BOUND: positional (vla, x) arguments +// - LOWER_BOUND: positional (vla, x) arguments +// - ADR: Both positional and named arguments (to verify equivalence) +// - REF: Both positional and named arguments (to verify equivalence) +// - DIV: positional arguments +// - SUB: positional arguments +// +// For ADR and REF, both calling conventions are tested to verify they produce equivalent results. + +FUNCTION main : DINT +VAR + result : ULINT; + x : DINT := 9; + y : DINT := 7331; + + a : DINT := 0; + b : DINT := 3; + c : DINT := 4; + + piAddress1 : REF_TO INT; + piAddress2 : REF_TO INT; + iVar1 : INT := 5; + + myarray : ARRAY [0..9] OF BYTE := [0,1,2,3,4,5,6,7,8,9]; + + dividend : DINT := 20; + divisor : DINT := 4; + subtrahend : DINT := 7; +END_VAR + + // MOVE + printf('%d$N', y); // CHECK: 7331 + x := MOVE(y); + printf('%d$N', x); // CHECK: 7331 + + // SEL + a := SEL(TRUE, b, c); + printf('%d$N', a); // CHECK: 4 + + // MUX + result := MUX(1, a, b, c); + printf('%d$N', result); // CHECK: 3 + + // SIZEOF + result := SIZEOF(myarray); + printf('%d$N', result); // CHECK: 10 + + // ADR - test both positional and named to verify equivalence + result := 0; + printf('%d$N', result); // CHECK: 0 + piAddress1 := ADR(iVar1); + piAddress2 := ADR(IN := iVar1); + result := (piAddress1 = piAddress2); + printf('%d$N', result); // CHECK: 1 + + // REF - test both positional and named to verify equivalence + result := 0; + printf('%d$N', result); // CHECK: 0 + piAddress1 := REF(iVar1); // positional + piAddress2 := REF(in := iVar1); // named argument + result := (piAddress1 = piAddress2); + printf('%d$N', result); // CHECK: 1 + + // DIV + result := DIV(dividend, divisor); + printf('%d$N', result); // CHECK: 5 + + // SUB + result := SUB(dividend, subtrahend); + printf('%d$N', result); // CHECK: 13 + + test_bounds(myarray); + + main := 0; + +END_FUNCTION + +FUNCTION test_bounds : DINT +VAR_IN_OUT + vla : ARRAY [*] OF BYTE; +END_VAR +VAR + result : DINT; + x : DINT := 1; +END_VAR + + // UPPER_BOUND + result := UPPER_BOUND(vla, x); + printf('%d$N', result); // CHECK: 9 + + // LOWER_BOUND + result := LOWER_BOUND(vla, x); + printf('%d$N', result); // CHECK: 0 + +END_FUNCTION From d0731318a621e576d2e140333b44e3457951d1d9 Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Thu, 25 Sep 2025 08:21:16 +0000 Subject: [PATCH 21/27] add positional test for missing builtins --- .../builtin-positional-arguments/main.st | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/lit/single/builtin-positional-arguments/main.st b/tests/lit/single/builtin-positional-arguments/main.st index b8b2a7101e9..1d9898c2295 100644 --- a/tests/lit/single/builtin-positional-arguments/main.st +++ b/tests/lit/single/builtin-positional-arguments/main.st @@ -13,6 +13,9 @@ // - REF: Both positional and named arguments (to verify equivalence) // - DIV: positional arguments // - SUB: positional arguments +// - ADD: positional arguments +// - MUL: positional arguments +// - GT, LT, NE, LE, EQ, GE: positional comparison arguments // // For ADR and REF, both calling conventions are tested to verify they produce equivalent results. @@ -35,6 +38,10 @@ VAR dividend : DINT := 20; divisor : DINT := 4; subtrahend : DINT := 7; + multiplicand: DINT := 6; + multiplier : DINT := 7; + addend1 : DINT := 17; + addend2 : DINT := 25; END_VAR // MOVE @@ -78,6 +85,40 @@ END_VAR result := SUB(dividend, subtrahend); printf('%d$N', result); // CHECK: 13 + // ADD - positional only + result := ADD(addend1, addend2); + printf('%d$N', result); // CHECK: 42 + + // MUL - positional only + result := MUL(multiplicand, multiplier); + printf('%d$N', result); // CHECK: 42 + + x := 5; + y := 10; + // GT - positional only > + result := GT(y, x); + printf('%d$N', result); // CHECK: 1 + + // LT - positional only < + result := LT(x, y); + printf('%d$N', result); // CHECK: 1 + + // EQ - positional only == + result := EQ(b, b); + printf('%d$N', result); // CHECK: 1 + + // NE - positional only <> + result := NE(b, c); + printf('%d$N', result); // CHECK: 1 + + // GE - positional only >= + result := GE(y, x); + printf('%d$N', result); // CHECK: 1 + + // LE - positional only <= + result := LE(x, y); + printf('%d$N', result); // CHECK: 1 + test_bounds(myarray); main := 0; From 12263a14bc8057d7fd4d2b4f802caec0aaf9d8fe Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Thu, 25 Sep 2025 08:29:13 +0000 Subject: [PATCH 22/27] remove correctness tests for named builtins as we have lit tests for it --- tests/correctness/functions.rs | 58 ---------------------------------- 1 file changed, 58 deletions(-) diff --git a/tests/correctness/functions.rs b/tests/correctness/functions.rs index 70db80e8fc2..c1b75d9672b 100644 --- a/tests/correctness/functions.rs +++ b/tests/correctness/functions.rs @@ -1172,28 +1172,6 @@ fn sel_string_literal() { assert_eq!(main.res, "world\0".as_bytes()); } -#[test] -fn sel_string_literal_with_named_arguments() { - #[repr(C)] - #[derive(Default)] - struct MainType { - res: [u8; 6], - } - - let function = r#" - PROGRAM main - VAR - str1 : STRING[5]; - END_VAR - str1 := SEL(G := TRUE, IN0 := 'hello', IN1 := 'world'); // world - END_PROGRAM - "#; - - let mut main = MainType::default(); - let _: i32 = compile_and_run(function.to_string(), &mut main); - assert_eq!(main.res, "world\0".as_bytes()); -} - #[test] fn move_test() { let function = r#" @@ -1210,22 +1188,6 @@ fn move_test() { assert_eq!(res, 4) } -#[test] -fn move_test_with_named_argument() { - let function = r#" - FUNCTION main : DINT - VAR a : DINT; END_VAR - a := 4; - main := MOVE(in := a); //Result is 4 - END_FUNCTION - "#; - - let context = CodegenContext::create(); - let module = compile(&context, function); - let res: i32 = module.run_no_param("main"); - assert_eq!(res, 4) -} - #[test] fn sizeof_test() { #[derive(Debug, Default, PartialEq)] @@ -1293,26 +1255,6 @@ fn sizeof_test() { assert_eq!(expected, maintype); } -#[test] -fn sizeof_minimal_test_with_argument() { - let function = r#" - PROGRAM main - VAR - s1 : INT; - a : INT; - END_VAR - s1 := SIZEOF(in := a); // a is INT, so SIZEOF(a) = 2 - END_PROGRAM - "#; - - let context = CodegenContext::create(); - let module = compile(&context, function); - let mut res = 0; - let _: i32 = module.run("main", &mut res); - - assert_eq!(2, res); -} - #[test] #[ignore = "variable sized arrays not yet implemented"] fn sizeof_len() { From 5d6f5137a8c69dac234c77c507ab907bad4c9e5c Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Tue, 30 Sep 2025 09:30:05 +0000 Subject: [PATCH 23/27] delete dangling snapshots --- ...sts__builtin_div_with_named_arguments.snap | 23 --------- ...call_lower_bound_with_named_arguments.snap | 48 ------------------- ...sts__builtin_sub_with_named_arguments.snap | 23 --------- 3 files changed, 94 deletions(-) delete mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_div_with_named_arguments.snap delete mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_lower_bound_with_named_arguments.snap delete mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_sub_with_named_arguments.snap diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_div_with_named_arguments.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_div_with_named_arguments.snap deleted file mode 100644 index 6fb2ea21bee..00000000000 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_div_with_named_arguments.snap +++ /dev/null @@ -1,23 +0,0 @@ ---- -source: src/codegen/tests/expression_tests.rs -expression: res ---- -; ModuleID = '' -source_filename = "" -target datalayout = "[filtered]" -target triple = "[filtered]" - -define i32 @main() { -entry: - %main = alloca i32, align 4 - %x = alloca i32, align 4 - %y = alloca i32, align 4 - store i32 20, i32* %x, align 4 - store i32 4, i32* %y, align 4 - store i32 0, i32* %main, align 4 - %load_x = load i32, i32* %x, align 4 - %load_y = load i32, i32* %y, align 4 - %tmpVar = sdiv i32 %load_x, %load_y - %main_ret = load i32, i32* %main, align 4 - ret i32 %main_ret -} diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_lower_bound_with_named_arguments.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_lower_bound_with_named_arguments.snap deleted file mode 100644 index 057045aaf37..00000000000 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_lower_bound_with_named_arguments.snap +++ /dev/null @@ -1,48 +0,0 @@ ---- -source: src/codegen/tests/expression_tests.rs -expression: result ---- -; ModuleID = '' -source_filename = "" -target datalayout = "[filtered]" -target triple = "[filtered]" - -%main = type { [2 x i32], i32 } -%__foo_vla = type { i32*, [2 x i32] } - -@main_instance = global %main zeroinitializer -@____foo_vla__init = unnamed_addr constant %__foo_vla zeroinitializer - -define void @main(%main* %0) { -entry: - %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 - %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 - %auto_deref = load [2 x i32], [2 x i32]* %a, align 4 - %outer_arr_gep = getelementptr inbounds [2 x i32], [2 x i32]* %a, i32 0, i32 0 - %vla_struct = alloca %__foo_vla, align 8 - %vla_array_gep = getelementptr inbounds %__foo_vla, %__foo_vla* %vla_struct, i32 0, i32 0 - %vla_dimensions_gep = getelementptr inbounds %__foo_vla, %__foo_vla* %vla_struct, i32 0, i32 1 - store [2 x i32] [i32 0, i32 1], [2 x i32]* %vla_dimensions_gep, align 4 - store i32* %outer_arr_gep, i32** %vla_array_gep, align 8 - %1 = load %__foo_vla, %__foo_vla* %vla_struct, align 8 - %vla_struct_ptr = alloca %__foo_vla, align 8 - store %__foo_vla %1, %__foo_vla* %vla_struct_ptr, align 8 - %call = call i32 @foo(%__foo_vla* %vla_struct_ptr) - store i32 %call, i32* %b, align 4 - ret void -} - -define i32 @foo(%__foo_vla* %0) { -entry: - %foo = alloca i32, align 4 - %vla = alloca %__foo_vla*, align 8 - store %__foo_vla* %0, %__foo_vla** %vla, align 8 - store i32 0, i32* %foo, align 4 - %deref = load %__foo_vla*, %__foo_vla** %vla, align 8 - %dim = getelementptr inbounds %__foo_vla, %__foo_vla* %deref, i32 0, i32 1 - %1 = getelementptr inbounds [2 x i32], [2 x i32]* %dim, i32 0, i32 0 - %2 = load i32, i32* %1, align 4 - store i32 %2, i32* %foo, align 4 - %foo_ret = load i32, i32* %foo, align 4 - ret i32 %foo_ret -} diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_sub_with_named_arguments.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_sub_with_named_arguments.snap deleted file mode 100644 index da09e11f83a..00000000000 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_sub_with_named_arguments.snap +++ /dev/null @@ -1,23 +0,0 @@ ---- -source: src/codegen/tests/expression_tests.rs -expression: res ---- -; ModuleID = '' -source_filename = "" -target datalayout = "[filtered]" -target triple = "[filtered]" - -define i32 @main() { -entry: - %main = alloca i32, align 4 - %x = alloca i32, align 4 - %y = alloca i32, align 4 - store i32 20, i32* %x, align 4 - store i32 4, i32* %y, align 4 - store i32 0, i32* %main, align 4 - %load_x = load i32, i32* %x, align 4 - %load_y = load i32, i32* %y, align 4 - %tmpVar = sub i32 %load_x, %load_y - %main_ret = load i32, i32* %main, align 4 - ret i32 %main_ret -} From 0180846d106a5b46dfe4dd91ba04df298a514f03 Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Tue, 30 Sep 2025 09:32:24 +0000 Subject: [PATCH 24/27] =?UTF-8?q?delete=20more=20dangling=20snapshots=20?= =?UTF-8?q?=F0=9F=98=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...unction_call_move_with_named_argument.snap | 21 -------- ...function_call_sel_with_named_argument.snap | 24 ---------- ...ction_call_sizeof_with_named_argument.snap | 20 -------- ..._call_upper_bound_with_named_argument.snap | 48 ------------------- 4 files changed, 113 deletions(-) delete mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_move_with_named_argument.snap delete mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sel_with_named_argument.snap delete mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sizeof_with_named_argument.snap delete mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_upper_bound_with_named_argument.snap diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_move_with_named_argument.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_move_with_named_argument.snap deleted file mode 100644 index 63ea222a11b..00000000000 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_move_with_named_argument.snap +++ /dev/null @@ -1,21 +0,0 @@ ---- -source: src/codegen/tests/expression_tests.rs -expression: result ---- -; ModuleID = '' -source_filename = "" -target datalayout = "[filtered]" -target triple = "[filtered]" - -%main = type { i32, i32 } - -@main_instance = global %main zeroinitializer - -define void @main(%main* %0) { -entry: - %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 - %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 - %load_b = load i32, i32* %b, align 4 - store i32 %load_b, i32* %a, align 4 - ret void -} diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sel_with_named_argument.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sel_with_named_argument.snap deleted file mode 100644 index 86692757aac..00000000000 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sel_with_named_argument.snap +++ /dev/null @@ -1,24 +0,0 @@ ---- -source: src/codegen/tests/expression_tests.rs -expression: result ---- -; ModuleID = '' -source_filename = "" -target datalayout = "[filtered]" -target triple = "[filtered]" - -%main = type { i32, i32, i32 } - -@main_instance = global %main zeroinitializer - -define void @main(%main* %0) { -entry: - %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 - %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 - %c = getelementptr inbounds %main, %main* %0, i32 0, i32 2 - %load_b = load i32, i32* %b, align 4 - %load_c = load i32, i32* %c, align 4 - %1 = select i1 true, i32 %load_c, i32 %load_b - store i32 %1, i32* %a, align 4 - ret void -} diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sizeof_with_named_argument.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sizeof_with_named_argument.snap deleted file mode 100644 index bd46100bc0e..00000000000 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sizeof_with_named_argument.snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: src/codegen/tests/expression_tests.rs -expression: result ---- -; ModuleID = '' -source_filename = "" -target datalayout = "[filtered]" -target triple = "[filtered]" - -%main = type { i32, i64 } - -@main_instance = global %main zeroinitializer - -define void @main(%main* %0) { -entry: - %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 - %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 - store i32 ptrtoint (i64* getelementptr (i64, i64* null, i32 1) to i32), i32* %a, align 4 - ret void -} diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_upper_bound_with_named_argument.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_upper_bound_with_named_argument.snap deleted file mode 100644 index e966e18d20b..00000000000 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_upper_bound_with_named_argument.snap +++ /dev/null @@ -1,48 +0,0 @@ ---- -source: src/codegen/tests/expression_tests.rs -expression: result ---- -; ModuleID = '' -source_filename = "" -target datalayout = "[filtered]" -target triple = "[filtered]" - -%main = type { [2 x i32], i32 } -%__foo_vla = type { i32*, [2 x i32] } - -@main_instance = global %main zeroinitializer -@____foo_vla__init = unnamed_addr constant %__foo_vla zeroinitializer - -define void @main(%main* %0) { -entry: - %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 - %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 - %auto_deref = load [2 x i32], [2 x i32]* %a, align 4 - %outer_arr_gep = getelementptr inbounds [2 x i32], [2 x i32]* %a, i32 0, i32 0 - %vla_struct = alloca %__foo_vla, align 8 - %vla_array_gep = getelementptr inbounds %__foo_vla, %__foo_vla* %vla_struct, i32 0, i32 0 - %vla_dimensions_gep = getelementptr inbounds %__foo_vla, %__foo_vla* %vla_struct, i32 0, i32 1 - store [2 x i32] [i32 0, i32 1], [2 x i32]* %vla_dimensions_gep, align 4 - store i32* %outer_arr_gep, i32** %vla_array_gep, align 8 - %1 = load %__foo_vla, %__foo_vla* %vla_struct, align 8 - %vla_struct_ptr = alloca %__foo_vla, align 8 - store %__foo_vla %1, %__foo_vla* %vla_struct_ptr, align 8 - %call = call i32 @foo(%__foo_vla* %vla_struct_ptr) - store i32 %call, i32* %b, align 4 - ret void -} - -define i32 @foo(%__foo_vla* %0) { -entry: - %foo = alloca i32, align 4 - %vla = alloca %__foo_vla*, align 8 - store %__foo_vla* %0, %__foo_vla** %vla, align 8 - store i32 0, i32* %foo, align 4 - %deref = load %__foo_vla*, %__foo_vla** %vla, align 8 - %dim = getelementptr inbounds %__foo_vla, %__foo_vla* %deref, i32 0, i32 1 - %1 = getelementptr inbounds [2 x i32], [2 x i32]* %dim, i32 0, i32 1 - %2 = load i32, i32* %1, align 4 - store i32 %2, i32* %foo, align 4 - %foo_ret = load i32, i32* %foo, align 4 - ret i32 %foo_ret -} From 76db03e8c33b49fab9a4ab6c2f70af5cbdeaa47b Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Tue, 7 Oct 2025 09:26:50 +0200 Subject: [PATCH 25/27] refactor --- src/builtins.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/builtins.rs b/src/builtins.rs index c83ecb2df29..53268016b40 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -220,9 +220,7 @@ lazy_static! { code: |generator, params, location| { if let &[g,in0,in1] = params { // Handle named arguments by extracting actual parameters - let actual_g = extract_actual_parameter(g); - let actual_in0 = extract_actual_parameter(in0); - let actual_in1 = extract_actual_parameter(in1); + let [actual_g, actual_in0, actual_in1] = [g, in0, in1].map(extract_actual_parameter); // evaluate the parameters let cond = expression_generator::to_i1(generator.generate_expression(actual_g)?.into_int_value(), &generator.llvm.builder)?; @@ -833,8 +831,7 @@ fn validate_variable_length_array_bound_function( let params = ast::flatten_expression_list(parameters); if let &[vla, dim] = params.as_slice() { - let actual_vla = extract_actual_parameter(vla); - let actual_idx = extract_actual_parameter(dim); + let [actual_vla, actual_idx] = [vla, dim].map(extract_actual_parameter); let idx_type = annotations.get_type_or_void(actual_idx, index); @@ -915,8 +912,7 @@ fn generate_variable_length_array_bound_function<'ink>( let builder = &generator.llvm.builder; if let &[vla, dim] = params { - let actual_vla = extract_actual_parameter(vla); - let actual_dim = extract_actual_parameter(dim); + let [actual_vla, actual_dim] = [vla, dim].map(extract_actual_parameter); let data_type_information = generator.annotations.get_type_or_void(actual_vla, generator.index).get_type_information(); From 463d17cd192f74f7c03ec192b47807b539039f31 Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Tue, 7 Oct 2025 09:29:51 +0200 Subject: [PATCH 26/27] cleanup --- src/builtins.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/builtins.rs b/src/builtins.rs index 53268016b40..4e551023cdf 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -975,7 +975,6 @@ fn generate_variable_length_array_bound_function<'ink>( Ok(ExpressionValue::RValue(bound)) } else { - // Err(Diagnostic::codegen_error("Invalid signature for LOWER_BOUND/UPPER_BOUND", location)) return Err(CodegenError::GenericError( format!("Invalid signature for LOWER_BOUND/UPPER_BOUND"), location, From 19f6b47b73ee42d96445d4f6aa8fe4aa5700eed0 Mon Sep 17 00:00:00 2001 From: abroooo <61380142+abroooo@users.noreply.github.com> Date: Tue, 7 Oct 2025 09:33:23 +0200 Subject: [PATCH 27/27] style --- src/builtins.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/builtins.rs b/src/builtins.rs index 4e551023cdf..7d5b8dd43a1 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -975,10 +975,7 @@ fn generate_variable_length_array_bound_function<'ink>( Ok(ExpressionValue::RValue(bound)) } else { - return Err(CodegenError::GenericError( - format!("Invalid signature for LOWER_BOUND/UPPER_BOUND"), - location, - )); + Err(CodegenError::GenericError("Invalid signature for LOWER_BOUND/UPPER_BOUND".to_string(), location)) } }