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