Skip to content

Commit 2090dd3

Browse files
authored
feat: Validate VAR_CONFIG and template variables (#1303)
* feat: Validate VAR_CONFIG and template variables Introduces a validation for template variables (e.g. `foo AT %I* : BOOL`) and their configuration as specified in VAR_CONFIG blocks. Specifically the validation checks if (1) the instances defined in VAR_CONFIG blocks have a valid reference to some variable defined elsewhere (2) the instances defined in VAR_CONFIG blocks are complete (i.e. no `*` in their hardware address) and (3) the datatypes defined in VAR_CONFIG blocks match with the reference variables datatype * refactor: Some polishing * chore: Add error codes * refactor: PR feedback
1 parent fcd4c02 commit 2090dd3

File tree

11 files changed

+409
-24
lines changed

11 files changed

+409
-24
lines changed

compiler/plc_ast/src/ast.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -935,17 +935,11 @@ impl AstNode {
935935
}
936936
}
937937

938-
pub fn get_flat_base_reference_name(&self) -> Option<&str> {
939-
match &self.stmt {
940-
AstStatement::ReferenceExpr(ReferenceExpr { base: Some(base), .. }, ..) => {
941-
base.as_ref().get_flat_base_reference_name()
942-
}
943-
AstStatement::ReferenceExpr(
944-
ReferenceExpr { access: ReferenceAccess::Member(reference), base: None },
945-
..,
946-
) => reference.as_ref().get_flat_base_reference_name(),
947-
AstStatement::Identifier(name, ..) => Some(name),
948-
_ => None,
938+
pub fn get_parent_name_of_reference(&self) -> Option<&str> {
939+
if let AstStatement::ReferenceExpr(ReferenceExpr { base: Some(base), .. }, ..) = &self.stmt {
940+
base.as_ref().get_flat_reference_name()
941+
} else {
942+
None
949943
}
950944
}
951945

@@ -1089,6 +1083,13 @@ impl AstNode {
10891083
let location = self.get_location();
10901084
AstFactory::create_not_expression(self, location, id_provider.next_id())
10911085
}
1086+
1087+
pub fn is_template(&self) -> bool {
1088+
matches!(
1089+
self.stmt,
1090+
AstStatement::HardwareAccess(HardwareAccess { access: DirectAccessType::Template, .. })
1091+
)
1092+
}
10921093
}
10931094

10941095
#[derive(Clone, Copy, Debug, PartialEq, Eq)]

compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,16 @@ lazy_static! {
196196
E092, Info, include_str!("./error_codes/E092.md"),
197197
E093, Warning, include_str!("./error_codes/E093.md"),
198198
E094, Error, include_str!("./error_codes/E094.md"),
199-
E095, Error, include_str!("./error_codes/E095.md"), // Action call without `()`
200-
E096, Warning, include_str!("./error_codes/E096.md"), // Integer Condition
201-
E097, Error, include_str!("./error_codes/E097.md"), // Invalid Array Range
202-
E098, Error, include_str!("./error_codes/E098.md"), // Invalid `REF=` assignment
203-
E099, Error, include_str!("./error_codes/E099.md"), // Invalid `REFERENCE TO` declaration
204-
E100, Error, include_str!("./error_codes/E100.md"), // Immutable variable address
199+
E095, Error, include_str!("./error_codes/E095.md"), // Action call without `()`
200+
E096, Warning, include_str!("./error_codes/E096.md"), // Integer Condition
201+
E097, Error, include_str!("./error_codes/E097.md"), // Invalid Array Range
202+
E098, Error, include_str!("./error_codes/E098.md"), // Invalid `REF=` assignment
203+
E099, Error, include_str!("./error_codes/E099.md"), // Invalid `REFERENCE TO` declaration
204+
E100, Error, include_str!("./error_codes/E100.md"), // Immutable variable address
205+
E101, Error, include_str!("./error_codes/E101.md"), // Invalid VAR_CONFIG / Template Variable Declaration
206+
E102, Error, include_str!("./error_codes/E102.md"), // Template variable without hardware binding
207+
E103, Error, include_str!("./error_codes/E103.md"), // Immutable Hardware Binding
208+
E104, Error, include_str!("./error_codes/E104.md"), // Config Variable With Incomplete Address
205209
);
206210
}
207211

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Template variable does not exist
2+
3+
A variable was configured in a `VAR_CONFIG` block, but the variable can not be found in the code.
4+
5+
Erroneous code example:
6+
```
7+
VAR_CONFIG
8+
main.foo.bar AT %IX1.0 : BOOL;
9+
END_VAR
10+
11+
PROGRAM main
12+
VAR
13+
foo : foo_fb;
14+
END_VAR
15+
END_PROGRAM
16+
17+
FUNCTION_BLOCK foo_fb
18+
VAR
19+
qux AT %I* : BOOL;
20+
END_VAR
21+
END_FUNCTION_BLOCK
22+
```
23+
24+
In this example a variable named `bar` is configured, however the function block `foo_fb` does not contain
25+
a `bar` variable. The could should have been `main.foo.qux AT %IX1.0 : BOOL` instead for it to be valid.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Template variable without hardware binding
2+
3+
A template variable must contain a hardware binding.
4+
5+
6+
Erroneous code example:
7+
```
8+
VAR_CONFIG
9+
main.foo.bar AT %IX1.0 : BOOL;
10+
END_VAR
11+
12+
PROGRAM main
13+
VAR
14+
foo : foo_fb;
15+
END_VAR
16+
END_PROGRAM
17+
18+
FUNCTION_BLOCK foo_fb
19+
VAR
20+
bar : BOOL;
21+
END_VAR
22+
END_FUNCTION_BLOCK
23+
```
24+
25+
In this example the `VAR_CONFIG` block declares the `bar` variable inside `foo_fb` as a
26+
template variable. However `bar` does not have a hardware binding. For the example to be
27+
considered valid, `bar` should have been declared as e.g. `bar AT %I* : BOOL`.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Immutable Hardware Binding
2+
3+
Variables configured in a `VAR_CONFIG` block can not override their hardware binding.
4+
5+
Erroneous code example:
6+
```
7+
VAR_CONFIG
8+
main.foo.bar AT %IX1.0 : BOOL;
9+
END_VAR
10+
11+
PROGRAM main
12+
VAR
13+
foo : foo_fb;
14+
END_VAR
15+
END_PROGRAM
16+
17+
FUNCTION_BLOCK foo_fb
18+
VAR
19+
bar AT IX1.5: BOOL;
20+
END_VAR
21+
END_FUNCTION_BLOCK
22+
```
23+
24+
In this example the `VAR_CONFIG` block configures `bar` to have a hardware adress `IX1.0`.
25+
However, at the same time the `bar` inside the POU `foo_fb` assigns a hardware address `IX1.5`.
26+
27+
For the code to be considered valid, `bar` should have been declared as `bar AT %I* : BOOL`.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Config Variable With Incomplete Address
2+
3+
Variables defined in a `VAR_CONFIG` block, i.e. config variables, must specify a complete address.
4+
5+
Erroneous code example:
6+
```
7+
VAR_CONFIG
8+
main.foo.bar AT %I* : BOOL;
9+
END_VAR
10+
```
11+
12+
In this example `main.foo.bar` has specified a placeholder hardware address.
13+
For the example to be considered valid, a specific address such as `%IX1.0` should have been declared.

