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::collections::BTreeMap;
7use std::rc::Rc;
8use std::sync::Arc;
9
10use itertools::Itertools;
11use smol_str::SmolStr;
12
13use crate::diagnostics::{BuildDiagnostics, SourceLocation, Spanned};
14use crate::expression_tree::{BindingExpression, Callable, Expression};
15use crate::langtype::{ElementType, Function, PropertyLookupMode, PropertyLookupResult, Type};
16use crate::namedreference::NamedReference;
17use crate::object_tree::{
18    Element, ElementRc, PropertyDeclaration, PropertyVisibility, QualifiedTypeName,
19    find_element_by_id,
20};
21use crate::parser::{self, SyntaxNode, SyntaxToken};
22use crate::parser::{SyntaxKind, syntax_nodes};
23use crate::reject_experimental_feature;
24use crate::typeregister::TypeRegister;
25
26fn check_property_declaration_conflicts(
27    result: &PropertyLookupResult,
28    base_type: &ElementType,
29) -> Result<(), String> {
30    match result.property_type {
31        Type::Invalid => Ok(()),
32        Type::Callback { .. } => Err(format!(
33            "- '{}' conflicts with an existing callback in '{}'",
34            result.resolved_name, base_type
35        )),
36        Type::Function { .. } => Err(format!(
37            "- '{}' conflicts with an existing function in '{}'",
38            result.resolved_name, base_type
39        )),
40        _ => Err(format!(
41            "- '{}' conflicts with an existing property in '{}'",
42            result.resolved_name, base_type
43        )),
44    }
45}
46
47#[derive(Debug, PartialEq)]
48pub(super) enum ImplementBinding {
49    OnSelf,
50    OnChild {
51        /// The normalized id of the element.
52        child_id: SmolStr,
53        /// The id as used in the .slint source.
54        child_name: SmolStr,
55    },
56}
57
58impl ImplementBinding {
59    fn from_target(target_id: &SmolStr, target_name: &SmolStr) -> ImplementBinding {
60        if target_id.as_str() == "self" {
61            ImplementBinding::OnSelf
62        } else {
63            ImplementBinding::OnChild {
64                child_id: target_id.clone(),
65                child_name: target_name.clone(),
66            }
67        }
68    }
69}
70
71pub(super) struct ImplementedInterface {
72    node: syntax_nodes::ImplementStatement,
73    interface: ElementRc,
74    interface_name: SmolStr,
75    binding: ImplementBinding,
76}
77
78fn resolve_implement_statement(
79    element: &Element,
80    node: syntax_nodes::ImplementStatement,
81    type_register: &TypeRegister,
82    diagnostics: &mut BuildDiagnostics,
83) -> Option<ImplementedInterface> {
84    #[cfg(feature = "slint-sc")]
85    diagnostics.slint_sc_error("'implement' is", &node);
86
87    if reject_experimental_feature(diagnostics, type_register, "implement", &node) {
88        return None;
89    }
90
91    let qualified_name = node.QualifiedName();
92    let interface_name = QualifiedTypeName::from_node(qualified_name.clone()).to_smolstr();
93    let target_name =
94        node.DeclaredIdentifier().child_text(SyntaxKind::Identifier).unwrap_or_default();
95    let target_id = parser::normalize_identifier(&target_name);
96
97    if let Some(target) = match target_id.as_str() {
98        "parent" => Some("a parent element"),
99        "root" => Some("the root element; use 'self' instead"),
100        _ => None,
101    } {
102        diagnostics.push_error(
103            format!("Cannot implement an interface based on {}", target),
104            &node.DeclaredIdentifier(),
105        );
106        return None;
107    }
108
109    match element.base_type.lookup_type_for_child_element(&interface_name, type_register) {
110        Ok(ElementType::Component(c)) => {
111            if !c.is_interface() {
112                diagnostics.push_error(
113                    format!("Cannot implement {}. It is not an interface", interface_name),
114                    &qualified_name,
115                );
116                return None;
117            }
118
119            c.used.set(true);
120            Some(ImplementedInterface {
121                node,
122                interface: c.root_element.clone(),
123                interface_name,
124                binding: ImplementBinding::from_target(&target_id, &target_name),
125            })
126        }
127        Ok(_) => {
128            // `lookup_type_for_child_element` resolves names like `Row` that are only valid
129            // within a specific parent context (e.g. `GridLayout`), since it accounts for the
130            // element's own base type. `tr.lookup_element` ignores that context and, for such
131            // names, fails with a more specific diagnostic instead - reuse it here when it
132            // applies, rather than the generic "not an interface" message.
133            let message = match type_register.lookup_element(&interface_name) {
134                Err(context_restricted_message) => context_restricted_message,
135                Ok(_) => format!("Cannot implement {}. It is not an interface", interface_name),
136            };
137            diagnostics.push_error(message, &qualified_name);
138            None
139        }
140        Err(err) => {
141            diagnostics.push_error(err, &qualified_name);
142            None
143        }
144    }
145}
146
147fn filter_conflicting_implement_statements(
148    diagnostics: &mut BuildDiagnostics,
149    statements: Vec<ImplementedInterface>,
150) -> Vec<ImplementedInterface> {
151    let mut seen_interfaces: Vec<ElementRc> = Vec::new();
152    let mut seen_interface_api: BTreeMap<SmolStr, SmolStr> = BTreeMap::new();
153    statements
154        .into_iter()
155        .filter(|stmt| {
156            // Interface identity is the resolved interface's root element, not the syntactic name,
157            // so this also catches the same interface implemented twice under different aliases.
158            if seen_interfaces.iter().any(|seen| Rc::ptr_eq(seen, &stmt.interface)) {
159                diagnostics.push_error(
160                    format!("'{}' is implemented multiple times", stmt.interface_name),
161                    &stmt.node,
162                );
163                return false;
164            }
165            seen_interfaces.push(stmt.interface.clone());
166
167            let mut valid = true;
168            for prop_name in stmt.interface.borrow().property_declarations.keys() {
169                if let Some(existing_interface) = seen_interface_api.get(prop_name) {
170                    diagnostics.push_error(
171                        format!(
172                            "'{}' occurs in '{}' and '{}'",
173                            prop_name, stmt.interface_name, existing_interface
174                        ),
175                        &stmt.node.QualifiedName(),
176                    );
177                    valid = false;
178                } else {
179                    seen_interface_api.insert(prop_name.clone(), stmt.interface_name.clone());
180                }
181            }
182            valid
183        })
184        .collect()
185}
186
187pub(super) fn get_implemented_interfaces(
188    element: &Element,
189    node: &syntax_nodes::Element,
190    type_register: &TypeRegister,
191    diagnostics: &mut BuildDiagnostics,
192) -> (Vec<ImplementedInterface>, Vec<ImplementedInterface>) {
193    let resolved: Vec<ImplementedInterface> = node
194        .ImplementStatement()
195        .filter_map(|stmt| resolve_implement_statement(element, stmt, type_register, diagnostics))
196        .collect();
197
198    let filtered = filter_conflicting_implement_statements(diagnostics, resolved);
199
200    let mut self_interfaces = Vec::new();
201    let mut child_implements = Vec::new();
202    for stmt in filtered {
203        if stmt.binding == ImplementBinding::OnSelf {
204            self_interfaces.push(stmt);
205        } else {
206            child_implements.push(stmt);
207        }
208    }
209    (self_interfaces, child_implements)
210}
211
212pub(super) fn disallow_implement_in_non_root(
213    node: &syntax_nodes::Element,
214    type_register: &TypeRegister,
215    diagnostics: &mut BuildDiagnostics,
216) {
217    for stmt in node.ImplementStatement() {
218        if reject_experimental_feature(diagnostics, type_register, "implement", &stmt) {
219            continue;
220        }
221        diagnostics.push_error("'implement' is only allowed in the root element".into(), &stmt);
222    }
223}
224
225pub(super) fn validate_self_implement_statements(
226    element: &Element,
227    implemented_interfaces: &[ImplementedInterface],
228    diagnostics: &mut BuildDiagnostics,
229) {
230    for ImplementedInterface { interface, node, interface_name, binding } in implemented_interfaces
231    {
232        validate_interface_implementation(
233            element,
234            interface,
235            interface_name,
236            &node.QualifiedName(),
237            binding,
238            diagnostics,
239        );
240    }
241}
242
243struct NoteWithSource {
244    note: String,
245    source: SourceLocation,
246}
247
248struct InterfaceMemberDiagnostics {
249    error: String,
250    notes: Vec<NoteWithSource>,
251}
252
253impl From<String> for InterfaceMemberDiagnostics {
254    fn from(error: String) -> Self {
255        Self { error, notes: Default::default() }
256    }
257}
258
259enum DeclarationAnchor {
260    Name,
261    PropertyType,
262    /// The n-th parameter of a callback or function.
263    Argument(usize),
264    ReturnType,
265    Visibility(PropertyVisibility),
266    Purity,
267}
268
269impl DeclarationAnchor {
270    fn source_location(&self, declaration: &SyntaxNode) -> SourceLocation {
271        self.narrow(declaration)
272            .or_else(|| {
273                Some(declaration.child_node(SyntaxKind::DeclaredIdentifier)?.to_source_location())
274            })
275            .unwrap_or_else(|| declaration.to_source_location())
276    }
277
278    fn narrow(&self, declaration: &SyntaxNode) -> Option<SourceLocation> {
279        let node = match self {
280            Self::Name => return None,
281            Self::PropertyType => declaration.child_node(SyntaxKind::Type)?,
282            Self::Argument(index) => parameter_type(declaration, *index)?,
283            Self::ReturnType => declaration.child_node(SyntaxKind::ReturnType)?,
284            Self::Visibility(visibility) => {
285                return Some(
286                    keyword_token(declaration, &visibility.to_string())?.to_source_location(),
287                );
288            }
289            Self::Purity => {
290                return Some(keyword_token(declaration, "pure")?.to_source_location());
291            }
292        };
293        Some(node.to_source_location())
294    }
295}
296
297fn parameter_type(declaration: &SyntaxNode, index: usize) -> Option<SyntaxNode> {
298    let parameter_kind = match declaration.kind() {
299        SyntaxKind::Function => SyntaxKind::ArgumentDeclaration,
300        SyntaxKind::CallbackDeclaration => SyntaxKind::CallbackDeclarationParameter,
301        _ => return None,
302    };
303    declaration
304        .children()
305        .filter(|child| child.kind() == parameter_kind)
306        .nth(index)?
307        .child_node(SyntaxKind::Type)
308}
309
310/// Visibility and purity are plain identifier tokens rather than syntax nodes, so they can only be
311/// located by their text - the inverse of how [`Element::from_node`] reads them.
312fn keyword_token(declaration: &SyntaxNode, keyword: &str) -> Option<SyntaxToken> {
313    declaration.children_with_tokens().filter_map(|child| child.into_token()).find(|token| {
314        token.kind() == SyntaxKind::Identifier
315            && parser::normalize_identifier(token.text()) == keyword
316    })
317}
318
319struct MemberViolation {
320    error: String,
321    expected_syntax: String,
322    anchor: DeclarationAnchor,
323}
324
325fn validate_interface_implementation(
326    element: &Element,
327    interface: &ElementRc,
328    interface_name: &SmolStr,
329    node: &SyntaxNode,
330    binding: &ImplementBinding,
331    diagnostics: &mut BuildDiagnostics,
332) -> bool {
333    let mut errors = Vec::new();
334    let mut notes = Vec::new();
335    for (member_name, member_declaration) in interface.borrow().property_declarations.iter() {
336        if let Some(mut conflict) = validate_interface_member_implementation(
337            element,
338            member_name,
339            member_declaration,
340            interface_name,
341            binding,
342        ) {
343            errors.push(conflict.error);
344            notes.append(&mut conflict.notes);
345        };
346    }
347
348    if !errors.is_empty() {
349        let based_on = match binding {
350            ImplementBinding::OnChild { child_name, .. } => {
351                format!(" based on '{child_name}'")
352            }
353            ImplementBinding::OnSelf => String::new(),
354        };
355        diagnostics.push_error(
356            format!("Cannot implement '{interface_name}'{based_on}.\n{}", errors.join("\n")),
357            node,
358        );
359
360        for note in notes {
361            diagnostics.push_note_with_span(note.note, note.source);
362        }
363    }
364    errors.is_empty()
365}
366
367fn validate_interface_member_implementation(
368    element: &Element,
369    member_name: &SmolStr,
370    interface_member: &PropertyDeclaration,
371    interface_name: &SmolStr,
372    binding: &ImplementBinding,
373) -> Option<InterfaceMemberDiagnostics> {
374    if matches!(interface_member.property_type, Type::Invalid) {
375        // The interface's own declaration is invalid (e.g. an unknown property type). A diagnostic
376        // was already emitted when the interface was parsed, so there is nothing meaningful to
377        // validate here.
378        return None;
379    }
380
381    let lookup_result = element.lookup_property(member_name, PropertyLookupMode::ComponentLocal);
382    let Err(violations) =
383        property_matches_interface(&lookup_result, interface_member, member_name, binding)
384    else {
385        return None;
386    };
387
388    let joined_errors = violations.iter().map(|v| v.error.as_str()).join("\n");
389    let mut conflicts = InterfaceMemberDiagnostics::from(joined_errors);
390
391    // A shadowing member is stored under a mangled name, so look it up shadow-aware first,
392    // falling back to the base chain for an inherited member.
393    let source = element
394        .declaration(member_name)
395        .and_then(|(_, declaration)| declaration.node.clone())
396        .or_else(|| element.base_type.property_declaration_node(member_name));
397    if lookup_result.is_valid()
398        && let Some(source) = source
399    {
400        conflicts.notes = violations
401            .into_iter()
402            .map(|violation| NoteWithSource {
403                note: declared_here_note(
404                    member_name,
405                    interface_name,
406                    &violation.expected_syntax,
407                    &source,
408                ),
409                source: violation.anchor.source_location(&source),
410            })
411            .collect();
412    }
413    Some(conflicts)
414}
415
416pub(super) fn apply_child_implement_statements(
417    element: &ElementRc,
418    child_implements: Vec<ImplementedInterface>,
419    diagnostics: &mut BuildDiagnostics,
420) {
421    for ImplementedInterface { node, interface, interface_name, binding } in child_implements {
422        debug_assert_ne!(binding, ImplementBinding::OnSelf);
423        let ImplementBinding::OnChild { child_id, child_name } = &binding else {
424            continue;
425        };
426        let Some(child) = find_element_by_id(element, child_id) else {
427            diagnostics
428                .push_error(format!("'{}' does not exist", child_name), &node.DeclaredIdentifier());
429            continue;
430        };
431
432        if !validate_interface_implementation(
433            &child.borrow(),
434            &interface,
435            &interface_name,
436            &node.DeclaredIdentifier(),
437            &binding,
438            diagnostics,
439        ) {
440            continue;
441        }
442
443        let mut conflicts = Vec::new();
444        let mut notes = Vec::new();
445        for (name, prop_decl) in interface.borrow().property_declarations.iter() {
446            let lookup_result = element
447                .borrow()
448                .base_type
449                .lookup_property(name, PropertyLookupMode::ComponentLocal);
450            if let Err(message) =
451                check_property_declaration_conflicts(&lookup_result, &element.borrow().base_type)
452            {
453                conflicts.push(message);
454                if let Some(source) = element.borrow().property_declaration_node(name) {
455                    notes.push(NoteWithSource {
456                        note: declared_here_note(
457                            name,
458                            &interface_name,
459                            &syntax_for_declaration(prop_decl, name),
460                            &source,
461                        ),
462                        source: DeclarationAnchor::Name.source_location(&source),
463                    });
464                }
465                continue;
466            }
467
468            // Replace the node with the interface name for better diagnostics later, since the declaration won't have a
469            // node in this element.
470            let mut prop_decl = prop_decl.clone();
471            prop_decl.node = Some(node.QualifiedName().into());
472
473            // A shadowing declaration also occupies the name, though stored under a different one
474            let shadowing =
475                element.borrow().declaration(name).map(|(_, declaration)| declaration.node.clone());
476            let existing_node = shadowing.or_else(|| {
477                element
478                    .borrow_mut()
479                    .property_declarations
480                    .insert(name.clone(), prop_decl.clone())
481                    .map(|existing| existing.node.clone())
482            });
483            if let Some(existing_node) = existing_node {
484                let source = existing_node
485                    .as_ref()
486                    .and_then(|node| node.child_node(SyntaxKind::DeclaredIdentifier))
487                    .and_then(|node| node.child_token(SyntaxKind::Identifier))
488                    .map_or_else(
489                        || parser::NodeOrToken::Node(node.DeclaredIdentifier().into()),
490                        parser::NodeOrToken::Token,
491                    );
492
493                diagnostics.push_error(
494                    format!("Cannot override '{}' from '{}'", name, interface_name),
495                    &source,
496                );
497                diagnostics.push_note(
498                    declares_as_note(
499                        &interface_name,
500                        name,
501                        &syntax_for_declaration(&prop_decl, name),
502                    ),
503                    &node.QualifiedName(),
504                );
505                continue;
506            }
507
508            let existing_binding = match &prop_decl.property_type {
509                Type::Function(func) => {
510                    apply_uses_statement_function_binding(element, &child, name, func)
511                }
512                _ => element.borrow_mut().set_binding(
513                    name.clone(),
514                    BindingExpression::new_two_way(member_reference(&child, name).into()),
515                ),
516            };
517            debug_assert!(
518                existing_binding.is_none(),
519                "Duplicate bindings should have been caught earlier"
520            );
521        }
522
523        if !conflicts.is_empty() {
524            diagnostics.push_error(
525                format!(
526                    "Cannot implement '{interface_name}' based on '{child_id}'.\n{}",
527                    conflicts.join("\n")
528                ),
529                &node.QualifiedName(),
530            );
531            for note in notes {
532                diagnostics.push_note(note.note, &note.source);
533            }
534        }
535    }
536}
537
538fn purity_description(purity: &Option<bool>) -> &str {
539    if purity.unwrap_or(false) { "pure " } else { "" }
540}
541
542fn syntax_for(
543    name: &SmolStr,
544    property_type: &Type,
545    pure: &Option<bool>,
546    visibility: &PropertyVisibility,
547) -> String {
548    match property_type {
549        Type::Function(function) => {
550            format!("{}{} function {name}{} {{ }}", purity_description(pure), visibility, function)
551        }
552        Type::Callback(function) => {
553            format!("{}callback {name}{};", purity_description(pure), function)
554        }
555        _ if property_type.is_property_type() => {
556            format!("{} property <{}> {name};", visibility, property_type)
557        }
558        _ => name.to_string(),
559    }
560}
561
562fn syntax_for_declaration(interface_declaration: &PropertyDeclaration, name: &SmolStr) -> String {
563    syntax_for(
564        name,
565        &interface_declaration.property_type,
566        &interface_declaration.pure,
567        &interface_declaration.visibility,
568    )
569}
570
571fn syntax_for_lookup_result(lookup_result: &PropertyLookupResult, name: &SmolStr) -> String {
572    syntax_for(
573        name,
574        &lookup_result.property_type,
575        &lookup_result.declared_pure,
576        &lookup_result.property_visibility,
577    )
578}
579
580fn missing_type_error(name: &SmolStr, interface_declaration: &PropertyDeclaration) -> String {
581    format!("- missing '{}'", syntax_for_declaration(interface_declaration, name))
582}
583
584fn declares_as_note(interface_name: &SmolStr, name: &SmolStr, expected_syntax: &String) -> String {
585    format!("'{interface_name}' declares '{name}' as '{expected_syntax}'")
586}
587
588fn declaring_component_name(declaration: SyntaxNode) -> Option<SmolStr> {
589    std::iter::successors(Some(declaration), SyntaxNode::parent).find_map(|node| {
590        match node.kind() {
591            SyntaxKind::SubElement => node.child_text(SyntaxKind::Identifier),
592            SyntaxKind::Component => {
593                parser::identifier_text(&node.child_node(SyntaxKind::DeclaredIdentifier)?)
594            }
595            _ => None,
596        }
597    })
598}
599
600fn declared_here_note(
601    member_name: &SmolStr,
602    interface_name: &SmolStr,
603    expected_syntax: &String,
604    property_declaration_source: &SyntaxNode,
605) -> String {
606    let Some(declaring_type) = declaring_component_name(property_declaration_source.clone()) else {
607        return declares_as_note(interface_name, member_name, expected_syntax);
608    };
609    format!(
610        "'{declaring_type}' declares '{member_name}' here, '{interface_name}' expects '{expected_syntax}'"
611    )
612}
613
614fn signature_anchor(interface_declaration: &Function, declaration: &Function) -> DeclarationAnchor {
615    if let Some(index) = interface_declaration
616        .args
617        .iter()
618        .zip(declaration.args.iter())
619        .position(|(expected, declared)| expected != declared)
620    {
621        DeclarationAnchor::Argument(index)
622    } else if declaration.args.len() > interface_declaration.args.len() {
623        DeclarationAnchor::Argument(interface_declaration.args.len())
624    } else if declaration.args.len() < interface_declaration.args.len() {
625        DeclarationAnchor::Name
626    } else {
627        DeclarationAnchor::ReturnType
628    }
629}
630
631/// [PartialEq] for [Function] means that the argument names must match. That is not required for a valid interface implementation.
632fn function_matches_for_interface(lhs: &Function, rhs: &Function) -> bool {
633    lhs.return_type == rhs.return_type && lhs.args == rhs.args
634}
635
636fn property_type_matches_for_interface(lhs: &Type, rhs: &Type) -> bool {
637    match (lhs, rhs) {
638        (Type::Callback(lhs), Type::Callback(rhs)) => function_matches_for_interface(lhs, rhs),
639        (Type::Function(lhs), Type::Function(rhs)) => function_matches_for_interface(lhs, rhs),
640        _ => lhs == rhs,
641    }
642}
643
644fn property_matches_interface(
645    property: &PropertyLookupResult,
646    interface_declaration: &PropertyDeclaration,
647    name: &SmolStr,
648    binding: &ImplementBinding,
649) -> Result<(), Vec<MemberViolation>> {
650    let expected_syntax = syntax_for_declaration(interface_declaration, name);
651    if property.property_type == Type::Invalid {
652        return Err(vec![MemberViolation {
653            error: missing_type_error(name, interface_declaration),
654            expected_syntax,
655            anchor: DeclarationAnchor::Name,
656        }]);
657    }
658
659    let mut errors = Vec::new();
660
661    let member_name = if let ImplementBinding::OnChild { child_name, .. } = binding {
662        format!("{child_name}.{name}")
663    } else {
664        name.to_string()
665    };
666
667    if !property_type_matches_for_interface(
668        &property.property_type,
669        &interface_declaration.property_type,
670    ) {
671        let is_same_type = match (&interface_declaration.property_type, &property.property_type) {
672            (Type::Callback(..), Type::Callback(..)) | (Type::Function(..), Type::Function(..)) => {
673                true
674            }
675            (lhs, rhs) => lhs.is_property_type() && rhs.is_property_type(),
676        };
677
678        let property_description = |property_type: &Type| format!("a '{}' property", property_type);
679
680        let expected = if is_same_type && interface_declaration.property_type.is_property_type() {
681            property_description(&interface_declaration.property_type)
682        } else {
683            format!("'{}'", syntax_for_declaration(interface_declaration, name))
684        };
685
686        let actual = if property.property_type.is_property_type() {
687            property_description(&property.property_type)
688        } else {
689            format!("'{}'", syntax_for_lookup_result(property, name))
690        };
691
692        let error = format!("- '{member_name}' must be {expected} (found {actual})");
693
694        if !is_same_type {
695            // Visibility and purity are unlikely to make sense, so return early in this case.
696            return Err(vec![MemberViolation {
697                error,
698                expected_syntax,
699                anchor: DeclarationAnchor::Name,
700            }]);
701        }
702
703        let anchor = match (&interface_declaration.property_type, &property.property_type) {
704            (Type::Callback(expected), Type::Callback(declared))
705            | (Type::Function(expected), Type::Function(declared)) => {
706                signature_anchor(expected, declared)
707            }
708
709            (_, _) => DeclarationAnchor::PropertyType,
710        };
711        errors.push(MemberViolation { error, expected_syntax: expected_syntax.clone(), anchor });
712    }
713
714    if property.property_visibility != interface_declaration.visibility {
715        errors.push(MemberViolation {
716            error: format!(
717                "- '{member_name}' must be '{}' (found '{}')",
718                interface_declaration.visibility, property.property_visibility
719            ),
720            expected_syntax: expected_syntax.clone(),
721            anchor: DeclarationAnchor::Visibility(property.property_visibility),
722        });
723    }
724
725    // The implementation can be "more pure" than the interface, but never less pure.
726    if interface_declaration.pure.unwrap_or(false) && !property.declared_pure.unwrap_or(false) {
727        errors.push(MemberViolation {
728            error: format!("- '{member_name}' must be 'pure'"),
729            expected_syntax,
730            anchor: DeclarationAnchor::Purity,
731        });
732    }
733
734    if errors.is_empty() { Ok(()) } else { Err(errors) }
735}
736
737/// A reference to the member of `elem` written as `name` in the source, which for a shadowing
738/// declaration is stored under a different internal name.
739fn member_reference(elem: &ElementRc, name: &SmolStr) -> NamedReference {
740    let internal_name =
741        elem.borrow().declaration(name).map_or_else(|| name.clone(), |(n, _)| n.clone());
742    NamedReference::new(elem, internal_name)
743}
744
745fn apply_uses_statement_function_binding(
746    element: &ElementRc,
747    child: &ElementRc,
748    name: &SmolStr,
749    function: &Arc<Function>,
750) -> Option<BindingExpression> {
751    let args_expr: Vec<Expression> = function
752        .args
753        .iter()
754        .enumerate()
755        .map(|(i, ty)| Expression::FunctionParameterReference { index: i, ty: ty.clone() })
756        .collect();
757
758    let call_expr = Expression::FunctionCall {
759        function: Callable::Function(member_reference(child, name)),
760        arguments: args_expr,
761        source_location: None,
762    };
763
764    let body = Expression::CodeBlock(vec![call_expr]);
765    element.borrow_mut().set_binding(name.clone(), BindingExpression::from(body))
766}