Skip to main content

i_slint_compiler/object_tree/
interfaces.rs

1// Copyright © 2026 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>, author Nathan Collins <nathan.collins@kdab.com>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! Module containing interfaces related types and functions.
5
6use std::cell::RefCell;
7use std::collections::BTreeMap;
8use std::fmt::Display;
9use std::rc::Rc;
10
11use itertools::Itertools;
12use smol_str::{SmolStr, ToSmolStr};
13
14use crate::diagnostics::BuildDiagnostics;
15use crate::expression_tree::{BindingExpression, Callable, Expression};
16use crate::langtype::{ElementType, Function, PropertyLookupResult, Type};
17use crate::namedreference::NamedReference;
18use crate::object_tree::{
19    Component, Element, ElementRc, PropertyDeclaration, QualifiedTypeName, find_element_by_id,
20    visit_named_references_in_expression,
21};
22use crate::parser;
23use crate::parser::{SyntaxKind, syntax_nodes};
24use crate::typeregister::TypeRegister;
25
26/// A parsed [syntax_nodes::UsesIdentifier].
27#[derive(Clone, Debug)]
28struct UsesStatement {
29    interface_name: QualifiedTypeName,
30    child_id: SmolStr,
31    node: syntax_nodes::UsesIdentifier,
32}
33
34impl UsesStatement {
35    /// Get the node representing the interface name.
36    fn interface_name_node(&self) -> syntax_nodes::QualifiedName {
37        self.node.QualifiedName()
38    }
39
40    /// Get the node representing the child identifier.
41    fn child_id_node(&self) -> syntax_nodes::DeclaredIdentifier {
42        self.node.DeclaredIdentifier()
43    }
44
45    /// Lookup the interface component for this uses statement. Emits an error if the iterface could not be found, or
46    /// was not actually an interface.
47    fn lookup_interface(
48        &self,
49        tr: &TypeRegister,
50        diag: &mut BuildDiagnostics,
51    ) -> Result<Rc<Component>, ()> {
52        let interface_name = self.interface_name.to_smolstr();
53        match tr.lookup_element(&interface_name) {
54            Ok(element_type) => match element_type {
55                ElementType::Component(component) => {
56                    if !component.is_interface() {
57                        diag.push_error(
58                            format!("'{}' is not an interface", self.interface_name),
59                            &self.interface_name_node(),
60                        );
61                        return Err(());
62                    }
63
64                    Ok(component)
65                }
66                _ => {
67                    diag.push_error(
68                        format!("'{}' is not an interface", self.interface_name),
69                        &self.interface_name_node(),
70                    );
71                    Err(())
72                }
73            },
74            Err(error) => {
75                diag.push_error(error, &self.interface_name_node());
76                Err(())
77            }
78        }
79    }
80}
81
82impl From<&syntax_nodes::UsesIdentifier> for UsesStatement {
83    fn from(node: &syntax_nodes::UsesIdentifier) -> UsesStatement {
84        UsesStatement {
85            interface_name: QualifiedTypeName::from_node(
86                node.child_node(SyntaxKind::QualifiedName).unwrap().clone().into(),
87            ),
88            child_id: parser::identifier_text(&node.DeclaredIdentifier()).unwrap_or_default(),
89            node: node.clone(),
90        }
91    }
92}
93
94enum InterfaceUseMode {
95    Implements,
96    Uses,
97}
98
99fn validate_property_declaration_for_interface(
100    mode: InterfaceUseMode,
101    result: &PropertyLookupResult,
102    base_type: &ElementType,
103    interface_name: &dyn Display,
104) -> Result<(), String> {
105    let usage = match mode {
106        InterfaceUseMode::Implements => "implement",
107        InterfaceUseMode::Uses => "use",
108    };
109
110    match result.property_type {
111        Type::Invalid => Ok(()),
112        Type::Callback { .. } => Err(format!(
113            "Cannot {} interface '{}' because '{}' conflicts with an existing callback in '{}'",
114            usage, interface_name, result.resolved_name, base_type
115        )),
116        Type::Function { .. } => Err(format!(
117            "Cannot {} interface '{}' because '{}' conflicts with an existing function in '{}'",
118            usage, interface_name, result.resolved_name, base_type
119        )),
120        _ => Err(format!(
121            "Cannot {} interface '{}' because '{}' conflicts with an existing property in '{}'",
122            usage, interface_name, result.resolved_name, base_type
123        )),
124    }
125}
126
127/// An ImplementsSpecifier and the corresponding interface element.
128pub(super) struct ImplementedInterface {
129    implements_specifier: syntax_nodes::ImplementsSpecifier,
130    interface: ElementRc,
131    interface_name: SmolStr,
132}
133
134/// If the element implements a valid interface, return the corresponding ImplementedInterface. Otherwise return None.
135/// Emits diagnostics if the implements specifier is invalid.
136pub(super) fn get_implemented_interface(
137    e: &Element,
138    node: &syntax_nodes::Element,
139    tr: &TypeRegister,
140    diag: &mut BuildDiagnostics,
141) -> Option<ImplementedInterface> {
142    let parent: syntax_nodes::Component =
143        node.parent().filter(|p| p.kind() == SyntaxKind::Component)?.into();
144
145    let implements_specifier = parent.ImplementsSpecifier()?;
146
147    if !diag.enable_experimental && !tr.expose_internal_types {
148        diag.push_error("'implements' is an experimental feature".into(), &implements_specifier);
149        return None;
150    }
151
152    let interface_name =
153        QualifiedTypeName::from_node(implements_specifier.QualifiedName()).to_smolstr();
154
155    match e.base_type.lookup_type_for_child_element(&interface_name, tr) {
156        Ok(ElementType::Component(c)) => {
157            if !c.is_interface() {
158                diag.push_error(
159                    format!("Cannot implement {}. It is not an interface", interface_name),
160                    &implements_specifier.QualifiedName(),
161                );
162                return None;
163            }
164
165            c.used.set(true);
166            Some(ImplementedInterface {
167                implements_specifier,
168                interface: c.root_element.clone(),
169                interface_name,
170            })
171        }
172        Ok(_) => {
173            diag.push_error(
174                format!("Cannot implement {}. It is not an interface", interface_name),
175                &implements_specifier.QualifiedName(),
176            );
177            None
178        }
179        Err(err) => {
180            diag.push_error(err, &implements_specifier.QualifiedName());
181            None
182        }
183    }
184}
185
186/// Apply the properties declared in the interface to the element, emitting diagnostics if there are any conflicts.
187/// Existing property declarations are permitted, provided they match the declaration from the interface.
188pub(super) fn apply_properties(
189    e: &mut Element,
190    implemented_interface: &Option<ImplementedInterface>,
191    diag: &mut BuildDiagnostics,
192) {
193    let Some(ImplementedInterface { interface, implements_specifier, interface_name }) =
194        implemented_interface
195    else {
196        return;
197    };
198
199    for (unresolved_prop_name, prop_decl) in
200        interface.borrow().property_declarations.iter().filter(|(_, prop_decl)| {
201            // Functions are expected to be implemented manually, so we don't automatically add them.
202            !matches!(prop_decl.property_type, Type::Function { .. } | Type::Callback { .. })
203        })
204    {
205        apply_interface_property_declaration(
206            e,
207            unresolved_prop_name,
208            prop_decl,
209            implements_specifier,
210            interface_name,
211            diag,
212        );
213    }
214}
215
216/// Apply the callbacks declared in the interface to the element, emitting diagnostics if there are any conflicts.
217/// Existing callback declarations are permitted, provided they match the declaration from the interface.
218pub(super) fn apply_callbacks(
219    e: &mut Element,
220    implemented_interface: &Option<ImplementedInterface>,
221    diag: &mut BuildDiagnostics,
222) {
223    let Some(ImplementedInterface { interface, implements_specifier, interface_name }) =
224        implemented_interface
225    else {
226        return;
227    };
228
229    for (unresolved_prop_name, prop_decl) in
230        interface.borrow().property_declarations.iter().filter(|(_, prop_decl)| {
231            // Functions are expected to be implemented manually, so we don't automatically add them.
232            matches!(prop_decl.property_type, Type::Callback { .. })
233        })
234    {
235        apply_interface_property_declaration(
236            e,
237            unresolved_prop_name,
238            prop_decl,
239            implements_specifier,
240            interface_name,
241            diag,
242        );
243    }
244}
245
246/// Apply a [PropertyDeclaration] from an interface to the element, emitting diagnostics if there are any conflicts. An
247/// existing declaration with the same name is permitted, provided it matches the declaration from the interface.
248fn apply_interface_property_declaration(
249    e: &mut Element,
250    unresolved_prop_name: &SmolStr,
251    prop_decl: &PropertyDeclaration,
252    implements_specifier: &syntax_nodes::ImplementsSpecifier,
253    interface_name: &SmolStr,
254    diag: &mut BuildDiagnostics,
255) {
256    let lookup_result = e.lookup_property(unresolved_prop_name);
257
258    if lookup_result.property_type != Type::Invalid {
259        match property_matches_interface(&lookup_result, prop_decl) {
260            Ok(()) => {
261                // The property already exists and matches the interface declaration, so we don't need to do anything.
262                return;
263            }
264            Err(error) => {
265                // Attempt to find a node for the existing property for better diagnostics. If the property is not local
266                // to the component, we fall back to pointing at the implements specifier below.
267                if let Some(local_property_node) = e
268                    .property_declarations
269                    .get(unresolved_prop_name)
270                    .and_then(|decl| decl.node.clone())
271                {
272                    diag.push_error(
273                        format!("Conflict with '{}' which {}", interface_name, error),
274                        &local_property_node,
275                    );
276                    return;
277                }
278            }
279        }
280    }
281
282    if let Err(message) = validate_property_declaration_for_interface(
283        InterfaceUseMode::Implements,
284        &lookup_result,
285        &e.base_type,
286        &interface_name,
287    ) {
288        diag.push_error(message, &implements_specifier.QualifiedName());
289        return;
290    }
291
292    e.property_declarations.insert(unresolved_prop_name.clone(), prop_decl.clone());
293}
294
295/// Apply default property values defined in the interface to the element.
296pub(super) fn apply_default_property_values(
297    e: &ElementRc,
298    implemented_interface: &Option<ImplementedInterface>,
299) {
300    let Some(ImplementedInterface { interface, .. }) = implemented_interface else {
301        return;
302    };
303
304    let interface_root = interface.clone();
305
306    // Collect the bindings to apply first, to avoid borrow conflicts
307    let bindings_to_apply: Vec<_> = interface
308        .borrow()
309        .property_declarations
310        .iter()
311        .filter(|(_, prop_decl)| {
312            // Only apply default bindings for properties
313            !matches!(prop_decl.property_type, Type::Function { .. } | Type::Callback { .. })
314        })
315        .filter_map(|(property_name, _)| {
316            interface
317                .borrow()
318                .bindings
319                .get(property_name)
320                .map(|binding| (property_name.clone(), binding.clone()))
321        })
322        .filter(|(property_name, _)| {
323            // Only apply the default binding if there isn't already a binding set on the element.
324            !e.borrow().is_binding_set(property_name, true)
325        })
326        .collect();
327
328    for (property_name, binding) in bindings_to_apply {
329        // Remap NamedReferences from the interface's root element to the implementing element
330        visit_named_references_in_expression(&mut binding.borrow_mut().expression, &mut |nr| {
331            if Rc::ptr_eq(&nr.element(), &interface_root) {
332                *nr = NamedReference::new(e, nr.name().clone());
333            }
334        });
335        e.borrow_mut().bindings.insert(property_name, binding);
336    }
337}
338
339/// Validate that the functions declared in the interface are correctly implemented in the element. Emits diagnostics if not.
340pub(super) fn validate_function_implementations(
341    e: &Element,
342    implemented_interface: &Option<ImplementedInterface>,
343    diag: &mut BuildDiagnostics,
344) {
345    let Some(ImplementedInterface { interface, implements_specifier, interface_name }) =
346        implemented_interface
347    else {
348        return;
349    };
350
351    for (function_name, function_property_decl) in interface
352        .borrow()
353        .property_declarations
354        .iter()
355        .filter(|(_, prop_decl)| matches!(prop_decl.property_type, Type::Function { .. }))
356    {
357        let Type::Function(ref function_declaration) = function_property_decl.property_type else {
358            debug_assert!(false, "Non-functions should have been filtered out already");
359            continue;
360        };
361
362        let push_interface_error = |diag: &mut BuildDiagnostics, is_local_to_component, error| {
363            if is_local_to_component {
364                let source = e
365                    .property_declarations
366                    .get(function_name)
367                    .and_then(|decl| decl.node.clone())
368                    .map_or_else(
369                        || parser::NodeOrToken::Node(implements_specifier.QualifiedName().into()),
370                        parser::NodeOrToken::Node,
371                    );
372                diag.push_error(error, &source);
373            } else {
374                diag.push_error(error, &implements_specifier.QualifiedName());
375            }
376        };
377
378        let found_function = e.lookup_property(function_name);
379        let function_impl = match found_function.property_type {
380            Type::Invalid => {
381                diag.push_error(
382                    format!("Missing implementation of function '{}'", function_name),
383                    &implements_specifier.QualifiedName(),
384                );
385                None
386            }
387            Type::Function(function) => Some(function.clone()),
388            _ => {
389                push_interface_error(
390                    diag,
391                    found_function.is_local_to_component,
392                    format!(
393                        "Cannot override '{}' from interface '{}'",
394                        function_name, interface_name
395                    ),
396                );
397                None
398            }
399        };
400        let Some(function_impl) = function_impl else { continue };
401
402        match (function_property_decl.pure, found_function.declared_pure) {
403            (Some(true), Some(false)) | (Some(true), None) => push_interface_error(
404                diag,
405                found_function.is_local_to_component,
406                format!(
407                    "Implementation of pure function '{}' from interface '{}' cannot be impure",
408                    function_name, interface_name
409                ),
410            ),
411            _ => {
412                // If the implementation is pure but the declaration is not, we allow it.
413            }
414        }
415
416        if function_property_decl.visibility != found_function.property_visibility {
417            push_interface_error(
418                diag,
419                found_function.is_local_to_component,
420                format!(
421                    "Incorrect visibility for implementation of '{}' from interface '{}'. Expected '{}'",
422                    function_name, interface_name, function_property_decl.visibility,
423                ),
424            );
425        }
426
427        if function_impl.args != function_declaration.args {
428            let display_args = |args: &Vec<Type>| -> SmolStr {
429                args.iter().map(|t| t.to_string()).join(", ").into()
430            };
431
432            push_interface_error(
433                diag,
434                found_function.is_local_to_component,
435                format!(
436                    "Incorrect arguments for implementation of '{}' from interface '{}'. Expected ({}) but got ({})",
437                    function_name,
438                    interface_name,
439                    display_args(&function_declaration.args),
440                    display_args(&function_impl.args),
441                ),
442            );
443        }
444
445        if function_impl.return_type != function_declaration.return_type {
446            push_interface_error(
447                diag,
448                found_function.is_local_to_component,
449                format!(
450                    "Incorrect return type for implementation of '{}' from interface '{}'. Expected '{}' but got '{}'",
451                    function_name,
452                    interface_name,
453                    function_declaration.return_type,
454                    function_impl.return_type,
455                ),
456            );
457        }
458    }
459}
460
461pub(super) fn apply_uses_statement(
462    e: &ElementRc,
463    uses_specifier: Option<syntax_nodes::UsesSpecifier>,
464    tr: &TypeRegister,
465    diag: &mut BuildDiagnostics,
466) {
467    let Some(uses_specifier) = uses_specifier else {
468        return;
469    };
470
471    if !diag.enable_experimental && !tr.expose_internal_types {
472        diag.push_error("'uses' is an experimental feature".into(), &uses_specifier);
473        return;
474    }
475
476    let uses_statements = gather_valid_uses_statements(e, tr, diag, uses_specifier);
477    let uses_statements = filter_conflicting_uses_statements(diag, uses_statements);
478
479    for ValidUsesStatement { uses_statement, interface, child } in uses_statements {
480        for (name, prop_decl) in interface.borrow().property_declarations.iter() {
481            let lookup_result = e.borrow().base_type.lookup_property(name);
482            if let Err(message) = validate_property_declaration_for_interface(
483                InterfaceUseMode::Uses,
484                &lookup_result,
485                &e.borrow().base_type,
486                &uses_statement.interface_name,
487            ) {
488                diag.push_error(message, &uses_statement.interface_name_node());
489                continue;
490            }
491
492            // Replace the node with the interface name for better diagnostics later, since the declaration won't have a
493            // node in this element.
494            let mut prop_decl = prop_decl.clone();
495            prop_decl.node = Some(uses_statement.interface_name_node().into());
496
497            if let Some(existing_property) =
498                e.borrow_mut().property_declarations.insert(name.clone(), prop_decl.clone())
499            {
500                let source = existing_property
501                    .node
502                    .as_ref()
503                    .and_then(|node| node.child_node(SyntaxKind::DeclaredIdentifier))
504                    .and_then(|node| node.child_token(SyntaxKind::Identifier))
505                    .map_or_else(
506                        || parser::NodeOrToken::Node(uses_statement.child_id_node().into()),
507                        parser::NodeOrToken::Token,
508                    );
509
510                diag.push_error(
511                    format!("Cannot override '{}' from '{}'", name, uses_statement.interface_name),
512                    &source,
513                );
514                continue;
515            }
516
517            let exisitng_binding = match &prop_decl.property_type {
518                Type::Function(func) => {
519                    apply_uses_statement_function_binding(e, &child, name, func)
520                }
521                _ => e.borrow_mut().bindings.insert(
522                    name.clone(),
523                    BindingExpression::new_two_way(
524                        NamedReference::new(&child, name.clone()).into(),
525                    )
526                    .into(),
527                ),
528            };
529
530            if let Some(existing_binding) = exisitng_binding {
531                let message = format!(
532                    "Cannot override binding for '{}' from interface '{}'",
533                    name, uses_statement.interface_name
534                );
535                if let Some(location) = &existing_binding.borrow().span {
536                    diag.push_error(message, location);
537                } else {
538                    diag.push_error(message, &uses_statement.interface_name_node());
539                }
540            }
541        }
542    }
543}
544
545/// A valid `uses` statement, containing the looked up interface and child element.
546struct ValidUsesStatement {
547    uses_statement: UsesStatement,
548    interface: ElementRc,
549    child: ElementRc,
550}
551
552/// Gather valid `uses` statements, emitting diagnostics for invalid ones. A valid `uses` statement is one where the
553/// interface can be found, the child element can be found, and the child element implements the interface.
554fn gather_valid_uses_statements(
555    e: &Rc<RefCell<Element>>,
556    tr: &TypeRegister,
557    diag: &mut BuildDiagnostics,
558    uses_specifier: syntax_nodes::UsesSpecifier,
559) -> Vec<ValidUsesStatement> {
560    let mut valid_uses_statements: Vec<ValidUsesStatement> = Vec::new();
561
562    for uses_identifier_node in uses_specifier.UsesIdentifier() {
563        let uses_statement: UsesStatement = (&uses_identifier_node).into();
564        let Ok(interface_component) = uses_statement.lookup_interface(tr, diag) else {
565            continue;
566        };
567
568        let Some(child) = find_element_by_id(e, &uses_statement.child_id) else {
569            diag.push_error(
570                format!("'{}' does not exist", uses_statement.child_id),
571                &uses_statement.child_id_node(),
572            );
573            continue;
574        };
575
576        let interface = interface_component.root_element.clone();
577        if !element_implements_interface(&child, &interface, &uses_statement, diag) {
578            continue;
579        }
580
581        valid_uses_statements.push(ValidUsesStatement { uses_statement, interface, child });
582    }
583    valid_uses_statements
584}
585
586/// Filter out conflicting `uses` statements, emitting diagnostics for each conflict. Two `uses` statements conflict if
587/// they introduce properties/callbacks/functions with the same name. In that case we keep the first one and filter out
588/// the rest.
589fn filter_conflicting_uses_statements(
590    diag: &mut BuildDiagnostics,
591    uses_statements: Vec<ValidUsesStatement>,
592) -> Vec<ValidUsesStatement> {
593    let mut seen_interfaces: Vec<SmolStr> = Vec::new();
594    let mut seen_interface_api: BTreeMap<SmolStr, SmolStr> = BTreeMap::new();
595    let valid_uses_statements: Vec<ValidUsesStatement> = uses_statements
596        .into_iter()
597        .filter(|vus| {
598            let interface_name = vus.uses_statement.interface_name.to_smolstr();
599            if seen_interfaces.contains(&interface_name) {
600                diag.push_error(
601                    format!("'{}' is used multiple times", vus.uses_statement.interface_name),
602                    &vus.uses_statement.interface_name_node(),
603                );
604                return false;
605            }
606            seen_interfaces.push(interface_name.clone());
607
608            let mut valid = true;
609            for (prop_name, _) in vus.interface.borrow().property_declarations.iter() {
610                if let Some(existing_interface) = seen_interface_api.get(prop_name) {
611                    diag.push_error(
612                        format!(
613                            "'{}' occurs in '{}' and '{}'",
614                            prop_name, vus.uses_statement.interface_name, existing_interface
615                        ),
616                        &vus.uses_statement.interface_name_node(),
617                    );
618                    valid = false;
619                } else {
620                    seen_interface_api.insert(prop_name.clone(), interface_name.clone());
621                }
622            }
623            valid
624        })
625        .collect();
626    valid_uses_statements
627}
628
629/// Check that the given element implements the given interface. Emits a diagnostic if the interface is not implemented.
630fn element_implements_interface(
631    element: &ElementRc,
632    interface: &ElementRc,
633    uses_statement: &UsesStatement,
634    diag: &mut BuildDiagnostics,
635) -> bool {
636    let mut valid = true;
637    let mut check = |property_name: &SmolStr, property_declaration: &PropertyDeclaration| {
638        let lookup_result = element.borrow().lookup_property(property_name);
639        if let Err(e) = property_matches_interface(&lookup_result, property_declaration) {
640            diag.push_error(
641                format!(
642                    "'{}' does not implement '{}' from '{}' - {}",
643                    uses_statement.child_id, property_name, uses_statement.interface_name, e
644                ),
645                &uses_statement.child_id_node(),
646            );
647            valid = false;
648        }
649    };
650
651    for (property_name, property_declaration) in interface.borrow().property_declarations.iter() {
652        check(property_name, property_declaration);
653    }
654
655    valid
656}
657
658/// Check that the given property matches the declaration from the interface. Emits a diagnostic if it doesn't match.
659fn property_matches_interface(
660    property: &PropertyLookupResult,
661    interface_declaration: &PropertyDeclaration,
662) -> Result<(), String> {
663    if property.property_type == Type::Invalid {
664        return Err("not found".into());
665    }
666
667    let mut errors = Vec::new();
668
669    if property.property_type != interface_declaration.property_type {
670        errors.push(format!("type: '{}'", interface_declaration.property_type));
671    }
672
673    if property.property_visibility != interface_declaration.visibility {
674        errors.push(format!("visibility: '{}'", interface_declaration.visibility));
675    }
676
677    if property.declared_pure.unwrap_or(false) != interface_declaration.pure.unwrap_or(false) {
678        errors
679            .push(format!("purity declaration: '{}'", interface_declaration.pure.unwrap_or(false)));
680    }
681
682    if errors.is_empty() {
683        Ok(())
684    } else {
685        Err(format!("expected {}", errors.into_iter().join(", ")))
686    }
687}
688
689/// Apply the function from the interface to the element, creating a forwarding bindings to the function on the child
690/// element. Emits diagnostics if there are conflicting functions.
691fn apply_uses_statement_function_binding(
692    e: &ElementRc,
693    child: &ElementRc,
694    name: &SmolStr,
695    func: &Rc<Function>,
696) -> Option<RefCell<BindingExpression>> {
697    // Create forwarding call expression: child.function_name(arg0, arg1, ...)
698    let args_expr: Vec<Expression> = func
699        .args
700        .iter()
701        .enumerate()
702        .map(|(i, ty)| Expression::FunctionParameterReference { index: i, ty: ty.clone() })
703        .collect();
704
705    // Use Callable::Function with a NamedReference to the child's function
706    let call_expr = Expression::FunctionCall {
707        function: Callable::Function(NamedReference::new(child, name.clone())),
708        arguments: args_expr,
709        source_location: None,
710    };
711
712    // The function body is just the forwarding call. CodeBlock handles the return implicitly for the last expression
713    let body = Expression::CodeBlock(vec![call_expr]);
714    e.borrow_mut().bindings.insert(name.clone(), RefCell::new(BindingExpression::from(body)))
715}