src/index.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,10 @@ impl VariableIndexEntry {
273273
self.binding.is_some()
274274
}
275275

276+
pub fn is_template(&self) -> bool {
277+
matches!(self.binding, Some(HardwareBinding { access: DirectAccessType::Template, .. }))
278+
}
279+
276280
pub fn get_hardware_binding(&self) -> Option<&HardwareBinding> {
277281
self.binding.as_ref()
278282
}

src/parser/tests/parse_errors/parse_error_messages_test.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,5 +165,11 @@ fn test_incomplete_var_config_block() {
165165
│ ╰───────────────────────^ Unexpected token: expected KeywordEndVar but found 'AT %IX3.1;
166166
%IX3.1;'
167167
168+
error[E101]: Template variable `bar` does not exist
169+
┌─ <internal>:7:17
170+
171+
7 │ instance4.bar AT %IX3.1 : BOOL;
172+
│ ^^^^^^^^^^^^^ Template variable `bar` does not exist
173+
168174
"###);
169175
}

src/validation.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use plc_ast::ast::{AstNode, CompilationUnit};
22
use plc_derive::Validators;
33
use plc_diagnostics::diagnostics::Diagnostic;
44
use plc_index::GlobalContext;
5+
use variable::visit_config_variable;
56

67
use crate::{
78
index::{
@@ -168,22 +169,27 @@ impl<'a> Validator<'a> {
168169

169170
pub fn visit_unit<T: AnnotationMap>(&mut self, annotations: &T, index: &Index, unit: &CompilationUnit) {
170171
let context = ValidationContext { annotations, index, qualifier: None, is_call: false };
171-
// validate POU and declared Variables
172+
// Validate POU and declared Variables
172173
for pou in &unit.units {
173174
visit_pou(self, pou, &context.with_qualifier(pou.name.as_str()));
174175
}
175176

176-
// validate user declared types
177+
// Validate user declared types
177178
for t in &unit.user_types {
178179
visit_user_type_declaration(self, t, &context);
179180
}
180181

181-
// validate global variables
182+
// Validate config variables (VAR_CONFIG)
183+
for variable in &unit.var_config {
184+
visit_config_variable(self, variable, &context);
185+
}
186+
187+
// Validate global variables
182188
for gv in &unit.global_vars {
183189
visit_variable_block(self, None, gv, &context);
184190
}
185191

186-
// validate implementations
192+
// Validate implementations
187193
for implementation in &unit.implementations {
188194
visit_implementation(self, implementation, &context);
189195
}

src/validation/tests/variable_validation_tests.rs

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,3 +459,172 @@ fn date_invalid_declaration() {
459459

460460
assert_snapshot!(diagnostics, @r###""###);
461461
}
462+
463+
#[test]
464+
fn var_conf_template_variable_does_not_exist() {
465+
let diagnostics = parse_and_validate_buffered(
466+
r#"
467+
FUNCTION_BLOCK foo_fb
468+
VAR
469+
bar AT %I* : BOOL;
470+
END_VAR
471+
END_FUNCTION_BLOCK
472+
473+
PROGRAM main
474+
VAR
475+
foo : foo_fb;
476+
END_VAR
477+
END_PROGRAM
478+
479+
VAR_CONFIG
480+
main.foo.qux AT %IX1.0 : BOOL;
481+
END_VAR
482+
"#,
483+
);
484+
485+
assert_snapshot!(diagnostics, @r###"
486+
error[E101]: Template variable `qux` does not exist
487+
┌─ <internal>:15:13
488+
489+
15 │ main.foo.qux AT %IX1.0 : BOOL;
490+
│ ^^^^^^^^^^^^ Template variable `qux` does not exist
491+
492+
"###);
493+
}
494+
495+
#[test]
496+
fn var_conf_config_and_template_variable_types_differ() {
497+
let diagnostics = parse_and_validate_buffered(
498+
r#"
499+
FUNCTION_BLOCK foo_fb
500+
VAR
501+
bar AT %I* : DINT;
502+
END_VAR
503+
END_FUNCTION_BLOCK
504+
505+
PROGRAM main
506+
VAR
507+
foo : foo_fb;
508+
END_VAR
509+
END_PROGRAM
510+
511+
VAR_CONFIG
512+
main.foo.bar AT %IX1.0 : BOOL;
513+
END_VAR
514+
"#,
515+
);
516+
517+
assert_snapshot!(diagnostics, @r###"
518+
error[E001]: Config and Template variable types differ (BOOL and : DINT)
519+
┌─ <internal>:15:13
520+
521+
4 │ bar AT %I* : DINT;
522+
│ --- see also
523+
·
524+
15 │ main.foo.bar AT %IX1.0 : BOOL;
525+
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Config and Template variable types differ (BOOL and : DINT)
526+
527+
"###);
528+
}
529+
530+
#[test]
531+
fn var_conf_config_variable_has_incomplete_address() {
532+
let diagnostics = parse_and_validate_buffered(
533+
r#"
534+
FUNCTION_BLOCK foo_fb
535+
VAR
536+
bar AT %I* : BOOL;
537+
END_VAR
538+
END_FUNCTION_BLOCK
539+
540+
PROGRAM main
541+
VAR
542+
foo : foo_fb;
543+
END_VAR
544+
END_PROGRAM
545+
546+
VAR_CONFIG
547+
main.foo.bar AT %I* : BOOL;
548+
END_VAR
549+
"#,
550+
);
551+
552+
assert_snapshot!(diagnostics, @r###"
553+
error[E104]: Variables defined in a VAR_CONFIG block must have a complete address
554+
┌─ <internal>:15:26
555+
556+
15 │ main.foo.bar AT %I* : BOOL;
557+
│ ^^^^^^ Variables defined in a VAR_CONFIG block must have a complete address
558+
559+
"###);
560+
}
561+
562+
#[test]
563+
fn var_conf_template_address_has_complete_address() {
564+
let diagnostics = parse_and_validate_buffered(
565+
r#"
566+
FUNCTION_BLOCK foo_fb
567+
VAR
568+
bar AT %IX1.0 : BOOL;
569+
END_VAR
570+
END_FUNCTION_BLOCK
571+
572+
PROGRAM main
573+
VAR
574+
foo : foo_fb;
575+
END_VAR
576+
END_PROGRAM
577+
578+
VAR_CONFIG
579+
main.foo.bar AT %IX1.0 : BOOL;
580+
END_VAR
581+
"#,
582+
);
583+
584+
assert_snapshot!(diagnostics, @r###"
585+
error[E103]: The configured variable is not a template, overriding non-template hardware addresses is not allowed
586+
┌─ <internal>:15:13
587+
588+
4 │ bar AT %IX1.0 : BOOL;
589+
│ --- see also
590+
·
591+
15 │ main.foo.bar AT %IX1.0 : BOOL;
592+
│ ^^^^^^^^^^^^ The configured variable is not a template, overriding non-template hardware addresses is not allowed
593+
594+
"###);
595+
}
596+
597+
#[test]
598+
fn var_conf_template_variable_is_no_template() {
599+
let diagnostics = parse_and_validate_buffered(
600+
r#"
601+
FUNCTION_BLOCK foo_fb
602+
VAR
603+
bar : BOOL;
604+
END_VAR
605+
END_FUNCTION_BLOCK
606+
607+
PROGRAM main
608+
VAR
609+
foo : foo_fb;
610+
END_VAR
611+
END_PROGRAM
612+
613+
VAR_CONFIG
614+
main.foo.bar AT %IX1.0 : BOOL;
615+
END_VAR
616+
"#,
617+
);
618+
619+
assert_snapshot!(diagnostics, @r###"
620+
error[E102]: `foo` is missing a hardware binding
621+
┌─ <internal>:4:17
622+
623+
4 │ bar : BOOL;
624+
│ ^^^ `foo` is missing a hardware binding
625+
·
626+
15 │ main.foo.bar AT %IX1.0 : BOOL;
627+
│ ----------------------------- see also
628+
629+
"###);
630+
}

0 commit comments

Comments
 (0)