Skip to content

Commit b676e54

Browse files
committed
move evaluation to its own pass after constant resolution
1 parent 9f534b2 commit b676e54

File tree

7 files changed

+94
-158
lines changed

7 files changed

+94
-158
lines changed

compiler/plc_driver/src/pipelines.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,11 @@ impl ParsedProject {
561561
global_index.import(indexer::index(&builtins));
562562

563563
//TODO: evaluate constants should probably be a participant
564-
let (index, unresolvables) = plc::resolver::const_evaluator::evaluate_constants(global_index);
564+
let (mut index, unresolvables) = plc::resolver::const_evaluator::evaluate_constants(global_index);
565+
566+
// Fix up enum defaults after constants are resolved
567+
index.finalize_enum_defaults();
568+
565569
IndexedProject { project: ParsedProject { units }, index, unresolvables }
566570
}
567571
}

src/index.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2025,6 +2025,59 @@ impl Index {
20252025
self.type_index.pou_types.insert(datatype.get_name().to_lowercase(), datatype);
20262026
}
20272027

2028+
/// Fixes up enum types to set their default initial values.
2029+
/// This must be called after constant resolution, as it needs to evaluate
2030+
/// constant expressions to determine which variant is zero.
2031+
///
2032+
/// For each enum without an explicit initializer, this sets the initial_value to:
2033+
/// 1. The zero-variant (if one exists), or
2034+
/// 2. The first variant (as fallback)
2035+
pub fn finalize_enum_defaults(&mut self) {
2036+
// Process all types and update enum defaults
2037+
let mut fixed_types = Vec::new();
2038+
2039+
for (name, mut datatypes) in self.type_index.types.drain(..) {
2040+
for mut datatype in datatypes.drain(..) {
2041+
if let DataTypeInformation::Enum { variants, .. } = &datatype.information {
2042+
// Only process if there's no explicit initializer
2043+
if datatype.initial_value.is_none() && !variants.is_empty() {
2044+
let mut zero_variant_id: Option<ConstId> = None;
2045+
let mut first_variant_id: Option<ConstId> = None;
2046+
2047+
// Look for a variant that evaluates to zero, or use the first one
2048+
for (idx, variant) in variants.iter().enumerate() {
2049+
if let Some(variant_init) = variant.initial_value {
2050+
if idx == 0 {
2051+
first_variant_id = Some(variant_init);
2052+
}
2053+
2054+
if let Ok(0) =
2055+
self.constant_expressions.get_constant_int_statement_value(&variant_init)
2056+
{
2057+
zero_variant_id = Some(variant_init);
2058+
break;
2059+
}
2060+
}
2061+
}
2062+
2063+
// Prefer zero variant, fall back to first variant
2064+
let default_value = zero_variant_id.or(first_variant_id);
2065+
if let Some(const_id) = default_value {
2066+
datatype.initial_value = Some(const_id);
2067+
}
2068+
}
2069+
}
2070+
2071+
fixed_types.push((name.clone(), datatype));
2072+
}
2073+
}
2074+
2075+
// Re-insert all types
2076+
for (name, datatype) in fixed_types {
2077+
self.type_index.types.insert(name, datatype);
2078+
}
2079+
}
2080+
20282081
pub fn find_callable_instance_variable(
20292082
&self,
20302083
context: Option<&str>,

src/index/indexer/user_type_indexer.rs

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -292,10 +292,8 @@ impl UserTypeIndexer<'_, '_> {
292292

293293
fn index_enum_type(&mut self, name: &str, numeric_type: &str, elements: &AstNode) {
294294
let mut variants = Vec::new();
295-
let mut zero_value_const_id: Option<ConstId> = None;
296-
let mut first_element_const_id: Option<ConstId> = None;
297295

298-
for (idx, ele) in flatten_expression_list(elements).iter().enumerate() {
296+
for ele in flatten_expression_list(elements) {
299297
let variant = get_enum_element_name(ele);
300298
if let AstStatement::Assignment(Assignment { right, .. }) = ele.get_stmt() {
301299
let scope = self.current_scope();
@@ -306,16 +304,6 @@ impl UserTypeIndexer<'_, '_> {
306304
None,
307305
);
308306

309-
// Track if we have a zero element and remember its ConstId
310-
if let AstStatement::Literal(AstLiteral::Integer(0)) = right.get_stmt() {
311-
zero_value_const_id = Some(init);
312-
}
313-
314-
// Remember the first element's ConstId
315-
if idx == 0 {
316-
first_element_const_id = Some(init);
317-
}
318-
319307
variants.push(self.index.register_enum_variant(
320308
name,
321309
&variant,
@@ -327,17 +315,6 @@ impl UserTypeIndexer<'_, '_> {
327315
}
328316
}
329317

330-
// If no explicit initializer was provided, determine the default value
331-
if self.pending_initializer.is_none() {
332-
self.pending_initializer = if zero_value_const_id.is_some() {
333-
// If zero is defined, use its ConstId
334-
zero_value_const_id
335-
} else {
336-
// Otherwise use the first element's ConstId
337-
first_element_const_id
338-
};
339-
}
340-
341318
let information = DataTypeInformation::Enum {
342319
name: name.to_owned(),
343320
variants,

src/parser.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1136,7 +1136,6 @@ fn parse_string_type_definition(
11361136
let location = lexer.source_range_factory.create_range(start..end);
11371137

11381138
// Check if this is actually an enum type (e.g., STRING (a := 1, b := 2))
1139-
// If size is an ExpressionList with assignments, it's likely an invalid enum definition
11401139
let is_enum_like = matches!(&size, Some(AstNode { stmt: AstStatement::ExpressionList(_), .. }));
11411140

11421141
match (size, &name, is_enum_like) {

src/parser/tests/type_parser_tests.rs

Lines changed: 0 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -785,138 +785,6 @@ fn enum_codesys_style_with_int_type_and_default() {
785785
assert_eq!(diagnostics.len(), 0);
786786
}
787787

788-
#[test]
789-
fn enum_with_zero_element_no_default() {
790-
let (result, diagnostics) = parse(
791-
r#"
792-
TYPE STATE_WITH_ZERO : BYTE (idle := 0, running := 1, stopped := 2);
793-
END_TYPE
794-
"#,
795-
);
796-
assert_debug_snapshot!(result.user_types[0], @r#"
797-
UserTypeDeclaration {
798-
data_type: EnumType {
799-
name: Some(
800-
"STATE_WITH_ZERO",
801-
),
802-
numeric_type: "BYTE",
803-
elements: ExpressionList {
804-
expressions: [
805-
Assignment {
806-
left: ReferenceExpr {
807-
kind: Member(
808-
Identifier {
809-
name: "idle",
810-
},
811-
),
812-
base: None,
813-
},
814-
right: LiteralInteger {
815-
value: 0,
816-
},
817-
},
818-
Assignment {
819-
left: ReferenceExpr {
820-
kind: Member(
821-
Identifier {
822-
name: "running",
823-
},
824-
),
825-
base: None,
826-
},
827-
right: LiteralInteger {
828-
value: 1,
829-
},
830-
},
831-
Assignment {
832-
left: ReferenceExpr {
833-
kind: Member(
834-
Identifier {
835-
name: "stopped",
836-
},
837-
),
838-
base: None,
839-
},
840-
right: LiteralInteger {
841-
value: 2,
842-
},
843-
},
844-
],
845-
},
846-
},
847-
initializer: None,
848-
scope: None,
849-
}
850-
"#);
851-
assert_eq!(diagnostics.len(), 0);
852-
}
853-
854-
#[test]
855-
fn enum_without_zero_no_default() {
856-
let (result, diagnostics) = parse(
857-
r#"
858-
TYPE PRIORITY : INT (low := 10, medium := 20, high := 30);
859-
END_TYPE
860-
"#,
861-
);
862-
assert_debug_snapshot!(result.user_types[0], @r#"
863-
UserTypeDeclaration {
864-
data_type: EnumType {
865-
name: Some(
866-
"PRIORITY",
867-
),
868-
numeric_type: "INT",
869-
elements: ExpressionList {
870-
expressions: [
871-
Assignment {
872-
left: ReferenceExpr {
873-
kind: Member(
874-
Identifier {
875-
name: "low",
876-
},
877-
),
878-
base: None,
879-
},
880-
right: LiteralInteger {
881-
value: 10,
882-
},
883-
},
884-
Assignment {
885-
left: ReferenceExpr {
886-
kind: Member(
887-
Identifier {
888-
name: "medium",
889-
},
890-
),
891-
base: None,
892-
},
893-
right: LiteralInteger {
894-
value: 20,
895-
},
896-
},
897-
Assignment {
898-
left: ReferenceExpr {
899-
kind: Member(
900-
Identifier {
901-
name: "high",
902-
},
903-
),
904-
base: None,
905-
},
906-
right: LiteralInteger {
907-
value: 30,
908-
},
909-
},
910-
],
911-
},
912-
},
913-
initializer: None,
914-
scope: None,
915-
}
916-
"#);
917-
assert_eq!(diagnostics.len(), 0);
918-
}
919-
920788
#[test]
921789
fn enum_with_no_elements_produces_syntax_error() {
922790
// Empty enums are syntactically invalid - parser requires at least one element

src/test_utils.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ pub mod tests {
141141
id_provider: IdProvider,
142142
) -> Lowered {
143143
let (mut index, _) = evaluate_constants(index);
144+
index.finalize_enum_defaults();
145+
144146
let mut all_annotations = AnnotationMapImpl::default();
145147

146148
let (mut annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, id_provider.clone());
@@ -302,6 +304,7 @@ pub mod tests {
302304
},
303305
);
304306
let (mut index, ..) = evaluate_constants(index);
307+
index.finalize_enum_defaults();
305308
let mut all_annotations = AnnotationMapImpl::default();
306309
let units = units
307310
.into_iter()
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// RUN: (%COMPILE %s && %RUN) | %CHECK %s
2+
3+
VAR_GLOBAL CONSTANT
4+
MYCONST: INT := 0;
5+
END_VAR
6+
7+
// Test enum with no explicit default and a zero variant that is NOT also the first variant - BUT instead of a literal use a constant to initialize
8+
TYPE Status : INT (
9+
active := 1,
10+
inactive := MYCONST,
11+
error := 2
12+
);
13+
END_TYPE
14+
15+
VAR_GLOBAL
16+
s_global: Status; // Should initialize to inactive
17+
END_VAR
18+
19+
PROGRAM prog
20+
VAR
21+
s1 : Status; // Should initialize to inactive
22+
END_VAR
23+
// CHECK: 0
24+
printf('%d$N', s1); // inactive
25+
26+
// CHECK: 0
27+
printf('%d$N', s_global); // inactive
28+
END_PROGRAM
29+
30+
FUNCTION main
31+
prog();
32+
END_FUNCTION

0 commit comments

Comments
 (0)