Skip to content

Commit

Permalink
Merge pull request #329 from PLC-lang/issue-273-Validation_Invalid_Fu…
Browse files Browse the repository at this point in the history
…nction_return_types

added validation for function return types (#273)
  • Loading branch information
99NIMI committed Oct 18, 2021
2 parents eed5b41 + 13c5567 commit 6145712
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 4 deletions.
14 changes: 13 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use std::fs;
use std::ops::Range;
use std::path::Path;

use ast::{PouType, SourceRange};
use ast::{DataTypeDeclaration, PouType, SourceRange};
use codespan_reporting::diagnostic::{self, Label};
use codespan_reporting::files::SimpleFiles;
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
Expand Down Expand Up @@ -82,6 +82,7 @@ pub enum ErrNo {
// pou related
pou__missing_return_type,
pou__unexpected_return_type,
pou__unsupported_return_type,
pou__empty_variable_block,

//variable related
Expand Down Expand Up @@ -141,6 +142,17 @@ impl Diagnostic {
}
}

pub fn function_unsupported_return_type(data_type: &DataTypeDeclaration) -> Diagnostic {
Diagnostic::SyntaxError {
message: format!(
"Data Type {:?} not supported as a function return type!",
data_type
),
range: data_type.get_location(),
err_no: ErrNo::pou__unsupported_return_type,
}
}

pub fn function_return_missing(range: SourceRange) -> Diagnostic {
Diagnostic::SyntaxError {
message: "Function Return type missing".into(),
Expand Down
11 changes: 11 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,17 @@ fn parse_return_type(lexer: &mut ParseSession, pou_type: &PouType) -> Option<Dat
SourceRange::new(start_return_type..lexer.last_range.end),
));
}

if let DataTypeDeclaration::DataTypeDefinition { data_type, .. } = &declaration {
if matches!(
data_type,
DataType::EnumType { .. } | DataType::StructType { .. }
) {
lexer.accept_diagnostic(Diagnostic::function_unsupported_return_type(
&declaration,
))
}
}
Some(declaration)
} else {
//missing return type
Expand Down
192 changes: 192 additions & 0 deletions src/parser/tests/function_parser_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,195 @@ fn varargs_parameters_can_be_parsed() {
};
assert_eq!(format!("{:#?}", expected), format!("{:#?}", x).as_str());
}

// Tests for function return types
// supported return types
#[test]
fn function_array_return_supported() {
//GIVEN FUNCTION returning an ARRAY
let function = "FUNCTION foo : ARRAY[0..3] OF INT VAR_INPUT END_VAR END_FUNCTION";
//WHEN parsing is done
let lexer = lex(function);
let (_parse_result, diagnostics) = parse(lexer);
//THEN there shouldn't be any diagnostics -> valid return type
assert_eq!(diagnostics, vec![]);
}

#[test]
fn function_subrange_return_supported() {
//GIVEN FUNCTION returning a SubRange
let function = "FUNCTION foo : INT(0..10) VAR_INPUT END_VAR END_FUNCTION";
//WHEN parsing is done
let lexer = lex(function);
let (_parse_result, diagnostics) = parse(lexer);
//THEN there shouldn't be any diagnostics -> valid return type
assert_eq!(diagnostics, vec![]);
}

#[test]
fn function_pointer_return_supported() {
//GIVEN FUNCTION returning a POINTER
let function = "FUNCTION foo : REF_TO INT VAR_INPUT END_VAR END_FUNCTION";
//WHEN parsing is done
let lexer = lex(function);
let (_parse_result, diagnostics) = parse(lexer);
//THEN there shouldn't be any diagnostics -> valid return type
assert_eq!(diagnostics, vec![]);
}

// STRING types
#[test]
fn function_string_return_supported() {
//GIVEN FUNCTION returning a STRING
let function = "FUNCTION foo : STRING VAR_INPUT END_VAR END_FUNCTION";
//WHEN parsing is done
let lexer = lex(function);
let (_parse_result, diagnostics) = parse(lexer);
//THEN there shouldn't be any diagnostics -> valid return type
assert_eq!(diagnostics, vec![]);
}

#[test]
fn function_string_len_return_supported() {
//GIVEN FUNCTION returning a STRING[10]
let function = "FUNCTION foo : STRING[10] VAR_INPUT END_VAR END_FUNCTION";
//WHEN parsing is done
let lexer = lex(function);
let (_parse_result, diagnostics) = parse(lexer);
//THEN there shouldn't be any diagnostics -> valid return type
assert_eq!(diagnostics, vec![]);
}

#[test]
fn function_wstring_return_supported() {
//GIVEN FUNCTION returning a WSTRING
let function = "FUNCTION foo : WSTRING VAR_INPUT END_VAR END_FUNCTION";
//WHEN parsing is done
let lexer = lex(function);
let (_parse_result, diagnostics) = parse(lexer);
//THEN there shouldn't be any diagnostics -> valid return type
assert_eq!(diagnostics, vec![]);
}

#[test]
fn function_wstring_len_return_supported() {
//GIVEN FUNCTION returning a WSTRING[10]
let function = "FUNCTION foo : WSTRING[10] VAR_INPUT END_VAR END_FUNCTION";
//WHEN parsing is done
let lexer = lex(function);
let (_parse_result, diagnostics) = parse(lexer);
//THEN there shouldn't be any diagnostics -> valid return type
assert_eq!(diagnostics, vec![]);
}

// SCALAR types
#[test]
fn function_int_return_supported() {
//GIVEN FUNCTION returning an INT
let function = "FUNCTION foo : INT VAR_INPUT END_VAR END_FUNCTION";
//WHEN parsing is done
let lexer = lex(function);
let (_parse_result, diagnostics) = parse(lexer);
//THEN there shouldn't be any diagnostics -> valid return type
assert_eq!(diagnostics, vec![]);
}

