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