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