Skip to content
Draft
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 @@ -243,6 +243,7 @@ lazy_static! {
E139, Error, include_str!("./error_codes/E139.md"), // Linker invocation failed (spawn / cmdline)
E140, Error, include_str!("./error_codes/E140.md"), // ':=' used for an output parameter
E141, Error, include_str!("./error_codes/E141.md"), // Member access on a non-auto-deref pointer base
E142, Error, include_str!("./error_codes/E142.md"), // Program used as a variable type
);
}

Expand Down
43 changes: 43 additions & 0 deletions compiler/plc_diagnostics/src/diagnostics/error_codes/E142.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Program used as a variable type

A `PROGRAM` is a singleton: the compiler allocates exactly one global instance for it, and every
call to the program by name operates on that single instance. A program name therefore cannot be
used as the data type of a variable — there is nothing to instantiate.

Declaring a variable with a program type would create a detached copy of the singleton's state.
Calls through such a variable would mutate the copy while the real program instance never
advances, silently diverging from the intended behavior. IEC 61131-3 only permits program
instantiation inside `CONFIGURATION`/`RESOURCE` declarations, which are not supported.

Erroneous code example:

```iecst
PROGRAM myProg
VAR_INPUT
in1 : DINT;
END_VAR
VAR_OUTPUT
out1 : DINT;
END_VAR
out1 := in1 + 1;
END_PROGRAM

FUNCTION main : DINT
VAR
myInstance : myProg; // error: programs cannot be used as a variable type
END_VAR
myInstance(in1 := 5);
main := myInstance.out1;
END_FUNCTION
```

Instead, call the program directly by its name — this operates on the singleton instance:

```iecst
FUNCTION main : DINT
myProg(in1 := 5);
main := myProg.out1;
END_FUNCTION
```

