From 54c78d9cdb9669143a3583813a639b742d188deb Mon Sep 17 00:00:00 2001 From: Ghaith Hachem Date: Mon, 10 Jun 2024 16:25:08 +0200 Subject: [PATCH 1/2] ci: rename workflow, update readme --- .github/workflows/{rust.yml => linux.yml} | 0 README.md | 6 +++++- 2 files changed, 5 insertions(+), 1 deletion(-) rename .github/workflows/{rust.yml => linux.yml} (100%) diff --git a/.github/workflows/rust.yml b/.github/workflows/linux.yml similarity index 100% rename from .github/workflows/rust.yml rename to .github/workflows/linux.yml diff --git a/README.md b/README.md index e52386e6b0..151b2893c5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ # RuSTy -[![Rust Build](https://github.com/PLC-lang/rusty/actions/workflows/rust.yml/badge.svg)](https://github.com/PLC-lang/ruSTy/actions) +[![Linux Build](https://github.com/PLC-lang/rusty/actions/workflows/linux.yml/badge.svg)](https://github.com/PLC-lang/ruSTy/actions) +[![Windows Build](https://github.com/PLC-lang/rusty/actions/workflows/windows.yml/badge.svg)](https://github.com/PLC-lang/rusty/actions/workflows/windows.yml) +[![Documentation](https://github.com/PLC-lang/rusty/actions/workflows/doc.yml/badge.svg)](https://plc-lang.github.io/rusty) [![codecov](https://codecov.io/gh/PLC-lang/rusty/branch/master/graph/badge.svg?token=7ZZ5XZYE9V)](https://codecov.io/gh/PLC-lang/rusty) +[![Metrics](https://github.com/PLC-lang/rusty/actions/workflows/metrics.yml/badge.svg)](https://plc-lang.github.io/metrics) + [![Lines of Code](https://tokei.rs/b1/github/PLC-lang/rusty)](https://github.com/XAMPPRocky/tokei) [Structured text](https://en.wikipedia.org/wiki/Structured_text) compiler written in Rust From 8d0e9e5be90eadd8d5e22d3fc9f08ed2fe6e15b3 Mon Sep 17 00:00:00 2001 From: Volkan Date: Mon, 17 Jun 2024 11:13:15 +0200 Subject: [PATCH 2/2] feat: Validate argument count (#1233) Resolves https://github.com/PLC-lang/rusty/issues/1027 and https://github.com/PLC-lang/rusty/issues/1201 --- compiler/plc_diagnostics/src/diagnostics.rs | 33 +- .../src/diagnostics/diagnostics_registry.rs | 389 +++++------------- .../src/diagnostics/error_codes/E032.md | 19 +- src/builtins.rs | 16 +- src/index.rs | 4 + src/resolver/generics.rs | 2 +- src/validation/statement.rs | 69 +++- ...ltins_called_with_invalid_param_count.snap | 18 +- ...ltins_called_with_invalid_param_count.snap | 14 +- ..._function_reports_invalid_param_count.snap | 10 +- ..._called_with_invalid_number_of_params.snap | 34 +- .../tests/statement_validation_tests.rs | 154 +++++++ 12 files changed, 399 insertions(+), 363 deletions(-) diff --git a/compiler/plc_diagnostics/src/diagnostics.rs b/compiler/plc_diagnostics/src/diagnostics.rs index d45546a92d..669ffa7b2e 100644 --- a/compiler/plc_diagnostics/src/diagnostics.rs +++ b/compiler/plc_diagnostics/src/diagnostics.rs @@ -2,6 +2,7 @@ use std::fmt::Display; use serde::{Deserialize, Serialize}; +use crate::diagnostics::diagnostics_registry::DIAGNOSTICS; use plc_ast::ast::AstNode; use plc_source::{ source_location::{SourceLocation, SourceLocationFactory}, @@ -88,8 +89,10 @@ impl Diagnostic { self } - pub fn with_error_code(mut self, error_code: &'static str) -> Self { - self.error_code = error_code; + pub fn with_error_code(mut self, code: &'static str) -> Self { + debug_assert!(DIAGNOSTICS.get(code).is_some(), "Error {code} does not exist"); + + self.error_code = code; self } @@ -199,12 +202,26 @@ impl Diagnostic { .with_error_code("E006") } - pub fn invalid_parameter_count(expected: usize, received: usize, location: SourceLocation) -> Diagnostic { - Diagnostic::new( - format!( - "Invalid parameter count. Received {received} parameters while {expected} parameters were expected.", - )).with_error_code("E032") - .with_location(location) + pub fn invalid_argument_count(expected: usize, actual: usize, location: T) -> Diagnostic + where + T: Into, + { + // Let's be extra fancy here 🕺 + fn message(value: usize) -> String { + match value { + 1 => format!("{value} argument"), + _ => format!("{value} arguments"), + } + } + + Diagnostic::new(format!( + "this POU takes {expected} but {actual} {was_or_were} supplied", + expected = message(expected), + actual = message(actual), + was_or_were = if actual == 1 { "was" } else { "were" } + )) + .with_error_code("E032") + .with_location(location.into()) } pub fn unknown_type(type_name: &str, location: SourceLocation) -> Diagnostic { diff --git a/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs b/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs index b9999558bc..f076bdf045 100644 --- a/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs +++ b/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs @@ -99,296 +99,107 @@ impl From<&DiagnosticsRegistry> for DiagnosticsConfiguration { } } +#[rustfmt::skip] lazy_static! { - static ref DIAGNOSTICS: FxHashMap<&'static str, DiagnosticEntry> = add_diagnostic!( - E001, - Error, - include_str!("./error_codes/E001.md"), //General Error - E002, - Error, - include_str!("./error_codes/E002.md"), //General IO Error - E003, - Error, - include_str!("./error_codes/E003.md"), //Parameter Error - E004, - Error, - include_str!("./error_codes/E004.md"), //Duplicate Symbol - E005, - Error, - include_str!("./error_codes/E005.md"), //Generic LLVM Error - E006, - Error, - include_str!("./error_codes/E006.md"), //Missing Token - E007, - Error, - include_str!("./error_codes/E007.md"), //Unexpected Token - E008, - Error, - include_str!("./error_codes/E008.md"), //Invalid Range - E009, - Error, - include_str!("./error_codes/E009.md"), //Mismatched Parantheses - E010, - Error, - include_str!("./error_codes/E010.md"), //Invalid Time Literal - E011, - Error, - include_str!("./error_codes/E011.md"), //Invalid Number - E012, - Error, - include_str!("./error_codes/E012.md"), //Missing Case Condition - E013, - Warning, - include_str!("./error_codes/E013.md"), //Keywords shoud contain underscores - E014, - Warning, - include_str!("./error_codes/E014.md"), //Wrong parantheses type - E015, - Warning, - include_str!("./error_codes/E015.md"), //Pointer is not standard - E016, - Warning, - include_str!("./error_codes/E016.md"), //Return types cannot have a default value - E017, - Error, - include_str!("./error_codes/E017.md"), //Classes cannot contain implementations - E018, - Error, - include_str!("./error_codes/E018.md"), //Duplicate Label - E019, - Error, - include_str!("./error_codes/E019.md"), //Classes cannot contain IN_OUT variables - E020, - Error, - include_str!("./error_codes/E020.md"), //Classes cannot contain a return type - E021, - Error, - include_str!("./error_codes/E021.md"), //POUs cannot be extended - E022, - Warning, - include_str!("./error_codes/E022.md"), //Missing action container - E023, - Warning, - include_str!("./error_codes/E023.md"), //Statement with no effect - E024, - Warning, - include_str!("./error_codes/E024.md"), //Invalid pragma location - E025, - Error, - include_str!("./error_codes/E025.md"), // Missing return type - E026, - Error, - include_str!("./error_codes/E026.md"), - E027, - Error, - include_str!("./error_codes/E027.md"), - E028, - Error, - include_str!("./error_codes/E028.md"), - E029, - Error, - include_str!("./error_codes/E029.md"), - E030, - Error, - include_str!("./error_codes/E030.md"), - E031, - Error, - include_str!("./error_codes/E031.md"), - E032, - Error, - include_str!("./error_codes/E032.md"), - E033, - Error, - include_str!("./error_codes/E033.md"), - E034, - Error, - include_str!("./error_codes/E034.md"), - E035, - Error, - include_str!("./error_codes/E035.md"), - E036, - Error, - include_str!("./error_codes/E036.md"), - E037, - Error, - include_str!("./error_codes/E037.md"), - E038, - Error, - include_str!("./error_codes/E038.md"), //Missing type - E039, - Warning, - include_str!("./error_codes/E039.md"), - E040, - Error, - include_str!("./error_codes/E040.md"), - E041, - Error, - include_str!("./error_codes/E041.md"), - E042, - Warning, - include_str!("./error_codes/E042.md"), //Assignment to reference - E043, - Error, - include_str!("./error_codes/E043.md"), - E044, - Error, - include_str!("./error_codes/E044.md"), - E045, - Error, - include_str!("./error_codes/E045.md"), - E046, - Error, - include_str!("./error_codes/E046.md"), - E047, - Warning, - include_str!("./error_codes/E047.md"), //VLAs are always by reference - E048, - Error, - include_str!("./error_codes/E048.md"), - E049, - Error, - include_str!("./error_codes/E049.md"), - E050, - Error, - include_str!("./error_codes/E050.md"), - E051, - Error, - include_str!("./error_codes/E051.md"), - E052, - Error, - include_str!("./error_codes/E052.md"), - E053, - Error, - include_str!("./error_codes/E053.md"), - E054, - Error, - include_str!("./error_codes/E054.md"), - E055, - Error, - include_str!("./error_codes/E055.md"), - E056, - Error, - include_str!("./error_codes/E056.md"), - E057, - Error, - include_str!("./error_codes/E057.md"), - E058, - Error, - include_str!("./error_codes/E058.md"), - E059, - Error, - include_str!("./error_codes/E059.md"), - E060, - Info, - include_str!("./error_codes/E060.md"), //Variable direct access with % - E061, - Error, - include_str!("./error_codes/E061.md"), - E062, - Error, - include_str!("./error_codes/E062.md"), - E063, - Error, - include_str!("./error_codes/E063.md"), - E064, - Error, - include_str!("./error_codes/E064.md"), - E065, - Error, - include_str!("./error_codes/E065.md"), - E066, - Error, - include_str!("./error_codes/E066.md"), - E067, - Warning, - include_str!("./error_codes/E067.md"), //Implicit typecast - E068, - Error, - include_str!("./error_codes/E068.md"), - E069, - Error, - include_str!("./error_codes/E069.md"), - E070, - Error, - include_str!("./error_codes/E070.md"), - E071, - Error, - include_str!("./error_codes/E071.md"), - E072, - Error, - include_str!("./error_codes/E072.md"), - E073, - Error, - include_str!("./error_codes/E073.md"), - E074, - Error, - include_str!("./error_codes/E074.md"), - E075, - Error, - include_str!("./error_codes/E075.md"), - E076, - Error, - include_str!("./error_codes/E076.md"), - E077, - Error, - include_str!("./error_codes/E077.md"), - E078, - Error, - include_str!("./error_codes/E078.md"), - E079, - Error, - include_str!("./error_codes/E079.md"), - E080, - Error, - include_str!("./error_codes/E080.md"), - E081, - Error, - include_str!("./error_codes/E081.md"), - E082, - Error, - include_str!("./error_codes/E082.md"), - E083, - Error, - include_str!("./error_codes/E083.md"), - E084, - Error, - include_str!("./error_codes/E084.md"), - E085, - Error, - include_str!("./error_codes/E085.md"), - E086, - Error, - include_str!("./error_codes/E086.md"), - E087, - Error, - include_str!("./error_codes/E087.md"), - E088, - Error, - include_str!("./error_codes/E088.md"), - E089, - Error, - include_str!("./error_codes/E089.md"), - E090, - Warning, - include_str!("./error_codes/E090.md"), //Incompatible reference Assignment - E091, - Warning, - include_str!("./error_codes/E091.md"), - E092, - Info, - include_str!("./error_codes/E092.md"), - E093, - Warning, - include_str!("./error_codes/E093.md"), - E094, - Error, - include_str!("./error_codes/E094.md"), - E095, - Error, - include_str!("./error_codes/E095.md"), // Action call without `()` - E096, Warning, include_str!("./error_codes/E096.md"), // Integer Condition - E097, Error, include_str!("./error_codes/E097.md"), // Invalid Array Range -); + pub static ref DIAGNOSTICS: FxHashMap<&'static str, DiagnosticEntry> = add_diagnostic!( + E001, Error, include_str!("./error_codes/E001.md"), //General Error + E002, Error, include_str!("./error_codes/E002.md"), //General IO Error + E003, Error, include_str!("./error_codes/E003.md"), //Parameter Error + E004, Error, include_str!("./error_codes/E004.md"), //Duplicate Symbol + E005, Error, include_str!("./error_codes/E005.md"), //Generic LLVM Error + E006, Error, include_str!("./error_codes/E006.md"), //Missing Token + E007, Error, include_str!("./error_codes/E007.md"), //Unexpected Token + E008, Error, include_str!("./error_codes/E008.md"), //Invalid Range + E009, Error, include_str!("./error_codes/E009.md"), //Mismatched Parantheses + E010, Error, include_str!("./error_codes/E010.md"), //Invalid Time Literal + E011, Error, include_str!("./error_codes/E011.md"), //Invalid Number + E012, Error, include_str!("./error_codes/E012.md"), //Missing Case Condition + E013, Warning, include_str!("./error_codes/E013.md"), //Keywords shoud contain underscores + E014, Warning, include_str!("./error_codes/E014.md"), //Wrong parantheses type + E015, Warning, include_str!("./error_codes/E015.md"), //Pointer is not standard + E016, Warning, include_str!("./error_codes/E016.md"), //Return types cannot have a default value + E017, Error, include_str!("./error_codes/E017.md"), //Classes cannot contain implementations + E018, Error, include_str!("./error_codes/E018.md"), //Duplicate Label + E019, Error, include_str!("./error_codes/E019.md"), //Classes cannot contain IN_OUT variables + E020, Error, include_str!("./error_codes/E020.md"), //Classes cannot contain a return type + E021, Error, include_str!("./error_codes/E021.md"), //POUs cannot be extended + E022, Warning, include_str!("./error_codes/E022.md"), //Missing action container + E023, Warning, include_str!("./error_codes/E023.md"), //Statement with no effect + E024, Warning, include_str!("./error_codes/E024.md"), //Invalid pragma location + E025, Error, include_str!("./error_codes/E025.md"), // Missing return type + E026, Error, include_str!("./error_codes/E026.md"), + E027, Error, include_str!("./error_codes/E027.md"), + E028, Error, include_str!("./error_codes/E028.md"), + E029, Error, include_str!("./error_codes/E029.md"), + E030, Error, include_str!("./error_codes/E030.md"), + E031, Error, include_str!("./error_codes/E031.md"), + E032, Error, include_str!("./error_codes/E032.md"), + E033, Error, include_str!("./error_codes/E033.md"), + E034, Error, include_str!("./error_codes/E034.md"), + E035, Error, include_str!("./error_codes/E035.md"), + E036, Error, include_str!("./error_codes/E036.md"), + E037, Error, include_str!("./error_codes/E037.md"), + E038, Error, include_str!("./error_codes/E038.md"), //Missing type + E039, Warning, include_str!("./error_codes/E039.md"), + E040, Error, include_str!("./error_codes/E040.md"), + E041, Error, include_str!("./error_codes/E041.md"), + E042, Warning, include_str!("./error_codes/E042.md"), //Assignment to reference + E043, Error, include_str!("./error_codes/E043.md"), + E044, Error, include_str!("./error_codes/E044.md"), + E045, Error, include_str!("./error_codes/E045.md"), + E046, Error, include_str!("./error_codes/E046.md"), + E047, Warning, include_str!("./error_codes/E047.md"), //VLAs are always by reference + E048, Error, include_str!("./error_codes/E048.md"), + E049, Error, include_str!("./error_codes/E049.md"), + E050, Error, include_str!("./error_codes/E050.md"), + E051, Error, include_str!("./error_codes/E051.md"), + E052, Error, include_str!("./error_codes/E052.md"), + E053, Error, include_str!("./error_codes/E053.md"), + E054, Error, include_str!("./error_codes/E054.md"), + E055, Error, include_str!("./error_codes/E055.md"), + E056, Error, include_str!("./error_codes/E056.md"), + E057, Error, include_str!("./error_codes/E057.md"), + E058, Error, include_str!("./error_codes/E058.md"), + E059, Error, include_str!("./error_codes/E059.md"), + E060, Info, include_str!("./error_codes/E060.md"), //Variable direct access with % + E061, Error, include_str!("./error_codes/E061.md"), + E062, Error, include_str!("./error_codes/E062.md"), + E063, Error, include_str!("./error_codes/E063.md"), + E064, Error, include_str!("./error_codes/E064.md"), + E065, Error, include_str!("./error_codes/E065.md"), + E066, Error, include_str!("./error_codes/E066.md"), + E067, Warning, include_str!("./error_codes/E067.md"), //Implicit typecast + E068, Error, include_str!("./error_codes/E068.md"), + E069, Error, include_str!("./error_codes/E069.md"), + E070, Error, include_str!("./error_codes/E070.md"), + E071, Error, include_str!("./error_codes/E071.md"), + E072, Error, include_str!("./error_codes/E072.md"), + E073, Error, include_str!("./error_codes/E073.md"), + E074, Error, include_str!("./error_codes/E074.md"), + E075, Error, include_str!("./error_codes/E075.md"), + E076, Error, include_str!("./error_codes/E076.md"), + E077, Error, include_str!("./error_codes/E077.md"), + E078, Error, include_str!("./error_codes/E078.md"), + E079, Error, include_str!("./error_codes/E079.md"), + E080, Error, include_str!("./error_codes/E080.md"), + E081, Error, include_str!("./error_codes/E081.md"), + E082, Error, include_str!("./error_codes/E082.md"), + E083, Error, include_str!("./error_codes/E083.md"), + E084, Error, include_str!("./error_codes/E084.md"), + E085, Error, include_str!("./error_codes/E085.md"), + E086, Error, include_str!("./error_codes/E086.md"), + E087, Error, include_str!("./error_codes/E087.md"), + E088, Error, include_str!("./error_codes/E088.md"), + E089, Error, include_str!("./error_codes/E089.md"), + E090, Warning, include_str!("./error_codes/E090.md"), //Incompatible reference Assignment + E091, Warning, include_str!("./error_codes/E091.md"), + E092, Info, include_str!("./error_codes/E092.md"), + E093, Warning, include_str!("./error_codes/E093.md"), + E094, Error, include_str!("./error_codes/E094.md"), + E095, Error, include_str!("./error_codes/E095.md"), // Action call without `()` + E096, Warning, include_str!("./error_codes/E096.md"), // Integer Condition + E097, Error, include_str!("./error_codes/E097.md"), // Invalid Array Range + ); } #[cfg(test)] diff --git a/compiler/plc_diagnostics/src/diagnostics/error_codes/E032.md b/compiler/plc_diagnostics/src/diagnostics/error_codes/E032.md index 53e170129c..cb2b5ed020 100644 --- a/compiler/plc_diagnostics/src/diagnostics/error_codes/E032.md +++ b/compiler/plc_diagnostics/src/diagnostics/error_codes/E032.md @@ -1 +1,18 @@ -# Invalid number of parameters +# Invalid number of arguments + +An invalid number of arguments was passed to a POU. For example + +``` +FUNCTION foo + (* ... *) +END_FUNCTION + +FUNCTION main : DINT + foo('bar'); // Error, foo isn't expecting any arguments +END_FUNCTION +``` + +Note that for `FUNCTION`s the argument count must match with the parameter list and can be bigger if a variadic +parameter is present. For stateful POUs variadic parameters are not supported, thus the argument count must be equal +or less than the parameter list depending on whether optional arguments such as `VAR_INPUT` or `VAR_OUTPUT` were +passed or not. diff --git a/src/builtins.rs b/src/builtins.rs index de6de63e59..9f5da9f6f9 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -92,14 +92,14 @@ lazy_static! { }), validation: Some(|validator, operator, parameters, _, _| { let Some(params) = parameters else { - validator.push_diagnostic(Diagnostic::invalid_parameter_count(1, 0, operator.get_location())); + validator.push_diagnostic(Diagnostic::invalid_argument_count(1, 0, operator.get_location())); return; }; let params = flatten_expression_list(params); if params.len() > 1 { - validator.push_diagnostic(Diagnostic::invalid_parameter_count(1, params.len(), operator.get_location())); + validator.push_diagnostic(Diagnostic::invalid_argument_count(1, params.len(), operator.get_location())); } }), generic_name_resolver: no_generic_name_resolver, @@ -579,7 +579,7 @@ fn validate_builtin_symbol_parameter_count( operation: Operator, ) { let Some(params) = parameters else { - validator.push_diagnostic(Diagnostic::invalid_parameter_count(2, 0, operator.get_location())); + validator.push_diagnostic(Diagnostic::invalid_argument_count(2, 0, operator.get_location())); return; }; @@ -588,7 +588,7 @@ fn validate_builtin_symbol_parameter_count( // non-extensible operators Operator::Minus | Operator::Division | Operator::NotEqual => { if count != 2 { - validator.push_diagnostic(Diagnostic::invalid_parameter_count( + validator.push_diagnostic(Diagnostic::invalid_argument_count( 2, count, operator.get_location(), @@ -597,7 +597,7 @@ fn validate_builtin_symbol_parameter_count( } _ => { if count < 2 { - validator.push_diagnostic(Diagnostic::invalid_parameter_count( + validator.push_diagnostic(Diagnostic::invalid_argument_count( 2, count, operator.get_location(), @@ -746,7 +746,7 @@ fn validate_variable_length_array_bound_function( index: &Index, ) { let Some(parameters) = parameters else { - validator.push_diagnostic(Diagnostic::invalid_parameter_count(2, 0, operator.get_location())); + validator.push_diagnostic(Diagnostic::invalid_argument_count(2, 0, operator.get_location())); // no params, nothing to validate return; }; @@ -754,7 +754,7 @@ fn validate_variable_length_array_bound_function( let params = ast::flatten_expression_list(parameters); if params.len() > 2 { - validator.push_diagnostic(Diagnostic::invalid_parameter_count( + validator.push_diagnostic(Diagnostic::invalid_argument_count( 2, params.len(), operator.get_location(), @@ -798,7 +798,7 @@ fn validate_variable_length_array_bound_function( }; } (Some(_), None) => { - validator.push_diagnostic(Diagnostic::invalid_parameter_count(2, 1, operator.get_location())) + validator.push_diagnostic(Diagnostic::invalid_argument_count(2, 1, operator.get_location())) } _ => unreachable!(), } diff --git a/src/index.rs b/src/index.rs index 0370ce073a..11be9a5d1a 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1199,6 +1199,10 @@ impl Index { .collect::>() } + pub fn has_variadic_parameter(&self, pou_name: &str) -> bool { + self.get_pou_members(pou_name).iter().any(|member| member.is_parameter() && member.is_variadic()) + } + /// returns some if the current index is a VAR_INPUT, VAR_IN_OUT or VAR_OUTPUT that is not a variadic argument /// In other words it returns some if the member variable at `index` of the given container is a possible parameter in /// the call to it diff --git a/src/resolver/generics.rs b/src/resolver/generics.rs index 7cea493142..379460b346 100644 --- a/src/resolver/generics.rs +++ b/src/resolver/generics.rs @@ -440,7 +440,7 @@ pub fn generic_name_resolver( .fold(qualified_name.to_string(), |accum, s| format!("{accum}__{s}")) // TODO: Naming convention (see plc_util/src/convention.rs) } -/// This method returns the qualified name, but has the same signature as the generic resover to be used in builtins +/// This method returns the qualified name, but has the same signature as the generic resolver to be used in builtins pub fn no_generic_name_resolver( qualified_name: &str, _: &[GenericBinding], diff --git a/src/validation/statement.rs b/src/validation/statement.rs index d995ed440f..eb91f49d83 100644 --- a/src/validation/statement.rs +++ b/src/validation/statement.rs @@ -1119,7 +1119,7 @@ fn is_aggregate_type_missmatch(left_type: &DataType, right_type: &DataType, inde fn validate_call( validator: &mut Validator, operator: &AstNode, - parameters: Option<&AstNode>, + operator_arguments: Option<&AstNode>, context: &ValidationContext, ) { // visit called pou @@ -1128,20 +1128,24 @@ fn validate_call( if let Some(pou) = context.find_pou(operator) { // additional validation for builtin calls if necessary if let Some(validation) = builtins::get_builtin(pou.get_name()).and_then(BuiltIn::get_validation) { - validation(validator, operator, parameters, context.annotations, context.index) + validation(validator, operator, operator_arguments, context.annotations, context.index) } - let declared_parameters = context.index.get_declared_parameters(pou.get_name()); - let passed_parameters = parameters.map(flatten_expression_list).unwrap_or_default(); + let arguments = operator_arguments.map(flatten_expression_list).unwrap_or_default(); + let parameters = context.index.get_declared_parameters(pou.get_name()); - let mut are_implicit_parameters = true; + if builtins::get_builtin(pou.get_name()).is_none() { + validate_argument_count(context, validator, pou, &arguments, &operator.location); + } + + let mut arguments_are_implicit = true; let mut variable_location_in_parent = vec![]; // validate parameters - for (i, param) in passed_parameters.iter().enumerate() { - match get_implicit_call_parameter(param, &declared_parameters, i) { + for (i, param) in arguments.iter().enumerate() { + match get_implicit_call_parameter(param, ¶meters, i) { Ok((parameter_location_in_parent, right, is_implicit)) => { - let left = declared_parameters.get(parameter_location_in_parent); + let left = parameters.get(parameter_location_in_parent); if let Some(left) = left { validate_call_by_ref(validator, left, param); // 'parameter location in parent' and 'variable location in parent' are not the same (e.g VAR blocks are not counted as param). @@ -1155,11 +1159,11 @@ fn validate_call( validate_assignment(validator, right, None, ¶m.get_location(), context); } - // mixing implicit and explicit parameters is not allowed - // allways compare to the first passed parameter + // mixing implicit and explicit arguments is not allowed + // allways compare to the first argument if i == 0 { - are_implicit_parameters = is_implicit; - } else if are_implicit_parameters != is_implicit { + arguments_are_implicit = is_implicit; + } else if arguments_are_implicit != is_implicit { validator.push_diagnostic( Diagnostic::new("Cannot mix implicit and explicit call parameters!") .with_error_code("E031") @@ -1190,7 +1194,7 @@ fn validate_call( return; } let declared_in_out_params: Vec<&&VariableIndexEntry> = - declared_parameters.iter().filter(|p| VariableType::InOut == p.get_variable_type()).collect(); + parameters.iter().filter(|p| VariableType::InOut == p.get_variable_type()).collect(); // if the called pou has declared inouts, we need to make sure that these were passed to the pou call if !declared_in_out_params.is_empty() { @@ -1208,7 +1212,7 @@ fn validate_call( } } else { // POU could not be found, we can still partially validate the passed parameters - if let Some(s) = parameters.as_ref() { + if let Some(s) = operator_arguments.as_ref() { visit_statement(validator, s, context); } } @@ -1472,6 +1476,43 @@ fn validate_assignment_type_sizes( }); } +/// Validates if a POU call has the correct number of arguments. Specifically, for functions, +/// the argument count must be equal to the required count unless the interface is variadic, +/// in which case the argument count may be greater than or equal to the required count. For stateful +/// POUs, the argument count can be less than or equal to the required count since VAR_INPUT and +/// VAR_OUTPUT arguments are optional. +fn validate_argument_count( + context: &ValidationContext, + validator: &mut Validator, + pou: &PouIndexEntry, + arguments: &[&AstNode], + operator_location: &SourceLocation, +) { + let parameters = context.index.get_declared_parameters(pou.get_name()); + let has_variadic_parameter = context.index.has_variadic_parameter(pou.get_name()); + + let argument_count_is_incorrect = match pou { + PouIndexEntry::Function { .. } => { + !((has_variadic_parameter && arguments.len() >= parameters.len()) + || (arguments.len() == parameters.len())) + } + + PouIndexEntry::Program { .. } | PouIndexEntry::FunctionBlock { .. } => { + arguments.len() > parameters.len() && !has_variadic_parameter + } + + _ => false, + }; + + if argument_count_is_incorrect { + validator.push_diagnostic(Diagnostic::invalid_argument_count( + parameters.len(), + arguments.len(), + operator_location, + )); + } +} + mod helper { use std::ops::Range; diff --git a/src/validation/tests/snapshots/rusty__validation__tests__builtin_validation_tests__arithmetic_builtins_called_with_invalid_param_count.snap b/src/validation/tests/snapshots/rusty__validation__tests__builtin_validation_tests__arithmetic_builtins_called_with_invalid_param_count.snap index 118b727271..f32c66c15d 100644 --- a/src/validation/tests/snapshots/rusty__validation__tests__builtin_validation_tests__arithmetic_builtins_called_with_invalid_param_count.snap +++ b/src/validation/tests/snapshots/rusty__validation__tests__builtin_validation_tests__arithmetic_builtins_called_with_invalid_param_count.snap @@ -2,11 +2,11 @@ source: src/validation/tests/builtin_validation_tests.rs expression: "&diagnostics" --- -error[E032]: Invalid parameter count. Received 0 parameters while 2 parameters were expected. +error[E032]: this POU takes 2 arguments but 0 arguments were supplied ┌─ :7:13 │ 7 │ ADD(); - │ ^^^ Invalid parameter count. Received 0 parameters while 2 parameters were expected. + │ ^^^ this POU takes 2 arguments but 0 arguments were supplied error[E064]: Could not resolve generic type T with ANY_NUMBER ┌─ :7:13 @@ -14,22 +14,20 @@ error[E064]: Could not resolve generic type T with ANY_NUMBER 7 │ ADD(); │ ^^^^^^ Could not resolve generic type T with ANY_NUMBER -error[E032]: Invalid parameter count. Received 1 parameters while 2 parameters were expected. +error[E032]: this POU takes 2 arguments but 1 argument was supplied ┌─ :8:13 │ 8 │ MUL(x1); - │ ^^^ Invalid parameter count. Received 1 parameters while 2 parameters were expected. + │ ^^^ this POU takes 2 arguments but 1 argument was supplied -error[E032]: Invalid parameter count. Received 4 parameters while 2 parameters were expected. +error[E032]: this POU takes 2 arguments but 4 arguments were supplied ┌─ :9:13 │ 9 │ DIV(x2, x2, x1, x2); // DIV and SUB are not extensible - │ ^^^ Invalid parameter count. Received 4 parameters while 2 parameters were expected. + │ ^^^ this POU takes 2 arguments but 4 arguments were supplied -error[E032]: Invalid parameter count. Received 4 parameters while 2 parameters were expected. +error[E032]: this POU takes 2 arguments but 4 arguments were supplied ┌─ :10:13 │ 10 │ SUB(x2, x2, x1, x2); - │ ^^^ Invalid parameter count. Received 4 parameters while 2 parameters were expected. - - + │ ^^^ this POU takes 2 arguments but 4 arguments were supplied diff --git a/src/validation/tests/snapshots/rusty__validation__tests__builtin_validation_tests__comparison_builtins_called_with_invalid_param_count.snap b/src/validation/tests/snapshots/rusty__validation__tests__builtin_validation_tests__comparison_builtins_called_with_invalid_param_count.snap index cafd37f35c..5eb779a4d0 100644 --- a/src/validation/tests/snapshots/rusty__validation__tests__builtin_validation_tests__comparison_builtins_called_with_invalid_param_count.snap +++ b/src/validation/tests/snapshots/rusty__validation__tests__builtin_validation_tests__comparison_builtins_called_with_invalid_param_count.snap @@ -2,22 +2,20 @@ source: src/validation/tests/builtin_validation_tests.rs expression: "&diagnostics" --- -error[E032]: Invalid parameter count. Received 0 parameters while 2 parameters were expected. +error[E032]: this POU takes 2 arguments but 0 arguments were supplied ┌─ :7:13 │ 7 │ EQ(); - │ ^^ Invalid parameter count. Received 0 parameters while 2 parameters were expected. + │ ^^ this POU takes 2 arguments but 0 arguments were supplied -error[E032]: Invalid parameter count. Received 1 parameters while 2 parameters were expected. +error[E032]: this POU takes 2 arguments but 1 argument was supplied ┌─ :8:13 │ 8 │ GT(x1); - │ ^^ Invalid parameter count. Received 1 parameters while 2 parameters were expected. + │ ^^ this POU takes 2 arguments but 1 argument was supplied -error[E032]: Invalid parameter count. Received 4 parameters while 2 parameters were expected. +error[E032]: this POU takes 2 arguments but 4 arguments were supplied ┌─ :10:13 │ 10 │ NE(x2, x2, x1, x2); // NE is not extensible - │ ^^ Invalid parameter count. Received 4 parameters while 2 parameters were expected. - - + │ ^^ this POU takes 2 arguments but 4 arguments were supplied diff --git a/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__ref_builtin_function_reports_invalid_param_count.snap b/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__ref_builtin_function_reports_invalid_param_count.snap index f30dceb1b4..02950337cb 100644 --- a/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__ref_builtin_function_reports_invalid_param_count.snap +++ b/src/validation/tests/snapshots/rusty__validation__tests__statement_validation_tests__ref_builtin_function_reports_invalid_param_count.snap @@ -2,16 +2,14 @@ source: src/validation/tests/statement_validation_tests.rs expression: diagnostics --- -error[E032]: Invalid parameter count. Received 0 parameters while 1 parameters were expected. +error[E032]: this POU takes 1 argument but 0 arguments were supplied ┌─ :7:13 │ 7 │ REF(); - │ ^^^ Invalid parameter count. Received 0 parameters while 1 parameters were expected. + │ ^^^ this POU takes 1 argument but 0 arguments were supplied -error[E032]: Invalid parameter count. Received 4 parameters while 1 parameters were expected. +error[E032]: this POU takes 1 argument but 4 arguments were supplied ┌─ :8:13 │ 8 │ REF(x, 1, 2, 'abc'); - │ ^^^ Invalid parameter count. Received 4 parameters while 1 parameters were expected. - - + │ ^^^ this POU takes 1 argument but 4 arguments were supplied diff --git a/src/validation/tests/snapshots/rusty__validation__tests__variable_length_array_test__builtins__builtins_called_with_invalid_number_of_params.snap b/src/validation/tests/snapshots/rusty__validation__tests__variable_length_array_test__builtins__builtins_called_with_invalid_number_of_params.snap index 61e2cbe523..d2c0b22a83 100644 --- a/src/validation/tests/snapshots/rusty__validation__tests__variable_length_array_test__builtins__builtins_called_with_invalid_number_of_params.snap +++ b/src/validation/tests/snapshots/rusty__validation__tests__variable_length_array_test__builtins__builtins_called_with_invalid_number_of_params.snap @@ -2,23 +2,23 @@ source: src/validation/tests/variable_length_array_test.rs expression: diagnostics --- -error[E032]: Invalid parameter count. Received 0 parameters while 2 parameters were expected. +error[E032]: this POU takes 2 arguments but 0 arguments were supplied ┌─ :13:13 │ 13 │ LOWER_BOUND(); - │ ^^^^^^^^^^^ Invalid parameter count. Received 0 parameters while 2 parameters were expected. + │ ^^^^^^^^^^^ this POU takes 2 arguments but 0 arguments were supplied -error[E032]: Invalid parameter count. Received 1 parameters while 2 parameters were expected. +error[E032]: this POU takes 2 arguments but 1 argument was supplied ┌─ :14:13 │ 14 │ LOWER_BOUND(vla); - │ ^^^^^^^^^^^ Invalid parameter count. Received 1 parameters while 2 parameters were expected. + │ ^^^^^^^^^^^ this POU takes 2 arguments but 1 argument was supplied -error[E032]: Invalid parameter count. Received 1 parameters while 2 parameters were expected. +error[E032]: this POU takes 2 arguments but 1 argument was supplied ┌─ :15:13 │ 15 │ LOWER_BOUND(1); - │ ^^^^^^^^^^^ Invalid parameter count. Received 1 parameters while 2 parameters were expected. + │ ^^^^^^^^^^^ this POU takes 2 arguments but 1 argument was supplied error[E031]: Expected a reference for parameter arr because their type is InOut ┌─ :15:25 @@ -32,29 +32,29 @@ error[E037]: Invalid assignment: cannot assign 'DINT' to 'VARIABLE LENGTH ARRAY' 15 │ LOWER_BOUND(1); │ ^ Invalid assignment: cannot assign 'DINT' to 'VARIABLE LENGTH ARRAY' -error[E032]: Invalid parameter count. Received 4 parameters while 2 parameters were expected. +error[E032]: this POU takes 2 arguments but 4 arguments were supplied ┌─ :16:13 │ 16 │ LOWER_BOUND(vla, 1, 2, 3); - │ ^^^^^^^^^^^ Invalid parameter count. Received 4 parameters while 2 parameters were expected. + │ ^^^^^^^^^^^ this POU takes 2 arguments but 4 arguments were supplied -error[E032]: Invalid parameter count. Received 0 parameters while 2 parameters were expected. +error[E032]: this POU takes 2 arguments but 0 arguments were supplied ┌─ :18:13 │ 18 │ UPPER_BOUND(); - │ ^^^^^^^^^^^ Invalid parameter count. Received 0 parameters while 2 parameters were expected. + │ ^^^^^^^^^^^ this POU takes 2 arguments but 0 arguments were supplied -error[E032]: Invalid parameter count. Received 1 parameters while 2 parameters were expected. +error[E032]: this POU takes 2 arguments but 1 argument was supplied ┌─ :19:13 │ 19 │ UPPER_BOUND(vla); - │ ^^^^^^^^^^^ Invalid parameter count. Received 1 parameters while 2 parameters were expected. + │ ^^^^^^^^^^^ this POU takes 2 arguments but 1 argument was supplied -error[E032]: Invalid parameter count. Received 1 parameters while 2 parameters were expected. +error[E032]: this POU takes 2 arguments but 1 argument was supplied ┌─ :20:13 │ 20 │ UPPER_BOUND(1); - │ ^^^^^^^^^^^ Invalid parameter count. Received 1 parameters while 2 parameters were expected. + │ ^^^^^^^^^^^ this POU takes 2 arguments but 1 argument was supplied error[E031]: Expected a reference for parameter arr because their type is InOut ┌─ :20:25 @@ -68,10 +68,8 @@ error[E037]: Invalid assignment: cannot assign 'DINT' to 'VARIABLE LENGTH ARRAY' 20 │ UPPER_BOUND(1); │ ^ Invalid assignment: cannot assign 'DINT' to 'VARIABLE LENGTH ARRAY' -error[E032]: Invalid parameter count. Received 4 parameters while 2 parameters were expected. +error[E032]: this POU takes 2 arguments but 4 arguments were supplied ┌─ :21:13 │ 21 │ UPPER_BOUND(vla, 1, 2, 3); - │ ^^^^^^^^^^^ Invalid parameter count. Received 4 parameters while 2 parameters were expected. - - + │ ^^^^^^^^^^^ this POU takes 2 arguments but 4 arguments were supplied diff --git a/src/validation/tests/statement_validation_tests.rs b/src/validation/tests/statement_validation_tests.rs index fe186996be..e2cc050b35 100644 --- a/src/validation/tests/statement_validation_tests.rs +++ b/src/validation/tests/statement_validation_tests.rs @@ -1667,3 +1667,157 @@ fn action_assignment_attempt_does_not_report_missing_parentheses() { "###); } + +#[test] +fn incorrect_argument_count_stateless_pous() { + let diagnostics = parse_and_validate_buffered( + " + FUNCTION main : DINT + fn_with_one_parameter(); + fn_with_one_parameter(1, 2); + + fn_with_two_parameters(1); + fn_with_two_parameters(1, 2, 3); + + fn_with_one_variadic_parameter(); + fn_with_one_variadic_parameter(1); + fn_with_one_variadic_parameter(1, 2); + fn_with_one_variadic_parameter(1, 2, 3); + END_FUNCTION + + FUNCTION fn_with_one_parameter + VAR_INPUT + in_one : DINT; + END_VAR + END_FUNCTION + + FUNCTION fn_with_two_parameters + VAR_INPUT + in_one : DINT; + END_VAR + + VAR_OUTPUT + out_two : DINT; + END_VAR + END_FUNCTION + + FUNCTION fn_with_one_variadic_parameter + VAR_INPUT + vararg: DINT...; + END_VAR + END_FUNCTION + ", + ); + + assert_snapshot!(diagnostics, @r###" + error[E032]: this POU takes 1 argument but 0 arguments were supplied + ┌─ :3:13 + │ + 3 │ fn_with_one_parameter(); + │ ^^^^^^^^^^^^^^^^^^^^^ this POU takes 1 argument but 0 arguments were supplied + + error[E032]: this POU takes 1 argument but 2 arguments were supplied + ┌─ :4:13 + │ + 4 │ fn_with_one_parameter(1, 2); + │ ^^^^^^^^^^^^^^^^^^^^^ this POU takes 1 argument but 2 arguments were supplied + + error[E032]: this POU takes 2 arguments but 1 argument was supplied + ┌─ :6:13 + │ + 6 │ fn_with_two_parameters(1); + │ ^^^^^^^^^^^^^^^^^^^^^^ this POU takes 2 arguments but 1 argument was supplied + + error[E032]: this POU takes 2 arguments but 3 arguments were supplied + ┌─ :7:13 + │ + 7 │ fn_with_two_parameters(1, 2, 3); + │ ^^^^^^^^^^^^^^^^^^^^^^ this POU takes 2 arguments but 3 arguments were supplied + + error[E031]: Expected a reference for parameter out_two because their type is Output + ┌─ :7:39 + │ + 7 │ fn_with_two_parameters(1, 2, 3); + │ ^ Expected a reference for parameter out_two because their type is Output + + "###); +} + +#[test] +fn incorrect_argument_count_stateful_pous() { + let source = r" + FUNCTION main : DINT + VAR + one_instance : fn_with_one_parameter; + two_instance : fn_with_two_parameters; + END_VAR + + one_instance(); + one_instance(1, 2); + + two_instance(1); + two_instance(1, 2, 3); + END_FUNCTION + + fn_with_one_parameter + VAR_INPUT + in_one : DINT; + END_VAR + END_ + + fn_with_two_parameters + VAR_INPUT + in_one : DINT; + END_VAR + + VAR_OUTPUT + out_two : DINT; + END_VAR + END_ + + "; + + let diagnostics = parse_and_validate_buffered(&source.replace("", "FUNCTION_BLOCK")); + assert_snapshot!(diagnostics, @r###" + error[E032]: this POU takes 1 argument but 2 arguments were supplied + ┌─ :9:13 + │ + 9 │ one_instance(1, 2); + │ ^^^^^^^^^^^^ this POU takes 1 argument but 2 arguments were supplied + + error[E032]: this POU takes 2 arguments but 3 arguments were supplied + ┌─ :12:13 + │ + 12 │ two_instance(1, 2, 3); + │ ^^^^^^^^^^^^ this POU takes 2 arguments but 3 arguments were supplied + + error[E031]: Expected a reference for parameter out_two because their type is Output + ┌─ :12:29 + │ + 12 │ two_instance(1, 2, 3); + │ ^ Expected a reference for parameter out_two because their type is Output + + "###); + + let diagnostics = parse_and_validate_buffered(&source.replace("", "PROGRAM")); + assert_snapshot!(diagnostics, @r###" + error[E032]: this POU takes 1 argument but 2 arguments were supplied + ┌─ :9:13 + │ + 9 │ one_instance(1, 2); + │ ^^^^^^^^^^^^ this POU takes 1 argument but 2 arguments were supplied + + error[E032]: this POU takes 2 arguments but 3 arguments were supplied + ┌─ :12:13 + │ + 12 │ two_instance(1, 2, 3); + │ ^^^^^^^^^^^^ this POU takes 2 arguments but 3 arguments were supplied + + error[E031]: Expected a reference for parameter out_two because their type is Output + ┌─ :12:29 + │ + 12 │ two_instance(1, 2, 3); + │ ^ Expected a reference for parameter out_two because their type is Output + + "###); +}