Skip to content

Commit b357a05

Browse files
mhaselriedermvolsa
authored
fix: false-positive downcast warnings (PLC-lang#1114)
* fix: false-positive downcast warnings Fixes false-positive downcast warnings on integrally promoted expressions. This is achieved by checking each element's type in the expression individually and comparing it to the resulting type rather than checking the expression's type as a whole. --------- Co-authored-by: Mathias Rieder <[email protected]> Co-authored-by: Volkan Sagcan <[email protected]>
1 parent 3e84974 commit b357a05

File tree

34 files changed

+890
-97
lines changed

34 files changed

+890
-97
lines changed

compiler/plc_ast/src/ast.rs

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,20 +1057,8 @@ pub fn pre_process(unit: &mut CompilationUnit, id_provider: IdProvider) {
10571057
pre_processor::pre_process(unit, id_provider)
10581058
}
10591059
impl Operator {
1060-
/// returns true, if this operator results in a bool value
1061-
pub fn is_bool_type(&self) -> bool {
1062-
matches!(
1063-
self,
1064-
Operator::Equal
1065-
| Operator::NotEqual
1066-
| Operator::Less
1067-
| Operator::Greater
1068-
| Operator::LessOrEqual
1069-
| Operator::GreaterOrEqual
1070-
)
1071-
}
1072-
1073-
/// returns true, if this operator is a comparison operator
1060+
/// returns true, if this operator is a comparison operator,
1061+
/// resulting in a bool value
10741062
/// (=, <>, >, <, >=, <=)
10751063
pub fn is_comparison_operator(&self) -> bool {
10761064
matches!(

compiler/plc_ast/src/literals.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,18 @@ impl AstLiteral {
259259
| AstLiteral::DateAndTime { .. }
260260
)
261261
}
262+
263+
pub fn is_numerical(&self) -> bool {
264+
matches!(
265+
self,
266+
AstLiteral::Integer { .. }
267+
| AstLiteral::Real { .. }
268+
| AstLiteral::Time { .. }
269+
| AstLiteral::Date { .. }
270+
| AstLiteral::TimeOfDay { .. }
271+
| AstLiteral::DateAndTime { .. }
272+
)
273+
}
262274
}
263275

264276
impl Debug for AstLiteral {

compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ lazy_static! {
300300
Error,
301301
include_str!("./error_codes/E066.md"),
302302
E067,
303-
Info,
303+
Warning,
304304
include_str!("./error_codes/E067.md"), //Implicit typecast
305305
E068,
306306
Error,

src/resolver.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1327,7 +1327,7 @@ impl<'i> TypeAnnotator<'i> {
13271327
)
13281328
};
13291329

1330-
let target_name = if data.operator.is_bool_type() {
1330+
let target_name = if data.operator.is_comparison_operator() {
13311331
BOOL_TYPE.to_string()
13321332
} else {
13331333
bigger_type.get_name().to_string()

src/validation.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ mod variable;
3030
#[cfg(test)]
3131
mod tests;
3232

33-
#[derive(Clone)]
3433
pub struct ValidationContext<'s, T: AnnotationMap> {
3534
annotations: &'s T,
3635
index: &'s Index,
@@ -86,6 +85,17 @@ impl<'s, T: AnnotationMap> ValidationContext<'s, T> {
8685
}
8786
}
8887

88+
impl<'s, T: AnnotationMap> Clone for ValidationContext<'s, T> {
89+
fn clone(&self) -> Self {
90+
ValidationContext {
91+
annotations: self.annotations,
92+
index: self.index,
93+
qualifier: self.qualifier,
94+
is_call: self.is_call,
95+
}
96+
}
97+
}
98+
8999
/// This trait should be implemented by any validator used by `validation::Validator`
90100
pub trait Validators {
91101
fn push_diagnostic(&mut self, diagnostic: Diagnostic);

src/validation/statement.rs

Lines changed: 99 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use std::{collections::HashSet, mem::discriminant};
33
use plc_ast::control_statements::ForLoopStatement;
44
use plc_ast::{
55
ast::{
6-
flatten_expression_list, AstNode, AstStatement, DirectAccess, DirectAccessType, JumpStatement,
7-
Operator, ReferenceAccess,
6+
flatten_expression_list, AstNode, AstStatement, BinaryExpression, CallStatement, DirectAccess,
7+
DirectAccessType, JumpStatement, Operator, ReferenceAccess, UnaryExpression,
88
},
99
control_statements::{AstControlStatement, ConditionalBlock},
1010
literals::{Array, AstLiteral, StringValue},
@@ -833,9 +833,8 @@ fn validate_assignment<T: AnnotationMap>(
833833
location.clone(),
834834
));
835835
}
836-
} else if right.is_literal() {
837-
// TODO: See https://github.com/PLC-lang/rusty/issues/857
838-
// validate_assignment_type_sizes(validator, left_type, right_type, location, context)
836+
} else {
837+
validate_assignment_type_sizes(validator, left_type, right, context)
839838
}
840839
}
841840
}
@@ -1343,26 +1342,106 @@ fn validate_type_nature<T: AnnotationMap>(
13431342
}
13441343
}
13451344

