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    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                Self::common_target_type_for_type_list([lhs.ty(), rhs.ty()].iter().cloned())
1003            }
1004            OperatorClass::LogicalOp => Type::Bool,
1005            OperatorClass::ArithmeticOp => {
1006                let (lhs_ty, rhs_ty) = (lhs.ty(), rhs.ty());
1007                if op == '+' && (lhs_ty == Type::String || rhs_ty == Type::String) {
1008                    Type::String
1009                } else if op == '+' || op == '-' {
1010                    if lhs_ty.default_unit().is_some() {
1011                        lhs_ty
1012                    } else if rhs_ty.default_unit().is_some() {
1013                        rhs_ty
1014                    } else if matches!(lhs_ty, Type::UnitProduct(_)) {
1015                        lhs_ty
1016                    } else if matches!(rhs_ty, Type::UnitProduct(_)) {
1017                        rhs_ty
1018                    } else {
1019                        Type::Float32
1020                    }
1021                } else if op == '*' || op == '/' {
1022                    let has_unit = |ty: &Type| {
1023                        matches!(ty, Type::UnitProduct(_)) || ty.default_unit().is_some()
1024                    };
1025                    match (has_unit(&lhs_ty), has_unit(&rhs_ty)) {
1026                        (true, true) => {
1027                            return Expression::BinaryExpression {
1028                                lhs: Box::new(lhs),
1029                                rhs: Box::new(rhs),
1030                                op,
1031                            }
1032                        }
1033                        (true, false) => {
1034                            return Expression::BinaryExpression {
1035                                lhs: Box::new(lhs),
1036                                rhs: Box::new(rhs.maybe_convert_to(
1037                                    Type::Float32,
1038                                    &rhs_n,
1039                                    ctx.diag,
1040                                )),
1041                                op,
1042                            }
1043                        }
1044                        (false, true) => {
1045                            return Expression::BinaryExpression {
1046                                lhs: Box::new(lhs.maybe_convert_to(
1047                                    Type::Float32,
1048                                    &lhs_n,
1049                                    ctx.diag,
1050                                )),
1051                                rhs: Box::new(rhs),
1052                                op,
1053                            }
1054                        }
1055                        (false, false) => Type::Float32,
1056                    }
1057                } else {
1058                    unreachable!()
1059                }
1060            }
1061        };
1062        Expression::BinaryExpression {
1063            lhs: Box::new(lhs.maybe_convert_to(expected_ty.clone(), &lhs_n, ctx.diag)),
1064            rhs: Box::new(rhs.maybe_convert_to(expected_ty, &rhs_n, ctx.diag)),
1065            op,
1066        }
1067    }
1068
1069    fn from_unaryop_expression_node(
1070        node: syntax_nodes::UnaryOpExpression,
1071        ctx: &mut LookupCtx,
1072    ) -> Expression {
1073        let exp_n = node.Expression();
1074        let exp = Self::from_expression_node(exp_n, ctx);
1075
1076        let op = node
1077            .children_with_tokens()
1078            .find_map(|n| match n.kind() {
1079                SyntaxKind::Plus => Some('+'),
1080                SyntaxKind::Minus => Some('-'),
1081                SyntaxKind::Bang => Some('!'),
1082                _ => None,
1083            })
1084            .unwrap_or('_');
1085
1086        let exp = match op {
1087            '!' => exp.maybe_convert_to(Type::Bool, &node, ctx.diag),
1088            '+' | '-' => {
1089                let ty = exp.ty();
1090                if ty.default_unit().is_none()
1091                    && !matches!(
1092                        ty,
1093                        Type::Int32
1094                            | Type::Float32
1095                            | Type::Percent
1096                            | Type::UnitProduct(..)
1097                            | Type::Invalid
1098                    )
1099                {
1100                    ctx.diag.push_error(format!("Unary '{op}' not supported on {ty}"), &node);
1101                }
1102                exp
1103            }
1104            _ => {
1105                assert!(ctx.diag.has_errors());
1106                exp
1107            }
1108        };
1109
1110        Expression::UnaryOp { sub: Box::new(exp), op }
1111    }
1112
1113    fn from_conditional_expression_node(
1114        node: syntax_nodes::ConditionalExpression,
1115        ctx: &mut LookupCtx,
1116    ) -> Expression {
1117        let (condition_n, true_expr_n, false_expr_n) = node.Expression();
1118        // FIXME: we should we add bool to the context
1119        let condition = Self::from_expression_node(condition_n.clone(), ctx).maybe_convert_to(
1120            Type::Bool,
1121            &condition_n,
1122            ctx.diag,
1123        );
1124        let true_expr = Self::from_expression_node(true_expr_n.clone(), ctx);
1125        let false_expr = Self::from_expression_node(false_expr_n.clone(), ctx);
1126        let result_ty = Self::common_target_type_for_type_list(
1127            [true_expr.ty(), false_expr.ty()].iter().cloned(),
1128        );
1129        let true_expr = true_expr.maybe_convert_to(result_ty.clone(), &true_expr_n, ctx.diag);
1130        let false_expr = false_expr.maybe_convert_to(result_ty, &false_expr_n, ctx.diag);
1131        Expression::Condition {
1132            condition: Box::new(condition),
1133            true_expr: Box::new(true_expr),
1134            false_expr: Box::new(false_expr),
1135        }
1136    }
1137
1138    fn from_index_expression_node(
1139        node: syntax_nodes::IndexExpression,
1140        ctx: &mut LookupCtx,
1141    ) -> Expression {
1142        let (array_expr_n, index_expr_n) = node.Expression();
1143        let array_expr = Self::from_expression_node(array_expr_n, ctx);
1144        let index_expr = Self::from_expression_node(index_expr_n.clone(), ctx).maybe_convert_to(
1145            Type::Int32,
1146            &index_expr_n,
1147            ctx.diag,
1148        );
1149
1150        let ty = array_expr.ty();
1151        if !matches!(ty, Type::Array(_) | Type::Invalid | Type::Function(_) | Type::Callback(_)) {
1152            ctx.diag.push_error(format!("{ty} is not an indexable type"), &node);
1153        }
1154        Expression::ArrayIndex { array: Box::new(array_expr), index: Box::new(index_expr) }
1155    }
1156
1157    fn from_object_literal_node(
1158        node: syntax_nodes::ObjectLiteral,
1159        ctx: &mut LookupCtx,
1160    ) -> Expression {
1161        let values: HashMap<SmolStr, Expression> = node
1162            .ObjectMember()
1163            .map(|n| {
1164                (
1165                    identifier_text(&n).unwrap_or_default(),
1166                    Expression::from_expression_node(n.Expression(), ctx),
1167                )
1168            })
1169            .collect();
1170        let ty = Rc::new(Struct {
1171            fields: values.iter().map(|(k, v)| (k.clone(), v.ty())).collect(),
1172            name: None,
1173            node: None,
1174            rust_attributes: None,
1175        });
1176        Expression::Struct { ty, values }
1177    }
1178
1179    fn from_array_node(node: syntax_nodes::Array, ctx: &mut LookupCtx) -> Expression {
1180        let mut values: Vec<Expression> =
1181            node.Expression().map(|e| Expression::from_expression_node(e, ctx)).collect();
1182
1183        let element_ty = if values.is_empty() {
1184            Type::Void
1185        } else {
1186            Self::common_target_type_for_type_list(values.iter().map(|expr| expr.ty()))
1187        };
1188
1189        for e in values.iter_mut() {
1190            *e = core::mem::replace(e, Expression::Invalid).maybe_convert_to(
1191                element_ty.clone(),
1192                &node,
1193                ctx.diag,
1194            );
1195        }
1196
1197        Expression::Array { element_ty, values }
1198    }
1199
1200    fn from_string_template_node(
1201        node: syntax_nodes::StringTemplate,
1202        ctx: &mut LookupCtx,
1203    ) -> Expression {
1204        let mut exprs = node.Expression().map(|e| {
1205            Expression::from_expression_node(e.clone(), ctx).maybe_convert_to(
1206                Type::String,
1207                &e,
1208                ctx.diag,
1209            )
1210        });
1211        let mut result = exprs.next().unwrap_or_default();
1212        for x in exprs {
1213            result = Expression::BinaryExpression {
1214                lhs: Box::new(std::mem::take(&mut result)),
1215                rhs: Box::new(x),
1216                op: '+',
1217            }
1218        }
1219        result
1220    }
1221
1222    /// This function is used to find a type that's suitable for casting each instance of a bunch of expressions
1223    /// to a type that captures most aspects. For example for an array of object literals the result is a merge of
1224    /// all seen fields.
1225    pub fn common_target_type_for_type_list(types: impl Iterator<Item = Type>) -> Type {
1226        types.fold(Type::Invalid, |target_type, expr_ty| {
1227            if target_type == expr_ty {
1228                target_type
1229            } else if target_type == Type::Invalid {
1230                expr_ty
1231            } else {
1232                match (target_type, expr_ty) {
1233                    (Type::Struct(ref result), Type::Struct(ref elem)) => {
1234                        let mut fields = result.fields.clone();
1235                        for (elem_name, elem_ty) in elem.fields.iter() {
1236                            match fields.entry(elem_name.clone()) {
1237                                std::collections::btree_map::Entry::Vacant(free_entry) => {
1238                                    free_entry.insert(elem_ty.clone());
1239                                }
1240                                std::collections::btree_map::Entry::Occupied(
1241                                    mut existing_field,
1242                                ) => {
1243                                    *existing_field.get_mut() =
1244                                        Self::common_target_type_for_type_list(
1245                                            [existing_field.get().clone(), elem_ty.clone()]
1246                                                .into_iter(),
1247                                        );
1248                                }
1249                            }
1250                        }
1251                        Type::Struct(Rc::new(Struct {
1252                            name: result.name.as_ref().or(elem.name.as_ref()).cloned(),
1253                            fields,
1254                            node: result.node.as_ref().or(elem.node.as_ref()).cloned(),
1255                            rust_attributes: result
1256                                .rust_attributes
1257                                .as_ref()
1258                                .or(elem.rust_attributes.as_ref())
1259                                .cloned(),
1260                        }))
1261                    }
1262                    (Type::Array(lhs), Type::Array(rhs)) => Type::Array(if *lhs == Type::Void {
1263                        rhs
1264                    } else if *rhs == Type::Void {
1265                        lhs
1266                    } else {
1267                        Self::common_target_type_for_type_list(
1268                            [(*lhs).clone(), (*rhs).clone()].into_iter(),
1269                        )
1270                        .into()
1271                    }),
1272                    (Type::Color, Type::Brush) | (Type::Brush, Type::Color) => Type::Brush,
1273                    (target_type, expr_ty) => {
1274                        if expr_ty.can_convert(&target_type) {
1275                            target_type
1276                        } else if target_type.can_convert(&expr_ty)
1277                            || (expr_ty.default_unit().is_some()
1278                                && matches!(target_type, Type::Float32 | Type::Int32))
1279                        {
1280                            // in the or case: The `0` literal.
1281                            expr_ty
1282                        } else {
1283                            // otherwise, use the target type and let further conversion report an error
1284                            target_type
1285                        }
1286                    }
1287                }
1288            }
1289        })
1290    }
1291}
1292
1293/// Perform the lookup
1294fn lookup_qualified_name_node(
1295    node: syntax_nodes::QualifiedName,
1296    ctx: &mut LookupCtx,
1297    phase: LookupPhase,
1298) -> Option<LookupResult> {
1299    let mut it = node
1300        .children_with_tokens()
1301        .filter(|n| n.kind() == SyntaxKind::Identifier)
1302        .filter_map(|n| n.into_token());
1303
1304    let first = if let Some(first) = it.next() {
1305        first
1306    } else {
1307        // There must be at least one member (parser should ensure that)
1308        debug_assert!(ctx.diag.has_errors());
1309        return None;
1310    };
1311
1312    ctx.current_token = Some(first.clone().into());
1313    let first_str = crate::parser::normalize_identifier(first.text());
1314    let global_lookup = crate::lookup::global_lookup();
1315    let result = match global_lookup.lookup(ctx, &first_str) {
1316        None => {
1317            if let Some(minus_pos) = first.text().find('-') {
1318                // Attempt to recover if the user wanted to write "-" for minus
1319                let first_str = &first.text()[0..minus_pos];
1320                if global_lookup
1321                    .lookup(ctx, &crate::parser::normalize_identifier(first_str))
1322                    .is_some()
1323                {
1324                    ctx.diag.push_error(format!("Unknown unqualified identifier '{}'. Use space before the '-' if you meant a subtraction", first.text()), &node);
1325                    return None;
1326                }
1327            }
1328            for (prefix, e) in
1329                [("self", ctx.component_scope.last()), ("root", ctx.component_scope.first())]
1330            {
1331                if let Some(e) = e {
1332                    if e.lookup(ctx, &first_str).is_some() {
1333                        ctx.diag.push_error(format!("Unknown unqualified identifier '{0}'. Did you mean '{prefix}.{0}'?", first.text()), &node);
1334                        return None;
1335                    }
1336                }
1337            }
1338
1339            if it.next().is_some() {
1340                ctx.diag.push_error(format!("Cannot access id '{}'", first.text()), &node);
1341            } else {
1342                ctx.diag.push_error(
1343                    format!("Unknown unqualified identifier '{}'", first.text()),
1344                    &node,
1345                );
1346            }
1347            return None;
1348        }
1349        Some(x) => x,
1350    };
1351
1352    if let Some(depr) = result.deprecated() {
1353        ctx.diag.push_property_deprecation_warning(&first_str, depr, &first);
1354    }
1355
1356    match result {
1357        LookupResult::Expression { expression: Expression::ElementReference(e), .. } => {
1358            continue_lookup_within_element(&e.upgrade().unwrap(), &mut it, node, ctx)
1359        }
1360        LookupResult::Expression {
1361            expression: Expression::RepeaterModelReference { .. }, ..
1362        } if matches!(phase, LookupPhase::ResolvingTwoWayBindings) => {
1363            ctx.diag.push_error(
1364                "Two-way bindings to model data is not supported yet".to_string(),
1365                &node,
1366            );
1367            None
1368        }
1369        result => maybe_lookup_object(result, it, ctx),
1370    }
1371}
1372
1373fn continue_lookup_within_element(
1374    elem: &ElementRc,
1375    it: &mut impl Iterator<Item = crate::parser::SyntaxToken>,
1376    node: syntax_nodes::QualifiedName,
1377    ctx: &mut LookupCtx,
1378) -> Option<LookupResult> {
1379    let second = if let Some(second) = it.next() {
1380        second
1381    } else if matches!(ctx.property_type, Type::ElementReference) {
1382        return Some(Expression::ElementReference(Rc::downgrade(elem)).into());
1383    } else {
1384        // Try to recover in case we wanted to access a property
1385        let mut rest = String::new();
1386        if let Some(LookupResult::Expression {
1387            expression: Expression::PropertyReference(nr),
1388            ..
1389        }) = crate::lookup::InScopeLookup.lookup(ctx, &elem.borrow().id)
1390        {
1391            let e = nr.element();
1392            let e_borrowed = e.borrow();
1393            let mut id = e_borrowed.id.as_str();
1394            if id.is_empty() {
1395                if ctx.component_scope.last().is_some_and(|x| Rc::ptr_eq(&e, x)) {
1396                    id = "self";
1397                } else if ctx.component_scope.first().is_some_and(|x| Rc::ptr_eq(&e, x)) {
1398                    id = "root";
1399                } else if ctx.component_scope.iter().nth_back(1).is_some_and(|x| Rc::ptr_eq(&e, x))
1400                {
1401                    id = "parent";
1402                }
1403            };
1404            if !id.is_empty() {
1405                rest =
1406                    format!(". Use '{id}.{}' to access the property with the same name", nr.name());
1407            }
1408        } else if let Some(LookupResult::Expression {
1409            expression: Expression::EnumerationValue(value),
1410            ..
1411        }) = crate::lookup::ReturnTypeSpecificLookup.lookup(ctx, &elem.borrow().id)
1412        {
1413            rest = format!(
1414                ". Use '{}.{value}' to access the enumeration value",
1415                value.enumeration.name
1416            );
1417        }
1418        ctx.diag.push_error(format!("Cannot take reference of an element{rest}"), &node);
1419        return None;
1420    };
1421    let prop_name = crate::parser::normalize_identifier(second.text());
1422
1423    let lookup_result = elem.borrow().lookup_property(&prop_name);
1424    let local_to_component = lookup_result.is_local_to_component && ctx.is_local_element(elem);
1425
1426    let err = |ctx: &mut LookupCtx, extra: &str| {
1427        let what = match &elem.borrow().base_type {
1428            ElementType::Global => {
1429                let global = elem.borrow().enclosing_component.upgrade().unwrap();
1430                assert!(global.is_global());
1431                format!("'{}'", global.id)
1432            }
1433            ElementType::Component(c) => format!("Element '{}'", c.id),
1434            ElementType::Builtin(b) => format!("Element '{}'", b.name),
1435            ElementType::Native(_) => unreachable!("the native pass comes later"),
1436            ElementType::Error => {
1437                assert!(ctx.diag.has_errors());
1438                return;
1439            }
1440        };
1441        ctx.diag.push_error(
1442            format!("{} does not have a property '{}'{}", what, second.text(), extra),
1443            &second,
1444        );
1445    };
1446
1447    if lookup_result.property_type.is_property_type() {
1448        if !local_to_component && lookup_result.property_visibility == PropertyVisibility::Private {
1449            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);
1450            return None;
1451        } else if lookup_result.property_visibility == PropertyVisibility::Fake {
1452            ctx.diag.push_error(
1453                "This special property can only be used to make a binding and cannot be accessed"
1454                    .to_string(),
1455                &second,
1456            );
1457            return None;
1458        } else if lookup_result.resolved_name != prop_name.as_str() {
1459            ctx.diag.push_property_deprecation_warning(
1460                &prop_name,
1461                &lookup_result.resolved_name,
1462                &second,
1463            );
1464        } else {
1465            match crate::lookup::check_deprecated_stylemetrics(elem, ctx, &prop_name) {
1466                crate::lookup::StyleMetricsPropertyUse::Acceptable => {}
1467                crate::lookup::StyleMetricsPropertyUse::Deprecated(deprecated) => {
1468                    ctx.diag.push_property_deprecation_warning(&prop_name, &deprecated, &second)
1469                }
1470                crate::lookup::StyleMetricsPropertyUse::Unacceptable => {
1471                    err(ctx, "");
1472                    return None;
1473                }
1474            }
1475        }
1476        let prop = Expression::PropertyReference(NamedReference::new(
1477            elem,
1478            lookup_result.resolved_name.to_smolstr(),
1479        ));
1480        maybe_lookup_object(prop.into(), it, ctx)
1481    } else if matches!(lookup_result.property_type, Type::Callback { .. }) {
1482        if let Some(x) = it.next() {
1483            ctx.diag.push_error("Cannot access fields of callback".into(), &x)
1484        }
1485        Some(LookupResult::Callable(LookupResultCallable::Callable(Callable::Callback(
1486            NamedReference::new(elem, lookup_result.resolved_name.to_smolstr()),
1487        ))))
1488    } else if let Type::Function(fun) = lookup_result.property_type {
1489        if lookup_result.property_visibility == PropertyVisibility::Private && !local_to_component {
1490            let message = format!("The function '{}' is private. Annotate it with 'public' to make it accessible from other components", second.text());
1491            if !lookup_result.is_local_to_component {
1492                ctx.diag.push_error(message, &second);
1493            } else {
1494                ctx.diag.push_warning(message+". Note: this used to be allowed in previous version, but this should be considered an error", &second);
1495            }
1496        } else if lookup_result.property_visibility == PropertyVisibility::Protected
1497            && !local_to_component
1498            && !(lookup_result.is_in_direct_base
1499                && ctx.component_scope.first().is_some_and(|x| Rc::ptr_eq(x, elem)))
1500        {
1501            ctx.diag.push_error(format!("The function '{}' is protected", second.text()), &second);
1502        }
1503        if let Some(x) = it.next() {
1504            ctx.diag.push_error("Cannot access fields of a function".into(), &x)
1505        }
1506        let callable = match lookup_result.builtin_function {
1507            Some(builtin) => Callable::Builtin(builtin),
1508            None => Callable::Function(NamedReference::new(
1509                elem,
1510                lookup_result.resolved_name.to_smolstr(),
1511            )),
1512        };
1513        if matches!(fun.args.first(), Some(Type::ElementReference)) {
1514            LookupResult::Callable(LookupResultCallable::MemberFunction {
1515                base: Expression::ElementReference(Rc::downgrade(elem)),
1516                base_node: Some(NodeOrToken::Node(node.into())),
1517                member: Box::new(LookupResultCallable::Callable(callable)),
1518            })
1519            .into()
1520        } else {
1521            LookupResult::from(callable).into()
1522        }
1523    } else {
1524        if let Some(minus_pos) = second.text().find('-') {
1525            // Attempt to recover if the user wanted to write "-"
1526            if elem
1527                .borrow()
1528                .lookup_property(&crate::parser::normalize_identifier(&second.text()[0..minus_pos]))
1529                .property_type
1530                != Type::Invalid
1531            {
1532                err(ctx, ". Use space before the '-' if you meant a subtraction");
1533                return None;
1534            }
1535        }
1536        err(ctx, "");
1537        None
1538    }
1539}
1540
1541fn maybe_lookup_object(
1542    mut base: LookupResult,
1543    it: impl Iterator<Item = crate::parser::SyntaxToken>,
1544    ctx: &mut LookupCtx,
1545) -> Option<LookupResult> {
1546    for next in it {
1547        let next_str = crate::parser::normalize_identifier(next.text());
1548        ctx.current_token = Some(next.clone().into());
1549        match base.lookup(ctx, &next_str) {
1550            Some(r) => {
1551                base = r;
1552            }
1553            None => {
1554                if let Some(minus_pos) = next.text().find('-') {
1555                    if base.lookup(ctx, &SmolStr::new(&next.text()[0..minus_pos])).is_some() {
1556                        ctx.diag.push_error(format!("Cannot access the field '{}'. Use space before the '-' if you meant a subtraction", next.text()), &next);
1557                        return None;
1558                    }
1559                }
1560
1561                match base {
1562                    LookupResult::Callable(LookupResultCallable::Callable(Callable::Callback(
1563                        ..,
1564                    ))) => ctx.diag.push_error("Cannot access fields of callback".into(), &next),
1565                    LookupResult::Callable(..) => {
1566                        ctx.diag.push_error("Cannot access fields of a function".into(), &next)
1567                    }
1568                    LookupResult::Enumeration(enumeration) => ctx.diag.push_error(
1569                        format!(
1570                            "'{}' is not a member of the enum {}",
1571                            next.text(),
1572                            enumeration.name
1573                        ),
1574                        &next,
1575                    ),
1576
1577                    LookupResult::Namespace(ns) => {
1578                        ctx.diag.push_error(
1579                            format!("'{}' is not a member of the namespace {}", next.text(), ns),
1580                            &next,
1581                        );
1582                    }
1583                    LookupResult::Expression { expression, .. } => {
1584                        let ty_descr = match expression.ty() {
1585                            Type::Struct { .. } => String::new(),
1586                            Type::Float32
1587                                if ctx.property_type == Type::Model
1588                                    && matches!(
1589                                        expression,
1590                                        Expression::NumberLiteral(_, Unit::None),
1591                                    ) =>
1592                            {
1593                                // usually something like `0..foo`
1594                                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())
1595                            }
1596
1597                            ty => format!(" of {ty}"),
1598                        };
1599                        ctx.diag.push_error(
1600                            format!("Cannot access the field '{}'{}", next.text(), ty_descr),
1601                            &next,
1602                        );
1603                    }
1604                }
1605                return None;
1606            }
1607        }
1608    }
1609    Some(base)
1610}
1611
1612/// Go through all the two way binding and resolve them first
1613fn resolve_two_way_bindings(
1614    doc: &Document,
1615    type_register: &TypeRegister,
1616    diag: &mut BuildDiagnostics,
1617) {
1618    for component in doc.inner_components.iter() {
1619        recurse_elem_with_scope(
1620            &component.root_element,
1621            ComponentScope(vec![]),
1622            &mut |elem, scope| {
1623                for (prop_name, binding) in &elem.borrow().bindings {
1624                    let mut binding = binding.borrow_mut();
1625                    if let Expression::Uncompiled(node) =
1626                        binding.expression.ignore_debug_hooks().clone()
1627                    {
1628                        if let Some(n) = syntax_nodes::TwoWayBinding::new(node.clone()) {
1629                            let lhs_lookup = elem.borrow().lookup_property(prop_name);
1630                            if !lhs_lookup.is_valid() {
1631                                // An attempt to resolve this already failed when trying to resolve the property type
1632                                assert!(diag.has_errors());
1633                                continue;
1634                            }
1635                            let mut lookup_ctx = LookupCtx {
1636                                property_name: Some(prop_name.as_str()),
1637                                property_type: lhs_lookup.property_type.clone(),
1638                                component_scope: &scope.0,
1639                                diag,
1640                                arguments: vec![],
1641                                type_register,
1642                                type_loader: None,
1643                                current_token: Some(node.clone().into()),
1644                            };
1645
1646                            binding.expression = Expression::Invalid;
1647
1648                            if let Some(nr) = resolve_two_way_binding(n, &mut lookup_ctx) {
1649                                binding.two_way_bindings.push(nr.clone());
1650
1651                                nr.element()
1652                                    .borrow()
1653                                    .property_analysis
1654                                    .borrow_mut()
1655                                    .entry(nr.name().clone())
1656                                    .or_default()
1657                                    .is_linked = true;
1658
1659                                if matches!(
1660                                    lhs_lookup.property_visibility,
1661                                    PropertyVisibility::Private | PropertyVisibility::Output
1662                                ) && !lhs_lookup.is_local_to_component
1663                                {
1664                                    // invalid property assignment should have been reported earlier
1665                                    assert!(diag.has_errors() || elem.borrow().is_legacy_syntax);
1666                                    continue;
1667                                }
1668
1669                                // Check the compatibility.
1670                                let mut rhs_lookup =
1671                                    nr.element().borrow().lookup_property(nr.name());
1672                                if rhs_lookup.property_type == Type::Invalid {
1673                                    // An attempt to resolve this already failed when trying to resolve the property type
1674                                    assert!(diag.has_errors());
1675                                    continue;
1676                                }
1677                                rhs_lookup.is_local_to_component &=
1678                                    lookup_ctx.is_local_element(&nr.element());
1679
1680                                if !rhs_lookup.is_valid_for_assignment() {
1681                                    match (
1682                                        lhs_lookup.property_visibility,
1683                                        rhs_lookup.property_visibility,
1684                                    ) {
1685                                        (PropertyVisibility::Input, PropertyVisibility::Input)
1686                                            if !lhs_lookup.is_local_to_component =>
1687                                        {
1688                                            assert!(rhs_lookup.is_local_to_component);
1689                                            marked_linked_read_only(elem, prop_name);
1690                                        }
1691                                        (
1692                                            PropertyVisibility::Output
1693                                            | PropertyVisibility::Private,
1694                                            PropertyVisibility::Output | PropertyVisibility::Input,
1695                                        ) => {
1696                                            assert!(lhs_lookup.is_local_to_component);
1697                                            marked_linked_read_only(elem, prop_name);
1698                                        }
1699                                        (PropertyVisibility::Input, PropertyVisibility::Output)
1700                                            if !lhs_lookup.is_local_to_component =>
1701                                        {
1702                                            assert!(!rhs_lookup.is_local_to_component);
1703                                            marked_linked_read_only(elem, prop_name);
1704                                        }
1705                                        _ => {
1706                                            if lookup_ctx.is_legacy_component() {
1707                                                diag.push_warning(
1708                                                    format!(
1709                                                        "Link to a {} property is deprecated",
1710                                                        rhs_lookup.property_visibility
1711                                                    ),
1712                                                    &node,
1713                                                );
1714                                            } else {
1715                                                diag.push_error(
1716                                                    format!(
1717                                                        "Cannot link to a {} property",
1718                                                        rhs_lookup.property_visibility
1719                                                    ),
1720                                                    &node,
1721                                                )
1722                                            }
1723                                        }
1724                                    }
1725                                } else if !lhs_lookup.is_valid_for_assignment() {
1726                                    if rhs_lookup.is_local_to_component
1727                                        && rhs_lookup.property_visibility
1728                                            == PropertyVisibility::InOut
1729                                    {
1730                                        if lookup_ctx.is_legacy_component() {
1731                                            debug_assert!(!diag.is_empty()); // warning should already be reported
1732                                        } else {
1733                                            diag.push_error(
1734                                                "Cannot link input property".into(),
1735                                                &node,
1736                                            );
1737                                        }
1738                                    } else if rhs_lookup.property_visibility
1739                                        == PropertyVisibility::InOut
1740                                    {
1741                                        diag.push_warning("Linking input properties to input output properties is deprecated".into(), &node);
1742                                        marked_linked_read_only(&nr.element(), nr.name());
1743                                    } else {
1744                                        // This is allowed, but then the rhs must also become read only.
1745                                        marked_linked_read_only(&nr.element(), nr.name());
1746                                    }
1747                                }
1748                            }
1749                        }
1750                    }
1751                }
1752            },
1753        );
1754    }
1755
1756    fn marked_linked_read_only(elem: &ElementRc, prop_name: &str) {
1757        elem.borrow()
1758            .property_analysis
1759            .borrow_mut()
1760            .entry(prop_name.into())
1761            .or_default()
1762            .is_linked_to_read_only = true;
1763    }
1764}
1765
1766pub fn resolve_two_way_binding(
1767    node: syntax_nodes::TwoWayBinding,
1768    ctx: &mut LookupCtx,
1769) -> Option<NamedReference> {
1770    let Some(n) = node.Expression().QualifiedName() else {
1771        ctx.diag.push_error(
1772            "The expression in a two way binding must be a property reference".into(),
1773            &node.Expression(),
1774        );
1775        return None;
1776    };
1777
1778    let Some(r) = lookup_qualified_name_node(n, ctx, LookupPhase::ResolvingTwoWayBindings) else {
1779        assert!(ctx.diag.has_errors());
1780        return None;
1781    };
1782
1783    // If type is invalid, error has already been reported,  when inferring, the error will be reported by the inferring code
1784    let report_error = !matches!(
1785        ctx.property_type,
1786        Type::InferredProperty | Type::InferredCallback | Type::Invalid
1787    );
1788    match r {
1789        LookupResult::Expression { expression: Expression::PropertyReference(n), .. } => {
1790            if report_error && n.ty() != ctx.property_type {
1791                ctx.diag.push_error(
1792                    "The property does not have the same type as the bound property".into(),
1793                    &node,
1794                );
1795            }
1796            Some(n)
1797        }
1798        LookupResult::Callable(LookupResultCallable::Callable(Callable::Callback(n))) => {
1799            if report_error && n.ty() != ctx.property_type {
1800                ctx.diag.push_error("Cannot bind to a callback".into(), &node);
1801                None
1802            } else {
1803                Some(n)
1804            }
1805        }
1806        LookupResult::Callable(..) => {
1807            if report_error {
1808                ctx.diag.push_error("Cannot bind to a function".into(), &node);
1809            }
1810            None
1811        }
1812        _ => {
1813            ctx.diag.push_error(
1814                "The expression in a two way binding must be a property reference".into(),
1815                &node,
1816            );
1817            None
1818        }
1819    }
1820}
1821
1822/// For connection to callback aliases, some check are to be performed later
1823fn check_callback_alias_validity(
1824    node: &syntax_nodes::CallbackConnection,
1825    elem: &ElementRc,
1826    name: &str,
1827    diag: &mut BuildDiagnostics,
1828) {
1829    let elem_borrow = elem.borrow();
1830    let Some(decl) = elem_borrow.property_declarations.get(name) else {
1831        if let ElementType::Component(c) = &elem_borrow.base_type {
1832            check_callback_alias_validity(node, &c.root_element, name, diag);
1833        }
1834        return;
1835    };
1836    let Some(b) = elem_borrow.bindings.get(name) else { return };
1837    // `try_borrow` because we might be called for the current binding
1838    let Some(alias) = b.try_borrow().ok().and_then(|b| b.two_way_bindings.first().cloned()) else {
1839        return;
1840    };
1841
1842    if alias.element().borrow().base_type == ElementType::Global {
1843        diag.push_error(
1844            "Can't assign a local callback handler to an alias to a global callback".into(),
1845            &node.child_token(SyntaxKind::Identifier).unwrap(),
1846        );
1847    }
1848    if let Type::Callback(callback) = &decl.property_type {
1849        let num_arg = node.DeclaredIdentifier().count();
1850        if num_arg > callback.args.len() {
1851            diag.push_error(
1852                format!(
1853                    "'{name}' only has {} arguments, but {num_arg} were provided",
1854                    callback.args.len(),
1855                ),
1856                &node.child_token(SyntaxKind::Identifier).unwrap(),
1857            );
1858        }
1859    }
1860}