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