If multiple independent instances are needed, declare a `FUNCTION_BLOCK` instead of a `PROGRAM`.
52 changes: 40 additions & 12 deletions src/validation/tests/statement_validation_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2504,25 +2504,53 @@ fn incorrect_argument_count_stateful_pous() {
│ ^ Expected a reference for parameter out_two because their type is Output
");

let diagnostics = parse_and_validate_buffered(&source.replace("<REPLACE_ME>", "PROGRAM"));
// Programs cannot be declared as instance variables (E142), so the PROGRAM variant
// calls the POUs directly by name instead.
let source = r"
FUNCTION main : DINT
prg_with_one_parameter();
prg_with_one_parameter(1, 2);

prg_with_two_parameters(1);
prg_with_two_parameters(1, 2, 3);
END_FUNCTION

PROGRAM prg_with_one_parameter
VAR_INPUT
in_one : DINT;
END_VAR
END_PROGRAM

PROGRAM prg_with_two_parameters
VAR_INPUT
in_one : DINT;
END_VAR

VAR_OUTPUT
out_two : DINT;
END_VAR
END_PROGRAM
";

let diagnostics = parse_and_validate_buffered(source);
assert_snapshot!(diagnostics, @r"
error[E032]: this POU takes 1 argument but 2 arguments were supplied
┌─ <internal>:9:13
┌─ <internal>:4:13
9one_instance(1, 2);
│ ^^^^^^^^^^^^ this POU takes 1 argument but 2 arguments were supplied
4prg_with_one_parameter(1, 2);
│ ^^^^^^^^^^^^^^^^^^^^^^ this POU takes 1 argument but 2 arguments were supplied

error[E032]: this POU takes 2 arguments but 3 arguments were supplied
┌─ <internal>:12:13
12two_instance(1, 2, 3);
│ ^^^^^^^^^^^^ this POU takes 2 arguments but 3 arguments were supplied
┌─ <internal>:7:13
7prg_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
┌─ <internal>:12:29
12two_instance(1, 2, 3);
^ Expected a reference for parameter out_two because their type is Output
┌─ <internal>:7:40
7prg_with_two_parameters(1, 2, 3);
^ Expected a reference for parameter out_two because their type is Output
");
}

Expand Down
212 changes: 212 additions & 0 deletions src/validation/tests/variable_validation_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1875,3 +1875,215 @@ fn fb_var_temp_visible_inside_fb_body_and_actions() {

assert!(diagnostics.is_empty(), "expected clean diagnostics, got:\n{diagnostics}");
}

#[test]
fn program_as_variable_type_in_function_is_reported() {
let diagnostics = parse_and_validate_buffered(
"
PROGRAM myProg
VAR_INPUT
in1 : DINT;
END_VAR
VAR_OUTPUT
out1 : DINT;
END_VAR
out1 := in1 + 1;
END_PROGRAM

FUNCTION main : DINT
VAR
myInstance : myProg;
END_VAR
myInstance(in1 := 5);
main := myInstance.out1;
END_FUNCTION
",
);

assert_snapshot!(diagnostics, @r"
error[E142]: Program `myProg` cannot be used as a variable type; programs are singletons — call `myProg` directly instead
┌─ <internal>:14:13
14 │ myInstance : myProg;
│ ^^^^^^^^^^ Program `myProg` cannot be used as a variable type; programs are singletons — call `myProg` directly instead
");
}

#[test]
fn program_as_variable_type_in_program_is_reported() {
let diagnostics = parse_and_validate_buffered(
"
PROGRAM myProg
VAR_INPUT
in1 : DINT;
END_VAR
END_PROGRAM

PROGRAM other
VAR
instance : myProg;
END_VAR
END_PROGRAM
",
);

assert_snapshot!(diagnostics, @r"
error[E142]: Program `myProg` cannot be used as a variable type; programs are singletons — call `myProg` directly instead
┌─ <internal>:10:13
10 │ instance : myProg;
│ ^^^^^^^^ Program `myProg` cannot be used as a variable type; programs are singletons — call `myProg` directly instead
");
}

#[test]
fn program_as_variable_type_in_function_block_is_reported() {
let diagnostics = parse_and_validate_buffered(
"
PROGRAM myProg
VAR_INPUT
in1 : DINT;
END_VAR
END_PROGRAM

FUNCTION_BLOCK fb
VAR_OUTPUT
instance : myProg;
END_VAR
END_FUNCTION_BLOCK
",
);

assert_snapshot!(diagnostics, @r"
error[E142]: Program `myProg` cannot be used as a variable type; programs are singletons — call `myProg` directly instead
┌─ <internal>:10:13
10 │ instance : myProg;
│ ^^^^^^^^ Program `myProg` cannot be used as a variable type; programs are singletons — call `myProg` directly instead
");
}

#[test]
fn program_as_variable_type_in_various_blocks_is_reported() {
let diagnostics = parse_and_validate_buffered(
"
PROGRAM myProg
VAR_INPUT
in1 : DINT;
END_VAR
END_PROGRAM

VAR_GLOBAL
gInstance : myProg;
END_VAR

FUNCTION_BLOCK fb
VAR_INPUT
a : myProg;
END_VAR
VAR_IN_OUT
b : myProg;
END_VAR
VAR_TEMP
c : myProg;
END_VAR
END_FUNCTION_BLOCK
",
);

assert_snapshot!(diagnostics, @r"
error[E142]: Program `myProg` cannot be used as a variable type; programs are singletons — call `myProg` directly instead
┌─ <internal>:14:13
14 │ a : myProg;
│ ^ Program `myProg` cannot be used as a variable type; programs are singletons — call `myProg` directly instead

error[E142]: Program `myProg` cannot be used as a variable type; programs are singletons — call `myProg` directly instead
┌─ <internal>:17:13
17 │ b : myProg;
│ ^ Program `myProg` cannot be used as a variable type; programs are singletons — call `myProg` directly instead

error[E142]: Program `myProg` cannot be used as a variable type; programs are singletons — call `myProg` directly instead
┌─ <internal>:20:13
20 │ c : myProg;
│ ^ Program `myProg` cannot be used as a variable type; programs are singletons — call `myProg` directly instead

error[E142]: Program `myProg` cannot be used as a variable type; programs are singletons — call `myProg` directly instead
┌─ <internal>:9:13
9 │ gInstance : myProg;
│ ^^^^^^^^^ Program `myProg` cannot be used as a variable type; programs are singletons — call `myProg` directly instead
");
}

#[test]
fn array_of_program_type_is_reported() {
let diagnostics = parse_and_validate_buffered(
"
PROGRAM myProg
VAR_INPUT
in1 : DINT;
END_VAR
END_PROGRAM

FUNCTION main : DINT
VAR
instances : ARRAY[1..3] OF myProg;
END_VAR
END_FUNCTION
",
);

assert_snapshot!(diagnostics, @r"
error[E142]: Program `myProg` cannot be used as a variable type; programs are singletons — call `myProg` directly instead
┌─ <internal>:10:13
10 │ instances : ARRAY[1..3] OF myProg;
│ ^^^^^^^^^ Program `myProg` cannot be used as a variable type; programs are singletons — call `myProg` directly instead
");
}

#[test]
fn direct_program_call_by_name_is_not_reported() {
let diagnostics = parse_and_validate_buffered(
"
PROGRAM myProg
VAR_INPUT
in1 : DINT;
END_VAR
VAR_OUTPUT
out1 : DINT;
END_VAR
out1 := in1 + 1;
END_PROGRAM

FUNCTION main : DINT
myProg(in1 := 5);
END_FUNCTION
",
);

assert!(diagnostics.is_empty(), "expected clean diagnostics, got:\n{diagnostics}");
}

#[test]
fn direct_member_access_on_program_name_is_not_reported() {
let diagnostics = parse_and_validate_buffered(
"
PROGRAM myProg
VAR_OUTPUT
out1 : DINT;
END_VAR
out1 := 1;
END_PROGRAM

FUNCTION main : DINT
main := myProg.out1;
END_FUNCTION
",
);

assert!(diagnostics.is_empty(), "expected clean diagnostics, got:\n{diagnostics}");
}
39 changes: 38 additions & 1 deletion src/validation/variable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ use super::{
ValidationContext, Validator, Validators,
};
use crate::{index::const_expressions::ConstExpression, resolver::AnnotationMap};
use crate::{index::const_expressions::UnresolvableKind, typesystem::DataTypeInformation};
use crate::{
index::const_expressions::UnresolvableKind,
typesystem::{DataTypeInformation, StructSource},
};
use crate::{index::PouIndexEntry, validation::statement::validate_enum_variant_assignment};
use crate::{index::VariableIndexEntry, resolver::StatementAnnotation};

Expand Down Expand Up @@ -393,6 +396,7 @@ fn validate_variable<T: AnnotationMap>(
context: &ValidationContext<T>,
) {
validate_variable_redeclaration(validator, variable, context);
validate_variable_is_not_a_program_instance(validator, variable, context);

validate_array_bounds_are_constant(validator, variable, context);
validate_array_ranges(validator, variable, context);
Expand Down Expand Up @@ -518,6 +522,39 @@ fn validate_variable<T: AnnotationMap>(
}
}

/// Programs are singletons with exactly one compiler-managed global instance; they cannot be
/// instantiated, so a program name must not be used as a variable type (also not as the element
/// type of an array). IEC 61131-3 only allows program instances inside CONFIGURATION/RESOURCE
/// declarations, which are not supported.
fn validate_variable_is_not_a_program_instance<T: AnnotationMap>(
validator: &mut Validator,
variable: &Variable,
context: &ValidationContext<T>,
) {
if variable.location.is_internal() {
return;
}

let ty_name = variable.data_type_declaration.get_name().unwrap_or_default();
let mut ty = context.index.get_effective_type_or_void_by_name(ty_name);
while let DataTypeInformation::Array { inner_type_name, .. } = ty.get_type_information() {
ty = context.index.get_effective_type_or_void_by_name(inner_type_name);
}

if let DataTypeInformation::Struct { source: StructSource::Pou(PouType::Program), .. } =
ty.get_type_information()
{
let program_name = ty.get_name();
validator.push_diagnostic(
Diagnostic::new(format!(
"Program `{program_name}` cannot be used as a variable type; programs are singletons — call `{program_name}` directly instead",
))
.with_error_code("E142")
.with_location(&variable.location),
);
}
}

/// Validates if a variable present in a parent POU has been redeclared in a child POU
fn validate_variable_redeclaration<T: AnnotationMap>(
validator: &mut Validator,
Expand Down
Loading