Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 40 additions & 8 deletions libs/stdlib/iec61131-st/bit_num_conversion.st
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,11 @@ FUNCTION BOOL_TO_LINT : LINT
VAR_INPUT
in : BOOL;
END_VAR
BOOL_TO_LINT := in;
IF in THEN
BOOL_TO_LINT := 1;
ELSE
BOOL_TO_LINT := 0;
END_IF;
END_FUNCTION

(********************
Expand All @@ -427,7 +431,11 @@ FUNCTION BOOL_TO_DINT : DINT
VAR_INPUT
in : BOOL;
END_VAR
BOOL_TO_DINT := in;
IF in THEN
BOOL_TO_DINT := 1;
ELSE
BOOL_TO_DINT := 0;
END_IF;
END_FUNCTION

(********************
Expand All @@ -439,7 +447,11 @@ FUNCTION BOOL_TO_INT : INT
VAR_INPUT
in : BOOL;
END_VAR
BOOL_TO_INT := in;
IF in THEN
BOOL_TO_INT := 1;
ELSE
BOOL_TO_INT := 0;
END_IF;
END_FUNCTION

(********************
Expand All @@ -451,7 +463,11 @@ FUNCTION BOOL_TO_SINT : SINT
VAR_INPUT
in : BOOL;
END_VAR
BOOL_TO_SINT := in;
IF in THEN
BOOL_TO_SINT := 1;
ELSE
BOOL_TO_SINT := 0;
END_IF;
END_FUNCTION

(********************
Expand All @@ -463,7 +479,11 @@ FUNCTION BOOL_TO_ULINT : ULINT
VAR_INPUT
in : BOOL;
END_VAR
BOOL_TO_ULINT := in;
IF in THEN
BOOL_TO_ULINT := 1;
ELSE
BOOL_TO_ULINT := 0;
END_IF;
END_FUNCTION

(********************
Expand All @@ -475,7 +495,11 @@ FUNCTION BOOL_TO_UDINT : UDINT
VAR_INPUT
in : BOOL;
END_VAR
BOOL_TO_UDINT := in;
IF in THEN
BOOL_TO_UDINT := 1;
ELSE
BOOL_TO_UDINT := 0;
END_IF;
END_FUNCTION

(********************
Expand All @@ -487,7 +511,11 @@ FUNCTION BOOL_TO_UINT : UINT
VAR_INPUT
in : BOOL;
END_VAR
BOOL_TO_UINT := in;
IF in THEN
BOOL_TO_UINT := 1;
ELSE
BOOL_TO_UINT := 0;
END_IF;
END_FUNCTION

(********************
Expand All @@ -499,7 +527,11 @@ FUNCTION BOOL_TO_USINT : USINT
VAR_INPUT
in : BOOL;
END_VAR
BOOL_TO_USINT := in;
IF in THEN
BOOL_TO_USINT := 1;
ELSE
BOOL_TO_USINT := 0;
END_IF;
END_FUNCTION

(********************
Expand Down
15 changes: 13 additions & 2 deletions src/codegen/generators/expression_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2907,8 +2907,19 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
);
for e in elements {
//generate with correct type hint using context-free generator
let value = ctx_free_gen.generate_literal(e)?;
v.push(value.get_basic_value_enum());
let value = ctx_free_gen.generate_literal(e)?.get_basic_value_enum();
// numeric literals take the type of the array element, e.g. `1.5` in an ARRAY OF INT
let value = match value {
BasicValueEnum::IntValue(_) | BasicValueEnum::FloatValue(_) => cast_if_needed!(
ctx_free_gen,
inner_type,
self.annotations.get_type_or_void(e, self.index),
value,
self.annotations.get(e)
)?,
_ => value,
};
v.push(value);
}

if v.len() < expected_len {
Expand Down
51 changes: 51 additions & 0 deletions src/codegen/tests/initialization_test/pou_initializers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,3 +489,54 @@ fn pou_local_constant_as_array_bound_of_a_constructed_element_type() {
assert!(result.contains("icmp sgt i32 %load___prog_arr__idx0, 5"), "{result}");
assert!(!result.contains("DINT_GREATER"), "{result}");
}

#[test]
fn numeric_array_literal_elements_are_cast_to_the_element_type() {
let result = codegen(
r#"
FUNCTION main : DINT
VAR
ints : ARRAY[0..1] OF INT := [1.5, 2];
dints : ARRAY[0..1] OF DINT := [2.5, 3.75];
later : ARRAY[0..1] OF INT;
END_VAR
later := [4.5, 5];
END_FUNCTION
"#,
);

filtered_assert_snapshot!(result, @r#"
; ModuleID = '<internal>'
source_filename = "<internal>"
target datalayout = "[filtered]"
target triple = "[filtered]"

@__main.ints__init = unnamed_addr constant [2 x i16] [i16 1, i16 2]
@__main.dints__init = unnamed_addr constant [2 x i32] [i32 2, i32 3]
@.const_init = private unnamed_addr constant [2 x i16] [i16 4, i16 5]

define i32 @main() {
entry:
%main = alloca i32, align [filtered]
%ints = alloca [2 x i16], align [filtered]
%dints = alloca [2 x i32], align [filtered]
%later = alloca [2 x i16], align [filtered]
call void @llvm.memcpy.p0.p0.i64(ptr align [filtered] %ints, ptr align [filtered] @__main.ints__init, i64 ptrtoint (ptr getelementptr ([2 x i16], ptr null, i32 1) to i64), i1 false)
call void @llvm.memcpy.p0.p0.i64(ptr align [filtered] %dints, ptr align [filtered] @__main.dints__init, i64 ptrtoint (ptr getelementptr ([2 x i32], ptr null, i32 1) to i64), i1 false)
call void @llvm.memset.p0.i64(ptr align [filtered] %later, i8 0, i64 ptrtoint (ptr getelementptr ([2 x i16], ptr null, i32 1) to i64), i1 false)
store i32 0, ptr %main, align [filtered]
call void @llvm.memcpy.p0.p0.i64(ptr align [filtered] %later, ptr align [filtered] @.const_init, i64 ptrtoint (ptr getelementptr ([2 x i16], ptr null, i32 1) to i64), i1 false)
%main_ret = load i32, ptr %main, align [filtered]
ret i32 %main_ret
}

; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
declare void @llvm.memcpy.p0.p0.i64(ptr noalias writeonly captures(none), ptr noalias readonly captures(none), i64, i1 immarg) #0

; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: write)
declare void @llvm.memset.p0.i64(ptr writeonly captures(none), i8, i64, i1 immarg) #1

attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
attributes #1 = { nocallback nofree nounwind willreturn memory(argmem: write) }
"#);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ target triple = "[filtered]"
@e = global [3 x i8] c"\01\02\03"
@f = global [3 x i16] [i16 1, i16 2, i16 3]
@g = global [3 x i64] [i64 1, i64 2, i64 3]
@h = global [3 x i8] [i8 true, i8 false, i8 true]
@h = global [3 x i8] c"\01\00\01"
4 changes: 2 additions & 2 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1169,6 +1169,7 @@ fn parse_pointer_definition(
is_function: bool,
) -> Option<(DataTypeDeclaration, Option<AstNode>)> {
parse_data_type_definition(lexer, None).map(|(decl, initializer)| {
let end = decl.get_location().to_range().map_or(lexer.last_range.end, |range| range.end);
(
DataTypeDeclaration::Definition {
data_type: Box::new(DataType::PointerType {
Expand All @@ -1178,8 +1179,7 @@ fn parse_pointer_definition(
type_safe,
is_function,
}),
// FIXME: this currently includes the initializer in the sourcelocation, resulting in 'REF_TO A := B' when creating a slice
location: lexer.source_range_factory.create_range(start_pos..lexer.last_range.end),
location: lexer.source_range_factory.create_range(start_pos..end),
scope: lexer.scope.clone(),
},
initializer,
Expand Down
34 changes: 22 additions & 12 deletions src/resolver/const_evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -738,18 +738,28 @@ fn get_cast_statement_literal(
) -> Result<AstNode, UnresolvableKind> {
let dti = index.find_effective_type_info(type_name);
match dti {
Some(&DataTypeInformation::Integer { .. }) => {
let evaluated_initial =
evaluate_with_target_hint(cast_statement, scope, index, Some(type_name), lhs)?
.as_ref()
.map(|v| {
if let AstStatement::Literal(AstLiteral::Integer(value)) = v.get_stmt() {
Ok(*value)
} else {
Err(UnresolvableKind::Misc(format!("Expected integer value, found {v:?}")))
}
})
.transpose()?;
Some(dti @ &DataTypeInformation::Integer { .. }) => {
let evaluated = evaluate_with_target_hint(cast_statement, scope, index, Some(type_name), lhs)?;
// BOOL#TRUE stays a BOOL literal
if let (true, Some(AstStatement::Literal(AstLiteral::Bool(value)))) =
(dti.is_bool(), evaluated.as_ref().map(|it| it.get_stmt()))
{
return Ok(AstNode::new(
AstStatement::Literal(AstLiteral::Bool(*value)),
cast_statement.get_id(),
cast_statement.get_location(),
));
}
let evaluated_initial = evaluated
.as_ref()
.map(|v| {
if let AstStatement::Literal(AstLiteral::Integer(value)) = v.get_stmt() {
Ok(*value)
} else {
Err(UnresolvableKind::Misc(format!("Expected integer value, found {v:?}")))
}
})
.transpose()?;

if let Some(value) = evaluated_initial {
return Ok(AstNode::new(
Expand Down
50 changes: 49 additions & 1 deletion src/validation/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use plc_index::GlobalContext;

use crate::{resolver::AnnotationMap, typesystem::DataTypeInformation};

use super::{ValidationContext, Validator, Validators};
use super::{statement::validate_assignment, ValidationContext, Validator, Validators};

/// Indicates whether an array was assigned in a VAR block, a POU body, or a TYPE declaration
#[derive(Debug, Clone, Copy)]
Expand Down Expand Up @@ -92,6 +92,8 @@ fn validate_array<T: AnnotationMap>(
return; // Return here, because array size validation is error-prone with incorrect assignments
}

validate_array_elements(validator, context, lhs_type, stmt_rhs);

let len_lhs = lhs_type.get_array_length(context.index).unwrap_or(0);
let Some(len_rhs) = statement_to_array_length(context, stmt_rhs) else { return };

Expand Down Expand Up @@ -122,6 +124,52 @@ fn validate_array<T: AnnotationMap>(
}
}

/// Validates every element of an array literal against the element type, e.g. `[TRUE]` is not an
/// `ARRAY OF DINT`. Struct elements are validated through their member assignments.
fn validate_array_elements<T: AnnotationMap>(
validator: &mut Validator,
context: &ValidationContext<T>,
array_type: &DataTypeInformation,
literal: &AstNode,
) {
let AstStatement::Literal(AstLiteral::Array(array)) = literal.get_stmt() else { return };
let Some(elements) = array.elements() else { return };
let Some(inner_type) =
array_type.get_inner_array_type_name().and_then(|name| context.index.find_effective_type_info(name))
else {
return;
};
if inner_type.is_struct() {
return;
}

for element in array_literal_elements(elements) {
if inner_type.is_array() && element.is_literal_array() {
validate_array_elements(validator, context, inner_type, element);
continue;
}
// spliced array references and flat initializers of nested arrays carry array types
let element_is_array = context.annotations.get_type_or_void(element, context.index).is_array();
let hint_is_array =
context.annotations.get_type_hint(element, context.index).is_some_and(|hint| hint.is_array());
if !element_is_array && !hint_is_array {
validate_assignment(validator, element, None, &element.location, context);
}
}
}

/// Returns the element expressions of an array literal, each multiplied element once
fn array_literal_elements(node: &AstNode) -> Vec<&AstNode> {
match node.get_stmt() {
AstStatement::ExpressionList(expressions) => {
expressions.iter().flat_map(array_literal_elements).collect()
}
AstStatement::MultipliedStatement(data) => array_literal_elements(&data.element),
AstStatement::ParenExpression(expression) => array_literal_elements(expression),
_ => vec![node],
}
}

/// Checks if an expression is a valid element in an array of structs.
/// Valid elements are:
/// - Parenthesized expressions (struct initializers like `(a := 1, b := 2)`)
Expand Down
33 changes: 31 additions & 2 deletions src/validation/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,10 @@ pub fn visit_statement<T: AnnotationMap>(
visit_statement(validator, &data.left, context);
visit_statement(validator, &data.right, context);

validate_assignment(validator, &data.right, Some(&data.left), &statement.location, context);
// lowered copies of variable initializers are validated at their declaration
if !statement.location.is_internal() {
validate_assignment(validator, &data.right, Some(&data.left), &statement.location, context);
}
validate_array_assignment(validator, context, statement);
}
AstStatement::OutputAssignment(data) => {
Expand Down Expand Up @@ -608,6 +611,13 @@ fn validate_cast_literal<T: AnnotationMap>(
.with_error_code("E061")
.with_location(location),
)
} else if matches!(literal, AstLiteral::Bool(_)) && !cast_type.is_bool() {
// a BOOL literal is no numeric value, e.g. DINT#TRUE
validator.push_diagnostic(incompatible_literal_cast(
cast_type.get_name(),
literal.get_literal_value().as_str(),
location.clone(),
));
} else if cast_type.is_date_or_time_type() || literal_type.is_date_or_time_type() {
validator.push_diagnostic(incompatible_literal_cast(
cast_type.get_name(),
Expand Down Expand Up @@ -1557,7 +1567,7 @@ fn validate_alias_assignment<T: AnnotationMap>(
}
}

fn validate_assignment<T: AnnotationMap>(
pub(super) fn validate_assignment<T: AnnotationMap>(
validator: &mut Validator,
right: &AstNode,
left: Option<&AstNode>,
Expand Down Expand Up @@ -2067,6 +2077,8 @@ fn is_valid_assignment(
// because those would fail
return true;
} else if is_invalid_char_assignment(left_type.get_type_information(), right_type.get_type_information())
| is_invalid_bool_assignment(left_type.get_type_information(), right_type.get_type_information())
| is_invalid_bool_literal_assignment(left_type.get_type_information(), right)
| is_invalid_pointer_assignment(
left_type.get_type_information(),
right_type.get_type_information(),
Expand Down Expand Up @@ -2171,6 +2183,23 @@ fn is_invalid_char_assignment(left_type: &DataTypeInformation, right_type: &Data
false
}

/// a BOOL value is not implicitly converted to another numeric type
fn is_invalid_bool_assignment(left_type: &DataTypeInformation, right_type: &DataTypeInformation) -> bool {
right_type.is_bool() && left_type.is_numerical() && !left_type.is_bool()
}

/// only the literals 0 and 1 are BOOL values
fn is_invalid_bool_literal_assignment(left_type: &DataTypeInformation, right: &AstNode) -> bool {
if !left_type.is_bool() {
return false;
}
match right.get_stmt_peeled() {
AstStatement::Literal(AstLiteral::Integer(value)) => *value != 0 && *value != 1,
AstStatement::Literal(AstLiteral::Real(_)) => true,
_ => false,
}
}

/// aggregate types can only be assigned to aggregate types
/// special case char := string_with_length_1, handled by `is_valid_string_to_char_assignment()`
fn is_aggregate_to_none_aggregate_assignment(left_type: &DataType, right_type: &DataType) -> bool {
Expand Down
Loading
Loading