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