#[test]
fn function_bool_return_supported() {
//GIVEN FUNCTION returning a BOOL
let function = "FUNCTION foo : BOOL VAR_INPUT END_VAR END_FUNCTION";
//WHEN parsing is done
let lexer = lex(function);
let (_parse_result, diagnostics) = parse(lexer);
//THEN there shouldn't be any diagnostics -> valid return type
assert_eq!(diagnostics, vec![]);
}

#[test]
fn function_type_enum_return_supported() {
// GIVEN FUNCTION returning a type ENUM
let function = "TYPE MyEnum: (green, yellow, red); END_TYPE
FUNCTION foo : MyEnum VAR_INPUT END_VAR END_FUNCTION";
//WHEN parsing is done
let lexer = lex(function);
let (_parse_result, diagnostics) = parse(lexer);
//THEN there shouldn't be any diagnostics -> valid return type
assert_eq!(diagnostics, vec![]);
}

#[test]
fn function_type_struct_return_supported() {
// GIVEN FUNCTION returning a type STRUCT
let function = "TYPE MyStruct: STRUCT x : INT; y : INT; END_STRUCT END_TYPE
FUNCTION foo : MyStruct VAR_INPUT END_VAR END_FUNCTION";
//WHEN parsing is done
let lexer = lex(function);
let (_parse_result, diagnostics) = parse(lexer);
//THEN there shouldn't be any diagnostics -> valid return type
assert_eq!(diagnostics, vec![]);
}

// unsupported return types
#[test]
fn function_inline_enum_return_unsupported() {
// GIVEN FUNCTION returning an inline ENUM
let function = "FUNCTION foo : (green, yellow, red) VAR_INPUT END_VAR END_FUNCTION";
// WHEN parsing is done
let lexer = lex(function);
let (_parse_result, diagnostics) = parse(lexer);
// THEN there should be one diagnostic -> unsupported return type
assert_eq!(
diagnostics,
vec![Diagnostic::function_unsupported_return_type(
&DataTypeDeclaration::DataTypeDefinition {
data_type: DataType::EnumType {
name: None,
elements: vec!["green".into(), "yellow".into(), "red".into()]
},
location: (15..35).into()
}
)]
);
}

#[test]
fn function_inline_struct_return_unsupported() {
// GIVEN FUNCTION returning an inline STRUCT
let function =
"FUNCTION foo : STRUCT x : INT; y : INT; END_STRUCT VAR_INPUT END_VAR END_FUNCTION";
// WHEN parsing is done
let lexer = lex(function);
let (_parse_result, diagnostics) = parse(lexer);
// THEN there should be one diagnostic -> unsupported return type
assert_eq!(
true,
diagnostics.contains(&Diagnostic::function_unsupported_return_type(
&DataTypeDeclaration::DataTypeDefinition {
data_type: DataType::StructType {
name: None,
variables: vec![
Variable {
name: "x".into(),
location: SourceRange::undefined(),
data_type: DataTypeDeclaration::DataTypeReference {
location: SourceRange::undefined(),
referenced_type: "INT".into()
},
initializer: None
},
Variable {
name: "y".into(),
location: SourceRange::undefined(),
data_type: DataTypeDeclaration::DataTypeReference {
location: SourceRange::undefined(),
referenced_type: "INT".into()
},
initializer: None
}
],
},
location: (15..50).into()
}
))
);
}
2 changes: 1 addition & 1 deletion src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ impl Validator {
}

pub fn visit_pou(&mut self, pou: &Pou, context: &ValidationContext) {
self.pou_validator.validate_pou(pou);
self.pou_validator.validate_pou(pou, context);

for block in &pou.variable_blocks {
self.visit_variable_container(context, block);
Expand Down
18 changes: 16 additions & 2 deletions src/validation/pou_validator.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{ast::Pou, Diagnostic};
use super::ValidationContext;
use crate::{ast::Pou, Diagnostic, PouType};

/// validates POUs
pub struct PouValidator {
Expand All @@ -12,5 +13,18 @@ impl PouValidator {
}
}

pub fn validate_pou(&mut self, _pou: &Pou) {}
pub fn validate_pou(&mut self, pou: &Pou, context: &ValidationContext) {
if pou.pou_type == PouType::Function {
self.validate_function(pou, context);
};
}

pub fn validate_function(&mut self, pou: &Pou, context: &ValidationContext) {
let return_type = context.index.find_return_type(&pou.name);
// functions must have a return type
if return_type.is_none() {
self.diagnostics
.push(Diagnostic::function_return_missing(pou.location.to_owned()));
}
}
}
2 changes: 2 additions & 0 deletions src/validation/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ mod variable_validation_tests;

mod statement_validation_tests;

mod pou_validation_tests;

pub fn parse_and_validate(src: &str) -> Vec<Diagnostic> {
let mut idx = Index::new();
let (mut ast, _) = parse(lex(src));
Expand Down
14 changes: 14 additions & 0 deletions src/validation/tests/pou_validation_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use crate::{validation::tests::parse_and_validate, Diagnostic};

// unsupported return types
#[test]
fn function_no_return_unsupported() {
// GIVEN FUNCTION with no return type
// WHEN parse_and_validate is done
let diagnostics = parse_and_validate("FUNCTION foo VAR_INPUT END_VAR END_FUNCTION");
// THEN there should be one diagnostic -> missing return type
assert_eq!(
diagnostics,
vec![Diagnostic::function_return_missing((0..43).into())]
);
}

0 comments on commit 6145712

Please sign in to comment.