1346-
fn _validate_assignment_type_sizes<T: AnnotationMap>(
1345+
fn validate_assignment_type_sizes<T: AnnotationMap>(
13471346
validator: &mut Validator,
13481347
left: &DataType,
1349-
right: &DataType,
1350-
location: &SourceLocation,
1348+
right: &AstNode,
13511349
context: &ValidationContext<T>,
13521350
) {
1353-
if left.get_type_information().get_size(context.index)
1354-
< right.get_type_information().get_size(context.index)
1355-
{
1356-
validator.push_diagnostic(
1357-
Diagnostic::new(format!(
1358-
"Potential loss of information due to assigning '{}' to variable of type '{}'.",
1359-
left.get_name(),
1360-
right.get_name()
1361-
))
1362-
.with_error_code("E067")
1363-
.with_location(location.clone()),
1364-
)
1351+
use std::collections::HashMap;
1352+
fn get_expression_types_and_locations<'b, T: AnnotationMap>(
1353+
expression: &AstNode,
1354+
context: &'b ValidationContext<T>,
1355+
lhs_is_signed_int: bool,
1356+
is_builtin_call: bool,
1357+
) -> HashMap<&'b DataType, Vec<SourceLocation>> {
1358+
let mut map: HashMap<&DataType, Vec<SourceLocation>> = HashMap::new();
1359+
match expression.get_stmt_peeled() {
1360+
AstStatement::BinaryExpression(BinaryExpression { operator, left, right, .. })
1361+
if !operator.is_comparison_operator() =>
1362+
{
1363+
get_expression_types_and_locations(left, context, lhs_is_signed_int, false)
1364+
.into_iter()
1365+
.for_each(|(k, v)| map.entry(k).or_default().extend(v));
1366+
// the RHS type in a MOD expression has no impact on the resulting value type
1367+
if matches!(operator, Operator::Modulo) {
1368+
return map
1369+
};
1370+
get_expression_types_and_locations(right, context, lhs_is_signed_int, false)
1371+
.into_iter()
1372+
.for_each(|(k, v)| map.entry(k).or_default().extend(v));
1373+
}
1374+
AstStatement::UnaryExpression(UnaryExpression { operator, value })
1375+
if !operator.is_comparison_operator() =>
1376+
{
1377+
get_expression_types_and_locations(value, context, lhs_is_signed_int, false)
1378+
.into_iter()
1379+
.for_each(|(k, v)| map.entry(k).or_default().extend(v));
1380+
}
1381+
// `get_literal_actual_signed_type_name` will always return `LREAL` for FP literals, so they will be handled by the fall-through case according to their annotated type
1382+
AstStatement::Literal(lit) if !matches!(lit, &AstLiteral::Real(_)) => {
1383+
if !lit.is_numerical() {
1384+
return map
1385+
}
1386+
if let Some(dt) = get_literal_actual_signed_type_name(lit, lhs_is_signed_int)
1387+
.map(|name| context.index.get_type(name).unwrap_or(context.index.get_void_type()))
1388+
{
1389+
map.entry(dt).or_default().push(expression.get_location());
1390+
}
1391+
}
1392+
AstStatement::CallStatement(CallStatement { operator, parameters })
1393+
// special handling for builtin selector functions MUX and SEL
1394+
if matches!(operator.get_flat_reference_name().unwrap_or_default(), "MUX" | "SEL") =>
1395+
{
1396+
let Some(args) = parameters else {
1397+
return map
1398+
};
1399+
if let AstStatement::ExpressionList(list) = args.get_stmt_peeled() {
1400+
// skip the selector argument since it will never be assigned to the target type
1401+
list.iter().skip(1).flat_map(|arg| {
1402+
get_expression_types_and_locations(arg, context, lhs_is_signed_int, true)
1403+
})
1404+
.for_each(|(k, v)| map.entry(k).or_default().extend(v));
1405+
};
1406+
}
1407+
_ => {
1408+
if !(context.annotations.get_generic_nature(expression).is_none() || is_builtin_call) {
1409+
return map
1410+
};
1411+
if let Some(dt) = context.annotations.get_type(expression, context.index) {
1412+
map.entry(dt).or_default().push(expression.get_location());
1413+
}
1414+
}
1415+
};
1416+
map
13651417
}
1418+
1419+
let lhs = left.get_type_information();
1420+
let lhs_size = lhs.get_size(context.index);
1421+
let results_in_truncation = |rhs: &DataType| {
1422+
let rhs = rhs.get_type_information();
1423+
let rhs_size = rhs.get_size(context.index);
1424+
lhs_size < rhs_size
1425+
|| (lhs_size == rhs_size
1426+
&& ((lhs.is_signed_int() && rhs.is_unsigned_int()) || (lhs.is_int() && rhs.is_float())))
1427+
};
1428+
1429+
get_expression_types_and_locations(right, context, lhs.is_signed_int(), false)
1430+
.into_iter()
1431+
.filter(|(dt, _)| !dt.is_aggregate_type() && results_in_truncation(dt))
1432+
.for_each(|(dt, location)| {
1433+
location.into_iter().for_each(|loc| {
1434+
validator.push_diagnostic(
1435+
Diagnostic::new(format!(
1436+
"Implicit downcast from '{}' to '{}'.",
1437+
get_datatype_name_or_slice(validator.context, dt),
1438+
get_datatype_name_or_slice(validator.context, left)
1439+
))
1440+
.with_error_code("E067")
1441+
.with_location(loc),
1442+
);
1443+
})
1444+
});
13661445
}
13671446

13681447
mod helper {

0 commit comments

Comments
 (0)