Skip to main content

i_slint_compiler/passes/
resolving.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// cSpell: ignore depr descr idents shiftbehavior unaryop Unshiftable uppercased
5//! This pass resolves the property binding expressions.
6//!
7//! Before this pass, all the expression are of type Expression::Uncompiled,
8//! and there should no longer be Uncompiled expression after this pass.
9//!
10//! Most of the code for the resolving actually lies in the expression_tree module
11
12use crate::diagnostics::{BuildDiagnostics, Spanned};
13use crate::expression_tree::*;
14use crate::langtype;
15use crate::langtype::{ElementType, KeyboardModifiers, Struct, StructName, Type};
16use crate::lookup::{LookupCtx, LookupObject, LookupResult, LookupResultCallable};
17use crate::object_tree::*;
18use crate::parser::{NodeOrToken, SyntaxKind, SyntaxNode, identifier_text, syntax_nodes};
19use crate::typeregister::TypeRegister;
20use core::num::IntErrorKind;
21use smol_str::{SmolStr, ToSmolStr};
22use std::collections::{BTreeMap, HashMap};
23use std::rc::Rc;
24use unicode_segmentation::UnicodeSegmentation;
25
26mod remove_noop;
27
28/// This represents a scope for the Component, where Component is the repeated component, but
29/// does not represent a component in the .slint file
30#[derive(Clone)]
31struct ComponentScope(Vec<ElementRc>);
32
33fn resolve_expression(
34    elem: &ElementRc,
35    expr: &mut Expression,
36    property_name: Option<&str>,
37    property_type: Type,
38    scope: &[ElementRc],
39    type_register: &TypeRegister,
40    type_loader: &crate::typeloader::TypeLoader,
41    diag: &mut BuildDiagnostics,
42) {
43    if let Expression::Uncompiled(node) = expr.ignore_debug_hooks() {
44        let mut lookup_ctx = LookupCtx {
45            property_name,
46            property_type,
47            component_scope: scope,
48            diag,
49            arguments: Vec::new(),
50            type_register,
51            type_loader: Some(type_loader),
52            current_token: None,
53            local_variables: Vec::new(),
54        };
55
56        let new_expr = match node.kind() {
57            SyntaxKind::CallbackConnection => {
58                let node = syntax_nodes::CallbackConnection::from(node.clone());
59                if let Some(property_name) = property_name {
60                    check_callback_alias_validity(&node, elem, property_name, lookup_ctx.diag);
61                }
62                Expression::from_callback_connection(node, &mut lookup_ctx)
63            }
64            SyntaxKind::Function => Expression::from_function(node.clone().into(), &mut lookup_ctx),
65            SyntaxKind::Expression => {
66                //FIXME again: this happen for non-binding expression (i.e: model)
67                Expression::from_expression_node(node.clone().into(), &mut lookup_ctx)
68                    .maybe_convert_to(lookup_ctx.property_type.clone(), node, diag)
69            }
70            SyntaxKind::BindingExpression => {
71                Expression::from_binding_expression_node(node.clone(), &mut lookup_ctx)
72            }
73            SyntaxKind::PropertyChangedCallback => {
74                let node = syntax_nodes::PropertyChangedCallback::from(node.clone());
75                if let Some(code_block_node) = node.CodeBlock() {
76                    Expression::from_codeblock_node(code_block_node, &mut lookup_ctx)
77                } else if let Some(expr_node) = node.Expression() {
78                    Expression::from_expression_node(expr_node, &mut lookup_ctx)
79                } else {
80                    assert!(diag.has_errors());
81                    Expression::Invalid
82                }
83            }
84            SyntaxKind::TwoWayBinding => {
85                assert!(
86                    diag.has_errors(),
87                    "Two way binding should have been resolved already  (property: {property_name:?})"
88                );
89                Expression::Invalid
90            }
91            SyntaxKind::AtKeys => {
92                Expression::from_at_keys_node(node.clone().into(), &mut lookup_ctx)
93            }
94            _ => {
95                debug_assert!(diag.has_errors());
96                Expression::Invalid
97            }
98        };
99        match expr {
100            Expression::DebugHook { expression, .. } => **expression = new_expr,
101            _ => *expr = new_expr,
102        }
103    }
104}
105
106/// Call the visitor for each children of the element recursively, starting with the element itself
107///
108/// The item that is being visited will be pushed to the scope and popped once visitation is over.
109fn recurse_elem_with_scope(
110    elem: &ElementRc,
111    mut scope: ComponentScope,
112    vis: &mut impl FnMut(&ElementRc, &ComponentScope),
113) -> ComponentScope {
114    scope.0.push(elem.clone());
115    vis(elem, &scope);
116    for sub in &elem.borrow().children {
117        scope = recurse_elem_with_scope(sub, scope, vis);
118    }
119    scope.0.pop();
120    scope
121}
122
123pub fn resolve_expressions(
124    doc: &Document,
125    type_loader: &crate::typeloader::TypeLoader,
126    diag: &mut BuildDiagnostics,
127) {
128    for component in doc.inner_components.iter() {
129        recurse_elem_with_scope(
130            &component.root_element,
131            ComponentScope(Vec::new()),
132            &mut |elem, scope| {
133                // Resolve the model expression (of a `for`) with the parent
134                // scope, and before the two-way bindings below so they can
135                // type-check field accesses against the model row type.
136                if elem.borrow().repeated.is_some() {
137                    debug_assert!(scope.0.len() > 1);
138                    let parent_scope = &scope.0[..scope.0.len() - 1];
139                    visit_repeater_model_expression(elem, |expr, property_name, property_type| {
140                        resolve_expression(
141                            elem,
142                            expr,
143                            property_name,
144                            property_type(),
145                            parent_scope,
146                            &doc.local_registry,
147                            type_loader,
148                            diag,
149                        );
150                    });
151                }
152
153                resolve_two_way_bindings_for_element(elem, &scope.0, &doc.local_registry, diag);
154
155                visit_element_expressions_excluding_repeater_model(
156                    elem,
157                    |expr, property_name, property_type| {
158                        resolve_expression(
159                            elem,
160                            expr,
161                            property_name,
162                            property_type(),
163                            &scope.0,
164                            &doc.local_registry,
165                            type_loader,
166                            diag,
167                        );
168                    },
169                );
170            },
171        );
172    }
173}
174
175/// To be used in [`Expression::from_qualified_name_node`] to specify if the lookup is performed
176/// for two ways binding (which happens before the models and other expressions are resolved),
177/// or after that.
178#[derive(Default)]
179enum LookupPhase {
180    #[default]
181    UnspecifiedPhase,
182    ResolvingTwoWayBindings,
183}
184
185impl Expression {
186    pub fn from_binding_expression_node(node: SyntaxNode, ctx: &mut LookupCtx) -> Self {
187        debug_assert_eq!(node.kind(), SyntaxKind::BindingExpression);
188        let e = node
189            .children()
190            .find_map(|n| match n.kind() {
191                SyntaxKind::Expression => Some(Self::from_expression_node(n.into(), ctx)),
192                SyntaxKind::CodeBlock => Some(Self::from_codeblock_node(n.into(), ctx)),
193                _ => None,
194            })
195            .unwrap_or(Self::Invalid);
196        if ctx.property_type == Type::LogicalLength && e.ty() == Type::Percent {
197            // See if a conversion from percentage to length is allowed
198            const RELATIVE_TO_PARENT_PROPERTIES: &[&str] =
199                &["width", "height", "preferred-width", "preferred-height"];
200            let property_name = ctx.property_name.unwrap_or_default();
201            if RELATIVE_TO_PARENT_PROPERTIES.contains(&property_name) {
202                return e;
203            } else {
204                ctx.diag.push_error(
205                    format!(
206                        "Automatic conversion from percentage to length is only possible for the following properties: {}",
207                        RELATIVE_TO_PARENT_PROPERTIES.join(", ")
208                    ),
209                    &node
210                );
211                return Expression::Invalid;
212            }
213        };
214        if !matches!(ctx.property_type, Type::Callback { .. } | Type::Function { .. }) {
215            e.maybe_convert_to(ctx.property_type.clone(), &node, ctx.diag)
216        } else {
217            // Binding to a callback or function shouldn't happen
218            assert!(ctx.diag.has_errors());
219            e
220        }
221    }
222
223    fn from_codeblock_node(node: syntax_nodes::CodeBlock, ctx: &mut LookupCtx) -> Expression {
224        debug_assert_eq!(node.kind(), SyntaxKind::CodeBlock);
225
226        // new scope for locals
227        ctx.local_variables.push(Vec::new());
228
229        let mut statements_or_exprs = node
230            .children()
231            .filter_map(|n| match n.kind() {
232                SyntaxKind::Expression => {
233                    Some((n.clone(), Self::from_expression_node(n.into(), ctx)))
234                }
235                SyntaxKind::ReturnStatement => {
236                    Some((n.clone(), Self::from_return_statement(n.into(), ctx)))
237                }
238                SyntaxKind::LetStatement => {
239                    Some((n.clone(), Self::from_let_statement(n.into(), ctx)))
240                }
241                _ => None,
242            })
243            .collect::<Vec<_>>();
244
245        remove_noop::remove_from_codeblock(&mut statements_or_exprs, ctx.diag);
246
247        let mut statements_or_exprs = statements_or_exprs
248            .into_iter()
249            .map(|(_node, statement_or_expr)| statement_or_expr)
250            .collect::<Vec<_>>();
251
252        let exit_points_and_return_types = statements_or_exprs
253            .iter()
254            .enumerate()
255            .filter_map(|(index, statement_or_expr)| {
256                if index == statements_or_exprs.len()
257                    || matches!(statement_or_expr, Expression::ReturnStatement(..))
258                {
259                    Some((index, statement_or_expr.ty()))
260                } else {
261                    None
262                }
263            })
264            .collect::<Vec<_>>();
265
266        let common_return_type = Self::common_target_type_for_type_list(
267            exit_points_and_return_types.iter().map(|(_, ty)| ty.clone()),
268        );
269
270        exit_points_and_return_types.into_iter().for_each(|(index, _)| {
271            let mut expr = std::mem::replace(&mut statements_or_exprs[index], Expression::Invalid);
272            expr = expr.maybe_convert_to(common_return_type.clone(), &node, ctx.diag);
273            statements_or_exprs[index] = expr;
274        });
275
276        // pop local scope
277        ctx.local_variables.pop();
278
279        Expression::CodeBlock(statements_or_exprs)
280    }
281
282    fn from_let_statement(node: syntax_nodes::LetStatement, ctx: &mut LookupCtx) -> Expression {
283        let name = identifier_text(&node.DeclaredIdentifier()).unwrap_or_default();
284
285        let global_lookup = crate::lookup::global_lookup();
286        if let Some(LookupResult::Expression {
287            expression:
288                Expression::ReadLocalVariable { .. } | Expression::FunctionParameterReference { .. },
289            ..
290        }) = global_lookup.lookup(ctx, &name)
291        {
292            ctx.diag
293                .push_error("Redeclaration of local variables is not allowed".to_string(), &node);
294            return Expression::Invalid;
295        }
296
297        // prefix with "local_" to avoid conflicts
298        let name: SmolStr = format!("local_{name}",).into();
299
300        let value = Self::from_expression_node(node.Expression(), ctx);
301        let ty = match node.Type() {
302            Some(ty) => type_from_node(ty, ctx.diag, ctx.type_register),
303            None => value.ty(),
304        };
305
306        // we can get the last scope exists, because each codeblock creates a new scope and we are inside a codeblock here by necessity
307        ctx.local_variables.last_mut().unwrap().push((name.clone(), ty.clone()));
308
309        let value = Box::new(value.maybe_convert_to(ty.clone(), &node, ctx.diag));
310
311        Expression::StoreLocalVariable { name, value }
312    }
313
314    fn from_return_statement(
315        node: syntax_nodes::ReturnStatement,
316        ctx: &mut LookupCtx,
317    ) -> Expression {
318        let return_type = ctx.return_type().clone();
319        let e = node.Expression();
320        if e.is_none() && !matches!(return_type, Type::Void | Type::Invalid) {
321            ctx.diag.push_error(format!("Must return a value of type '{return_type}'"), &node);
322        }
323        Expression::ReturnStatement(e.map(|n| {
324            Box::new(Self::from_expression_node(n, ctx).maybe_convert_to(
325                return_type,
326                &node,
327                ctx.diag,
328            ))
329        }))
330    }
331
332    fn from_callback_connection(
333        node: syntax_nodes::CallbackConnection,
334        ctx: &mut LookupCtx,
335    ) -> Expression {
336        ctx.arguments =
337            node.DeclaredIdentifier().map(|x| identifier_text(&x).unwrap_or_default()).collect();
338        if let Some(code_block_node) = node.CodeBlock() {
339            Self::from_codeblock_node(code_block_node, ctx).maybe_convert_to(
340                ctx.return_type().clone(),
341                &node,
342                ctx.diag,
343            )
344        } else if let Some(expr_node) = node.Expression() {
345            Self::from_expression_node(expr_node, ctx).maybe_convert_to(
346                ctx.return_type().clone(),
347                &node,
348                ctx.diag,
349            )
350        } else {
351            Expression::Invalid
352        }
353    }
354
355    fn from_function(node: syntax_nodes::Function, ctx: &mut LookupCtx) -> Expression {
356        ctx.arguments = node
357            .ArgumentDeclaration()
358            .map(|x| identifier_text(&x.DeclaredIdentifier()).unwrap_or_default())
359            .collect();
360        let Some(code_block) = node.CodeBlock() else {
361            debug_assert!(ctx.diag.has_errors());
362            return Expression::Invalid;
363        };
364        Self::from_codeblock_node(code_block, ctx).maybe_convert_to(
365            ctx.return_type().clone(),
366            &node,
367            ctx.diag,
368        )
369    }
370
371    pub fn from_expression_node(node: syntax_nodes::Expression, ctx: &mut LookupCtx) -> Self {
372        node.children_with_tokens()
373            .find_map(|child| match child {
374                NodeOrToken::Node(node) => match node.kind() {
375                    SyntaxKind::Expression => Some(Self::from_expression_node(node.into(), ctx)),
376                    SyntaxKind::AtImageUrl => {
377                        #[cfg(feature = "slint-sc")]
378                        ctx.diag.slint_sc_error("@image-url() expressions are", &node);
379                        Some(Self::from_at_image_url_node(node.into(), ctx))
380                    }
381                    SyntaxKind::AtGradient => {
382                        #[cfg(feature = "slint-sc")]
383                        ctx.diag.slint_sc_error("@gradient expressions are", &node);
384                        Some(Self::from_at_gradient(node.into(), ctx))
385                    }
386                    SyntaxKind::AtTr => {
387                        #[cfg(feature = "slint-sc")]
388                        ctx.diag.slint_sc_error("@tr() expressions are", &node);
389                        Some(Self::from_at_tr(node.into(), ctx))
390                    }
391                    SyntaxKind::AtMarkdown => {
392                        #[cfg(feature = "slint-sc")]
393                        ctx.diag.slint_sc_error("@markdown() expressions are", &node);
394                        Some(Self::from_at_markdown(node.into(), ctx))
395                    }
396                    SyntaxKind::AtKeys => {
397                        #[cfg(feature = "slint-sc")]
398                        ctx.diag.slint_sc_error("@keys() expressions are", &node);
399                        Some(Self::from_at_keys_node(node.into(), ctx))
400                    }
401                    SyntaxKind::QualifiedName => {
402                        #[cfg(feature = "slint-sc")]
403                        ctx.diag.slint_sc_error("Identifier references are", &node);
404                        Some(Self::from_qualified_name_node(node.clone().into(), ctx))
405                    }
406                    SyntaxKind::FunctionCallExpression => {
407                        #[cfg(feature = "slint-sc")]
408                        ctx.diag.slint_sc_error("Function calls are", &node);
409                        Some(Self::from_function_call_node(node.into(), ctx))
410                    }
411                    SyntaxKind::MemberAccess => {
412                        #[cfg(feature = "slint-sc")]
413                        ctx.diag.slint_sc_error("Member access expressions are", &node);
414                        Some(Self::from_member_access_node(node.into(), ctx))
415                    }
416                    SyntaxKind::IndexExpression => {
417                        #[cfg(feature = "slint-sc")]
418                        ctx.diag.slint_sc_error("Index expressions are", &node);
419                        Some(Self::from_index_expression_node(node.into(), ctx))
420                    }
421                    SyntaxKind::SelfAssignment => {
422                        #[cfg(feature = "slint-sc")]
423                        ctx.diag.slint_sc_error("Self-assignment expressions are", &node);
424                        Some(Self::from_self_assignment_node(node.into(), ctx))
425                    }
426                    SyntaxKind::BinaryExpression => {
427                        #[cfg(feature = "slint-sc")]
428                        ctx.diag.slint_sc_error("Binary expressions are", &node);
429                        Some(Self::from_binary_expression_node(node.into(), ctx))
430                    }
431                    SyntaxKind::UnaryOpExpression => {
432                        #[cfg(feature = "slint-sc")]
433                        ctx.diag.slint_sc_error("Unary expressions are", &node);
434                        Some(Self::from_unaryop_expression_node(node.into(), ctx))
435                    }
436                    SyntaxKind::ConditionalExpression => {
437                        #[cfg(feature = "slint-sc")]
438                        ctx.diag.slint_sc_error("Conditional expressions are", &node);
439                        Some(Self::from_conditional_expression_node(node.into(), ctx))
440                    }
441                    SyntaxKind::ObjectLiteral => {
442                        #[cfg(feature = "slint-sc")]
443                        ctx.diag.slint_sc_error("Object literal expressions are", &node);
444                        Some(Self::from_object_literal_node(node.into(), ctx))
445                    }
446                    SyntaxKind::Array => {
447                        #[cfg(feature = "slint-sc")]
448                        ctx.diag.slint_sc_error("Array expressions are", &node);
449                        Some(Self::from_array_node(node.into(), ctx))
450                    }
451                    SyntaxKind::CodeBlock => {
452                        #[cfg(feature = "slint-sc")]
453                        ctx.diag.slint_sc_error("Code blocks are", &node);
454                        Some(Self::from_codeblock_node(node.into(), ctx))
455                    }
456                    SyntaxKind::StringTemplate => {
457                        #[cfg(feature = "slint-sc")]
458                        ctx.diag.slint_sc_error("String interpolation expressions are", &node);
459                        Some(Self::from_string_template_node(node.into(), ctx))
460                    }
461                    _ => None,
462                },
463                NodeOrToken::Token(token) => match token.kind() {
464                    SyntaxKind::StringLiteral => {
465                        #[cfg(feature = "slint-sc")]
466                        ctx.diag.slint_sc_error("String literals are", &token);
467                        Some(
468                            crate::literals::unescape_string_reporting(
469                                Some(&token),
470                                ctx.diag,
471                                &token,
472                            )
473                            .map(Self::StringLiteral)
474                            .unwrap_or(Self::Invalid),
475                        )
476                    }
477                    SyntaxKind::NumberLiteral => {
478                        #[cfg(feature = "slint-sc")]
479                        ctx.diag.slint_sc_error("Number literals are", &token);
480                        Some(
481                            crate::literals::parse_number_literal(token.text().into())
482                                .unwrap_or_else(|e| {
483                                    ctx.diag.push_error(e.to_string(), &node);
484                                    Self::Invalid
485                                }),
486                        )
487                    }
488                    SyntaxKind::ColorLiteral => {
489                        #[cfg(feature = "slint-sc")]
490                        ctx.diag.slint_sc_error("Color literals are", &token);
491                        Some(
492                            i_slint_common::color_parsing::parse_color_literal(token.text())
493                                .map(|i| Expression::Cast {
494                                    from: Box::new(Expression::NumberLiteral(i as _, Unit::None)),
495                                    to: Type::Color,
496                                })
497                                .unwrap_or_else(|| {
498                                    ctx.diag.push_error("Invalid color literal".into(), &node);
499                                    Self::Invalid
500                                }),
501                        )
502                    }
503
504                    _ => None,
505                },
506            })
507            .unwrap_or(Self::Invalid)
508    }
509
510    fn from_at_image_url_node(node: syntax_nodes::AtImageUrl, ctx: &mut LookupCtx) -> Self {
511        let Some(s) = crate::literals::unescape_string_reporting(
512            node.child_token(SyntaxKind::StringLiteral).as_ref(),
513            ctx.diag,
514            &node,
515        ) else {
516            return Self::Invalid;
517        };
518
519        if s.is_empty() {
520            return Expression::ImageReference {
521                resource_ref: ImageReference::None,
522                source_location: Some(node.to_source_location()),
523                nine_slice: None,
524            };
525        }
526
527        let resource_ref = if s.starts_with("data:") {
528            ImageReference::AbsolutePath(s)
529        } else {
530            let absolute_source_path = {
531                let path = std::path::Path::new(&s);
532                if crate::pathutils::is_absolute(path) {
533                    s
534                } else {
535                    ctx.type_loader
536                        .and_then(|loader| {
537                            loader.resolve_import_path(Some(&(*node).clone().into()), &s)
538                        })
539                        .map(|i| i.0.to_string_lossy().into())
540                        .unwrap_or_else(|| {
541                            crate::pathutils::join(
542                                &crate::pathutils::dirname(node.source_file.path()),
543                                path,
544                            )
545                            .map(|p| p.to_string_lossy().into())
546                            .unwrap_or(s.clone())
547                        })
548                }
549            };
550            ImageReference::AbsolutePath(absolute_source_path)
551        };
552
553        let nine_slice = node
554            .children_with_tokens()
555            .filter_map(|n| n.into_token())
556            .filter(|t| t.kind() == SyntaxKind::NumberLiteral)
557            .map(|arg| {
558                arg.text().parse().unwrap_or_else(|err: std::num::ParseIntError| {
559                    match err.kind() {
560                        IntErrorKind::PosOverflow | IntErrorKind::NegOverflow => {
561                            ctx.diag.push_error("Number too big".into(), &arg)
562                        }
563                        IntErrorKind::InvalidDigit => ctx.diag.push_error(
564                            "Border widths of a nine-slice can't have units".into(),
565                            &arg,
566                        ),
567                        _ => ctx.diag.push_error("Cannot parse number literal".into(), &arg),
568                    };
569                    0u16
570                })
571            })
572            .collect::<Vec<u16>>();
573
574        let nine_slice = match nine_slice.as_slice() {
575            [x] => Some([*x, *x, *x, *x]),
576            [x, y] => Some([*x, *y, *x, *y]),
577            [x, y, z, w] => Some([*x, *y, *z, *w]),
578            [] => None,
579            _ => {
580                assert!(ctx.diag.has_errors());
581                None
582            }
583        };
584
585        Expression::ImageReference {
586            resource_ref,
587            source_location: Some(node.to_source_location()),
588            nine_slice,
589        }
590    }
591
592    pub fn from_at_gradient(node: syntax_nodes::AtGradient, ctx: &mut LookupCtx) -> Self {
593        enum GradKind {
594            Linear {
595                angle: Box<Expression>,
596            },
597            Radial {
598                center: Option<(Box<Expression>, Box<Expression>)>,
599                radius: Option<Box<Expression>>,
600            },
601            Conic {
602                from_angle: Box<Expression>,
603                center: Option<(Box<Expression>, Box<Expression>)>,
604            },
605        }
606
607        let all_subs: Vec<_> = node
608            .children_with_tokens()
609            .filter(|n| matches!(n.kind(), SyntaxKind::Comma | SyntaxKind::Expression))
610            .collect();
611
612        let grad_token = node.child_token(SyntaxKind::Identifier).unwrap();
613        let grad_text = grad_token.text();
614
615        // Helper: parse two consecutive length expressions at positions idx and idx+1
616        let parse_at_center = |idx: usize,
617                               ctx: &mut LookupCtx|
618         -> Option<(Box<Expression>, Box<Expression>)> {
619            let cx_node = all_subs.get(idx)?;
620            let cy_node = all_subs.get(idx + 1)?;
621            if cx_node.kind() != SyntaxKind::Expression || cy_node.kind() != SyntaxKind::Expression
622            {
623                return None;
624            }
625            let cx_syn = syntax_nodes::Expression::from(cx_node.as_node().unwrap().clone());
626            let cy_syn = syntax_nodes::Expression::from(cy_node.as_node().unwrap().clone());
627            let cx =
628                Box::new(Expression::from_expression_node(cx_syn.clone(), ctx).maybe_convert_to(
629                    Type::LogicalLength,
630                    &cx_syn,
631                    ctx.diag,
632                ));
633            let cy =
634                Box::new(Expression::from_expression_node(cy_syn.clone(), ctx).maybe_convert_to(
635                    Type::LogicalLength,
636                    &cy_syn,
637                    ctx.diag,
638                ));
639            Some((cx, cy))
640        };
641
642        let (grad_kind, stops_start_idx) = if grad_text.starts_with("linear") {
643            let angle_expr = match all_subs.first() {
644                Some(e) if e.kind() == SyntaxKind::Expression => {
645                    syntax_nodes::Expression::from(e.as_node().unwrap().clone())
646                }
647                _ => {
648                    ctx.diag.push_error("Expected angle expression".into(), &node);
649                    return Expression::Invalid;
650                }
651            };
652            if all_subs.get(1).is_none_or(|s| s.kind() != SyntaxKind::Comma) {
653                ctx.diag.push_error(
654                    "Angle expression must be an angle followed by a comma".into(),
655                    &node,
656                );
657                return Expression::Invalid;
658            }
659            let angle = Box::new(
660                Expression::from_expression_node(angle_expr.clone(), ctx).maybe_convert_to(
661                    Type::Angle,
662                    &angle_expr,
663                    ctx.diag,
664                ),
665            );
666            (GradKind::Linear { angle }, 2)
667        } else if grad_text.starts_with("radial") {
668            if !all_subs.first().is_some_and(|n| {
669                matches!(n, NodeOrToken::Node(node) if node.text().to_string().trim() == "circle")
670            }) {
671                ctx.diag.push_error("Expected 'circle': currently, only @radial-gradient(circle, ...) are supported".into(), &node);
672                return Expression::Invalid;
673            }
674            // CSS syntax: `circle [<radius>] [at <x> <y>]` — radius before center, no keyword.
675            let mut idx = 1;
676
677            // Parse optional radius (a length expression that is not the "at" keyword).
678            // Only consume the node when it actually resolves to a length-compatible type;
679            // a colour keyword like `blue` must not silently become a failed conversion.
680            let radius = if all_subs.get(idx).is_some_and(|n| {
681                n.kind() == SyntaxKind::Expression
682                    && !matches!(n, NodeOrToken::Node(node) if node.text().to_string().trim() == "at")
683            }) {
684                let r = all_subs.get(idx).unwrap();
685                let r_syn = syntax_nodes::Expression::from(r.as_node().unwrap().clone());
686                let expr = Expression::from_expression_node(r_syn.clone(), ctx);
687                if matches!(expr.ty(), Type::LogicalLength | Type::Float32 | Type::Int32) {
688                    let radius = Box::new(
689                        expr.maybe_convert_to(Type::LogicalLength, &r_syn, ctx.diag),
690                    );
691                    idx += 1;
692                    Some(radius)
693                } else {
694                    None
695                }
696            } else {
697                None
698            };
699
700            // Parse optional "at <x> <y>".
701            let center = if all_subs.get(idx).is_some_and(
702                |n| matches!(n, NodeOrToken::Node(node) if node.text().to_string().trim() == "at"),
703            ) {
704                let center = parse_at_center(idx + 1, ctx);
705                if center.is_none() {
706                    ctx.diag.push_error(
707                        "Expected two length values after 'at'".into(),
708                        all_subs.get(idx).unwrap(),
709                    );
710                    return Expression::Invalid;
711                }
712                idx += 3; // consumed "at x y"
713                center
714            } else {
715                None
716            };
717
718            let stops_start = if all_subs.get(idx).is_none() {
719                idx
720            } else if all_subs.get(idx).is_some_and(|s| s.kind() == SyntaxKind::Comma) {
721                idx + 1
722            } else {
723                if idx == 1 {
724                    let message = "'circle' must be followed by a comma, a radius, or 'at'".into();
725                    if let Some(error_node) = all_subs.get(idx) {
726                        ctx.diag.push_error(message, error_node);
727                    } else {
728                        ctx.diag.push_error(message, &node);
729                    }
730                } else {
731                    ctx.diag
732                        .push_error("gradient header must be followed by a comma".into(), &node);
733                }
734                return Expression::Invalid;
735            };
736            (GradKind::Radial { center, radius }, stops_start)
737        } else if grad_text.starts_with("conic") {
738            // Parse optional "from <angle>" and/or "at <x> <y>" before the comma
739            let mut idx = 0usize;
740            let from_angle = if all_subs.first().is_some_and(|n| {
741                matches!(n, NodeOrToken::Node(node) if node.text().to_string().trim() == "from")
742            }) {
743                // Parse "from <angle>" syntax
744                let angle_expr = match all_subs.get(1) {
745                    Some(e) if e.kind() == SyntaxKind::Expression => {
746                        syntax_nodes::Expression::from(e.as_node().unwrap().clone())
747                    }
748                    _ => {
749                        ctx.diag.push_error("Expected angle expression after 'from'".into(), &node);
750                        return Expression::Invalid;
751                    }
752                };
753                let angle = Box::new(
754                    Expression::from_expression_node(angle_expr.clone(), ctx).maybe_convert_to(
755                        Type::Angle,
756                        &angle_expr,
757                        ctx.diag,
758                    ),
759                );
760                idx = 2; // consumed "from" and angle
761                angle
762            } else {
763                // Default to 0deg when "from" is omitted
764                Box::new(Expression::NumberLiteral(0., Unit::Deg))
765            };
766
767            // Parse optional "at <x> <y>" after the optional "from <angle>"
768            let center = if all_subs.get(idx).is_some_and(
769                |n| matches!(n, NodeOrToken::Node(node) if node.text().to_string().trim() == "at"),
770            ) {
771                let center = parse_at_center(idx + 1, ctx);
772                if center.is_none() {
773                    ctx.diag.push_error(
774                        "Expected two length values after 'at'".into(),
775                        all_subs.get(idx).unwrap(),
776                    );
777                    return Expression::Invalid;
778                }
779                idx += 3; // consumed "at", x, y
780                center
781            } else {
782                None
783            };
784
785            // Expect a comma after the header (if any header elements were present)
786            if (idx > 0) && all_subs.get(idx).is_none_or(|s| s.kind() != SyntaxKind::Comma) {
787                ctx.diag.push_error("gradient header must be followed by a comma".into(), &node);
788                return Expression::Invalid;
789            }
790            let stops_start = if idx > 0 { idx + 1 } else { 0 };
791            (GradKind::Conic { from_angle, center }, stops_start)
792        } else {
793            // Parser should have ensured we have one of the linear, radial or conic gradient
794            panic!("Not a gradient {grad_text:?}");
795        };
796
797        let mut stops = Vec::new();
798        enum Stop {
799            Empty,
800            Color(Expression),
801            Finished,
802        }
803        let mut current_stop = Stop::Empty;
804        for n in all_subs.iter().skip(stops_start_idx) {
805            if n.kind() == SyntaxKind::Comma {
806                match std::mem::replace(&mut current_stop, Stop::Empty) {
807                    Stop::Empty => {
808                        ctx.diag.push_error("Expected expression".into(), n);
809                        break;
810                    }
811                    Stop::Finished => {}
812                    Stop::Color(col) => stops.push((
813                        col,
814                        if stops.is_empty() {
815                            Expression::NumberLiteral(0., Unit::None)
816                        } else {
817                            Expression::Invalid
818                        },
819                    )),
820                }
821            } else {
822                // To facilitate color literal conversion, adjust the expected return type.
823                let e = {
824                    let old_property_type = std::mem::replace(&mut ctx.property_type, Type::Color);
825                    let e =
826                        Expression::from_expression_node(n.as_node().unwrap().clone().into(), ctx);
827                    ctx.property_type = old_property_type;
828                    e
829                };
830                match std::mem::replace(&mut current_stop, Stop::Finished) {
831                    Stop::Empty => {
832                        current_stop = Stop::Color(e.maybe_convert_to(Type::Color, n, ctx.diag))
833                    }
834                    Stop::Finished => {
835                        ctx.diag.push_error("Expected comma".into(), n);
836                        break;
837                    }
838                    Stop::Color(col) => {
839                        let stop_type = match &grad_kind {
840                            GradKind::Conic { .. } => Type::Angle,
841                            _ => Type::Float32,
842                        };
843                        stops.push((col, e.maybe_convert_to(stop_type, n, ctx.diag)))
844                    }
845                }
846            }
847        }
848        match current_stop {
849            Stop::Color(col) => stops.push((col, Expression::NumberLiteral(1., Unit::None))),
850            Stop::Empty => {
851                if let Some((_, e @ Expression::Invalid)) = stops.last_mut() {
852                    *e = Expression::NumberLiteral(1., Unit::None)
853                }
854            }
855            Stop::Finished => (),
856        };
857
858        // Fix the stop so each has a position.
859        let mut start = 0;
860        while start < stops.len() {
861            start += match stops[start..].iter().position(|s| matches!(s.1, Expression::Invalid)) {
862                Some(p) => p,
863                None => break,
864            };
865            let (before, rest) = stops.split_at_mut(start);
866            let pos =
867                rest.iter().position(|s| !matches!(s.1, Expression::Invalid)).unwrap_or(rest.len());
868            if pos > 0 && pos < rest.len() {
869                let (middle, after) = rest.split_at_mut(pos);
870                let begin = before
871                    .last()
872                    .map(|s| &s.1)
873                    .unwrap_or(&Expression::NumberLiteral(1., Unit::None));
874                let end = &after.first().expect("The last should never be invalid").1;
875                for (i, (_, e)) in middle.iter_mut().enumerate() {
876                    debug_assert!(matches!(e, Expression::Invalid));
877                    // e = begin + (i+1) * (end - begin) / (pos+1)
878                    *e = Expression::BinaryExpression {
879                        lhs: Box::new(begin.clone()),
880                        rhs: Box::new(Expression::BinaryExpression {
881                            lhs: Box::new(Expression::BinaryExpression {
882                                lhs: Box::new(Expression::NumberLiteral(i as f64 + 1., Unit::None)),
883                                rhs: Box::new(Expression::BinaryExpression {
884                                    lhs: Box::new(end.clone()),
885                                    rhs: Box::new(begin.clone()),
886                                    op: '-',
887                                }),
888                                op: '*',
889                            }),
890                            rhs: Box::new(Expression::NumberLiteral(pos as f64 + 1., Unit::None)),
891                            op: '/',
892                        }),
893                        op: '+',
894                    };
895                }
896            }
897            start += pos + 1;
898        }
899
900        match grad_kind {
901            GradKind::Linear { angle } => Expression::LinearGradient { angle, stops },
902            GradKind::Radial { center, radius } => {
903                Expression::RadialGradient { center, radius, stops }
904            }
905            GradKind::Conic { from_angle, center } => {
906                // Normalize stop angles to 0-1 range by dividing by 360deg
907                let normalized_stops = stops
908                    .into_iter()
909                    .map(|(color, angle_expr)| {
910                        let angle_typed = angle_expr.maybe_convert_to(Type::Angle, &node, ctx.diag);
911                        let normalized_pos = Expression::BinaryExpression {
912                            lhs: Box::new(angle_typed),
913                            rhs: Box::new(Expression::NumberLiteral(360., Unit::Deg)),
914                            op: '/',
915                        };
916                        (color, normalized_pos)
917                    })
918                    .collect();
919
920                // Convert from_angle to degrees (don't normalize to 0-1)
921                let from_angle_degrees = from_angle.maybe_convert_to(Type::Angle, &node, ctx.diag);
922
923                Expression::ConicGradient {
924                    from_angle: Box::new(from_angle_degrees),
925                    center,
926                    stops: normalized_stops,
927                }
928            }
929        }
930    }
931
932    fn from_at_markdown(node: syntax_nodes::AtMarkdown, ctx: &mut LookupCtx) -> Expression {
933        let mut raw_exprs: Vec<(Expression, crate::parser::SyntaxNode)> = Vec::new();
934        let mut source_map = crate::literals::StringLiteralSourceMap::new();
935        use i_slint_common::styled_text::MARKDOWN_INTERPOLATION_PLACEHOLDER as PLACEHOLDER;
936
937        let push_and_check =
938            |token: &crate::parser::SyntaxToken,
939             source_map: &mut crate::literals::StringLiteralSourceMap,
940             diag: &mut crate::diagnostics::BuildDiagnostics| {
941                let before = source_map.as_str().len();
942                source_map.push(token, diag);
943                for (offset, _) in source_map.as_str()[before..].match_indices(PLACEHOLDER) {
944                    source_map.report(
945                        diag,
946                        "\\u{e541} is reserved for @markdown interpolation".into(),
947                        (before + offset)..(before + offset + PLACEHOLDER.len_utf8()),
948                        &node,
949                    );
950                }
951            };
952
953        for n in node.children_with_tokens() {
954            if n.kind() == SyntaxKind::StringLiteral {
955                push_and_check(n.as_token().unwrap(), &mut source_map, ctx.diag);
956            } else if n.kind() == SyntaxKind::StringTemplate {
957                for n in n.as_node().unwrap().children_with_tokens() {
958                    if n.kind() == SyntaxKind::StringLiteral {
959                        push_and_check(n.as_token().unwrap(), &mut source_map, ctx.diag);
960                    } else if n.kind() == SyntaxKind::Expression {
961                        let expr_node = n.into_node().unwrap();
962                        let expr = Expression::from_expression_node(expr_node.clone().into(), ctx);
963                        source_map.push_raw_char(PLACEHOLDER, expr_node.to_source_location());
964                        raw_exprs.push((expr, expr_node));
965                    }
966                }
967            }
968        }
969
970        let markdown = source_map.as_str();
971        let placeholder_positions: Vec<usize> =
972            markdown.match_indices(PLACEHOLDER).map(|(pos, _)| pos).collect();
973
974        // Replace each placeholder with an ASCII string of the same byte length
975        // and re-parse.
976        // pulldown_cmark treats `<zzz>` as inline HTML (unlike the private-use char),
977        // so errors reveal interpolations inside HTML tag structure.
978        const PROBE: &str = "zzz";
979        const _: () = assert!(PROBE.len() == PLACEHOLDER.len_utf8());
980        let probe = markdown.replace(PLACEHOLDER, PROBE);
981
982        let (_, parse_errors) = i_slint_common::styled_text::parse_interpolated::<
983            &[i_slint_common::styled_text::StyledTextParagraph],
984        >(&probe, &[]);
985
986        let mut color_indices = std::collections::BTreeSet::new();
987
988        for e in &parse_errors {
989            let placeholders_in_range = |r: &core::ops::Range<usize>| -> Vec<usize> {
990                placeholder_positions
991                    .iter()
992                    .enumerate()
993                    .filter(|(_, pos)| **pos >= r.start && **pos < r.end)
994                    .map(|(idx, _)| idx)
995                    .collect()
996            };
997
998            if let Some(r) = e.range() {
999                let hits = placeholders_in_range(&r);
1000
1001                // InvalidColor("zzz") at a placeholder position →
1002                // this interpolation is a color attribute value.
1003                if i_slint_common::styled_text::invalid_color_value(e) == Some(PROBE)
1004                    && !hits.is_empty()
1005                {
1006                    color_indices.extend(hits);
1007                    continue;
1008                }
1009
1010                // Other errors overlapping a placeholder mean interpolation
1011                // inside HTML tag structure.
1012                if !hits.is_empty() {
1013                    source_map.report(
1014                        ctx.diag,
1015                        "Interpolation (`\\{}`) is not allowed inside HTML tags".into(),
1016                        r,
1017                        &node,
1018                    );
1019                } else {
1020                    source_map.report(ctx.diag, e.to_string(), r, &node);
1021                }
1022            } else {
1023                ctx.diag.push_error(e.to_string(), &node);
1024            }
1025        }
1026
1027        let values = raw_exprs
1028            .into_iter()
1029            .enumerate()
1030            .map(|(idx, (expr, expr_node))| {
1031                if color_indices.contains(&idx) {
1032                    // Color placeholder: require Color type
1033                    Expression::FunctionCall {
1034                        function: BuiltinFunction::ColorToStyledText.into(),
1035                        arguments: vec![expr.maybe_convert_to(Type::Color, &expr_node, ctx.diag)],
1036                        source_location: Some(expr_node.to_source_location()),
1037                    }
1038                } else if expr.ty() == Type::StyledText {
1039                    expr
1040                } else {
1041                    Expression::FunctionCall {
1042                        function: BuiltinFunction::StringToStyledText.into(),
1043                        arguments: vec![expr.maybe_convert_to(Type::String, &expr_node, ctx.diag)],
1044                        source_location: Some(expr_node.to_source_location()),
1045                    }
1046                }
1047            })
1048            .collect();
1049
1050        Expression::FunctionCall {
1051            function: BuiltinFunction::ParseMarkdown.into(),
1052            arguments: vec![
1053                Expression::StringLiteral(source_map.into_string().into()),
1054                Expression::Array { element_ty: Type::StyledText, values },
1055            ],
1056            source_location: Some(node.to_source_location()),
1057        }
1058    }
1059
1060    fn from_at_tr(node: syntax_nodes::AtTr, ctx: &mut LookupCtx) -> Expression {
1061        let mut source_map = crate::literals::StringLiteralSourceMap::new();
1062        let Some(string_token) = node.child_token(SyntaxKind::StringLiteral) else {
1063            ctx.diag.push_error("Cannot parse string literal".into(), &node);
1064            return Expression::Invalid;
1065        };
1066        if !source_map.push(&string_token, ctx.diag) {
1067            return Expression::Invalid;
1068        }
1069        let string: SmolStr = source_map.as_str().into();
1070        let context = node.TrContext().map(|n| {
1071            crate::literals::unescape_string_reporting(
1072                n.child_token(SyntaxKind::StringLiteral).as_ref(),
1073                ctx.diag,
1074                &n,
1075            )
1076            .unwrap_or_default()
1077        });
1078        let plural = node.TrPlural().map(|pl| {
1079            let s = crate::literals::unescape_string_reporting(
1080                pl.child_token(SyntaxKind::StringLiteral).as_ref(),
1081                ctx.diag,
1082                &pl,
1083            )
1084            .unwrap_or_default();
1085            let n = pl.Expression();
1086            let expr = Expression::from_expression_node(n.clone(), ctx).maybe_convert_to(
1087                Type::Int32,
1088                &n,
1089                ctx.diag,
1090            );
1091            (s, expr)
1092        });
1093
1094        let domain = ctx
1095            .type_loader
1096            .and_then(|tl| tl.compiler_config.translation_domain.clone())
1097            .unwrap_or_default();
1098
1099        let subs = node.Expression().map(|n| {
1100            Expression::from_expression_node(n.clone(), ctx).maybe_convert_to(
1101                Type::String,
1102                &n,
1103                ctx.diag,
1104            )
1105        });
1106        let values = subs.collect::<Vec<_>>();
1107
1108        // check format string
1109        {
1110            let mut arg_idx = 0;
1111            let mut pos_max = 0;
1112            let mut pos = 0;
1113            let mut has_n = false;
1114            while let Some(mut p) = string[pos..].find(['{', '}']) {
1115                if string.len() - pos < p + 1 {
1116                    p += pos;
1117                    source_map.report(
1118                        ctx.diag,
1119                        "Unescaped trailing '{' in format string. Escape '{' with '{{'".into(),
1120                        p..p + 1,
1121                        &node,
1122                    );
1123                    break;
1124                }
1125                p += pos;
1126
1127                // Skip escaped }
1128                if string.get(p..=p) == Some("}") {
1129                    if string.get(p + 1..=p + 1) == Some("}") {
1130                        pos = p + 2;
1131                        continue;
1132                    } else {
1133                        source_map.report(
1134                            ctx.diag,
1135                            "Unescaped '}' in format string. Escape '}' with '}}'".into(),
1136                            p..p + 1,
1137                            &node,
1138                        );
1139                        break;
1140                    }
1141                }
1142
1143                // Skip escaped {
1144                if string.get(p + 1..=p + 1) == Some("{") {
1145                    pos = p + 2;
1146                    continue;
1147                }
1148
1149                // Find the argument
1150                let end = if let Some(end) = string[p..].find('}') {
1151                    end + p
1152                } else {
1153                    source_map.report(
1154                        ctx.diag,
1155                        "Unterminated placeholder in format string. '{' must be escaped with '{{'"
1156                            .into(),
1157                        p..string.len(),
1158                        &node,
1159                    );
1160                    break;
1161                };
1162                let argument = &string[p + 1..end];
1163                if argument.is_empty() {
1164                    arg_idx += 1;
1165                } else if let Ok(n) = argument.parse::<u16>() {
1166                    pos_max = pos_max.max(n as usize + 1);
1167                } else if argument == "n" {
1168                    has_n = true;
1169                    if plural.is_none() {
1170                        source_map.report(
1171                            ctx.diag,
1172                            "`{n}` placeholder can only be found in plural form".into(),
1173                            p..end + 1,
1174                            &node,
1175                        );
1176                    }
1177                } else {
1178                    source_map.report(
1179                        ctx.diag,
1180                        "Invalid '{...}' placeholder in format string. The placeholder must be a number, or braces must be escaped with '{{' and '}}'".into(),
1181                        p..end + 1,
1182                        &node,
1183                    );
1184                    break;
1185                };
1186                pos = end + 1;
1187            }
1188            if arg_idx > 0 && pos_max > 0 {
1189                ctx.diag.push_error(
1190                    "Cannot mix positional and non-positional placeholder in format string".into(),
1191                    &node,
1192                );
1193            } else if arg_idx > values.len() || pos_max > values.len() {
1194                let num = arg_idx.max(pos_max);
1195                let note = if !has_n && plural.is_some() {
1196                    ". Note: use `{n}` for the argument after '%'"
1197                } else {
1198                    ""
1199                };
1200                ctx.diag.push_error(
1201                    format!("Format string contains {num} placeholders, but only {} extra arguments were given{note}", values.len()),
1202                    &node,
1203                );
1204            }
1205        }
1206
1207        let plural =
1208            plural.unwrap_or((SmolStr::default(), Expression::NumberLiteral(1., Unit::None)));
1209
1210        let context = context.or_else(|| {
1211            if !ctx.type_loader.is_some_and(|tl| {
1212                tl.compiler_config.default_translation_context
1213                    == crate::DefaultTranslationContext::None
1214            }) {
1215                // Get the component name as a default
1216                ctx.component_scope
1217                    .first()
1218                    .and_then(|e| e.borrow().enclosing_component.upgrade())
1219                    .map(|c| c.id.clone())
1220            } else {
1221                None
1222            }
1223        });
1224
1225        Expression::FunctionCall {
1226            function: BuiltinFunction::Translate.into(),
1227            arguments: vec![
1228                Expression::StringLiteral(string),
1229                Expression::StringLiteral(context.unwrap_or_default()),
1230                Expression::StringLiteral(domain.into()),
1231                Expression::Array { element_ty: Type::String, values },
1232                plural.1,
1233                Expression::StringLiteral(plural.0),
1234            ],
1235            source_location: Some(node.to_source_location()),
1236        }
1237    }
1238
1239    pub fn from_at_keys_node(node: syntax_nodes::AtKeys, ctx: &mut LookupCtx) -> Self {
1240        let mut keys = langtype::Keys::default();
1241
1242        let mut key_code: Option<(SmolStr, ShiftBehavior, NodeOrToken)> = None;
1243
1244        let idents_and_questions: Vec<_> = node
1245            .children_with_tokens()
1246            .filter(|n| matches!(n.kind(), SyntaxKind::Identifier | SyntaxKind::Question))
1247            // The first identifier is always `keys`
1248            .skip(1)
1249            .collect();
1250
1251        for (index, ident_or_question) in idents_and_questions.iter().enumerate() {
1252            if ident_or_question.kind() == SyntaxKind::Question {
1253                continue;
1254            }
1255            let identifier = ident_or_question;
1256
1257            let is_question = || -> bool {
1258                matches!(
1259                    idents_and_questions.get(index + 1).map(NodeOrToken::kind),
1260                    Some(SyntaxKind::Question)
1261                )
1262            };
1263
1264            match identifier.as_token().unwrap().text() {
1265                "Alt" => {
1266                    if is_question() {
1267                        keys.ignore_alt = true;
1268                    } else {
1269                        keys.modifiers.alt = true;
1270                    }
1271                }
1272                "Control" => keys.modifiers.control = true,
1273                "Meta" => keys.modifiers.meta = true,
1274                "Shift" => {
1275                    if is_question() {
1276                        keys.ignore_shift = true;
1277                    } else {
1278                        keys.modifiers.shift = true;
1279                    }
1280                }
1281                key_name => {
1282                    if let Some((key, shiftbehavior)) = lookup_key_name(key_name) {
1283                        key_code = Some((
1284                            SmolStr::from_iter(core::iter::once(key)),
1285                            shiftbehavior,
1286                            identifier.clone(),
1287                        ))
1288                    } else {
1289                        // TODO: This should suggest more kinds of close matches
1290                        let uppercased = key_name.to_uppercase();
1291                        let hint = if lookup_key_name(&uppercased).is_some() {
1292                            // common case: @keys(Control+a) instead of @keys(Control+A)
1293                            format!("Use uppercase {uppercased} instead")
1294                        } else {
1295                            format!("Consider using \"{key_name}\"")
1296                        };
1297                        ctx.diag.push_error(
1298                            format!("{key_name} not defined in the Keys namespace\n({hint})"),
1299                            identifier,
1300                        );
1301                        keys.modifiers = KeyboardModifiers::default();
1302                        break;
1303                    }
1304                }
1305            }
1306        }
1307
1308        // Handle localization issues regarding shift per-keycode
1309        // This only applies to keys that are in the Key namespace
1310        if let Some((key_code, shift_behavior, node)) = key_code {
1311            match shift_behavior {
1312                ShiftBehavior::LocalizedShiftable { shifted_hint } => {
1313                    if keys.ignore_shift {
1314                        ctx.diag.push_warning(
1315                            format!(
1316                                "{name} already implies Shift? (remove Shift?)",
1317                                name = node.as_token().unwrap().text()
1318                            ),
1319                            &node,
1320                        );
1321                    }
1322                    keys.ignore_shift = true;
1323                    if keys.modifiers.shift {
1324                        let shifted_hint = lookup_key_name(shifted_hint).map(|(shifted_code, _shift_behavior)|
1325                            format!("\nConsider using {shifted_hint} to match when the user types '{shifted_code}'")
1326                        ).unwrap_or_default();
1327
1328                        ctx.diag.push_error(
1329                            format!(
1330                                "{name} implies Shift? to support different keyboard layouts\n\
1331                                Remove Shift to match when the user types '{key_code}'{shifted_hint}",
1332                                name = node.as_token().unwrap().text()
1333                            ),
1334                            &node,
1335                        );
1336                    }
1337                }
1338                // Unshiftable keys ignore the shift state in their key_code
1339                // No special action needed
1340                ShiftBehavior::Unshiftable => {}
1341            }
1342            keys.key = key_code;
1343        }
1344
1345        // If there is a string literal, use it as the key
1346        if let Some(token) = node.child_token(SyntaxKind::StringLiteral)
1347            && let Some(key) =
1348                crate::literals::unescape_string_reporting(Some(&token), ctx.diag, &token)
1349        {
1350            // NFC-normalize the key string for consistent matching
1351            let normalizer = icu_normalizer::ComposingNormalizer::new_nfc();
1352            let key: SmolStr = normalizer.normalize(&key).into();
1353
1354            // Validate that the string literal contains exactly one grapheme cluster
1355            let grapheme_count = key.graphemes(true).count();
1356            if grapheme_count == 0 {
1357                ctx.diag.push_error("Key string literal must not be empty".to_string(), &token);
1358            } else if grapheme_count > 1 {
1359                ctx.diag.push_error(
1360                    format!(
1361                        "Key string literal must contain exactly one grapheme cluster, found {grapheme_count}",
1362                    ),
1363                    &token,
1364                );
1365            }
1366
1367            keys.key = key;
1368
1369            let lowercase: SmolStr = keys.key.to_lowercase().into();
1370            if lowercase != keys.key {
1371                ctx.diag.push_error(
1372                    format!(
1373                        "Key string literals must currently be lowercase, use \"{lowercase}\" instead",
1374                    ),
1375                    &token,
1376                );
1377            }
1378        }
1379
1380        Expression::Keys(keys)
1381    }
1382
1383    /// Perform the lookup
1384    fn from_qualified_name_node(node: syntax_nodes::QualifiedName, ctx: &mut LookupCtx) -> Self {
1385        Self::from_lookup_result(
1386            lookup_qualified_name_node(node.clone(), ctx, LookupPhase::default()),
1387            ctx,
1388            &node,
1389        )
1390    }
1391
1392    fn from_lookup_result(
1393        r: Option<LookupResult>,
1394        ctx: &mut LookupCtx,
1395        node: &dyn Spanned,
1396    ) -> Self {
1397        let Some(r) = r else {
1398            assert!(ctx.diag.has_errors());
1399            return Self::Invalid;
1400        };
1401        match r {
1402            LookupResult::Expression { expression, .. } => expression,
1403            LookupResult::Callable(c) => {
1404                let what = match c {
1405                    LookupResultCallable::Callable(Callable::Callback(..)) => "Callback",
1406                    LookupResultCallable::Callable(Callable::Builtin(..)) => "Builtin function",
1407                    LookupResultCallable::Macro(..) => "Builtin function",
1408                    LookupResultCallable::MemberFunction { .. } => "Member function",
1409                    _ => "Function",
1410                };
1411                ctx.diag
1412                    .push_error(format!("{what} must be called. Did you forgot the '()'?",), node);
1413                Self::Invalid
1414            }
1415            LookupResult::Enumeration(..) => {
1416                ctx.diag.push_error("Cannot take reference to an enum".to_string(), node);
1417                Self::Invalid
1418            }
1419            LookupResult::Namespace(..) => {
1420                ctx.diag.push_error("Cannot take reference to a namespace".to_string(), node);
1421                Self::Invalid
1422            }
1423        }
1424    }
1425
1426    fn from_function_call_node(
1427        node: syntax_nodes::FunctionCallExpression,
1428        ctx: &mut LookupCtx,
1429    ) -> Expression {
1430        let mut arguments = Vec::new();
1431
1432        let mut sub_expr = node.Expression();
1433
1434        let func_expr = sub_expr.next().unwrap();
1435
1436        let (function, source_location) = if let Some(qn) = func_expr.QualifiedName() {
1437            let sl = qn.last_token().unwrap().to_source_location();
1438            (lookup_qualified_name_node(qn, ctx, LookupPhase::default()), sl)
1439        } else if let Some(ma) = func_expr.MemberAccess() {
1440            let base = Self::from_expression_node(ma.Expression(), ctx);
1441            let field = ma.child_token(SyntaxKind::Identifier);
1442            let sl = field.to_source_location();
1443            (maybe_lookup_object(base.into(), field.clone().into_iter(), ctx), sl)
1444        } else {
1445            if Self::from_expression_node(func_expr, ctx).ty() == Type::Invalid {
1446                assert!(ctx.diag.has_errors());
1447            } else {
1448                ctx.diag.push_error("The expression is not a function".into(), &node);
1449            }
1450            return Self::Invalid;
1451        };
1452        let sub_expr = sub_expr.map(|n| {
1453            (Self::from_expression_node(n.clone(), ctx), Some(NodeOrToken::from((*n).clone())))
1454        });
1455        let Some(function) = function else {
1456            // Check sub expressions anyway
1457            sub_expr.count();
1458            assert!(ctx.diag.has_errors());
1459            return Self::Invalid;
1460        };
1461        let LookupResult::Callable(function) = function else {
1462            // Check sub expressions anyway
1463            sub_expr.count();
1464            ctx.diag.push_error("The expression is not a function".into(), &node);
1465            return Self::Invalid;
1466        };
1467
1468        let mut adjust_arg_count = 0;
1469        let function = match function {
1470            LookupResultCallable::Callable(c) => c,
1471            LookupResultCallable::Macro(mac) => {
1472                arguments.extend(sub_expr);
1473                return crate::builtin_macros::lower_macro(
1474                    mac,
1475                    &source_location,
1476                    arguments.into_iter(),
1477                    ctx.diag,
1478                );
1479            }
1480            LookupResultCallable::MemberFunction { member, base, base_node } => {
1481                arguments.push((base, base_node));
1482                adjust_arg_count = 1;
1483                match *member {
1484                    LookupResultCallable::Callable(c) => c,
1485                    LookupResultCallable::Macro(mac) => {
1486                        arguments.extend(sub_expr);
1487                        return crate::builtin_macros::lower_macro(
1488                            mac,
1489                            &source_location,
1490                            arguments.into_iter(),
1491                            ctx.diag,
1492                        );
1493                    }
1494                    LookupResultCallable::MemberFunction { .. } => {
1495                        unreachable!()
1496                    }
1497                }
1498            }
1499        };
1500
1501        arguments.extend(sub_expr);
1502
1503        if matches!(&function, Callable::Callback(nr) if nr.name() == "init") {
1504            ctx.diag.push_warning(
1505                "Calling 'init' explicitly does nothing and is deprecated".into(),
1506                &node,
1507            );
1508        }
1509
1510        let arguments = match function.ty() {
1511            Type::Function(function) | Type::Callback(function) => {
1512                if arguments.len() != function.args.len() {
1513                    ctx.diag.push_error(
1514                        format!(
1515                            "The callback or function expects {} arguments, but {} are provided",
1516                            function.args.len() - adjust_arg_count,
1517                            arguments.len() - adjust_arg_count,
1518                        ),
1519                        &node,
1520                    );
1521                    arguments.into_iter().map(|x| x.0).collect()
1522                } else {
1523                    arguments
1524                        .into_iter()
1525                        .zip(function.args.iter())
1526                        .map(|((e, node), ty)| e.maybe_convert_to(ty.clone(), &node, ctx.diag))
1527                        .collect()
1528                }
1529            }
1530            Type::Invalid => {
1531                debug_assert!(ctx.diag.has_errors(), "The error must already have been reported.");
1532                arguments.into_iter().map(|x| x.0).collect()
1533            }
1534            _ => {
1535                ctx.diag.push_error("The expression is not a function".into(), &node);
1536                arguments.into_iter().map(|x| x.0).collect()
1537            }
1538        };
1539
1540        Expression::FunctionCall { function, arguments, source_location: Some(source_location) }
1541    }
1542
1543    fn from_member_access_node(
1544        node: syntax_nodes::MemberAccess,
1545        ctx: &mut LookupCtx,
1546    ) -> Expression {
1547        let base = Self::from_expression_node(node.Expression(), ctx);
1548        let field = node.child_token(SyntaxKind::Identifier);
1549        Self::from_lookup_result(
1550            maybe_lookup_object(base.into(), field.clone().into_iter(), ctx),
1551            ctx,
1552            &field,
1553        )
1554    }
1555
1556    fn from_self_assignment_node(
1557        node: syntax_nodes::SelfAssignment,
1558        ctx: &mut LookupCtx,
1559    ) -> Expression {
1560        let (lhs_n, rhs_n) = node.Expression();
1561        let mut lhs = Self::from_expression_node(lhs_n.clone(), ctx);
1562        let op = node
1563            .children_with_tokens()
1564            .find_map(|n| match n.kind() {
1565                SyntaxKind::PlusEqual => Some('+'),
1566                SyntaxKind::MinusEqual => Some('-'),
1567                SyntaxKind::StarEqual => Some('*'),
1568                SyntaxKind::DivEqual => Some('/'),
1569                SyntaxKind::Equal => Some('='),
1570                _ => None,
1571            })
1572            .unwrap_or('_');
1573        if lhs.ty() != Type::Invalid {
1574            lhs.try_set_rw(ctx, if op == '=' { "Assignment" } else { "Self assignment" }, &node);
1575        }
1576        let ty = lhs.ty();
1577        let expected_ty = match op {
1578            '=' => ty,
1579            '+' if ty == Type::String || ty.as_unit_product().is_some() => ty,
1580            '-' if ty.as_unit_product().is_some() => ty,
1581            '/' | '*' if ty.as_unit_product().is_some() => Type::Float32,
1582            _ => {
1583                if ty != Type::Invalid {
1584                    ctx.diag.push_error(
1585                        format!("the {op}= operation cannot be done on a {ty}"),
1586                        &lhs_n,
1587                    );
1588                }
1589                Type::Invalid
1590            }
1591        };
1592        let rhs = Self::from_expression_node(rhs_n.clone(), ctx);
1593        Expression::SelfAssignment {
1594            lhs: Box::new(lhs),
1595            rhs: Box::new(rhs.maybe_convert_to(expected_ty, &rhs_n, ctx.diag)),
1596            op,
1597            node: Some(NodeOrToken::Node(node.into())),
1598        }
1599    }
1600
1601    fn from_binary_expression_node(
1602        node: syntax_nodes::BinaryExpression,
1603        ctx: &mut LookupCtx,
1604    ) -> Expression {
1605        let op = node
1606            .children_with_tokens()
1607            .find_map(|n| match n.kind() {
1608                SyntaxKind::Plus => Some('+'),
1609                SyntaxKind::Minus => Some('-'),
1610                SyntaxKind::Star => Some('*'),
1611                SyntaxKind::Div => Some('/'),
1612                SyntaxKind::LessEqual => Some('≤'),
1613                SyntaxKind::GreaterEqual => Some('≥'),
1614                SyntaxKind::LAngle => Some('<'),
1615                SyntaxKind::RAngle => Some('>'),
1616                SyntaxKind::EqualEqual => Some('='),
1617                SyntaxKind::NotEqual => Some('!'),
1618                SyntaxKind::AndAnd => Some('&'),
1619                SyntaxKind::OrOr => Some('|'),
1620                _ => None,
1621            })
1622            .unwrap_or('_');
1623
1624        let (lhs_n, rhs_n) = node.Expression();
1625        let lhs = Self::from_expression_node(lhs_n.clone(), ctx);
1626        let rhs = Self::from_expression_node(rhs_n.clone(), ctx);
1627
1628        let expected_ty = match operator_class(op) {
1629            OperatorClass::ComparisonOp => {
1630                let ty =
1631                    Self::common_target_type_for_type_list([lhs.ty(), rhs.ty()].iter().cloned());
1632                if !matches!(op, '=' | '!') && ty.as_unit_product().is_none() && ty != Type::String
1633                {
1634                    ctx.diag.push_error(format!("Values of type {ty} cannot be compared"), &node);
1635                }
1636                ty
1637            }
1638            OperatorClass::LogicalOp => Type::Bool,
1639            OperatorClass::ArithmeticOp => {
1640                let (lhs_ty, rhs_ty) = (lhs.ty(), rhs.ty());
1641                if op == '+' && (lhs_ty == Type::String || rhs_ty == Type::String) {
1642                    Type::String
1643                } else if op == '+' || op == '-' {
1644                    if lhs_ty.default_unit().is_some() {
1645                        lhs_ty
1646                    } else if rhs_ty.default_unit().is_some() {
1647                        rhs_ty
1648                    } else if matches!(lhs_ty, Type::UnitProduct(_)) {
1649                        lhs_ty
1650                    } else if matches!(rhs_ty, Type::UnitProduct(_)) {
1651                        rhs_ty
1652                    } else {
1653                        Type::Float32
1654                    }
1655                } else if op == '*' || op == '/' {
1656                    let has_unit = |ty: &Type| {
1657                        matches!(ty, Type::UnitProduct(_)) || ty.default_unit().is_some()
1658                    };
1659                    match (has_unit(&lhs_ty), has_unit(&rhs_ty)) {
1660                        (true, true) => {
1661                            return Expression::BinaryExpression {
1662                                lhs: Box::new(lhs),
1663                                rhs: Box::new(rhs),
1664                                op,
1665                            };
1666                        }
1667                        (true, false) => {
1668                            return Expression::BinaryExpression {
1669                                lhs: Box::new(lhs),
1670                                rhs: Box::new(rhs.maybe_convert_to(
1671                                    Type::Float32,
1672                                    &rhs_n,
1673                                    ctx.diag,
1674                                )),
1675                                op,
1676                            };
1677                        }
1678                        (false, true) => {
1679                            return Expression::BinaryExpression {
1680                                lhs: Box::new(lhs.maybe_convert_to(
1681                                    Type::Float32,
1682                                    &lhs_n,
1683                                    ctx.diag,
1684                                )),
1685                                rhs: Box::new(rhs),
1686                                op,
1687                            };
1688                        }
1689                        (false, false) => Type::Float32,
1690                    }
1691                } else {
1692                    unreachable!()
1693                }
1694            }
1695        };
1696        Expression::BinaryExpression {
1697            lhs: Box::new(lhs.maybe_convert_to(expected_ty.clone(), &lhs_n, ctx.diag)),
1698            rhs: Box::new(rhs.maybe_convert_to(expected_ty, &rhs_n, ctx.diag)),
1699            op,
1700        }
1701    }
1702
1703    fn from_unaryop_expression_node(
1704        node: syntax_nodes::UnaryOpExpression,
1705        ctx: &mut LookupCtx,
1706    ) -> Expression {
1707        let exp_n = node.Expression();
1708        let exp = Self::from_expression_node(exp_n, ctx);
1709
1710        let op = node
1711            .children_with_tokens()
1712            .find_map(|n| match n.kind() {
1713                SyntaxKind::Plus => Some('+'),
1714                SyntaxKind::Minus => Some('-'),
1715                SyntaxKind::Bang => Some('!'),
1716                _ => None,
1717            })
1718            .unwrap_or('_');
1719
1720        let exp = match op {
1721            '!' => exp.maybe_convert_to(Type::Bool, &node, ctx.diag),
1722            '+' | '-' => {
1723                let ty = exp.ty();
1724                if ty.default_unit().is_none()
1725                    && !matches!(
1726                        ty,
1727                        Type::Int32
1728                            | Type::Float32
1729                            | Type::Percent
1730                            | Type::UnitProduct(..)
1731                            | Type::Invalid
1732                    )
1733                {
1734                    ctx.diag.push_error(format!("Unary '{op}' not supported on {ty}"), &node);
1735                }
1736                exp
1737            }
1738            _ => {
1739                assert!(ctx.diag.has_errors());
1740                exp
1741            }
1742        };
1743
1744        Expression::UnaryOp { sub: Box::new(exp), op }
1745    }
1746
1747    fn from_conditional_expression_node(
1748        node: syntax_nodes::ConditionalExpression,
1749        ctx: &mut LookupCtx,
1750    ) -> Expression {
1751        let (condition_n, true_expr_n, false_expr_n) = node.Expression();
1752        // FIXME: we should we add bool to the context
1753        let condition = Self::from_expression_node(condition_n.clone(), ctx).maybe_convert_to(
1754            Type::Bool,
1755            &condition_n,
1756            ctx.diag,
1757        );
1758        let true_expr = Self::from_expression_node(true_expr_n.clone(), ctx);
1759        let false_expr = Self::from_expression_node(false_expr_n.clone(), ctx);
1760        let result_ty = common_expression_type(&true_expr, &false_expr);
1761        let true_expr = true_expr.maybe_convert_to(result_ty.clone(), &true_expr_n, ctx.diag);
1762        let false_expr = false_expr.maybe_convert_to(result_ty, &false_expr_n, ctx.diag);
1763        Expression::Condition {
1764            condition: Box::new(condition),
1765            true_expr: Box::new(true_expr),
1766            false_expr: Box::new(false_expr),
1767        }
1768    }
1769
1770    fn from_index_expression_node(
1771        node: syntax_nodes::IndexExpression,
1772        ctx: &mut LookupCtx,
1773    ) -> Expression {
1774        let (array_expr_n, index_expr_n) = node.Expression();
1775        let array_expr = Self::from_expression_node(array_expr_n, ctx);
1776        let index_expr = Self::from_expression_node(index_expr_n.clone(), ctx).maybe_convert_to(
1777            Type::Int32,
1778            &index_expr_n,
1779            ctx.diag,
1780        );
1781
1782        let ty = array_expr.ty();
1783        if !matches!(ty, Type::Array(_) | Type::Invalid | Type::Function(_) | Type::Callback(_)) {
1784            ctx.diag.push_error(format!("{ty} is not an indexable type"), &node);
1785        }
1786        Expression::ArrayIndex { array: Box::new(array_expr), index: Box::new(index_expr) }
1787    }
1788
1789    fn from_object_literal_node(
1790        node: syntax_nodes::ObjectLiteral,
1791        ctx: &mut LookupCtx,
1792    ) -> Expression {
1793        let values: HashMap<SmolStr, Expression> = node
1794            .ObjectMember()
1795            .map(|n| {
1796                (
1797                    identifier_text(&n).unwrap_or_default(),
1798                    Expression::from_expression_node(n.Expression(), ctx),
1799                )
1800            })
1801            .collect();
1802        let ty = Rc::new(Struct {
1803            fields: values.iter().map(|(k, v)| (k.clone(), v.ty())).collect(),
1804            name: StructName::None,
1805        });
1806        Expression::Struct { ty, values }
1807    }
1808
1809    fn from_array_node(node: syntax_nodes::Array, ctx: &mut LookupCtx) -> Expression {
1810        let mut values: Vec<Expression> =
1811            node.Expression().map(|e| Expression::from_expression_node(e, ctx)).collect();
1812
1813        let element_ty = if values.is_empty() {
1814            Type::Void
1815        } else {
1816            Self::common_target_type_for_type_list(values.iter().map(|expr| expr.ty()))
1817        };
1818
1819        for e in values.iter_mut() {
1820            *e = core::mem::replace(e, Expression::Invalid).maybe_convert_to(
1821                element_ty.clone(),
1822                &node,
1823                ctx.diag,
1824            );
1825        }
1826
1827        Expression::Array { element_ty, values }
1828    }
1829
1830    fn from_string_template_node(
1831        node: syntax_nodes::StringTemplate,
1832        ctx: &mut LookupCtx,
1833    ) -> Expression {
1834        let mut result = None;
1835        for n in node.children_with_tokens() {
1836            let expr = if n.kind() == SyntaxKind::StringLiteral {
1837                let token = n.as_token().unwrap();
1838                crate::literals::unescape_string_reporting(Some(token), ctx.diag, token)
1839                    .map(Self::StringLiteral)
1840                    .unwrap_or(Self::Invalid)
1841            } else if n.kind() == SyntaxKind::Expression {
1842                let node = n.into_node().unwrap();
1843                let expr = Expression::from_expression_node(node.clone().into(), ctx);
1844                expr.maybe_convert_to(Type::String, &node, ctx.diag)
1845            } else {
1846                continue;
1847            };
1848            result = match result {
1849                Some(result) => Some(Expression::BinaryExpression {
1850                    lhs: Box::new(result),
1851                    rhs: Box::new(expr),
1852                    op: '+',
1853                }),
1854                None => Some(expr),
1855            }
1856        }
1857        result.unwrap_or_default()
1858    }
1859
1860    /// This function is used to find a type that's suitable for casting each instance of a bunch of expressions
1861    /// to a type that captures most aspects. For example for an array of object literals the result is a merge of
1862    /// all seen fields.
1863    pub fn common_target_type_for_type_list(types: impl Iterator<Item = Type>) -> Type {
1864        types.fold(Type::Invalid, |target_type, expr_ty| {
1865            if target_type == expr_ty {
1866                target_type
1867            } else if target_type == Type::Invalid {
1868                expr_ty
1869            } else {
1870                match (target_type, expr_ty) {
1871                    (Type::Struct(ref result), Type::Struct(ref elem)) => {
1872                        let mut fields = result.fields.clone();
1873                        for (elem_name, elem_ty) in elem.fields.iter() {
1874                            match fields.entry(elem_name.clone()) {
1875                                std::collections::btree_map::Entry::Vacant(free_entry) => {
1876                                    free_entry.insert(elem_ty.clone());
1877                                }
1878                                std::collections::btree_map::Entry::Occupied(
1879                                    mut existing_field,
1880                                ) => {
1881                                    *existing_field.get_mut() =
1882                                        Self::common_target_type_for_type_list(
1883                                            [existing_field.get().clone(), elem_ty.clone()]
1884                                                .into_iter(),
1885                                        );
1886                                }
1887                            }
1888                        }
1889                        Type::Struct(Rc::new(Struct {
1890                            name: result.name.clone().or(elem.name.clone()),
1891                            fields,
1892                        }))
1893                    }
1894                    (Type::Array(lhs), Type::Array(rhs)) => Type::Array(if *lhs == Type::Void {
1895                        rhs
1896                    } else if *rhs == Type::Void {
1897                        lhs
1898                    } else {
1899                        Self::common_target_type_for_type_list(
1900                            [(*lhs).clone(), (*rhs).clone()].into_iter(),
1901                        )
1902                        .into()
1903                    }),
1904                    (Type::Color, Type::Brush) | (Type::Brush, Type::Color) => Type::Brush,
1905                    (Type::Float32, Type::Int32) | (Type::Int32, Type::Float32) => Type::Float32,
1906                    (target_type, expr_ty) => {
1907                        if expr_ty.can_convert(&target_type) {
1908                            target_type
1909                        } else if target_type.can_convert(&expr_ty)
1910                            || (expr_ty.default_unit().is_some()
1911                                && matches!(target_type, Type::Float32 | Type::Int32))
1912                        {
1913                            // in the or case: The `0` literal.
1914                            expr_ty
1915                        } else {
1916                            // otherwise, use the target type and let further conversion report an error
1917                            target_type
1918                        }
1919                    }
1920                }
1921            }
1922        })
1923    }
1924}
1925
1926use i_slint_common::key_codes::{ShiftBehavior, lookup_key_name};
1927
1928/// Return the type that merge two times when they are used in two branch of a condition
1929///
1930/// Ideally this could just be Expression::common_target_type_for_type_list, but that function
1931/// has a bug actually that it tries to convert things that only works for array literal,
1932/// but doesn't work if we have a type of an array.
1933/// So try to recurse into struct literal and array literal in expression to only call
1934/// common_target_type_for_type_list for them, but always keep the type of the array
1935/// if it is NOT an literal
1936fn common_expression_type(true_expr: &Expression, false_expr: &Expression) -> Type {
1937    fn merge_struct(origin: &Struct, other: &Struct) -> Type {
1938        let mut fields = other.fields.clone();
1939        fields.extend(origin.fields.iter().map(|(k, v)| (k.clone(), v.clone())));
1940        Rc::new(Struct { fields, name: StructName::None }).into()
1941    }
1942
1943    if let Expression::Struct { ty, values } = true_expr {
1944        if let Expression::Struct { values: values2, .. } = false_expr {
1945            let mut fields = BTreeMap::new();
1946            for (k, v) in values.iter() {
1947                if let Some(v2) = values2.get(k) {
1948                    fields.insert(k.clone(), common_expression_type(v, v2));
1949                } else {
1950                    fields.insert(k.clone(), v.ty());
1951                }
1952            }
1953            for (k, v) in values2.iter() {
1954                if !values.contains_key(k) {
1955                    fields.insert(k.clone(), v.ty());
1956                }
1957            }
1958            return Type::Struct(Rc::new(Struct { fields, name: StructName::None }));
1959        } else if let Type::Struct(false_ty) = false_expr.ty() {
1960            return merge_struct(&false_ty, ty);
1961        }
1962    } else if let Expression::Struct { ty, .. } = false_expr
1963        && let Type::Struct(true_ty) = true_expr.ty()
1964    {
1965        return merge_struct(&true_ty, ty);
1966    }
1967
1968    if let Expression::Array { .. } = true_expr {
1969        if let Expression::Array { .. } = false_expr {
1970            // fallback to common_target_type_for_type_list
1971        } else if let Type::Array(ty) = false_expr.ty() {
1972            return Type::Array(ty);
1973        }
1974    } else if let Expression::Array { .. } = false_expr
1975        && let Type::Array(ty) = true_expr.ty()
1976    {
1977        return Type::Array(ty);
1978    }
1979
1980    Expression::common_target_type_for_type_list([true_expr.ty(), false_expr.ty()].into_iter())
1981}
1982
1983/// Perform the lookup
1984fn lookup_qualified_name_node(
1985    node: syntax_nodes::QualifiedName,
1986    ctx: &mut LookupCtx,
1987    phase: LookupPhase,
1988) -> Option<LookupResult> {
1989    let mut it = node
1990        .children_with_tokens()
1991        .filter(|n| n.kind() == SyntaxKind::Identifier)
1992        .filter_map(|n| n.into_token());
1993
1994    let first = if let Some(first) = it.next() {
1995        first
1996    } else {
1997        // There must be at least one member (parser should ensure that)
1998        debug_assert!(ctx.diag.has_errors());
1999        return None;
2000    };
2001
2002    ctx.current_token = Some(first.clone().into());
2003    let first_str = crate::parser::normalize_identifier(first.text());
2004    let global_lookup = crate::lookup::global_lookup();
2005    let result = match global_lookup.lookup(ctx, &first_str) {
2006        None => {
2007            if let Some(minus_pos) = first.text().find('-') {
2008                // Attempt to recover if the user wanted to write "-" for minus
2009                let first_str = &first.text()[0..minus_pos];
2010                if global_lookup
2011                    .lookup(ctx, &crate::parser::normalize_identifier(first_str))
2012                    .is_some()
2013                {
2014                    ctx.diag.push_error(format!("Unknown unqualified identifier '{}'. Use space before the '-' if you meant a subtraction", first.text()), &node);
2015                    return None;
2016                }
2017            }
2018            for (prefix, e) in
2019                [("self", ctx.component_scope.last()), ("root", ctx.component_scope.first())]
2020            {
2021                if let Some(e) = e
2022                    && e.lookup(ctx, &first_str).is_some()
2023                {
2024                    ctx.diag.push_error(
2025                        format!(
2026                            "Unknown unqualified identifier '{0}'. Did you mean '{prefix}.{0}'?",
2027                            first.text()
2028                        ),
2029                        &node,
2030                    );
2031                    return None;
2032                }
2033            }
2034
2035            if it.next().is_some() {
2036                ctx.diag.push_error(format!("Cannot access id '{}'", first.text()), &node);
2037            } else {
2038                ctx.diag.push_error(
2039                    format!("Unknown unqualified identifier '{}'", first.text()),
2040                    &node,
2041                );
2042            }
2043            return None;
2044        }
2045        Some(x) => x,
2046    };
2047
2048    if let Some(depr) = result.deprecated() {
2049        ctx.diag.push_property_deprecation_warning(&first_str, depr, &first);
2050    }
2051
2052    match result {
2053        LookupResult::Expression { expression: Expression::ElementReference(e), .. } => {
2054            continue_lookup_within_element(&e.upgrade().unwrap(), &mut it, node, ctx)
2055        }
2056        LookupResult::Expression {
2057            expression: mut e @ Expression::RepeaterModelReference { .. },
2058            ..
2059        } if matches!(phase, LookupPhase::ResolvingTwoWayBindings) => {
2060            // The enclosing model expression may not be resolved yet
2061            // (e.g. when called from `infer_aliases_types`). Skip type
2062            // checking here; `resolve_two_way_binding` does it later.
2063            for n in it {
2064                e = Expression::StructFieldAccess { base: e.into(), name: n.text().into() };
2065            }
2066            Some(e.into())
2067        }
2068        result => maybe_lookup_object(result, it, ctx),
2069    }
2070}
2071
2072fn continue_lookup_within_element(
2073    elem: &ElementRc,
2074    it: &mut impl Iterator<Item = crate::parser::SyntaxToken>,
2075    node: syntax_nodes::QualifiedName,
2076    ctx: &mut LookupCtx,
2077) -> Option<LookupResult> {
2078    let second = if let Some(second) = it.next() {
2079        second
2080    } else if matches!(ctx.property_type, Type::ElementReference) {
2081        return Some(Expression::ElementReference(Rc::downgrade(elem)).into());
2082    } else {
2083        // Try to recover in case we wanted to access a property
2084        let mut rest = String::new();
2085        if let Some(LookupResult::Expression {
2086            expression: Expression::PropertyReference(nr),
2087            ..
2088        }) = crate::lookup::InScopeLookup.lookup(ctx, &elem.borrow().id)
2089        {
2090            let e = nr.element();
2091            let e_borrowed = e.borrow();
2092            let mut id = e_borrowed.id.as_str();
2093            if id.is_empty() {
2094                if ctx.component_scope.last().is_some_and(|x| Rc::ptr_eq(&e, x)) {
2095                    id = "self";
2096                } else if ctx.component_scope.first().is_some_and(|x| Rc::ptr_eq(&e, x)) {
2097                    id = "root";
2098                } else if ctx.component_scope.iter().nth_back(1).is_some_and(|x| Rc::ptr_eq(&e, x))
2099                {
2100                    id = "parent";
2101                }
2102            };
2103            if !id.is_empty() {
2104                rest =
2105                    format!(". Use '{id}.{}' to access the property with the same name", nr.name());
2106            }
2107        } else if let Some(LookupResult::Expression {
2108            expression: Expression::EnumerationValue(value),
2109            ..
2110        }) = crate::lookup::ReturnTypeSpecificLookup.lookup(ctx, &elem.borrow().id)
2111        {
2112            rest = format!(
2113                ". Use '{}.{value}' to access the enumeration value",
2114                value.enumeration.name
2115            );
2116        }
2117        ctx.diag.push_error(format!("Cannot take reference of an element{rest}"), &node);
2118        return None;
2119    };
2120    let prop_name = crate::parser::normalize_identifier(second.text());
2121
2122    let lookup_result = elem.borrow().lookup_property(&prop_name);
2123    let local_to_component = lookup_result.is_local_to_component && ctx.is_local_element(elem);
2124
2125    if lookup_result.property_type.is_property_type() {
2126        if !local_to_component && lookup_result.property_visibility == PropertyVisibility::Private {
2127            ctx.diag.push_error(format!("The property '{}' is private. Annotate it with 'in', 'out' or 'in-out' to make it accessible from other components", second.text()), &second);
2128            return None;
2129        } else if lookup_result.property_visibility == PropertyVisibility::Fake {
2130            ctx.diag.push_error(
2131                "This special property can only be used to make a binding and cannot be accessed"
2132                    .to_string(),
2133                &second,
2134            );
2135            return None;
2136        } else if lookup_result.resolved_name != prop_name.as_str() {
2137            ctx.diag.push_property_deprecation_warning(
2138                &prop_name,
2139                &lookup_result.resolved_name,
2140                &second,
2141            );
2142        } else if let Some(deprecated) =
2143            crate::lookup::check_extra_deprecated(elem, ctx, &prop_name)
2144        {
2145            ctx.diag.push_property_deprecation_warning(&prop_name, &deprecated, &second);
2146        }
2147        let prop = Expression::PropertyReference(NamedReference::new(
2148            elem,
2149            lookup_result.resolved_name.to_smolstr(),
2150        ));
2151        maybe_lookup_object(prop.into(), it, ctx)
2152    } else if matches!(lookup_result.property_type, Type::Callback { .. }) {
2153        if let Some(x) = it.next() {
2154            ctx.diag.push_error("Cannot access fields of callback".into(), &x)
2155        }
2156        Some(LookupResult::Callable(LookupResultCallable::Callable(Callable::Callback(
2157            NamedReference::new(elem, lookup_result.resolved_name.to_smolstr()),
2158        ))))
2159    } else if let Type::Function(fun) = lookup_result.property_type {
2160        if lookup_result.property_visibility == PropertyVisibility::Private && !local_to_component {
2161            let message = format!(
2162                "The function '{}' is private. Annotate it with 'public' to make it accessible from other components",
2163                second.text()
2164            );
2165            if !lookup_result.is_local_to_component {
2166                ctx.diag.push_error(message, &second);
2167            } else {
2168                ctx.diag.push_warning(message+". Note: this used to be allowed in previous version, but this should be considered an error", &second);
2169            }
2170        } else if lookup_result.property_visibility == PropertyVisibility::Protected
2171            && !local_to_component
2172            && !(lookup_result.is_in_direct_base
2173                && ctx.component_scope.first().is_some_and(|x| Rc::ptr_eq(x, elem)))
2174        {
2175            ctx.diag.push_error(format!("The function '{}' is protected", second.text()), &second);
2176        }
2177        if let Some(x) = it.next() {
2178            ctx.diag.push_error("Cannot access fields of a function".into(), &x)
2179        }
2180        let callable = match lookup_result.builtin_function {
2181            Some(builtin) => Callable::Builtin(builtin),
2182            None => Callable::Function(NamedReference::new(
2183                elem,
2184                lookup_result.resolved_name.to_smolstr(),
2185            )),
2186        };
2187        if matches!(fun.args.first(), Some(Type::ElementReference)) {
2188            LookupResult::Callable(LookupResultCallable::MemberFunction {
2189                base: Expression::ElementReference(Rc::downgrade(elem)),
2190                base_node: Some(NodeOrToken::Node(node.into())),
2191                member: Box::new(LookupResultCallable::Callable(callable)),
2192            })
2193            .into()
2194        } else {
2195            LookupResult::from(callable).into()
2196        }
2197    } else {
2198        let mut err = |extra: &str| {
2199            let what = match &elem.borrow().base_type {
2200                ElementType::Global | ElementType::Interface => {
2201                    let enclosing_type = elem.borrow().enclosing_component.upgrade().unwrap();
2202                    assert!(enclosing_type.is_global() || enclosing_type.is_interface());
2203                    format!("'{}'", enclosing_type.id)
2204                }
2205                ElementType::Component(c) => format!("Element '{}'", c.id),
2206                ElementType::Builtin(b) => format!("Element '{}'", b.name),
2207                ElementType::Native(_) => unreachable!("the native pass comes later"),
2208                ElementType::Error => {
2209                    assert!(ctx.diag.has_errors());
2210                    return;
2211                }
2212            };
2213            ctx.diag.push_error(
2214                format!("{} does not have a property '{}'{}", what, second.text(), extra),
2215                &second,
2216            );
2217        };
2218        if let Some(minus_pos) = second.text().find('-') {
2219            // Attempt to recover if the user wanted to write "-"
2220            if elem
2221                .borrow()
2222                .lookup_property(&crate::parser::normalize_identifier(&second.text()[0..minus_pos]))
2223                .property_type
2224                != Type::Invalid
2225            {
2226                err(". Use space before the '-' if you meant a subtraction");
2227                return None;
2228            }
2229        }
2230        err("");
2231        None
2232    }
2233}
2234
2235fn maybe_lookup_object(
2236    mut base: LookupResult,
2237    it: impl Iterator<Item = crate::parser::SyntaxToken>,
2238    ctx: &mut LookupCtx,
2239) -> Option<LookupResult> {
2240    for next in it {
2241        let next_str = crate::parser::normalize_identifier(next.text());
2242        ctx.current_token = Some(next.clone().into());
2243        match base.lookup(ctx, &next_str) {
2244            Some(r) => {
2245                base = r;
2246            }
2247            None => {
2248                if let Some(minus_pos) = next.text().find('-')
2249                    && base.lookup(ctx, &SmolStr::new(&next.text()[0..minus_pos])).is_some()
2250                {
2251                    ctx.diag.push_error(format!("Cannot access the field '{}'. Use space before the '-' if you meant a subtraction", next.text()), &next);
2252                    return None;
2253                }
2254
2255                match base {
2256                    LookupResult::Callable(LookupResultCallable::Callable(Callable::Callback(
2257                        ..,
2258                    ))) => ctx.diag.push_error("Cannot access fields of callback".into(), &next),
2259                    LookupResult::Callable(..) => {
2260                        ctx.diag.push_error("Cannot access fields of a function".into(), &next)
2261                    }
2262                    LookupResult::Enumeration(enumeration) => ctx.diag.push_error(
2263                        format!(
2264                            "'{}' is not a member of the enum {}",
2265                            next.text(),
2266                            enumeration.name
2267                        ),
2268                        &next,
2269                    ),
2270
2271                    LookupResult::Namespace(ns) => {
2272                        ctx.diag.push_error(
2273                            format!("'{}' is not a member of the namespace {}", next.text(), ns),
2274                            &next,
2275                        );
2276                    }
2277                    LookupResult::Expression { expression, .. } => {
2278                        let ty_descr = match expression.ty() {
2279                            Type::Struct { .. } => String::new(),
2280                            Type::Float32
2281                                if ctx.property_type == Type::Model
2282                                    && matches!(
2283                                        expression,
2284                                        Expression::NumberLiteral(_, Unit::None),
2285                                    ) =>
2286                            {
2287                                // usually something like `0..foo`
2288                                format!(
2289                                    " of float. Range expressions are not supported in Slint, but you can use an integer as a model to repeat something multiple time. Eg: `for i in {}`",
2290                                    next.text()
2291                                )
2292                            }
2293
2294                            ty => format!(" of {ty}"),
2295                        };
2296                        ctx.diag.push_error(
2297                            format!("Cannot access the field '{}'{}", next.text(), ty_descr),
2298                            &next,
2299                        );
2300                    }
2301                }
2302                return None;
2303            }
2304        }
2305    }
2306    Some(base)
2307}
2308
2309/// Resolve all two way bindings on `elem`, and finalize the type of any
2310/// `property foo <=> ...` declared without an explicit type. Run after any
2311/// enclosing `for` model expression has been resolved.
2312fn resolve_two_way_bindings_for_element(
2313    elem: &ElementRc,
2314    scope: &[ElementRc],
2315    type_register: &TypeRegister,
2316    diag: &mut BuildDiagnostics,
2317) {
2318    // Queued here and applied after the loop, since the iterator holds a
2319    // borrow on `elem` that blocks `borrow_mut`.
2320    let mut to_infer: Vec<(SmolStr, Type)> = Vec::new();
2321
2322    for (prop_name, binding) in &elem.borrow().bindings {
2323        let mut binding = binding.borrow_mut();
2324        // The alias node is normally the binding's own (uncompiled) expression. But a
2325        // global callback may both alias another global's callback and provide a handler:
2326        // the handler then occupies the expression slot and the alias node lives on the
2327        // callback declaration, in which case the handler expression must be preserved.
2328        let twb_from_expression = match binding.expression.ignore_debug_hooks() {
2329            Expression::Uncompiled(node) => syntax_nodes::TwoWayBinding::new(node.clone()),
2330            _ => None,
2331        };
2332        let twb_node = twb_from_expression
2333            .clone()
2334            .or_else(|| elem.borrow().callback_alias_declaration_node(prop_name));
2335        if let Some(n) = twb_node {
2336            let node: SyntaxNode = n.clone().into();
2337            let lhs_lookup = elem.borrow().lookup_property(prop_name);
2338            if !lhs_lookup.is_valid() {
2339                // An attempt to resolve this already failed when trying to resolve the property type
2340                assert!(diag.has_errors());
2341                continue;
2342            }
2343            let mut lookup_ctx = LookupCtx {
2344                property_name: Some(prop_name.as_str()),
2345                property_type: lhs_lookup.property_type.clone(),
2346                component_scope: scope,
2347                diag,
2348                arguments: Vec::new(),
2349                type_register,
2350                type_loader: None,
2351                current_token: Some(node.clone().into()),
2352                local_variables: Vec::new(),
2353            };
2354
2355            // Only the alias-only case stores the two-way binding in the expression slot;
2356            // the combined case must keep its handler expression intact.
2357            if twb_from_expression.is_some() {
2358                binding.expression = Expression::Invalid;
2359            }
2360
2361            if let Some(twb) = resolve_two_way_binding(n, &mut lookup_ctx) {
2362                if matches!(lhs_lookup.property_type, Type::InferredProperty) {
2363                    to_infer.push((prop_name.clone(), twb.ty()));
2364                }
2365                let nr = twb.property().cloned();
2366                binding.two_way_bindings.push(twb);
2367
2368                let Some(nr) = nr else { continue };
2369                nr.element()
2370                    .borrow()
2371                    .property_analysis
2372                    .borrow_mut()
2373                    .entry(nr.name().clone())
2374                    .or_default()
2375                    .is_linked = true;
2376
2377                if matches!(
2378                    lhs_lookup.property_visibility,
2379                    PropertyVisibility::Private | PropertyVisibility::Output
2380                ) && !lhs_lookup.is_local_to_component
2381                {
2382                    // invalid property assignment should have been reported earlier
2383                    assert!(diag.has_errors() || elem.borrow().is_legacy_syntax);
2384                    continue;
2385                }
2386
2387                // Check the compatibility.
2388                let mut rhs_lookup = nr.element().borrow().lookup_property(nr.name());
2389                if rhs_lookup.property_type == Type::Invalid {
2390                    // An attempt to resolve this already failed when trying to resolve the property type
2391                    assert!(diag.has_errors());
2392                    continue;
2393                }
2394                rhs_lookup.is_local_to_component &= lookup_ctx.is_local_element(&nr.element());
2395
2396                if !rhs_lookup.is_valid_for_assignment() {
2397                    match (lhs_lookup.property_visibility, rhs_lookup.property_visibility) {
2398                        (PropertyVisibility::Input, PropertyVisibility::Input)
2399                            if !lhs_lookup.is_local_to_component =>
2400                        {
2401                            assert!(rhs_lookup.is_local_to_component);
2402                            marked_linked_read_only(elem, prop_name);
2403                        }
2404                        (
2405                            PropertyVisibility::Output | PropertyVisibility::Private,
2406                            PropertyVisibility::Output | PropertyVisibility::Input,
2407                        ) => {
2408                            assert!(lhs_lookup.is_local_to_component);
2409                            marked_linked_read_only(elem, prop_name);
2410                        }
2411                        (PropertyVisibility::Input, PropertyVisibility::Output)
2412                            if !lhs_lookup.is_local_to_component =>
2413                        {
2414                            assert!(!rhs_lookup.is_local_to_component);
2415                            marked_linked_read_only(elem, prop_name);
2416                        }
2417                        _ => {
2418                            if lookup_ctx.is_legacy_component() {
2419                                diag.push_warning(
2420                                    format!(
2421                                        "Link to a {} property is deprecated",
2422                                        rhs_lookup.property_visibility
2423                                    ),
2424                                    &node,
2425                                );
2426                            } else {
2427                                diag.push_error(
2428                                    format!(
2429                                        "Cannot link to a {} property",
2430                                        rhs_lookup.property_visibility
2431                                    ),
2432                                    &node,
2433                                )
2434                            }
2435                        }
2436                    }
2437                } else if !lhs_lookup.is_valid_for_assignment() {
2438                    if rhs_lookup.is_local_to_component
2439                        && rhs_lookup.property_visibility == PropertyVisibility::InOut
2440                    {
2441                        if lookup_ctx.is_legacy_component() {
2442                            debug_assert!(!diag.is_empty()); // warning should already be reported
2443                        } else {
2444                            diag.push_error("Cannot link input property".into(), &node);
2445                        }
2446                    } else if rhs_lookup.property_visibility == PropertyVisibility::InOut {
2447                        diag.push_warning(
2448                            "Linking input properties to input output properties is deprecated"
2449                                .into(),
2450                            &node,
2451                        );
2452                        marked_linked_read_only(&nr.element(), nr.name());
2453                    } else {
2454                        // This is allowed, but then the rhs must also become read only.
2455                        marked_linked_read_only(&nr.element(), nr.name());
2456                    }
2457                }
2458            }
2459        }
2460    }
2461
2462    if !to_infer.is_empty() {
2463        let mut elem_mut = elem.borrow_mut();
2464        for (prop_name, inferred) in to_infer {
2465            let decl = elem_mut.property_declarations.get_mut(&prop_name).unwrap();
2466            if inferred.is_property_type() {
2467                decl.property_type = inferred;
2468            } else {
2469                let type_node = decl.type_node();
2470                diag.push_error(
2471                    format!("Could not infer type of property '{prop_name}'"),
2472                    &type_node,
2473                );
2474            }
2475        }
2476    }
2477
2478    fn marked_linked_read_only(elem: &ElementRc, prop_name: &str) {
2479        elem.borrow()
2480            .property_analysis
2481            .borrow_mut()
2482            .entry(prop_name.into())
2483            .or_default()
2484            .is_linked_to_read_only = true;
2485    }
2486}
2487
2488pub fn resolve_two_way_binding(
2489    node: syntax_nodes::TwoWayBinding,
2490    ctx: &mut LookupCtx,
2491) -> Option<TwoWayBinding> {
2492    const ERROR_MESSAGE: &str = "The expression in a two way binding must be a property reference";
2493
2494    let Some(n) = node.Expression().QualifiedName() else {
2495        ctx.diag.push_error(ERROR_MESSAGE.into(), &node.Expression());
2496        return None;
2497    };
2498
2499    let Some(r) = lookup_qualified_name_node(n, ctx, LookupPhase::ResolvingTwoWayBindings) else {
2500        assert!(ctx.diag.has_errors());
2501        return None;
2502    };
2503
2504    // If type is invalid, error has already been reported,  when inferring, the error will be reported by the inferring code
2505    let report_error = !matches!(
2506        ctx.property_type,
2507        Type::InferredProperty | Type::InferredCallback | Type::Invalid
2508    );
2509    match r {
2510        LookupResult::Expression { expression, .. } => {
2511            fn unwrap_fields(expression: &Expression) -> Option<TwoWayBinding> {
2512                match expression {
2513                    Expression::PropertyReference(nr) => Some(nr.clone().into()),
2514                    Expression::StructFieldAccess { base, name } => {
2515                        let mut prop = unwrap_fields(base)?;
2516                        let field_access = match &mut prop {
2517                            TwoWayBinding::Property { field_access, .. } => field_access,
2518                            TwoWayBinding::ModelData { field_access, .. } => field_access,
2519                        };
2520                        field_access.push(name.clone());
2521                        Some(prop)
2522                    }
2523                    Expression::RepeaterModelReference { element } => {
2524                        Some(TwoWayBinding::ModelData {
2525                            repeated_element: element.clone(),
2526                            field_access: vec![],
2527                        })
2528                    }
2529                    _ => None,
2530                }
2531            }
2532            if let Some(result) = unwrap_fields(&expression) {
2533                // Walk the `ModelData` field path now: the qualified-name
2534                // lookup built it without type checks (the row type may not
2535                // have been known yet). Emits per-field diagnostics and
2536                // yields the leaf type as `expr_ty`.
2537                let expr_ty = if let TwoWayBinding::ModelData { repeated_element, field_access } =
2538                    &result
2539                {
2540                    let mut ty =
2541                        Expression::RepeaterModelReference { element: repeated_element.clone() }
2542                            .ty();
2543                    if !matches!(ty, Type::Invalid) {
2544                        for f in field_access {
2545                            let next = if let Type::Struct(s) = &ty {
2546                                s.fields.get(f.as_str()).cloned()
2547                            } else {
2548                                None
2549                            };
2550                            let Some(next) = next else {
2551                                ctx.diag.push_error(
2552                                    format!("Cannot access the field '{f}' of {ty}"),
2553                                    &node,
2554                                );
2555                                return None;
2556                            };
2557                            ty = next;
2558                        }
2559                    }
2560                    ty
2561                } else {
2562                    result.ty()
2563                };
2564                if report_error && expr_ty != ctx.property_type {
2565                    ctx.diag.push_error(
2566                        format!(
2567                            "The property '{}' does not have the same type as the bound expression: {} != {expr_ty}",
2568                            ctx.property_name.unwrap_or(""),
2569                            ctx.property_type,
2570                        ),
2571                        &node,
2572                    );
2573                }
2574                Some(result)
2575            } else {
2576                let kind = match expression {
2577                    Expression::StructFieldAccess { .. } | Expression::ArrayIndex { .. } => {
2578                        "Two-way bindings can only target property references"
2579                    }
2580                    _ => ERROR_MESSAGE,
2581                };
2582                ctx.diag.push_error(kind.into(), &node);
2583                None
2584            }
2585        }
2586        LookupResult::Callable(LookupResultCallable::Callable(Callable::Callback(n))) => {
2587            if report_error && n.ty() != ctx.property_type {
2588                ctx.diag.push_error("Cannot bind to a callback".into(), &node);
2589                None
2590            } else {
2591                Some(n.into())
2592            }
2593        }
2594        LookupResult::Callable(..) => {
2595            if report_error {
2596                ctx.diag.push_error("Cannot bind to a function".into(), &node);
2597            }
2598            None
2599        }
2600        _ => {
2601            ctx.diag.push_error(ERROR_MESSAGE.into(), &node);
2602            None
2603        }
2604    }
2605}
2606
2607/// For connection to callback aliases, some check are to be performed later
2608fn check_callback_alias_validity(
2609    node: &syntax_nodes::CallbackConnection,
2610    elem: &ElementRc,
2611    name: &str,
2612    diag: &mut BuildDiagnostics,
2613) {
2614    let elem_borrow = elem.borrow();
2615    let Some(decl) = elem_borrow.property_declarations.get(name) else {
2616        if let ElementType::Component(c) = &elem_borrow.base_type {
2617            check_callback_alias_validity(node, &c.root_element, name, diag);
2618        }
2619        return;
2620    };
2621    let Some(b) = elem_borrow.bindings.get(name) else { return };
2622    // `try_borrow` because we might be called for the current binding
2623    let Some(alias) = b
2624        .try_borrow()
2625        .ok()
2626        .and_then(|b| b.two_way_bindings.first().and_then(|x| x.property()).cloned())
2627    else {
2628        return;
2629    };
2630
2631    // A non-global element can be instantiated many times, so letting it assign a handler
2632    // to a singleton global's callback is ambiguous. A global is itself a singleton, so it
2633    // may implement another global's callback.
2634    if alias.element().borrow().base_type == ElementType::Global
2635        && elem_borrow.base_type != ElementType::Global
2636    {
2637        diag.push_error(
2638            "Can't assign a local callback handler to an alias to a global callback".into(),
2639            &node.child_token(SyntaxKind::Identifier).unwrap(),
2640        );
2641    }
2642    if let Type::Callback(callback) = &decl.property_type {
2643        let num_arg = node.DeclaredIdentifier().count();
2644        if num_arg > callback.args.len() {
2645            diag.push_error(
2646                format!(
2647                    "'{name}' only has {} arguments, but {num_arg} were provided",
2648                    callback.args.len(),
2649                ),
2650                &node.child_token(SyntaxKind::Identifier).unwrap(),
2651            );
2652        }
2653    }
2654}