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