slint-ui/slint · error

model two-way bindings only exist on sub-components

Error message

model two-way bindings only exist on sub-components

What it means

This panic fires in the C++ code generator while emitting a model two-way binding (`generate_model_two_way_binding`). The generator needs the name of the current sub-component to compute the C++ class name for casting back from a type-erased VRc, but `ctx.current_sub_component()` returned None. Model two-way bindings are only lowered when attached to a sub-component (repeater), so hitting this means the code generator encountered the binding outside that context.

Solutions

  1. Check how the binding reached this code path; model two-way bindings must be emitted from `generate_sub_component` where the context has a current sub-component
  2. Ensure the EvaluationContext was built with `new_sub_component` and the correct sub-component index before compiling the binding
  3. If the binding legitimately belongs elsewhere, handle it explicitly instead of relying on the expect
  4. File/report a compiler bug with the .slint input that reproduces it

Example fix

// before
let self_type = ident(&ctx.current_sub_component().expect("...").name);
// after
let Some(sub) = ctx.current_sub_component() else {
    panic!("model two-way binding generated outside sub-component context: {:?}", info.repeater_index);
};
let self_type = ident(&sub.name);
Defensive patterns

Strategy: validation

Validate before calling

debug_assert!(
    ctx.current_sub_component().is_some(),
    "model two-way binding must be generated inside a sub-component context"
);

Type guard

let sub_component = match ctx.current_sub_component() {
    Some(sub) => sub,
    None => panic!("model two-way binding generated outside sub-component context"),
};

Prevention

When it happens

Trigger: A model two-way binding expression is generated for a context whose current entity is a global or the root without a sub-component frame on the EvaluationContext stack — i.e. a compiler-internal scheduling/pass-ordering bug, not something a .slint author writes directly.

Common situations: Slint compiler development: adding a new lowering pass or generator path that routes model two-way binding expressions through `compile_expression` without setting up a sub-component context; regressions after refactoring EvaluationContext creation.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of slint-ui/slint@3a7e700487 (2026-09-16). Data as JSON: /api/errors/8cf48e1f0b727b44. Report an issue: GitHub.

Appendix: source

Thrown at internal/compiler/generator/cpp.rs:675

/// Emit a `link_two_way_to_model_data` call wiring `p1` to a row of the
/// model described by `info`, optionally through a struct `field_access`.
fn generate_model_two_way_binding(
    ctx: &EvaluationContext,
    info: &llr::ResolvedModelTwoWayBinding,
    p1: &str,
    field_access: &[SmolStr],
) -> String {
    let body_sc = &ctx.compilation_unit.sub_components[info.body_sub_component];
    let data_prop_name = field_name(&body_sc.properties[info.data_prop].name);
    let index_prop_name = field_name(&body_sc.properties[info.index_prop].name);
    let repeater_index = usize::from(info.repeater_index);

    // Determine the C++ class name of `self` so we can cast back from
    // the type-erased VRc obtained by locking the weak pointer.
    let self_type = ident(
        &ctx.current_sub_component()
            .expect("model two-way bindings only exist on sub-components")
            .name,
    );

    // Walk the parent chain in a single expression so the intermediate
    // `lock().value()` temporaries live until we assign to `body_rc`.
    let (body_setup, body) = if info.parent_level == 0 {
        (String::new(), "self")
    } else {
        let chain: String = (0..info.parent_level).map(|_| "->parent.lock().value()").collect();
        (format!("auto body_rc = self{chain}; "), "body_rc")
    };

    let (getter_expr, ty) = lower_field_access_chain(
        format!("{body}->{data_prop_name}.get()"),
        info.data_prop_ty,
        field_access,
    );
    let (setter_lvalue, _) =

View on GitHub (pinned to 3a7e700487)