Skip to main content

i_slint_compiler/passes/
resolving.rs

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