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