1use crate::diagnostics::{BuildDiagnostics, Spanned};
13use crate::expression_tree::*;
14use crate::langtype;
15use crate::langtype::{
16 ElementType, KeyboardModifiers, PropertyLookupMode, Struct, StructName, Type,
17};
18use crate::lookup::{LookupCtx, LookupObject, LookupResult, LookupResultCallable};
19use crate::object_tree::*;
20use crate::parser::{
21 NodeOrToken, SyntaxKind, SyntaxNode, TextRange, identifier_text, syntax_nodes,
22};
23use crate::symbol_counters::SymbolCounters;
24use crate::typeregister::TypeRegister;
25use core::num::IntErrorKind;
26use smol_str::{SmolStr, ToSmolStr};
27use std::collections::BTreeMap;
28use std::rc::Rc;
29use std::sync::Arc;
30use unicode_segmentation::UnicodeSegmentation;
31
32mod remove_noop;
33
34#[derive(Clone)]
37struct ComponentScope(Vec<ElementRc>);
38
39fn resolve_expression(
40 elem: &ElementRc,
41 expr: &mut Expression,
42 property_name: Option<&str>,
43 property_type: Type,
44 scope: &[ElementRc],
45 type_register: &TypeRegister,
46 type_loader: &crate::typeloader::TypeLoader,
47 diag: &mut BuildDiagnostics,
48) {
49 if let Expression::Uncompiled(node) = expr.ignore_debug_hooks() {
50 let mut lookup_ctx = LookupCtx {
51 property_name,
52 property_type,
53 expected_type: Type::default(),
54 component_scope: scope,
55 diag,
56 symbol_counters: type_loader.symbol_counters.clone(),
57 arguments: Vec::new(),
58 type_register,
59 type_loader: Some(type_loader),
60 current_token: None,
61 local_variables: Vec::new(),
62 expected_type_probe: None,
63 };
64 lookup_ctx.expected_type = lookup_ctx.return_type().clone();
65
66 let new_expr = match node.kind() {
67 SyntaxKind::CallbackConnection => {
68 let node = syntax_nodes::CallbackConnection::from(node.clone());
69 if let Some(property_name) = property_name {
70 check_callback_alias_validity(&node, elem, property_name, lookup_ctx.diag);
71 }
72 let expr = Expression::from_callback_connection(node.clone(), &mut lookup_ctx);
73 #[cfg(feature = "slint-sc")]
74 check_slint_sc_handler_body(&expr, &node, &mut lookup_ctx);
75 expr
76 }
77 SyntaxKind::Function => Expression::from_function(node.clone().into(), &mut lookup_ctx),
78 SyntaxKind::Expression => {
79 Expression::from_expression_node(node.clone().into(), &mut lookup_ctx)
81 .maybe_convert_to(
82 lookup_ctx.property_type.clone(),
83 node,
84 lookup_ctx.diag,
85 &lookup_ctx.symbol_counters,
86 )
87 }
88 SyntaxKind::BindingExpression => {
89 Expression::from_binding_expression_node(node.clone(), &mut lookup_ctx)
90 }
91 SyntaxKind::PropertyChangedCallback => {
92 let node = syntax_nodes::PropertyChangedCallback::from(node.clone());
93 if let Some(code_block_node) = node.CodeBlock() {
94 Expression::from_codeblock_node(code_block_node, &mut lookup_ctx)
95 } else if let Some(expr_node) = node.Expression() {
96 Expression::from_expression_node(expr_node, &mut lookup_ctx)
97 } else {
98 assert!(diag.has_errors());
99 Expression::Invalid
100 }
101 }
102 SyntaxKind::TwoWayBinding => {
103 assert!(
104 diag.has_errors(),
105 "Two way binding should have been resolved already (property: {property_name:?})"
106 );
107 Expression::Invalid
108 }
109 SyntaxKind::AtKeys => {
110 Expression::from_at_keys_node(node.clone().into(), &mut lookup_ctx)
111 }
112 _ => {
113 debug_assert!(diag.has_errors());
114 Expression::Invalid
115 }
116 };
117 match expr {
118 Expression::DebugHook { expression, .. } => **expression = new_expr,
119 _ => *expr = new_expr,
120 }
121 }
122}
123
124fn resolve_match_elements(
126 elem: &ElementRc,
127 scope: &[ElementRc],
128 type_register: &TypeRegister,
129 type_loader: &crate::typeloader::TypeLoader,
130 diag: &mut BuildDiagnostics,
131) {
132 let mut match_elements = std::mem::take(&mut elem.borrow_mut().match_elements);
133 for match_element in &mut match_elements {
134 if match_element.cases.is_empty()
135 && matches!(match_element.wildcard, WildcardMatchCaseInfo::None)
136 {
137 continue;
138 }
139 resolve_expression(
140 elem,
141 &mut match_element.subject,
142 None,
143 Type::Invalid,
144 scope,
145 type_register,
146 type_loader,
147 diag,
148 );
149 let case_type = match_element.subject.ty();
150 for case in &mut match_element.cases {
151 resolve_expression(
152 elem,
153 &mut case.value,
154 None,
155 case_type.clone(),
156 scope,
157 type_register,
158 type_loader,
159 diag,
160 );
161 check_case_value(&case.value, &case.node, diag);
162 }
163 let values: Vec<Option<CaseValue>> =
164 match_element.cases.iter().map(|case| CaseValue::new(&case.value)).collect();
165 check_duplicate_cases(&match_element.cases, &values, diag);
166 check_exhaustiveness(match_element, &values, diag);
167
168 let subject_ref = crate::layout::create_new_prop(elem, "match-subject".into(), case_type);
169 let subject = std::mem::replace(
170 &mut match_element.subject,
171 Expression::PropertyReference(subject_ref.clone()),
172 );
173 elem.borrow_mut().set_binding(subject_ref.name().clone(), subject.into());
174
175 match_element.lower_to_conditional_elements();
176 }
177}
178
179fn check_case_value(value: &Expression, node: &SyntaxNode, diag: &mut BuildDiagnostics) {
181 let is_literal = as_number_literal(value).is_some()
182 || matches!(
183 value,
184 Expression::StringLiteral(..)
185 | Expression::BoolLiteral(..)
186 | Expression::EnumerationValue(..)
187 );
188 let is_valid_cast = matches!(
189 value,
190 Expression::Cast { from, to, .. }
191 if as_number_literal(from).is_some()
192 && matches!(to, Type::Color | Type::Int32)
193 );
194
195 if let Some((number, Unit::None)) = as_number_literal(value)
196 && number.fract() != 0.0
197 {
198 diag.push_warning("Floating point comparison is not recommended".into(), node);
199 }
200
201 if is_literal || is_valid_cast {
202 } else if matches!(value, Expression::Cast { .. }) {
204 diag.push_error("Cannot perform type conversion".into(), node);
205 } else {
206 diag.push_error("Cases must be literal values".into(), node);
207 }
208}
209
210fn as_number_literal(value: &Expression) -> Option<(f64, Unit)> {
211 match value {
212 Expression::NumberLiteral(number, unit) => Some((*number, *unit)),
213 Expression::UnaryOp { sub, op: '-' } => as_number_literal(sub).map(|(n, u)| (-n, u)),
214 _ => None,
215 }
216}
217
218#[derive(PartialEq)]
219enum CaseValue {
220 Number(f64, Unit),
221 String(SmolStr),
222 Bool(bool),
223 Enumeration(langtype::EnumerationValue),
224}
225
226impl CaseValue {
227 fn new(value: &Expression) -> Option<Self> {
228 match value {
229 Expression::Cast { from, .. } => Self::new(from),
230 Expression::UnaryOp { sub, op: '-' } => match Self::new(sub)? {
231 Self::Number(number, unit) => Some(Self::Number(-number, unit)),
232 _ => None,
233 },
234 Expression::NumberLiteral(number, unit) => Some(Self::Number(*number, *unit)),
235 Expression::StringLiteral(string) => Some(Self::String(string.clone())),
236 Expression::BoolLiteral(boolean) => Some(Self::Bool(*boolean)),
237 Expression::EnumerationValue(value) => Some(Self::Enumeration(value.clone())),
238 _ => None, }
240 }
241}
242
243fn check_duplicate_cases(
245 cases: &[MatchCaseInfo],
246 values: &[Option<CaseValue>],
247 diag: &mut BuildDiagnostics,
248) {
249 let mut seen: Vec<&CaseValue> = Vec::with_capacity(values.len());
250 for (case, value) in cases.iter().zip(values) {
251 let Some(value) = value else {
252 continue; };
254 if seen.contains(&value) {
255 diag.push_error("Duplicate case value".into(), &case.node);
256 } else {
257 seen.push(value);
258 }
259 }
260}
261
262impl std::fmt::Display for CaseValue {
263 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
264 match self {
265 CaseValue::Number(number, _) => write!(f, "{number}"),
266 CaseValue::String(string) => write!(f, "{string:?}"),
267 CaseValue::Bool(boolean) => write!(f, "{boolean}"),
268 CaseValue::Enumeration(value) => write!(f, "{value}"),
269 }
270 }
271}
272
273fn check_exhaustiveness(
275 match_element: &MatchElementInfo,
276 values: &[Option<CaseValue>],
277 diag: &mut BuildDiagnostics,
278) {
279 if !matches!(match_element.wildcard, WildcardMatchCaseInfo::None) {
280 return;
281 }
282 let mut covered: Vec<&CaseValue> = Vec::with_capacity(values.len());
284 for value in values {
285 let Some(value) = value else {
286 return;
287 };
288 covered.push(value);
289 }
290 let subject_node = match_element.node.Expression();
291 let subject_type = match_element.subject.ty();
292 let expected: Vec<CaseValue> = match &subject_type {
293 Type::Bool => vec![CaseValue::Bool(true), CaseValue::Bool(false)],
294 Type::Enumeration(enumeration) => (0..enumeration.values.len())
295 .map(|value| {
296 CaseValue::Enumeration(langtype::EnumerationValue {
297 value,
298 enumeration: enumeration.clone(),
299 })
300 })
301 .collect(),
302 Type::Invalid => return,
304 _ => {
305 diag.push_error(
306 format!("Non-exhaustive match on {subject_type}: a '*' case is required"),
307 &subject_node,
308 );
309 return;
310 }
311 };
312
313 let mut missing = Vec::new();
314 for value in &expected {
315 if !covered.contains(&value) {
316 missing.push(format!("'{value}'"));
317 }
318 }
319 if !missing.is_empty() {
320 diag.push_error(
321 format!("Non-exhaustive match on {subject_type}: missing {}", missing.join(", ")),
322 &subject_node,
323 );
324 }
325}
326
327fn recurse_elem_with_scope(
331 elem: &ElementRc,
332 mut scope: ComponentScope,
333 vis: &mut impl FnMut(&ElementRc, &ComponentScope),
334) -> ComponentScope {
335 scope.0.push(elem.clone());
336 vis(elem, &scope);
337 for sub in &elem.borrow().children {
338 scope = recurse_elem_with_scope(sub, scope, vis);
339 }
340 scope.0.pop();
341 scope
342}
343
344pub fn resolve_expressions(
345 doc: &Document,
346 type_loader: &crate::typeloader::TypeLoader,
347 diag: &mut BuildDiagnostics,
348) {
349 for component in doc.inner_components.iter() {
350 recurse_elem_with_scope(
351 &component.root_element,
352 ComponentScope(Vec::new()),
353 &mut |elem, scope| {
354 if elem.borrow().repeated.is_some() {
358 debug_assert!(scope.0.len() > 1);
359 let parent_scope = &scope.0[..scope.0.len() - 1];
360 visit_repeater_model_expression(elem, |expr, property_name, property_type| {
361 resolve_expression(
362 elem,
363 expr,
364 property_name,
365 property_type(),
366 parent_scope,
367 &doc.local_registry,
368 type_loader,
369 diag,
370 );
371 });
372 }
373
374 resolve_match_elements(elem, &scope.0, &doc.local_registry, type_loader, diag);
375
376 resolve_two_way_bindings_for_element(elem, &scope.0, &doc.local_registry, diag);
377
378 visit_element_expressions_excluding_repeater_model(
379 elem,
380 |expr, property_name, property_type| {
381 resolve_expression(
382 elem,
383 expr,
384 property_name,
385 property_type(),
386 &scope.0,
387 &doc.local_registry,
388 type_loader,
389 diag,
390 );
391 },
392 );
393 },
394 );
395 }
396}
397
398#[derive(Default)]
402enum LookupPhase {
403 #[default]
404 UnspecifiedPhase,
405 ResolvingTwoWayBindings,
406}
407
408fn probe_range(node: &SyntaxNode) -> TextRange {
411 let range = node.text_range();
412 let mut start = range.start();
413 let mut prev = node.node.prev_sibling_or_token();
414 while let Some(rowan::NodeOrToken::Token(t)) = &prev {
415 if !matches!(t.kind(), SyntaxKind::Whitespace | SyntaxKind::Comment) {
416 break;
417 }
418 start = t.text_range().start();
419 prev = t.prev_sibling_or_token();
420 }
421 TextRange::new(start, range.end())
422}
423
424impl Expression {
425 pub fn from_binding_expression_node(node: SyntaxNode, ctx: &mut LookupCtx) -> Self {
426 debug_assert_eq!(node.kind(), SyntaxKind::BindingExpression);
427 let e = node
428 .children()
429 .find_map(|n| match n.kind() {
430 SyntaxKind::Expression => Some(Self::from_expression_node(n.into(), ctx)),
431 SyntaxKind::CodeBlock => Some(Self::from_codeblock_node(n.into(), ctx)),
432 _ => None,
433 })
434 .unwrap_or(Self::Invalid);
435 if ctx.property_type == Type::LogicalLength && e.ty() == Type::Percent {
436 const RELATIVE_TO_PARENT_PROPERTIES: &[&str] =
438 &["width", "height", "preferred-width", "preferred-height"];
439 let property_name = ctx.property_name.unwrap_or_default();
440 if RELATIVE_TO_PARENT_PROPERTIES.contains(&property_name) {
441 return e;
442 } else {
443 ctx.diag.push_error(
444 format!(
445 "Automatic conversion from percentage to length is only possible for the following properties: {}",
446 RELATIVE_TO_PARENT_PROPERTIES.join(", ")
447 ),
448 &node
449 );
450 return Expression::Invalid;
451 }
452 };
453 if !matches!(ctx.property_type, Type::Callback { .. } | Type::Function { .. }) {
454 e.maybe_convert_to(ctx.property_type.clone(), &node, ctx.diag, &ctx.symbol_counters)
455 } else {
456 assert!(ctx.diag.has_errors());
458 e
459 }
460 }
461
462 fn from_codeblock_node(node: syntax_nodes::CodeBlock, ctx: &mut LookupCtx) -> Expression {
463 debug_assert_eq!(node.kind(), SyntaxKind::CodeBlock);
464
465 ctx.local_variables.push(Vec::new());
467
468 let mut statements_or_exprs = node
469 .children()
470 .filter_map(|n| match n.kind() {
471 SyntaxKind::Expression => {
472 Some((n.clone(), Self::from_expression_node(n.into(), ctx)))
473 }
474 SyntaxKind::ReturnStatement => {
475 Some((n.clone(), Self::from_return_statement(n.into(), ctx)))
476 }
477 SyntaxKind::LetStatement => {
478 Some((n.clone(), Self::from_let_statement(n.into(), ctx)))
479 }
480 _ => None,
481 })
482 .collect::<Vec<_>>();
483
484 remove_noop::remove_from_codeblock(&mut statements_or_exprs, ctx.diag);
485
486 let mut statements_or_exprs = statements_or_exprs
487 .into_iter()
488 .map(|(_node, statement_or_expr)| statement_or_expr)
489 .collect::<Vec<_>>();
490
491 let exit_points_and_return_types = statements_or_exprs
492 .iter()
493 .enumerate()
494 .filter_map(|(index, statement_or_expr)| {
495 if index == statements_or_exprs.len()
496 || matches!(statement_or_expr, Expression::ReturnStatement(..))
497 {
498 Some((index, statement_or_expr.ty()))
499 } else {
500 None
501 }
502 })
503 .collect::<Vec<_>>();
504
505 let common_return_type = Self::common_target_type_for_type_list(
506 exit_points_and_return_types.iter().map(|(_, ty)| ty.clone()),
507 );
508
509 exit_points_and_return_types.into_iter().for_each(|(index, _)| {
510 let mut expr = std::mem::replace(&mut statements_or_exprs[index], Expression::Invalid);
511 expr = expr.maybe_convert_to(
512 common_return_type.clone(),
513 &node,
514 ctx.diag,
515 &ctx.symbol_counters,
516 );
517 statements_or_exprs[index] = expr;
518 });
519
520 ctx.local_variables.pop();
522
523 Expression::CodeBlock(statements_or_exprs)
524 }
525
526 fn from_let_statement(node: syntax_nodes::LetStatement, ctx: &mut LookupCtx) -> Expression {
527 let name = identifier_text(&node.DeclaredIdentifier()).unwrap_or_default();
528
529 let global_lookup = crate::lookup::global_lookup();
530 if let Some(LookupResult::Expression {
531 expression:
532 Expression::ReadLocalVariable { .. } | Expression::FunctionParameterReference { .. },
533 ..
534 }) = global_lookup.lookup(ctx, &name)
535 {
536 ctx.diag
537 .push_error("Redeclaration of local variables is not allowed".to_string(), &node);
538 return Expression::Invalid;
539 }
540
541 let name: SmolStr = format!("local_{name}",).into();
543
544 let declared_ty = node.Type().map(|ty| type_from_node(ty, ctx.diag, ctx.type_register));
545 let value = match &declared_ty {
546 Some(t) => ctx.with_expected_type(t.clone(), |ctx| {
547 Self::from_expression_node(node.Expression(), ctx)
548 }),
549 None => Self::from_expression_node(node.Expression(), ctx),
550 };
551 let ty = declared_ty.unwrap_or_else(|| value.ty());
552
553 ctx.local_variables.last_mut().unwrap().push((name.clone(), ty.clone()));
555
556 let value =
557 Box::new(value.maybe_convert_to(ty.clone(), &node, ctx.diag, &ctx.symbol_counters));
558
559 Expression::StoreLocalVariable { name, value }
560 }
561
562 fn from_return_statement(
563 node: syntax_nodes::ReturnStatement,
564 ctx: &mut LookupCtx,
565 ) -> Expression {
566 let return_type = ctx.return_type().clone();
567 let e = node.Expression();
568 if e.is_none() && !matches!(return_type, Type::Void | Type::Invalid) {
569 ctx.diag.push_error(format!("Must return a value of type '{return_type}'"), &node);
570 }
571 Expression::ReturnStatement(e.map(|n| {
572 let e = ctx
573 .with_expected_type(return_type.clone(), |ctx| Self::from_expression_node(n, ctx));
574 Box::new(e.maybe_convert_to(return_type, &node, ctx.diag, &ctx.symbol_counters))
575 }))
576 }
577
578 fn from_callback_connection(
579 node: syntax_nodes::CallbackConnection,
580 ctx: &mut LookupCtx,
581 ) -> Expression {
582 ctx.arguments =
583 node.DeclaredIdentifier().map(|x| identifier_text(&x).unwrap_or_default()).collect();
584 if let Some(code_block_node) = node.CodeBlock() {
585 Self::from_codeblock_node(code_block_node, ctx).maybe_convert_to(
586 ctx.return_type().clone(),
587 &node,
588 ctx.diag,
589 &ctx.symbol_counters,
590 )
591 } else if let Some(expr_node) = node.Expression() {
592 Self::from_expression_node(expr_node, ctx).maybe_convert_to(
593 ctx.return_type().clone(),
594 &node,
595 ctx.diag,
596 &ctx.symbol_counters,
597 )
598 } else {
599 Expression::Invalid
600 }
601 }
602
603 fn from_function(node: syntax_nodes::Function, ctx: &mut LookupCtx) -> Expression {
604 ctx.arguments = node
605 .ArgumentDeclaration()
606 .map(|x| identifier_text(&x.DeclaredIdentifier()).unwrap_or_default())
607 .collect();
608 let Some(code_block) = node.CodeBlock() else {
609 debug_assert!(ctx.diag.has_errors());
610 return Expression::Invalid;
611 };
612 Self::from_codeblock_node(code_block, ctx).maybe_convert_to(
613 ctx.return_type().clone(),
614 &node,
615 ctx.diag,
616 &ctx.symbol_counters,
617 )
618 }
619
620 pub fn from_expression_node(node: syntax_nodes::Expression, ctx: &mut LookupCtx) -> Self {
621 if ctx.expected_type_probe.is_some() {
623 let ty = ctx.expected_type.clone();
624 ctx.record_expected_type_probe(probe_range(&node), &ty);
625 }
626
627 for child in node.children_with_tokens() {
633 match child {
634 NodeOrToken::Node(node) => match node.kind() {
635 SyntaxKind::Expression => return Self::from_expression_node(node.into(), ctx),
636 SyntaxKind::AtImageUrl => {
637 return Self::from_at_image_url_node(node.into(), ctx);
638 }
639 SyntaxKind::AtGradient => {
640 #[cfg(feature = "slint-sc")]
641 ctx.diag.slint_sc_error("@gradient expressions are", &node);
642 return Self::from_at_gradient(node.into(), ctx);
643 }
644 SyntaxKind::AtTr => {
645 #[cfg(feature = "slint-sc")]
646 ctx.diag.slint_sc_error("@tr() expressions are", &node);
647 return Self::from_at_tr(node.into(), ctx);
648 }
649 SyntaxKind::AtMarkdown => {
650 #[cfg(feature = "slint-sc")]
651 ctx.diag.slint_sc_error("@markdown() expressions are", &node);
652 return Self::from_at_markdown(node.into(), ctx);
653 }
654 SyntaxKind::AtKeys => {
655 #[cfg(feature = "slint-sc")]
656 ctx.diag.slint_sc_error("@keys() expressions are", &node);
657 return Self::from_at_keys_node(node.into(), ctx);
658 }
659 SyntaxKind::QualifiedName => {
660 return Self::from_qualified_name_node(node.into(), ctx);
661 }
662 SyntaxKind::FunctionCallExpression => {
663 let expr = Self::from_function_call_node(node.clone().into(), ctx);
664 #[cfg(feature = "slint-sc")]
667 if !matches!(
668 (&expr, &ctx.property_type),
669 (Expression::Invalid, _)
670 | (
671 Expression::FunctionCall {
672 function: Callable::Callback(..),
673 ..
674 },
675 Type::Callback(..)
676 )
677 ) {
678 ctx.diag.slint_sc_error("Function calls are", &node);
679 }
680 return expr;
681 }
682 SyntaxKind::MemberAccess => {
683 return Self::from_member_access_node(node.into(), ctx);
684 }
685 SyntaxKind::IndexExpression => {
686 #[cfg(feature = "slint-sc")]
687 ctx.diag.slint_sc_error("Index expressions are", &node);
688 return Self::from_index_expression_node(node.into(), ctx);
689 }
690 SyntaxKind::SelfAssignment => {
691 #[cfg(feature = "slint-sc")]
692 ctx.diag.slint_sc_error("Self-assignment expressions are", &node);
693 return Self::from_self_assignment_node(node.into(), ctx);
694 }
695 SyntaxKind::BinaryExpression => {
696 return Self::from_binary_expression_node(node.into(), ctx);
697 }
698 SyntaxKind::UnaryOpExpression => {
699 return Self::from_unaryop_expression_node(node.into(), ctx);
702 }
703 SyntaxKind::ConditionalExpression => {
704 return Self::from_conditional_expression_node(node.into(), ctx);
707 }
708 SyntaxKind::ObjectLiteral => {
709 return Self::from_object_literal_node(node.into(), ctx);
710 }
711 SyntaxKind::Array => {
712 #[cfg(feature = "slint-sc")]
713 ctx.diag.slint_sc_error("Array expressions are", &node);
714 return Self::from_array_node(node.into(), ctx);
715 }
716 SyntaxKind::CodeBlock => {
717 #[cfg(feature = "slint-sc")]
718 ctx.diag.slint_sc_error("Code blocks are", &node);
719 return Self::from_codeblock_node(node.into(), ctx);
720 }
721 SyntaxKind::StringTemplate => {
722 #[cfg(feature = "slint-sc")]
723 ctx.diag.slint_sc_error("String interpolation expressions are", &node);
724 return Self::from_string_template_node(node.into(), ctx);
725 }
726 SyntaxKind::Closure => {
727 return Self::from_closure_node(node.into(), ctx, None);
728 }
729 _ => {}
730 },
731 NodeOrToken::Token(token) => match token.kind() {
732 SyntaxKind::StringLiteral => {
733 #[cfg(feature = "slint-sc")]
734 ctx.diag.slint_sc_error("String literals are", &token);
735 return crate::literals::unescape_string_reporting(
736 Some(&token),
737 ctx.diag,
738 &token,
739 )
740 .map(Self::StringLiteral)
741 .unwrap_or(Self::Invalid);
742 }
743 SyntaxKind::NumberLiteral => {
744 return match crate::literals::parse_number_literal(token.text().into()) {
745 Ok((value, unit)) => {
746 #[cfg(feature = "slint-sc")]
747 {
748 use crate::expression_tree::WrittenUnit;
749 match unit {
750 WrittenUnit::Px if value.fract() != 0. => ctx
751 .diag
752 .slint_sc_error("Non-integral lengths are", &token),
753 WrittenUnit::Px => {}
754 WrittenUnit::None if value.fract() != 0. => ctx
757 .diag
758 .slint_sc_error("Non-integral numbers are", &token),
759 WrittenUnit::None => {}
760 _ => ctx.diag.slint_sc_error(
761 &format!("Number literals with the unit '{unit}' are"),
762 &token,
763 ),
764 }
765 }
766 let (value, unit) = unit.normalize(value);
767 Expression::NumberLiteral(value, unit)
768 }
769 Err(e) => {
770 ctx.diag.push_error(e.to_string(), &node);
771 Self::Invalid
772 }
773 };
774 }
775 SyntaxKind::ColorLiteral => {
776 return i_slint_common::color_parsing::parse_color_literal(token.text())
777 .map(|i| Expression::Cast {
778 from: Box::new(Expression::NumberLiteral(i as _, Unit::None)),
779 to: Type::Color,
780 })
781 .unwrap_or_else(|| {
782 ctx.diag.push_error("Invalid color literal".into(), &node);
783 Self::Invalid
784 });
785 }
786
787 _ => {}
788 },
789 }
790 }
791 Self::Invalid
792 }
793
794 fn from_at_image_url_node(node: syntax_nodes::AtImageUrl, ctx: &mut LookupCtx) -> Self {
795 let Some(s) = crate::literals::unescape_string_reporting(
796 node.child_token(SyntaxKind::StringLiteral).as_ref(),
797 ctx.diag,
798 &node,
799 ) else {
800 return Self::Invalid;
801 };
802
803 if s.is_empty() {
804 return Expression::ImageReference {
805 resource_ref: ImageReference::None,
806 source_location: Some(node.to_source_location()),
807 nine_slice: None,
808 };
809 }
810
811 let resource_ref = if s.starts_with("data:") {
812 ImageReference::DataUri(s)
813 } else {
814 let absolute_source_path = {
815 let path = std::path::Path::new(&s);
816 if crate::pathutils::is_absolute(path) {
817 s
818 } else {
819 ctx.type_loader
820 .and_then(|loader| {
821 loader.resolve_import_path(Some(&(*node).clone().into()), &s)
822 })
823 .map(|i| i.0.to_string_lossy().into())
824 .unwrap_or_else(|| {
825 crate::pathutils::join(
826 &crate::pathutils::dirname(node.source_file.path()),
827 path,
828 )
829 .map(|p| p.to_string_lossy().into())
830 .unwrap_or(s.clone())
831 })
832 }
833 };
834 ImageReference::from_resolved(absolute_source_path)
835 };
836
837 #[cfg(feature = "slint-sc")]
840 match &resource_ref {
841 ImageReference::DataUri(_) => {
842 ctx.diag.slint_sc_error("Data URIs in @image-url() are", &node)
843 }
844 ImageReference::Url(_) => ctx.diag.slint_sc_error("URLs in @image-url() are", &node),
845 _ => {}
846 }
847
848 let nine_slice = node
849 .children_with_tokens()
850 .filter_map(|n| n.into_token())
851 .filter(|t| t.kind() == SyntaxKind::NumberLiteral)
852 .map(|arg| {
853 arg.text().parse().unwrap_or_else(|err: std::num::ParseIntError| {
854 match err.kind() {
855 IntErrorKind::PosOverflow | IntErrorKind::NegOverflow => {
856 ctx.diag.push_error("Number too big".into(), &arg)
857 }
858 IntErrorKind::InvalidDigit => ctx.diag.push_error(
859 "Border widths of a nine-slice can't have units".into(),
860 &arg,
861 ),
862 _ => ctx.diag.push_error("Cannot parse number literal".into(), &arg),
863 };
864 0u16
865 })
866 })
867 .collect::<Vec<u16>>();
868
869 let nine_slice = match nine_slice.as_slice() {
870 [x] => Some([*x, *x, *x, *x]),
871 [x, y] => Some([*x, *y, *x, *y]),
872 [x, y, z, w] => Some([*x, *y, *z, *w]),
873 [] => None,
874 _ => {
875 assert!(ctx.diag.has_errors());
876 None
877 }
878 };
879
880 #[cfg(feature = "slint-sc")]
881 if nine_slice.is_some() {
882 ctx.diag.slint_sc_error("Nine-slice borders in @image-url() are", &node);
883 }
884
885 Expression::ImageReference {
886 resource_ref,
887 source_location: Some(node.to_source_location()),
888 nine_slice,
889 }
890 }
891
892 pub fn from_at_gradient(node: syntax_nodes::AtGradient, ctx: &mut LookupCtx) -> Self {
893 enum GradKind {
894 Linear {
895 angle: Box<Expression>,
896 },
897 Radial {
898 center: Option<(Box<Expression>, Box<Expression>)>,
899 radius: Option<Box<Expression>>,
900 },
901 Conic {
902 from_angle: Box<Expression>,
903 center: Option<(Box<Expression>, Box<Expression>)>,
904 },
905 }
906
907 let all_subs: Vec<_> = node
908 .children_with_tokens()
909 .filter(|n| matches!(n.kind(), SyntaxKind::Comma | SyntaxKind::Expression))
910 .collect();
911
912 let grad_token = node.child_token(SyntaxKind::Identifier).unwrap();
913 let grad_text = grad_token.text();
914
915 let parse_at_center = |idx: usize,
917 ctx: &mut LookupCtx|
918 -> Option<(Box<Expression>, Box<Expression>)> {
919 let cx_node = all_subs.get(idx)?;
920 let cy_node = all_subs.get(idx + 1)?;
921 if cx_node.kind() != SyntaxKind::Expression || cy_node.kind() != SyntaxKind::Expression
922 {
923 return None;
924 }
925 let cx_syn = syntax_nodes::Expression::from(cx_node.as_node().unwrap().clone());
926 let cy_syn = syntax_nodes::Expression::from(cy_node.as_node().unwrap().clone());
927 let cx =
928 Box::new(Expression::from_expression_node(cx_syn.clone(), ctx).maybe_convert_to(
929 Type::LogicalLength,
930 &cx_syn,
931 ctx.diag,
932 &ctx.symbol_counters,
933 ));
934 let cy =
935 Box::new(Expression::from_expression_node(cy_syn.clone(), ctx).maybe_convert_to(
936 Type::LogicalLength,
937 &cy_syn,
938 ctx.diag,
939 &ctx.symbol_counters,
940 ));
941 Some((cx, cy))
942 };
943
944 let (grad_kind, stops_start_idx) = if grad_text.starts_with("linear") {
945 let angle_expr = match all_subs.first() {
946 Some(e) if e.kind() == SyntaxKind::Expression => {
947 syntax_nodes::Expression::from(e.as_node().unwrap().clone())
948 }
949 _ => {
950 ctx.diag.push_error("Expected angle expression".into(), &node);
951 return Expression::Invalid;
952 }
953 };
954 if all_subs.get(1).is_none_or(|s| s.kind() != SyntaxKind::Comma) {
955 ctx.diag.push_error(
956 "Angle expression must be an angle followed by a comma".into(),
957 &node,
958 );
959 return Expression::Invalid;
960 }
961 let angle = Box::new(
962 Expression::from_expression_node(angle_expr.clone(), ctx).maybe_convert_to(
963 Type::Angle,
964 &angle_expr,
965 ctx.diag,
966 &ctx.symbol_counters,
967 ),
968 );
969 (GradKind::Linear { angle }, 2)
970 } else if grad_text.starts_with("radial") {
971 if !all_subs.first().is_some_and(|n| {
972 matches!(n, NodeOrToken::Node(node) if node.text().to_string().trim() == "circle")
973 }) {
974 ctx.diag.push_error("Expected 'circle': currently, only @radial-gradient(circle, ...) are supported".into(), &node);
975 return Expression::Invalid;
976 }
977 let mut idx = 1;
979
980 let radius = if all_subs.get(idx).is_some_and(|n| {
984 n.kind() == SyntaxKind::Expression
985 && !matches!(n, NodeOrToken::Node(node) if node.text().to_string().trim() == "at")
986 }) {
987 let r = all_subs.get(idx).unwrap();
988 let r_syn = syntax_nodes::Expression::from(r.as_node().unwrap().clone());
989 let expr = Expression::from_expression_node(r_syn.clone(), ctx);
990 if matches!(expr.ty(), Type::LogicalLength | Type::Float32 | Type::Int32) {
991 let radius = Box::new(
992 expr.maybe_convert_to(Type::LogicalLength, &r_syn, ctx.diag, &ctx.symbol_counters),
993 );
994 idx += 1;
995 Some(radius)
996 } else {
997 None
998 }
999 } else {
1000 None
1001 };
1002
1003 let center = if all_subs.get(idx).is_some_and(
1005 |n| matches!(n, NodeOrToken::Node(node) if node.text().to_string().trim() == "at"),
1006 ) {
1007 let center = parse_at_center(idx + 1, ctx);
1008 if center.is_none() {
1009 ctx.diag.push_error(
1010 "Expected two length values after 'at'".into(),
1011 all_subs.get(idx).unwrap(),
1012 );
1013 return Expression::Invalid;
1014 }
1015 idx += 3; center
1017 } else {
1018 None
1019 };
1020
1021 let stops_start = if all_subs.get(idx).is_none() {
1022 idx
1023 } else if all_subs.get(idx).is_some_and(|s| s.kind() == SyntaxKind::Comma) {
1024 idx + 1
1025 } else {
1026 if idx == 1 {
1027 let message = "'circle' must be followed by a comma, a radius, or 'at'".into();
1028 if let Some(error_node) = all_subs.get(idx) {
1029 ctx.diag.push_error(message, error_node);
1030 } else {
1031 ctx.diag.push_error(message, &node);
1032 }
1033 } else {
1034 ctx.diag
1035 .push_error("gradient header must be followed by a comma".into(), &node);
1036 }
1037 return Expression::Invalid;
1038 };
1039 (GradKind::Radial { center, radius }, stops_start)
1040 } else if grad_text.starts_with("conic") {
1041 let mut idx = 0usize;
1043 let from_angle = if all_subs.first().is_some_and(|n| {
1044 matches!(n, NodeOrToken::Node(node) if node.text().to_string().trim() == "from")
1045 }) {
1046 let angle_expr = match all_subs.get(1) {
1048 Some(e) if e.kind() == SyntaxKind::Expression => {
1049 syntax_nodes::Expression::from(e.as_node().unwrap().clone())
1050 }
1051 _ => {
1052 ctx.diag.push_error("Expected angle expression after 'from'".into(), &node);
1053 return Expression::Invalid;
1054 }
1055 };
1056 let angle = Box::new(
1057 Expression::from_expression_node(angle_expr.clone(), ctx).maybe_convert_to(
1058 Type::Angle,
1059 &angle_expr,
1060 ctx.diag, &ctx.symbol_counters),
1061 );
1062 idx = 2; angle
1064 } else {
1065 Box::new(Expression::NumberLiteral(0., Unit::Deg))
1067 };
1068
1069 let center = if all_subs.get(idx).is_some_and(
1071 |n| matches!(n, NodeOrToken::Node(node) if node.text().to_string().trim() == "at"),
1072 ) {
1073 let center = parse_at_center(idx + 1, ctx);
1074 if center.is_none() {
1075 ctx.diag.push_error(
1076 "Expected two length values after 'at'".into(),
1077 all_subs.get(idx).unwrap(),
1078 );
1079 return Expression::Invalid;
1080 }
1081 idx += 3; center
1083 } else {
1084 None
1085 };
1086
1087 if (idx > 0) && all_subs.get(idx).is_none_or(|s| s.kind() != SyntaxKind::Comma) {
1089 ctx.diag.push_error("gradient header must be followed by a comma".into(), &node);
1090 return Expression::Invalid;
1091 }
1092 let stops_start = if idx > 0 { idx + 1 } else { 0 };
1093 (GradKind::Conic { from_angle, center }, stops_start)
1094 } else {
1095 panic!("Not a gradient {grad_text:?}");
1097 };
1098
1099 let mut stops = Vec::new();
1100 enum Stop {
1101 Empty,
1102 Color(Expression),
1103 Finished,
1104 }
1105 let mut current_stop = Stop::Empty;
1106 for n in all_subs.iter().skip(stops_start_idx) {
1107 if n.kind() == SyntaxKind::Comma {
1108 match std::mem::replace(&mut current_stop, Stop::Empty) {
1109 Stop::Empty => {
1110 ctx.diag.push_error("Expected expression".into(), n);
1111 break;
1112 }
1113 Stop::Finished => {}
1114 Stop::Color(col) => stops.push((
1115 col,
1116 if stops.is_empty() {
1117 Expression::NumberLiteral(0., Unit::None)
1118 } else {
1119 Expression::Invalid
1120 },
1121 )),
1122 }
1123 } else {
1124 let e = ctx.with_expected_type(Type::Color, |ctx| {
1126 Expression::from_expression_node(n.as_node().unwrap().clone().into(), ctx)
1127 });
1128 match std::mem::replace(&mut current_stop, Stop::Finished) {
1129 Stop::Empty => {
1130 current_stop = Stop::Color(e.maybe_convert_to(
1131 Type::Color,
1132 n,
1133 ctx.diag,
1134 &ctx.symbol_counters,
1135 ))
1136 }
1137 Stop::Finished => {
1138 ctx.diag.push_error("Expected comma".into(), n);
1139 break;
1140 }
1141 Stop::Color(col) => {
1142 let stop_type = match &grad_kind {
1143 GradKind::Conic { .. } => Type::Angle,
1144 _ => Type::Float32,
1145 };
1146 stops.push((
1147 col,
1148 e.maybe_convert_to(stop_type, n, ctx.diag, &ctx.symbol_counters),
1149 ))
1150 }
1151 }
1152 }
1153 }
1154 match current_stop {
1155 Stop::Color(col) => stops.push((col, Expression::NumberLiteral(1., Unit::None))),
1156 Stop::Empty => {
1157 if let Some((_, e @ Expression::Invalid)) = stops.last_mut() {
1158 *e = Expression::NumberLiteral(1., Unit::None)
1159 }
1160 }
1161 Stop::Finished => (),
1162 };
1163
1164 let mut start = 0;
1166 while start < stops.len() {
1167 start += match stops[start..].iter().position(|s| matches!(s.1, Expression::Invalid)) {
1168 Some(p) => p,
1169 None => break,
1170 };
1171 let (before, rest) = stops.split_at_mut(start);
1172 let pos =
1173 rest.iter().position(|s| !matches!(s.1, Expression::Invalid)).unwrap_or(rest.len());
1174 if pos > 0 && pos < rest.len() {
1175 let (middle, after) = rest.split_at_mut(pos);
1176 let begin = before
1177 .last()
1178 .map(|s| &s.1)
1179 .unwrap_or(&Expression::NumberLiteral(1., Unit::None));
1180 let end = &after.first().expect("The last should never be invalid").1;
1181 for (i, (_, e)) in middle.iter_mut().enumerate() {
1182 debug_assert!(matches!(e, Expression::Invalid));
1183 *e = Expression::BinaryExpression {
1185 lhs: Box::new(begin.clone()),
1186 rhs: Box::new(Expression::BinaryExpression {
1187 source_location: None,
1188 lhs: Box::new(Expression::BinaryExpression {
1189 source_location: None,
1190 lhs: Box::new(Expression::NumberLiteral(i as f64 + 1., Unit::None)),
1191 rhs: Box::new(Expression::BinaryExpression {
1192 source_location: None,
1193 lhs: Box::new(end.clone()),
1194 rhs: Box::new(begin.clone()),
1195 op: '-',
1196 }),
1197 op: '*',
1198 }),
1199 rhs: Box::new(Expression::NumberLiteral(pos as f64 + 1., Unit::None)),
1200 op: '/',
1201 }),
1202 op: '+',
1203 source_location: None,
1204 };
1205 }
1206 }
1207 start += pos + 1;
1208 }
1209
1210 match grad_kind {
1211 GradKind::Linear { angle } => Expression::LinearGradient { angle, stops },
1212 GradKind::Radial { center, radius } => {
1213 Expression::RadialGradient { center, radius, stops }
1214 }
1215 GradKind::Conic { from_angle, center } => {
1216 let normalized_stops = stops
1218 .into_iter()
1219 .map(|(color, angle_expr)| {
1220 let angle_typed = angle_expr.maybe_convert_to(
1221 Type::Angle,
1222 &node,
1223 ctx.diag,
1224 &ctx.symbol_counters,
1225 );
1226 let normalized_pos = Expression::BinaryExpression {
1227 lhs: Box::new(angle_typed),
1228 rhs: Box::new(Expression::NumberLiteral(360., Unit::Deg)),
1229 op: '/',
1230 source_location: None,
1231 };
1232 (color, normalized_pos)
1233 })
1234 .collect();
1235
1236 let from_angle_degrees =
1238 from_angle.maybe_convert_to(Type::Angle, &node, ctx.diag, &ctx.symbol_counters);
1239
1240 Expression::ConicGradient {
1241 from_angle: Box::new(from_angle_degrees),
1242 center,
1243 stops: normalized_stops,
1244 }
1245 }
1246 }
1247 }
1248
1249 fn from_at_markdown(node: syntax_nodes::AtMarkdown, ctx: &mut LookupCtx) -> Expression {
1250 let mut raw_exprs: Vec<(Expression, crate::parser::SyntaxNode)> = Vec::new();
1251 let mut source_map = crate::literals::StringLiteralSourceMap::new();
1252 use i_slint_common::styled_text::MARKDOWN_INTERPOLATION_PLACEHOLDER as PLACEHOLDER;
1253
1254 let push_and_check =
1255 |token: &crate::parser::SyntaxToken,
1256 source_map: &mut crate::literals::StringLiteralSourceMap,
1257 diag: &mut crate::diagnostics::BuildDiagnostics| {
1258 let before = source_map.as_str().len();
1259 source_map.push(token, diag);
1260 for (offset, _) in source_map.as_str()[before..].match_indices(PLACEHOLDER) {
1261 source_map.report(
1262 diag,
1263 "\\u{e541} is reserved for @markdown interpolation".into(),
1264 (before + offset)..(before + offset + PLACEHOLDER.len_utf8()),
1265 &node,
1266 );
1267 }
1268 };
1269
1270 for n in node.children_with_tokens() {
1271 if n.kind() == SyntaxKind::StringLiteral {
1272 push_and_check(n.as_token().unwrap(), &mut source_map, ctx.diag);
1273 } else if n.kind() == SyntaxKind::StringTemplate {
1274 for n in n.as_node().unwrap().children_with_tokens() {
1275 if n.kind() == SyntaxKind::StringLiteral {
1276 push_and_check(n.as_token().unwrap(), &mut source_map, ctx.diag);
1277 } else if n.kind() == SyntaxKind::Expression {
1278 let expr_node = n.into_node().unwrap();
1279 let expr = Expression::from_expression_node(expr_node.clone().into(), ctx);
1280 source_map.push_raw_char(PLACEHOLDER, expr_node.to_source_location());
1281 raw_exprs.push((expr, expr_node));
1282 }
1283 }
1284 }
1285 }
1286
1287 let markdown = source_map.as_str();
1288 let placeholder_positions: Vec<usize> =
1289 markdown.match_indices(PLACEHOLDER).map(|(pos, _)| pos).collect();
1290
1291 const PROBE: &str = "zzz";
1296 const _: () = assert!(PROBE.len() == PLACEHOLDER.len_utf8());
1297 let probe = markdown.replace(PLACEHOLDER, PROBE);
1298
1299 let (_, parse_errors) = i_slint_common::styled_text::parse_interpolated::<
1300 &[i_slint_common::styled_text::StyledTextParagraph],
1301 >(&probe, &[]);
1302
1303 let mut color_indices = std::collections::BTreeSet::new();
1304
1305 for e in &parse_errors {
1306 let placeholders_in_range = |r: &core::ops::Range<usize>| -> Vec<usize> {
1307 placeholder_positions
1308 .iter()
1309 .enumerate()
1310 .filter(|(_, pos)| **pos >= r.start && **pos < r.end)
1311 .map(|(idx, _)| idx)
1312 .collect()
1313 };
1314
1315 if let Some(r) = e.range() {
1316 let hits = placeholders_in_range(&r);
1317
1318 if i_slint_common::styled_text::invalid_color_value(e) == Some(PROBE)
1321 && !hits.is_empty()
1322 {
1323 color_indices.extend(hits);
1324 continue;
1325 }
1326
1327 if !hits.is_empty() {
1330 source_map.report(
1331 ctx.diag,
1332 "Interpolation (`\\{}`) is not allowed inside HTML tags".into(),
1333 r,
1334 &node,
1335 );
1336 } else {
1337 source_map.report(ctx.diag, e.to_string(), r, &node);
1338 }
1339 } else {
1340 ctx.diag.push_error(e.to_string(), &node);
1341 }
1342 }
1343
1344 let values = raw_exprs
1345 .into_iter()
1346 .enumerate()
1347 .map(|(idx, (expr, expr_node))| {
1348 if color_indices.contains(&idx) {
1349 Expression::FunctionCall {
1351 function: BuiltinFunction::ColorToStyledText.into(),
1352 arguments: vec![expr.maybe_convert_to(
1353 Type::Color,
1354 &expr_node,
1355 ctx.diag,
1356 &ctx.symbol_counters,
1357 )],
1358 source_location: Some(expr_node.to_source_location()),
1359 }
1360 } else if expr.ty() == Type::StyledText {
1361 expr
1362 } else {
1363 Expression::FunctionCall {
1364 function: BuiltinFunction::StringToStyledText.into(),
1365 arguments: vec![expr.maybe_convert_to(
1366 Type::String,
1367 &expr_node,
1368 ctx.diag,
1369 &ctx.symbol_counters,
1370 )],
1371 source_location: Some(expr_node.to_source_location()),
1372 }
1373 }
1374 })
1375 .collect();
1376
1377 Expression::FunctionCall {
1378 function: BuiltinFunction::ParseMarkdown.into(),
1379 arguments: vec![
1380 Expression::StringLiteral(source_map.into_string().into()),
1381 Expression::Array { element_ty: Type::StyledText, values },
1382 ],
1383 source_location: Some(node.to_source_location()),
1384 }
1385 }
1386
1387 fn from_at_tr(node: syntax_nodes::AtTr, ctx: &mut LookupCtx) -> Expression {
1388 let mut source_map = crate::literals::StringLiteralSourceMap::new();
1389 let Some(string_token) = node.child_token(SyntaxKind::StringLiteral) else {
1390 ctx.diag.push_error("Cannot parse string literal".into(), &node);
1391 return Expression::Invalid;
1392 };
1393 if !source_map.push(&string_token, ctx.diag) {
1394 return Expression::Invalid;
1395 }
1396 let string: SmolStr = source_map.as_str().into();
1397 let context = node.TrContext().map(|n| {
1398 crate::literals::unescape_string_reporting(
1399 n.child_token(SyntaxKind::StringLiteral).as_ref(),
1400 ctx.diag,
1401 &n,
1402 )
1403 .unwrap_or_default()
1404 });
1405 let plural = node.TrPlural().map(|pl| {
1406 let s = crate::literals::unescape_string_reporting(
1407 pl.child_token(SyntaxKind::StringLiteral).as_ref(),
1408 ctx.diag,
1409 &pl,
1410 )
1411 .unwrap_or_default();
1412 let n = pl.Expression();
1413 let expr = Expression::from_expression_node(n.clone(), ctx).maybe_convert_to(
1414 Type::Int32,
1415 &n,
1416 ctx.diag,
1417 &ctx.symbol_counters,
1418 );
1419 (s, expr)
1420 });
1421
1422 let domain = ctx
1423 .type_loader
1424 .and_then(|tl| tl.compiler_config.translation_domain.clone())
1425 .unwrap_or_default();
1426
1427 let subs = node.Expression().map(|n| {
1428 Expression::from_expression_node(n.clone(), ctx).maybe_convert_to(
1429 Type::String,
1430 &n,
1431 ctx.diag,
1432 &ctx.symbol_counters,
1433 )
1434 });
1435 let values = subs.collect::<Vec<_>>();
1436
1437 {
1439 let mut arg_idx = 0;
1440 let mut pos_max = 0;
1441 let mut pos = 0;
1442 let mut has_n = false;
1443 while let Some(mut p) = string[pos..].find(['{', '}']) {
1444 if string.len() - pos < p + 1 {
1445 p += pos;
1446 source_map.report(
1447 ctx.diag,
1448 "Unescaped trailing '{' in format string. Escape '{' with '{{'".into(),
1449 p..p + 1,
1450 &node,
1451 );
1452 break;
1453 }
1454 p += pos;
1455
1456 if string.get(p..=p) == Some("}") {
1458 if string.get(p + 1..=p + 1) == Some("}") {
1459 pos = p + 2;
1460 continue;
1461 } else {
1462 source_map.report(
1463 ctx.diag,
1464 "Unescaped '}' in format string. Escape '}' with '}}'".into(),
1465 p..p + 1,
1466 &node,
1467 );
1468 break;
1469 }
1470 }
1471
1472 if string.get(p + 1..=p + 1) == Some("{") {
1474 pos = p + 2;
1475 continue;
1476 }
1477
1478 let end = if let Some(end) = string[p..].find('}') {
1480 end + p
1481 } else {
1482 source_map.report(
1483 ctx.diag,
1484 "Unterminated placeholder in format string. '{' must be escaped with '{{'"
1485 .into(),
1486 p..string.len(),
1487 &node,
1488 );
1489 break;
1490 };
1491 let argument = &string[p + 1..end];
1492 if argument.is_empty() {
1493 arg_idx += 1;
1494 } else if let Ok(n) = argument.parse::<u16>() {
1495 pos_max = pos_max.max(n as usize + 1);
1496 } else if argument == "n" {
1497 has_n = true;
1498 if plural.is_none() {
1499 source_map.report(
1500 ctx.diag,
1501 "`{n}` placeholder can only be found in plural form".into(),
1502 p..end + 1,
1503 &node,
1504 );
1505 }
1506 } else {
1507 source_map.report(
1508 ctx.diag,
1509 "Invalid '{...}' placeholder in format string. The placeholder must be a number, or braces must be escaped with '{{' and '}}'".into(),
1510 p..end + 1,
1511 &node,
1512 );
1513 break;
1514 };
1515 pos = end + 1;
1516 }
1517 if arg_idx > 0 && pos_max > 0 {
1518 ctx.diag.push_error(
1519 "Cannot mix positional and non-positional placeholder in format string".into(),
1520 &node,
1521 );
1522 } else if arg_idx > values.len() || pos_max > values.len() {
1523 let num = arg_idx.max(pos_max);
1524 let note = if !has_n && plural.is_some() {
1525 ". Note: use `{n}` for the argument after '%'"
1526 } else {
1527 ""
1528 };
1529 ctx.diag.push_error(
1530 format!("Format string contains {num} placeholders, but only {} extra arguments were given{note}", values.len()),
1531 &node,
1532 );
1533 }
1534 }
1535
1536 let plural =
1537 plural.unwrap_or((SmolStr::default(), Expression::NumberLiteral(1., Unit::None)));
1538
1539 let context = context.or_else(|| {
1540 if !ctx.type_loader.is_some_and(|tl| {
1541 tl.compiler_config.default_translation_context
1542 == crate::DefaultTranslationContext::None
1543 }) {
1544 ctx.component_scope
1546 .first()
1547 .and_then(|e| e.borrow().enclosing_component.upgrade())
1548 .map(|c| c.id.clone())
1549 } else {
1550 None
1551 }
1552 });
1553
1554 Expression::FunctionCall {
1555 function: BuiltinFunction::Translate.into(),
1556 arguments: vec![
1557 Expression::StringLiteral(string),
1558 Expression::StringLiteral(context.unwrap_or_default()),
1559 Expression::StringLiteral(domain.into()),
1560 Expression::Array { element_ty: Type::String, values },
1561 plural.1,
1562 Expression::StringLiteral(plural.0),
1563 ],
1564 source_location: Some(node.to_source_location()),
1565 }
1566 }
1567
1568 pub fn from_at_keys_node(node: syntax_nodes::AtKeys, ctx: &mut LookupCtx) -> Self {
1569 let mut keys = langtype::Keys::default();
1570
1571 let mut key_code: Option<(SmolStr, ShiftBehavior, NodeOrToken)> = None;
1572
1573 let idents_and_questions: Vec<_> = node
1574 .children_with_tokens()
1575 .filter(|n| matches!(n.kind(), SyntaxKind::Identifier | SyntaxKind::Question))
1576 .skip(1)
1578 .collect();
1579
1580 for (index, ident_or_question) in idents_and_questions.iter().enumerate() {
1581 if ident_or_question.kind() == SyntaxKind::Question {
1582 continue;
1583 }
1584 let identifier = ident_or_question;
1585
1586 let is_question = || -> bool {
1587 matches!(
1588 idents_and_questions.get(index + 1).map(NodeOrToken::kind),
1589 Some(SyntaxKind::Question)
1590 )
1591 };
1592
1593 match identifier.as_token().unwrap().text() {
1594 "Alt" => {
1595 if is_question() {
1596 keys.ignore_alt = true;
1597 } else {
1598 keys.modifiers.alt = true;
1599 }
1600 }
1601 "Control" => keys.modifiers.control = true,
1602 "Meta" => keys.modifiers.meta = true,
1603 "Shift" => {
1604 if is_question() {
1605 keys.ignore_shift = true;
1606 } else {
1607 keys.modifiers.shift = true;
1608 }
1609 }
1610 key_name => {
1611 if let Some((key, shiftbehavior)) = lookup_key_name(key_name) {
1612 key_code = Some((
1613 SmolStr::from_iter(core::iter::once(key)),
1614 shiftbehavior,
1615 identifier.clone(),
1616 ))
1617 } else {
1618 let uppercased = key_name.to_uppercase();
1620 let hint = if lookup_key_name(&uppercased).is_some() {
1621 format!("Use uppercase {uppercased} instead")
1623 } else {
1624 format!("Consider using \"{key_name}\"")
1625 };
1626 ctx.diag.push_error(
1627 format!("{key_name} not defined in the Keys namespace\n({hint})"),
1628 identifier,
1629 );
1630 keys.modifiers = KeyboardModifiers::default();
1631 break;
1632 }
1633 }
1634 }
1635 }
1636
1637 if let Some((key_code, shift_behavior, node)) = key_code {
1640 match shift_behavior {
1641 ShiftBehavior::LocalizedShiftable { shifted_hint } => {
1642 if keys.ignore_shift {
1643 ctx.diag.push_warning(
1644 format!(
1645 "{name} already implies Shift? (remove Shift?)",
1646 name = node.as_token().unwrap().text()
1647 ),
1648 &node,
1649 );
1650 }
1651 keys.ignore_shift = true;
1652 if keys.modifiers.shift {
1653 let shifted_hint = lookup_key_name(shifted_hint).map(|(shifted_code, _shift_behavior)|
1654 format!("\nConsider using {shifted_hint} to match when the user types '{shifted_code}'")
1655 ).unwrap_or_default();
1656
1657 ctx.diag.push_error(
1658 format!(
1659 "{name} implies Shift? to support different keyboard layouts\n\
1660 Remove Shift to match when the user types '{key_code}'{shifted_hint}",
1661 name = node.as_token().unwrap().text()
1662 ),
1663 &node,
1664 );
1665 }
1666 }
1667 ShiftBehavior::Unshiftable => {}
1670 }
1671 keys.key = key_code;
1672 }
1673
1674 if let Some(token) = node.child_token(SyntaxKind::StringLiteral)
1676 && let Some(key) =
1677 crate::literals::unescape_string_reporting(Some(&token), ctx.diag, &token)
1678 {
1679 let normalizer = icu_normalizer::ComposingNormalizer::new_nfc();
1681 let key: SmolStr = normalizer.normalize(&key).into();
1682
1683 let grapheme_count = key.graphemes(true).count();
1685 if grapheme_count == 0 {
1686 ctx.diag.push_error("Key string literal must not be empty".to_string(), &token);
1687 } else if grapheme_count > 1 {
1688 ctx.diag.push_error(
1689 format!(
1690 "Key string literal must contain exactly one grapheme cluster, found {grapheme_count}",
1691 ),
1692 &token,
1693 );
1694 }
1695
1696 keys.key = key;
1697
1698 let lowercase: SmolStr = keys.key.to_lowercase().into();
1699 if lowercase != keys.key {
1700 ctx.diag.push_error(
1701 format!(
1702 "Key string literals must currently be lowercase, use \"{lowercase}\" instead",
1703 ),
1704 &token,
1705 );
1706 }
1707 }
1708
1709 Expression::Keys(keys)
1710 }
1711
1712 fn from_qualified_name_node(node: syntax_nodes::QualifiedName, ctx: &mut LookupCtx) -> Self {
1714 Self::from_lookup_result(
1715 lookup_qualified_name_node(node.clone(), ctx, LookupPhase::default()),
1716 ctx,
1717 &node,
1718 )
1719 }
1720
1721 fn from_lookup_result(
1722 r: Option<LookupResult>,
1723 ctx: &mut LookupCtx,
1724 node: &dyn Spanned,
1725 ) -> Self {
1726 let Some(r) = r else {
1727 assert!(ctx.diag.has_errors());
1728 return Self::Invalid;
1729 };
1730 match r {
1731 LookupResult::Expression { expression, .. } => expression,
1732 LookupResult::Callable(LookupResultCallable::Macro(BuiltinMacroFunction::Spring)) => {
1734 Expression::EasingCurve(crate::expression_tree::EasingCurve::Spring(0.))
1735 }
1736 LookupResult::Callable(c) => {
1737 let what = match c {
1738 LookupResultCallable::Callable(Callable::Callback(..)) => "Callback",
1739 LookupResultCallable::Callable(Callable::Builtin(..)) => "Builtin function",
1740 LookupResultCallable::Macro(..) => "Builtin function",
1741 LookupResultCallable::MemberFunction { .. } => "Member function",
1742 _ => "Function",
1743 };
1744 ctx.diag
1745 .push_error(format!("{what} must be called. Did you forgot the '()'?",), node);
1746 Self::Invalid
1747 }
1748 LookupResult::Enumeration(..) => {
1749 ctx.diag.push_error("Cannot take reference to an enum".to_string(), node);
1750 Self::Invalid
1751 }
1752 LookupResult::Namespace(..) => {
1753 ctx.diag.push_error("Cannot take reference to a namespace".to_string(), node);
1754 Self::Invalid
1755 }
1756 }
1757 }
1758
1759 fn from_function_call_node(
1760 node: syntax_nodes::FunctionCallExpression,
1761 ctx: &mut LookupCtx,
1762 ) -> Expression {
1763 let mut arguments = Vec::new();
1764
1765 let mut sub_expr = node.Expression();
1766
1767 let func_expr = sub_expr.next().unwrap();
1768 let args_range = TextRange::new(func_expr.text_range().end(), node.text_range().end());
1770
1771 let (function, source_location) = if let Some(qn) = func_expr.QualifiedName() {
1772 let sl = qn.last_token().unwrap().to_source_location();
1773 (lookup_qualified_name_node(qn, ctx, LookupPhase::default()), sl)
1774 } else if let Some(ma) = func_expr.MemberAccess() {
1775 let base = Self::from_expression_node(ma.Expression(), ctx);
1776 let field = ma.child_token(SyntaxKind::Identifier);
1777 let sl = field.to_source_location();
1778 (maybe_lookup_object(base.into(), field.clone().into_iter(), ctx), sl)
1779 } else {
1780 if Self::from_expression_node(func_expr, ctx).ty() == Type::Invalid {
1781 assert!(ctx.diag.has_errors());
1782 } else {
1783 ctx.diag.push_error("The expression is not a function".into(), &node);
1784 }
1785 return Self::Invalid;
1786 };
1787 let expected_closure_arg_type = match &function {
1792 Some(LookupResult::Callable(LookupResultCallable::MemberFunction {
1793 base,
1794 member,
1795 ..
1796 })) if matches!(
1797 **member,
1798 LookupResultCallable::Callable(Callable::Builtin(
1799 BuiltinFunction::ArrayAny
1800 | BuiltinFunction::ArrayAll
1801 | BuiltinFunction::ArrayFindIndex
1802 ))
1803 ) =>
1804 {
1805 let Type::Array(elem_ty) = base.ty() else { unreachable!() };
1806 Some((*elem_ty).clone())
1807 }
1808 _ => None,
1809 };
1810
1811 let arg_nodes = sub_expr.collect::<Vec<_>>();
1814 let convert_args = |ctx: &mut LookupCtx, expected: &[Type]| {
1815 if let Some(offset) = ctx.expected_type_probe_offset() {
1817 let idx = arg_nodes.iter().take_while(|n| n.text_range().end() <= offset).count();
1818 if let Some(ty) = expected.get(idx).cloned() {
1819 ctx.record_expected_type_probe(args_range, &ty);
1820 }
1821 }
1822 arg_nodes
1823 .iter()
1824 .enumerate()
1825 .map(|(i, n)| {
1826 let ty = expected.get(i).cloned().unwrap_or(Type::Invalid);
1827 let expression = ctx.with_expected_type(ty, |ctx| {
1828 Self::from_argument_expression_node(
1829 (*n).clone(),
1830 ctx,
1831 &expected_closure_arg_type,
1832 )
1833 });
1834 (expression, Some(NodeOrToken::from((**n).clone())))
1835 })
1836 .collect::<Vec<_>>()
1837 };
1838
1839 let Some(function) = function else {
1840 convert_args(ctx, &[]);
1842 assert!(ctx.diag.has_errors());
1843 return Self::Invalid;
1844 };
1845 let LookupResult::Callable(function) = function else {
1846 convert_args(ctx, &[]);
1848 ctx.diag.push_error("The expression is not a function".into(), &node);
1849 return Self::Invalid;
1850 };
1851
1852 let mut adjust_arg_count = 0;
1853 let function = match function {
1854 LookupResultCallable::Callable(c) => c,
1855 LookupResultCallable::Macro(mac) => {
1856 arguments.extend(convert_args(ctx, &[]));
1857 return crate::builtin_macros::lower_macro(
1858 mac,
1859 &source_location,
1860 arguments.into_iter(),
1861 ctx.diag,
1862 &ctx.symbol_counters,
1863 );
1864 }
1865 LookupResultCallable::MemberFunction { member, base, source_node } => {
1866 arguments.push((base, source_node));
1867 adjust_arg_count = 1;
1868 match *member {
1869 LookupResultCallable::Callable(c) => c,
1870 LookupResultCallable::Macro(mac) => {
1871 arguments.extend(convert_args(ctx, &[]));
1872 return crate::builtin_macros::lower_macro(
1873 mac,
1874 &source_location,
1875 arguments.into_iter(),
1876 ctx.diag,
1877 &ctx.symbol_counters,
1878 );
1879 }
1880 LookupResultCallable::MemberFunction { .. } => {
1881 unreachable!()
1882 }
1883 }
1884 }
1885 };
1886
1887 match function.ty() {
1888 Type::Function(f) | Type::Callback(f) => {
1889 arguments.extend(convert_args(ctx, f.args.get(adjust_arg_count..).unwrap_or(&[])));
1890 }
1891 _ => arguments.extend(convert_args(ctx, &[])),
1892 }
1893
1894 if matches!(&function, Callable::Callback(nr) if nr.name() == "init") {
1895 ctx.diag.push_warning(
1896 "Calling 'init' explicitly does nothing and is deprecated".into(),
1897 &node,
1898 );
1899 }
1900
1901 let arguments = match function.ty() {
1902 Type::Function(function) | Type::Callback(function) => {
1903 if arguments.len() != function.args.len() {
1904 ctx.diag.push_error(
1905 format!(
1906 "The callback or function expects {} arguments, but {} are provided",
1907 function.args.len() - adjust_arg_count,
1908 arguments.len() - adjust_arg_count,
1909 ),
1910 &node,
1911 );
1912 arguments.into_iter().map(|x| x.0).collect()
1913 } else {
1914 arguments
1915 .into_iter()
1916 .zip(function.args.iter())
1917 .map(|((e, node), ty)| {
1918 e.maybe_convert_to(ty.clone(), &node, ctx.diag, &ctx.symbol_counters)
1919 })
1920 .collect()
1921 }
1922 }
1923 Type::Invalid => {
1924 debug_assert!(ctx.diag.has_errors(), "The error must already have been reported.");
1925 arguments.into_iter().map(|x| x.0).collect()
1926 }
1927 _ => {
1928 ctx.diag.push_error("The expression is not a function".into(), &node);
1929 arguments.into_iter().map(|x| x.0).collect()
1930 }
1931 };
1932
1933 Expression::FunctionCall { function, arguments, source_location: Some(source_location) }
1934 }
1935
1936 fn from_member_access_node(
1937 node: syntax_nodes::MemberAccess,
1938 ctx: &mut LookupCtx,
1939 ) -> Expression {
1940 let base = Self::from_expression_node(node.Expression(), ctx);
1941 let field = node.child_token(SyntaxKind::Identifier);
1942 Self::from_lookup_result(
1943 maybe_lookup_object(base.into(), field.clone().into_iter(), ctx),
1944 ctx,
1945 &field,
1946 )
1947 }
1948
1949 fn from_self_assignment_node(
1950 node: syntax_nodes::SelfAssignment,
1951 ctx: &mut LookupCtx,
1952 ) -> Expression {
1953 let (lhs_n, rhs_n) = node.Expression();
1954 let mut lhs = Self::from_expression_node(lhs_n.clone(), ctx);
1955 let op = node
1956 .children_with_tokens()
1957 .find_map(|n| match n.kind() {
1958 SyntaxKind::PlusEqual => Some('+'),
1959 SyntaxKind::MinusEqual => Some('-'),
1960 SyntaxKind::StarEqual => Some('*'),
1961 SyntaxKind::DivEqual => Some('/'),
1962 SyntaxKind::Equal => Some('='),
1963 _ => None,
1964 })
1965 .unwrap_or('_');
1966 if lhs.ty() != Type::Invalid {
1967 lhs.try_set_rw(ctx, if op == '=' { "Assignment" } else { "Self assignment" }, &node);
1968 }
1969 let ty = lhs.ty();
1970 let expected_ty = match op {
1971 '=' => ty,
1972 '+' if ty == Type::String || ty.as_unit_product().is_some() => ty,
1973 '-' if ty.as_unit_product().is_some() => ty,
1974 '/' | '*' if ty.as_unit_product().is_some() => Type::Float32,
1975 _ => {
1976 if ty != Type::Invalid {
1977 ctx.diag.push_error(
1978 format!("the {op}= operation cannot be done on a {ty}"),
1979 &lhs_n,
1980 );
1981 }
1982 Type::Invalid
1983 }
1984 };
1985 let rhs = ctx.with_expected_type(expected_ty.clone(), |ctx| {
1986 Self::from_expression_node(rhs_n.clone(), ctx)
1987 });
1988 Expression::SelfAssignment {
1989 lhs: Box::new(lhs),
1990 rhs: Box::new(rhs.maybe_convert_to(
1991 expected_ty,
1992 &rhs_n,
1993 ctx.diag,
1994 &ctx.symbol_counters,
1995 )),
1996 op,
1997 node: Some(NodeOrToken::Node(node.into())),
1998 }
1999 }
2000
2001 fn from_binary_expression_node(
2002 node: syntax_nodes::BinaryExpression,
2003 ctx: &mut LookupCtx,
2004 ) -> Expression {
2005 let (op, operator) = node
2006 .children_with_tokens()
2007 .find_map(|n| {
2008 let op = match n.kind() {
2009 SyntaxKind::Plus => '+',
2010 SyntaxKind::Minus => '-',
2011 SyntaxKind::Star => '*',
2012 SyntaxKind::Div => '/',
2013 SyntaxKind::LessEqual => '≤',
2014 SyntaxKind::GreaterEqual => '≥',
2015 SyntaxKind::LAngle => '<',
2016 SyntaxKind::RAngle => '>',
2017 SyntaxKind::EqualEqual => '=',
2018 SyntaxKind::NotEqual => '!',
2019 SyntaxKind::AndAnd => '&',
2020 SyntaxKind::OrOr => '|',
2021 _ => return None,
2022 };
2023 Some((op, Some(n.to_source_location())))
2024 })
2025 .unwrap_or(('_', None));
2026
2027 #[cfg(feature = "slint-sc")]
2032 if op == '/' {
2033 ctx.diag.slint_sc_error("Operator '/'", &node);
2034 }
2035
2036 let op_class = operator_class(op);
2037 let (lhs_n, rhs_n) = node.Expression();
2038 let lhs = if op_class == OperatorClass::LogicalOp {
2041 ctx.with_expected_type(Type::Bool, |ctx| Self::from_expression_node(lhs_n.clone(), ctx))
2042 } else {
2043 Self::from_expression_node(lhs_n.clone(), ctx)
2044 };
2045 let rhs = match op_class {
2046 OperatorClass::ComparisonOp => ctx
2047 .with_expected_type(lhs.ty(), |ctx| Self::from_expression_node(rhs_n.clone(), ctx)),
2048 OperatorClass::LogicalOp => ctx.with_expected_type(Type::Bool, |ctx| {
2049 Self::from_expression_node(rhs_n.clone(), ctx)
2050 }),
2051 OperatorClass::ArithmeticOp => Self::from_expression_node(rhs_n.clone(), ctx),
2052 };
2053
2054 let (lhs_target, rhs_target) = match op_class {
2061 OperatorClass::ComparisonOp => {
2062 let ty =
2063 Self::common_target_type_for_type_list([lhs.ty(), rhs.ty()].iter().cloned());
2064 if !matches!(op, '=' | '!') && ty.as_unit_product().is_none() && ty != Type::String
2065 {
2066 ctx.diag.push_error(format!("Values of type {ty} cannot be compared"), &node);
2067 }
2068 (Some(ty.clone()), Some(ty))
2069 }
2070 OperatorClass::LogicalOp => (Some(Type::Bool), Some(Type::Bool)),
2071 OperatorClass::ArithmeticOp => {
2072 let (lhs_ty, rhs_ty) = (lhs.ty(), rhs.ty());
2073 if op == '*' || op == '/' {
2074 let has_unit = |ty: &Type| {
2075 matches!(ty, Type::UnitProduct(_)) || ty.default_unit().is_some()
2076 };
2077 match (has_unit(&lhs_ty), has_unit(&rhs_ty)) {
2078 (true, true) => (None, None),
2079 (true, false) => (None, Some(Type::Float32)),
2080 (false, true) => (Some(Type::Float32), None),
2081 (false, false) => (Some(Type::Float32), Some(Type::Float32)),
2082 }
2083 } else if op == '+' || op == '-' {
2084 let expected_ty =
2085 if op == '+' && (lhs_ty == Type::String || rhs_ty == Type::String) {
2086 Type::String
2087 } else if lhs_ty.default_unit().is_some() {
2088 lhs_ty
2089 } else if rhs_ty.default_unit().is_some() {
2090 rhs_ty
2091 } else if matches!(lhs_ty, Type::UnitProduct(_)) {
2092 lhs_ty
2093 } else if matches!(rhs_ty, Type::UnitProduct(_)) {
2094 rhs_ty
2095 } else {
2096 Type::Float32
2097 };
2098 (Some(expected_ty.clone()), Some(expected_ty))
2099 } else {
2100 unreachable!()
2101 }
2102 }
2103 };
2104 let lhs = match lhs_target {
2105 Some(ty) => lhs.maybe_convert_to(ty, &lhs_n, ctx.diag, &ctx.symbol_counters),
2106 None => lhs,
2107 };
2108 let rhs = match rhs_target {
2109 Some(ty) => rhs.maybe_convert_to(ty, &rhs_n, ctx.diag, &ctx.symbol_counters),
2110 None => rhs,
2111 };
2112 Expression::BinaryExpression {
2113 lhs: Box::new(lhs),
2114 rhs: Box::new(rhs),
2115 op,
2116 source_location: operator,
2117 }
2118 }
2119
2120 fn from_unaryop_expression_node(
2121 node: syntax_nodes::UnaryOpExpression,
2122 ctx: &mut LookupCtx,
2123 ) -> Expression {
2124 let op = node
2125 .children_with_tokens()
2126 .find_map(|n| match n.kind() {
2127 SyntaxKind::Plus => Some('+'),
2128 SyntaxKind::Minus => Some('-'),
2129 SyntaxKind::Bang => Some('!'),
2130 _ => None,
2131 })
2132 .unwrap_or('_');
2133
2134 let exp_n = node.Expression();
2135 let exp = if op == '!' {
2136 ctx.with_expected_type(Type::Bool, |ctx| Self::from_expression_node(exp_n, ctx))
2137 } else {
2138 Self::from_expression_node(exp_n, ctx)
2139 };
2140
2141 let exp = match op {
2142 '!' => exp.maybe_convert_to(Type::Bool, &node, ctx.diag, &ctx.symbol_counters),
2143 '+' | '-' => {
2144 let ty = exp.ty();
2145 if ty.default_unit().is_none()
2146 && !matches!(
2147 ty,
2148 Type::Int32
2149 | Type::Float32
2150 | Type::Percent
2151 | Type::UnitProduct(..)
2152 | Type::Invalid
2153 )
2154 {
2155 ctx.diag.push_error(format!("Unary '{op}' not supported on {ty}"), &node);
2156 }
2157 exp
2158 }
2159 _ => {
2160 assert!(ctx.diag.has_errors());
2161 exp
2162 }
2163 };
2164
2165 Expression::UnaryOp { sub: Box::new(exp), op }
2166 }
2167
2168 fn from_conditional_expression_node(
2169 node: syntax_nodes::ConditionalExpression,
2170 ctx: &mut LookupCtx,
2171 ) -> Expression {
2172 let (condition_n, true_expr_n, false_expr_n) = node.Expression();
2173 let condition = ctx
2174 .with_expected_type(Type::Bool, |ctx| {
2175 Self::from_expression_node(condition_n.clone(), ctx)
2176 })
2177 .maybe_convert_to(Type::Bool, &condition_n, ctx.diag, &ctx.symbol_counters);
2178 let true_expr = Self::from_expression_node(true_expr_n.clone(), ctx);
2179 let false_expr = Self::from_expression_node(false_expr_n.clone(), ctx);
2180 let result_ty = common_expression_type(&true_expr, &false_expr);
2181 let true_expr = true_expr.maybe_convert_to(
2182 result_ty.clone(),
2183 &true_expr_n,
2184 ctx.diag,
2185 &ctx.symbol_counters,
2186 );
2187 let false_expr =
2188 false_expr.maybe_convert_to(result_ty, &false_expr_n, ctx.diag, &ctx.symbol_counters);
2189 Expression::Condition {
2190 condition: Box::new(condition),
2191 true_expr: Box::new(true_expr),
2192 false_expr: Box::new(false_expr),
2193 source_location: node
2194 .child_token(SyntaxKind::Question)
2195 .map(|t| ConditionLocation::Question(t.to_source_location())),
2196 }
2197 }
2198
2199 fn from_index_expression_node(
2200 node: syntax_nodes::IndexExpression,
2201 ctx: &mut LookupCtx,
2202 ) -> Expression {
2203 let (array_expr_n, index_expr_n) = node.Expression();
2204 let array_expr = Self::from_expression_node(array_expr_n, ctx);
2205 let index_expr = ctx
2206 .with_expected_type(Type::Int32, |ctx| {
2207 Self::from_expression_node(index_expr_n.clone(), ctx)
2208 })
2209 .maybe_convert_to(Type::Int32, &index_expr_n, ctx.diag, &ctx.symbol_counters);
2210
2211 let ty = array_expr.ty();
2212 if !matches!(ty, Type::Array(_) | Type::Invalid | Type::Function(_) | Type::Callback(_)) {
2213 ctx.diag.push_error(format!("{ty} is not an indexable type"), &node);
2214 }
2215 Expression::ArrayIndex { array: Box::new(array_expr), index: Box::new(index_expr) }
2216 }
2217
2218 fn from_object_literal_node(
2219 node: syntax_nodes::ObjectLiteral,
2220 ctx: &mut LookupCtx,
2221 ) -> Expression {
2222 let values: BTreeMap<SmolStr, Expression> = node
2223 .ObjectMember()
2224 .map(|n| {
2225 let name = identifier_text(&n).unwrap_or_default();
2226 let field_ty = match &ctx.expected_type {
2227 Type::Struct(s) => s.fields.get(&name).cloned().unwrap_or_default(),
2228 _ => Type::Invalid,
2229 };
2230 let value = ctx.with_expected_type(field_ty, |ctx| {
2231 Expression::from_expression_node(n.Expression(), ctx)
2232 });
2233 (name, value)
2234 })
2235 .collect();
2236 let ty = Arc::new(Struct::new(
2237 values.iter().map(|(k, v)| (k.clone(), v.ty())).collect(),
2238 StructName::None,
2239 ));
2240 Expression::Struct { ty, values }
2241 }
2242
2243 fn from_array_node(node: syntax_nodes::Array, ctx: &mut LookupCtx) -> Expression {
2244 let element_expected = match &ctx.expected_type {
2245 Type::Array(el) => (**el).clone(),
2246 _ => Type::Invalid,
2247 };
2248 ctx.record_expected_type_probe(node.text_range(), &element_expected);
2250 let mut values: Vec<Expression> = node
2251 .Expression()
2252 .map(|e| {
2253 ctx.with_expected_type(element_expected.clone(), |ctx| {
2254 Expression::from_expression_node(e, ctx)
2255 })
2256 })
2257 .collect();
2258
2259 let element_ty = if values.is_empty() {
2260 Type::Void
2261 } else {
2262 Self::common_target_type_for_type_list(values.iter().map(|expr| expr.ty()))
2263 };
2264
2265 for e in values.iter_mut() {
2266 *e = core::mem::replace(e, Expression::Invalid).maybe_convert_to(
2267 element_ty.clone(),
2268 &node,
2269 ctx.diag,
2270 &ctx.symbol_counters,
2271 );
2272 }
2273
2274 Expression::Array { element_ty, values }
2275 }
2276
2277 fn from_closure_node(
2284 node: syntax_nodes::Closure,
2285 ctx: &mut LookupCtx,
2286 arg_type: Option<Type>,
2287 ) -> Expression {
2288 if crate::reject_experimental_feature(ctx.diag, ctx.type_register, "closures", &node) {
2289 return Expression::Invalid;
2290 }
2291 let has_expected_arg_type = arg_type.is_some();
2292 let ty = arg_type.unwrap_or(Type::Invalid);
2293 let arg_name = node.DeclaredIdentifier().to_smolstr();
2294 let internal_arg_name: SmolStr = format!("local_{arg_name}").into();
2295
2296 ctx.local_variables.push(vec![(internal_arg_name.clone(), ty)]);
2297 let body_expected_type = if has_expected_arg_type { Type::Bool } else { Type::Invalid };
2298 let expression = ctx.with_expected_type(body_expected_type, |ctx| {
2299 Expression::from_expression_node(node.Expression(), ctx)
2300 });
2301 ctx.local_variables.pop();
2302
2303 let body_ty = expression.ty();
2304 if has_expected_arg_type && body_ty != Type::Bool && body_ty != Type::Invalid {
2305 ctx.diag.push_error(
2306 format!("Closure body must be of type bool, but is {body_ty}"),
2307 &node.Expression(),
2308 );
2309 return Expression::Invalid;
2310 }
2311
2312 Expression::Closure { arg_name: internal_arg_name, expression: Box::new(expression) }
2313 }
2314
2315 fn from_argument_expression_node(
2326 node: syntax_nodes::Expression,
2327 ctx: &mut LookupCtx,
2328 expected_closure_arg_type: &Option<Type>,
2329 ) -> Expression {
2330 if expected_closure_arg_type.is_some() {
2331 let mut current = node.clone();
2332 loop {
2333 let first_meaningful_child = current
2334 .children()
2335 .find(|n| matches!(n.kind(), SyntaxKind::Expression | SyntaxKind::Closure));
2336 match first_meaningful_child {
2337 Some(child) if child.kind() == SyntaxKind::Closure => {
2338 return Self::from_closure_node(
2339 child.into(),
2340 ctx,
2341 expected_closure_arg_type.clone(),
2342 );
2343 }
2344 Some(child) if child.kind() == SyntaxKind::Expression => {
2345 current = child.into();
2346 }
2347 _ => break,
2348 }
2349 }
2350 }
2351 let expression = Self::from_expression_node(node.clone(), ctx);
2352 if expected_closure_arg_type.is_some()
2353 && expression.ty() == Type::Closure
2354 && !matches!(expression, Expression::Closure { .. })
2355 {
2356 ctx.diag.push_error(
2357 "Closures must be written inline as the argument of 'any', 'all' or 'find-index'"
2358 .into(),
2359 &node,
2360 );
2361 return Expression::Invalid;
2362 }
2363 expression
2364 }
2365
2366 fn from_string_template_node(
2367 node: syntax_nodes::StringTemplate,
2368 ctx: &mut LookupCtx,
2369 ) -> Expression {
2370 let mut result = None;
2371 for n in node.children_with_tokens() {
2372 let expr = if n.kind() == SyntaxKind::StringLiteral {
2373 let token = n.as_token().unwrap();
2374 crate::literals::unescape_string_reporting(Some(token), ctx.diag, token)
2375 .map(Self::StringLiteral)
2376 .unwrap_or(Self::Invalid)
2377 } else if n.kind() == SyntaxKind::Expression {
2378 let node = n.into_node().unwrap();
2379 let expr = Expression::from_expression_node(node.clone().into(), ctx);
2380 expr.maybe_convert_to(Type::String, &node, ctx.diag, &ctx.symbol_counters)
2381 } else {
2382 continue;
2383 };
2384 result = match result {
2385 Some(result) => Some(Expression::BinaryExpression {
2386 lhs: Box::new(result),
2387 rhs: Box::new(expr),
2388 op: '+',
2389 source_location: None,
2390 }),
2391 None => Some(expr),
2392 }
2393 }
2394 result.unwrap_or_default()
2395 }
2396
2397 pub fn common_target_type_for_type_list(types: impl Iterator<Item = Type>) -> Type {
2401 types.fold(Type::Invalid, |target_type, expr_ty| {
2402 if target_type == expr_ty {
2403 target_type
2404 } else if target_type == Type::Invalid {
2405 expr_ty
2406 } else {
2407 match (target_type, expr_ty) {
2408 (Type::Struct(ref result), Type::Struct(ref elem)) => {
2409 let mut fields = result.fields.clone();
2410 for (elem_name, elem_ty) in elem.fields.iter() {
2411 match fields.entry(elem_name.clone()) {
2412 std::collections::btree_map::Entry::Vacant(free_entry) => {
2413 free_entry.insert(elem_ty.clone());
2414 }
2415 std::collections::btree_map::Entry::Occupied(
2416 mut existing_field,
2417 ) => {
2418 *existing_field.get_mut() =
2419 Self::common_target_type_for_type_list(
2420 [existing_field.get().clone(), elem_ty.clone()]
2421 .into_iter(),
2422 );
2423 }
2424 }
2425 }
2426 let source = if result.name.is_some() { &result } else { &elem };
2428 Type::Struct(Arc::new(Struct {
2429 fields,
2430 field_defaults: source.field_defaults.clone(),
2431 name: source.name.clone(),
2432 }))
2433 }
2434 (Type::Array(lhs), Type::Array(rhs)) => Type::Array(if *lhs == Type::Void {
2435 rhs
2436 } else if *rhs == Type::Void {
2437 lhs
2438 } else {
2439 Self::common_target_type_for_type_list(
2440 [(*lhs).clone(), (*rhs).clone()].into_iter(),
2441 )
2442 .into()
2443 }),
2444 (Type::Color, Type::Brush) | (Type::Brush, Type::Color) => Type::Brush,
2445 (Type::Float32, Type::Int32) | (Type::Int32, Type::Float32) => Type::Float32,
2446 (target_type, expr_ty) => {
2447 if expr_ty.can_convert(&target_type) {
2448 target_type
2449 } else if target_type.can_convert(&expr_ty)
2450 || (expr_ty.default_unit().is_some()
2451 && matches!(target_type, Type::Float32 | Type::Int32))
2452 {
2453 expr_ty
2455 } else {
2456 target_type
2458 }
2459 }
2460 }
2461 }
2462 })
2463 }
2464}
2465
2466use i_slint_common::key_codes::{ShiftBehavior, lookup_key_name};
2467
2468fn common_expression_type(true_expr: &Expression, false_expr: &Expression) -> Type {
2477 fn merge_struct(origin: &Struct, other: &Struct) -> Type {
2478 let mut fields = other.fields.clone();
2479 fields.extend(origin.fields.iter().map(|(k, v)| (k.clone(), v.clone())));
2480 Arc::new(Struct::new(fields, StructName::None)).into()
2481 }
2482
2483 if let Expression::Struct { ty, values } = true_expr {
2484 if let Expression::Struct { values: values2, .. } = false_expr {
2485 let mut fields = BTreeMap::new();
2486 for (k, v) in values.iter() {
2487 if let Some(v2) = values2.get(k) {
2488 fields.insert(k.clone(), common_expression_type(v, v2));
2489 } else {
2490 fields.insert(k.clone(), v.ty());
2491 }
2492 }
2493 for (k, v) in values2.iter() {
2494 if !values.contains_key(k) {
2495 fields.insert(k.clone(), v.ty());
2496 }
2497 }
2498 return Type::Struct(Arc::new(Struct::new(fields, StructName::None)));
2499 } else if let Type::Struct(false_ty) = false_expr.ty() {
2500 return merge_struct(&false_ty, ty);
2501 }
2502 } else if let Expression::Struct { ty, .. } = false_expr
2503 && let Type::Struct(true_ty) = true_expr.ty()
2504 {
2505 return merge_struct(&true_ty, ty);
2506 }
2507
2508 if let Expression::Array { .. } = true_expr {
2509 if let Expression::Array { .. } = false_expr {
2510 } else if let Type::Array(ty) = false_expr.ty() {
2512 return Type::Array(ty);
2513 }
2514 } else if let Expression::Array { .. } = false_expr
2515 && let Type::Array(ty) = true_expr.ty()
2516 {
2517 return Type::Array(ty);
2518 }
2519
2520 Expression::common_target_type_for_type_list([true_expr.ty(), false_expr.ty()].into_iter())
2521}
2522
2523fn lookup_qualified_name_node(
2525 node: syntax_nodes::QualifiedName,
2526 ctx: &mut LookupCtx,
2527 phase: LookupPhase,
2528) -> Option<LookupResult> {
2529 let mut it = node
2530 .children_with_tokens()
2531 .filter(|n| n.kind() == SyntaxKind::Identifier)
2532 .filter_map(|n| n.into_token());
2533
2534 let first = if let Some(first) = it.next() {
2535 first
2536 } else {
2537 debug_assert!(ctx.diag.has_errors());
2539 return None;
2540 };
2541
2542 ctx.current_token = Some(first.clone().into());
2543 let first_str = crate::parser::normalize_identifier(first.text());
2544 let global_lookup = crate::lookup::global_lookup();
2545 let result = match global_lookup.lookup(ctx, &first_str) {
2546 None => {
2547 if let Some(slot_element) =
2548 resolve_slot_reference_element(first_str.as_str(), ctx, &node)
2549 {
2550 return continue_lookup_within_element(&slot_element, &mut it, node, ctx);
2551 }
2552 if first_str == "children" || is_declared_slot_in_scope(first_str.as_str(), ctx) {
2553 return None;
2555 }
2556
2557 if let Some(minus_pos) = first.text().find('-') {
2558 let first_str = &first.text()[0..minus_pos];
2560 if global_lookup
2561 .lookup(ctx, &crate::parser::normalize_identifier(first_str))
2562 .is_some()
2563 {
2564 ctx.diag.push_error(format!("Unknown unqualified identifier '{}'. Use space before the '-' if you meant a subtraction", first.text()), &node);
2565 return None;
2566 }
2567 }
2568 for (prefix, e) in
2569 [("self", ctx.component_scope.last()), ("root", ctx.component_scope.first())]
2570 {
2571 if let Some(e) = e
2572 && e.lookup(ctx, &first_str).is_some()
2573 {
2574 ctx.diag.push_error(
2575 format!(
2576 "Unknown unqualified identifier '{0}'. Did you mean '{prefix}.{0}'?",
2577 first.text()
2578 ),
2579 &node,
2580 );
2581 return None;
2582 }
2583 }
2584
2585 if it.next().is_some() {
2586 ctx.diag.push_error(format!("Cannot access id '{}'", first.text()), &node);
2587 } else {
2588 let mut parts = crate::lookup::enum_or_color_suggestions(ctx, &first_str)
2589 .iter()
2590 .map(|s| format!("'{s}'"))
2591 .collect::<Vec<_>>();
2592 let hint = match parts.pop() {
2593 None => String::new(),
2594 Some(last) if parts.is_empty() => format!(". Did you mean {last}?"),
2595 Some(last) => format!(". Did you mean {} or {last}?", parts.join(", ")),
2596 };
2597 ctx.diag.push_error(
2598 format!("Unknown unqualified identifier '{}'{hint}", first.text()),
2599 &node,
2600 );
2601 }
2602 return None;
2603 }
2604 Some(x) => x,
2605 };
2606
2607 if let Some(depr) = result.deprecated() {
2608 ctx.diag.push_property_deprecation_warning_with_message(&first_str, depr, &first);
2609 }
2610
2611 match result {
2612 LookupResult::Expression { expression: Expression::ElementReference(e), .. } => {
2613 continue_lookup_within_element(&e.upgrade().unwrap(), &mut it, node, ctx)
2614 }
2615 LookupResult::Expression {
2616 expression: mut e @ Expression::RepeaterModelReference { .. },
2617 ..
2618 } if matches!(phase, LookupPhase::ResolvingTwoWayBindings) => {
2619 for n in it {
2623 e = Expression::StructFieldAccess { base: e.into(), name: n.text().into() };
2624 }
2625 Some(e.into())
2626 }
2627 result => maybe_lookup_object(result, it, ctx),
2628 }
2629}
2630
2631fn resolve_slot_reference_element(
2632 name: &str,
2633 ctx: &mut LookupCtx,
2634 node: &dyn Spanned,
2635) -> Option<ElementRc> {
2636 if name == "children" {
2637 ctx.diag.push_error(
2638 "The default slot '@children' cannot be referenced in expressions".into(),
2639 node,
2640 );
2641 return None;
2642 }
2643
2644 for scope_elem in ctx.component_scope.iter().rev() {
2645 let scope_elem_ref = scope_elem.borrow();
2646 let repeated = scope_elem_ref.repeated.is_some();
2647 let mut matches = scope_elem_ref.children.iter().filter(|child| {
2648 child.borrow().slot_target.as_ref().is_some_and(|slot| slot.as_str() == name)
2649 });
2650 if let Some(found) = matches.next() {
2651 if matches.next().is_some() {
2652 ctx.diag.push_error(format!("Duplicate assignment to slot '{name}'"), node);
2653 return None;
2654 }
2655
2656 if repeated {
2657 ctx.diag.push_error(
2658 format!(
2659 "Slot '{name}' cannot be referenced inside repeated or conditional elements"
2660 ),
2661 node,
2662 );
2663 return None;
2664 }
2665
2666 return Some(found.clone());
2667 }
2668 }
2669
2670 if is_declared_slot_in_scope(name, ctx) {
2671 ctx.diag.push_error(format!("Slot '{name}' is not assigned in this instance"), node);
2672 return None;
2673 }
2674
2675 None
2676}
2677
2678fn is_declared_slot_in_scope(name: &str, ctx: &LookupCtx) -> bool {
2679 ctx.component_scope.iter().rev().any(|scope_elem| {
2680 let scope_elem_ref = scope_elem.borrow();
2681 let ElementType::Component(component) = &scope_elem_ref.base_type else {
2682 return false;
2683 };
2684 component.declared_slots.borrow().iter().any(|slot| slot.name == name)
2685 })
2686}
2687
2688fn continue_lookup_within_element(
2689 elem: &ElementRc,
2690 it: &mut impl Iterator<Item = crate::parser::SyntaxToken>,
2691 node: syntax_nodes::QualifiedName,
2692 ctx: &mut LookupCtx,
2693) -> Option<LookupResult> {
2694 let second = if let Some(second) = it.next() {
2695 second
2696 } else if matches!(ctx.property_type, Type::ElementReference) {
2697 return Some(Expression::ElementReference(Rc::downgrade(elem)).into());
2698 } else {
2699 let mut rest = String::new();
2701 if let Some(LookupResult::Expression {
2702 expression: Expression::PropertyReference(nr),
2703 ..
2704 }) = crate::lookup::InScopeLookup.lookup(ctx, &elem.borrow().id)
2705 {
2706 let e = nr.element();
2707 let e_borrowed = e.borrow();
2708 let mut id = e_borrowed.id.as_str();
2709 if id.is_empty() {
2710 if ctx.component_scope.last().is_some_and(|x| Rc::ptr_eq(&e, x)) {
2711 id = "self";
2712 } else if ctx.component_scope.first().is_some_and(|x| Rc::ptr_eq(&e, x)) {
2713 id = "root";
2714 } else if ctx.component_scope.iter().nth_back(1).is_some_and(|x| Rc::ptr_eq(&e, x))
2715 {
2716 id = "parent";
2717 }
2718 };
2719 if !id.is_empty() {
2720 rest =
2721 format!(". Use '{id}.{}' to access the property with the same name", nr.name());
2722 }
2723 } else if let Some(LookupResult::Expression {
2724 expression: Expression::EnumerationValue(value),
2725 ..
2726 }) = crate::lookup::TypeSpecificLookup.lookup(ctx, &elem.borrow().id)
2727 {
2728 rest = format!(
2729 ". Use '{}.{value}' to access the enumeration value",
2730 value.enumeration.name
2731 );
2732 }
2733 ctx.diag.push_error(format!("Cannot take reference of an element{rest}"), &node);
2734 return None;
2735 };
2736 let prop_name = crate::parser::normalize_identifier(second.text());
2737
2738 let is_local_element = ctx.is_local_element(elem);
2739 let mode = if is_local_element {
2740 PropertyLookupMode::ComponentLocal
2741 } else {
2742 PropertyLookupMode::FromOutside
2743 };
2744 let lookup_result = elem.borrow().lookup_property(&prop_name, mode);
2745 let local_to_component = lookup_result.is_local_to_component && is_local_element;
2746 let sc_resolves = !ctx.diag.is_slint_sc() || lookup_result.property_type.is_slint_sc();
2749
2750 if sc_resolves && lookup_result.property_type.is_property_type() {
2751 if !local_to_component && lookup_result.property_visibility == PropertyVisibility::Private {
2752 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);
2753 return None;
2754 } else if lookup_result.property_visibility == PropertyVisibility::Fake {
2755 ctx.diag.push_error(
2756 "This special property can only be used to make a binding and cannot be accessed"
2757 .to_string(),
2758 &second,
2759 );
2760 return None;
2761 } else if lookup_result.resolved_name != prop_name.as_str() {
2762 ctx.diag.push_property_deprecation_warning(
2763 &prop_name,
2764 &lookup_result.resolved_name,
2765 &second,
2766 );
2767 } else if let Some(message) =
2768 lookup_result.deprecated.as_ref().filter(|_| !local_to_component)
2769 {
2770 ctx.diag.push_property_deprecation_warning_with_message(&prop_name, message, &second);
2772 } else if let Some(deprecated) =
2773 crate::lookup::check_extra_deprecated(elem, ctx, &prop_name)
2774 {
2775 ctx.diag.push_property_deprecation_warning_with_message(
2776 &prop_name,
2777 &deprecated,
2778 &second,
2779 );
2780 }
2781 let prop = Expression::PropertyReference(NamedReference::new(
2782 elem,
2783 lookup_result.internal_or_resolved_name(),
2784 ));
2785 maybe_lookup_object(prop.into(), it, ctx)
2786 } else if matches!(lookup_result.property_type, Type::Callback { .. }) {
2787 if let Some(message) = lookup_result.deprecated.as_ref().filter(|_| !local_to_component) {
2788 ctx.diag.push_property_deprecation_warning_with_message(&prop_name, message, &second);
2789 }
2790 if let Some(x) = it.next() {
2791 ctx.diag.push_error("Cannot access fields of callback".into(), &x)
2792 }
2793 Some(LookupResult::Callable(LookupResultCallable::Callable(Callable::Callback(
2794 NamedReference::new(elem, lookup_result.internal_or_resolved_name()),
2795 ))))
2796 } else if sc_resolves && let Type::Function(fun) = &lookup_result.property_type {
2797 if lookup_result.property_visibility == PropertyVisibility::Private && !local_to_component {
2798 let message = format!(
2799 "The function '{}' is private. Annotate it with 'public' to make it accessible from other components",
2800 second.text()
2801 );
2802 if !lookup_result.is_local_to_component {
2803 ctx.diag.push_error(message, &second);
2804 } else {
2805 ctx.diag.push_warning(message+". Note: this used to be allowed in previous version, but this should be considered an error", &second);
2806 }
2807 } else if lookup_result.property_visibility == PropertyVisibility::Protected
2808 && !local_to_component
2809 && !(lookup_result.is_in_direct_base
2810 && ctx.component_scope.first().is_some_and(|x| Rc::ptr_eq(x, elem)))
2811 {
2812 ctx.diag.push_error(format!("The function '{}' is protected", second.text()), &second);
2813 }
2814 if let Some(message) = lookup_result.deprecated.as_ref().filter(|_| !local_to_component) {
2815 ctx.diag.push_property_deprecation_warning_with_message(&prop_name, message, &second);
2816 }
2817 if let Some(x) = it.next() {
2818 ctx.diag.push_error("Cannot access fields of a function".into(), &x)
2819 }
2820 let callable = match lookup_result.builtin_function {
2821 Some(builtin) => Callable::Builtin(builtin),
2822 None => Callable::Function(NamedReference::new(
2823 elem,
2824 lookup_result.internal_or_resolved_name(),
2825 )),
2826 };
2827 if matches!(fun.args.first(), Some(Type::ElementReference)) {
2828 LookupResult::Callable(LookupResultCallable::MemberFunction {
2829 base: Expression::ElementReference(Rc::downgrade(elem)),
2830 source_node: Some(NodeOrToken::Node(node.into())),
2831 member: Box::new(LookupResultCallable::Callable(callable)),
2832 })
2833 .into()
2834 } else {
2835 LookupResult::from(callable).into()
2836 }
2837 } else {
2838 let mut err = |extra: &str| {
2839 let what = match &elem.borrow().base_type {
2840 ElementType::Global | ElementType::Interface => {
2841 let enclosing_type = elem.borrow().enclosing_component.upgrade().unwrap();
2842 assert!(enclosing_type.is_global() || enclosing_type.is_interface());
2843 format!("'{}'", enclosing_type.id)
2844 }
2845 ElementType::Component(c) => format!("Element '{}'", c.id),
2846 ElementType::Builtin(b) => format!("Element '{}'", b.name),
2847 ElementType::Native(_) => unreachable!("the native pass comes later"),
2848 ElementType::Error => {
2849 assert!(ctx.diag.has_errors());
2850 return;
2851 }
2852 };
2853 ctx.diag.push_error(
2854 format!("{} does not have a property '{}'{}", what, second.text(), extra),
2855 &second,
2856 );
2857 };
2858 if let Some(minus_pos) = second.text().find('-') {
2859 if elem
2861 .borrow()
2862 .lookup_property(
2863 &crate::parser::normalize_identifier(&second.text()[0..minus_pos]),
2864 mode,
2865 )
2866 .property_type
2867 != Type::Invalid
2868 {
2869 err(". Use space before the '-' if you meant a subtraction");
2870 return None;
2871 }
2872 }
2873 err("");
2874 None
2875 }
2876}
2877
2878fn maybe_lookup_object(
2879 mut base: LookupResult,
2880 it: impl Iterator<Item = crate::parser::SyntaxToken>,
2881 ctx: &mut LookupCtx,
2882) -> Option<LookupResult> {
2883 for next in it {
2884 let next_str = crate::parser::normalize_identifier(next.text());
2885 ctx.current_token = Some(next.clone().into());
2886 match base.lookup(ctx, &next_str) {
2887 Some(r) => {
2888 base = r;
2889 }
2890 None => {
2891 if let Some(minus_pos) = next.text().find('-')
2892 && base.lookup(ctx, &SmolStr::new(&next.text()[0..minus_pos])).is_some()
2893 {
2894 ctx.diag.push_error(format!("Cannot access the field '{}'. Use space before the '-' if you meant a subtraction", next.text()), &next);
2895 return None;
2896 }
2897
2898 match base {
2899 LookupResult::Callable(LookupResultCallable::Callable(Callable::Callback(
2900 ..,
2901 ))) => ctx.diag.push_error("Cannot access fields of callback".into(), &next),
2902 LookupResult::Callable(..) => {
2903 ctx.diag.push_error("Cannot access fields of a function".into(), &next)
2904 }
2905 LookupResult::Enumeration(enumeration) => ctx.diag.push_error(
2906 format!(
2907 "'{}' is not a member of the enum {}",
2908 next.text(),
2909 enumeration.name
2910 ),
2911 &next,
2912 ),
2913
2914 LookupResult::Namespace(ns) => {
2915 ctx.diag.push_error(
2916 format!("'{}' is not a member of the namespace {}", next.text(), ns),
2917 &next,
2918 );
2919 }
2920 LookupResult::Expression { expression, .. } => {
2921 let ty_descr = match expression.ty() {
2922 Type::Struct { .. } => String::new(),
2923 Type::Float32
2924 if ctx.property_type == Type::Model
2925 && matches!(
2926 expression,
2927 Expression::NumberLiteral(_, Unit::None),
2928 ) =>
2929 {
2930 format!(
2932 " 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 {}`",
2933 next.text()
2934 )
2935 }
2936
2937 ty => format!(" of {ty}"),
2938 };
2939 ctx.diag.push_error(
2940 format!("Cannot access the field '{}'{}", next.text(), ty_descr),
2941 &next,
2942 );
2943 }
2944 }
2945 return None;
2946 }
2947 }
2948 }
2949 Some(base)
2950}
2951
2952fn resolve_two_way_bindings_for_element(
2956 elem: &ElementRc,
2957 scope: &[ElementRc],
2958 type_register: &TypeRegister,
2959 diag: &mut BuildDiagnostics,
2960) {
2961 let mut to_infer: Vec<(SmolStr, Type)> = Vec::new();
2964
2965 for (prop_name, binding) in elem.borrow().real_bindings() {
2966 let mut binding = binding.borrow_mut();
2967 let twb_from_expression = match binding.value_expression() {
2972 Expression::Uncompiled(node) => syntax_nodes::TwoWayBinding::new(node.clone()),
2973 _ => None,
2974 };
2975 let twb_node = twb_from_expression
2976 .clone()
2977 .or_else(|| elem.borrow().callback_alias_declaration_node(prop_name));
2978 if let Some(n) = twb_node {
2979 let node: SyntaxNode = n.clone().into();
2980 let lhs_lookup =
2981 elem.borrow().lookup_property(prop_name, PropertyLookupMode::InternalName);
2982 if !lhs_lookup.is_valid() {
2983 assert!(diag.has_errors());
2985 continue;
2986 }
2987 let declared_name = elem
2989 .borrow()
2990 .property_declarations
2991 .get(prop_name)
2992 .and_then(|d| d.shadowed_name.clone())
2993 .unwrap_or_else(|| prop_name.clone());
2994 let mut lookup_ctx = LookupCtx {
2995 property_name: Some(declared_name.as_str()),
2996 property_type: lhs_lookup.property_type.clone(),
2997 expected_type: lhs_lookup.property_type.clone(),
2998 component_scope: scope,
2999 diag,
3000 symbol_counters: SymbolCounters::shared(),
3002 arguments: Vec::new(),
3003 type_register,
3004 type_loader: None,
3005 current_token: Some(node.clone().into()),
3006 local_variables: Vec::new(),
3007 expected_type_probe: None,
3008 };
3009
3010 if twb_from_expression.is_some() {
3013 binding.expression = Expression::Invalid;
3014 }
3015
3016 if let Some(twb) = resolve_two_way_binding(n, &mut lookup_ctx) {
3017 if matches!(lhs_lookup.property_type, Type::InferredProperty) {
3018 to_infer.push((prop_name.clone(), twb.ty()));
3019 }
3020 let nr = twb.property().cloned();
3021 binding.two_way_bindings.push(twb);
3022
3023 let Some(nr) = nr else { continue };
3024 nr.element()
3025 .borrow()
3026 .property_analysis
3027 .borrow_mut()
3028 .entry(nr.name().clone())
3029 .or_default()
3030 .is_linked = true;
3031
3032 if matches!(
3033 lhs_lookup.property_visibility,
3034 PropertyVisibility::Private | PropertyVisibility::Output
3035 ) && !lhs_lookup.is_local_to_component
3036 {
3037 assert!(diag.has_errors() || elem.borrow().is_legacy_syntax);
3039 continue;
3040 }
3041
3042 let mut rhs_lookup = nr
3044 .element()
3045 .borrow()
3046 .lookup_property(nr.name(), PropertyLookupMode::InternalName);
3047 if rhs_lookup.property_type == Type::Invalid {
3048 assert!(diag.has_errors());
3050 continue;
3051 }
3052 rhs_lookup.is_local_to_component &= lookup_ctx.is_local_element(&nr.element());
3053
3054 if elem
3058 .borrow()
3059 .property_declarations
3060 .get(prop_name)
3061 .is_some_and(|d| d.has_derived_deprecation())
3062 && !(Rc::ptr_eq(&nr.element(), elem)
3063 && rhs_lookup.property_visibility != PropertyVisibility::Private)
3064 {
3065 lookup_ctx.diag.push_error(
3066 "@deprecated without a message derives the replacement from the two-way binding target, which must be a public property of the same element; provide an explicit @deprecated(\"...\") message instead".into(),
3067 &node,
3068 );
3069 }
3070
3071 if !rhs_lookup.is_valid_for_assignment() {
3072 match (lhs_lookup.property_visibility, rhs_lookup.property_visibility) {
3073 (PropertyVisibility::Input, PropertyVisibility::Input)
3074 if !lhs_lookup.is_local_to_component =>
3075 {
3076 assert!(rhs_lookup.is_local_to_component);
3077 marked_linked_read_only(elem, prop_name);
3078 }
3079 (
3080 PropertyVisibility::Output | PropertyVisibility::Private,
3081 PropertyVisibility::Output | PropertyVisibility::Input,
3082 ) => {
3083 assert!(lhs_lookup.is_local_to_component);
3084 marked_linked_read_only(elem, prop_name);
3085 }
3086 (PropertyVisibility::Input, PropertyVisibility::Output)
3087 if !lhs_lookup.is_local_to_component =>
3088 {
3089 assert!(!rhs_lookup.is_local_to_component);
3090 marked_linked_read_only(elem, prop_name);
3091 }
3092 _ => {
3093 if lookup_ctx.is_legacy_component() {
3094 diag.push_warning(
3095 format!(
3096 "Link to an '{}' property is deprecated",
3097 rhs_lookup.property_visibility
3098 ),
3099 &node,
3100 );
3101 } else {
3102 diag.push_error(
3103 format!(
3104 "Cannot link to an '{}' property",
3105 rhs_lookup.property_visibility
3106 ),
3107 &node,
3108 )
3109 }
3110 }
3111 }
3112 } else if !lhs_lookup.is_valid_for_assignment() {
3113 if rhs_lookup.is_local_to_component
3114 && rhs_lookup.property_visibility == PropertyVisibility::InOut
3115 {
3116 if lookup_ctx.is_legacy_component() {
3117 debug_assert!(!diag.is_empty()); } else {
3119 diag.push_error(
3120 format!("Cannot link '{}' property", PropertyVisibility::Input),
3121 &node,
3122 );
3123 }
3124 } else if rhs_lookup.property_visibility == PropertyVisibility::InOut {
3125 diag.push_warning(
3126 format!(
3127 "Linking '{}' properties to '{}' properties is deprecated",
3128 PropertyVisibility::Input,
3129 PropertyVisibility::InOut
3130 ),
3131 &node,
3132 );
3133 marked_linked_read_only(&nr.element(), nr.name());
3134 } else {
3135 marked_linked_read_only(&nr.element(), nr.name());
3137 }
3138 }
3139 }
3140 }
3141 }
3142
3143 if !to_infer.is_empty() {
3144 let mut elem_mut = elem.borrow_mut();
3145 for (prop_name, inferred) in to_infer {
3146 let decl = elem_mut.property_declarations.get_mut(&prop_name).unwrap();
3147 if inferred.is_property_type() {
3148 decl.property_type = inferred;
3149 } else {
3150 let type_node = decl.type_node();
3151 diag.push_error(
3152 format!("Could not infer type of property '{prop_name}'"),
3153 &type_node,
3154 );
3155 }
3156 }
3157 }
3158
3159 fn marked_linked_read_only(elem: &ElementRc, prop_name: &str) {
3160 elem.borrow()
3161 .property_analysis
3162 .borrow_mut()
3163 .entry(prop_name.into())
3164 .or_default()
3165 .is_linked_to_read_only = true;
3166 }
3167}
3168
3169pub fn resolve_two_way_binding(
3170 node: syntax_nodes::TwoWayBinding,
3171 ctx: &mut LookupCtx,
3172) -> Option<TwoWayBinding> {
3173 const ERROR_MESSAGE: &str = "The expression in a two way binding must be a property reference";
3174
3175 let Some(n) = node.Expression().QualifiedName() else {
3176 ctx.diag.push_error(ERROR_MESSAGE.into(), &node.Expression());
3177 return None;
3178 };
3179
3180 let Some(r) = lookup_qualified_name_node(n, ctx, LookupPhase::ResolvingTwoWayBindings) else {
3181 assert!(ctx.diag.has_errors());
3182 return None;
3183 };
3184
3185 let report_error = !matches!(
3187 ctx.property_type,
3188 Type::InferredProperty | Type::InferredCallback | Type::Invalid
3189 );
3190 match r {
3191 LookupResult::Expression { expression, .. } => {
3192 fn unwrap_fields(expression: &Expression) -> Option<TwoWayBinding> {
3193 match expression {
3194 Expression::PropertyReference(nr) => Some(nr.clone().into()),
3195 Expression::StructFieldAccess { base, name } => {
3196 let mut prop = unwrap_fields(base)?;
3197 let field_access = match &mut prop {
3198 TwoWayBinding::Property { field_access, .. } => field_access,
3199 TwoWayBinding::ModelData { field_access, .. } => field_access,
3200 };
3201 field_access.push(name.clone());
3202 Some(prop)
3203 }
3204 Expression::RepeaterModelReference { element } => {
3205 Some(TwoWayBinding::ModelData {
3206 repeated_element: element.clone(),
3207 field_access: vec![],
3208 })
3209 }
3210 _ => None,
3211 }
3212 }
3213 if let Some(result) = unwrap_fields(&expression) {
3214 let expr_ty = if let TwoWayBinding::ModelData { repeated_element, field_access } =
3219 &result
3220 {
3221 let mut ty =
3222 Expression::RepeaterModelReference { element: repeated_element.clone() }
3223 .ty();
3224 if !matches!(ty, Type::Invalid) {
3225 for f in field_access {
3226 let next = if let Type::Struct(s) = &ty {
3227 s.fields.get(f.as_str()).cloned()
3228 } else {
3229 None
3230 };
3231 let Some(next) = next else {
3232 ctx.diag.push_error(
3233 format!("Cannot access the field '{f}' of {ty}"),
3234 &node,
3235 );
3236 return None;
3237 };
3238 ty = next;
3239 }
3240 }
3241 ty
3242 } else {
3243 result.ty()
3244 };
3245 if report_error && expr_ty != ctx.property_type {
3246 ctx.diag.push_error(
3247 format!(
3248 "The property '{}' does not have the same type as the bound expression: {} != {expr_ty}",
3249 ctx.property_name.unwrap_or(""),
3250 ctx.property_type,
3251 ),
3252 &node,
3253 );
3254 }
3255 Some(result)
3256 } else {
3257 let kind = match expression {
3258 Expression::StructFieldAccess { .. } | Expression::ArrayIndex { .. } => {
3259 "Two-way bindings can only target property references"
3260 }
3261 _ => ERROR_MESSAGE,
3262 };
3263 ctx.diag.push_error(kind.into(), &node);
3264 None
3265 }
3266 }
3267 LookupResult::Callable(LookupResultCallable::Callable(Callable::Callback(n))) => {
3268 if report_error && n.ty() != ctx.property_type {
3269 ctx.diag.push_error("Cannot bind to a callback".into(), &node);
3270 None
3271 } else {
3272 Some(n.into())
3273 }
3274 }
3275 LookupResult::Callable(..) => {
3276 if report_error {
3277 ctx.diag.push_error("Cannot bind to a function".into(), &node);
3278 }
3279 None
3280 }
3281 _ => {
3282 ctx.diag.push_error(ERROR_MESSAGE.into(), &node);
3283 None
3284 }
3285 }
3286}
3287
3288fn check_callback_alias_validity(
3290 node: &syntax_nodes::CallbackConnection,
3291 elem: &ElementRc,
3292 name: &str,
3293 diag: &mut BuildDiagnostics,
3294) {
3295 let elem_borrow = elem.borrow();
3296 let Some(decl) = elem_borrow.property_declarations.get(name) else {
3297 if let ElementType::Component(c) = &elem_borrow.base_type {
3298 check_callback_alias_validity(node, &c.root_element, name, diag);
3299 }
3300 return;
3301 };
3302 let Some(b) = elem_borrow.binding_cell_including_synthetic(name) else { return };
3303 let Some(alias) = b
3305 .try_borrow()
3306 .ok()
3307 .and_then(|b| b.two_way_bindings.first().and_then(|x| x.property()).cloned())
3308 else {
3309 return;
3310 };
3311
3312 if alias.element().borrow().base_type == ElementType::Global
3316 && elem_borrow.base_type != ElementType::Global
3317 {
3318 diag.push_error(
3319 "Can't assign a local callback handler to an alias to a global callback".into(),
3320 &node.child_token(SyntaxKind::Identifier).unwrap(),
3321 );
3322 }
3323 if let Type::Callback(callback) = &decl.property_type {
3324 let num_arg = node.DeclaredIdentifier().count();
3325 if num_arg > callback.args.len() {
3326 diag.push_error(
3327 format!(
3328 "'{name}' only has {} arguments, but {num_arg} were provided",
3329 callback.args.len(),
3330 ),
3331 &node.child_token(SyntaxKind::Identifier).unwrap(),
3332 );
3333 }
3334 }
3335}
3336
3337#[cfg(feature = "slint-sc")]
3344fn check_slint_sc_handler_body(
3345 expr: &Expression,
3346 node: &syntax_nodes::CallbackConnection,
3347 ctx: &mut LookupCtx,
3348) {
3349 let statements = match expr {
3350 Expression::CodeBlock(statements) => statements.as_slice(),
3351 single => core::slice::from_ref(single),
3352 };
3353 if !statements.iter().all(|statement| {
3354 matches!(
3355 statement,
3356 Expression::Invalid | Expression::FunctionCall { function: Callable::Callback(..), .. }
3358 )
3359 }) {
3360 let name = node.child_token(SyntaxKind::Identifier);
3363 ctx.diag.slint_sc_error(
3364 "A callback handler body that isn't a callback invocation is",
3365 name.as_ref().map_or(&**node as &dyn Spanned, |name| name),
3366 );
3367 }
3368}