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 pub fn from_expression_node(node: syntax_nodes::Expression, ctx: &mut LookupCtx) -> Self {
280 node.children_with_tokens()
281 .find_map(|child| match child {
282 NodeOrToken::Node(node) => match node.kind() {
283 SyntaxKind::Expression => Some(Self::from_expression_node(node.into(), ctx)),
284 SyntaxKind::AtImageUrl => Some(Self::from_at_image_url_node(node.into(), ctx)),
285 SyntaxKind::AtGradient => Some(Self::from_at_gradient(node.into(), ctx)),
286 SyntaxKind::AtTr => Some(Self::from_at_tr(node.into(), ctx)),
287 SyntaxKind::QualifiedName => Some(Self::from_qualified_name_node(
288 node.clone().into(),
289 ctx,
290 LookupPhase::default(),
291 )),
292 SyntaxKind::FunctionCallExpression => {
293 Some(Self::from_function_call_node(node.into(), ctx))
294 }
295 SyntaxKind::MemberAccess => {
296 Some(Self::from_member_access_node(node.into(), ctx))
297 }
298 SyntaxKind::IndexExpression => {
299 Some(Self::from_index_expression_node(node.into(), ctx))
300 }
301 SyntaxKind::SelfAssignment => {
302 Some(Self::from_self_assignment_node(node.into(), ctx))
303 }
304 SyntaxKind::BinaryExpression => {
305 Some(Self::from_binary_expression_node(node.into(), ctx))
306 }
307 SyntaxKind::UnaryOpExpression => {
308 Some(Self::from_unaryop_expression_node(node.into(), ctx))
309 }
310 SyntaxKind::ConditionalExpression => {
311 Some(Self::from_conditional_expression_node(node.into(), ctx))
312 }
313 SyntaxKind::ObjectLiteral => {
314 Some(Self::from_object_literal_node(node.into(), ctx))
315 }
316 SyntaxKind::Array => Some(Self::from_array_node(node.into(), ctx)),
317 SyntaxKind::CodeBlock => Some(Self::from_codeblock_node(node.into(), ctx)),
318 SyntaxKind::StringTemplate => {
319 Some(Self::from_string_template_node(node.into(), ctx))
320 }
321 _ => None,
322 },
323 NodeOrToken::Token(token) => match token.kind() {
324 SyntaxKind::StringLiteral => Some(
325 crate::literals::unescape_string(token.text())
326 .map(Self::StringLiteral)
327 .unwrap_or_else(|| {
328 ctx.diag.push_error("Cannot parse string literal".into(), &token);
329 Self::Invalid
330 }),
331 ),
332 SyntaxKind::NumberLiteral => Some(
333 crate::literals::parse_number_literal(token.text().into()).unwrap_or_else(
334 |e| {
335 ctx.diag.push_error(e.to_string(), &node);
336 Self::Invalid
337 },
338 ),
339 ),
340 SyntaxKind::ColorLiteral => Some(
341 crate::literals::parse_color_literal(token.text())
342 .map(|i| Expression::Cast {
343 from: Box::new(Expression::NumberLiteral(i as _, Unit::None)),
344 to: Type::Color,
345 })
346 .unwrap_or_else(|| {
347 ctx.diag.push_error("Invalid color literal".into(), &node);
348 Self::Invalid
349 }),
350 ),
351
352 _ => None,
353 },
354 })
355 .unwrap_or(Self::Invalid)
356 }
357
358 fn from_at_image_url_node(node: syntax_nodes::AtImageUrl, ctx: &mut LookupCtx) -> Self {
359 let s = match node
360 .child_text(SyntaxKind::StringLiteral)
361 .and_then(|x| crate::literals::unescape_string(&x))
362 {
363 Some(s) => s,
364 None => {
365 ctx.diag.push_error("Cannot parse string literal".into(), &node);
366 return Self::Invalid;
367 }
368 };
369
370 if s.is_empty() {
371 return Expression::ImageReference {
372 resource_ref: ImageReference::None,
373 source_location: Some(node.to_source_location()),
374 nine_slice: None,
375 };
376 }
377
378 let absolute_source_path = {
379 let path = std::path::Path::new(&s);
380 if crate::pathutils::is_absolute(path) {
381 s
382 } else {
383 ctx.type_loader
384 .and_then(|loader| {
385 loader.resolve_import_path(Some(&(*node).clone().into()), &s)
386 })
387 .map(|i| i.0.to_string_lossy().into())
388 .unwrap_or_else(|| {
389 crate::pathutils::join(
390 &crate::pathutils::dirname(node.source_file.path()),
391 path,
392 )
393 .map(|p| p.to_string_lossy().into())
394 .unwrap_or(s.clone())
395 })
396 }
397 };
398
399 let nine_slice = node
400 .children_with_tokens()
401 .filter_map(|n| n.into_token())
402 .filter(|t| t.kind() == SyntaxKind::NumberLiteral)
403 .map(|arg| {
404 arg.text().parse().unwrap_or_else(|err: std::num::ParseIntError| {
405 match err.kind() {
406 IntErrorKind::PosOverflow | IntErrorKind::NegOverflow => {
407 ctx.diag.push_error("Number too big".into(), &arg)
408 }
409 IntErrorKind::InvalidDigit => ctx.diag.push_error(
410 "Border widths of a nine-slice can't have units".into(),
411 &arg,
412 ),
413 _ => ctx.diag.push_error("Cannot parse number literal".into(), &arg),
414 };
415 0u16
416 })
417 })
418 .collect::<Vec<u16>>();
419
420 let nine_slice = match nine_slice.as_slice() {
421 [x] => Some([*x, *x, *x, *x]),
422 [x, y] => Some([*x, *y, *x, *y]),
423 [x, y, z, w] => Some([*x, *y, *z, *w]),
424 [] => None,
425 _ => {
426 assert!(ctx.diag.has_errors());
427 None
428 }
429 };
430
431 Expression::ImageReference {
432 resource_ref: ImageReference::AbsolutePath(absolute_source_path),
433 source_location: Some(node.to_source_location()),
434 nine_slice,
435 }
436 }
437
438 pub fn from_at_gradient(node: syntax_nodes::AtGradient, ctx: &mut LookupCtx) -> Self {
439 enum GradKind {
440 Linear { angle: Box<Expression> },
441 Radial,
442 }
443
444 let mut subs = node
445 .children_with_tokens()
446 .filter(|n| matches!(n.kind(), SyntaxKind::Comma | SyntaxKind::Expression));
447
448 let grad_token = node.child_token(SyntaxKind::Identifier).unwrap();
449 let grad_text = grad_token.text();
450
451 let grad_kind = if grad_text.starts_with("linear") {
452 let angle_expr = match subs.next() {
453 Some(e) if e.kind() == SyntaxKind::Expression => {
454 syntax_nodes::Expression::from(e.into_node().unwrap())
455 }
456 _ => {
457 ctx.diag.push_error("Expected angle expression".into(), &node);
458 return Expression::Invalid;
459 }
460 };
461 if subs.next().is_some_and(|s| s.kind() != SyntaxKind::Comma) {
462 ctx.diag.push_error(
463 "Angle expression must be an angle followed by a comma".into(),
464 &node,
465 );
466 return Expression::Invalid;
467 }
468 let angle = Box::new(
469 Expression::from_expression_node(angle_expr.clone(), ctx).maybe_convert_to(
470 Type::Angle,
471 &angle_expr,
472 ctx.diag,
473 ),
474 );
475 GradKind::Linear { angle }
476 } else if grad_text.starts_with("radial") {
477 if !matches!(subs.next(), Some(NodeOrToken::Node(n)) if n.text().to_string().trim() == "circle")
478 {
479 ctx.diag.push_error("Expected 'circle': currently, only @radial-gradient(circle, ...) are supported".into(), &node);
480 return Expression::Invalid;
481 }
482 let comma = subs.next();
483 if matches!(&comma, Some(NodeOrToken::Node(n)) if n.text().to_string().trim() == "at") {
484 ctx.diag.push_error("'at' in @radial-gradient is not yet supported".into(), &comma);
485 return Expression::Invalid;
486 }
487 if comma.as_ref().is_some_and(|s| s.kind() != SyntaxKind::Comma) {
488 ctx.diag.push_error(
489 "'circle' must be followed by a comma".into(),
490 comma.as_ref().map_or(&node, |x| x as &dyn Spanned),
491 );
492 return Expression::Invalid;
493 }
494 GradKind::Radial
495 } else {
496 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 let ty =
1003 Self::common_target_type_for_type_list([lhs.ty(), rhs.ty()].iter().cloned());
1004 if !matches!(op, '=' | '!') && !ty.as_unit_product().is_some() && ty != Type::String
1005 {
1006 ctx.diag.push_error(format!("Values of type {ty} cannot be compared"), &node);
1007 }
1008 ty
1009 }
1010 OperatorClass::LogicalOp => Type::Bool,
1011 OperatorClass::ArithmeticOp => {
1012 let (lhs_ty, rhs_ty) = (lhs.ty(), rhs.ty());
1013 if op == '+' && (lhs_ty == Type::String || rhs_ty == Type::String) {
1014 Type::String
1015 } else if op == '+' || op == '-' {
1016 if lhs_ty.default_unit().is_some() {
1017 lhs_ty
1018 } else if rhs_ty.default_unit().is_some() {
1019 rhs_ty
1020 } else if matches!(lhs_ty, Type::UnitProduct(_)) {
1021 lhs_ty
1022 } else if matches!(rhs_ty, Type::UnitProduct(_)) {
1023 rhs_ty
1024 } else {
1025 Type::Float32
1026 }
1027 } else if op == '*' || op == '/' {
1028 let has_unit = |ty: &Type| {
1029 matches!(ty, Type::UnitProduct(_)) || ty.default_unit().is_some()
1030 };
1031 match (has_unit(&lhs_ty), has_unit(&rhs_ty)) {
1032 (true, true) => {
1033 return Expression::BinaryExpression {
1034 lhs: Box::new(lhs),
1035 rhs: Box::new(rhs),
1036 op,
1037 }
1038 }
1039 (true, false) => {
1040 return Expression::BinaryExpression {
1041 lhs: Box::new(lhs),
1042 rhs: Box::new(rhs.maybe_convert_to(
1043 Type::Float32,
1044 &rhs_n,
1045 ctx.diag,
1046 )),
1047 op,
1048 }
1049 }
1050 (false, true) => {
1051 return Expression::BinaryExpression {
1052 lhs: Box::new(lhs.maybe_convert_to(
1053 Type::Float32,
1054 &lhs_n,
1055 ctx.diag,
1056 )),
1057 rhs: Box::new(rhs),
1058 op,
1059 }
1060 }
1061 (false, false) => Type::Float32,
1062 }
1063 } else {
1064 unreachable!()
1065 }
1066 }
1067 };
1068 Expression::BinaryExpression {
1069 lhs: Box::new(lhs.maybe_convert_to(expected_ty.clone(), &lhs_n, ctx.diag)),
1070 rhs: Box::new(rhs.maybe_convert_to(expected_ty, &rhs_n, ctx.diag)),
1071 op,
1072 }
1073 }
1074
1075 fn from_unaryop_expression_node(
1076 node: syntax_nodes::UnaryOpExpression,
1077 ctx: &mut LookupCtx,
1078 ) -> Expression {
1079 let exp_n = node.Expression();
1080 let exp = Self::from_expression_node(exp_n, ctx);
1081
1082 let op = node
1083 .children_with_tokens()
1084 .find_map(|n| match n.kind() {
1085 SyntaxKind::Plus => Some('+'),
1086 SyntaxKind::Minus => Some('-'),
1087 SyntaxKind::Bang => Some('!'),
1088 _ => None,
1089 })
1090 .unwrap_or('_');
1091
1092 let exp = match op {
1093 '!' => exp.maybe_convert_to(Type::Bool, &node, ctx.diag),
1094 '+' | '-' => {
1095 let ty = exp.ty();
1096 if ty.default_unit().is_none()
1097 && !matches!(
1098 ty,
1099 Type::Int32
1100 | Type::Float32
1101 | Type::Percent
1102 | Type::UnitProduct(..)
1103 | Type::Invalid
1104 )
1105 {
1106 ctx.diag.push_error(format!("Unary '{op}' not supported on {ty}"), &node);
1107 }
1108 exp
1109 }
1110 _ => {
1111 assert!(ctx.diag.has_errors());
1112 exp
1113 }
1114 };
1115
1116 Expression::UnaryOp { sub: Box::new(exp), op }
1117 }
1118
1119 fn from_conditional_expression_node(
1120 node: syntax_nodes::ConditionalExpression,
1121 ctx: &mut LookupCtx,
1122 ) -> Expression {
1123 let (condition_n, true_expr_n, false_expr_n) = node.Expression();
1124 let condition = Self::from_expression_node(condition_n.clone(), ctx).maybe_convert_to(
1126 Type::Bool,
1127 &condition_n,
1128 ctx.diag,
1129 );
1130 let true_expr = Self::from_expression_node(true_expr_n.clone(), ctx);
1131 let false_expr = Self::from_expression_node(false_expr_n.clone(), ctx);
1132 let result_ty = Self::common_target_type_for_type_list(
1133 [true_expr.ty(), false_expr.ty()].iter().cloned(),
1134 );
1135 let true_expr = true_expr.maybe_convert_to(result_ty.clone(), &true_expr_n, ctx.diag);
1136 let false_expr = false_expr.maybe_convert_to(result_ty, &false_expr_n, ctx.diag);
1137 Expression::Condition {
1138 condition: Box::new(condition),
1139 true_expr: Box::new(true_expr),
1140 false_expr: Box::new(false_expr),
1141 }
1142 }
1143
1144 fn from_index_expression_node(
1145 node: syntax_nodes::IndexExpression,
1146 ctx: &mut LookupCtx,
1147 ) -> Expression {
1148 let (array_expr_n, index_expr_n) = node.Expression();
1149 let array_expr = Self::from_expression_node(array_expr_n, ctx);
1150 let index_expr = Self::from_expression_node(index_expr_n.clone(), ctx).maybe_convert_to(
1151 Type::Int32,
1152 &index_expr_n,
1153 ctx.diag,
1154 );
1155
1156 let ty = array_expr.ty();
1157 if !matches!(ty, Type::Array(_) | Type::Invalid | Type::Function(_) | Type::Callback(_)) {
1158 ctx.diag.push_error(format!("{ty} is not an indexable type"), &node);
1159 }
1160 Expression::ArrayIndex { array: Box::new(array_expr), index: Box::new(index_expr) }
1161 }
1162
1163 fn from_object_literal_node(
1164 node: syntax_nodes::ObjectLiteral,
1165 ctx: &mut LookupCtx,
1166 ) -> Expression {
1167 let values: HashMap<SmolStr, Expression> = node
1168 .ObjectMember()
1169 .map(|n| {
1170 (
1171 identifier_text(&n).unwrap_or_default(),
1172 Expression::from_expression_node(n.Expression(), ctx),
1173 )
1174 })
1175 .collect();
1176 let ty = Rc::new(Struct {
1177 fields: values.iter().map(|(k, v)| (k.clone(), v.ty())).collect(),
1178 name: None,
1179 node: None,
1180 rust_attributes: None,
1181 });
1182 Expression::Struct { ty, values }
1183 }
1184
1185 fn from_array_node(node: syntax_nodes::Array, ctx: &mut LookupCtx) -> Expression {
1186 let mut values: Vec<Expression> =
1187 node.Expression().map(|e| Expression::from_expression_node(e, ctx)).collect();
1188
1189 let element_ty = if values.is_empty() {
1190 Type::Void
1191 } else {
1192 Self::common_target_type_for_type_list(values.iter().map(|expr| expr.ty()))
1193 };
1194
1195 for e in values.iter_mut() {
1196 *e = core::mem::replace(e, Expression::Invalid).maybe_convert_to(
1197 element_ty.clone(),
1198 &node,
1199 ctx.diag,
1200 );
1201 }
1202
1203 Expression::Array { element_ty, values }
1204 }
1205
1206 fn from_string_template_node(
1207 node: syntax_nodes::StringTemplate,
1208 ctx: &mut LookupCtx,
1209 ) -> Expression {
1210 let mut exprs = node.Expression().map(|e| {
1211 Expression::from_expression_node(e.clone(), ctx).maybe_convert_to(
1212 Type::String,
1213 &e,
1214 ctx.diag,
1215 )
1216 });
1217 let mut result = exprs.next().unwrap_or_default();
1218 for x in exprs {
1219 result = Expression::BinaryExpression {
1220 lhs: Box::new(std::mem::take(&mut result)),
1221 rhs: Box::new(x),
1222 op: '+',
1223 }
1224 }
1225 result
1226 }
1227
1228 pub fn common_target_type_for_type_list(types: impl Iterator<Item = Type>) -> Type {
1232 types.fold(Type::Invalid, |target_type, expr_ty| {
1233 if target_type == expr_ty {
1234 target_type
1235 } else if target_type == Type::Invalid {
1236 expr_ty
1237 } else {
1238 match (target_type, expr_ty) {
1239 (Type::Struct(ref result), Type::Struct(ref elem)) => {
1240 let mut fields = result.fields.clone();
1241 for (elem_name, elem_ty) in elem.fields.iter() {
1242 match fields.entry(elem_name.clone()) {
1243 std::collections::btree_map::Entry::Vacant(free_entry) => {
1244 free_entry.insert(elem_ty.clone());
1245 }
1246 std::collections::btree_map::Entry::Occupied(
1247 mut existing_field,
1248 ) => {
1249 *existing_field.get_mut() =
1250 Self::common_target_type_for_type_list(
1251 [existing_field.get().clone(), elem_ty.clone()]
1252 .into_iter(),
1253 );
1254 }
1255 }
1256 }
1257 Type::Struct(Rc::new(Struct {
1258 name: result.name.as_ref().or(elem.name.as_ref()).cloned(),
1259 fields,
1260 node: result.node.as_ref().or(elem.node.as_ref()).cloned(),
1261 rust_attributes: result
1262 .rust_attributes
1263 .as_ref()
1264 .or(elem.rust_attributes.as_ref())
1265 .cloned(),
1266 }))
1267 }
1268 (Type::Array(lhs), Type::Array(rhs)) => Type::Array(if *lhs == Type::Void {
1269 rhs
1270 } else if *rhs == Type::Void {
1271 lhs
1272 } else {
1273 Self::common_target_type_for_type_list(
1274 [(*lhs).clone(), (*rhs).clone()].into_iter(),
1275 )
1276 .into()
1277 }),
1278 (Type::Color, Type::Brush) | (Type::Brush, Type::Color) => Type::Brush,
1279 (target_type, expr_ty) => {
1280 if expr_ty.can_convert(&target_type) {
1281 target_type
1282 } else if target_type.can_convert(&expr_ty)
1283 || (expr_ty.default_unit().is_some()
1284 && matches!(target_type, Type::Float32 | Type::Int32))
1285 {
1286 expr_ty
1288 } else {
1289 target_type
1291 }
1292 }
1293 }
1294 }
1295 })
1296 }
1297}
1298
1299fn lookup_qualified_name_node(
1301 node: syntax_nodes::QualifiedName,
1302 ctx: &mut LookupCtx,
1303 phase: LookupPhase,
1304) -> Option<LookupResult> {
1305 let mut it = node
1306 .children_with_tokens()
1307 .filter(|n| n.kind() == SyntaxKind::Identifier)
1308 .filter_map(|n| n.into_token());
1309
1310 let first = if let Some(first) = it.next() {
1311 first
1312 } else {
1313 debug_assert!(ctx.diag.has_errors());
1315 return None;
1316 };
1317
1318 ctx.current_token = Some(first.clone().into());
1319 let first_str = crate::parser::normalize_identifier(first.text());
1320 let global_lookup = crate::lookup::global_lookup();
1321 let result = match global_lookup.lookup(ctx, &first_str) {
1322 None => {
1323 if let Some(minus_pos) = first.text().find('-') {
1324 let first_str = &first.text()[0..minus_pos];
1326 if global_lookup
1327 .lookup(ctx, &crate::parser::normalize_identifier(first_str))
1328 .is_some()
1329 {
1330 ctx.diag.push_error(format!("Unknown unqualified identifier '{}'. Use space before the '-' if you meant a subtraction", first.text()), &node);
1331 return None;
1332 }
1333 }
1334 for (prefix, e) in
1335 [("self", ctx.component_scope.last()), ("root", ctx.component_scope.first())]
1336 {
1337 if let Some(e) = e {
1338 if e.lookup(ctx, &first_str).is_some() {
1339 ctx.diag.push_error(format!("Unknown unqualified identifier '{0}'. Did you mean '{prefix}.{0}'?", first.text()), &node);
1340 return None;
1341 }
1342 }
1343 }
1344
1345 if it.next().is_some() {
1346 ctx.diag.push_error(format!("Cannot access id '{}'", first.text()), &node);
1347 } else {
1348 ctx.diag.push_error(
1349 format!("Unknown unqualified identifier '{}'", first.text()),
1350 &node,
1351 );
1352 }
1353 return None;
1354 }
1355 Some(x) => x,
1356 };
1357
1358 if let Some(depr) = result.deprecated() {
1359 ctx.diag.push_property_deprecation_warning(&first_str, depr, &first);
1360 }
1361
1362 match result {
1363 LookupResult::Expression { expression: Expression::ElementReference(e), .. } => {
1364 continue_lookup_within_element(&e.upgrade().unwrap(), &mut it, node, ctx)
1365 }
1366 LookupResult::Expression {
1367 expression: Expression::RepeaterModelReference { .. }, ..
1368 } if matches!(phase, LookupPhase::ResolvingTwoWayBindings) => {
1369 ctx.diag.push_error(
1370 "Two-way bindings to model data is not supported yet".to_string(),
1371 &node,
1372 );
1373 None
1374 }
1375 result => maybe_lookup_object(result, it, ctx),
1376 }
1377}
1378
1379fn continue_lookup_within_element(
1380 elem: &ElementRc,
1381 it: &mut impl Iterator<Item = crate::parser::SyntaxToken>,
1382 node: syntax_nodes::QualifiedName,
1383 ctx: &mut LookupCtx,
1384) -> Option<LookupResult> {
1385 let second = if let Some(second) = it.next() {
1386 second
1387 } else if matches!(ctx.property_type, Type::ElementReference) {
1388 return Some(Expression::ElementReference(Rc::downgrade(elem)).into());
1389 } else {
1390 let mut rest = String::new();
1392 if let Some(LookupResult::Expression {
1393 expression: Expression::PropertyReference(nr),
1394 ..
1395 }) = crate::lookup::InScopeLookup.lookup(ctx, &elem.borrow().id)
1396 {
1397 let e = nr.element();
1398 let e_borrowed = e.borrow();
1399 let mut id = e_borrowed.id.as_str();
1400 if id.is_empty() {
1401 if ctx.component_scope.last().is_some_and(|x| Rc::ptr_eq(&e, x)) {
1402 id = "self";
1403 } else if ctx.component_scope.first().is_some_and(|x| Rc::ptr_eq(&e, x)) {
1404 id = "root";
1405 } else if ctx.component_scope.iter().nth_back(1).is_some_and(|x| Rc::ptr_eq(&e, x))
1406 {
1407 id = "parent";
1408 }
1409 };
1410 if !id.is_empty() {
1411 rest =
1412 format!(". Use '{id}.{}' to access the property with the same name", nr.name());
1413 }
1414 } else if let Some(LookupResult::Expression {
1415 expression: Expression::EnumerationValue(value),
1416 ..
1417 }) = crate::lookup::ReturnTypeSpecificLookup.lookup(ctx, &elem.borrow().id)
1418 {
1419 rest = format!(
1420 ". Use '{}.{value}' to access the enumeration value",
1421 value.enumeration.name
1422 );
1423 }
1424 ctx.diag.push_error(format!("Cannot take reference of an element{rest}"), &node);
1425 return None;
1426 };
1427 let prop_name = crate::parser::normalize_identifier(second.text());
1428
1429 let lookup_result = elem.borrow().lookup_property(&prop_name);
1430 let local_to_component = lookup_result.is_local_to_component && ctx.is_local_element(elem);
1431
1432 if lookup_result.property_type.is_property_type() {
1433 if !local_to_component && lookup_result.property_visibility == PropertyVisibility::Private {
1434 ctx.diag.push_error(format!("The property '{}' is private. Annotate it with 'in', 'out' or 'in-out' to make it accessible from other components", second.text()), &second);
1435 return None;
1436 } else if lookup_result.property_visibility == PropertyVisibility::Fake {
1437 ctx.diag.push_error(
1438 "This special property can only be used to make a binding and cannot be accessed"
1439 .to_string(),
1440 &second,
1441 );
1442 return None;
1443 } else if lookup_result.resolved_name != prop_name.as_str() {
1444 ctx.diag.push_property_deprecation_warning(
1445 &prop_name,
1446 &lookup_result.resolved_name,
1447 &second,
1448 );
1449 } else if let Some(deprecated) =
1450 crate::lookup::check_deprecated_stylemetrics(elem, ctx, &prop_name)
1451 {
1452 ctx.diag.push_property_deprecation_warning(&prop_name, &deprecated, &second);
1453 }
1454 let prop = Expression::PropertyReference(NamedReference::new(
1455 elem,
1456 lookup_result.resolved_name.to_smolstr(),
1457 ));
1458 maybe_lookup_object(prop.into(), it, ctx)
1459 } else if matches!(lookup_result.property_type, Type::Callback { .. }) {
1460 if let Some(x) = it.next() {
1461 ctx.diag.push_error("Cannot access fields of callback".into(), &x)
1462 }
1463 Some(LookupResult::Callable(LookupResultCallable::Callable(Callable::Callback(
1464 NamedReference::new(elem, lookup_result.resolved_name.to_smolstr()),
1465 ))))
1466 } else if let Type::Function(fun) = lookup_result.property_type {
1467 if lookup_result.property_visibility == PropertyVisibility::Private && !local_to_component {
1468 let message = format!("The function '{}' is private. Annotate it with 'public' to make it accessible from other components", second.text());
1469 if !lookup_result.is_local_to_component {
1470 ctx.diag.push_error(message, &second);
1471 } else {
1472 ctx.diag.push_warning(message+". Note: this used to be allowed in previous version, but this should be considered an error", &second);
1473 }
1474 } else if lookup_result.property_visibility == PropertyVisibility::Protected
1475 && !local_to_component
1476 && !(lookup_result.is_in_direct_base
1477 && ctx.component_scope.first().is_some_and(|x| Rc::ptr_eq(x, elem)))
1478 {
1479 ctx.diag.push_error(format!("The function '{}' is protected", second.text()), &second);
1480 }
1481 if let Some(x) = it.next() {
1482 ctx.diag.push_error("Cannot access fields of a function".into(), &x)
1483 }
1484 let callable = match lookup_result.builtin_function {
1485 Some(builtin) => Callable::Builtin(builtin),
1486 None => Callable::Function(NamedReference::new(
1487 elem,
1488 lookup_result.resolved_name.to_smolstr(),
1489 )),
1490 };
1491 if matches!(fun.args.first(), Some(Type::ElementReference)) {
1492 LookupResult::Callable(LookupResultCallable::MemberFunction {
1493 base: Expression::ElementReference(Rc::downgrade(elem)),
1494 base_node: Some(NodeOrToken::Node(node.into())),
1495 member: Box::new(LookupResultCallable::Callable(callable)),
1496 })
1497 .into()
1498 } else {
1499 LookupResult::from(callable).into()
1500 }
1501 } else {
1502 let mut err = |extra: &str| {
1503 let what = match &elem.borrow().base_type {
1504 ElementType::Global => {
1505 let global = elem.borrow().enclosing_component.upgrade().unwrap();
1506 assert!(global.is_global());
1507 format!("'{}'", global.id)
1508 }
1509 ElementType::Component(c) => format!("Element '{}'", c.id),
1510 ElementType::Builtin(b) => format!("Element '{}'", b.name),
1511 ElementType::Native(_) => unreachable!("the native pass comes later"),
1512 ElementType::Error => {
1513 assert!(ctx.diag.has_errors());
1514 return;
1515 }
1516 };
1517 ctx.diag.push_error(
1518 format!("{} does not have a property '{}'{}", what, second.text(), extra),
1519 &second,
1520 );
1521 };
1522 if let Some(minus_pos) = second.text().find('-') {
1523 if elem
1525 .borrow()
1526 .lookup_property(&crate::parser::normalize_identifier(&second.text()[0..minus_pos]))
1527 .property_type
1528 != Type::Invalid
1529 {
1530 err(". Use space before the '-' if you meant a subtraction");
1531 return None;
1532 }
1533 }
1534 err("");
1535 None
1536 }
1537}
1538
1539fn maybe_lookup_object(
1540 mut base: LookupResult,
1541 it: impl Iterator<Item = crate::parser::SyntaxToken>,
1542 ctx: &mut LookupCtx,
1543) -> Option<LookupResult> {
1544 for next in it {
1545 let next_str = crate::parser::normalize_identifier(next.text());
1546 ctx.current_token = Some(next.clone().into());
1547 match base.lookup(ctx, &next_str) {
1548 Some(r) => {
1549 base = r;
1550 }
1551 None => {
1552 if let Some(minus_pos) = next.text().find('-') {
1553 if base.lookup(ctx, &SmolStr::new(&next.text()[0..minus_pos])).is_some() {
1554 ctx.diag.push_error(format!("Cannot access the field '{}'. Use space before the '-' if you meant a subtraction", next.text()), &next);
1555 return None;
1556 }
1557 }
1558
1559 match base {
1560 LookupResult::Callable(LookupResultCallable::Callable(Callable::Callback(
1561 ..,
1562 ))) => ctx.diag.push_error("Cannot access fields of callback".into(), &next),
1563 LookupResult::Callable(..) => {
1564 ctx.diag.push_error("Cannot access fields of a function".into(), &next)
1565 }
1566 LookupResult::Enumeration(enumeration) => ctx.diag.push_error(
1567 format!(
1568 "'{}' is not a member of the enum {}",
1569 next.text(),
1570 enumeration.name
1571 ),
1572 &next,
1573 ),
1574
1575 LookupResult::Namespace(ns) => {
1576 ctx.diag.push_error(
1577 format!("'{}' is not a member of the namespace {}", next.text(), ns),
1578 &next,
1579 );
1580 }
1581 LookupResult::Expression { expression, .. } => {
1582 let ty_descr = match expression.ty() {
1583 Type::Struct { .. } => String::new(),
1584 Type::Float32
1585 if ctx.property_type == Type::Model
1586 && matches!(
1587 expression,
1588 Expression::NumberLiteral(_, Unit::None),
1589 ) =>
1590 {
1591 format!(" of float. Range expressions are not supported in Slint, but you can use an integer as a model to repeat something multiple time. Eg: `for i in {}`", next.text())
1593 }
1594
1595 ty => format!(" of {ty}"),
1596 };
1597 ctx.diag.push_error(
1598 format!("Cannot access the field '{}'{}", next.text(), ty_descr),
1599 &next,
1600 );
1601 }
1602 }
1603 return None;
1604 }
1605 }
1606 }
1607 Some(base)
1608}
1609
1610fn resolve_two_way_bindings(
1612 doc: &Document,
1613 type_register: &TypeRegister,
1614 diag: &mut BuildDiagnostics,
1615) {
1616 for component in doc.inner_components.iter() {
1617 recurse_elem_with_scope(
1618 &component.root_element,
1619 ComponentScope(vec![]),
1620 &mut |elem, scope| {
1621 for (prop_name, binding) in &elem.borrow().bindings {
1622 let mut binding = binding.borrow_mut();
1623 if let Expression::Uncompiled(node) =
1624 binding.expression.ignore_debug_hooks().clone()
1625 {
1626 if let Some(n) = syntax_nodes::TwoWayBinding::new(node.clone()) {
1627 let lhs_lookup = elem.borrow().lookup_property(prop_name);
1628 if !lhs_lookup.is_valid() {
1629 assert!(diag.has_errors());
1631 continue;
1632 }
1633 let mut lookup_ctx = LookupCtx {
1634 property_name: Some(prop_name.as_str()),
1635 property_type: lhs_lookup.property_type.clone(),
1636 component_scope: &scope.0,
1637 diag,
1638 arguments: vec![],
1639 type_register,
1640 type_loader: None,
1641 current_token: Some(node.clone().into()),
1642 };
1643
1644 binding.expression = Expression::Invalid;
1645
1646 if let Some(nr) = resolve_two_way_binding(n, &mut lookup_ctx) {
1647 binding.two_way_bindings.push(nr.clone());
1648
1649 nr.element()
1650 .borrow()
1651 .property_analysis
1652 .borrow_mut()
1653 .entry(nr.name().clone())
1654 .or_default()
1655 .is_linked = true;
1656
1657 if matches!(
1658 lhs_lookup.property_visibility,
1659 PropertyVisibility::Private | PropertyVisibility::Output
1660 ) && !lhs_lookup.is_local_to_component
1661 {
1662 assert!(diag.has_errors() || elem.borrow().is_legacy_syntax);
1664 continue;
1665 }
1666
1667 let mut rhs_lookup =
1669 nr.element().borrow().lookup_property(nr.name());
1670 if rhs_lookup.property_type == Type::Invalid {
1671 assert!(diag.has_errors());
1673 continue;
1674 }
1675 rhs_lookup.is_local_to_component &=
1676 lookup_ctx.is_local_element(&nr.element());
1677
1678 if !rhs_lookup.is_valid_for_assignment() {
1679 match (
1680 lhs_lookup.property_visibility,
1681 rhs_lookup.property_visibility,
1682 ) {
1683 (PropertyVisibility::Input, PropertyVisibility::Input)
1684 if !lhs_lookup.is_local_to_component =>
1685 {
1686 assert!(rhs_lookup.is_local_to_component);
1687 marked_linked_read_only(elem, prop_name);
1688 }
1689 (
1690 PropertyVisibility::Output
1691 | PropertyVisibility::Private,
1692 PropertyVisibility::Output | PropertyVisibility::Input,
1693 ) => {
1694 assert!(lhs_lookup.is_local_to_component);
1695 marked_linked_read_only(elem, prop_name);
1696 }
1697 (PropertyVisibility::Input, PropertyVisibility::Output)
1698 if !lhs_lookup.is_local_to_component =>
1699 {
1700 assert!(!rhs_lookup.is_local_to_component);
1701 marked_linked_read_only(elem, prop_name);
1702 }
1703 _ => {
1704 if lookup_ctx.is_legacy_component() {
1705 diag.push_warning(
1706 format!(
1707 "Link to a {} property is deprecated",
1708 rhs_lookup.property_visibility
1709 ),
1710 &node,
1711 );
1712 } else {
1713 diag.push_error(
1714 format!(
1715 "Cannot link to a {} property",
1716 rhs_lookup.property_visibility
1717 ),
1718 &node,
1719 )
1720 }
1721 }
1722 }
1723 } else if !lhs_lookup.is_valid_for_assignment() {
1724 if rhs_lookup.is_local_to_component
1725 && rhs_lookup.property_visibility
1726 == PropertyVisibility::InOut
1727 {
1728 if lookup_ctx.is_legacy_component() {
1729 debug_assert!(!diag.is_empty()); } else {
1731 diag.push_error(
1732 "Cannot link input property".into(),
1733 &node,
1734 );
1735 }
1736 } else if rhs_lookup.property_visibility
1737 == PropertyVisibility::InOut
1738 {
1739 diag.push_warning("Linking input properties to input output properties is deprecated".into(), &node);
1740 marked_linked_read_only(&nr.element(), nr.name());
1741 } else {
1742 marked_linked_read_only(&nr.element(), nr.name());
1744 }
1745 }
1746 }
1747 }
1748 }
1749 }
1750 },
1751 );
1752 }
1753
1754 fn marked_linked_read_only(elem: &ElementRc, prop_name: &str) {
1755 elem.borrow()
1756 .property_analysis
1757 .borrow_mut()
1758 .entry(prop_name.into())
1759 .or_default()
1760 .is_linked_to_read_only = true;
1761 }
1762}
1763
1764pub fn resolve_two_way_binding(
1765 node: syntax_nodes::TwoWayBinding,
1766 ctx: &mut LookupCtx,
1767) -> Option<NamedReference> {
1768 let Some(n) = node.Expression().QualifiedName() else {
1769 ctx.diag.push_error(
1770 "The expression in a two way binding must be a property reference".into(),
1771 &node.Expression(),
1772 );
1773 return None;
1774 };
1775
1776 let Some(r) = lookup_qualified_name_node(n, ctx, LookupPhase::ResolvingTwoWayBindings) else {
1777 assert!(ctx.diag.has_errors());
1778 return None;
1779 };
1780
1781 let report_error = !matches!(
1783 ctx.property_type,
1784 Type::InferredProperty | Type::InferredCallback | Type::Invalid
1785 );
1786 match r {
1787 LookupResult::Expression { expression: Expression::PropertyReference(n), .. } => {
1788 if report_error && n.ty() != ctx.property_type {
1789 ctx.diag.push_error(
1790 "The property does not have the same type as the bound property".into(),
1791 &node,
1792 );
1793 }
1794 Some(n)
1795 }
1796 LookupResult::Callable(LookupResultCallable::Callable(Callable::Callback(n))) => {
1797 if report_error && n.ty() != ctx.property_type {
1798 ctx.diag.push_error("Cannot bind to a callback".into(), &node);
1799 None
1800 } else {
1801 Some(n)
1802 }
1803 }
1804 LookupResult::Callable(..) => {
1805 if report_error {
1806 ctx.diag.push_error("Cannot bind to a function".into(), &node);
1807 }
1808 None
1809 }
1810 _ => {
1811 ctx.diag.push_error(
1812 "The expression in a two way binding must be a property reference".into(),
1813 &node,
1814 );
1815 None
1816 }
1817 }
1818}
1819
1820fn check_callback_alias_validity(
1822 node: &syntax_nodes::CallbackConnection,
1823 elem: &ElementRc,
1824 name: &str,
1825 diag: &mut BuildDiagnostics,
1826) {
1827 let elem_borrow = elem.borrow();
1828 let Some(decl) = elem_borrow.property_declarations.get(name) else {
1829 if let ElementType::Component(c) = &elem_borrow.base_type {
1830 check_callback_alias_validity(node, &c.root_element, name, diag);
1831 }
1832 return;
1833 };
1834 let Some(b) = elem_borrow.bindings.get(name) else { return };
1835 let Some(alias) = b.try_borrow().ok().and_then(|b| b.two_way_bindings.first().cloned()) else {
1837 return;
1838 };
1839
1840 if alias.element().borrow().base_type == ElementType::Global {
1841 diag.push_error(
1842 "Can't assign a local callback handler to an alias to a global callback".into(),
1843 &node.child_token(SyntaxKind::Identifier).unwrap(),
1844 );
1845 }
1846 if let Type::Callback(callback) = &decl.property_type {
1847 let num_arg = node.DeclaredIdentifier().count();
1848 if num_arg > callback.args.len() {
1849 diag.push_error(
1850 format!(
1851 "'{name}' only has {} arguments, but {num_arg} were provided",
1852 callback.args.len(),
1853 ),
1854 &node.child_token(SyntaxKind::Identifier).unwrap(),
1855 );
1856 }
1857 }
1858}