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//! This pass resolves the property binding expressions.
5//!
6//! Before this pass, all the expression are of type Expression::Uncompiled,
7//! and there should no longer be Uncompiled expression after this pass.
8//!
9//! Most of the code for the resolving actually lies in the expression_tree module
10
11use crate::diagnostics::{BuildDiagnostics, Spanned};
12use crate::expression_tree::*;
13use crate::langtype::{ElementType, Struct, Type};
14use crate::lookup::{LookupCtx, LookupObject, LookupResult, LookupResultCallable};
15use crate::object_tree::*;
16use crate::parser::{identifier_text, syntax_nodes, NodeOrToken, SyntaxKind, SyntaxNode};
17use crate::typeregister::TypeRegister;
18use core::num::IntErrorKind;
19use smol_str::{SmolStr, ToSmolStr};
20use std::collections::{BTreeMap, HashMap};
21use std::rc::Rc;
22
23mod remove_noop;
24
25/// This represents a scope for the Component, where Component is the repeated component, but
26/// does not represent a component in the .slint file
27#[derive(Clone)]
28struct ComponentScope(Vec<ElementRc>);
29
30fn resolve_expression(
31    elem: &ElementRc,
32    expr: &mut Expression,
33    property_name: Option<&str>,
34    property_type: Type,
35    scope: &[ElementRc],
36    type_register: &TypeRegister,
37    type_loader: &crate::typeloader::TypeLoader,
38    diag: &mut BuildDiagnostics,
39) {
40    if let Expression::Uncompiled(node) = expr.ignore_debug_hooks() {
41        let mut lookup_ctx = LookupCtx {
42            property_name,
43            property_type,
44            component_scope: scope,
45            diag,
46            arguments: vec![],
47            type_register,
48            type_loader: Some(type_loader),
49            current_token: None,
50            local_variables: vec![],
51        };
52
53        let new_expr = match node.kind() {
54            SyntaxKind::CallbackConnection => {
55                let node = syntax_nodes::CallbackConnection::from(node.clone());
56                if let Some(property_name) = property_name {
57                    check_callback_alias_validity(&node, elem, property_name, lookup_ctx.diag);
58                }
59                Expression::from_callback_connection(node, &mut lookup_ctx)
60            }
61            SyntaxKind::Function => Expression::from_function(node.clone().into(), &mut lookup_ctx),
62            SyntaxKind::Expression => {
63                //FIXME again: this happen for non-binding expression (i.e: model)
64                Expression::from_expression_node(node.clone().into(), &mut lookup_ctx)
65                    .maybe_convert_to(lookup_ctx.property_type.clone(), node, diag)
66            }
67            SyntaxKind::BindingExpression => {
68                Expression::from_binding_expression_node(node.clone(), &mut lookup_ctx)
69            }
70            SyntaxKind::PropertyChangedCallback => {
71                let node = syntax_nodes::PropertyChangedCallback::from(node.clone());
72                if let Some(code_block_node) = node.CodeBlock() {
73                    Expression::from_codeblock_node(code_block_node, &mut lookup_ctx)
74                } else if let Some(expr_node) = node.Expression() {
75                    Expression::from_expression_node(expr_node, &mut lookup_ctx)
76                } else {
77                    assert!(diag.has_errors());
78                    Expression::Invalid
79                }
80            }
81            SyntaxKind::TwoWayBinding => {
82                assert!(diag.has_errors(), "Two way binding should have been resolved already  (property: {property_name:?})");
83                Expression::Invalid
84            }
85            _ => {
86                debug_assert!(diag.has_errors());
87                Expression::Invalid
88            }
89        };
90        match expr {
91            Expression::DebugHook { expression, .. } => *expression = Box::new(new_expr),
92            _ => *expr = new_expr,
93        }
94    }
95}
96
97/// Call the visitor for each children of the element recursively, starting with the element itself
98///
99/// The item that is being visited will be pushed to the scope and popped once visitation is over.
100fn recurse_elem_with_scope(
101    elem: &ElementRc,
102    mut scope: ComponentScope,
103    vis: &mut impl FnMut(&ElementRc, &ComponentScope),
104) -> ComponentScope {
105    scope.0.push(elem.clone());
106    vis(elem, &scope);
107    for sub in &elem.borrow().children {
108        scope = recurse_elem_with_scope(sub, scope, vis);
109    }
110    scope.0.pop();
111    scope
112}
113
114pub fn resolve_expressions(
115    doc: &Document,
116    type_loader: &crate::typeloader::TypeLoader,
117    diag: &mut BuildDiagnostics,
118) {
119    resolve_two_way_bindings(doc, &doc.local_registry, diag);
120
121    for component in doc.inner_components.iter() {
122        recurse_elem_with_scope(
123            &component.root_element,
124            ComponentScope(vec![]),
125            &mut |elem, scope| {
126                let mut is_repeated = elem.borrow().repeated.is_some();
127                visit_element_expressions(elem, |expr, property_name, property_type| {
128                    let scope = if is_repeated {
129                        // The first expression is always the model and it needs to be resolved with the parent scope
130                        debug_assert!(matches!(
131                            elem.borrow().repeated.as_ref().unwrap().model,
132                            Expression::Invalid
133                        )); // should be Invalid because it is taken by the visit_element_expressions function
134
135                        is_repeated = false;
136
137                        debug_assert!(scope.0.len() > 1);
138                        &scope.0[..scope.0.len() - 1]
139                    } else {
140                        &scope.0
141                    };
142
143                    resolve_expression(
144                        elem,
145                        expr,
146                        property_name,
147                        property_type(),
148                        scope,
149                        &doc.local_registry,
150                        type_loader,
151                        diag,
152                    );
153                });
154            },
155        );
156    }
157}
158
159/// To be used in [`Expression::from_qualified_name_node`] to specify if the lookup is performed
160/// for two ways binding (which happens before the models and other expressions are resolved),
161/// or after that.
162#[derive(Default)]
163enum LookupPhase {
164    #[default]
165    UnspecifiedPhase,
166    ResolvingTwoWayBindings,
167}
168
169impl Expression {
170    pub fn from_binding_expression_node(node: SyntaxNode, ctx: &mut LookupCtx) -> Self {
171        debug_assert_eq!(node.kind(), SyntaxKind::BindingExpression);
172        let e = node
173            .children()
174            .find_map(|n| match n.kind() {
175                SyntaxKind::Expression => Some(Self::from_expression_node(n.into(), ctx)),
176                SyntaxKind::CodeBlock => Some(Self::from_codeblock_node(n.into(), ctx)),
177                _ => None,
178            })
179            .unwrap_or(Self::Invalid);
180        if ctx.property_type == Type::LogicalLength && e.ty() == Type::Percent {
181            // See if a conversion from percentage to length is allowed
182            const RELATIVE_TO_PARENT_PROPERTIES: &[&str] =
183                &["width", "height", "preferred-width", "preferred-height"];
184            let property_name = ctx.property_name.unwrap_or_default();
185            if RELATIVE_TO_PARENT_PROPERTIES.contains(&property_name) {
186                return e;
187            } else {
188                ctx.diag.push_error(
189                    format!(
190                        "Automatic conversion from percentage to length is only possible for the following properties: {}",
191                        RELATIVE_TO_PARENT_PROPERTIES.join(", ")
192                    ),
193                    &node
194                );
195                return Expression::Invalid;
196            }
197        };
198        if !matches!(ctx.property_type, Type::Callback { .. } | Type::Function { .. }) {
199            e.maybe_convert_to(ctx.property_type.clone(), &node, ctx.diag)
200        } else {
201            // Binding to a callback or function shouldn't happen
202            assert!(ctx.diag.has_errors());
203            e
204        }
205    }
206
207    fn from_codeblock_node(node: syntax_nodes::CodeBlock, ctx: &mut LookupCtx) -> Expression {
208        debug_assert_eq!(node.kind(), SyntaxKind::CodeBlock);
209
210        // new scope for locals
211        ctx.local_variables.push(Vec::new());
212
213        let mut statements_or_exprs = node
214            .children()
215            .filter_map(|n| match n.kind() {
216                SyntaxKind::Expression => {
217                    Some((n.clone(), Self::from_expression_node(n.into(), ctx)))
218                }
219                SyntaxKind::ReturnStatement => {
220                    Some((n.clone(), Self::from_return_statement(n.into(), ctx)))
221                }
222                SyntaxKind::LetStatement => {
223                    Some((n.clone(), Self::from_let_statement(n.into(), ctx)))
224                }
225                _ => None,
226            })
227            .collect::<Vec<_>>();
228
229        remove_noop::remove_from_codeblock(&mut statements_or_exprs, ctx.diag);
230
231        let mut statements_or_exprs = statements_or_exprs
232            .into_iter()
233            .map(|(_node, statement_or_expr)| statement_or_expr)
234            .collect::<Vec<_>>();
235
236        let exit_points_and_return_types = statements_or_exprs
237            .iter()
238            .enumerate()
239            .filter_map(|(index, statement_or_expr)| {
240                if index == statements_or_exprs.len()
241                    || matches!(statement_or_expr, Expression::ReturnStatement(..))
242                {
243                    Some((index, statement_or_expr.ty()))
244                } else {
245                    None
246                }
247            })
248            .collect::<Vec<_>>();
249
250        let common_return_type = Self::common_target_type_for_type_list(
251            exit_points_and_return_types.iter().map(|(_, ty)| ty.clone()),
252        );
253
254        exit_points_and_return_types.into_iter().for_each(|(index, _)| {
255            let mut expr = std::mem::replace(&mut statements_or_exprs[index], Expression::Invalid);
256            expr = expr.maybe_convert_to(common_return_type.clone(), &node, ctx.diag);
257            statements_or_exprs[index] = expr;
258        });
259
260        // pop local scope
261        ctx.local_variables.pop();
262
263        Expression::CodeBlock(statements_or_exprs)
264    }
265
266    fn from_let_statement(node: syntax_nodes::LetStatement, ctx: &mut LookupCtx) -> Expression {
267        let name = identifier_text(&node.DeclaredIdentifier()).unwrap_or_default();
268
269        let global_lookup = crate::lookup::global_lookup();
270        if let Some(LookupResult::Expression {
271            expression:
272                Expression::ReadLocalVariable { .. } | Expression::FunctionParameterReference { .. },
273            ..
274        }) = global_lookup.lookup(ctx, &name)
275        {
276            ctx.diag
277                .push_error("Redeclaration of local variables is not allowed".to_string(), &node);
278            return Expression::Invalid;
279        }
280
281        // prefix with "local_" to avoid conflicts
282        let name: SmolStr = format!("local_{name}",).into();
283
284        let value = Self::from_expression_node(node.Expression(), ctx);
285        let ty = match node.Type() {
286            Some(ty) => type_from_node(ty, ctx.diag, ctx.type_register),
287            None => value.ty(),
288        };
289
290        // we can get the last scope exists, because each codeblock creates a new scope and we are inside a codeblock here by necessity
291        ctx.local_variables.last_mut().unwrap().push((name.clone(), ty.clone()));
292
293        let value = Box::new(value.maybe_convert_to(ty.clone(), &node, ctx.diag));
294
295        Expression::StoreLocalVariable { name, value }
296    }
297
298    fn from_return_statement(
299        node: syntax_nodes::ReturnStatement,
300        ctx: &mut LookupCtx,
301    ) -> Expression {
302        let return_type = ctx.return_type().clone();
303        let e = node.Expression();
304        if e.is_none() && !matches!(return_type, Type::Void | Type::Invalid) {
305            ctx.diag.push_error(format!("Must return a value of type '{return_type}'"), &node);
306        }
307        Expression::ReturnStatement(e.map(|n| {
308            Box::new(Self::from_expression_node(n, ctx).maybe_convert_to(
309                return_type,
310                &node,
311                ctx.diag,
312            ))
313        }))
314    }
315
316    fn from_callback_connection(
317        node: syntax_nodes::CallbackConnection,
318        ctx: &mut LookupCtx,
319    ) -> Expression {
320        ctx.arguments =
321            node.DeclaredIdentifier().map(|x| identifier_text(&x).unwrap_or_default()).collect();
322        if let Some(code_block_node) = node.CodeBlock() {
323            Self::from_codeblock_node(code_block_node, ctx).maybe_convert_to(
324                ctx.return_type().clone(),
325                &node,
326                ctx.diag,
327            )
328        } else if let Some(expr_node) = node.Expression() {
329            Self::from_expression_node(expr_node, ctx).maybe_convert_to(
330                ctx.return_type().clone(),
331                &node,
332                ctx.diag,
333            )
334        } else {
335            return Expression::Invalid;
336        }
337    }
338
339    fn from_function(node: syntax_nodes::Function, ctx: &mut LookupCtx) -> Expression {
340        ctx.arguments = node
341            .ArgumentDeclaration()
342            .map(|x| identifier_text(&x.DeclaredIdentifier()).unwrap_or_default())
343            .collect();
344        Self::from_codeblock_node(node.CodeBlock(), ctx).maybe_convert_to(
345            ctx.return_type().clone(),
346            &node,
347            ctx.diag,
348        )
349    }
350
351    pub fn from_expression_node(node: syntax_nodes::Expression, ctx: &mut LookupCtx) -> Self {
352        node.children_with_tokens()
353            .find_map(|child| match child {
354                NodeOrToken::Node(node) => match node.kind() {
355                    SyntaxKind::Expression => Some(Self::from_expression_node(node.into(), ctx)),
356                    SyntaxKind::AtImageUrl => Some(Self::from_at_image_url_node(node.into(), ctx)),
357                    SyntaxKind::AtGradient => Some(Self::from_at_gradient(node.into(), ctx)),
358                    SyntaxKind::AtTr => Some(Self::from_at_tr(node.into(), ctx)),
359                    SyntaxKind::QualifiedName => Some(Self::from_qualified_name_node(
360                        node.clone().into(),
361                        ctx,
362                        LookupPhase::default(),
363                    )),
364                    SyntaxKind::FunctionCallExpression => {
365                        Some(Self::from_function_call_node(node.into(), ctx))
366                    }
367                    SyntaxKind::MemberAccess => {
368                        Some(Self::from_member_access_node(node.into(), ctx))
369                    }
370                    SyntaxKind::IndexExpression => {
371                        Some(Self::from_index_expression_node(node.into(), ctx))
372                    }
373                    SyntaxKind::SelfAssignment => {
374                        Some(Self::from_self_assignment_node(node.into(), ctx))
375                    }
376                    SyntaxKind::BinaryExpression => {
377                        Some(Self::from_binary_expression_node(node.into(), ctx))
378                    }
379                    SyntaxKind::UnaryOpExpression => {
380                        Some(Self::from_unaryop_expression_node(node.into(), ctx))
381                    }
382                    SyntaxKind::ConditionalExpression => {
383                        Some(Self::from_conditional_expression_node(node.into(), ctx))
384                    }
385                    SyntaxKind::ObjectLiteral => {
386                        Some(Self::from_object_literal_node(node.into(), ctx))
387                    }
388                    SyntaxKind::Array => Some(Self::from_array_node(node.into(), ctx)),
389                    SyntaxKind::CodeBlock => Some(Self::from_codeblock_node(node.into(), ctx)),
390                    SyntaxKind::StringTemplate => {
391                        Some(Self::from_string_template_node(node.into(), ctx))
392                    }
393                    _ => None,
394                },
395                NodeOrToken::Token(token) => match token.kind() {
396                    SyntaxKind::StringLiteral => Some(
397                        crate::literals::unescape_string(token.text())
398                            .map(Self::StringLiteral)
399                            .unwrap_or_else(|| {
400                                ctx.diag.push_error("Cannot parse string literal".into(), &token);
401                                Self::Invalid
402                            }),
403                    ),
404                    SyntaxKind::NumberLiteral => Some(
405                        crate::literals::parse_number_literal(token.text().into()).unwrap_or_else(
406                            |e| {
407                                ctx.diag.push_error(e.to_string(), &node);
408                                Self::Invalid
409                            },
410                        ),
411                    ),
412                    SyntaxKind::ColorLiteral => Some(
413                        crate::literals::parse_color_literal(token.text())
414                            .map(|i| Expression::Cast {
415                                from: Box::new(Expression::NumberLiteral(i as _, Unit::None)),
416                                to: Type::Color,
417                            })
418                            .unwrap_or_else(|| {
419                                ctx.diag.push_error("Invalid color literal".into(), &node);
420                                Self::Invalid
421                            }),
422                    ),
423
424                    _ => None,
425                },
426            })
427            .unwrap_or(Self::Invalid)
428    }
429
430    fn from_at_image_url_node(node: syntax_nodes::AtImageUrl, ctx: &mut LookupCtx) -> Self {
431        let s = match node
432            .child_text(SyntaxKind::StringLiteral)
433            .and_then(|x| crate::literals::unescape_string(&x))
434        {
435            Some(s) => s,
436            None => {
437                ctx.diag.push_error("Cannot parse string literal".into(), &node);
438                return Self::Invalid;
439            }
440        };
441
442        if s.is_empty() {
443            return Expression::ImageReference {
444                resource_ref: ImageReference::None,
445                source_location: Some(node.to_source_location()),
446                nine_slice: None,
447            };
448        }
449
450        let absolute_source_path = {
451            let path = std::path::Path::new(&s);
452            if crate::pathutils::is_absolute(path) {
453                s
454            } else {
455                ctx.type_loader
456                    .and_then(|loader| {
457                        loader.resolve_import_path(Some(&(*node).clone().into()), &s)
458                    })
459                    .map(|i| i.0.to_string_lossy().into())
460                    .unwrap_or_else(|| {
461                        crate::pathutils::join(
462                            &crate::pathutils::dirname(node.source_file.path()),
463                            path,
464                        )
465                        .map(|p| p.to_string_lossy().into())
466                        .unwrap_or(s.clone())
467                    })
468            }
469        };
470
471        let nine_slice = node
472            .children_with_tokens()
473            .filter_map(|n| n.into_token())
474            .filter(|t| t.kind() == SyntaxKind::NumberLiteral)
475            .map(|arg| {
476                arg.text().parse().unwrap_or_else(|err: std::num::ParseIntError| {
477                    match err.kind() {
478                        IntErrorKind::PosOverflow | IntErrorKind::NegOverflow => {
479                            ctx.diag.push_error("Number too big".into(), &arg)
480                        }
481                        IntErrorKind::InvalidDigit => ctx.diag.push_error(
482                            "Border widths of a nine-slice can't have units".into(),
483                            &arg,
484                        ),
485                        _ => ctx.diag.push_error("Cannot parse number literal".into(), &arg),
486                    };
487                    0u16
488                })
489            })
490            .collect::<Vec<u16>>();
491
492        let nine_slice = match nine_slice.as_slice() {
493            [x] => Some([*x, *x, *x, *x]),
494            [x, y] => Some([*x, *y, *x, *y]),
495            [x, y, z, w] => Some([*x, *y, *z, *w]),
496            [] => None,
497            _ => {
498                assert!(ctx.diag.has_errors());
499                None
500            }
501        };
502
503        Expression::ImageReference {
504            resource_ref: ImageReference::AbsolutePath(absolute_source_path),
505            source_location: Some(node.to_source_location()),
506            nine_slice,
507        }
508    }
509
510    pub fn from_at_gradient(node: syntax_nodes::AtGradient, ctx: &mut LookupCtx) -> Self {
511        enum GradKind {
512            Linear { angle: Box<Expression> },
513            Radial,
514            Conic,
515        }
516
517        let mut subs = node
518            .children_with_tokens()
519            .filter(|n| matches!(n.kind(), SyntaxKind::Comma | SyntaxKind::Expression));
520
521        let grad_token = node.child_token(SyntaxKind::Identifier).unwrap();
522        let grad_text = grad_token.text();
523
524        let grad_kind = if grad_text.starts_with("linear") {
525            let angle_expr = match subs.next() {
526                Some(e) if e.kind() == SyntaxKind::Expression => {
527                    syntax_nodes::Expression::from(e.into_node().unwrap())
528                }
529                _ => {
530                    ctx.diag.push_error("Expected angle expression".into(), &node);
531                    return Expression::Invalid;
532                }
533            };
534            if subs.next().is_some_and(|s| s.kind() != SyntaxKind::Comma) {
535                ctx.diag.push_error(
536                    "Angle expression must be an angle followed by a comma".into(),
537                    &node,
538                );
539                return Expression::Invalid;
540            }
541            let angle = Box::new(
542                Expression::from_expression_node(angle_expr.clone(), ctx).maybe_convert_to(
543                    Type::Angle,
544                    &angle_expr,
545                    ctx.diag,
546                ),
547            );
548            GradKind::Linear { angle }
549        } else if grad_text.starts_with("radial") {
550            if !matches!(subs.next(), Some(NodeOrToken::Node(n)) if n.text().to_string().trim() == "circle")
551            {
552                ctx.diag.push_error("Expected 'circle': currently, only @radial-gradient(circle, ...) are supported".into(), &node);
553                return Expression::Invalid;
554            }
555            let comma = subs.next();
556            if matches!(&comma, Some(NodeOrToken::Node(n)) if n.text().to_string().trim() == "at") {
557                ctx.diag.push_error("'at' in @radial-gradient is not yet supported".into(), &comma);
558                return Expression::Invalid;
559            }
560            if comma.as_ref().is_some_and(|s| s.kind() != SyntaxKind::Comma) {
561                ctx.diag.push_error(
562                    "'circle' must be followed by a comma".into(),
563                    comma.as_ref().map_or(&node, |x| x as &dyn Spanned),
564                );
565                return Expression::Invalid;
566            }
567            GradKind::Radial
568        } else if grad_text.starts_with("conic") {
569            GradKind::Conic
570        } else {
571            // Parser should have ensured we have one of the linear, radial or conic gradient
572            panic!("Not a gradient {grad_text:?}");
573        };
574
575        let mut stops = vec![];
576        enum Stop {
577            Empty,
578            Color(Expression),
579            Finished,
580        }
581        let mut current_stop = Stop::Empty;
582        for n in subs {
583            if n.kind() == SyntaxKind::Comma {
584                match std::mem::replace(&mut current_stop, Stop::Empty) {
585                    Stop::Empty => {
586                        ctx.diag.push_error("Expected expression".into(), &n);
587                        break;
588                    }
589                    Stop::Finished => {}
590                    Stop::Color(col) => stops.push((
591                        col,
592                        if stops.is_empty() {
593                            Expression::NumberLiteral(0., Unit::None)
594                        } else {
595                            Expression::Invalid
596                        },
597                    )),
598                }
599            } else {
600                // To facilitate color literal conversion, adjust the expected return type.
601                let e = {
602                    let old_property_type = std::mem::replace(&mut ctx.property_type, Type::Color);
603                    let e =
604                        Expression::from_expression_node(n.as_node().unwrap().clone().into(), ctx);
605                    ctx.property_type = old_property_type;
606                    e
607                };
608                match std::mem::replace(&mut current_stop, Stop::Finished) {
609                    Stop::Empty => {
610                        current_stop = Stop::Color(e.maybe_convert_to(Type::Color, &n, ctx.diag))
611                    }
612                    Stop::Finished => {
613                        ctx.diag.push_error("Expected comma".into(), &n);
614                        break;
615                    }
616                    Stop::Color(col) => {
617                        let stop_type = match &grad_kind {
618                            GradKind::Conic => Type::Angle,
619                            _ => Type::Float32,
620                        };
621                        stops.push((col, e.maybe_convert_to(stop_type, &n, ctx.diag)))
622                    }
623                }
624            }
625        }
626        match current_stop {
627            Stop::Color(col) => stops.push((col, Expression::NumberLiteral(1., Unit::None))),
628            Stop::Empty => {
629                if let Some((_, e @ Expression::Invalid)) = stops.last_mut() {
630                    *e = Expression::NumberLiteral(1., Unit::None)
631                }
632            }
633            Stop::Finished => (),
634        };
635
636        // Fix the stop so each has a position.
637        let mut start = 0;
638        while start < stops.len() {
639            start += match stops[start..].iter().position(|s| matches!(s.1, Expression::Invalid)) {
640                Some(p) => p,
641                None => break,
642            };
643            let (before, rest) = stops.split_at_mut(start);
644            let pos =
645                rest.iter().position(|s| !matches!(s.1, Expression::Invalid)).unwrap_or(rest.len());
646            if pos > 0 && pos < rest.len() {
647                let (middle, after) = rest.split_at_mut(pos);
648                let begin = before
649                    .last()
650                    .map(|s| &s.1)
651                    .unwrap_or(&Expression::NumberLiteral(1., Unit::None));
652                let end = &after.first().expect("The last should never be invalid").1;
653                for (i, (_, e)) in middle.iter_mut().enumerate() {
654                    debug_assert!(matches!(e, Expression::Invalid));
655                    // e = begin + (i+1) * (end - begin) / (pos+1)
656                    *e = Expression::BinaryExpression {
657                        lhs: Box::new(begin.clone()),
658                        rhs: Box::new(Expression::BinaryExpression {
659                            lhs: Box::new(Expression::BinaryExpression {
660                                lhs: Box::new(Expression::NumberLiteral(i as f64 + 1., Unit::None)),
661                                rhs: Box::new(Expression::BinaryExpression {
662                                    lhs: Box::new(end.clone()),
663                                    rhs: Box::new(begin.clone()),
664                                    op: '-',
665                                }),
666                                op: '*',
667                            }),
668                            rhs: Box::new(Expression::NumberLiteral(pos as f64 + 1., Unit::None)),
669                            op: '/',
670                        }),
671                        op: '+',
672                    };
673                }
674            }
675            start += pos + 1;
676        }
677
678        match grad_kind {
679            GradKind::Linear { angle } => Expression::LinearGradient { angle, stops },
680            GradKind::Radial => Expression::RadialGradient { stops },
681            GradKind::Conic => {
682                // For conic gradients, we need to:
683                // 1. Ensure angle expressions are converted to Type::Angle
684                // 2. Normalize to 0-1 range for internal representation
685                let normalized_stops = stops
686                    .into_iter()
687                    .map(|(color, angle_expr)| {
688                        // First ensure the angle expression is properly typed as Angle
689                        let angle_typed =
690                            angle_expr.maybe_convert_to(Type::Angle, &node, &mut ctx.diag);
691
692                        // Convert angle to 0-1 range by dividing by 360deg
693                        // This ensures all angle units (deg, rad, turn) are normalized
694                        let normalized_pos = Expression::BinaryExpression {
695                            lhs: Box::new(angle_typed),
696                            rhs: Box::new(Expression::NumberLiteral(360., Unit::Deg)),
697                            op: '/',
698                        };
699                        (color, normalized_pos)
700                    })
701                    .collect();
702                Expression::ConicGradient { stops: normalized_stops }
703            }
704        }
705    }
706
707    fn from_at_tr(node: syntax_nodes::AtTr, ctx: &mut LookupCtx) -> Expression {
708        let Some(string) = node
709            .child_text(SyntaxKind::StringLiteral)
710            .and_then(|s| crate::literals::unescape_string(&s))
711        else {
712            ctx.diag.push_error("Cannot parse string literal".into(), &node);
713            return Expression::Invalid;
714        };
715        let context = node.TrContext().map(|n| {
716            n.child_text(SyntaxKind::StringLiteral)
717                .and_then(|s| crate::literals::unescape_string(&s))
718                .unwrap_or_else(|| {
719                    ctx.diag.push_error("Cannot parse string literal".into(), &n);
720                    Default::default()
721                })
722        });
723        let plural = node.TrPlural().map(|pl| {
724            let s = pl
725                .child_text(SyntaxKind::StringLiteral)
726                .and_then(|s| crate::literals::unescape_string(&s))
727                .unwrap_or_else(|| {
728                    ctx.diag.push_error("Cannot parse string literal".into(), &pl);
729                    Default::default()
730                });
731            let n = pl.Expression();
732            let expr = Expression::from_expression_node(n.clone(), ctx).maybe_convert_to(
733                Type::Int32,
734                &n,
735                ctx.diag,
736            );
737            (s, expr)
738        });
739
740        let domain = ctx
741            .type_loader
742            .and_then(|tl| tl.compiler_config.translation_domain.clone())
743            .unwrap_or_default();
744
745        let subs = node.Expression().map(|n| {
746            Expression::from_expression_node(n.clone(), ctx).maybe_convert_to(
747                Type::String,
748                &n,
749                ctx.diag,
750            )
751        });
752        let values = subs.collect::<Vec<_>>();
753
754        // check format string
755        {
756            let mut arg_idx = 0;
757            let mut pos_max = 0;
758            let mut pos = 0;
759            let mut has_n = false;
760            while let Some(mut p) = string[pos..].find(['{', '}']) {
761                if string.len() - pos < p + 1 {
762                    ctx.diag.push_error(
763                        "Unescaped trailing '{' in format string. Escape '{' with '{{'".into(),
764                        &node,
765                    );
766                    break;
767                }
768                p += pos;
769
770                // Skip escaped }
771                if string.get(p..=p) == Some("}") {
772                    if string.get(p + 1..=p + 1) == Some("}") {
773                        pos = p + 2;
774                        continue;
775                    } else {
776                        ctx.diag.push_error(
777                            "Unescaped '}' in format string. Escape '}' with '}}'".into(),
778                            &node,
779                        );
780                        break;
781                    }
782                }
783
784                // Skip escaped {
785                if string.get(p + 1..=p + 1) == Some("{") {
786                    pos = p + 2;
787                    continue;
788                }
789
790                // Find the argument
791                let end = if let Some(end) = string[p..].find('}') {
792                    end + p
793                } else {
794                    ctx.diag.push_error(
795                        "Unterminated placeholder in format string. '{' must be escaped with '{{'"
796                            .into(),
797                        &node,
798                    );
799                    break;
800                };
801                let argument = &string[p + 1..end];
802                if argument.is_empty() {
803                    arg_idx += 1;
804                } else if let Ok(n) = argument.parse::<u16>() {
805                    pos_max = pos_max.max(n as usize + 1);
806                } else if argument == "n" {
807                    has_n = true;
808                    if plural.is_none() {
809                        ctx.diag.push_error(
810                            "`{n}` placeholder can only be found in plural form".into(),
811                            &node,
812                        );
813                    }
814                } else {
815                    ctx.diag
816                        .push_error("Invalid '{...}' placeholder in format string. The placeholder must be a number, or braces must be escaped with '{{' and '}}'".into(), &node);
817                    break;
818                };
819                pos = end + 1;
820            }
821            if arg_idx > 0 && pos_max > 0 {
822                ctx.diag.push_error(
823                    "Cannot mix positional and non-positional placeholder in format string".into(),
824                    &node,
825                );
826            } else if arg_idx > values.len() || pos_max > values.len() {
827                let num = arg_idx.max(pos_max);
828                let note = if !has_n && plural.is_some() {
829                    ". Note: use `{n}` for the argument after '%'"
830                } else {
831                    ""
832                };
833                ctx.diag.push_error(
834                    format!("Format string contains {num} placeholders, but only {} extra arguments were given{note}", values.len()),
835                    &node,
836                );
837            }
838        }
839
840        let plural =
841            plural.unwrap_or((SmolStr::default(), Expression::NumberLiteral(1., Unit::None)));
842
843        let get_component_name = || {
844            ctx.component_scope
845                .first()
846                .and_then(|e| e.borrow().enclosing_component.upgrade())
847                .map(|c| c.id.clone())
848        };
849
850        Expression::FunctionCall {
851            function: BuiltinFunction::Translate.into(),
852            arguments: vec![
853                Expression::StringLiteral(string),
854                Expression::StringLiteral(context.or_else(get_component_name).unwrap_or_default()),
855                Expression::StringLiteral(domain.into()),
856                Expression::Array { element_ty: Type::String, values },
857                plural.1,
858                Expression::StringLiteral(plural.0),
859            ],
860            source_location: Some(node.to_source_location()),
861        }
862    }
863
864    /// Perform the lookup
865    fn from_qualified_name_node(
866        node: syntax_nodes::QualifiedName,
867        ctx: &mut LookupCtx,
868        phase: LookupPhase,
869    ) -> Self {
870        Self::from_lookup_result(lookup_qualified_name_node(node.clone(), ctx, phase), ctx, &node)
871    }
872
873    fn from_lookup_result(
874        r: Option<LookupResult>,
875        ctx: &mut LookupCtx,
876        node: &dyn Spanned,
877    ) -> Self {
878        let Some(r) = r else {
879            assert!(ctx.diag.has_errors());
880            return Self::Invalid;
881        };
882        match r {
883            LookupResult::Expression { expression, .. } => expression,
884            LookupResult::Callable(c) => {
885                let what = match c {
886                    LookupResultCallable::Callable(Callable::Callback(..)) => "Callback",
887                    LookupResultCallable::Callable(Callable::Builtin(..)) => "Builtin function",
888                    LookupResultCallable::Macro(..) => "Builtin function",
889                    LookupResultCallable::MemberFunction { .. } => "Member function",
890                    _ => "Function",
891                };
892                ctx.diag
893                    .push_error(format!("{what} must be called. Did you forgot the '()'?",), node);
894                Self::Invalid
895            }
896            LookupResult::Enumeration(..) => {
897                ctx.diag.push_error("Cannot take reference to an enum".to_string(), node);
898                Self::Invalid
899            }
900            LookupResult::Namespace(..) => {
901                ctx.diag.push_error("Cannot take reference to a namespace".to_string(), node);
902                Self::Invalid
903            }
904        }
905    }
906
907    fn from_function_call_node(
908        node: syntax_nodes::FunctionCallExpression,
909        ctx: &mut LookupCtx,
910    ) -> Expression {
911        let mut arguments = Vec::new();
912
913        let mut sub_expr = node.Expression();
914
915        let func_expr = sub_expr.next().unwrap();
916
917        let (function, source_location) = if let Some(qn) = func_expr.QualifiedName() {
918            let sl = qn.last_token().unwrap().to_source_location();
919            (lookup_qualified_name_node(qn, ctx, LookupPhase::default()), sl)
920        } else if let Some(ma) = func_expr.MemberAccess() {
921            let base = Self::from_expression_node(ma.Expression(), ctx);
922            let field = ma.child_token(SyntaxKind::Identifier);
923            let sl = field.to_source_location();
924            (maybe_lookup_object(base.into(), field.clone().into_iter(), ctx), sl)
925        } else {
926            if Self::from_expression_node(func_expr, ctx).ty() == Type::Invalid {
927                assert!(ctx.diag.has_errors());
928            } else {
929                ctx.diag.push_error("The expression is not a function".into(), &node);
930            }
931            return Self::Invalid;
932        };
933        let sub_expr = sub_expr.map(|n| {
934            (Self::from_expression_node(n.clone(), ctx), Some(NodeOrToken::from((*n).clone())))
935        });
936        let Some(function) = function else {
937            // Check sub expressions anyway
938            sub_expr.count();
939            assert!(ctx.diag.has_errors());
940            return Self::Invalid;
941        };
942        let LookupResult::Callable(function) = function else {
943            // Check sub expressions anyway
944            sub_expr.count();
945            ctx.diag.push_error("The expression is not a function".into(), &node);
946            return Self::Invalid;
947        };
948
949        let mut adjust_arg_count = 0;
950        let function = match function {
951            LookupResultCallable::Callable(c) => c,
952            LookupResultCallable::Macro(mac) => {
953                arguments.extend(sub_expr);
954                return crate::builtin_macros::lower_macro(
955                    mac,
956                    &source_location,
957                    arguments.into_iter(),
958                    ctx.diag,
959                );
960            }
961            LookupResultCallable::MemberFunction { member, base, base_node } => {
962                arguments.push((base, base_node));
963                adjust_arg_count = 1;
964                match *member {
965                    LookupResultCallable::Callable(c) => c,
966                    LookupResultCallable::Macro(mac) => {
967                        arguments.extend(sub_expr);
968                        return crate::builtin_macros::lower_macro(
969                            mac,
970                            &source_location,
971                            arguments.into_iter(),
972                            ctx.diag,
973                        );
974                    }
975                    LookupResultCallable::MemberFunction { .. } => {
976                        unreachable!()
977                    }
978                }
979            }
980        };
981
982        arguments.extend(sub_expr);
983
984        let arguments = match function.ty() {
985            Type::Function(function) | Type::Callback(function) => {
986                if arguments.len() != function.args.len() {
987                    ctx.diag.push_error(
988                        format!(
989                            "The callback or function expects {} arguments, but {} are provided",
990                            function.args.len() - adjust_arg_count,
991                            arguments.len() - adjust_arg_count,
992                        ),
993                        &node,
994                    );
995                    arguments.into_iter().map(|x| x.0).collect()
996                } else {
997                    arguments
998                        .into_iter()
999                        .zip(function.args.iter())
1000                        .map(|((e, node), ty)| e.maybe_convert_to(ty.clone(), &node, ctx.diag))
1001                        .collect()
1002                }
1003            }
1004            Type::Invalid => {
1005                debug_assert!(ctx.diag.has_errors(), "The error must already have been reported.");
1006                arguments.into_iter().map(|x| x.0).collect()
1007            }
1008            _ => {
1009                ctx.diag.push_error("The expression is not a function".into(), &node);
1010                arguments.into_iter().map(|x| x.0).collect()
1011            }
1012        };
1013
1014        Expression::FunctionCall { function, arguments, source_location: Some(source_location) }
1015    }
1016
1017    fn from_member_access_node(
1018        node: syntax_nodes::MemberAccess,
1019        ctx: &mut LookupCtx,
1020    ) -> Expression {
1021        let base = Self::from_expression_node(node.Expression(), ctx);
1022        let field = node.child_token(SyntaxKind::Identifier);
1023        Self::from_lookup_result(
1024            maybe_lookup_object(base.into(), field.clone().into_iter(), ctx),
1025            ctx,
1026            &field,
1027        )
1028    }
1029
1030    fn from_self_assignment_node(
1031        node: syntax_nodes::SelfAssignment,
1032        ctx: &mut LookupCtx,
1033    ) -> Expression {
1034        let (lhs_n, rhs_n) = node.Expression();
1035        let mut lhs = Self::from_expression_node(lhs_n.clone(), ctx);
1036        let op = node
1037            .children_with_tokens()
1038            .find_map(|n| match n.kind() {
1039                SyntaxKind::PlusEqual => Some('+'),
1040                SyntaxKind::MinusEqual => Some('-'),
1041                SyntaxKind::StarEqual => Some('*'),
1042                SyntaxKind::DivEqual => Some('/'),
1043                SyntaxKind::Equal => Some('='),
1044                _ => None,
1045            })
1046            .unwrap_or('_');
1047        if lhs.ty() != Type::Invalid {
1048            lhs.try_set_rw(ctx, if op == '=' { "Assignment" } else { "Self assignment" }, &node);
1049        }
1050        let ty = lhs.ty();
1051        let expected_ty = match op {
1052            '=' => ty,
1053            '+' if ty == Type::String || ty.as_unit_product().is_some() => ty,
1054            '-' if ty.as_unit_product().is_some() => ty,
1055            '/' | '*' if ty.as_unit_product().is_some() => Type::Float32,
1056            _ => {
1057                if ty != Type::Invalid {
1058                    ctx.diag.push_error(
1059                        format!("the {op}= operation cannot be done on a {ty}"),
1060                        &lhs_n,
1061                    );
1062                }
1063                Type::Invalid
1064            }
1065        };
1066        let rhs = Self::from_expression_node(rhs_n.clone(), ctx);
1067        Expression::SelfAssignment {
1068            lhs: Box::new(lhs),
1069            rhs: Box::new(rhs.maybe_convert_to(expected_ty, &rhs_n, ctx.diag)),
1070            op,
1071            node: Some(NodeOrToken::Node(node.into())),
1072        }
1073    }
1074
1075    fn from_binary_expression_node(
1076        node: syntax_nodes::BinaryExpression,
1077        ctx: &mut LookupCtx,
1078    ) -> Expression {
1079        let op = node
1080            .children_with_tokens()
1081            .find_map(|n| match n.kind() {
1082                SyntaxKind::Plus => Some('+'),
1083                SyntaxKind::Minus => Some('-'),
1084                SyntaxKind::Star => Some('*'),
1085                SyntaxKind::Div => Some('/'),
1086                SyntaxKind::LessEqual => Some('≤'),
1087                SyntaxKind::GreaterEqual => Some('≥'),
1088                SyntaxKind::LAngle => Some('<'),
1089                SyntaxKind::RAngle => Some('>'),
1090                SyntaxKind::EqualEqual => Some('='),
1091                SyntaxKind::NotEqual => Some('!'),
1092                SyntaxKind::AndAnd => Some('&'),
1093                SyntaxKind::OrOr => Some('|'),
1094                _ => None,
1095            })
1096            .unwrap_or('_');
1097
1098        let (lhs_n, rhs_n) = node.Expression();
1099        let lhs = Self::from_expression_node(lhs_n.clone(), ctx);
1100        let rhs = Self::from_expression_node(rhs_n.clone(), ctx);
1101
1102        let expected_ty = match operator_class(op) {
1103            OperatorClass::ComparisonOp => {
1104                let ty =
1105                    Self::common_target_type_for_type_list([lhs.ty(), rhs.ty()].iter().cloned());
1106                if !matches!(op, '=' | '!') && !ty.as_unit_product().is_some() && ty != Type::String
1107                {
1108                    ctx.diag.push_error(format!("Values of type {ty} cannot be compared"), &node);
1109                }
1110                ty
1111            }
1112            OperatorClass::LogicalOp => Type::Bool,
1113            OperatorClass::ArithmeticOp => {
1114                let (lhs_ty, rhs_ty) = (lhs.ty(), rhs.ty());
1115                if op == '+' && (lhs_ty == Type::String || rhs_ty == Type::String) {
1116                    Type::String
1117                } else if op == '+' || op == '-' {
1118                    if lhs_ty.default_unit().is_some() {
1119                        lhs_ty
1120                    } else if rhs_ty.default_unit().is_some() {
1121                        rhs_ty
1122                    } else if matches!(lhs_ty, Type::UnitProduct(_)) {
1123                        lhs_ty
1124                    } else if matches!(rhs_ty, Type::UnitProduct(_)) {
1125                        rhs_ty
1126                    } else {
1127                        Type::Float32
1128                    }
1129                } else if op == '*' || op == '/' {
1130                    let has_unit = |ty: &Type| {
1131                        matches!(ty, Type::UnitProduct(_)) || ty.default_unit().is_some()
1132                    };
1133                    match (has_unit(&lhs_ty), has_unit(&rhs_ty)) {
1134                        (true, true) => {
1135                            return Expression::BinaryExpression {
1136                                lhs: Box::new(lhs),
1137                                rhs: Box::new(rhs),
1138                                op,
1139                            }
1140                        }
1141                        (true, false) => {
1142                            return Expression::BinaryExpression {
1143                                lhs: Box::new(lhs),
1144                                rhs: Box::new(rhs.maybe_convert_to(
1145                                    Type::Float32,
1146                                    &rhs_n,
1147                                    ctx.diag,
1148                                )),
1149                                op,
1150                            }
1151                        }
1152                        (false, true) => {
1153                            return Expression::BinaryExpression {
1154                                lhs: Box::new(lhs.maybe_convert_to(
1155                                    Type::Float32,
1156                                    &lhs_n,
1157                                    ctx.diag,
1158                                )),
1159                                rhs: Box::new(rhs),
1160                                op,
1161                            }
1162                        }
1163                        (false, false) => Type::Float32,
1164                    }
1165                } else {
1166                    unreachable!()
1167                }
1168            }
1169        };
1170        Expression::BinaryExpression {
1171            lhs: Box::new(lhs.maybe_convert_to(expected_ty.clone(), &lhs_n, ctx.diag)),
1172            rhs: Box::new(rhs.maybe_convert_to(expected_ty, &rhs_n, ctx.diag)),
1173            op,
1174        }
1175    }
1176
1177    fn from_unaryop_expression_node(
1178        node: syntax_nodes::UnaryOpExpression,
1179        ctx: &mut LookupCtx,
1180    ) -> Expression {
1181        let exp_n = node.Expression();
1182        let exp = Self::from_expression_node(exp_n, ctx);
1183
1184        let op = node
1185            .children_with_tokens()
1186            .find_map(|n| match n.kind() {
1187                SyntaxKind::Plus => Some('+'),
1188                SyntaxKind::Minus => Some('-'),
1189                SyntaxKind::Bang => Some('!'),
1190                _ => None,
1191            })
1192            .unwrap_or('_');
1193
1194        let exp = match op {
1195            '!' => exp.maybe_convert_to(Type::Bool, &node, ctx.diag),
1196            '+' | '-' => {
1197                let ty = exp.ty();
1198                if ty.default_unit().is_none()
1199                    && !matches!(
1200                        ty,
1201                        Type::Int32
1202                            | Type::Float32
1203                            | Type::Percent
1204                            | Type::UnitProduct(..)
1205                            | Type::Invalid
1206                    )
1207                {
1208                    ctx.diag.push_error(format!("Unary '{op}' not supported on {ty}"), &node);
1209                }
1210                exp
1211            }
1212            _ => {
1213                assert!(ctx.diag.has_errors());
1214                exp
1215            }
1216        };
1217
1218        Expression::UnaryOp { sub: Box::new(exp), op }
1219    }
1220
1221    fn from_conditional_expression_node(
1222        node: syntax_nodes::ConditionalExpression,
1223        ctx: &mut LookupCtx,
1224    ) -> Expression {
1225        let (condition_n, true_expr_n, false_expr_n) = node.Expression();
1226        // FIXME: we should we add bool to the context
1227        let condition = Self::from_expression_node(condition_n.clone(), ctx).maybe_convert_to(
1228            Type::Bool,
1229            &condition_n,
1230            ctx.diag,
1231        );
1232        let true_expr = Self::from_expression_node(true_expr_n.clone(), ctx);
1233        let false_expr = Self::from_expression_node(false_expr_n.clone(), ctx);
1234        let result_ty = common_expression_type(&true_expr, &false_expr);
1235        let true_expr = true_expr.maybe_convert_to(result_ty.clone(), &true_expr_n, ctx.diag);
1236        let false_expr = false_expr.maybe_convert_to(result_ty, &false_expr_n, ctx.diag);
1237        Expression::Condition {
1238            condition: Box::new(condition),
1239            true_expr: Box::new(true_expr),
1240            false_expr: Box::new(false_expr),
1241        }
1242    }
1243
1244    fn from_index_expression_node(
1245        node: syntax_nodes::IndexExpression,
1246        ctx: &mut LookupCtx,
1247    ) -> Expression {
1248        let (array_expr_n, index_expr_n) = node.Expression();
1249        let array_expr = Self::from_expression_node(array_expr_n, ctx);
1250        let index_expr = Self::from_expression_node(index_expr_n.clone(), ctx).maybe_convert_to(
1251            Type::Int32,
1252            &index_expr_n,
1253            ctx.diag,
1254        );
1255
1256        let ty = array_expr.ty();
1257        if !matches!(ty, Type::Array(_) | Type::Invalid | Type::Function(_) | Type::Callback(_)) {
1258            ctx.diag.push_error(format!("{ty} is not an indexable type"), &node);
1259        }
1260        Expression::ArrayIndex { array: Box::new(array_expr), index: Box::new(index_expr) }
1261    }
1262
1263    fn from_object_literal_node(
1264        node: syntax_nodes::ObjectLiteral,
1265        ctx: &mut LookupCtx,
1266    ) -> Expression {
1267        let values: HashMap<SmolStr, Expression> = node
1268            .ObjectMember()
1269            .map(|n| {
1270                (
1271                    identifier_text(&n).unwrap_or_default(),
1272                    Expression::from_expression_node(n.Expression(), ctx),
1273                )
1274            })
1275            .collect();
1276        let ty = Rc::new(Struct {
1277            fields: values.iter().map(|(k, v)| (k.clone(), v.ty())).collect(),
1278            name: None,
1279            node: None,
1280            rust_attributes: None,
1281        });
1282        Expression::Struct { ty, values }
1283    }
1284
1285    fn from_array_node(node: syntax_nodes::Array, ctx: &mut LookupCtx) -> Expression {
1286        let mut values: Vec<Expression> =
1287            node.Expression().map(|e| Expression::from_expression_node(e, ctx)).collect();
1288
1289        let element_ty = if values.is_empty() {
1290            Type::Void
1291        } else {
1292            Self::common_target_type_for_type_list(values.iter().map(|expr| expr.ty()))
1293        };
1294
1295        for e in values.iter_mut() {
1296            *e = core::mem::replace(e, Expression::Invalid).maybe_convert_to(
1297                element_ty.clone(),
1298                &node,
1299                ctx.diag,
1300            );
1301        }
1302
1303        Expression::Array { element_ty, values }
1304    }
1305
1306    fn from_string_template_node(
1307        node: syntax_nodes::StringTemplate,
1308        ctx: &mut LookupCtx,
1309    ) -> Expression {
1310        let mut exprs = node.Expression().map(|e| {
1311            Expression::from_expression_node(e.clone(), ctx).maybe_convert_to(
1312                Type::String,
1313                &e,
1314                ctx.diag,
1315            )
1316        });
1317        let mut result = exprs.next().unwrap_or_default();
1318        for x in exprs {
1319            result = Expression::BinaryExpression {
1320                lhs: Box::new(std::mem::take(&mut result)),
1321                rhs: Box::new(x),
1322                op: '+',
1323            }
1324        }
1325        result
1326    }
1327
1328    /// This function is used to find a type that's suitable for casting each instance of a bunch of expressions
1329    /// to a type that captures most aspects. For example for an array of object literals the result is a merge of
1330    /// all seen fields.
1331    pub fn common_target_type_for_type_list(types: impl Iterator<Item = Type>) -> Type {
1332        types.fold(Type::Invalid, |target_type, expr_ty| {
1333            if target_type == expr_ty {
1334                target_type
1335            } else if target_type == Type::Invalid {
1336                expr_ty
1337            } else {
1338                match (target_type, expr_ty) {
1339                    (Type::Struct(ref result), Type::Struct(ref elem)) => {
1340                        let mut fields = result.fields.clone();
1341                        for (elem_name, elem_ty) in elem.fields.iter() {
1342                            match fields.entry(elem_name.clone()) {
1343                                std::collections::btree_map::Entry::Vacant(free_entry) => {
1344                                    free_entry.insert(elem_ty.clone());
1345                                }
1346                                std::collections::btree_map::Entry::Occupied(
1347                                    mut existing_field,
1348                                ) => {
1349                                    *existing_field.get_mut() =
1350                                        Self::common_target_type_for_type_list(
1351                                            [existing_field.get().clone(), elem_ty.clone()]
1352                                                .into_iter(),
1353                                        );
1354                                }
1355                            }
1356                        }
1357                        Type::Struct(Rc::new(Struct {
1358                            name: result.name.as_ref().or(elem.name.as_ref()).cloned(),
1359                            fields,
1360                            node: result.node.as_ref().or(elem.node.as_ref()).cloned(),
1361                            rust_attributes: result
1362                                .rust_attributes
1363                                .as_ref()
1364                                .or(elem.rust_attributes.as_ref())
1365                                .cloned(),
1366                        }))
1367                    }
1368                    (Type::Array(lhs), Type::Array(rhs)) => Type::Array(if *lhs == Type::Void {
1369                        rhs
1370                    } else if *rhs == Type::Void {
1371                        lhs
1372                    } else {
1373                        Self::common_target_type_for_type_list(
1374                            [(*lhs).clone(), (*rhs).clone()].into_iter(),
1375                        )
1376                        .into()
1377                    }),
1378                    (Type::Color, Type::Brush) | (Type::Brush, Type::Color) => Type::Brush,
1379                    (Type::Float32, Type::Int32) | (Type::Int32, Type::Float32) => Type::Float32,
1380                    (target_type, expr_ty) => {
1381                        if expr_ty.can_convert(&target_type) {
1382                            target_type
1383                        } else if target_type.can_convert(&expr_ty)
1384                            || (expr_ty.default_unit().is_some()
1385                                && matches!(target_type, Type::Float32 | Type::Int32))
1386                        {
1387                            // in the or case: The `0` literal.
1388                            expr_ty
1389                        } else {
1390                            // otherwise, use the target type and let further conversion report an error
1391                            target_type
1392                        }
1393                    }
1394                }
1395            }
1396        })
1397    }
1398}
1399
1400/// Return the type that merge two times when they are used in two branch of a condition
1401///
1402/// Ideally this could just be Expression::common_target_type_for_type_list, but that function
1403/// has a bug actually that it tries to convert things that only works for array literal,
1404/// but doesn't work if we have a type of an array.
1405/// So try to recurse into struct literal and array literal in expression to only call
1406/// common_target_type_for_type_list for them, but always keep the type of the array
1407/// if it is NOT an literal
1408fn common_expression_type(true_expr: &Expression, false_expr: &Expression) -> Type {
1409    fn merge_struct(origin: &Struct, other: &Struct) -> Type {
1410        let mut fields = other.fields.clone();
1411        fields.extend(origin.fields.iter().map(|(k, v)| (k.clone(), v.clone())));
1412        Rc::new(Struct { fields, name: None, node: None, rust_attributes: None }).into()
1413    }
1414
1415    if let Expression::Struct { ty, values } = true_expr {
1416        if let Expression::Struct { values: values2, .. } = false_expr {
1417            let mut fields = BTreeMap::new();
1418            for (k, v) in values.iter() {
1419                if let Some(v2) = values2.get(k) {
1420                    fields.insert(k.clone(), common_expression_type(v, v2));
1421                } else {
1422                    fields.insert(k.clone(), v.ty());
1423                }
1424            }
1425            for (k, v) in values2.iter() {
1426                if !values.contains_key(k) {
1427                    fields.insert(k.clone(), v.ty());
1428                }
1429            }
1430            return Type::Struct(Rc::new(Struct {
1431                fields,
1432                name: None,
1433                node: None,
1434                rust_attributes: None,
1435            }));
1436        } else if let Type::Struct(false_ty) = false_expr.ty() {
1437            return merge_struct(&false_ty, &ty);
1438        }
1439    } else if let Expression::Struct { ty, .. } = false_expr {
1440        if let Type::Struct(true_ty) = true_expr.ty() {
1441            return merge_struct(&true_ty, &ty);
1442        }
1443    }
1444
1445    if let Expression::Array { .. } = true_expr {
1446        if let Expression::Array { .. } = false_expr {
1447            // fallback to common_target_type_for_type_list
1448        } else if let Type::Array(ty) = false_expr.ty() {
1449            return Type::Array(ty);
1450        }
1451    } else if let Expression::Array { .. } = false_expr {
1452        if let Type::Array(ty) = true_expr.ty() {
1453            return Type::Array(ty);
1454        }
1455    }
1456
1457    Expression::common_target_type_for_type_list([true_expr.ty(), false_expr.ty()].into_iter())
1458}
1459
1460/// Perform the lookup
1461fn lookup_qualified_name_node(
1462    node: syntax_nodes::QualifiedName,
1463    ctx: &mut LookupCtx,
1464    phase: LookupPhase,
1465) -> Option<LookupResult> {
1466    let mut it = node
1467        .children_with_tokens()
1468        .filter(|n| n.kind() == SyntaxKind::Identifier)
1469        .filter_map(|n| n.into_token());
1470
1471    let first = if let Some(first) = it.next() {
1472        first
1473    } else {
1474        // There must be at least one member (parser should ensure that)
1475        debug_assert!(ctx.diag.has_errors());
1476        return None;
1477    };
1478
1479    ctx.current_token = Some(first.clone().into());
1480    let first_str = crate::parser::normalize_identifier(first.text());
1481    let global_lookup = crate::lookup::global_lookup();
1482    let result = match global_lookup.lookup(ctx, &first_str) {
1483        None => {
1484            if let Some(minus_pos) = first.text().find('-') {
1485                // Attempt to recover if the user wanted to write "-" for minus
1486                let first_str = &first.text()[0..minus_pos];
1487                if global_lookup
1488                    .lookup(ctx, &crate::parser::normalize_identifier(first_str))
1489                    .is_some()
1490                {
1491                    ctx.diag.push_error(format!("Unknown unqualified identifier '{}'. Use space before the '-' if you meant a subtraction", first.text()), &node);
1492                    return None;
1493                }
1494            }
1495            for (prefix, e) in
1496                [("self", ctx.component_scope.last()), ("root", ctx.component_scope.first())]
1497            {
1498                if let Some(e) = e {
1499                    if e.lookup(ctx, &first_str).is_some() {
1500                        ctx.diag.push_error(format!("Unknown unqualified identifier '{0}'. Did you mean '{prefix}.{0}'?", first.text()), &node);
1501                        return None;
1502                    }
1503                }
1504            }
1505
1506            if it.next().is_some() {
1507                ctx.diag.push_error(format!("Cannot access id '{}'", first.text()), &node);
1508            } else {
1509                ctx.diag.push_error(
1510                    format!("Unknown unqualified identifier '{}'", first.text()),
1511                    &node,
1512                );
1513            }
1514            return None;
1515        }
1516        Some(x) => x,
1517    };
1518
1519    if let Some(depr) = result.deprecated() {
1520        ctx.diag.push_property_deprecation_warning(&first_str, depr, &first);
1521    }
1522
1523    match result {
1524        LookupResult::Expression { expression: Expression::ElementReference(e), .. } => {
1525            continue_lookup_within_element(&e.upgrade().unwrap(), &mut it, node, ctx)
1526        }
1527        LookupResult::Expression {
1528            expression: Expression::RepeaterModelReference { .. }, ..
1529        } if matches!(phase, LookupPhase::ResolvingTwoWayBindings) => {
1530            ctx.diag.push_error(
1531                "Two-way bindings to model data is not supported yet".to_string(),
1532                &node,
1533            );
1534            None
1535        }
1536        result => maybe_lookup_object(result, it, ctx),
1537    }
1538}
1539
1540fn continue_lookup_within_element(
1541    elem: &ElementRc,
1542    it: &mut impl Iterator<Item = crate::parser::SyntaxToken>,
1543    node: syntax_nodes::QualifiedName,
1544    ctx: &mut LookupCtx,
1545) -> Option<LookupResult> {
1546    let second = if let Some(second) = it.next() {
1547        second
1548    } else if matches!(ctx.property_type, Type::ElementReference) {
1549        return Some(Expression::ElementReference(Rc::downgrade(elem)).into());
1550    } else {
1551        // Try to recover in case we wanted to access a property
1552        let mut rest = String::new();
1553        if let Some(LookupResult::Expression {
1554            expression: Expression::PropertyReference(nr),
1555            ..
1556        }) = crate::lookup::InScopeLookup.lookup(ctx, &elem.borrow().id)
1557        {
1558            let e = nr.element();
1559            let e_borrowed = e.borrow();
1560            let mut id = e_borrowed.id.as_str();
1561            if id.is_empty() {
1562                if ctx.component_scope.last().is_some_and(|x| Rc::ptr_eq(&e, x)) {
1563                    id = "self";
1564                } else if ctx.component_scope.first().is_some_and(|x| Rc::ptr_eq(&e, x)) {
1565                    id = "root";
1566                } else if ctx.component_scope.iter().nth_back(1).is_some_and(|x| Rc::ptr_eq(&e, x))
1567                {
1568                    id = "parent";
1569                }
1570            };
1571            if !id.is_empty() {
1572                rest =
1573                    format!(". Use '{id}.{}' to access the property with the same name", nr.name());
1574            }
1575        } else if let Some(LookupResult::Expression {
1576            expression: Expression::EnumerationValue(value),
1577            ..
1578        }) = crate::lookup::ReturnTypeSpecificLookup.lookup(ctx, &elem.borrow().id)
1579        {
1580            rest = format!(
1581                ". Use '{}.{value}' to access the enumeration value",
1582                value.enumeration.name
1583            );
1584        }
1585        ctx.diag.push_error(format!("Cannot take reference of an element{rest}"), &node);
1586        return None;
1587    };
1588    let prop_name = crate::parser::normalize_identifier(second.text());
1589
1590    let lookup_result = elem.borrow().lookup_property(&prop_name);
1591    let local_to_component = lookup_result.is_local_to_component && ctx.is_local_element(elem);
1592
1593    if lookup_result.property_type.is_property_type() {
1594        if !local_to_component && lookup_result.property_visibility == PropertyVisibility::Private {
1595            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);
1596            return None;
1597        } else if lookup_result.property_visibility == PropertyVisibility::Fake {
1598            ctx.diag.push_error(
1599                "This special property can only be used to make a binding and cannot be accessed"
1600                    .to_string(),
1601                &second,
1602            );
1603            return None;
1604        } else if lookup_result.resolved_name != prop_name.as_str() {
1605            ctx.diag.push_property_deprecation_warning(
1606                &prop_name,
1607                &lookup_result.resolved_name,
1608                &second,
1609            );
1610        } else if let Some(deprecated) =
1611            crate::lookup::check_extra_deprecated(elem, ctx, &prop_name)
1612        {
1613            ctx.diag.push_property_deprecation_warning(&prop_name, &deprecated, &second);
1614        }
1615        let prop = Expression::PropertyReference(NamedReference::new(
1616            elem,
1617            lookup_result.resolved_name.to_smolstr(),
1618        ));
1619        maybe_lookup_object(prop.into(), it, ctx)
1620    } else if matches!(lookup_result.property_type, Type::Callback { .. }) {
1621        if let Some(x) = it.next() {
1622            ctx.diag.push_error("Cannot access fields of callback".into(), &x)
1623        }
1624        Some(LookupResult::Callable(LookupResultCallable::Callable(Callable::Callback(
1625            NamedReference::new(elem, lookup_result.resolved_name.to_smolstr()),
1626        ))))
1627    } else if let Type::Function(fun) = lookup_result.property_type {
1628        if lookup_result.property_visibility == PropertyVisibility::Private && !local_to_component {
1629            let message = format!("The function '{}' is private. Annotate it with 'public' to make it accessible from other components", second.text());
1630            if !lookup_result.is_local_to_component {
1631                ctx.diag.push_error(message, &second);
1632            } else {
1633                ctx.diag.push_warning(message+". Note: this used to be allowed in previous version, but this should be considered an error", &second);
1634            }
1635        } else if lookup_result.property_visibility == PropertyVisibility::Protected
1636            && !local_to_component
1637            && !(lookup_result.is_in_direct_base
1638                && ctx.component_scope.first().is_some_and(|x| Rc::ptr_eq(x, elem)))
1639        {
1640            ctx.diag.push_error(format!("The function '{}' is protected", second.text()), &second);
1641        }
1642        if let Some(x) = it.next() {
1643            ctx.diag.push_error("Cannot access fields of a function".into(), &x)
1644        }
1645        let callable = match lookup_result.builtin_function {
1646            Some(builtin) => Callable::Builtin(builtin),
1647            None => Callable::Function(NamedReference::new(
1648                elem,
1649                lookup_result.resolved_name.to_smolstr(),
1650            )),
1651        };
1652        if matches!(fun.args.first(), Some(Type::ElementReference)) {
1653            LookupResult::Callable(LookupResultCallable::MemberFunction {
1654                base: Expression::ElementReference(Rc::downgrade(elem)),
1655                base_node: Some(NodeOrToken::Node(node.into())),
1656                member: Box::new(LookupResultCallable::Callable(callable)),
1657            })
1658            .into()
1659        } else {
1660            LookupResult::from(callable).into()
1661        }
1662    } else {
1663        let mut err = |extra: &str| {
1664            let what = match &elem.borrow().base_type {
1665                ElementType::Global => {
1666                    let global = elem.borrow().enclosing_component.upgrade().unwrap();
1667                    assert!(global.is_global());
1668                    format!("'{}'", global.id)
1669                }
1670                ElementType::Component(c) => format!("Element '{}'", c.id),
1671                ElementType::Builtin(b) => format!("Element '{}'", b.name),
1672                ElementType::Native(_) => unreachable!("the native pass comes later"),
1673                ElementType::Error => {
1674                    assert!(ctx.diag.has_errors());
1675                    return;
1676                }
1677            };
1678            ctx.diag.push_error(
1679                format!("{} does not have a property '{}'{}", what, second.text(), extra),
1680                &second,
1681            );
1682        };
1683        if let Some(minus_pos) = second.text().find('-') {
1684            // Attempt to recover if the user wanted to write "-"
1685            if elem
1686                .borrow()
1687                .lookup_property(&crate::parser::normalize_identifier(&second.text()[0..minus_pos]))
1688                .property_type
1689                != Type::Invalid
1690            {
1691                err(". Use space before the '-' if you meant a subtraction");
1692                return None;
1693            }
1694        }
1695        err("");
1696        None
1697    }
1698}
1699
1700fn maybe_lookup_object(
1701    mut base: LookupResult,
1702    it: impl Iterator<Item = crate::parser::SyntaxToken>,
1703    ctx: &mut LookupCtx,
1704) -> Option<LookupResult> {
1705    for next in it {
1706        let next_str = crate::parser::normalize_identifier(next.text());
1707        ctx.current_token = Some(next.clone().into());
1708        match base.lookup(ctx, &next_str) {
1709            Some(r) => {
1710                base = r;
1711            }
1712            None => {
1713                if let Some(minus_pos) = next.text().find('-') {
1714                    if base.lookup(ctx, &SmolStr::new(&next.text()[0..minus_pos])).is_some() {
1715                        ctx.diag.push_error(format!("Cannot access the field '{}'. Use space before the '-' if you meant a subtraction", next.text()), &next);
1716                        return None;
1717                    }
1718                }
1719
1720                match base {
1721                    LookupResult::Callable(LookupResultCallable::Callable(Callable::Callback(
1722                        ..,
1723                    ))) => ctx.diag.push_error("Cannot access fields of callback".into(), &next),
1724                    LookupResult::Callable(..) => {
1725                        ctx.diag.push_error("Cannot access fields of a function".into(), &next)
1726                    }
1727                    LookupResult::Enumeration(enumeration) => ctx.diag.push_error(
1728                        format!(
1729                            "'{}' is not a member of the enum {}",
1730                            next.text(),
1731                            enumeration.name
1732                        ),
1733                        &next,
1734                    ),
1735
1736                    LookupResult::Namespace(ns) => {
1737                        ctx.diag.push_error(
1738                            format!("'{}' is not a member of the namespace {}", next.text(), ns),
1739                            &next,
1740                        );
1741                    }
1742                    LookupResult::Expression { expression, .. } => {
1743                        let ty_descr = match expression.ty() {
1744                            Type::Struct { .. } => String::new(),
1745                            Type::Float32
1746                                if ctx.property_type == Type::Model
1747                                    && matches!(
1748                                        expression,
1749                                        Expression::NumberLiteral(_, Unit::None),
1750                                    ) =>
1751                            {
1752                                // usually something like `0..foo`
1753                                format!(" 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 {}`", next.text())
1754                            }
1755
1756                            ty => format!(" of {ty}"),
1757                        };
1758                        ctx.diag.push_error(
1759                            format!("Cannot access the field '{}'{}", next.text(), ty_descr),
1760                            &next,
1761                        );
1762                    }
1763                }
1764                return None;
1765            }
1766        }
1767    }
1768    Some(base)
1769}
1770
1771/// Go through all the two way binding and resolve them first
1772fn resolve_two_way_bindings(
1773    doc: &Document,
1774    type_register: &TypeRegister,
1775    diag: &mut BuildDiagnostics,
1776) {
1777    for component in doc.inner_components.iter() {
1778        recurse_elem_with_scope(
1779            &component.root_element,
1780            ComponentScope(vec![]),
1781            &mut |elem, scope| {
1782                for (prop_name, binding) in &elem.borrow().bindings {
1783                    let mut binding = binding.borrow_mut();
1784                    if let Expression::Uncompiled(node) =
1785                        binding.expression.ignore_debug_hooks().clone()
1786                    {
1787                        if let Some(n) = syntax_nodes::TwoWayBinding::new(node.clone()) {
1788                            let lhs_lookup = elem.borrow().lookup_property(prop_name);
1789                            if !lhs_lookup.is_valid() {
1790                                // An attempt to resolve this already failed when trying to resolve the property type
1791                                assert!(diag.has_errors());
1792                                continue;
1793                            }
1794                            let mut lookup_ctx = LookupCtx {
1795                                property_name: Some(prop_name.as_str()),
1796                                property_type: lhs_lookup.property_type.clone(),
1797                                component_scope: &scope.0,
1798                                diag,
1799                                arguments: vec![],
1800                                type_register,
1801                                type_loader: None,
1802                                current_token: Some(node.clone().into()),
1803                                local_variables: vec![],
1804                            };
1805
1806                            binding.expression = Expression::Invalid;
1807
1808                            if let Some(nr) = resolve_two_way_binding(n, &mut lookup_ctx) {
1809                                binding.two_way_bindings.push(nr.clone());
1810
1811                                nr.element()
1812                                    .borrow()
1813                                    .property_analysis
1814                                    .borrow_mut()
1815                                    .entry(nr.name().clone())
1816                                    .or_default()
1817                                    .is_linked = true;
1818
1819                                if matches!(
1820                                    lhs_lookup.property_visibility,
1821                                    PropertyVisibility::Private | PropertyVisibility::Output
1822                                ) && !lhs_lookup.is_local_to_component
1823                                {
1824                                    // invalid property assignment should have been reported earlier
1825                                    assert!(diag.has_errors() || elem.borrow().is_legacy_syntax);
1826                                    continue;
1827                                }
1828
1829                                // Check the compatibility.
1830                                let mut rhs_lookup =
1831                                    nr.element().borrow().lookup_property(nr.name());
1832                                if rhs_lookup.property_type == Type::Invalid {
1833                                    // An attempt to resolve this already failed when trying to resolve the property type
1834                                    assert!(diag.has_errors());
1835                                    continue;
1836                                }
1837                                rhs_lookup.is_local_to_component &=
1838                                    lookup_ctx.is_local_element(&nr.element());
1839
1840                                if !rhs_lookup.is_valid_for_assignment() {
1841                                    match (
1842                                        lhs_lookup.property_visibility,
1843                                        rhs_lookup.property_visibility,
1844                                    ) {
1845                                        (PropertyVisibility::Input, PropertyVisibility::Input)
1846                                            if !lhs_lookup.is_local_to_component =>
1847                                        {
1848                                            assert!(rhs_lookup.is_local_to_component);
1849                                            marked_linked_read_only(elem, prop_name);
1850                                        }
1851                                        (
1852                                            PropertyVisibility::Output
1853                                            | PropertyVisibility::Private,
1854                                            PropertyVisibility::Output | PropertyVisibility::Input,
1855                                        ) => {
1856                                            assert!(lhs_lookup.is_local_to_component);
1857                                            marked_linked_read_only(elem, prop_name);
1858                                        }
1859                                        (PropertyVisibility::Input, PropertyVisibility::Output)
1860                                            if !lhs_lookup.is_local_to_component =>
1861                                        {
1862                                            assert!(!rhs_lookup.is_local_to_component);
1863                                            marked_linked_read_only(elem, prop_name);
1864                                        }
1865                                        _ => {
1866                                            if lookup_ctx.is_legacy_component() {
1867                                                diag.push_warning(
1868                                                    format!(
1869                                                        "Link to a {} property is deprecated",
1870                                                        rhs_lookup.property_visibility
1871                                                    ),
1872                                                    &node,
1873                                                );
1874                                            } else {
1875                                                diag.push_error(
1876                                                    format!(
1877                                                        "Cannot link to a {} property",
1878                                                        rhs_lookup.property_visibility
1879                                                    ),
1880                                                    &node,
1881                                                )
1882                                            }
1883                                        }
1884                                    }
1885                                } else if !lhs_lookup.is_valid_for_assignment() {
1886                                    if rhs_lookup.is_local_to_component
1887                                        && rhs_lookup.property_visibility
1888                                            == PropertyVisibility::InOut
1889                                    {
1890                                        if lookup_ctx.is_legacy_component() {
1891                                            debug_assert!(!diag.is_empty()); // warning should already be reported
1892                                        } else {
1893                                            diag.push_error(
1894                                                "Cannot link input property".into(),
1895                                                &node,
1896                                            );
1897                                        }
1898                                    } else if rhs_lookup.property_visibility
1899                                        == PropertyVisibility::InOut
1900                                    {
1901                                        diag.push_warning("Linking input properties to input output properties is deprecated".into(), &node);
1902                                        marked_linked_read_only(&nr.element(), nr.name());
1903                                    } else {
1904                                        // This is allowed, but then the rhs must also become read only.
1905                                        marked_linked_read_only(&nr.element(), nr.name());
1906                                    }
1907                                }
1908                            }
1909                        }
1910                    }
1911                }
1912            },
1913        );
1914    }
1915
1916    fn marked_linked_read_only(elem: &ElementRc, prop_name: &str) {
1917        elem.borrow()
1918            .property_analysis
1919            .borrow_mut()
1920            .entry(prop_name.into())
1921            .or_default()
1922            .is_linked_to_read_only = true;
1923    }
1924}
1925
1926pub fn resolve_two_way_binding(
1927    node: syntax_nodes::TwoWayBinding,
1928    ctx: &mut LookupCtx,
1929) -> Option<NamedReference> {
1930    let Some(n) = node.Expression().QualifiedName() else {
1931        ctx.diag.push_error(
1932            "The expression in a two way binding must be a property reference".into(),
1933            &node.Expression(),
1934        );
1935        return None;
1936    };
1937
1938    let Some(r) = lookup_qualified_name_node(n, ctx, LookupPhase::ResolvingTwoWayBindings) else {
1939        assert!(ctx.diag.has_errors());
1940        return None;
1941    };
1942
1943    // If type is invalid, error has already been reported,  when inferring, the error will be reported by the inferring code
1944    let report_error = !matches!(
1945        ctx.property_type,
1946        Type::InferredProperty | Type::InferredCallback | Type::Invalid
1947    );
1948    match r {
1949        LookupResult::Expression { expression: Expression::PropertyReference(n), .. } => {
1950            if report_error && n.ty() != ctx.property_type {
1951                ctx.diag.push_error(
1952                    "The property does not have the same type as the bound property".into(),
1953                    &node,
1954                );
1955            }
1956            Some(n)
1957        }
1958        LookupResult::Callable(LookupResultCallable::Callable(Callable::Callback(n))) => {
1959            if report_error && n.ty() != ctx.property_type {
1960                ctx.diag.push_error("Cannot bind to a callback".into(), &node);
1961                None
1962            } else {
1963                Some(n)
1964            }
1965        }
1966        LookupResult::Callable(..) => {
1967            if report_error {
1968                ctx.diag.push_error("Cannot bind to a function".into(), &node);
1969            }
1970            None
1971        }
1972        _ => {
1973            ctx.diag.push_error(
1974                "The expression in a two way binding must be a property reference".into(),
1975                &node,
1976            );
1977            None
1978        }
1979    }
1980}
1981
1982/// For connection to callback aliases, some check are to be performed later
1983fn check_callback_alias_validity(
1984    node: &syntax_nodes::CallbackConnection,
1985    elem: &ElementRc,
1986    name: &str,
1987    diag: &mut BuildDiagnostics,
1988) {
1989    let elem_borrow = elem.borrow();
1990    let Some(decl) = elem_borrow.property_declarations.get(name) else {
1991        if let ElementType::Component(c) = &elem_borrow.base_type {
1992            check_callback_alias_validity(node, &c.root_element, name, diag);
1993        }
1994        return;
1995    };
1996    let Some(b) = elem_borrow.bindings.get(name) else { return };
1997    // `try_borrow` because we might be called for the current binding
1998    let Some(alias) = b.try_borrow().ok().and_then(|b| b.two_way_bindings.first().cloned()) else {
1999        return;
2000    };
2001
2002    if alias.element().borrow().base_type == ElementType::Global {
2003        diag.push_error(
2004            "Can't assign a local callback handler to an alias to a global callback".into(),
2005            &node.child_token(SyntaxKind::Identifier).unwrap(),
2006        );
2007    }
2008    if let Type::Callback(callback) = &decl.property_type {
2009        let num_arg = node.DeclaredIdentifier().count();
2010        if num_arg > callback.args.len() {
2011            diag.push_error(
2012                format!(
2013                    "'{name}' only has {} arguments, but {num_arg} were provided",
2014                    callback.args.len(),
2015                ),
2016                &node.child_token(SyntaxKind::Identifier).unwrap(),
2017            );
2018        }
2019    }
2020}