Fix missing invocation of init callbacks due to inlining (#4322)

When a component declares an init callback as well as the element that
instantiates it, the init callback of the container would not
get invoked if the container was inlined:

```
component InlinedContainer {
    init => { ... } // not invoked
    @children // force inlining
}

component UseSite {
    InlinedContainer {
        init => { ... }
    }
}
```

That's because the inlining happens very early, before the init
callbacks are collected into the init code. And during inlining that
would be treated like a property binding and the "binding" at the
element site overrides the one at the component site.

One natural approach would be to move the init code collection to an
earlier place before inlining, but that isn't possible because the
boundaries of the compiler components (Rc<Component>) aren't set
up yet (repeated component extraction phase happens much later).

An alternative would be to re-introduce the init callback code in
ElementRc and place it in there at *::from_node() time.

This patch chooses to solve this at the inlining time: When we're in the
first phase of the inlining (the optional one), do what
with the init binding what collect_init_code would do later: Move it
straight into the init_code of the component we're inlining into.

Fixes #4317
This commit is contained in:
Simon Hausmann 2024-01-12 10:14:27 +01:00 committed by GitHub
parent 4d58c9877d
commit fef9c446a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 45 additions and 15 deletions

View file

@ -149,7 +149,20 @@ fn inline_element(
.iter()
.map(|p| duplicate_popup(p, &mut mapping, priority_delta)),
);
for (k, val) in inlined_component.root_element.borrow().bindings.iter() {
// When inlining a component before the collect_init_code phase, do the collect_init_code phase for
// the init callback in the inlined component manually, by cloning the expression into the init_code
// and skipping "init" in the binding merge phase.
let maybe_init_callback_to_inline = inlined_component
.root_element
.borrow()
.bindings
.get("init")
.map(|binding| binding.borrow().expression.clone());
for (k, val) in
inlined_component.root_element.borrow().bindings.iter().filter(|(k, _)| *k != "init")
{
match elem_mut.bindings.entry(k.clone()) {
std::collections::btree_map::Entry::Vacant(entry) => {
let priority = &mut entry.insert(val.clone()).get_mut().priority;
@ -174,23 +187,36 @@ fn inline_element(
core::mem::drop(elem_mut);
let fixup_init_expression = |mut init_code: Expression| {
// Fix up any property references from within already collected init code.
visit_named_references_in_expression(&mut init_code, &mut |nr| {
fixup_reference(nr, &mapping)
});
fixup_element_references(&mut init_code, &mapping);
init_code
};
root_component
.init_code
.borrow_mut()
.constructor_code
.extend(maybe_init_callback_to_inline.map(fixup_init_expression));
let inlined_init_code = inlined_component
.init_code
.borrow()
.inlined_init_code
.values()
.cloned()
.chain(inlined_component.init_code.borrow().constructor_code.iter().map(
|constructor_code_expr| {
// Fix up any property references from within already collected init code.
let mut new_constructor_code = constructor_code_expr.clone();
visit_named_references_in_expression(&mut new_constructor_code, &mut |nr| {
fixup_reference(nr, &mapping)
});
fixup_element_references(&mut new_constructor_code, &mapping);
new_constructor_code
},
))
.chain(
inlined_component
.init_code
.borrow()
.constructor_code
.iter()
.cloned()
.map(fixup_init_expression),
)
.collect();
root_component