diff --git a/src/builtins.rs b/src/builtins.rs index b3cc0fee181..7d5b8dd43a1 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -53,6 +53,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, .. }) => { @@ -99,19 +100,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 @@ -124,6 +130,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) { @@ -212,20 +219,23 @@ 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, actual_in0, actual_in1] = [g, in0, in1].map(extract_actual_parameter); + // 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, "")?; @@ -255,7 +265,8 @@ 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) + 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).into()) } @@ -275,9 +286,10 @@ lazy_static! { generic_name_resolver: no_generic_name_resolver, code : |generator, params, location| { if let [reference] = params { + let actual_param = extract_actual_parameter(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(); @@ -605,7 +617,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() { @@ -704,11 +717,30 @@ fn annotate_arithmetic_function( ctx: VisitorContext, operation: Operator, ) { - let params_flattened = flatten_expression_list(parameters); - if params_flattened.iter().any(|it| { + let params = flatten_expression_list(parameters); + let params_extracted: Vec<_> = + 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) + .filter(|(it, _)| matches!(it.get_stmt(), AstStatement::Assignment(_))) + .for_each(|(_, extracted)| { + let param_type = annotator + .annotation_map + .get_type_or_void(extracted, annotator.index) + .get_type_information() + .get_name() + .to_owned(); + annotator.annotation_map.annotate_type_hint(extracted, StatementAnnotation::value(param_type)); + }); + + 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 @@ -722,9 +754,9 @@ fn annotate_arithmetic_function( 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); + .get_type_or_void(params_extracted.first().expect("must have this parameter"), annotator.index); - for param in params_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); } @@ -736,8 +768,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.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()) }); @@ -752,25 +784,34 @@ 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; - }; - - // 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_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, StatementAnnotation::value(vla_type_name)); + 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 + 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( @@ -789,46 +830,42 @@ 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, actual_idx] = [vla, dim].map(extract_actual_parameter); - match (params.first(), params.get(1)) { - (Some(vla), Some(idx)) => { - let idx_type = annotations.get_type_or_void(idx, index); + 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(*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() { - let dimension_idx = *dimension_idx as usize; + 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), + ) + } - let Some(n_dimensions) = - annotations.get_type_or_void(vla, index).get_type_information().get_dimension_count() - else { - // not a vla, validated via type nature - return; - }; + // 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; - if dimension_idx < 1 || dimension_idx > n_dimensions { - validator.push_diagnostic( - Diagnostic::new("Index out of bound").with_error_code("E046").with_location(operator), - ) - } + 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; }; - } - (Some(_), None) => validator.push_diagnostic(Diagnostic::invalid_argument_count(2, 1, operator)), - _ => unreachable!(), + + if dimension_idx < 1 || dimension_idx > n_dimensions { + validator.push_diagnostic( + Diagnostic::new("Index out of bound").with_error_code("E046").with_location(operator), + ) + } + }; + } else { + validator.push_diagnostic(Diagnostic::invalid_argument_count(2, params.len(), operator)); } } @@ -850,6 +887,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>( @@ -860,67 +910,73 @@ fn generate_variable_length_array_bound_function<'ink>( ) -> Result, CodegenError> { let llvm = generator.llvm; let builder = &generator.llvm.builder; - let data_type_information = - generator.annotations.get_type_or_void(params[0], 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(CodegenError::GenericError( - format!("Expected VLA type, received {}", data_type_information.get_name()), - location, - )); - }; - let vla = generator.generate_lvalue(params[0]).unwrap(); - let dim = builder.build_struct_gep(vla, 1, "dim").unwrap(); + if let &[vla, dim] = params { + 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(); + + // 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(CodegenError::GenericError( + format!("Expected VLA type, received {}", data_type_information.get_name()), + location, + )); + }; + + let vla = generator.generate_lvalue(actual_vla).unwrap(); + let dim = builder.build_struct_gep(vla, 1, "dim").unwrap(); - let accessor = match params[1].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") + 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(CodegenError::GenericError( + format!("Invalid literal type. Expected INT type, received {type_name} type"), + location, + )); }; - return Err(CodegenError::GenericError( - 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(params[1])?; - 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), + // 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_dim)?; + 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), + "", + )?, "", - )?, - "", - )?; - 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(CodegenError::GenericError("Invalid signature for LOWER_BOUND/UPPER_BOUND".to_string(), location)) + } } type AnnotationFunction = fn(&mut TypeAnnotator, &AstNode, &AstNode, Option<&AstNode>, VisitorContext); diff --git a/src/codegen/tests/expression_tests.rs b/src/codegen/tests/expression_tests.rs index c31329085ed..e3695ea4bbf 100644 --- a/src/codegen/tests/expression_tests.rs +++ b/src/codegen/tests/expression_tests.rs @@ -291,7 +291,60 @@ 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] +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, @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 +363,60 @@ 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] +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, @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] @@ -352,7 +458,63 @@ 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] +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, @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] @@ -380,7 +542,57 @@ 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] +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, @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] @@ -395,7 +607,56 @@ 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] +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, @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] @@ -419,7 +680,190 @@ 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] +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, @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] +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, @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] @@ -443,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] @@ -467,6 +956,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 ", @@ -748,6 +1238,80 @@ 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, @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] +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, @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] fn global_namespace_operator() { let src = r#" 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_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_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 } diff --git a/src/validation/statement.rs b/src/validation/statement.rs index 29b714ef94e..990d6f2f926 100644 --- a/src/validation/statement.rs +++ b/src/validation/statement.rs @@ -1685,7 +1685,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/src/validation/tests/statement_validation_tests.rs b/src/validation/tests/statement_validation_tests.rs index bb44fccae86..89199eaa652 100644 --- a/src/validation/tests/statement_validation_tests.rs +++ b/src/validation/tests/statement_validation_tests.rs @@ -723,6 +723,417 @@ 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( + " + 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 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); + END_FUNCTION + ", + ); + + 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] +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, @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] fn address_of_operations() { let diagnostics = parse_and_validate_buffered( 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..6d65288a71e --- /dev/null +++ b/tests/lit/single/builtin-named-arguments/main.st @@ -0,0 +1,80 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s +// +// 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 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 +// +// 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; + + 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 + y := MOVE(IN := x); + printf('%d$N', y); // CHECK: 9 + + // SEL + a := SEL(G := TRUE, IN0 := b, IN1 := c); + printf('%d$N', a); // CHECK: 4 + + // SIZEOF + result := SIZEOF(in := myarray); + printf('%d$N', result); // CHECK: 10 + + // DIV + result := DIV(IN1 := dividend, IN2 := divisor); + printf('%d$N', result); // CHECK: 5 + + // SUB + result := SUB(IN1 := dividend, IN2 := 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(arr := vla, dim := x); + printf('%d$N', result); // CHECK: 9 + + // LOWER_BOUND + result := LOWER_BOUND(arr := vla, dim := x); + printf('%d$N', result); // CHECK: 0 + +END_FUNCTION 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..1d9898c2295 --- /dev/null +++ b/tests/lit/single/builtin-positional-arguments/main.st @@ -0,0 +1,145 @@ +// 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 +// - 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. + +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; + multiplicand: DINT := 6; + multiplier : DINT := 7; + addend1 : DINT := 17; + addend2 : DINT := 25; +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 + + // 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; + +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