diff --git a/src/resolver.rs b/src/resolver.rs index a2ef8ea3ed..265f36719f 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -214,6 +214,8 @@ impl TypeAnnotator<'_> { } StatementAnnotation::Value { resulting_type } => { self.dependencies.insert(Dependency::Datatype(resulting_type.to_string())); + self.dependencies + .extend(self.get_datatype_dependencies(resulting_type, FxIndexSet::default())); } _ => (), }; diff --git a/src/resolver/tests/resolver_dependency_resolution.rs b/src/resolver/tests/resolver_dependency_resolution.rs index 158b30a31d..a183824df8 100644 --- a/src/resolver/tests/resolver_dependency_resolution.rs +++ b/src/resolver/tests/resolver_dependency_resolution.rs @@ -1471,3 +1471,55 @@ fn cross_unit_function_with_typedef_array_parameter() { assert!(dependencies.contains(&Dependency::Datatype("REAL".into()))); assert_eq!(dependencies.len(), 9); } + +#[test] +fn value_annotation_resolves_struct_member_dependencies() { + // When a cast expression like `MyStruct#(...)` produces a Value annotation with + // resulting_type = "MyStruct", the resolver must recursively resolve MyStruct's + // member types as dependencies. This is the mechanism that ensures itable struct + // types (and their function pointer members) are available during codegen. + // + // Simulates the post-lowering state where an FB in a separate unit casts a void + // pointer to an itable struct type: `__itable_IA#(reference.table^).foo^(...)` + let units = [ + " + TYPE inner : STRUCT + x : DINT; + END_STRUCT + END_TYPE + ", + " + TYPE wrapper : STRUCT + field : REF_TO inner; + END_STRUCT + END_TYPE + ", + // A program that references `wrapper` only through a cast expression + // (not through a variable declaration), analogous to how the lowered + // itable dispatch references the itable struct type. + " + FUNCTION foo : DINT + VAR + ptr : REF_TO BYTE; + END_VAR + foo := wrapper#(ptr^).field^.x; + END_FUNCTION + ", + ]; + + let id_provider = IdProvider::default(); + let (_unit1, index1) = index_with_ids(units[0], id_provider.clone()); + let (_unit2, index2) = index_with_ids(units[1], id_provider.clone()); + let (unit3, index3) = index_with_ids(units[2], id_provider.clone()); + let mut index = index1; + index.import(index2); + index.import(index3); + + let (_, dependencies, _) = TypeAnnotator::visit_unit(&index, &unit3, id_provider); + // The cast to `wrapper` must pull in wrapper and its member types + assert!(dependencies.contains(&Dependency::Datatype("foo".into()))); + assert!(dependencies.contains(&Dependency::Datatype("wrapper".into()))); + assert!(dependencies.contains(&Dependency::Datatype("__wrapper_field".into()))); + assert!(dependencies.contains(&Dependency::Datatype("inner".into()))); + assert!(dependencies.contains(&Dependency::Datatype("DINT".into()))); +} diff --git a/tests/lit/multi/interface_consumer_separate_file/consumer.st b/tests/lit/multi/interface_consumer_separate_file/consumer.st new file mode 100644 index 0000000000..b8e207a35f --- /dev/null +++ b/tests/lit/multi/interface_consumer_separate_file/consumer.st @@ -0,0 +1,16 @@ +FUNCTION_BLOCK FbConsumer + VAR + stored : IFoo; + END_VAR + + METHOD setRef + VAR_INPUT + iface : IFoo; + END_VAR + stored := iface; + END_METHOD + + METHOD invoke : DINT + invoke := stored.bar(); + END_METHOD +END_FUNCTION_BLOCK diff --git a/tests/lit/multi/interface_consumer_separate_file/iface.st b/tests/lit/multi/interface_consumer_separate_file/iface.st new file mode 100644 index 0000000000..adb132f61a --- /dev/null +++ b/tests/lit/multi/interface_consumer_separate_file/iface.st @@ -0,0 +1,4 @@ +INTERFACE IFoo + METHOD bar : DINT + END_METHOD +END_INTERFACE diff --git a/tests/lit/multi/interface_consumer_separate_file/impl.st b/tests/lit/multi/interface_consumer_separate_file/impl.st new file mode 100644 index 0000000000..24a5cbc1cf --- /dev/null +++ b/tests/lit/multi/interface_consumer_separate_file/impl.st @@ -0,0 +1,5 @@ +FUNCTION_BLOCK FbImpl IMPLEMENTS IFoo + METHOD bar : DINT + bar := 42; + END_METHOD +END_FUNCTION_BLOCK diff --git a/tests/lit/multi/interface_consumer_separate_file/main.st b/tests/lit/multi/interface_consumer_separate_file/main.st new file mode 100644 index 0000000000..6a9d569282 --- /dev/null +++ b/tests/lit/multi/interface_consumer_separate_file/main.st @@ -0,0 +1,9 @@ +FUNCTION main + VAR + impl : FbImpl; + consumer : FbConsumer; + END_VAR + + consumer.setRef(impl); + printf('%d$N', consumer.invoke()); +END_FUNCTION diff --git a/tests/lit/multi/interface_consumer_separate_file/run.test b/tests/lit/multi/interface_consumer_separate_file/run.test new file mode 100644 index 0000000000..b5027bdaef --- /dev/null +++ b/tests/lit/multi/interface_consumer_separate_file/run.test @@ -0,0 +1,2 @@ +RUN: %COMPILE %S/main.st %S/iface.st %S/impl.st %S/consumer.st && %RUN | %CHECK %s +CHECK: 42