Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ lazy_static! {
E124, Error, include_str!("./error_codes/E124.md"), // Invalid escape sequence in string literal
E125, Error, include_str!("./error_codes/E125.md"), // Incompatible POINTER TO types in class/FB hierarchy
E126, Error, include_str!("./error_codes/E126.md"), // Incompatible types in interface polymorphism
E127, Warning, include_str!("./error_codes/E127.md"), // Array initialized with fewer elements than expected
);
}

Expand Down
25 changes: 25 additions & 0 deletions compiler/plc_diagnostics/src/diagnostics/error_codes/E127.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Array initialized with fewer elements than expected

An array initializer provides fewer values than the declared array size.
The remaining elements will be zero-initialized.

Example:

```st
VAR
arr : ARRAY[1..5] OF DINT := [1, 2, 3]; // only 3 of 5 elements
END_VAR
```

This is a warning because the compiler will implicitly fill the unspecified
elements with their default (zero) value, which may or may not be intentional.

To silence this warning, provide all elements explicitly or use a multiplied
initializer to fill the rest:

```st
VAR
arr : ARRAY[1..5] OF DINT := [1, 2, 3, 0, 0]; // explicit
arr2 : ARRAY[1..5] OF DINT := [1, 2, 3, 2(0)]; // multiplied fill
END_VAR
```
18 changes: 10 additions & 8 deletions src/codegen/generators/expression_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2262,7 +2262,10 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
AstLiteral::Time(t) => self.create_const_int(t.value()).map(ExpressionValue::RValue),
AstLiteral::String(s) => self.generate_string_literal(literal_statement, s.value(), location),
AstLiteral::Array(arr) => self
.generate_literal_array(arr.elements().ok_or_else(cannot_generate_literal)?)
.generate_literal_array(
arr.elements().ok_or_else(cannot_generate_literal)?,
Some(self.get_type_hint_info_for(literal_statement)?),
)
.map(ExpressionValue::RValue),
AstLiteral::Null => self.llvm.create_null_ptr().map(ExpressionValue::RValue),
},
Expand All @@ -2279,15 +2282,15 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
}

AstStatement::MultipliedStatement { .. } => {
self.generate_literal_array(literal_statement).map(ExpressionValue::RValue)
self.generate_literal_array(literal_statement, None).map(ExpressionValue::RValue)
}
AstStatement::ParenExpression(expr) => self.generate_literal(expr),
// if there is an expression-list this might be a struct-initialization or array-initialization
AstStatement::ExpressionList { .. } => {
let type_hint = self.get_type_hint_info_for(literal_statement)?;
match type_hint {
DataTypeInformation::Array { .. } => {
self.generate_literal_array(literal_statement).map(ExpressionValue::RValue)
self.generate_literal_array(literal_statement, None).map(ExpressionValue::RValue)
}
_ => self.generate_literal_struct(literal_statement),
}
Expand Down Expand Up @@ -2517,12 +2520,11 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
pub fn generate_literal_array(
&self,
initializer: &AstNode,
type_hint: Option<&DataTypeInformation>,
) -> Result<BasicValueEnum<'ink>, CodegenError> {
let array_value = self.generate_literal_array_value(
initializer,
self.get_type_hint_info_for(initializer)?,
&initializer.get_location(),
)?;
let data_type = if let Some(dt) = type_hint { dt } else { self.get_type_hint_info_for(initializer)? };
let array_value =
self.generate_literal_array_value(initializer, data_type, &initializer.get_location())?;
Ok(array_value.as_basic_value_enum())
}

Expand Down
63 changes: 47 additions & 16 deletions src/validation/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//! introduced to make the validation code as generic as possible.

use plc_ast::{
ast::{AstNode, AstStatement, Variable},
ast::{AstNode, AstStatement, UserTypeDeclaration, Variable},
literals::AstLiteral,
};
use plc_diagnostics::diagnostics::Diagnostic;
Expand All @@ -20,11 +20,12 @@ use crate::{resolver::AnnotationMap, typesystem::DataTypeInformation};

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

/// Indicates whether an array was assigned in a VAR block or a POU body
/// Indicates whether an array was assigned in a VAR block, a POU body, or a TYPE declaration
#[derive(Debug, Clone, Copy)]
pub(super) enum StatementWrapper<'a> {
Statement(&'a AstNode),
Variable(&'a Variable),
TypeDeclaration(&'a UserTypeDeclaration),
}

impl<'a> From<&'a AstNode> for StatementWrapper<'a> {
Expand All @@ -39,6 +40,12 @@ impl<'a> From<&'a Variable> for StatementWrapper<'a> {
}
}

