sixtyfps_compilerlib/passes/
resolving.rs

1// Copyright © SixtyFPS GmbH <info@sixtyfps.io>
2// SPDX-License-Identifier: (GPL-3.0-only OR LicenseRef-SixtyFPS-commercial)
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::{PropertyLookupResult, Type};
14use crate::lookup::{LookupCtx, LookupObject, LookupResult};
15use crate::object_tree::*;
16use crate::parser::{identifier_text, syntax_nodes, NodeOrToken, SyntaxKind, SyntaxNode};
17use crate::typeregister::TypeRegister;
18use std::collections::HashMap;
19use std::rc::Rc;
20
21/// This represents a scope for the Component, where Component is the repeated component, but
22/// does not represent a component in the .60 file
23#[derive(Clone)]
24struct ComponentScope(Vec<ElementRc>);
25
26fn resolve_expression(
27    expr: &mut Expression,
28    property_name: Option<&str>,
29    property_type: Type,
30    scope: &ComponentScope,
31    type_register: &TypeRegister,
32    type_loader: &crate::typeloader::TypeLoader,
33    two_ways: &mut Vec<(String, NamedReference)>,
34    diag: &mut BuildDiagnostics,
35) {
36    if let Expression::Uncompiled(node) = expr {
37        let mut lookup_ctx = LookupCtx {
38            property_name,
39            property_type,
40            component_scope: &scope.0,
41            diag,
42            arguments: vec![],
43            type_register,
44            type_loader: Some(type_loader),
45            current_token: None,
46        };
47
48        let new_expr = match node.kind() {
49            SyntaxKind::CallbackConnection => {
50                //FIXME: proper callback support (node is a codeblock)
51                Expression::from_callback_connection(node.clone().into(), &mut lookup_ctx)
52            }
53            SyntaxKind::Expression => {
54                //FIXME again: this happen for non-binding expression (i.e: model)
55                Expression::from_expression_node(node.clone().into(), &mut lookup_ctx)
56                    .maybe_convert_to(lookup_ctx.property_type.clone(), node, diag)
57            }
58            SyntaxKind::BindingExpression => {
59                Expression::from_binding_expression_node(node.clone(), &mut lookup_ctx)
60            }
61            SyntaxKind::TwoWayBinding => {
62                if lookup_ctx.property_type == Type::Invalid {
63                    // An attempt to resolve this already failed when trying to resolve the property type
64                    assert!(diag.has_error());
65                    return;
66                }
67                if let Some(nr) = resolve_two_way_binding(node.clone().into(), &mut lookup_ctx) {
68                    two_ways.push((property_name.unwrap().into(), nr));
69                }
70                Expression::Invalid
71            }
72            _ => {
73                debug_assert!(diag.has_error());
74                Expression::Invalid
75            }
76        };
77        *expr = new_expr;
78    }
79}
80
81pub fn resolve_expressions(
82    doc: &Document,
83    type_loader: &crate::typeloader::TypeLoader,
84    diag: &mut BuildDiagnostics,
85) {
86    for component in doc.inner_components.iter() {
87        let scope = ComponentScope(vec![component.root_element.clone()]);
88
89        recurse_elem(&component.root_element, &scope, &mut |elem, scope| {
90            let mut new_scope = scope.clone();
91            let mut is_repeated = elem.borrow().repeated.is_some();
92            if is_repeated {
93                new_scope.0.push(elem.clone())
94            }
95            new_scope.0.push(elem.clone());
96            let mut two_ways = vec![];
97            visit_element_expressions(elem, |expr, property_name, property_type| {
98                if is_repeated {
99                    // The first expression is always the model and it needs to be resolved with the parent scope
100                    debug_assert!(elem.borrow().repeated.as_ref().is_none()); // should be none because it is taken by the visit_element_expressions function
101                    let mut parent_scope = scope.clone();
102                    if let Some(parent) = find_parent_element(elem) {
103                        parent_scope.0.push(parent)
104                    };
105                    resolve_expression(
106                        expr,
107                        property_name,
108                        property_type(),
109                        &parent_scope,
110                        &doc.local_registry,
111                        type_loader,
112                        &mut two_ways,
113                        diag,
114                    );
115                    is_repeated = false;
116                } else {
117                    resolve_expression(
118                        expr,
119                        property_name,
120                        property_type(),
121                        &new_scope,
122                        &doc.local_registry,
123                        type_loader,
124                        &mut two_ways,
125                        diag,
126                    )
127                }
128            });
129            for (prop, nr) in two_ways {
130                elem.borrow().bindings.get(&prop).unwrap().borrow_mut().two_way_bindings.push(nr);
131            }
132            new_scope.0.pop();
133            new_scope
134        })
135    }
136}
137
138impl Expression {
139    pub fn from_binding_expression_node(node: SyntaxNode, ctx: &mut LookupCtx) -> Self {
140        debug_assert_eq!(node.kind(), SyntaxKind::BindingExpression);
141        let e = node
142            .child_node(SyntaxKind::Expression)
143            .map(|n| Self::from_expression_node(n.into(), ctx))
144            .or_else(|| {
145                node.child_node(SyntaxKind::CodeBlock)
146                    .map(|c| Self::from_codeblock_node(c.into(), ctx))
147            })
148            .unwrap_or(Self::Invalid);
149        if ctx.property_type == Type::LogicalLength && e.ty() == Type::Percent {
150            // See if a conversion from percentage to length is allowed
151            const RELATIVE_TO_PARENT_PROPERTIES: [&str; 2] = ["width", "height"];
152            let property_name = ctx.property_name.unwrap_or_default();
153            if RELATIVE_TO_PARENT_PROPERTIES.contains(&property_name) {
154                return e;
155            } else {
156                ctx.diag.push_error(
157                    format!(
158                        "Automatic conversion from percentage to length is only possible for the properties {}",
159                        RELATIVE_TO_PARENT_PROPERTIES.join(" and ")
160                    ),
161                    &node
162                );
163                return Expression::Invalid;
164            }
165        };
166        e.maybe_convert_to(ctx.property_type.clone(), &node, ctx.diag)
167    }
168
169    fn from_codeblock_node(node: syntax_nodes::CodeBlock, ctx: &mut LookupCtx) -> Expression {
170        debug_assert_eq!(node.kind(), SyntaxKind::CodeBlock);
171
172        let mut statements_or_exprs = node
173            .children()
174            .filter_map(|n| match n.kind() {
175                SyntaxKind::Expression => Some(Self::from_expression_node(n.into(), ctx)),
176                SyntaxKind::ReturnStatement => Some(Self::from_return_statement(n.into(), ctx)),
177                _ => None,
178            })
179            .collect::<Vec<_>>();
180
181        let exit_points_and_return_types = statements_or_exprs
182            .iter()
183            .enumerate()
184            .filter_map(|(index, statement_or_expr)| {
185                if index == statements_or_exprs.len()
186                    || matches!(statement_or_expr, Expression::ReturnStatement(..))
187                {
188                    Some((index, statement_or_expr.ty()))
189                } else {
190                    None
191                }
192            })
193            .collect::<Vec<_>>();
194
195        let common_return_type = Self::common_target_type_for_type_list(
196            exit_points_and_return_types.iter().map(|(_, ty)| ty.clone()),
197        );
198
199        exit_points_and_return_types.into_iter().for_each(|(index, _)| {
200            let mut expr = std::mem::replace(&mut statements_or_exprs[index], Expression::Invalid);
201            expr = expr.maybe_convert_to(common_return_type.clone(), &node, ctx.diag);
202            statements_or_exprs[index] = expr;
203        });
204
205        Expression::CodeBlock(statements_or_exprs)
206    }
207
208    fn from_return_statement(
209        node: syntax_nodes::ReturnStatement,
210        ctx: &mut LookupCtx,
211    ) -> Expression {
212        let return_type = ctx.return_type().clone();
213        Expression::ReturnStatement(node.Expression().map(|n| {
214            Box::new(Self::from_expression_node(n, ctx).maybe_convert_to(
215                return_type,
216                &node,
217                ctx.diag,
218            ))
219        }))
220    }
221
222    fn from_callback_connection(
223        node: syntax_nodes::CallbackConnection,
224        ctx: &mut LookupCtx,
225    ) -> Expression {
226        ctx.arguments =
227            node.DeclaredIdentifier().map(|x| identifier_text(&x).unwrap_or_default()).collect();
228        Self::from_codeblock_node(node.CodeBlock(), ctx).maybe_convert_to(
229            ctx.return_type().clone(),
230            &node,
231            ctx.diag,
232        )
233    }
234
235    fn from_expression_node(node: syntax_nodes::Expression, ctx: &mut LookupCtx) -> Self {
236        node.Expression()
237            .map(|n| Self::from_expression_node(n, ctx))
238            .or_else(|| node.AtImageUrl().map(|n| Self::from_at_image_url_node(n, ctx)))
239            .or_else(|| node.AtLinearGradient().map(|n| Self::from_at_linear_gradient(n, ctx)))
240            .or_else(|| {
241                node.QualifiedName().map(|n| {
242                    let exp = Self::from_qualified_name_node(n.clone(), ctx);
243                    if matches!(exp.ty(), Type::Function { .. } | Type::Callback { .. }) {
244                        ctx.diag.push_error(
245                            format!(
246                                "'{}' must be called. Did you forgot the '()'?",
247                                QualifiedTypeName::from_node(n.clone())
248                            ),
249                            &n,
250                        )
251                    }
252                    exp
253                })
254            })
255            .or_else(|| {
256                node.child_text(SyntaxKind::StringLiteral).map(|s| {
257                    crate::literals::unescape_string(&s).map(Self::StringLiteral).unwrap_or_else(
258                        || {
259                            ctx.diag.push_error("Cannot parse string literal".into(), &node);
260                            Self::Invalid
261                        },
262                    )
263                })
264            })
265            .or_else(|| {
266                node.child_text(SyntaxKind::NumberLiteral)
267                    .map(crate::literals::parse_number_literal)
268                    .transpose()
269                    .unwrap_or_else(|e| {
270                        ctx.diag.push_error(e, &node);
271                        Some(Self::Invalid)
272                    })
273            })
274            .or_else(|| {
275                node.child_text(SyntaxKind::ColorLiteral).map(|s| {
276                    crate::literals::parse_color_literal(&s)
277                        .map(|i| Expression::Cast {
278                            from: Box::new(Expression::NumberLiteral(i as _, Unit::None)),
279                            to: Type::Color,
280                        })
281                        .unwrap_or_else(|| {
282                            ctx.diag.push_error("Invalid color literal".into(), &node);
283                            Self::Invalid
284                        })
285                })
286            })
287            .or_else(|| {
288                node.FunctionCallExpression().map(|n| Self::from_function_call_node(n, ctx))
289            })
290            .or_else(|| node.MemberAccess().map(|n| Self::from_member_access_node(n, ctx)))
291            .or_else(|| node.IndexExpression().map(|n| Self::from_index_expression_node(n, ctx)))
292            .or_else(|| node.SelfAssignment().map(|n| Self::from_self_assignment_node(n, ctx)))
293            .or_else(|| node.BinaryExpression().map(|n| Self::from_binary_expression_node(n, ctx)))
294            .or_else(|| {
295                node.UnaryOpExpression().map(|n| Self::from_unaryop_expression_node(n, ctx))
296            })
297            .or_else(|| {
298                node.ConditionalExpression().map(|n| Self::from_conditional_expression_node(n, ctx))
299            })
300            .or_else(|| node.ObjectLiteral().map(|n| Self::from_object_literal_node(n, ctx)))
301            .or_else(|| node.Array().map(|n| Self::from_array_node(n, ctx)))
302            .or_else(|| node.CodeBlock().map(|n| Self::from_codeblock_node(n, ctx)))
303            .or_else(|| node.StringTemplate().map(|n| Self::from_string_template_node(n, ctx)))
304            .unwrap_or(Self::Invalid)
305    }
306
307    fn from_at_image_url_node(node: syntax_nodes::AtImageUrl, ctx: &mut LookupCtx) -> Self {
308        let s = match node
309            .child_text(SyntaxKind::StringLiteral)
310            .and_then(|x| crate::literals::unescape_string(&x))
311        {
312            Some(s) => s,
313            None => {
314                ctx.diag.push_error("Cannot parse string literal".into(), &node);
315                return Self::Invalid;
316            }
317        };
318
319        if s.is_empty() {
320            return Expression::ImageReference {
321                resource_ref: ImageReference::None,
322                source_location: Some(node.to_source_location()),
323            };
324        }
325
326        let absolute_source_path = {
327            let path = std::path::Path::new(&s);
328            if path.is_absolute() || s.starts_with("http://") || s.starts_with("https://") {
329                s
330            } else {
331                ctx.type_loader
332                    .map(|loader| {
333                        loader
334                            .resolve_import_path(Some(&(*node).clone().into()), &s)
335                            .0
336                            .to_string_lossy()
337                            .to_string()
338                    })
339                    .unwrap_or(s)
340            }
341        };
342
343        Expression::ImageReference {
344            resource_ref: ImageReference::AbsolutePath(absolute_source_path),
345            source_location: Some(node.to_source_location()),
346        }
347    }
348
349    fn from_at_linear_gradient(node: syntax_nodes::AtLinearGradient, ctx: &mut LookupCtx) -> Self {
350        let mut subs = node
351            .children_with_tokens()
352            .filter(|n| matches!(n.kind(), SyntaxKind::Comma | SyntaxKind::Expression));
353        let angle_expr = match subs.next() {
354            Some(e) if e.kind() == SyntaxKind::Expression => {
355                syntax_nodes::Expression::from(e.into_node().unwrap())
356            }
357            _ => {
358                ctx.diag.push_error("Expected angle expression".into(), &node);
359                return Expression::Invalid;
360            }
361        };
362        if subs.next().map_or(false, |s| s.kind() != SyntaxKind::Comma) {
363            ctx.diag
364                .push_error("Angle expression must be an angle followed by a comma".into(), &node);
365            return Expression::Invalid;
366        }
367        let angle =
368            Box::new(Expression::from_expression_node(angle_expr.clone(), ctx).maybe_convert_to(
369                Type::Angle,
370                &angle_expr,
371                ctx.diag,
372            ));
373
374        let mut stops = vec![];
375        enum Stop {
376            Empty,
377            Color(Expression),
378            Finished,
379        }
380        let mut current_stop = Stop::Empty;
381        for n in subs {
382            if n.kind() == SyntaxKind::Comma {
383                match std::mem::replace(&mut current_stop, Stop::Empty) {
384                    Stop::Empty => {
385                        ctx.diag.push_error("Expected expression".into(), &n);
386                        break;
387                    }
388                    Stop::Finished => {}
389                    Stop::Color(col) => stops.push((
390                        col,
391                        if stops.is_empty() {
392                            Expression::NumberLiteral(0., Unit::None)
393                        } else {
394                            Expression::Invalid
395                        },
396                    )),
397                }
398            } else {
399                // To facilitate color literal conversion, adjust the expected return type.
400                let e = {
401                    let old_property_type = std::mem::replace(&mut ctx.property_type, Type::Color);
402                    let e =
403                        Expression::from_expression_node(n.as_node().unwrap().clone().into(), ctx);
404                    ctx.property_type = old_property_type;
405                    e
406                };
407                match std::mem::replace(&mut current_stop, Stop::Finished) {
408                    Stop::Empty => {
409                        current_stop = Stop::Color(e.maybe_convert_to(Type::Color, &n, ctx.diag))
410                    }
411                    Stop::Finished => {
412                        ctx.diag.push_error("Expected comma".into(), &n);
413                        break;
414                    }
415                    Stop::Color(col) => {
416                        stops.push((col, e.maybe_convert_to(Type::Float32, &n, ctx.diag)))
417                    }
418                }
419            }
420        }
421        match current_stop {
422            Stop::Color(col) => stops.push((col, Expression::NumberLiteral(1., Unit::None))),
423            Stop::Empty => {
424                if let Some((_, e @ Expression::Invalid)) = stops.last_mut() {
425                    *e = Expression::NumberLiteral(1., Unit::None)
426                }
427            }
428            Stop::Finished => (),
429        };
430
431        // Fix the stop so each has a position.
432        let mut start = 0;
433        while start < stops.len() {
434            start += match stops[start..].iter().position(|s| matches!(s.1, Expression::Invalid)) {
435                Some(p) => p,
436                None => break,
437            };
438            let (before, rest) = stops.split_at_mut(start);
439            let pos =
440                rest.iter().position(|s| !matches!(s.1, Expression::Invalid)).unwrap_or(rest.len());
441            if pos > 0 {
442                let (middle, after) = rest.split_at_mut(pos);
443                let begin = &before.last().expect("The first should never be invalid").1;
444                let end = &after.last().expect("The last should never be invalid").1;
445                for (i, (_, e)) in middle.iter_mut().enumerate() {
446                    debug_assert!(matches!(e, Expression::Invalid));
447                    // e = begin + (i+1) * (end - begin) / (pos+1)
448                    *e = Expression::BinaryExpression {
449                        lhs: Box::new(begin.clone()),
450                        rhs: Box::new(Expression::BinaryExpression {
451                            lhs: Box::new(Expression::BinaryExpression {
452                                lhs: Box::new(Expression::NumberLiteral(i as f64 + 1., Unit::None)),
453                                rhs: Box::new(Expression::BinaryExpression {
454                                    lhs: Box::new(end.clone()),
455                                    rhs: Box::new(begin.clone()),
456                                    op: '-',
457                                }),
458                                op: '*',
459                            }),
460                            rhs: Box::new(Expression::NumberLiteral(pos as f64 + 1., Unit::None)),
461                            op: '/',
462                        }),
463                        op: '+',
464                    };
465                }
466            }
467            start += pos + 1;
468        }
469
470        Expression::LinearGradient { angle, stops }
471    }
472
473    /// Perform the lookup
474    fn from_qualified_name_node(node: syntax_nodes::QualifiedName, ctx: &mut LookupCtx) -> Self {
475        let mut it = node
476            .children_with_tokens()
477            .filter(|n| n.kind() == SyntaxKind::Identifier)
478            .filter_map(|n| n.into_token());
479
480        let first = if let Some(first) = it.next() {
481            first
482        } else {
483            // There must be at least one member (parser should ensure that)
484            debug_assert!(ctx.diag.has_error());
485            return Self::Invalid;
486        };
487
488        ctx.current_token = Some(first.clone().into());
489        let first_str = crate::parser::normalize_identifier(first.text());
490        let global_lookup = crate::lookup::global_lookup();
491        let result = match global_lookup.lookup(ctx, &first_str) {
492            None => {
493                if let Some(minus_pos) = first.text().find('-') {
494                    // Attempt to recover if the user wanted to write "-" for minus
495                    let first_str = &first.text()[0..minus_pos];
496                    if global_lookup
497                        .lookup(ctx, &crate::parser::normalize_identifier(first_str))
498                        .is_some()
499                    {
500                        ctx.diag.push_error(format!("Unknown unqualified identifier '{}'. Use space before the '-' if you meant a subtraction", first.text()), &node);
501                        return Expression::Invalid;
502                    }
503                }
504
505                if it.next().is_some() {
506                    ctx.diag.push_error(format!("Cannot access id '{}'", first.text()), &node);
507                } else {
508                    ctx.diag.push_error(
509                        format!("Unknown unqualified identifier '{}'", first.text()),
510                        &node,
511                    );
512                }
513                return Expression::Invalid;
514            }
515            Some(x) => x,
516        };
517
518        if let Some(depr) = result.deprecated() {
519            ctx.diag.push_property_deprecation_warning(&first_str, depr, &first);
520        }
521
522        match result {
523            LookupResult::Expression { expression: Expression::ElementReference(e), .. } => {
524                continue_lookup_within_element(&e.upgrade().unwrap(), &mut it, node, ctx)
525            }
526            LookupResult::Expression {
527                expression: r @ Expression::CallbackReference(..), ..
528            } => {
529                if let Some(x) = it.next() {
530                    ctx.diag.push_error("Cannot access fields of callback".into(), &x)
531                }
532                r
533            }
534            LookupResult::Enumeration(enumeration) => {
535                if let Some(next_identifier) = it.next() {
536                    match enumeration
537                        .lookup(ctx, &crate::parser::normalize_identifier(next_identifier.text()))
538                    {
539                        Some(LookupResult::Expression { expression, .. }) => {
540                            maybe_lookup_object(expression, it, ctx)
541                        }
542                        _ => {
543                            ctx.diag.push_error(
544                                format!(
545                                    "'{}' is not a member of the enum {}",
546                                    next_identifier.text(),
547                                    enumeration.name
548                                ),
549                                &next_identifier,
550                            );
551                            Expression::Invalid
552                        }
553                    }
554                } else {
555                    ctx.diag.push_error("Cannot take reference to an enum".to_string(), &node);
556                    Expression::Invalid
557                }
558            }
559            LookupResult::Expression { expression, .. } => maybe_lookup_object(expression, it, ctx),
560            LookupResult::Namespace(_) => {
561                if let Some(next_identifier) = it.next() {
562                    match result
563                        .lookup(ctx, &crate::parser::normalize_identifier(next_identifier.text()))
564                    {
565                        Some(LookupResult::Expression { expression, .. }) => {
566                            maybe_lookup_object(expression, it, ctx)
567                        }
568                        _ => {
569                            ctx.diag.push_error(
570                                format!(
571                                    "'{}' is not a member of the namespace {}",
572                                    next_identifier.text(),
573                                    first_str
574                                ),
575                                &next_identifier,
576                            );
577                            Expression::Invalid
578                        }
579                    }
580                } else {
581                    ctx.diag.push_error("Cannot take reference to a namespace".to_string(), &node);
582                    Expression::Invalid
583                }
584            }
585        }
586    }
587
588    fn from_function_call_node(
589        node: syntax_nodes::FunctionCallExpression,
590        ctx: &mut LookupCtx,
591    ) -> Expression {
592        let mut arguments = Vec::new();
593
594        let mut sub_expr = node.Expression();
595
596        let function = sub_expr.next().map_or(Self::Invalid, |n| {
597            // Treat the QualifiedName separately so we can catch the uses of uncalled signal
598            n.QualifiedName()
599                .or_else(|| {
600                    n.Expression().and_then(|mut e| {
601                        while let Some(e2) = e.Expression() {
602                            e = e2;
603                        }
604                        e.QualifiedName().map(|q| {
605                            ctx.diag.push_warning(
606                            "Parentheses around callable are deprecated. Remove the parentheses"
607                                .into(),
608                            &n,
609                        );
610                            q
611                        })
612                    })
613                })
614                .map(|qn| Self::from_qualified_name_node(qn, ctx))
615                .unwrap_or_else(|| Self::from_expression_node(n, ctx))
616        });
617
618        let sub_expr = sub_expr.map(|n| {
619            (Self::from_expression_node(n.clone(), ctx), Some(NodeOrToken::from((*n).clone())))
620        });
621
622        let function = match function {
623            Expression::BuiltinMacroReference(mac, n) => {
624                arguments.extend(sub_expr);
625                return crate::builtin_macros::lower_macro(mac, n, arguments.into_iter(), ctx.diag);
626            }
627            Expression::MemberFunction { base, base_node, member } => {
628                arguments.push((*base, base_node));
629                member
630            }
631            _ => Box::new(function),
632        };
633        arguments.extend(sub_expr);
634
635        let arguments = match function.ty() {
636            Type::Function { args, .. } | Type::Callback { args, .. } => {
637                if arguments.len() != args.len() {
638                    ctx.diag.push_error(
639                        format!(
640                            "The callback or function expects {} arguments, but {} are provided",
641                            args.len(),
642                            arguments.len()
643                        ),
644                        &node,
645                    );
646                    arguments.into_iter().map(|x| x.0).collect()
647                } else {
648                    arguments
649                        .into_iter()
650                        .zip(args.iter())
651                        .map(|((e, node), ty)| e.maybe_convert_to(ty.clone(), &node, ctx.diag))
652                        .collect()
653                }
654            }
655            _ => {
656                ctx.diag.push_error("The expression is not a function".into(), &node);
657                arguments.into_iter().map(|x| x.0).collect()
658            }
659        };
660
661        Expression::FunctionCall {
662            function,
663            arguments,
664            source_location: Some(node.to_source_location()),
665        }
666    }
667
668    fn from_member_access_node(
669        node: syntax_nodes::MemberAccess,
670        ctx: &mut LookupCtx,
671    ) -> Expression {
672        let base = Self::from_expression_node(node.Expression(), ctx);
673        maybe_lookup_object(base, node.child_token(SyntaxKind::Identifier).into_iter(), ctx)
674    }
675
676    fn from_self_assignment_node(
677        node: syntax_nodes::SelfAssignment,
678        ctx: &mut LookupCtx,
679    ) -> Expression {
680        let (lhs_n, rhs_n) = node.Expression();
681        let mut lhs = Self::from_expression_node(lhs_n.clone(), ctx);
682        let op = None
683            .or_else(|| node.child_token(SyntaxKind::PlusEqual).and(Some('+')))
684            .or_else(|| node.child_token(SyntaxKind::MinusEqual).and(Some('-')))
685            .or_else(|| node.child_token(SyntaxKind::StarEqual).and(Some('*')))
686            .or_else(|| node.child_token(SyntaxKind::DivEqual).and(Some('/')))
687            .or_else(|| node.child_token(SyntaxKind::Equal).and(Some('=')))
688            .unwrap_or('_');
689        if !lhs.try_set_rw() && lhs.ty() != Type::Invalid {
690            ctx.diag.push_error(
691                format!(
692                    "{} needs to be done on a property",
693                    if op == '=' { "Assignment" } else { "Self assignment" }
694                ),
695                &node,
696            );
697        }
698        let ty = lhs.ty();
699        let expected_ty = match op {
700            '=' => ty,
701            '+' if ty == Type::String || ty.as_unit_product().is_some() => ty,
702            '-' if ty.as_unit_product().is_some() => ty,
703            '/' | '*' if ty.as_unit_product().is_some() => Type::Float32,
704            _ => {
705                if ty != Type::Invalid {
706                    ctx.diag.push_error(
707                        format!("the {}= operation cannot be done on a {}", op, ty),
708                        &lhs_n,
709                    );
710                }
711                Type::Invalid
712            }
713        };
714        let rhs = Self::from_expression_node(rhs_n.clone(), ctx);
715        Expression::SelfAssignment {
716            lhs: Box::new(lhs),
717            rhs: Box::new(rhs.maybe_convert_to(expected_ty, &rhs_n, ctx.diag)),
718            op,
719        }
720    }
721
722    fn from_binary_expression_node(
723        node: syntax_nodes::BinaryExpression,
724        ctx: &mut LookupCtx,
725    ) -> Expression {
726        let op = None
727            .or_else(|| node.child_token(SyntaxKind::Plus).and(Some('+')))
728            .or_else(|| node.child_token(SyntaxKind::Minus).and(Some('-')))
729            .or_else(|| node.child_token(SyntaxKind::Star).and(Some('*')))
730            .or_else(|| node.child_token(SyntaxKind::Div).and(Some('/')))
731            .or_else(|| node.child_token(SyntaxKind::LessEqual).and(Some('≤')))
732            .or_else(|| node.child_token(SyntaxKind::GreaterEqual).and(Some('≥')))
733            .or_else(|| node.child_token(SyntaxKind::LAngle).and(Some('<')))
734            .or_else(|| node.child_token(SyntaxKind::RAngle).and(Some('>')))
735            .or_else(|| node.child_token(SyntaxKind::EqualEqual).and(Some('=')))
736            .or_else(|| node.child_token(SyntaxKind::NotEqual).and(Some('!')))
737            .or_else(|| node.child_token(SyntaxKind::AndAnd).and(Some('&')))
738            .or_else(|| node.child_token(SyntaxKind::OrOr).and(Some('|')))
739            .unwrap_or('_');
740
741        let (lhs_n, rhs_n) = node.Expression();
742        let lhs = Self::from_expression_node(lhs_n.clone(), ctx);
743        let rhs = Self::from_expression_node(rhs_n.clone(), ctx);
744
745        let expected_ty = match operator_class(op) {
746            OperatorClass::ComparisonOp => {
747                Self::common_target_type_for_type_list([lhs.ty(), rhs.ty()].iter().cloned())
748            }
749            OperatorClass::LogicalOp => Type::Bool,
750            OperatorClass::ArithmeticOp => {
751                let (lhs_ty, rhs_ty) = (lhs.ty(), rhs.ty());
752                if op == '+' && (lhs_ty == Type::String || rhs_ty == Type::String) {
753                    Type::String
754                } else if op == '+' || op == '-' {
755                    if lhs_ty.default_unit().is_some() {
756                        lhs_ty
757                    } else if rhs_ty.default_unit().is_some() {
758                        rhs_ty
759                    } else if matches!(lhs_ty, Type::UnitProduct(_)) {
760                        lhs_ty
761                    } else if matches!(rhs_ty, Type::UnitProduct(_)) {
762                        rhs_ty
763                    } else {
764                        Type::Float32
765                    }
766                } else if op == '*' || op == '/' {
767                    let has_unit = |ty: &Type| {
768                        matches!(ty, Type::UnitProduct(_)) || ty.default_unit().is_some()
769                    };
770                    match (has_unit(&lhs_ty), has_unit(&rhs_ty)) {
771                        (true, true) => {
772                            return Expression::BinaryExpression {
773                                lhs: Box::new(lhs),
774                                rhs: Box::new(rhs),
775                                op,
776                            }
777                        }
778                        (true, false) => {
779                            return Expression::BinaryExpression {
780                                lhs: Box::new(lhs),
781                                rhs: Box::new(rhs.maybe_convert_to(
782                                    Type::Float32,
783                                    &rhs_n,
784                                    ctx.diag,
785                                )),
786                                op,
787                            }
788                        }
789                        (false, true) => {
790                            return Expression::BinaryExpression {
791                                lhs: Box::new(lhs.maybe_convert_to(
792                                    Type::Float32,
793                                    &lhs_n,
794                                    ctx.diag,
795                                )),
796                                rhs: Box::new(rhs),
797                                op,
798                            }
799                        }
800                        (false, false) => Type::Float32,
801                    }
802                } else {
803                    unreachable!()
804                }
805            }
806        };
807        Expression::BinaryExpression {
808            lhs: Box::new(lhs.maybe_convert_to(expected_ty.clone(), &lhs_n, ctx.diag)),
809            rhs: Box::new(rhs.maybe_convert_to(expected_ty, &rhs_n, ctx.diag)),
810            op,
811        }
812    }
813
814    fn from_unaryop_expression_node(
815        node: syntax_nodes::UnaryOpExpression,
816        ctx: &mut LookupCtx,
817    ) -> Expression {
818        let exp_n = node.Expression();
819        let exp = Self::from_expression_node(exp_n, ctx);
820
821        Expression::UnaryOp {
822            sub: Box::new(exp),
823            op: None
824                .or_else(|| node.child_token(SyntaxKind::Plus).and(Some('+')))
825                .or_else(|| node.child_token(SyntaxKind::Minus).and(Some('-')))
826                .or_else(|| node.child_token(SyntaxKind::Bang).and(Some('!')))
827                .unwrap_or('_'),
828        }
829    }
830
831    fn from_conditional_expression_node(
832        node: syntax_nodes::ConditionalExpression,
833        ctx: &mut LookupCtx,
834    ) -> Expression {
835        let (condition_n, true_expr_n, false_expr_n) = node.Expression();
836        // FIXME: we should we add bool to the context
837        let condition = Self::from_expression_node(condition_n.clone(), ctx).maybe_convert_to(
838            Type::Bool,
839            &condition_n,
840            ctx.diag,
841        );
842        let true_expr = Self::from_expression_node(true_expr_n.clone(), ctx);
843        let false_expr = Self::from_expression_node(false_expr_n.clone(), ctx);
844        let result_ty = Self::common_target_type_for_type_list(
845            [true_expr.ty(), false_expr.ty()].iter().cloned(),
846        );
847        let true_expr = true_expr.maybe_convert_to(result_ty.clone(), &true_expr_n, ctx.diag);
848        let false_expr = false_expr.maybe_convert_to(result_ty, &false_expr_n, ctx.diag);
849        Expression::Condition {
850            condition: Box::new(condition),
851            true_expr: Box::new(true_expr),
852            false_expr: Box::new(false_expr),
853        }
854    }
855
856    fn from_index_expression_node(
857        node: syntax_nodes::IndexExpression,
858        ctx: &mut LookupCtx,
859    ) -> Expression {
860        let (array_expr_n, index_expr_n) = node.Expression();
861        let array_expr = Self::from_expression_node(array_expr_n, ctx);
862        let index_expr = Self::from_expression_node(index_expr_n.clone(), ctx).maybe_convert_to(
863            Type::Int32,
864            &index_expr_n,
865            &mut ctx.diag,
866        );
867
868        let ty = array_expr.ty();
869        if !matches!(ty, Type::Array(_) | Type::Invalid) {
870            ctx.diag.push_error(format!("{} is not an indexable type", ty), &node);
871        }
872        Expression::ArrayIndex { array: Box::new(array_expr), index: Box::new(index_expr) }
873    }
874
875    fn from_object_literal_node(
876        node: syntax_nodes::ObjectLiteral,
877        ctx: &mut LookupCtx,
878    ) -> Expression {
879        let values: HashMap<String, Expression> = node
880            .ObjectMember()
881            .map(|n| {
882                (
883                    identifier_text(&n).unwrap_or_default(),
884                    Expression::from_expression_node(n.Expression(), ctx),
885                )
886            })
887            .collect();
888        let ty = Type::Struct {
889            fields: values.iter().map(|(k, v)| (k.clone(), v.ty())).collect(),
890            name: None,
891            node: None,
892        };
893        Expression::Struct { ty, values }
894    }
895
896    fn from_array_node(node: syntax_nodes::Array, ctx: &mut LookupCtx) -> Expression {
897        let mut values: Vec<Expression> =
898            node.Expression().map(|e| Expression::from_expression_node(e, ctx)).collect();
899
900        // FIXME: what's the type of an empty array ?
901        let element_ty =
902            Self::common_target_type_for_type_list(values.iter().map(|expr| expr.ty()));
903
904        for e in values.iter_mut() {
905            *e = core::mem::replace(e, Expression::Invalid).maybe_convert_to(
906                element_ty.clone(),
907                &node,
908                ctx.diag,
909            );
910        }
911
912        Expression::Array { element_ty, values }
913    }
914
915    fn from_string_template_node(
916        node: syntax_nodes::StringTemplate,
917        ctx: &mut LookupCtx,
918    ) -> Expression {
919        let mut exprs = node.Expression().map(|e| {
920            Expression::from_expression_node(e.clone(), ctx).maybe_convert_to(
921                Type::String,
922                &e,
923                ctx.diag,
924            )
925        });
926        let mut result = exprs.next().unwrap_or_default();
927        for x in exprs {
928            result = Expression::BinaryExpression {
929                lhs: Box::new(std::mem::take(&mut result)),
930                rhs: Box::new(x),
931                op: '+',
932            }
933        }
934        result
935    }
936
937    /// This function is used to find a type that's suitable for casting each instance of a bunch of expressions
938    /// to a type that captures most aspects. For example for an array of object literals the result is a merge of
939    /// all seen fields.
940    fn common_target_type_for_type_list(types: impl Iterator<Item = Type>) -> Type {
941        types.fold(Type::Invalid, |target_type, expr_ty| {
942            if target_type == expr_ty {
943                target_type
944            } else if target_type == Type::Invalid {
945                expr_ty
946            } else {
947                match (target_type, expr_ty) {
948                    (
949                        Type::Struct {
950                            fields: mut result_fields,
951                            name: result_name,
952                            node: result_node,
953                        },
954                        Type::Struct { fields: elem_fields, name: elem_name, node: elem_node },
955                    ) => {
956                        for (elem_name, elem_ty) in elem_fields.into_iter() {
957                            match result_fields.entry(elem_name) {
958                                std::collections::btree_map::Entry::Vacant(free_entry) => {
959                                    free_entry.insert(elem_ty);
960                                }
961                                std::collections::btree_map::Entry::Occupied(
962                                    mut existing_field,
963                                ) => {
964                                    *existing_field.get_mut() =
965                                        Self::common_target_type_for_type_list(
966                                            [existing_field.get().clone(), elem_ty].iter().cloned(),
967                                        );
968                                }
969                            }
970                        }
971                        Type::Struct {
972                            name: result_name.or(elem_name),
973                            fields: result_fields,
974                            node: result_node.or(elem_node),
975                        }
976                    }
977                    (target_type, expr_ty) => {
978                        if expr_ty.can_convert(&target_type) {
979                            target_type
980                        } else if target_type.can_convert(&expr_ty)
981                            || (expr_ty.default_unit().is_some()
982                                && matches!(target_type, Type::Float32 | Type::Int32))
983                        {
984                            // in the or case: The `0` literal.
985                            expr_ty
986                        } else {
987                            // otherwise, use the target type and let further conversion report an error
988                            target_type
989                        }
990                    }
991                }
992            }
993        })
994    }
995}
996
997fn continue_lookup_within_element(
998    elem: &ElementRc,
999    it: &mut impl Iterator<Item = crate::parser::SyntaxToken>,
1000    node: syntax_nodes::QualifiedName,
1001    ctx: &mut LookupCtx,
1002) -> Expression {
1003    let second = if let Some(second) = it.next() {
1004        second
1005    } else if matches!(ctx.property_type, Type::ElementReference) {
1006        return Expression::ElementReference(Rc::downgrade(elem));
1007    } else {
1008        ctx.diag.push_error("Cannot take reference of an element".into(), &node);
1009        return Expression::Invalid;
1010    };
1011    let prop_name = crate::parser::normalize_identifier(second.text());
1012
1013    let PropertyLookupResult { resolved_name, property_type } =
1014        elem.borrow().lookup_property(&prop_name);
1015    if property_type.is_property_type() {
1016        if resolved_name != prop_name {
1017            ctx.diag.push_property_deprecation_warning(&prop_name, &resolved_name, &second);
1018        }
1019        let prop = Expression::PropertyReference(NamedReference::new(elem, &resolved_name));
1020        maybe_lookup_object(prop, it, ctx)
1021    } else if matches!(property_type, Type::Callback { .. }) {
1022        if let Some(x) = it.next() {
1023            ctx.diag.push_error("Cannot access fields of callback".into(), &x)
1024        }
1025        Expression::CallbackReference(NamedReference::new(elem, &resolved_name))
1026    } else if matches!(property_type, Type::Function { .. }) {
1027        let member = elem.borrow().base_type.lookup_member_function(&resolved_name);
1028        Expression::MemberFunction {
1029            base: Box::new(Expression::ElementReference(Rc::downgrade(elem))),
1030            base_node: Some(NodeOrToken::Node(node.into())),
1031            member: Box::new(member),
1032        }
1033    } else {
1034        let mut err = |extra: &str| {
1035            let what = match &elem.borrow().base_type {
1036                Type::Void => {
1037                    let global = elem.borrow().enclosing_component.upgrade().unwrap();
1038                    assert!(global.is_global());
1039                    format!("'{}'", global.id)
1040                }
1041                Type::Component(c) => format!("Element '{}'", c.id),
1042                Type::Builtin(b) => format!("Element '{}'", b.name),
1043                _ => {
1044                    assert!(ctx.diag.has_error());
1045                    return;
1046                }
1047            };
1048            ctx.diag.push_error(
1049                format!("{} does not have a property '{}'{}", what, second.text(), extra),
1050                &second,
1051            );
1052        };
1053        if let Some(minus_pos) = second.text().find('-') {
1054            // Attempt to recover if the user wanted to write "-"
1055            if elem
1056                .borrow()
1057                .lookup_property(&crate::parser::normalize_identifier(&second.text()[0..minus_pos]))
1058                .property_type
1059                != Type::Invalid
1060            {
1061                err(". Use space before the '-' if you meant a subtraction");
1062                return Expression::Invalid;
1063            }
1064        }
1065        err("");
1066        Expression::Invalid
1067    }
1068}
1069
1070fn maybe_lookup_object(
1071    mut base: Expression,
1072    it: impl Iterator<Item = crate::parser::SyntaxToken>,
1073    ctx: &mut LookupCtx,
1074) -> Expression {
1075    for next in it {
1076        let next_str = crate::parser::normalize_identifier(next.text());
1077        ctx.current_token = Some(next.clone().into());
1078        match base.lookup(ctx, &next_str) {
1079            Some(LookupResult::Expression { expression, .. }) => {
1080                base = expression;
1081            }
1082            _ => {
1083                if let Some(minus_pos) = next.text().find('-') {
1084                    if base.lookup(ctx, &next.text()[0..minus_pos]).is_some() {
1085                        ctx.diag.push_error(format!("Cannot access the field '{}'. Use space before the '-' if you meant a subtraction", next.text()), &next);
1086                        return Expression::Invalid;
1087                    }
1088                }
1089                let ty_descr = match base.ty() {
1090                    Type::Struct { .. } => String::new(),
1091                    ty => format!(" of {}", ty),
1092                };
1093                ctx.diag.push_error(
1094                    format!("Cannot access the field '{}'{}", next.text(), ty_descr),
1095                    &next,
1096                );
1097                return Expression::Invalid;
1098            }
1099        }
1100    }
1101    base
1102}
1103
1104pub fn resolve_two_way_binding(
1105    node: syntax_nodes::TwoWayBinding,
1106    ctx: &mut LookupCtx,
1107) -> Option<NamedReference> {
1108    let e = node
1109        .Expression()
1110        .QualifiedName()
1111        .map_or(Expression::Invalid, |n| Expression::from_qualified_name_node(n, ctx));
1112    let ty = e.ty();
1113    match e {
1114        Expression::PropertyReference(n) => {
1115            if ty != ctx.property_type && ctx.property_type != Type::InferredProperty {
1116                ctx.diag.push_error(
1117                    "The property does not have the same type as the bound property".into(),
1118                    &node,
1119                );
1120            }
1121            Some(n)
1122        }
1123        Expression::CallbackReference(n) => {
1124            if ctx.property_type != Type::InferredCallback && ty != ctx.property_type {
1125                ctx.diag.push_error("Cannot bind to a callback".into(), &node);
1126                None
1127            } else {
1128                Some(n)
1129            }
1130        }
1131        _ => {
1132            ctx.diag.push_error(
1133                "The expression in a two way binding must be a property reference".into(),
1134                &node,
1135            );
1136            None
1137        }
1138    }
1139}