Draft
Conversation
Add a new DataTypeInformation::Interface variant so that interfaces are tracked as first-class data types. This allows variables, inputs, arrays, and return types to reference interface types directly. Because interfaces are now indexed as data types, the dedicated validate_unique_interfaces check is removed in favor of the existing data type uniqueness validation.
…ch structure Move vtable.rs and polymorphism.rs into a new polymorphism/ module tree organized by concern (table/ for vtable generation, dispatch/ for call lowering) and by target (pou.rs for classes/function blocks, interface.rs as empty placeholders for future work). Introduce PolymorphismLowerer as a single entry point that replaces the separate VirtualTableGenerator and PolymorphicCallLowerer pipeline participants, simplifying the pipeline registration to a single participant.
…ispatch Add InterfaceTableGenerator which produces per-interface itable struct definitions (one function pointer per method, including inherited methods) and per-(interface, POU) global instances with initializers pointing to the POU's concrete method implementations. Handles deep POU inheritance chains, interface EXTENDS hierarchies, diamond patterns, method overrides at any level, and multi-unit placement (struct definitions in the interface's unit, instances in the POU's unit). Integrated into the existing TableGenerator alongside VirtualTableGenerator.
…e declarations Replace all interface-typed declarations (variables, params, arrays, return types) with __FATPOINTER, a shared struct containing data and table void pointers. The struct is injected on demand into the first compilation unit. Alias types are resolved via find_effective_type_by_name.
Replace owned Option<Index>/Option<AnnotationMapImpl> with shared references in PolymorphicCallLowerer and InterfaceDispatchLowerer. Removes unnecessary take/unwrap patterns and the unused Index return value from DispatchLowerer::lower.
Expand `ref := instance` into two fat pointer field assignments: ref.data := ADR(instance) ref.table := ADR(__itable_<interface>_<POU>_instance) Both assignments are packed into an ExpressionList node since the visitor only has access to a single node. The codegen statement generator already iterates ExpressionList children as individual statements. Global itable references are wrapped in ReferenceExpr(Member(...)) so the re-annotation pass can resolve them as global variables.
… dispatch Implements concrete POU → interface expansion for both assignments and call arguments (unnamed and named). Assignments expand into fat pointer field writes (data + table). Call arguments allocate a temporary fat pointer, initialize it, and substitute the original argument. Also serializes AllocationStatement in the AST serializer and adds nesting tests for multi-depth call argument wrapping.
…rect dispatch Interface method calls lowered to itable dispatch (e.g. `__itable_IA#(ref.table^).foo^(ref.data)`) require an LLVM function stub so that `build_indirect_call` can resolve the function type signature. Previously, interface methods were only registered as POU declarations — not as implementations — so codegen could not find them. Register an ImplementationIndexEntry for each interface method during indexing. This causes LLVM function stubs to be generated alongside concrete method stubs. Two places in pou_generator assumed every method's parent class has a struct type, which is not the case for interfaces (they are abstract): - collect_parameters_for_implementation: skip the struct-type sanity check when the parent is an interface - debug info parameter list: filter out interface types that have no debug type information
… in dispatch - Override visit_interface in InterfaceDispatchLowerer so interface method parameters with interface types get rewritten to __FATPOINTER, matching the implementing POUs (fixes signature mismatch error E112) - Wrap the self-pointer argument in a deref (reference.data^) so codegen loads the actual instance pointer instead of passing the address of the fat pointer's data slot (fixes Bus error for aggregate return types) - Add lit tests for interface polymorphism: arguments (named, positional, mixed, sequential, implicit), aggregate returns (array, string, struct), diamond/linear inheritance, and basic dispatch
…INTER - Register a forward declaration for internal (no source location) struct types in create_struct_type instead of silently returning, so they are available for debug info references - Thread index/types_index through create_function and create_subroutine_type to enable lazy debug type registration for types not yet in the map - Update debug test snapshots
- Add #[allow(clippy::too_many_arguments)] to create_function in debug.rs - Replace `diagnostics.len() > 0` with `!diagnostics.is_empty()` - Remove unnecessary `&` on format string arguments - Remove unnecessary trailing `return` - Simplify closure to function reference - Reorder imports alphabetically - Apply rustfmt line wrapping adjustments
…atch When a call with interface arguments is the RHS of an assignment (e.g. `result := ref.foo(instance)`), the fat pointer preamble was incorrectly nested under the assignment, producing: `result := alloca ..., tmp.data := ..., tmp.table := ..., call` instead of: `alloca ..., tmp.data := ..., tmp.table := ..., result := call` Fix by detecting the enclosing assignment context in visit_call_statement and routing the preamble through assignment_preamble so the assignment wraps only the call. Also save/restore assignment_ctx in visit_assignment to prevent inner named-parameter assignments from clobbering the outer context.
…to post_annotate Previously, AggregateTypeLowerer modified POU signatures (inserting a VAR_IN_OUT parameter for aggregate returns) in post_index. This shifted parameter positions before the annotation phase, causing the resolver to assign incorrect type hints to call arguments. In particular, when a function had both an aggregate return type (e.g. STRING) and an interface-typed parameter, the InterfaceDispatchLowerer would see the wrong type hint and fail to wrap the argument in a fat pointer. By removing the post_index hook and performing all aggregate lowering in post_annotate, the annotation phase sees the original unmodified POU signatures with correct parameter positions. The post_annotate handler now also re-indexes from the modified units (instead of reusing the stale pre-modification index) so that subsequent pipeline stages and validation see the updated signatures.
…ateTypeLowerer When AggregateTypeLowerer's map() encountered an ExpressionList produced by a previous pass (e.g. InterfaceDispatchLowerer's fat pointer preamble), it used a single shared scope for all children. This caused the aggregate alloca and extracted call to be prepended before the entire list, placing the call before the fat pointer setup it depends on. Fix by detecting ExpressionList nodes in map() and processing each element with its own scope, so generated preamble statements are placed directly before the element that produced them. Also un-ignores interface_method_call_with_aggregate_return_and_interface_argument and adds integration tests covering: - STRING return + interface argument (method dispatch) - STRUCT return + interface argument (method dispatch) - STRUCT return + mixed scalar/interface args, unnamed and reordered named - Free FUNCTION with STRING return + interface argument
Replace drain(..).collect() with std::mem::take() per clippy suggestion.
- Rename existing tests with group prefixes: - hierarchy_: single, implicit, linear, diamond - type_: array/string/struct arguments and returns - combined_: aggregate return + interface argument tests - argument_: unchanged (already well-named) - Add new tests: - hierarchy_method_name_collision: two unrelated interfaces with same-named method - dispatch_member_variable: interface ref stored as FB member field - dispatch_conditional: IF/CASE runtime dispatch - dispatch_recursive: chained and self-referential interface dispatch - type_array_of_interfaces: array of root interface with hierarchy (IA <- IB <- IC) - Remove stale Output/ directory (lit artifacts, regenerated on next run)
Fix seven discrepancies between the polymorphism design document
and the actual code:
1. VTable naming case: __vtable_fbA → __vtable_FbA throughout,
matching the code's format!("__vtable_{}", pou.name) pattern.
2. Doc comment in table/pou.rs: __vtable_instance_A →
__vtable_A_instance to match get_vtable_instance_name().
3. Initializer placement: move default initializers from the
global vtable instance onto the struct member definitions,
matching the code where members carry ADR(...) defaults and
the instance has no explicit initializer.
4. __body entry: document the __body function pointer that
function blocks get as the first vtable field, with a note
that ASCII diagrams omit it for brevity.
5. Instance argument cast: add FbA#(...) wrapping around the
instance argument in all dispatch examples, matching the
maybe_cast_instance() behavior in dispatch/pou.rs.
6. THIS calls: add THIS alongside SUPER in the list of calls
left untouched by dispatch lowering.
7. __itable_ID field order: correct bar,foo,baz,qux to
foo,bar,baz,qux matching the DFS order the code produces.
The concrete→interface mismatch detection only lived in visit_reference_expr, which never fires for call expressions. Functions returning a concrete type (e.g. FbA) assigned to an interface variable (e.g. IA) were not wrapped in a fat pointer. Extract the detection logic into a shared maybe_expand_fat_pointer method and call it from both visit_reference_expr and visit_call_statement.
Indent list continuation lines to align with the list item text, fixing clippy doc_lazy_continuation warnings.
Revert the broad forward-declaration registration for all internal struct types introduced to fix a panic when __FATPOINTER appeared as a function parameter. Instead, gracefully skip unregistered types: create_subroutine_type filters out missing parameter types, get_or_create_debug_type returns Err for unresolvable types so callers can skip them, and generate_debug_types logs and continues instead of aborting. This keeps vtables, itables, and fat pointers out of the DWARF output entirely.
Reformat closure and function call expressions in debug.rs and data_type_generator.rs to satisfy rustfmt line-length rules.
The alloca alignment for small types (i8, i16) differs between macOS and Linux CI (1/2 vs 4). Restrict these tests to macOS until platform-specific snapshots are added for Linux.
…-list drain-and-rebuild Replaces the previous ExpressionList-based 1→N statement expansion hack with a cleaner architecture using a new `visit_statement_list` visitor hook. The lowerer now drains each statement list and rebuilds it, inserting preamble nodes (e.g. fat-pointer allocas) before the current statement and optionally replacing it entirely (e.g. expanding an interface assignment into .data + .table field writes). Key changes: - Add `visit_statement_list` hook to both `AstVisitor` and `AstVisitorMut` traits, routing all control-flow bodies (IF, FOR, WHILE, CASE, REPEAT) through it instead of `visit_all_nodes`. - Implement `Walker[Mut]` for `Interface` and `PropertyBlock` so their children (methods, properties, variable blocks) are visited during lowering and type replacement. - Rewrite `InterfaceDispatchLowerer` internals: replace `assignment_ctx` stack, `call_depth` counter, and `assignment_preamble`/`call_preamble` buffers with `preamble`, `replacement`, and `in_call_args` fields that integrate cleanly with the drain-and-rebuild loop. - Remove the `ExpressionList` special-case workaround from `AggregateTypeLowerer::map` that was compensating for the old expansion strategy. - Enhance `AstSerializer` with indentation tracking and structured formatting for control-flow statements, enabling readable snapshot tests for lowered output inside IF/FOR/CASE/WHILE blocks. - Add comprehensive test suite covering interface dispatch lowering for assignments, call argument wrapping (positional, named, nested, qualified, array, pointer deref, function/method call results), interface method calls, control flow bodies, and parameter directions.
Fixes compilation errors, runtime failures, and snapshot mismatches caused by merging master's constructor/initializer refactoring (feat!: Refactor initialisers #1552) into the interface polymorphism branch. The merge introduced 5 distinct issues: 1. Missing `linkage` field on `DataType` (compile error) Master added a `linkage: LinkageType` field to the `DataType` struct. The interface type registration in `src/index/indexer.rs` was missing this new field. Fixed by adding `linkage: LinkageType::Internal` and the `LinkageType` import. 2. `VirtualTableGenerator::new` signature change (compile error) Master added a `generate_external_constructors: bool` parameter to control whether constructor functions are emitted for externally- linked POUs. `TableGenerator::generate()` still used the old one-argument call. Fixed by threading the flag through the full call chain: Pipeline -> PolymorphismLowerer (new field) -> TableGenerator::generate (new param) -> VirtualTableGenerator::new. 3. Invalid `vtable::VirtualTableGenerator` in pipeline (compile error) The merge produced code that used `VirtualTableGenerator` directly as a pipeline participant, but: the module path `lowering::vtable` doesn't exist (it lives in the private `lowering::polymorphism::table::pou` module), and the type doesn't implement `PipelineParticipantMut`. This is redundant anyway since `PolymorphismLowerer` already calls `VirtualTableGenerator` internally via `TableGenerator::generate()`. Fixed by removing the standalone participant entry and passing `generate_external_ constructors` through `PolymorphismLowerer::new()` instead. Also fixed the same broken import in `plc_lowering` test code, a missing semicolon after `vec![...]`, and a missing return expression. 4. `IA__ctor` calls generated for interface types (18 lit test failures) Master's new `Initializer` generates `<Type>__ctor()` calls for any variable whose type is in the index with non-BuiltIn linkage. Interface types registered by this branch (with `LinkageType::Internal`) passed this check, causing `IA__ctor(reference)` calls to be emitted. But interfaces are abstract types that get lowered to `__FATPOINTER` fat pointers — no `IA__ctor` function is ever defined, so codegen failed with "cannot generate call statement". Fixed by adding `&& !dt.is_interface()` to `get_constructor_call()`. 5. Property local variables leaked into FB constructor (1 lit test failure) The `PropertyLowerer` converts property blocks into method POUs at `pre_index` time, but the original `PropertyBlock` is retained in the parent POU. `PropertyBlock::walk()` visits the variable blocks inside property implementations. When master's new `Initializer` walks a function block POU, it traverses into the retained property blocks and encounters local variables (e.g. `one: DINT := 1` from a getter). Because the FB is stateful, the initializer generates `self.one := 1` in the FB constructor — but `one` is local to the getter method, not a member of the FB. At codegen the resolver can't find `self.one`, causing "no type hint available". Fixed by adding a no-op `visit_property()` override to the `Initializer`. Snapshot updates reflect constructor naming changes from master's refactoring (`__init_xxx` -> `Xxx__ctor`, `__user_init_Xxx` removed) and the removal of spurious `IA__ctor` calls for interface types.
Reorder PropertyLowerer before PolymorphismLowerer in the pipeline participant list so that property references are lowered to __get_/__set_ method calls before dispatch lowering runs. This single-line change enables properties accessed through interface variables to dispatch dynamically through itables, with zero regressions. Add 8 itable generation tests, 12 dispatch lowering tests, and 6 runtime lit tests covering getter/setter dispatch, overrides, mixed method+property interfaces, conditional dispatch, and cross-reference argument passing.
…on chain When an interface IB extends IA and IA declares a property, accessing that property through a variable typed as IB failed with 'Could not resolve reference'. The resolver's property lookup only checked the direct qualifier and the POU super_class chain via find_method, which does not cover interface extensions. Extend resolve_property to walk the interface inheritance chain via get_derived_interfaces_recursive when the direct lookup fails and the qualifier is an interface. This mirrors how method resolution already works for interfaces. Update stale plc_lowering snapshots for inherited property tests — the previous commit's pipeline reorder (PropertyLowerer before PolymorphismLowerer) now correctly routes property calls through vtable dispatch, changing the expected AST output. Add property annotation unit test, interface property lit tests (diamond, deep linear chain with overrides), extended_interface coverage through child interface, and POU vtable dispatch lit test for properties.
Add lit tests covering dynamic dispatch of properties with selectively overridden get/set accessors for both vtable (POU-based) and itable (interface-based) polymorphism. POU-based (polymorphism/properties/): - inherited_getter_overridden_setter - alternating_accessor_overrides (3-level chain) - method_dispatches_to_overridden_property - multiple_properties_selective_overrides Interface-based (interfaces/properties/): - basic_get_set - multiple_implementors - inherited_getter_overridden_setter - inherited_setter_overridden_getter - implicit_obligation_through_pou_chain - deep_chain_alternating_overrides - mixed_methods_and_properties - interface_hierarchy - diamond_hierarchy Also add itable unit tests for partial property accessor overrides in interface.rs (overridden set-only, deep alternating overrides, extended interface hierarchy with property override).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.