Skip to main content

i_slint_compiler/passes/
check_builtin_shadowing.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! Diagnose declarations that shadow a builtin element member.
5//!
6//! A declaration may shadow a shadowable builtin member with a warning, but only as
7//! long as no base component uses that member: inlining merges a base component's root
8//! element into the inheriting element, and since references are looked up by name, the
9//! base component's references to the builtin member would silently resolve to the
10//! shadowing declaration instead. In that case, report an error.
11
12use crate::diagnostics::BuildDiagnostics;
13use crate::langtype::{ElementType, Type};
14use crate::object_tree::{Component, Document, recurse_elem};
15use crate::parser::SyntaxKind;
16use std::rc::Rc;
17
18pub fn check_builtin_shadowing(doc: &Document, diag: &mut BuildDiagnostics) {
19    for component in &doc.inner_components {
20        recurse_elem(&component.root_element, &(), &mut |elem, _| {
21            let elem = elem.borrow();
22            for (name, decl) in &elem.property_declarations {
23                if !decl.shadows_builtin {
24                    continue;
25                }
26                let Some(node) = &decl.node else { continue };
27                let span =
28                    node.child_node(SyntaxKind::DeclaredIdentifier).unwrap_or_else(|| node.clone());
29                let builtin_kind = member_kind(&elem.base_type.lookup_property(name).property_type);
30
31                // Look for a base component that uses the builtin member
32                let mut base = elem.base_type.clone();
33                let conflicting_base = loop {
34                    let ElementType::Component(c) = base else { break None };
35                    base = c.root_element.borrow().base_type.clone();
36                    if uses_member_on_root(&c, name) {
37                        break Some(c);
38                    }
39                };
40
41                if let Some(c) = conflicting_base {
42                    diag.push_error(
43                        format!(
44                            "Cannot shadow the builtin {builtin_kind} '{name}' because it is used by the base component '{}'",
45                            c.id
46                        ),
47                        &span,
48                    );
49                } else {
50                    diag.push_warning(
51                        format!("'{name}' shadows the builtin {builtin_kind} of the same name"),
52                        &span,
53                    );
54                }
55            }
56        });
57    }
58}
59
60/// True if the component's own code uses `name` on its root element
61fn uses_member_on_root(component: &Rc<Component>, name: &str) -> bool {
62    let root = component.root_element.borrow();
63    // bindings and change callbacks refer to the property by name,
64    // everything else goes through a NamedReference
65    root.named_references.is_referenced(name)
66        || root.bindings.contains_key(name)
67        || root.change_callbacks.contains_key(name)
68}
69
70/// The kind of element member that a type represents, for use in diagnostics
71fn member_kind(ty: &Type) -> &'static str {
72    match ty {
73        Type::Callback { .. } => "callback",
74        Type::Function { .. } => "function",
75        _ => "property",
76    }
77}