impl<'a> From<&'a UserTypeDeclaration> for StatementWrapper<'a> {
fn from(value: &'a UserTypeDeclaration) -> Self {
Self::TypeDeclaration(value)
}
}

pub(super) fn validate_array_assignment<'a, T, S>(
validator: &mut Validator,
context: &ValidationContext<T>,
Expand Down Expand Up @@ -78,7 +85,7 @@ fn validate_array<T: AnnotationMap>(
}

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

if len_lhs == 0 {
return;
Expand All @@ -89,11 +96,21 @@ fn validate_array<T: AnnotationMap>(
let location = stmt_rhs.get_location();
validator.push_diagnostic(
Diagnostic::new(format!(
"Array `{name}` has a size of {len_lhs}, but {len_rhs} elements were provided"
"Too many initial values for array `{name}`. Expected {len_lhs}, found {len_rhs}."
))
.with_error_code("E043")
Comment thread
ghaith marked this conversation as resolved.
.with_location(location),
);
} else if len_rhs < len_lhs {
let name = statement.lhs_name(validator.context);
let location = stmt_rhs.get_location();
validator.push_diagnostic(
Diagnostic::new(format!(
"Fewer initial values for array `{name}` than expected. Expected {len_lhs}, found {len_rhs}."
))
.with_error_code("E127")
.with_location(location),
);
}
}

Expand Down Expand Up @@ -145,34 +162,40 @@ fn validate_array_of_structs<T: AnnotationMap>(
}

/// Takes an [`AstStatementKind`] and returns its length as if it was an array. For example calling this function
/// on an expression-list such as `[(...), (...)]` would return 2.
fn statement_to_array_length<T: AnnotationMap>(context: &ValidationContext<T>, statement: &AstNode) -> usize {
/// on an expression-list such as `[(...), (...)]` would return 2. Returns `None` if the length could not be
/// determined (e.g. for unresolved calls or unknown AST nodes), signaling that size validation should be skipped.
fn statement_to_array_length<T: AnnotationMap>(
context: &ValidationContext<T>,
statement: &AstNode,
) -> Option<usize> {
match statement.get_stmt() {
AstStatement::Literal(AstLiteral::Array(arr)) => match arr.elements() {
Some(AstNode { stmt: AstStatement::ExpressionList(expressions), .. }) => {
expressions.iter().map(|it| statement_to_array_length(context, it)).sum::<usize>()
let mut total = 0;
for it in expressions {
total += statement_to_array_length(context, it)?;
}
Some(total)
}

Some(any) => statement_to_array_length(context, any),
None => 0,
None => Some(0),
},

AstStatement::CallStatement(_) => context
.annotations
.get_type(statement, context.index)
.and_then(|it| it.information.get_array_length(context.index))
.unwrap_or(0),
.and_then(|it| it.information.get_array_length(context.index)),

AstStatement::MultipliedStatement(data) => data.multiplier as usize,
AstStatement::ExpressionList { .. } | AstStatement::ParenExpression(_) => 1,
AstStatement::MultipliedStatement(data) => Some(data.multiplier as usize),
AstStatement::ExpressionList { .. } | AstStatement::ParenExpression(_) => Some(1),

// Any literal other than an array can be counted as 1
AstStatement::Literal { .. } => 1,
AstStatement::Literal { .. } => Some(1),

_any => {
// XXX: Not sure what else could be in here
log::debug!("Array size-counting for {statement:?} not covered; validation _might_ be wrong");
0
log::debug!("Array size-counting for {statement:?} not covered; skipping validation");
None
}
}
}
Expand All @@ -185,6 +208,9 @@ impl<'a> StatementWrapper<'a> {
let AstStatement::Assignment(data) = &statement.stmt else { return "".to_string() };
context.slice(&data.left.location)
}
StatementWrapper::TypeDeclaration(utd) => {
utd.data_type.get_name().map(|s| s.to_string()).unwrap_or_default()
}
}
}

Expand All @@ -195,6 +221,7 @@ impl<'a> StatementWrapper<'a> {
let AstStatement::Assignment(data) = &statement.stmt else { return None };
Some(&data.right)
}
StatementWrapper::TypeDeclaration(utd) => utd.initializer.as_ref(),
}
}

Expand All @@ -212,6 +239,10 @@ impl<'a> StatementWrapper<'a> {
.data_type_declaration
.get_referenced_type()
.and_then(|it| context.index.find_effective_type_info(it)),

StatementWrapper::TypeDeclaration(utd) => {
utd.data_type.get_name().and_then(|name| context.index.find_effective_type_info(name))
}
}?;

match ty {
Expand Down
Loading
Loading