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 70d9d930d81..af477a6e7d3 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,12 @@ 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) - })?; + 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 { @@ -1198,6 +1193,60 @@ 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 { + // 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); + + if !lexer.try_consume(KeywordComma) { + break; + } + + 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()); + } + + 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()); + 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(ref_expr, value, lexer.next_id()); + return Some(result); + } + + Some(ref_expr) +} + 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_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..8a056cde9a5 --- /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,15 @@ +--- +source: src/parser/tests/type_parser_tests.rs +expression: diagnostics +--- +error[E007]: Unexpected token: expected KeywordParensClose but found '(' + ┌─ :2:37 + │ +2 │ TYPE State : (Idle := 0, foo()); + │ ^ 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 new file mode 100644 index 00000000000..cf290c3d11e --- /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[E007]: Unexpected token: expected KeywordParensClose but found '= 1' + ┌─ :2:42 + │ +2 │ TYPE State : (Idle := 0, Running = 1); + │ ^^^ 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 new file mode 100644 index 00000000000..490ea5e165b --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__enum_with_qualified_member_variant_should_error.snap @@ -0,0 +1,9 @@ +--- +source: src/parser/tests/type_parser_tests.rs +expression: diagnostics +--- +error[E007]: Unexpected token: expected KeywordParensClose but found '.Fast, Running.Slow' + ┌─ :2:41 + │ +2 │ TYPE State : (Idle := 0, Running.Fast, Running.Slow); + │ ^^^^^^^^^^^^^^^^^^^ 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 f0f881ab734..ec5807a69aa 100644 --- a/src/parser/tests/type_parser_tests.rs +++ b/src/parser/tests/type_parser_tests.rs @@ -141,6 +141,42 @@ 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 (_, diagnostics) = parse_buffered( + r#" + TYPE State : (Idle := 0, Running = 1); + END_TYPE + "#, + ); + assert!(!diagnostics.is_empty(), "Expected parse error for enum using = instead of :="); + assert_snapshot!(diagnostics); +} + +#[test] +fn enum_with_call_statement_as_variant_should_error() { + let (_, diagnostics) = parse_buffered( + r#" + TYPE State : (Idle := 0, foo()); + END_TYPE + "#, + ); + assert!(!diagnostics.is_empty(), "Expected parse error for invalid enum variant"); + assert_snapshot!(diagnostics); +} + +#[test] +fn enum_with_qualified_member_variant_should_error() { + let (_, 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); +} + #[test] fn type_alias_can_be_parsed() { let (result, ..) = parse( @@ -796,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 - - 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