From 83483ca5853b8ecf1391c17a50b2fb0d7a94cab7 Mon Sep 17 00:00:00 2001 From: Michael Haselberger Date: Thu, 4 Dec 2025 13:49:20 +0100 Subject: [PATCH 1/7] fix: panic when using equality operator in enum definition --- .../src/diagnostics/diagnostics_registry.rs | 2 + .../src/diagnostics/error_codes/E122.md | 36 ++++++++++++++ .../src/diagnostics/error_codes/E123.md | 31 ++++++++++++ src/parser.rs | 42 +++++++++++++++-- ..._instead_of_assignment_should_error-2.snap | 47 +++++++++++++++++++ ...or_instead_of_assignment_should_error.snap | 9 ++++ src/parser/tests/type_parser_tests.rs | 14 ++++++ 7 files changed, 176 insertions(+), 5 deletions(-) create mode 100644 compiler/plc_diagnostics/src/diagnostics/error_codes/E122.md create mode 100644 compiler/plc_diagnostics/src/diagnostics/error_codes/E123.md create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_equality_operator_instead_of_assignment_should_error-2.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_equality_operator_instead_of_assignment_should_error.snap diff --git a/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs b/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs index 42721293bf0..f97d00de815 100644 --- a/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs +++ b/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs @@ -223,6 +223,8 @@ lazy_static! { E119, Error, include_str!("./error_codes/E119.md"), // Invalid use of `SUPER` keyword E120, Error, include_str!("./error_codes/E120.md"), // Invalid use of `THIS` keyword E121, Error, include_str!("./error_codes/E121.md"), // Recursive type alias + E122, Error, include_str!("./error_codes/E122.md"), // Enum element initialization must use := not = + E123, Error, include_str!("./error_codes/E123.md"), // Invalid enum element expression ); } diff --git a/compiler/plc_diagnostics/src/diagnostics/error_codes/E122.md b/compiler/plc_diagnostics/src/diagnostics/error_codes/E122.md new file mode 100644 index 00000000000..f8e7edb1b48 --- /dev/null +++ b/compiler/plc_diagnostics/src/diagnostics/error_codes/E122.md @@ -0,0 +1,36 @@ +# Enum Element Initialization Must Use Assignment Operator + +Enum element initialization must use the `:=` (assignment) operator, not the `=` (equality comparison) operator. + +## Example + +```st +// Incorrect - using = (equality operator) +TYPE State : ( + Idle := 0, + Running = 1, // Error: should be := + Stopped +); +END_TYPE + +// Correct - using := (assignment operator) +TYPE State : ( + Idle := 0, + Running := 1, + Stopped +); +END_TYPE +``` + +## Note + +You can mix assigned and unassigned enum elements. Unassigned elements will automatically get the next sequential value: + +```st +TYPE State : ( + Idle, // Automatically 0 + Running := 5, // Explicitly 5 + Stopped // Automatically 6 +); +END_TYPE +``` diff --git a/compiler/plc_diagnostics/src/diagnostics/error_codes/E123.md b/compiler/plc_diagnostics/src/diagnostics/error_codes/E123.md new file mode 100644 index 00000000000..e5d028156ac --- /dev/null +++ b/compiler/plc_diagnostics/src/diagnostics/error_codes/E123.md @@ -0,0 +1,31 @@ +# Invalid Enum Element Expression + +Enum elements must be either simple identifiers or assignments (e.g., `ElementName := Value`). + +## Example + +```st +// Incorrect +TYPE State : ( + Idle, + Running(), // Error: a call-statement cannot be an enum variant + Stopped +); +END_TYPE + +// Correct +TYPE State : ( + Idle, + Running := 3, + Stopped +); +END_TYPE +``` + +## Explanation + +Enum element definitions have a restricted syntax. Each element must be: +1. A simple identifier (e.g., `ElementName`), or +2. An assignment with a constant value (e.g., `ElementName := 123`) + +Other statement types are not allowed in enum definitions. diff --git a/src/parser.rs b/src/parser.rs index 4df9893b3aa..191fa255ce5 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4,11 +4,12 @@ use std::ops::Range; use plc_ast::{ ast::{ - AccessModifier, ArgumentProperty, AstFactory, AstNode, AstStatement, AutoDerefType, CompilationUnit, - ConfigVariable, DataType, DataTypeDeclaration, DeclarationKind, DirectAccessType, GenericBinding, - HardwareAccessType, Identifier, Implementation, Interface, LinkageType, PolymorphismMode, Pou, - PouType, PropertyBlock, PropertyImplementation, PropertyKind, ReferenceAccess, ReferenceExpr, - TypeNature, UserTypeDeclaration, Variable, VariableBlock, VariableBlockType, + flatten_expression_list, AccessModifier, ArgumentProperty, AstFactory, AstNode, AstStatement, + AutoDerefType, BinaryExpression, CompilationUnit, ConfigVariable, DataType, DataTypeDeclaration, + DeclarationKind, DirectAccessType, GenericBinding, HardwareAccessType, Identifier, Implementation, + Interface, LinkageType, Operator, PolymorphismMode, Pou, PouType, PropertyBlock, + PropertyImplementation, PropertyKind, ReferenceAccess, ReferenceExpr, TypeNature, + UserTypeDeclaration, Variable, VariableBlock, VariableBlockType, }, provider::IdProvider, }; @@ -1162,6 +1163,7 @@ fn parse_enum_type_definition( let elements = parse_any_in_region(lexer, vec![KeywordParensClose], |lexer| { // Parse Enum - we expect at least one element let elements = parse_expression_list(lexer); + validate_enum_elements(&elements, lexer); Some(elements) })?; let initializer = lexer.try_consume(KeywordAssignment).then(|| parse_expression(lexer)); @@ -1175,6 +1177,36 @@ fn parse_enum_type_definition( )) } +/// Validates that enum elements are either identifiers or assignments +fn validate_enum_elements(elements: &AstNode, lexer: &mut ParseSession) { + for element in flatten_expression_list(elements) { + match &element.stmt { + // Valid: simple identifier like `Red` or `Green` + AstStatement::ReferenceExpr(_) | AstStatement::Identifier(_) => {} + // Valid: assignment like `Red := 1` + AstStatement::Assignment(_) => {} + // Invalid: binary expression like `Red = 1` (equality comparison) + AstStatement::BinaryExpression(BinaryExpression { operator: Operator::Equal, .. }) => { + lexer.accept_diagnostic( + Diagnostic::new( + "Enum element initialization must use ':=' (assignment), not '=' (equality comparison)" + ) + .with_error_code("E122") + .with_location(element.get_location()) + ); + } + // Invalid: any other expression type in enum + _ => { + lexer.accept_diagnostic( + Diagnostic::new("Enum elements must be either identifiers or assignments (e.g., 'Value := 1')") + .with_error_code("E123") + .with_location(element.get_location()) + ); + } + } + } +} + fn parse_array_type_definition( lexer: &mut ParseSession, name: Option, diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_equality_operator_instead_of_assignment_should_error-2.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_equality_operator_instead_of_assignment_should_error-2.snap new file mode 100644 index 00000000000..001e19bf1e7 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_equality_operator_instead_of_assignment_should_error-2.snap @@ -0,0 +1,47 @@ +--- +source: src/parser/tests/type_parser_tests.rs +expression: result.user_types +--- +[ + UserTypeDeclaration { + data_type: EnumType { + name: Some( + "State", + ), + numeric_type: "DINT", + elements: ExpressionList { + expressions: [ + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "Idle", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 0, + }, + }, + BinaryExpression { + operator: Equal, + left: ReferenceExpr { + kind: Member( + Identifier { + name: "Running", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 1, + }, + }, + ], + }, + }, + initializer: None, + scope: None, + }, +] diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_equality_operator_instead_of_assignment_should_error.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_equality_operator_instead_of_assignment_should_error.snap new file mode 100644 index 00000000000..30956a939f6 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_equality_operator_instead_of_assignment_should_error.snap @@ -0,0 +1,9 @@ +--- +source: src/parser/tests/type_parser_tests.rs +expression: diagnostics +--- +error[E122]: Enum element initialization must use ':=' (assignment), not '=' (equality comparison) + ┌─ :2:34 + │ +2 │ TYPE State : (Idle := 0, Running = 1); + │ ^^^^^^^^^^^ Enum element initialization must use ':=' (assignment), not '=' (equality comparison) diff --git a/src/parser/tests/type_parser_tests.rs b/src/parser/tests/type_parser_tests.rs index 770aad11cea..7ceaace5734 100644 --- a/src/parser/tests/type_parser_tests.rs +++ b/src/parser/tests/type_parser_tests.rs @@ -141,6 +141,20 @@ fn typed_inline_enum_with_initial_values_can_be_parsed() { insta::assert_debug_snapshot!(result.pous[0]); } +#[test] +fn enum_with_equality_operator_instead_of_assignment_should_error() { + let (result, diagnostics) = parse_buffered( + r#" + TYPE State : (Idle := 0, Running = 1); + END_TYPE + "#, + ); + // Should have a parse error instead of panic in preprocessor + assert!(!diagnostics.is_empty(), "Expected parse error for enum using = instead of :="); + assert_snapshot!(diagnostics); + insta::assert_debug_snapshot!(result.user_types); +} + #[test] fn type_alias_can_be_parsed() { let (result, ..) = parse( From 2cbf61ef98a02afc3a9c5e8ad5dd782e6c176cc2 Mon Sep 17 00:00:00 2001 From: Michael Haselberger Date: Thu, 4 Dec 2025 13:58:48 +0100 Subject: [PATCH 2/7] add additional test, prepare diagnostic registry for merge --- .../src/diagnostics/diagnostics_registry.rs | 4 +- .../src/diagnostics/error_codes/E122.md | 36 --------------- .../src/diagnostics/error_codes/E123.md | 31 +++++++------ .../src/diagnostics/error_codes/E124.md | 31 +++++++++++++ src/parser.rs | 6 ++- ...l_statement_as_variant_should_error-2.snap | 44 +++++++++++++++++++ ...all_statement_as_variant_should_error.snap | 9 ++++ ...or_instead_of_assignment_should_error.snap | 2 +- src/parser/tests/type_parser_tests.rs | 14 ++++++ 9 files changed, 123 insertions(+), 54 deletions(-) delete mode 100644 compiler/plc_diagnostics/src/diagnostics/error_codes/E122.md create mode 100644 compiler/plc_diagnostics/src/diagnostics/error_codes/E124.md create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_call_statement_as_variant_should_error-2.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_call_statement_as_variant_should_error.snap diff --git a/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs b/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs index f97d00de815..4af1ad4a7b2 100644 --- a/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs +++ b/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs @@ -223,8 +223,8 @@ lazy_static! { E119, Error, include_str!("./error_codes/E119.md"), // Invalid use of `SUPER` keyword E120, Error, include_str!("./error_codes/E120.md"), // Invalid use of `THIS` keyword E121, Error, include_str!("./error_codes/E121.md"), // Recursive type alias - E122, Error, include_str!("./error_codes/E122.md"), // Enum element initialization must use := not = - E123, Error, include_str!("./error_codes/E123.md"), // Invalid enum element expression + E123, Error, include_str!("./error_codes/E123.md"), // Enum element initialization must use := not = + E124, Error, include_str!("./error_codes/E124.md"), // Invalid enum element expression ); } diff --git a/compiler/plc_diagnostics/src/diagnostics/error_codes/E122.md b/compiler/plc_diagnostics/src/diagnostics/error_codes/E122.md deleted file mode 100644 index f8e7edb1b48..00000000000 --- a/compiler/plc_diagnostics/src/diagnostics/error_codes/E122.md +++ /dev/null @@ -1,36 +0,0 @@ -# Enum Element Initialization Must Use Assignment Operator - -Enum element initialization must use the `:=` (assignment) operator, not the `=` (equality comparison) operator. - -## Example - -```st -// Incorrect - using = (equality operator) -TYPE State : ( - Idle := 0, - Running = 1, // Error: should be := - Stopped -); -END_TYPE - -// Correct - using := (assignment operator) -TYPE State : ( - Idle := 0, - Running := 1, - Stopped -); -END_TYPE -``` - -## Note - -You can mix assigned and unassigned enum elements. Unassigned elements will automatically get the next sequential value: - -```st -TYPE State : ( - Idle, // Automatically 0 - Running := 5, // Explicitly 5 - Stopped // Automatically 6 -); -END_TYPE -``` diff --git a/compiler/plc_diagnostics/src/diagnostics/error_codes/E123.md b/compiler/plc_diagnostics/src/diagnostics/error_codes/E123.md index e5d028156ac..f8e7edb1b48 100644 --- a/compiler/plc_diagnostics/src/diagnostics/error_codes/E123.md +++ b/compiler/plc_diagnostics/src/diagnostics/error_codes/E123.md @@ -1,31 +1,36 @@ -# Invalid Enum Element Expression +# Enum Element Initialization Must Use Assignment Operator -Enum elements must be either simple identifiers or assignments (e.g., `ElementName := Value`). +Enum element initialization must use the `:=` (assignment) operator, not the `=` (equality comparison) operator. ## Example ```st -// Incorrect +// Incorrect - using = (equality operator) TYPE State : ( - Idle, - Running(), // Error: a call-statement cannot be an enum variant + Idle := 0, + Running = 1, // Error: should be := Stopped ); END_TYPE -// Correct +// Correct - using := (assignment operator) TYPE State : ( - Idle, - Running := 3, + Idle := 0, + Running := 1, Stopped ); END_TYPE ``` -## Explanation +## Note -Enum element definitions have a restricted syntax. Each element must be: -1. A simple identifier (e.g., `ElementName`), or -2. An assignment with a constant value (e.g., `ElementName := 123`) +You can mix assigned and unassigned enum elements. Unassigned elements will automatically get the next sequential value: -Other statement types are not allowed in enum definitions. +```st +TYPE State : ( + Idle, // Automatically 0 + Running := 5, // Explicitly 5 + Stopped // Automatically 6 +); +END_TYPE +``` diff --git a/compiler/plc_diagnostics/src/diagnostics/error_codes/E124.md b/compiler/plc_diagnostics/src/diagnostics/error_codes/E124.md new file mode 100644 index 00000000000..e5d028156ac --- /dev/null +++ b/compiler/plc_diagnostics/src/diagnostics/error_codes/E124.md @@ -0,0 +1,31 @@ +# Invalid Enum Element Expression + +Enum elements must be either simple identifiers or assignments (e.g., `ElementName := Value`). + +## Example + +```st +// Incorrect +TYPE State : ( + Idle, + Running(), // Error: a call-statement cannot be an enum variant + Stopped +); +END_TYPE + +// Correct +TYPE State : ( + Idle, + Running := 3, + Stopped +); +END_TYPE +``` + +## Explanation + +Enum element definitions have a restricted syntax. Each element must be: +1. A simple identifier (e.g., `ElementName`), or +2. An assignment with a constant value (e.g., `ElementName := 123`) + +Other statement types are not allowed in enum definitions. diff --git a/src/parser.rs b/src/parser.rs index 191fa255ce5..693d2f2a240 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1185,13 +1185,15 @@ fn validate_enum_elements(elements: &AstNode, lexer: &mut ParseSession) { AstStatement::ReferenceExpr(_) | AstStatement::Identifier(_) => {} // Valid: assignment like `Red := 1` AstStatement::Assignment(_) => {} + // Invalid: empty statement, is validated elsewhere so we skip it here + AstStatement::EmptyStatement(_) => {} // Invalid: binary expression like `Red = 1` (equality comparison) AstStatement::BinaryExpression(BinaryExpression { operator: Operator::Equal, .. }) => { lexer.accept_diagnostic( Diagnostic::new( "Enum element initialization must use ':=' (assignment), not '=' (equality comparison)" ) - .with_error_code("E122") + .with_error_code("E123") .with_location(element.get_location()) ); } @@ -1199,7 +1201,7 @@ fn validate_enum_elements(elements: &AstNode, lexer: &mut ParseSession) { _ => { lexer.accept_diagnostic( Diagnostic::new("Enum elements must be either identifiers or assignments (e.g., 'Value := 1')") - .with_error_code("E123") + .with_error_code("E124") .with_location(element.get_location()) ); } diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_call_statement_as_variant_should_error-2.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_call_statement_as_variant_should_error-2.snap new file mode 100644 index 00000000000..fbafec09e42 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_call_statement_as_variant_should_error-2.snap @@ -0,0 +1,44 @@ +--- +source: src/parser/tests/type_parser_tests.rs +expression: result.user_types +--- +[ + UserTypeDeclaration { + data_type: EnumType { + name: Some( + "State", + ), + numeric_type: "DINT", + elements: ExpressionList { + expressions: [ + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "Idle", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 0, + }, + }, + CallStatement { + operator: ReferenceExpr { + kind: Member( + Identifier { + name: "foo", + }, + ), + base: None, + }, + parameters: None, + }, + ], + }, + }, + initializer: None, + scope: None, + }, +] diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_call_statement_as_variant_should_error.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_call_statement_as_variant_should_error.snap new file mode 100644 index 00000000000..d6f2c611ce9 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_call_statement_as_variant_should_error.snap @@ -0,0 +1,9 @@ +--- +source: src/parser/tests/type_parser_tests.rs +expression: diagnostics +--- +error[E124]: Enum elements must be either identifiers or assignments (e.g., 'Value := 1') + ┌─ :2:34 + │ +2 │ TYPE State : (Idle := 0, foo()); + │ ^^^^^^ Enum elements must be either identifiers or assignments (e.g., 'Value := 1') diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_equality_operator_instead_of_assignment_should_error.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_equality_operator_instead_of_assignment_should_error.snap index 30956a939f6..6e4dbe428ed 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_equality_operator_instead_of_assignment_should_error.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_equality_operator_instead_of_assignment_should_error.snap @@ -2,7 +2,7 @@ source: src/parser/tests/type_parser_tests.rs expression: diagnostics --- -error[E122]: Enum element initialization must use ':=' (assignment), not '=' (equality comparison) +error[E123]: Enum element initialization must use ':=' (assignment), not '=' (equality comparison) ┌─ :2:34 │ 2 │ TYPE State : (Idle := 0, Running = 1); diff --git a/src/parser/tests/type_parser_tests.rs b/src/parser/tests/type_parser_tests.rs index 7ceaace5734..9db10bfa03a 100644 --- a/src/parser/tests/type_parser_tests.rs +++ b/src/parser/tests/type_parser_tests.rs @@ -155,6 +155,20 @@ fn enum_with_equality_operator_instead_of_assignment_should_error() { insta::assert_debug_snapshot!(result.user_types); } +#[test] +fn enum_with_call_statement_as_variant_should_error() { + let (result, diagnostics) = parse_buffered( + r#" + TYPE State : (Idle := 0, foo()); + END_TYPE + "#, + ); + // Should have a parse error instead of panic in preprocessor + assert!(!diagnostics.is_empty(), "Expected parse error for invalid enum variant"); + assert_snapshot!(diagnostics); + insta::assert_debug_snapshot!(result.user_types); +} + #[test] fn type_alias_can_be_parsed() { let (result, ..) = parse( From d46025c86960f5e9e5e6062fbe483c0910b1af3f Mon Sep 17 00:00:00 2001 From: Michael Haselberger Date: Thu, 4 Dec 2025 14:24:46 +0100 Subject: [PATCH 3/7] disallow qualified references --- .../src/diagnostics/error_codes/E124.md | 6 +- src/parser.rs | 5 +- ...all_statement_as_variant_should_error.snap | 4 +- ...alified_member_variant_should_error-2.snap | 67 +++++++++++++++++++ ...qualified_member_variant_should_error.snap | 15 +++++ src/parser/tests/type_parser_tests.rs | 15 ++++- 6 files changed, 103 insertions(+), 9 deletions(-) create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_qualified_member_variant_should_error-2.snap create mode 100644 src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_qualified_member_variant_should_error.snap diff --git a/compiler/plc_diagnostics/src/diagnostics/error_codes/E124.md b/compiler/plc_diagnostics/src/diagnostics/error_codes/E124.md index e5d028156ac..9189c92e33e 100644 --- a/compiler/plc_diagnostics/src/diagnostics/error_codes/E124.md +++ b/compiler/plc_diagnostics/src/diagnostics/error_codes/E124.md @@ -8,8 +8,8 @@ Enum elements must be either simple identifiers or assignments (e.g., `ElementNa // Incorrect TYPE State : ( Idle, - Running(), // Error: a call-statement cannot be an enum variant - Stopped + Running.Fast, // Error: Enum variants cannot be a qualified reference, + Stopped(), // call-statement, etc. ); END_TYPE @@ -26,6 +26,6 @@ END_TYPE Enum element definitions have a restricted syntax. Each element must be: 1. A simple identifier (e.g., `ElementName`), or -2. An assignment with a constant value (e.g., `ElementName := 123`) +2. An assignment with a constant value/reference (e.g., `ElementName := 123`) Other statement types are not allowed in enum definitions. diff --git a/src/parser.rs b/src/parser.rs index b33eed613e3..e568e05f7a6 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1205,7 +1205,8 @@ fn validate_enum_elements(elements: &AstNode, lexer: &mut ParseSession) { for element in flatten_expression_list(elements) { match &element.stmt { // Valid: simple identifier like `Red` or `Green` - AstStatement::ReferenceExpr(_) | AstStatement::Identifier(_) => {} + AstStatement::ReferenceExpr(ReferenceExpr { access: ReferenceAccess::Member(_), base: None }) + | AstStatement::Identifier(_) => {} // Valid: assignment like `Red := 1` AstStatement::Assignment(_) => {} // Invalid: empty statement, is validated elsewhere so we skip it here @@ -1223,7 +1224,7 @@ fn validate_enum_elements(elements: &AstNode, lexer: &mut ParseSession) { // Invalid: any other expression type in enum _ => { lexer.accept_diagnostic( - Diagnostic::new("Enum elements must be either identifiers or assignments (e.g., 'Value := 1')") + Diagnostic::new("Enum elements must be either flat identifiers or assignments (e.g., 'Value' or 'Value := 1')") .with_error_code("E124") .with_location(element.get_location()) ); diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_call_statement_as_variant_should_error.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_call_statement_as_variant_should_error.snap index d6f2c611ce9..79254080d7a 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_call_statement_as_variant_should_error.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_call_statement_as_variant_should_error.snap @@ -2,8 +2,8 @@ source: src/parser/tests/type_parser_tests.rs expression: diagnostics --- -error[E124]: Enum elements must be either identifiers or assignments (e.g., 'Value := 1') +error[E124]: Enum elements must be either flat identifiers or assignments (e.g., 'Value' or 'Value := 1') ┌─ :2:34 │ 2 │ TYPE State : (Idle := 0, foo()); - │ ^^^^^^ Enum elements must be either identifiers or assignments (e.g., 'Value := 1') + │ ^^^^^^ Enum elements must be either flat identifiers or assignments (e.g., 'Value' or 'Value := 1') diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_qualified_member_variant_should_error-2.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_qualified_member_variant_should_error-2.snap new file mode 100644 index 00000000000..80f21a9e70f --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_qualified_member_variant_should_error-2.snap @@ -0,0 +1,67 @@ +--- +source: src/parser/tests/type_parser_tests.rs +expression: result.user_types +--- +[ + UserTypeDeclaration { + data_type: EnumType { + name: Some( + "State", + ), + numeric_type: "DINT", + elements: ExpressionList { + expressions: [ + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "Idle", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 0, + }, + }, + ReferenceExpr { + kind: Member( + Identifier { + name: "Fast", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "Running", + }, + ), + base: None, + }, + ), + }, + ReferenceExpr { + kind: Member( + Identifier { + name: "Slow", + }, + ), + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "Running", + }, + ), + base: None, + }, + ), + }, + ], + }, + }, + initializer: None, + scope: None, + }, +] diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_qualified_member_variant_should_error.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_qualified_member_variant_should_error.snap new file mode 100644 index 00000000000..ffde74ed323 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_qualified_member_variant_should_error.snap @@ -0,0 +1,15 @@ +--- +source: src/parser/tests/type_parser_tests.rs +expression: diagnostics +--- +error[E124]: Enum elements must be either flat identifiers or assignments (e.g., 'Value' or 'Value := 1') + ┌─ :2:34 + │ +2 │ TYPE State : (Idle := 0, Running.Fast, Running.Slow); + │ ^^^^^^^^^^^^ Enum elements must be either flat identifiers or assignments (e.g., 'Value' or 'Value := 1') + +error[E124]: Enum elements must be either flat identifiers or assignments (e.g., 'Value' or 'Value := 1') + ┌─ :2:48 + │ +2 │ TYPE State : (Idle := 0, Running.Fast, Running.Slow); + │ ^^^^^^^^^^^^ Enum elements must be either flat identifiers or assignments (e.g., 'Value' or 'Value := 1') diff --git a/src/parser/tests/type_parser_tests.rs b/src/parser/tests/type_parser_tests.rs index 6c36a9cae5a..b1cafde6e37 100644 --- a/src/parser/tests/type_parser_tests.rs +++ b/src/parser/tests/type_parser_tests.rs @@ -149,7 +149,6 @@ fn enum_with_equality_operator_instead_of_assignment_should_error() { END_TYPE "#, ); - // Should have a parse error instead of panic in preprocessor assert!(!diagnostics.is_empty(), "Expected parse error for enum using = instead of :="); assert_snapshot!(diagnostics); insta::assert_debug_snapshot!(result.user_types); @@ -163,7 +162,19 @@ fn enum_with_call_statement_as_variant_should_error() { END_TYPE "#, ); - // Should have a parse error instead of panic in preprocessor + assert!(!diagnostics.is_empty(), "Expected parse error for invalid enum variant"); + assert_snapshot!(diagnostics); + insta::assert_debug_snapshot!(result.user_types); +} + +#[test] +fn enum_with_qualified_member_variant_should_error() { + let (result, diagnostics) = parse_buffered( + r#" + TYPE State : (Idle := 0, Running.Fast, Running.Slow); + END_TYPE + "#, + ); assert!(!diagnostics.is_empty(), "Expected parse error for invalid enum variant"); assert_snapshot!(diagnostics); insta::assert_debug_snapshot!(result.user_types); From f4b1053eaee955e9108f6443eee46d2a31791b94 Mon Sep 17 00:00:00 2001 From: Michael Haselberger Date: Fri, 5 Dec 2025 09:34:42 +0000 Subject: [PATCH 4/7] reset parser --- src/parser.rs | 45 +++++---------------------------------------- 1 file changed, 5 insertions(+), 40 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index e568e05f7a6..70d9d930d81 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4,12 +4,11 @@ use std::ops::Range; use plc_ast::{ ast::{ - flatten_expression_list, AccessModifier, ArgumentProperty, AstFactory, AstNode, AstStatement, - AutoDerefType, BinaryExpression, CompilationUnit, ConfigVariable, DataType, DataTypeDeclaration, - DeclarationKind, DirectAccessType, GenericBinding, HardwareAccessType, Identifier, Implementation, - Interface, LinkageType, Operator, PolymorphismMode, Pou, PouType, PropertyBlock, - PropertyImplementation, PropertyKind, ReferenceAccess, ReferenceExpr, TypeNature, - UserTypeDeclaration, Variable, VariableBlock, VariableBlockType, + AccessModifier, ArgumentProperty, AstFactory, AstNode, AstStatement, AutoDerefType, CompilationUnit, + ConfigVariable, DataType, DataTypeDeclaration, DeclarationKind, DirectAccessType, GenericBinding, + HardwareAccessType, Identifier, Implementation, Interface, LinkageType, PolymorphismMode, Pou, + PouType, PropertyBlock, PropertyImplementation, PropertyKind, ReferenceAccess, ReferenceExpr, + TypeNature, UserTypeDeclaration, Variable, VariableBlock, VariableBlockType, }, provider::IdProvider, }; @@ -1180,7 +1179,6 @@ fn parse_enum_type_definition( let elements = parse_any_in_region(lexer, vec![KeywordParensClose], |lexer| { // Parse Enum - we expect at least one element let elements = parse_expression_list(lexer); - validate_enum_elements(&elements, lexer); Some(elements) })?; @@ -1200,39 +1198,6 @@ fn parse_enum_type_definition( )) } -/// Validates that enum elements are either identifiers or assignments -fn validate_enum_elements(elements: &AstNode, lexer: &mut ParseSession) { - for element in flatten_expression_list(elements) { - match &element.stmt { - // Valid: simple identifier like `Red` or `Green` - AstStatement::ReferenceExpr(ReferenceExpr { access: ReferenceAccess::Member(_), base: None }) - | AstStatement::Identifier(_) => {} - // Valid: assignment like `Red := 1` - AstStatement::Assignment(_) => {} - // Invalid: empty statement, is validated elsewhere so we skip it here - AstStatement::EmptyStatement(_) => {} - // Invalid: binary expression like `Red = 1` (equality comparison) - AstStatement::BinaryExpression(BinaryExpression { operator: Operator::Equal, .. }) => { - lexer.accept_diagnostic( - Diagnostic::new( - "Enum element initialization must use ':=' (assignment), not '=' (equality comparison)" - ) - .with_error_code("E123") - .with_location(element.get_location()) - ); - } - // Invalid: any other expression type in enum - _ => { - lexer.accept_diagnostic( - Diagnostic::new("Enum elements must be either flat identifiers or assignments (e.g., 'Value' or 'Value := 1')") - .with_error_code("E124") - .with_location(element.get_location()) - ); - } - } - } -} - fn parse_array_type_definition( lexer: &mut ParseSession, name: Option, From fa73ce1fb595ea430d1cd8bfd47843529c83799a Mon Sep 17 00:00:00 2001 From: Bipul Lamsal Date: Fri, 10 Oct 2025 12:46:48 +0545 Subject: [PATCH 5/7] fix: ast node expression conversion --- src/parser.rs | 59 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 70d9d930d81..6eacbd676d6 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -33,7 +33,7 @@ use crate::{ use self::{ control_parser::parse_control_statement, - expressions_parser::{parse_expression, parse_expression_list}, + expressions_parser::{parse_expression, parse_range_statement}, }; mod control_parser; @@ -1176,17 +1176,8 @@ fn parse_enum_type_definition( name: Option, ) -> Option<(DataTypeDeclaration, Option)> { let start = lexer.last_location(); - let elements = parse_any_in_region(lexer, vec![KeywordParensClose], |lexer| { - // Parse Enum - we expect at least one element - let elements = parse_expression_list(lexer); - Some(elements) - })?; - - // Check for Codesys-style type specification after the enum list - // TYPE COLOR : (...) DWORD; - let numeric_type = - if lexer.token == Identifier { lexer.slice_and_advance() } else { DINT_TYPE.to_string() }; + let elements = parse_any_in_region(lexer, vec![KeywordParensClose], parse_enum_elements(lexer))?; let initializer = lexer.try_consume(KeywordAssignment).then(|| parse_expression(lexer)); Some(( DataTypeDeclaration::Definition { @@ -1198,6 +1189,52 @@ fn parse_enum_type_definition( )) } +/// Parse comma-separated enum elements (identifier or identifier := literal) +fn parse_enum_elements(lexer: &mut ParseSession) -> Option { + let start = lexer.location(); + let mut elements = vec![]; + + loop { + let element = parse_enum_element(lexer)?; + elements.push(element); + + // check for comma + if !lexer.try_consume(KeywordComma) { + break; + } + + // check for trailing comma + if lexer.closes_open_region(&lexer.token) { + break; + } + } + + if elements.len() == 1 { + return Some(elements.into_iter().next().unwrap()); + } + + Some(AstFactory::create_expression_list(elements, start.span(&lexer.last_location()), lexer.next_id())) +} + +/// Parse a single enum element: identifier or identifier := literal +fn parse_enum_element(lexer: &mut ParseSession) -> Option { + let start = lexer.location(); + + let idfr = parse_identifier(lexer)?; + + let identifier_node = AstFactory::create_identifier(&idfr.0, start, lexer.next_id()); + + if lexer.try_consume(KeywordAssignment) { + let value = parse_range_statement(lexer); + let result = AstFactory::create_assignment(identifier_node, value, lexer.next_id()); + return Some(result); + } + + let ref_expr = AstFactory::create_member_reference(identifier_node, None, lexer.next_id()); + + Some(ref_expr) +} + fn parse_array_type_definition( lexer: &mut ParseSession, name: Option, From cf807930ad34ac535c2f01791f921a29cfffa3e9 Mon Sep 17 00:00:00 2001 From: Michael Haselberger Date: Fri, 5 Dec 2025 11:12:05 +0100 Subject: [PATCH 6/7] add additional tests, fix error recovery and fix codesys style typed enums --- .../src/diagnostics/diagnostics_registry.rs | 2 -- .../src/diagnostics/error_codes/E123.md | 36 ------------------- .../src/diagnostics/error_codes/E124.md | 31 ---------------- src/parser.rs | 23 +++++++++--- ...all_statement_as_variant_should_error.snap | 12 +++++-- ...or_instead_of_assignment_should_error.snap | 6 ++-- ...qualified_member_variant_should_error.snap | 12 ++----- src/parser/tests/type_parser_tests.rs | 21 ++++------- ...ests__validate_empty_enum_declaration.snap | 14 -------- 9 files changed, 39 insertions(+), 118 deletions(-) delete mode 100644 compiler/plc_diagnostics/src/diagnostics/error_codes/E123.md delete mode 100644 compiler/plc_diagnostics/src/diagnostics/error_codes/E124.md diff --git a/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs b/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs index 12ea6f3e9b3..28e51f3403d 100644 --- a/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs +++ b/compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs @@ -224,8 +224,6 @@ lazy_static! { E120, Error, include_str!("./error_codes/E120.md"), // Invalid use of `THIS` keyword E121, Error, include_str!("./error_codes/E121.md"), // Recursive type alias E122, Error, include_str!("./error_codes/E122.md"), // Invalid enum base type - E123, Error, include_str!("./error_codes/E123.md"), // Enum element initialization must use := not = - E124, Error, include_str!("./error_codes/E124.md"), // Invalid enum element expression ); } diff --git a/compiler/plc_diagnostics/src/diagnostics/error_codes/E123.md b/compiler/plc_diagnostics/src/diagnostics/error_codes/E123.md deleted file mode 100644 index f8e7edb1b48..00000000000 --- a/compiler/plc_diagnostics/src/diagnostics/error_codes/E123.md +++ /dev/null @@ -1,36 +0,0 @@ -# Enum Element Initialization Must Use Assignment Operator - -Enum element initialization must use the `:=` (assignment) operator, not the `=` (equality comparison) operator. - -## Example - -```st -// Incorrect - using = (equality operator) -TYPE State : ( - Idle := 0, - Running = 1, // Error: should be := - Stopped -); -END_TYPE - -// Correct - using := (assignment operator) -TYPE State : ( - Idle := 0, - Running := 1, - Stopped -); -END_TYPE -``` - -## Note - -You can mix assigned and unassigned enum elements. Unassigned elements will automatically get the next sequential value: - -```st -TYPE State : ( - Idle, // Automatically 0 - Running := 5, // Explicitly 5 - Stopped // Automatically 6 -); -END_TYPE -``` diff --git a/compiler/plc_diagnostics/src/diagnostics/error_codes/E124.md b/compiler/plc_diagnostics/src/diagnostics/error_codes/E124.md deleted file mode 100644 index 9189c92e33e..00000000000 --- a/compiler/plc_diagnostics/src/diagnostics/error_codes/E124.md +++ /dev/null @@ -1,31 +0,0 @@ -# Invalid Enum Element Expression - -Enum elements must be either simple identifiers or assignments (e.g., `ElementName := Value`). - -## Example - -```st -// Incorrect -TYPE State : ( - Idle, - Running.Fast, // Error: Enum variants cannot be a qualified reference, - Stopped(), // call-statement, etc. -); -END_TYPE - -// Correct -TYPE State : ( - Idle, - Running := 3, - Stopped -); -END_TYPE -``` - -## Explanation - -Enum element definitions have a restricted syntax. Each element must be: -1. A simple identifier (e.g., `ElementName`), or -2. An assignment with a constant value/reference (e.g., `ElementName := 123`) - -Other statement types are not allowed in enum definitions. diff --git a/src/parser.rs b/src/parser.rs index 6eacbd676d6..3a7e6bee747 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1177,7 +1177,11 @@ fn parse_enum_type_definition( ) -> Option<(DataTypeDeclaration, Option)> { let start = lexer.last_location(); - let elements = parse_any_in_region(lexer, vec![KeywordParensClose], parse_enum_elements(lexer))?; + let elements = parse_any_in_region(lexer, vec![KeywordParensClose], parse_enum_elements)?; + // Check for Codesys-style type specification after the enum list + // TYPE COLOR : (...) DWORD; + let numeric_type = + if lexer.token == Identifier { lexer.slice_and_advance() } else { DINT_TYPE.to_string() }; let initializer = lexer.try_consume(KeywordAssignment).then(|| parse_expression(lexer)); Some(( DataTypeDeclaration::Definition { @@ -1195,20 +1199,29 @@ fn parse_enum_elements(lexer: &mut ParseSession) -> Option { let mut elements = vec![]; loop { + // Check if we've hit the closing token (e.g., ')') immediately + // This handles empty enums like `()` for error recovery + if lexer.closes_open_region(&lexer.token) { + break; + } + let element = parse_enum_element(lexer)?; elements.push(element); - // check for comma if !lexer.try_consume(KeywordComma) { break; } - // check for trailing comma if lexer.closes_open_region(&lexer.token) { break; } } + // Handle empty enum case (no elements parsed) + if elements.is_empty() { + return Some(AstFactory::create_empty_statement(start, lexer.next_id())); + } + if elements.len() == 1 { return Some(elements.into_iter().next().unwrap()); } @@ -1223,14 +1236,14 @@ fn parse_enum_element(lexer: &mut ParseSession) -> Option { let idfr = parse_identifier(lexer)?; let identifier_node = AstFactory::create_identifier(&idfr.0, start, lexer.next_id()); + let ref_expr = AstFactory::create_member_reference(identifier_node, None, lexer.next_id()); if lexer.try_consume(KeywordAssignment) { let value = parse_range_statement(lexer); - let result = AstFactory::create_assignment(identifier_node, value, lexer.next_id()); + let result = AstFactory::create_assignment(ref_expr, value, lexer.next_id()); return Some(result); } - let ref_expr = AstFactory::create_member_reference(identifier_node, None, lexer.next_id()); Some(ref_expr) } diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_call_statement_as_variant_should_error.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_call_statement_as_variant_should_error.snap index 79254080d7a..8a056cde9a5 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_call_statement_as_variant_should_error.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_call_statement_as_variant_should_error.snap @@ -2,8 +2,14 @@ source: src/parser/tests/type_parser_tests.rs expression: diagnostics --- -error[E124]: Enum elements must be either flat identifiers or assignments (e.g., 'Value' or 'Value := 1') - ┌─ :2:34 +error[E007]: Unexpected token: expected KeywordParensClose but found '(' + ┌─ :2:37 │ 2 │ TYPE State : (Idle := 0, foo()); - │ ^^^^^^ Enum elements must be either flat identifiers or assignments (e.g., 'Value' or 'Value := 1') + │ ^ Unexpected token: expected KeywordParensClose but found '(' + +error[E007]: Unexpected token: expected KeywordSemicolon but found ')' + ┌─ :2:39 + │ +2 │ TYPE State : (Idle := 0, foo()); + │ ^ Unexpected token: expected KeywordSemicolon but found ')' diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_equality_operator_instead_of_assignment_should_error.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_equality_operator_instead_of_assignment_should_error.snap index 6e4dbe428ed..cf290c3d11e 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_equality_operator_instead_of_assignment_should_error.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_equality_operator_instead_of_assignment_should_error.snap @@ -2,8 +2,8 @@ source: src/parser/tests/type_parser_tests.rs expression: diagnostics --- -error[E123]: Enum element initialization must use ':=' (assignment), not '=' (equality comparison) - ┌─ :2:34 +error[E007]: Unexpected token: expected KeywordParensClose but found '= 1' + ┌─ :2:42 │ 2 │ TYPE State : (Idle := 0, Running = 1); - │ ^^^^^^^^^^^ Enum element initialization must use ':=' (assignment), not '=' (equality comparison) + │ ^^^ Unexpected token: expected KeywordParensClose but found '= 1' diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_qualified_member_variant_should_error.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_qualified_member_variant_should_error.snap index ffde74ed323..490ea5e165b 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_qualified_member_variant_should_error.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_qualified_member_variant_should_error.snap @@ -2,14 +2,8 @@ source: src/parser/tests/type_parser_tests.rs expression: diagnostics --- -error[E124]: Enum elements must be either flat identifiers or assignments (e.g., 'Value' or 'Value := 1') - ┌─ :2:34 +error[E007]: Unexpected token: expected KeywordParensClose but found '.Fast, Running.Slow' + ┌─ :2:41 │ 2 │ TYPE State : (Idle := 0, Running.Fast, Running.Slow); - │ ^^^^^^^^^^^^ Enum elements must be either flat identifiers or assignments (e.g., 'Value' or 'Value := 1') - -error[E124]: Enum elements must be either flat identifiers or assignments (e.g., 'Value' or 'Value := 1') - ┌─ :2:48 - │ -2 │ TYPE State : (Idle := 0, Running.Fast, Running.Slow); - │ ^^^^^^^^^^^^ Enum elements must be either flat identifiers or assignments (e.g., 'Value' or 'Value := 1') + │ ^^^^^^^^^^^^^^^^^^^ Unexpected token: expected KeywordParensClose but found '.Fast, Running.Slow' diff --git a/src/parser/tests/type_parser_tests.rs b/src/parser/tests/type_parser_tests.rs index b1cafde6e37..ec5807a69aa 100644 --- a/src/parser/tests/type_parser_tests.rs +++ b/src/parser/tests/type_parser_tests.rs @@ -143,7 +143,7 @@ fn typed_inline_enum_with_initial_values_can_be_parsed() { #[test] fn enum_with_equality_operator_instead_of_assignment_should_error() { - let (result, diagnostics) = parse_buffered( + let (_, diagnostics) = parse_buffered( r#" TYPE State : (Idle := 0, Running = 1); END_TYPE @@ -151,12 +151,11 @@ fn enum_with_equality_operator_instead_of_assignment_should_error() { ); assert!(!diagnostics.is_empty(), "Expected parse error for enum using = instead of :="); assert_snapshot!(diagnostics); - insta::assert_debug_snapshot!(result.user_types); } #[test] fn enum_with_call_statement_as_variant_should_error() { - let (result, diagnostics) = parse_buffered( + let (_, diagnostics) = parse_buffered( r#" TYPE State : (Idle := 0, foo()); END_TYPE @@ -164,12 +163,11 @@ fn enum_with_call_statement_as_variant_should_error() { ); assert!(!diagnostics.is_empty(), "Expected parse error for invalid enum variant"); assert_snapshot!(diagnostics); - insta::assert_debug_snapshot!(result.user_types); } #[test] fn enum_with_qualified_member_variant_should_error() { - let (result, diagnostics) = parse_buffered( + let (_, diagnostics) = parse_buffered( r#" TYPE State : (Idle := 0, Running.Fast, Running.Slow); END_TYPE @@ -177,7 +175,6 @@ fn enum_with_qualified_member_variant_should_error() { ); assert!(!diagnostics.is_empty(), "Expected parse error for invalid enum variant"); assert_snapshot!(diagnostics); - insta::assert_debug_snapshot!(result.user_types); } #[test] @@ -835,26 +832,20 @@ fn enum_with_no_elements_produces_syntax_error() { TYPE ANOTHER_EMPTY_ENUM : () INT; "#, ); - assert!(diagnostics.len() > 0); - assert_snapshot!(diagnostics, @r" + assert!(!diagnostics.is_empty()); + assert_snapshot!(diagnostics, @r###" error[E007]: Unexpected token: expected Literal but found ) ┌─ :2:32 │ 2 │ TYPE EMPTY_ENUM : INT (); │ ^ Unexpected token: expected Literal but found ) - error[E007]: Unexpected token: expected Literal but found ) - ┌─ :5:36 - │ - 5 │ TYPE ANOTHER_EMPTY_ENUM : () INT; - │ ^ Unexpected token: expected Literal but found ) - error[E007]: Unexpected token: expected KeywordEndType but found '' ┌─ :6:9 │ 6 │ │ ^ Unexpected token: expected KeywordEndType but found '' - "); + "###); // User type should still be created despite the error (error recovery) assert_debug_snapshot!(result.user_types[0], @r#" UserTypeDeclaration { diff --git a/src/validation/snapshots/rusty__validation__variable__variable_validator_tests__validate_empty_enum_declaration.snap b/src/validation/snapshots/rusty__validation__variable__variable_validator_tests__validate_empty_enum_declaration.snap index 6c0a0ea1db5..d709ee2e11e 100644 --- a/src/validation/snapshots/rusty__validation__variable__variable_validator_tests__validate_empty_enum_declaration.snap +++ b/src/validation/snapshots/rusty__validation__variable__variable_validator_tests__validate_empty_enum_declaration.snap @@ -2,18 +2,6 @@ source: src/validation/variable.rs expression: diagnostics --- -error[E007]: Unexpected token: expected Literal but found ) - ┌─ :2:25 - │ -2 │ TYPE my_enum : (); END_TYPE - │ ^ Unexpected token: expected Literal but found ) - -error[E007]: Unexpected token: expected Literal but found ) - ┌─ :6:28 - │ -6 │ my_enum : (); - │ ^ Unexpected token: expected Literal but found ) - error[E028]: Variable block is empty ┌─ :2:14 │ @@ -25,5 +13,3 @@ error[E028]: Variable block is empty │ 6 │ my_enum : (); │ ^^ Variable block is empty - - From be6f0a946e998c9339507a5b069dd8c1ac170394 Mon Sep 17 00:00:00 2001 From: Michael Haselberger Date: Fri, 5 Dec 2025 11:15:39 +0100 Subject: [PATCH 7/7] delete unreferenced snapshots, fmt --- ...ts__builtin_function_call_lower_bound.snap | 49 -------------- ...ion_tests__builtin_function_call_move.snap | 22 ------ ...sion_tests__builtin_function_call_sel.snap | 25 ------- ...n_tests__builtin_function_call_sizeof.snap | 21 ------ ...ts__builtin_function_call_upper_bound.snap | 49 -------------- ...ring__calls__tests__fnptr_no_argument.snap | 67 ------------------- src/parser.rs | 1 - ...l_statement_as_variant_should_error-2.snap | 44 ------------ ..._instead_of_assignment_should_error-2.snap | 47 ------------- ...alified_member_variant_should_error-2.snap | 67 ------------------- ...esolve_function_members_via_qualifier.snap | 21 ------ 11 files changed, 413 deletions(-) delete mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_lower_bound.snap delete mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_move.snap delete mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sel.snap delete mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sizeof.snap delete mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_upper_bound.snap delete mode 100644 src/lowering/snapshots/rusty__lowering__calls__tests__fnptr_no_argument.snap delete mode 100644 src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_call_statement_as_variant_should_error-2.snap delete mode 100644 src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_equality_operator_instead_of_assignment_should_error-2.snap delete mode 100644 src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_qualified_member_variant_should_error-2.snap delete mode 100644 src/validation/tests/snapshots/rusty__validation__tests__reference_resolve_tests__resolve_function_members_via_qualifier.snap diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_lower_bound.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_lower_bound.snap deleted file mode 100644 index bb4fd08e759..00000000000 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_lower_bound.snap +++ /dev/null @@ -1,49 +0,0 @@ ---- -source: src/codegen/tests/expression_tests.rs -expression: result -snapshot_kind: text ---- -; ModuleID = '' -source_filename = "" -target datalayout = "[filtered]" -target triple = "[filtered]" - -%main = type { [2 x i32], i32 } -%__foo_vla = type { i32*, [2 x i32] } - -@main_instance = global %main zeroinitializer -@____foo_vla__init = unnamed_addr constant %__foo_vla zeroinitializer - -define void @main(%main* %0) { -entry: - %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 - %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 - %auto_deref = load [2 x i32], [2 x i32]* %a, align 4 - %outer_arr_gep = getelementptr inbounds [2 x i32], [2 x i32]* %a, i32 0, i32 0 - %vla_struct = alloca %__foo_vla, align 8 - %vla_array_gep = getelementptr inbounds %__foo_vla, %__foo_vla* %vla_struct, i32 0, i32 0 - %vla_dimensions_gep = getelementptr inbounds %__foo_vla, %__foo_vla* %vla_struct, i32 0, i32 1 - store [2 x i32] [i32 0, i32 1], [2 x i32]* %vla_dimensions_gep, align 4 - store i32* %outer_arr_gep, i32** %vla_array_gep, align 8 - %1 = load %__foo_vla, %__foo_vla* %vla_struct, align 8 - %vla_struct_ptr = alloca %__foo_vla, align 8 - store %__foo_vla %1, %__foo_vla* %vla_struct_ptr, align 8 - %call = call i32 @foo(%__foo_vla* %vla_struct_ptr) - store i32 %call, i32* %b, align 4 - ret void -} - -define i32 @foo(%__foo_vla* %0) { -entry: - %foo = alloca i32, align 4 - %vla = alloca %__foo_vla*, align 8 - store %__foo_vla* %0, %__foo_vla** %vla, align 8 - store i32 0, i32* %foo, align 4 - %deref = load %__foo_vla*, %__foo_vla** %vla, align 8 - %dim = getelementptr inbounds %__foo_vla, %__foo_vla* %deref, i32 0, i32 1 - %1 = getelementptr inbounds [2 x i32], [2 x i32]* %dim, i32 0, i32 0 - %2 = load i32, i32* %1, align 4 - store i32 %2, i32* %foo, align 4 - %foo_ret = load i32, i32* %foo, align 4 - ret i32 %foo_ret -} diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_move.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_move.snap deleted file mode 100644 index 1d9a5d48e1d..00000000000 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_move.snap +++ /dev/null @@ -1,22 +0,0 @@ ---- -source: src/codegen/tests/expression_tests.rs -expression: result -snapshot_kind: text ---- -; ModuleID = '' -source_filename = "" -target datalayout = "[filtered]" -target triple = "[filtered]" - -%main = type { i32, i32 } - -@main_instance = global %main zeroinitializer - -define void @main(%main* %0) { -entry: - %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 - %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 - %load_b = load i32, i32* %b, align 4 - store i32 %load_b, i32* %a, align 4 - ret void -} diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sel.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sel.snap deleted file mode 100644 index a4fc1192dc0..00000000000 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sel.snap +++ /dev/null @@ -1,25 +0,0 @@ ---- -source: src/codegen/tests/expression_tests.rs -expression: result -snapshot_kind: text ---- -; ModuleID = '' -source_filename = "" -target datalayout = "[filtered]" -target triple = "[filtered]" - -%main = type { i32, i32, i32 } - -@main_instance = global %main zeroinitializer - -define void @main(%main* %0) { -entry: - %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 - %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 - %c = getelementptr inbounds %main, %main* %0, i32 0, i32 2 - %load_b = load i32, i32* %b, align 4 - %load_c = load i32, i32* %c, align 4 - %1 = select i1 true, i32 %load_c, i32 %load_b - store i32 %1, i32* %a, align 4 - ret void -} diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sizeof.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sizeof.snap deleted file mode 100644 index a1b23f4512d..00000000000 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_sizeof.snap +++ /dev/null @@ -1,21 +0,0 @@ ---- -source: src/codegen/tests/expression_tests.rs -expression: result -snapshot_kind: text ---- -; ModuleID = '' -source_filename = "" -target datalayout = "[filtered]" -target triple = "[filtered]" - -%main = type { i32, i64 } - -@main_instance = global %main zeroinitializer - -define void @main(%main* %0) { -entry: - %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 - %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 - store i32 ptrtoint (i64* getelementptr (i64, i64* null, i32 1) to i32), i32* %a, align 4 - ret void -} diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_upper_bound.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_upper_bound.snap deleted file mode 100644 index 79f45acfd0a..00000000000 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__builtin_function_call_upper_bound.snap +++ /dev/null @@ -1,49 +0,0 @@ ---- -source: src/codegen/tests/expression_tests.rs -expression: result -snapshot_kind: text ---- -; ModuleID = '' -source_filename = "" -target datalayout = "[filtered]" -target triple = "[filtered]" - -%main = type { [2 x i32], i32 } -%__foo_vla = type { i32*, [2 x i32] } - -@main_instance = global %main zeroinitializer -@____foo_vla__init = unnamed_addr constant %__foo_vla zeroinitializer - -define void @main(%main* %0) { -entry: - %a = getelementptr inbounds %main, %main* %0, i32 0, i32 0 - %b = getelementptr inbounds %main, %main* %0, i32 0, i32 1 - %auto_deref = load [2 x i32], [2 x i32]* %a, align 4 - %outer_arr_gep = getelementptr inbounds [2 x i32], [2 x i32]* %a, i32 0, i32 0 - %vla_struct = alloca %__foo_vla, align 8 - %vla_array_gep = getelementptr inbounds %__foo_vla, %__foo_vla* %vla_struct, i32 0, i32 0 - %vla_dimensions_gep = getelementptr inbounds %__foo_vla, %__foo_vla* %vla_struct, i32 0, i32 1 - store [2 x i32] [i32 0, i32 1], [2 x i32]* %vla_dimensions_gep, align 4 - store i32* %outer_arr_gep, i32** %vla_array_gep, align 8 - %1 = load %__foo_vla, %__foo_vla* %vla_struct, align 8 - %vla_struct_ptr = alloca %__foo_vla, align 8 - store %__foo_vla %1, %__foo_vla* %vla_struct_ptr, align 8 - %call = call i32 @foo(%__foo_vla* %vla_struct_ptr) - store i32 %call, i32* %b, align 4 - ret void -} - -define i32 @foo(%__foo_vla* %0) { -entry: - %foo = alloca i32, align 4 - %vla = alloca %__foo_vla*, align 8 - store %__foo_vla* %0, %__foo_vla** %vla, align 8 - store i32 0, i32* %foo, align 4 - %deref = load %__foo_vla*, %__foo_vla** %vla, align 8 - %dim = getelementptr inbounds %__foo_vla, %__foo_vla* %deref, i32 0, i32 1 - %1 = getelementptr inbounds [2 x i32], [2 x i32]* %dim, i32 0, i32 1 - %2 = load i32, i32* %1, align 4 - store i32 %2, i32* %foo, align 4 - %foo_ret = load i32, i32* %foo, align 4 - ret i32 %foo_ret -} diff --git a/src/lowering/snapshots/rusty__lowering__calls__tests__fnptr_no_argument.snap b/src/lowering/snapshots/rusty__lowering__calls__tests__fnptr_no_argument.snap deleted file mode 100644 index 0edd10b110a..00000000000 --- a/src/lowering/snapshots/rusty__lowering__calls__tests__fnptr_no_argument.snap +++ /dev/null @@ -1,67 +0,0 @@ ---- -source: src/lowering/calls.rs -expression: "unit.implementations[2].statements[0]" ---- -ExpressionList { - expressions: [ - Allocation { - name: "__0", - reference_type: "STRING", - }, - CallStatement { - operator: ReferenceExpr { - kind: Deref, - base: Some( - ReferenceExpr { - kind: Member( - Identifier { - name: "fooPtr", - }, - ), - base: None, - }, - ), - }, - parameters: Some( - ExpressionList { - expressions: [ - ReferenceExpr { - kind: Member( - Identifier { - name: "instanceFbA", - }, - ), - base: None, - }, - ReferenceExpr { - kind: Member( - Identifier { - name: "__0", - }, - ), - base: None, - }, - ], - }, - ), - }, - Assignment { - left: ReferenceExpr { - kind: Member( - Identifier { - name: "result", - }, - ), - base: None, - }, - right: ReferenceExpr { - kind: Member( - Identifier { - name: "__0", - }, - ), - base: None, - }, - }, - ], -} diff --git a/src/parser.rs b/src/parser.rs index 3a7e6bee747..af477a6e7d3 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1244,7 +1244,6 @@ fn parse_enum_element(lexer: &mut ParseSession) -> Option { return Some(result); } - Some(ref_expr) } diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_call_statement_as_variant_should_error-2.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_call_statement_as_variant_should_error-2.snap deleted file mode 100644 index fbafec09e42..00000000000 --- a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_call_statement_as_variant_should_error-2.snap +++ /dev/null @@ -1,44 +0,0 @@ ---- -source: src/parser/tests/type_parser_tests.rs -expression: result.user_types ---- -[ - UserTypeDeclaration { - data_type: EnumType { - name: Some( - "State", - ), - numeric_type: "DINT", - elements: ExpressionList { - expressions: [ - Assignment { - left: ReferenceExpr { - kind: Member( - Identifier { - name: "Idle", - }, - ), - base: None, - }, - right: LiteralInteger { - value: 0, - }, - }, - CallStatement { - operator: ReferenceExpr { - kind: Member( - Identifier { - name: "foo", - }, - ), - base: None, - }, - parameters: None, - }, - ], - }, - }, - initializer: None, - scope: None, - }, -] diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_equality_operator_instead_of_assignment_should_error-2.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_equality_operator_instead_of_assignment_should_error-2.snap deleted file mode 100644 index 001e19bf1e7..00000000000 --- a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_equality_operator_instead_of_assignment_should_error-2.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/parser/tests/type_parser_tests.rs -expression: result.user_types ---- -[ - UserTypeDeclaration { - data_type: EnumType { - name: Some( - "State", - ), - numeric_type: "DINT", - elements: ExpressionList { - expressions: [ - Assignment { - left: ReferenceExpr { - kind: Member( - Identifier { - name: "Idle", - }, - ), - base: None, - }, - right: LiteralInteger { - value: 0, - }, - }, - BinaryExpression { - operator: Equal, - left: ReferenceExpr { - kind: Member( - Identifier { - name: "Running", - }, - ), - base: None, - }, - right: LiteralInteger { - value: 1, - }, - }, - ], - }, - }, - initializer: None, - scope: None, - }, -] diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_qualified_member_variant_should_error-2.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_qualified_member_variant_should_error-2.snap deleted file mode 100644 index 80f21a9e70f..00000000000 --- a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_qualified_member_variant_should_error-2.snap +++ /dev/null @@ -1,67 +0,0 @@ ---- -source: src/parser/tests/type_parser_tests.rs -expression: result.user_types ---- -[ - UserTypeDeclaration { - data_type: EnumType { - name: Some( - "State", - ), - numeric_type: "DINT", - elements: ExpressionList { - expressions: [ - Assignment { - left: ReferenceExpr { - kind: Member( - Identifier { - name: "Idle", - }, - ), - base: None, - }, - right: LiteralInteger { - value: 0, - }, - }, - ReferenceExpr { - kind: Member( - Identifier { - name: "Fast", - }, - ), - base: Some( - ReferenceExpr { - kind: Member( - Identifier { - name: "Running", - }, - ), - base: None, - }, - ), - }, - ReferenceExpr { - kind: Member( - Identifier { - name: "Slow", - }, - ), - base: Some( - ReferenceExpr { - kind: Member( - Identifier { - name: "Running", - }, - ), - base: None, - }, - ), - }, - ], - }, - }, - initializer: None, - scope: None, - }, -] diff --git a/src/validation/tests/snapshots/rusty__validation__tests__reference_resolve_tests__resolve_function_members_via_qualifier.snap b/src/validation/tests/snapshots/rusty__validation__tests__reference_resolve_tests__resolve_function_members_via_qualifier.snap deleted file mode 100644 index 10a4de147bc..00000000000 --- a/src/validation/tests/snapshots/rusty__validation__tests__reference_resolve_tests__resolve_function_members_via_qualifier.snap +++ /dev/null @@ -1,21 +0,0 @@ ---- -source: src/validation/tests/reference_resolve_tests.rs -expression: "&diagnostics" ---- -error[E048]: Could not resolve reference to a - ┌─ :4:21 - │ -4 │ foo.a; (* not ok *) - │ ^ Could not resolve reference to a - -error[E048]: Could not resolve reference to b - ┌─ :5:21 - │ -5 │ foo.b; (* not ok *) - │ ^ Could not resolve reference to b - -error[E048]: Could not resolve reference to c - ┌─ :6:21 - │ -6 │ foo.c; (* not ok *) - │ ^ Could not resolve reference to c