1use std::rc::Rc;
7use std::sync::Arc;
8
9use crate::diagnostics::{BuildDiagnostics, Spanned};
10use crate::expression_tree::{
11 BuiltinFunction, BuiltinMacroFunction, Callable, EasingCurve, Expression, MouseCursorInner,
12 Unit,
13};
14use crate::langtype::{ElementType, Enumeration, EnumerationValue, PropertyLookupMode, Type};
15use crate::namedreference::NamedReference;
16use crate::object_tree::{ElementRc, PropertyVisibility};
17use crate::parser::{NodeOrToken, TextRange, TextSize};
18use crate::symbol_counters::SymbolCounters;
19use crate::typeregister::TypeRegister;
20use smol_str::{SmolStr, format_smolstr};
21use std::cell::RefCell;
22
23pub use i_slint_common::color_parsing::named_colors;
24
25pub struct LookupCtx<'a> {
27 pub property_name: Option<&'a str>,
29
30 pub property_type: Type,
33
34 pub expected_type: Type,
39
40 pub component_scope: &'a [ElementRc],
42
43 pub diag: &'a mut BuildDiagnostics,
45
46 pub symbol_counters: Rc<SymbolCounters>,
48
49 pub arguments: Vec<SmolStr>,
51
52 pub type_register: &'a TypeRegister,
54
55 pub type_loader: Option<&'a crate::typeloader::TypeLoader>,
58
59 pub current_token: Option<NodeOrToken>,
61
62 pub local_variables: Vec<Vec<(SmolStr, Type)>>,
64
65 pub expected_type_probe: Option<(TextSize, Type)>,
68}
69
70impl<'a> LookupCtx<'a> {
71 pub fn empty_context(
73 type_register: &'a TypeRegister,
74 diag: &'a mut BuildDiagnostics,
75 symbol_counters: Rc<SymbolCounters>,
76 ) -> Self {
77 Self {
78 property_name: Default::default(),
79 property_type: Default::default(),
80 expected_type: Default::default(),
81 component_scope: Default::default(),
82 diag,
83 symbol_counters,
84 arguments: Default::default(),
85 type_register,
86 type_loader: None,
87 current_token: None,
88 local_variables: Default::default(),
89 expected_type_probe: None,
90 }
91 }
92
93 pub fn return_type(&self) -> &Type {
94 match &self.property_type {
95 Type::Callback(f) | Type::Function(f) => &f.return_type,
96 _ => &self.property_type,
97 }
98 }
99
100 fn experimental_lookup_enabled(&self) -> bool {
103 self.diag.enable_experimental || self.type_register.expose_internal_types
104 }
105
106 pub fn set_expected_type_probe(&mut self, offset: TextSize) {
108 self.expected_type_probe = Some((offset, self.expected_type.clone()));
109 }
110
111 pub fn expected_type_probe_offset(&self) -> Option<TextSize> {
113 self.expected_type_probe.as_ref().map(|(offset, _)| *offset)
114 }
115
116 pub fn take_expected_type_probe(&mut self) -> Option<Type> {
118 self.expected_type_probe.take().map(|(_, ty)| ty)
119 }
120
121 pub fn record_expected_type_probe(&mut self, range: TextRange, ty: &Type) {
124 if let Some((offset, slot)) = &mut self.expected_type_probe
125 && range.contains_inclusive(*offset)
126 {
127 *slot = ty.clone();
128 }
129 }
130
131 pub fn with_expected_type<R>(&mut self, ty: Type, f: impl FnOnce(&mut Self) -> R) -> R {
133 let old = std::mem::replace(&mut self.expected_type, ty);
134 let r = f(self);
135 self.expected_type = old;
136 r
137 }
138
139 pub fn is_legacy_component(&self) -> bool {
140 self.component_scope.first().is_some_and(|e| e.borrow().is_legacy_syntax)
141 }
142
143 pub fn is_local_element(&self, elem: &ElementRc) -> bool {
145 Option::zip(
146 elem.borrow().enclosing_component.upgrade(),
147 self.component_scope.first().and_then(|x| x.borrow().enclosing_component.upgrade()),
148 )
149 .is_none_or(|(x, y)| Rc::ptr_eq(&x, &y))
150 }
151}
152
153#[derive(Debug)]
154pub enum LookupResult {
155 Expression {
156 expression: Expression,
157 deprecated: Option<SmolStr>,
160 },
161 Enumeration(Arc<Enumeration>),
162 Namespace(BuiltinNamespace),
163 Callable(LookupResultCallable),
164}
165
166#[derive(Debug)]
167pub enum LookupResultCallable {
168 Callable(Callable),
169 Macro(BuiltinMacroFunction),
170 MemberFunction {
172 base: Expression,
174 source_node: Option<NodeOrToken>,
178 member: Box<LookupResultCallable>,
179 },
180}
181
182#[derive(Debug, derive_more::Display)]
183pub enum BuiltinNamespace {
184 Colors,
185 Easing,
186 Math,
187 Key,
188 FontWeight,
189 MouseCursor,
190 SlintInternal,
191}
192
193impl From<Expression> for LookupResult {
194 fn from(expression: Expression) -> Self {
195 Self::Expression { expression, deprecated: None }
196 }
197}
198impl From<Callable> for LookupResult {
199 fn from(callable: Callable) -> Self {
200 Self::Callable(LookupResultCallable::Callable(callable))
201 }
202}
203impl From<BuiltinMacroFunction> for LookupResult {
204 fn from(macro_function: BuiltinMacroFunction) -> Self {
205 Self::Callable(LookupResultCallable::Macro(macro_function))
206 }
207}
208impl From<BuiltinFunction> for LookupResult {
209 fn from(function: BuiltinFunction) -> Self {
210 Self::Callable(LookupResultCallable::Callable(Callable::Builtin(function)))
211 }
212}
213
214impl LookupResult {
215 pub fn deprecated(&self) -> Option<&str> {
216 match self {
217 Self::Expression { deprecated: Some(x), .. } => Some(x.as_str()),
218 _ => None,
219 }
220 }
221}
222
223pub trait LookupObject {
225 fn for_each_entry<R>(
228 &self,
229 ctx: &LookupCtx,
230 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
231 ) -> Option<R>;
232
233 fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
236 self.for_each_entry(ctx, &mut |prop, expr| (prop == name).then_some(expr))
237 }
238}
239
240impl<T1: LookupObject, T2: LookupObject> LookupObject for (T1, T2) {
241 fn for_each_entry<R>(
242 &self,
243 ctx: &LookupCtx,
244 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
245 ) -> Option<R> {
246 self.0.for_each_entry(ctx, f).or_else(|| self.1.for_each_entry(ctx, f))
247 }
248
249 fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
250 self.0.lookup(ctx, name).or_else(|| self.1.lookup(ctx, name))
251 }
252}
253
254impl LookupObject for LookupResult {
255 fn for_each_entry<R>(
256 &self,
257 ctx: &LookupCtx,
258 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
259 ) -> Option<R> {
260 match self {
261 LookupResult::Expression { expression, .. } => expression.for_each_entry(ctx, f),
262 LookupResult::Enumeration(e) => e.for_each_entry(ctx, f),
263 LookupResult::Namespace(BuiltinNamespace::Colors) => {
264 (ColorSpecific, ColorFunctions).for_each_entry(ctx, f)
265 }
266 LookupResult::Namespace(BuiltinNamespace::Easing) => {
267 EasingSpecific.for_each_entry(ctx, f)
268 }
269 LookupResult::Namespace(BuiltinNamespace::Math) => MathFunctions.for_each_entry(ctx, f),
270 LookupResult::Namespace(BuiltinNamespace::Key) => KeysLookup.for_each_entry(ctx, f),
271 LookupResult::Namespace(BuiltinNamespace::FontWeight) => {
272 FontWeightLookup.for_each_entry(ctx, f)
273 }
274 LookupResult::Namespace(BuiltinNamespace::MouseCursor) => {
275 MouseCursorSpecific.for_each_entry(ctx, f)
276 }
277 LookupResult::Namespace(BuiltinNamespace::SlintInternal) => {
278 SlintInternal.for_each_entry(ctx, f)
279 }
280 LookupResult::Callable(..) => None,
281 }
282 }
283
284 fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
285 match self {
286 LookupResult::Expression { expression, .. } => expression.lookup(ctx, name),
287 LookupResult::Enumeration(e) => e.lookup(ctx, name),
288 LookupResult::Namespace(BuiltinNamespace::Colors) => {
289 (ColorSpecific, ColorFunctions).lookup(ctx, name)
290 }
291 LookupResult::Namespace(BuiltinNamespace::Easing) => EasingSpecific.lookup(ctx, name),
292 LookupResult::Namespace(BuiltinNamespace::Math) => MathFunctions.lookup(ctx, name),
293 LookupResult::Namespace(BuiltinNamespace::Key) => KeysLookup.lookup(ctx, name),
294 LookupResult::Namespace(BuiltinNamespace::FontWeight) => {
295 FontWeightLookup.lookup(ctx, name)
296 }
297 LookupResult::Namespace(BuiltinNamespace::MouseCursor) => {
298 MouseCursorSpecific.lookup(ctx, name)
299 }
300 LookupResult::Namespace(BuiltinNamespace::SlintInternal) => {
301 SlintInternal.lookup(ctx, name)
302 }
303 LookupResult::Callable(..) => None,
304 }
305 }
306}
307
308struct LocalVariableLookup;
309impl LookupObject for LocalVariableLookup {
310 fn for_each_entry<R>(
311 &self,
312 ctx: &LookupCtx,
313 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
314 ) -> Option<R> {
315 for scope in ctx.local_variables.iter().rev() {
316 for (name, ty) in scope.iter().rev() {
317 if let Some(r) = f(
318 &name.strip_prefix("local_").unwrap_or(name).into(),
320 Expression::ReadLocalVariable { name: name.clone(), ty: ty.clone() }.into(),
321 ) {
322 return Some(r);
323 }
324 }
325 }
326 None
327 }
328}
329
330struct ArgumentsLookup;
331impl LookupObject for ArgumentsLookup {
332 fn for_each_entry<R>(
333 &self,
334 ctx: &LookupCtx,
335 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
336 ) -> Option<R> {
337 let args = match &ctx.property_type {
338 Type::Callback(f) | Type::Function(f) => &f.args,
339 _ => return None,
340 };
341 for (index, (name, ty)) in ctx.arguments.iter().zip(args.iter()).enumerate() {
342 if let Some(r) =
343 f(name, Expression::FunctionParameterReference { index, ty: ty.clone() }.into())
344 {
345 return Some(r);
346 }
347 }
348 None
349 }
350}
351
352struct SpecialIdLookup;
353impl LookupObject for SpecialIdLookup {
354 fn for_each_entry<R>(
355 &self,
356 ctx: &LookupCtx,
357 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
358 ) -> Option<R> {
359 let last = ctx.component_scope.last();
360 let mut f = |n, e: Expression| f(&SmolStr::new_static(n), e.into());
361 None.or_else(|| f("self", Expression::ElementReference(Rc::downgrade(last?))))
362 .or_else(|| {
363 let len = ctx.component_scope.len();
364 if len >= 2 {
365 f(
366 "parent",
367 Expression::ElementReference(Rc::downgrade(&ctx.component_scope[len - 2])),
368 )
369 } else {
370 None
371 }
372 })
373 .or_else(|| f("true", Expression::BoolLiteral(true)))
374 .or_else(|| f("false", Expression::BoolLiteral(false)))
375 }
377}
378
379struct IdLookup;
380impl LookupObject for IdLookup {
381 fn for_each_entry<R>(
382 &self,
383 ctx: &LookupCtx,
384 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
385 ) -> Option<R> {
386 fn visit<R>(
387 root: &ElementRc,
388 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
389 ) -> Option<R> {
390 if !root.borrow().id.is_empty()
391 && let Some(r) =
392 f(&root.borrow().id, Expression::ElementReference(Rc::downgrade(root)).into())
393 {
394 return Some(r);
395 }
396 for x in &root.borrow().children {
397 if x.borrow().repeated.is_some() {
398 continue;
399 }
400 if let Some(r) = visit(x, f) {
401 return Some(r);
402 }
403 }
404 None
405 }
406 for e in ctx.component_scope.iter().rev() {
407 if e.borrow().repeated.is_some()
408 && let Some(r) = visit(e, f)
409 {
410 return Some(r);
411 }
412 }
413 if let Some(root) = ctx.component_scope.first()
414 && let Some(r) = visit(root, f)
415 {
416 return Some(r);
417 }
418 None
419 }
420 }
422
423pub struct InScopeLookup;
425impl InScopeLookup {
426 fn visit_scope<R>(
427 ctx: &LookupCtx,
428 mut visit_entry: impl FnMut(&SmolStr, LookupResult) -> Option<R>,
429 mut visit_legacy_scope: impl FnMut(&ElementRc) -> Option<R>,
430 mut visit_scope: impl FnMut(&ElementRc) -> Option<R>,
431 ) -> Option<R> {
432 let is_legacy = ctx.is_legacy_component();
433 for (idx, elem) in ctx.component_scope.iter().rev().enumerate() {
434 if let Some(repeated) = &elem.borrow().repeated {
435 if !repeated.index_id.is_empty()
436 && let Some(r) = visit_entry(
437 &repeated.index_id,
438 Expression::RepeaterIndexReference { element: Rc::downgrade(elem) }.into(),
439 )
440 {
441 return Some(r);
442 }
443 if !repeated.model_data_id.is_empty()
444 && let Some(r) = visit_entry(
445 &repeated.model_data_id,
446 Expression::RepeaterModelReference { element: Rc::downgrade(elem) }.into(),
447 )
448 {
449 return Some(r);
450 }
451 }
452
453 if is_legacy {
454 if (elem.borrow().repeated.is_some()
455 || idx == 0
456 || idx == ctx.component_scope.len() - 1)
457 && let Some(r) = visit_legacy_scope(elem)
458 {
459 return Some(r);
460 }
461 } else if let Some(r) = visit_scope(elem) {
462 return Some(r);
463 }
464 }
465 None
466 }
467}
468impl LookupObject for InScopeLookup {
469 fn for_each_entry<R>(
470 &self,
471 ctx: &LookupCtx,
472 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
473 ) -> Option<R> {
474 let f = RefCell::new(f);
475 Self::visit_scope(
476 ctx,
477 |str, r| f.borrow_mut()(str, r),
478 |elem| elem.for_each_entry(ctx, *f.borrow_mut()),
479 |elem| {
480 for (internal_name, prop) in &elem.borrow().property_declarations {
481 let e = expression_from_reference(
482 NamedReference::new(elem, internal_name.clone()),
483 &prop.property_type,
484 None,
485 );
486 if let Some(r) = f.borrow_mut()(prop.declared_name(internal_name), e) {
487 return Some(r);
488 }
489 }
490 None
491 },
492 )
493 }
494
495 fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
496 if name.is_empty() {
497 return None;
498 }
499 Self::visit_scope(
500 ctx,
501 |str, r| (str == name).then_some(r),
502 |elem| elem.lookup(ctx, name),
503 |elem| {
504 let elem_borrow = elem.borrow();
505 elem_borrow.declaration(name).map(|(internal_name, prop)| {
506 expression_from_reference(
507 NamedReference::new(elem, internal_name.clone()),
508 &prop.property_type,
509 None,
510 )
511 })
512 },
513 )
514 }
515}
516
517impl LookupObject for ElementRc {
518 fn for_each_entry<R>(
519 &self,
520 ctx: &LookupCtx,
521 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
522 ) -> Option<R> {
523 for (internal_name, prop) in &self.borrow().property_declarations {
524 let name = prop.declared_name(internal_name);
525 let r = expression_from_reference(
526 NamedReference::new(self, internal_name.clone()),
527 &prop.property_type,
528 check_extra_deprecated(self, ctx, name),
529 );
530 if let Some(r) = f(name, r) {
531 return Some(r);
532 }
533 }
534 let has_shadows = !self.borrow().shadowing_members.is_empty();
536 let list = self.borrow().base_type.property_list();
537 for (name, ty) in list {
538 if has_shadows && self.borrow().shadowing_members.contains_key(&name) {
540 continue;
541 }
542 let key = self
544 .borrow()
545 .lookup_property(&name, PropertyLookupMode::ComponentLocal)
546 .internal_or_resolved_name();
547 let e = expression_from_reference(NamedReference::new(self, key), &ty, None);
548 if let Some(r) = f(&name, e) {
549 return Some(r);
550 }
551 }
552
553 let is_global = match &self.borrow().base_type {
554 ElementType::Global => true,
555 ElementType::Builtin(b) => b.is_global,
556 _ => false,
557 };
558 if !is_global {
559 for (name, ty, visibility) in crate::typeregister::reserved_properties() {
560 if visibility == PropertyVisibility::Private {
561 continue;
562 }
563 let name = SmolStr::new_static(name);
564 let e =
565 expression_from_reference(NamedReference::new(self, name.clone()), &ty, None);
566 if let Some(r) = f(&name, e) {
567 return Some(r);
568 }
569 }
570 }
571 None
572 }
573
574 fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
575 let lookup_result = self.borrow().lookup_property(name, PropertyLookupMode::ComponentLocal);
576 if lookup_result.property_type != Type::Invalid
577 && (lookup_result.is_local_to_component
578 || lookup_result.property_visibility != PropertyVisibility::Private)
579 {
580 let deprecated = (lookup_result.resolved_name != name.as_str())
581 .then(|| format_smolstr!("Please use '{}' instead", lookup_result.resolved_name))
582 .or_else(|| {
583 lookup_result
586 .deprecated
587 .clone()
588 .filter(|_| !lookup_result.is_local_to_component)
589 })
590 .or_else(|| check_extra_deprecated(self, ctx, name));
591 Some(expression_from_reference(
592 NamedReference::new(self, lookup_result.internal_or_resolved_name()),
593 &lookup_result.property_type,
594 deprecated,
595 ))
596 } else {
597 None
598 }
599 }
600}
601
602pub fn check_extra_deprecated(
604 elem: &ElementRc,
605 ctx: &LookupCtx<'_>,
606 name: &SmolStr,
607) -> Option<SmolStr> {
608 if crate::typeregister::DEPRECATED_ROTATION_ORIGIN_PROPERTIES.iter().any(|(p, _)| p == name) {
609 return Some(format_smolstr!(
610 "Please use 'transform-origin.{}' instead",
611 &name[name.len() - 1..]
612 ));
613 }
614 let borrow = elem.borrow();
615 (!ctx.type_register.expose_internal_types
616 && matches!(
617 borrow.enclosing_component.upgrade().unwrap().id.as_str(),
618 "StyleMetrics" | "NativeStyleMetrics"
619 )
620 && borrow
621 .debug
622 .first()
623 .and_then(|x| x.node.source_file())
624 .is_none_or(|x| x.path().starts_with("builtin:"))
625 && !name.starts_with("layout-"))
626 .then(|| format_smolstr!("Please use 'Palette.{name}' instead"))
627}
628
629fn expression_from_reference(
630 n: NamedReference,
631 ty: &Type,
632 deprecated: Option<SmolStr>,
633) -> LookupResult {
634 match ty {
635 Type::Callback { .. } => Callable::Callback(n).into(),
636 Type::InferredCallback => Callable::Callback(n).into(),
637 Type::Function(function) => {
638 let base_expr = Rc::downgrade(&n.element());
639 let callable = Callable::Function(n);
640 if matches!(function.args.first(), Some(Type::ElementReference)) {
643 LookupResult::Callable(LookupResultCallable::MemberFunction {
644 base: Expression::ElementReference(base_expr),
645 source_node: None,
646 member: Box::new(LookupResultCallable::Callable(callable)),
647 })
648 } else {
649 callable.into()
650 }
651 }
652 _ => LookupResult::Expression { expression: Expression::PropertyReference(n), deprecated },
653 }
654}
655
656struct LookupType;
658impl LookupObject for LookupType {
659 fn for_each_entry<R>(
660 &self,
661 ctx: &LookupCtx,
662 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
663 ) -> Option<R> {
664 for (name, ty) in ctx.type_register.all_types() {
665 if let Some(r) = Self::from_type(ty).and_then(|e| f(&name, e)) {
666 return Some(r);
667 }
668 }
669 for (name, ty) in ctx.type_register.all_elements() {
670 if let Some(r) = Self::from_element(ty, ctx, &name).and_then(|e| f(&name, e)) {
671 return Some(r);
672 }
673 }
674 None
675 }
676
677 fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
678 Self::from_type(ctx.type_register.lookup(name))
679 .or_else(|| Self::from_element(ctx.type_register.lookup_element(name).ok()?, ctx, name))
680 }
681}
682impl LookupType {
683 fn from_type(ty: Type) -> Option<LookupResult> {
684 match ty {
685 Type::Enumeration(e) => Some(LookupResult::Enumeration(e)),
686 _ => None,
687 }
688 }
689
690 fn from_element(el: ElementType, ctx: &LookupCtx, name: &str) -> Option<LookupResult> {
691 match el {
692 ElementType::Component(c) if c.is_global() => {
693 if c.root_element
695 .borrow()
696 .builtin_type()
697 .is_some_and(|x| x.is_internal && x.name == name)
698 && !ctx.type_register.expose_internal_types
699 {
700 None
701 } else {
702 Some(Expression::ElementReference(Rc::downgrade(&c.root_element)).into())
703 }
704 }
705 _ => None,
706 }
707 }
708}
709
710pub struct TypeSpecificLookup;
712impl LookupObject for TypeSpecificLookup {
713 fn for_each_entry<R>(
714 &self,
715 ctx: &LookupCtx,
716 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
717 ) -> Option<R> {
718 let sc = ctx.diag.is_slint_sc();
719 match &ctx.expected_type {
720 Type::Color | Type::Brush if !sc => ColorSpecific.for_each_entry(ctx, f),
721 Type::Easing if !sc => EasingSpecific.for_each_entry(ctx, f),
722 Type::MouseCursor if !sc => MouseCursorSpecific.for_each_entry(ctx, f),
723 Type::Enumeration(enumeration) => enumeration.clone().for_each_entry(ctx, f),
724 _ => None,
725 }
726 }
727
728 fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
729 let sc = ctx.diag.is_slint_sc();
730 match &ctx.expected_type {
731 Type::Color | Type::Brush if !sc => ColorSpecific.lookup(ctx, name),
732 Type::Easing if !sc => EasingSpecific.lookup(ctx, name),
733 Type::MouseCursor if !sc => MouseCursorSpecific.lookup(ctx, name),
734 Type::Enumeration(enumeration) => enumeration.clone().lookup(ctx, name),
735 _ => None,
736 }
737 }
738}
739
740struct ColorSpecific;
741impl LookupObject for ColorSpecific {
742 fn for_each_entry<R>(
743 &self,
744 _ctx: &LookupCtx,
745 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
746 ) -> Option<R> {
747 for (name, c) in named_colors().iter() {
748 if let Some(r) = f(&SmolStr::new_static(name), Self::as_result(*c)) {
749 return Some(r);
750 }
751 }
752 None
753 }
754 fn lookup(&self, _ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
755 named_colors().get(name.as_str()).map(|c| Self::as_result(*c))
756 }
757}
758impl ColorSpecific {
759 fn as_result(value: u32) -> LookupResult {
760 Expression::Cast {
761 from: Box::new(Expression::NumberLiteral(value as f64, Unit::None)),
762 to: Type::Color,
763 }
764 .into()
765 }
766}
767
768pub fn enum_or_color_suggestions(ctx: &LookupCtx, name: &str) -> Vec<SmolStr> {
774 let name = crate::parser::normalize_identifier(name);
775 let mut result = Vec::new();
776 if named_colors().contains_key(name.as_str())
777 && BuiltinNamespaceLookup.lookup(ctx, &SmolStr::new_static("Colors")).is_some()
778 {
779 result.push(smol_str::format_smolstr!("{}.{name}", BuiltinNamespace::Colors));
780 }
781 for ty in ctx.type_register.all_types().values() {
782 if let Type::Enumeration(e) = ty
783 && e.lookup(ctx, &name).is_some()
784 {
785 result.push(smol_str::format_smolstr!("{}.{name}", e.name));
786 }
787 }
788 result.sort();
789 result.dedup();
790 result
791}
792
793pub struct KeysLookup;
794
795macro_rules! special_keys_lookup {
796 ($($char:literal # $name:ident # $($shifted:ident)? $(=> $($_muda:ident)? # $($qt:ident)|* # $($winit:ident $(($_pos:ident))?)|* # $($_xkb:ident)|*)? ;)*) => {
797 impl LookupObject for KeysLookup {
798 fn for_each_entry<R>(
799 &self,
800 _ctx: &LookupCtx,
801 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
802 ) -> Option<R> {
803 None
804 $(.or_else(|| {
805 let mut tmp = [0; 4];
806 f(&SmolStr::new_static(stringify!($name)), Expression::StringLiteral(SmolStr::new_inline($char.encode_utf8(&mut tmp))).into())
807 }))*
808 }
809 }
810 };
811}
812
813i_slint_common::for_each_keys!(special_keys_lookup);
814
815struct EasingSpecific;
816impl LookupObject for EasingSpecific {
817 fn for_each_entry<R>(
818 &self,
819 _ctx: &LookupCtx,
820 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
821 ) -> Option<R> {
822 use EasingCurve::CubicBezier;
823 let mut curve = |n, e| f(&SmolStr::new_static(n), Expression::EasingCurve(e).into());
824 let r = None
825 .or_else(|| curve("linear", EasingCurve::Linear))
826 .or_else(|| curve("ease-in-quad", CubicBezier(0.11, 0.0, 0.5, 0.0)))
827 .or_else(|| curve("ease-out-quad", CubicBezier(0.5, 1.0, 0.89, 1.0)))
828 .or_else(|| curve("ease-in-out-quad", CubicBezier(0.45, 0.0, 0.55, 1.0)))
829 .or_else(|| curve("ease", CubicBezier(0.25, 0.1, 0.25, 1.0)))
830 .or_else(|| curve("ease-in", CubicBezier(0.42, 0.0, 1.0, 1.0)))
831 .or_else(|| curve("ease-in-out", CubicBezier(0.42, 0.0, 0.58, 1.0)))
832 .or_else(|| curve("ease-out", CubicBezier(0.0, 0.0, 0.58, 1.0)))
833 .or_else(|| curve("ease-in-quart", CubicBezier(0.5, 0.0, 0.75, 0.0)))
834 .or_else(|| curve("ease-out-quart", CubicBezier(0.25, 1.0, 0.5, 1.0)))
835 .or_else(|| curve("ease-in-out-quart", CubicBezier(0.76, 0.0, 0.24, 1.0)))
836 .or_else(|| curve("ease-in-quint", CubicBezier(0.64, 0.0, 0.78, 0.0)))
837 .or_else(|| curve("ease-out-quint", CubicBezier(0.22, 1.0, 0.36, 1.0)))
838 .or_else(|| curve("ease-in-out-quint", CubicBezier(0.83, 0.0, 0.17, 1.0)))
839 .or_else(|| curve("ease-in-expo", CubicBezier(0.7, 0.0, 0.84, 0.0)))
840 .or_else(|| curve("ease-out-expo", CubicBezier(0.16, 1.0, 0.3, 1.0)))
841 .or_else(|| curve("ease-in-out-expo", CubicBezier(0.87, 0.0, 0.13, 1.0)))
842 .or_else(|| curve("ease-in-back", CubicBezier(0.36, 0.0, 0.66, -0.56)))
843 .or_else(|| curve("ease-out-back", CubicBezier(0.34, 1.56, 0.64, 1.0)))
844 .or_else(|| curve("ease-in-out-back", CubicBezier(0.68, -0.6, 0.32, 1.6)))
845 .or_else(|| curve("ease-in-sine", CubicBezier(0.12, 0.0, 0.39, 0.0)))
846 .or_else(|| curve("ease-out-sine", CubicBezier(0.61, 1.0, 0.88, 1.0)))
847 .or_else(|| curve("ease-in-out-sine", CubicBezier(0.37, 0.0, 0.63, 1.0)))
848 .or_else(|| curve("ease-in-circ", CubicBezier(0.55, 0.0, 1.0, 0.45)))
849 .or_else(|| curve("ease-out-circ", CubicBezier(0.0, 0.55, 0.45, 1.0)))
850 .or_else(|| curve("ease-in-out-circ", CubicBezier(0.85, 0.0, 0.15, 1.0)))
851 .or_else(|| curve("ease-in-elastic", EasingCurve::EaseInElastic))
852 .or_else(|| curve("ease-out-elastic", EasingCurve::EaseOutElastic))
853 .or_else(|| curve("ease-in-out-elastic", EasingCurve::EaseInOutElastic))
854 .or_else(|| curve("ease-in-bounce", EasingCurve::EaseInBounce))
855 .or_else(|| curve("ease-out-bounce", EasingCurve::EaseOutBounce))
856 .or_else(|| curve("ease-in-out-bounce", EasingCurve::EaseInOutBounce));
857 r.or_else(|| {
858 f(&SmolStr::new_static("cubic-bezier"), BuiltinMacroFunction::CubicBezier.into())
859 })
860 .or_else(|| f(&SmolStr::new_static("spring"), BuiltinMacroFunction::Spring.into()))
861 }
862}
863
864struct FontWeightLookup;
865impl LookupObject for FontWeightLookup {
866 fn for_each_entry<R>(
867 &self,
868 _ctx: &LookupCtx,
869 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
870 ) -> Option<R> {
871 let mut weight =
872 |n, v: f64| f(&SmolStr::new_static(n), Expression::NumberLiteral(v, Unit::None).into());
873 None.or_else(|| weight("thin", 100.0))
874 .or_else(|| weight("extra-light", 200.0))
875 .or_else(|| weight("light", 300.0))
876 .or_else(|| weight("normal", 400.0))
877 .or_else(|| weight("medium", 500.0))
878 .or_else(|| weight("semi-bold", 600.0))
879 .or_else(|| weight("bold", 700.0))
880 .or_else(|| weight("extra-bold", 800.0))
881 .or_else(|| weight("black", 900.0))
882 }
883}
884
885impl LookupObject for Arc<Enumeration> {
886 fn for_each_entry<R>(
887 &self,
888 ctx: &LookupCtx,
889 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
890 ) -> Option<R> {
891 if ctx.diag.is_slint_sc() && self.node.is_none() {
893 return None;
894 }
895 for (value, name) in self.values.iter().enumerate() {
896 if name == "auto"
898 && Arc::ptr_eq(self, &crate::typeregister::BUILTIN.enums.CrossAxisAlignment)
899 && ctx.property_name == Some("cross-axis-alignment")
900 {
901 continue;
902 }
903 if let Some(r) = f(
904 name,
905 Expression::EnumerationValue(EnumerationValue { value, enumeration: self.clone() })
906 .into(),
907 ) {
908 return Some(r);
909 }
910 }
911 None
912 }
913
914 fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
915 if ctx.diag.is_slint_sc() && self.node.is_none() {
917 return None;
918 }
919 let value = self.values.iter().position(|v| v == name)?;
920 Some(
921 Expression::EnumerationValue(EnumerationValue { value, enumeration: self.clone() })
922 .into(),
923 )
924 }
925}
926
927struct MathFunctions;
928impl LookupObject for MathFunctions {
929 fn for_each_entry<R>(
930 &self,
931 _ctx: &LookupCtx,
932 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
933 ) -> Option<R> {
934 let mut f = |n, e| f(&SmolStr::new_static(n), e);
935 let b = |b| LookupResult::from(Callable::Builtin(b));
936 None.or_else(|| f("mod", BuiltinMacroFunction::Mod.into()))
937 .or_else(|| f("round", b(BuiltinFunction::Round)))
938 .or_else(|| f("ceil", b(BuiltinFunction::Ceil)))
939 .or_else(|| f("floor", b(BuiltinFunction::Floor)))
940 .or_else(|| f("clamp", BuiltinMacroFunction::Clamp.into()))
941 .or_else(|| f("abs", BuiltinMacroFunction::Abs.into()))
942 .or_else(|| f("sqrt", b(BuiltinFunction::Sqrt)))
943 .or_else(|| f("max", BuiltinMacroFunction::Max.into()))
944 .or_else(|| f("min", BuiltinMacroFunction::Min.into()))
945 .or_else(|| f("sin", b(BuiltinFunction::Sin)))
946 .or_else(|| f("cos", b(BuiltinFunction::Cos)))
947 .or_else(|| f("tan", b(BuiltinFunction::Tan)))
948 .or_else(|| f("asin", b(BuiltinFunction::ASin)))
949 .or_else(|| f("acos", b(BuiltinFunction::ACos)))
950 .or_else(|| f("atan", b(BuiltinFunction::ATan)))
951 .or_else(|| f("atan2", b(BuiltinFunction::ATan2)))
952 .or_else(|| f("log", b(BuiltinFunction::Log)))
953 .or_else(|| f("ln", b(BuiltinFunction::Ln)))
954 .or_else(|| f("pow", b(BuiltinFunction::Pow)))
955 .or_else(|| f("exp", b(BuiltinFunction::Exp)))
956 .or_else(|| f("sign", BuiltinMacroFunction::Sign.into()))
957 }
958}
959
960struct MouseCursorSpecific;
961impl LookupObject for MouseCursorSpecific {
962 fn for_each_entry<R>(
963 &self,
964 ctx: &LookupCtx,
965 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
966 ) -> Option<R> {
967 let e = crate::typeregister::BUILTIN.enums.BuiltInMouseCursor.clone();
968 let mut cursor = |n, e| f(n, Expression::MouseCursor(MouseCursorInner::BuiltIn(e)).into());
969 let mut r = None;
970 for value in &e.values {
971 if let Some(enum_value) = e.clone().try_value_from_string(value.as_str()) {
972 r = r.or_else(|| cursor(value, Box::new(Expression::EnumerationValue(enum_value))));
973 }
974 }
975 r.or_else(|| {
976 if !ctx.experimental_lookup_enabled() {
978 return None;
979 }
980 f(&SmolStr::new_static("custom"), BuiltinMacroFunction::CustomMouseCursor.into())
981 })
982 }
983}
984
985struct SlintInternal;
986impl LookupObject for SlintInternal {
987 fn for_each_entry<R>(
988 &self,
989 ctx: &LookupCtx,
990 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
991 ) -> Option<R> {
992 let sl = || ctx.current_token.as_ref().map(|t| t.to_source_location());
993 let mut f = |n, e: LookupResult| f(&SmolStr::new_static(n), e);
994 let b = |b| LookupResult::from(Callable::Builtin(b));
995 None.or_else(|| {
996 let style = ctx.type_loader.and_then(|tl| tl.compiler_config.style.as_ref());
997 f(
998 "color-scheme",
999 if style.is_some_and(|s| s.ends_with("-light")) {
1000 let e = crate::typeregister::BUILTIN.enums.ColorScheme.clone();
1001 Expression::EnumerationValue(e.try_value_from_string("light").unwrap())
1002 } else if style.is_some_and(|s| s.ends_with("-dark")) {
1003 let e = crate::typeregister::BUILTIN.enums.ColorScheme.clone();
1004 Expression::EnumerationValue(e.try_value_from_string("dark").unwrap())
1005 } else {
1006 Expression::FunctionCall {
1007 function: BuiltinFunction::ColorScheme.into(),
1008 arguments: Vec::new(),
1009 source_location: sl(),
1010 }
1011 }
1012 .into(),
1013 )
1014 })
1015 .or_else(|| {
1016 f(
1017 "accent-color",
1018 Expression::FunctionCall {
1019 function: BuiltinFunction::AccentColor.into(),
1020 arguments: Vec::new(),
1021 source_location: sl(),
1022 }
1023 .into(),
1024 )
1025 })
1026 .or_else(|| {
1027 f(
1028 "use-24-hour-format",
1029 Expression::FunctionCall {
1030 function: BuiltinFunction::Use24HourFormat.into(),
1031 arguments: Vec::new(),
1032 source_location: sl(),
1033 }
1034 .into(),
1035 )
1036 })
1037 .or_else(|| f("month-day-count", b(BuiltinFunction::MonthDayCount)))
1038 .or_else(|| f("month-offset", b(BuiltinFunction::MonthOffset)))
1039 .or_else(|| f("format-date", b(BuiltinFunction::FormatDate)))
1040 .or_else(|| f("date-now", b(BuiltinFunction::DateNow)))
1041 .or_else(|| f("valid-date", b(BuiltinFunction::ValidDate)))
1042 .or_else(|| f("parse-date", b(BuiltinFunction::ParseDate)))
1043 }
1044}
1045
1046struct ColorFunctions;
1047impl LookupObject for ColorFunctions {
1048 fn for_each_entry<R>(
1049 &self,
1050 _ctx: &LookupCtx,
1051 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1052 ) -> Option<R> {
1053 let mut f = |n, m| f(&SmolStr::new_static(n), LookupResult::from(m));
1054 None.or_else(|| f("rgb", BuiltinMacroFunction::Rgb))
1055 .or_else(|| f("rgba", BuiltinMacroFunction::Rgb))
1056 .or_else(|| f("hsv", BuiltinMacroFunction::Hsv))
1057 .or_else(|| f("oklch", BuiltinMacroFunction::Oklch))
1058 }
1059}
1060
1061struct BuiltinFunctionLookup;
1062impl LookupObject for BuiltinFunctionLookup {
1063 fn for_each_entry<R>(
1064 &self,
1065 ctx: &LookupCtx,
1066 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1067 ) -> Option<R> {
1068 if ctx.diag.is_slint_sc() {
1069 return None;
1070 }
1071 (MathFunctions, ColorFunctions)
1072 .for_each_entry(ctx, f)
1073 .or_else(|| f(&SmolStr::new_static("debug"), BuiltinMacroFunction::Debug.into()))
1074 .or_else(|| {
1075 f(&SmolStr::new_static("animation-tick"), BuiltinFunction::AnimationTick.into())
1076 })
1077 }
1078}
1079
1080struct BuiltinNamespaceLookup;
1081impl LookupObject for BuiltinNamespaceLookup {
1082 fn for_each_entry<R>(
1083 &self,
1084 ctx: &LookupCtx,
1085 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1086 ) -> Option<R> {
1087 if ctx.diag.is_slint_sc() {
1088 return None;
1089 }
1090 let mut f = |s, res| f(&SmolStr::new_static(s), res);
1091 None.or_else(|| f("Colors", LookupResult::Namespace(BuiltinNamespace::Colors)))
1092 .or_else(|| f("Easing", LookupResult::Namespace(BuiltinNamespace::Easing)))
1093 .or_else(|| f("Math", LookupResult::Namespace(BuiltinNamespace::Math)))
1094 .or_else(|| f("Key", LookupResult::Namespace(BuiltinNamespace::Key)))
1095 .or_else(|| f("FontWeight", LookupResult::Namespace(BuiltinNamespace::FontWeight)))
1096 .or_else(|| {
1097 if ctx.type_register.expose_internal_types {
1098 f("SlintInternal", LookupResult::Namespace(BuiltinNamespace::SlintInternal))
1099 } else {
1100 None
1101 }
1102 })
1103 .or_else(|| f("MouseCursor", LookupResult::Namespace(BuiltinNamespace::MouseCursor)))
1104 }
1105}
1106
1107pub fn global_lookup() -> impl LookupObject {
1108 (
1109 LocalVariableLookup,
1110 (
1111 ArgumentsLookup,
1112 (
1113 SpecialIdLookup,
1114 (
1115 IdLookup,
1116 (
1117 InScopeLookup,
1118 (
1119 LookupType,
1120 (BuiltinNamespaceLookup, (TypeSpecificLookup, BuiltinFunctionLookup)),
1121 ),
1122 ),
1123 ),
1124 ),
1125 ),
1126 )
1127}
1128
1129impl LookupObject for Expression {
1130 fn for_each_entry<R>(
1131 &self,
1132 ctx: &LookupCtx,
1133 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1134 ) -> Option<R> {
1135 match self {
1136 Expression::ElementReference(e) => e.upgrade().unwrap().for_each_entry(ctx, f),
1137 _ => match self.ty() {
1138 Type::Struct(s) => {
1139 for name in s.fields.keys() {
1140 if let Some(r) = f(
1141 name,
1142 Expression::StructFieldAccess {
1143 base: Box::new(self.clone()),
1144 name: name.clone(),
1145 }
1146 .into(),
1147 ) {
1148 return Some(r);
1149 }
1150 }
1151 None
1152 }
1153 Type::Image => ImageExpression(self).for_each_entry(ctx, f),
1154 _ if ctx.diag.is_slint_sc() => None,
1156 Type::String => StringExpression(self).for_each_entry(ctx, f),
1157 Type::Brush | Type::Color => ColorExpression(self).for_each_entry(ctx, f),
1158 Type::Array(_) => ArrayExpression(self).for_each_entry(ctx, f),
1159 Type::Float32 | Type::Int32 | Type::Percent => {
1160 NumberExpression(self).for_each_entry(ctx, f)
1161 }
1162 Type::Keys => KeysExpression(self).for_each_entry(ctx, f),
1163 ty if ty.as_unit_product().is_some() => {
1164 NumberWithUnitExpression(self).for_each_entry(ctx, f)
1165 }
1166 _ => None,
1167 },
1168 }
1169 }
1170
1171 fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
1172 match self {
1173 Expression::ElementReference(e) => e.upgrade().unwrap().lookup(ctx, name),
1174 _ => match self.ty() {
1175 Type::Struct(s) => s.fields.contains_key(name).then(|| {
1176 LookupResult::from(Expression::StructFieldAccess {
1177 base: Box::new(self.clone()),
1178 name: name.clone(),
1179 })
1180 }),
1181 Type::Image => ImageExpression(self).lookup(ctx, name),
1182 _ if ctx.diag.is_slint_sc() => None,
1184 Type::String => StringExpression(self).lookup(ctx, name),
1185 Type::Brush | Type::Color => ColorExpression(self).lookup(ctx, name),
1186 Type::Array(_) => ArrayExpression(self).lookup(ctx, name),
1187 Type::Float32 | Type::Int32 | Type::Percent => {
1188 NumberExpression(self).lookup(ctx, name)
1189 }
1190 Type::Keys => KeysExpression(self).lookup(ctx, name),
1191 ty if ty.as_unit_product().is_some() => {
1192 NumberWithUnitExpression(self).lookup(ctx, name)
1193 }
1194 _ => None,
1195 },
1196 }
1197 }
1198}
1199
1200struct StringExpression<'a>(&'a Expression);
1201impl LookupObject for StringExpression<'_> {
1202 fn for_each_entry<R>(
1203 &self,
1204 ctx: &LookupCtx,
1205 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1206 ) -> Option<R> {
1207 let member_function = builtin_member_function_generator(self.0, ctx);
1208 let function_call = |f: BuiltinFunction| {
1209 LookupResult::from(Expression::FunctionCall {
1210 function: Callable::Builtin(f),
1211 source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()),
1212 arguments: vec![self.0.clone()],
1213 })
1214 };
1215
1216 let mut f = |s, res| f(&SmolStr::new_static(s), res);
1217 None.or_else(|| f("is-float", member_function(BuiltinFunction::StringIsFloat)))
1218 .or_else(|| f("to-float", member_function(BuiltinFunction::StringToFloat)))
1219 .or_else(|| f("is-empty", function_call(BuiltinFunction::StringIsEmpty)))
1220 .or_else(|| f("character-count", function_call(BuiltinFunction::StringCharacterCount)))
1221 .or_else(|| f("to-lowercase", member_function(BuiltinFunction::StringToLowercase)))
1222 .or_else(|| f("to-uppercase", member_function(BuiltinFunction::StringToUppercase)))
1223 .or_else(|| f("starts-with", member_function(BuiltinFunction::StringStartsWith)))
1224 .or_else(|| f("ends-with", member_function(BuiltinFunction::StringEndsWith)))
1225 .or_else(|| f("replace-all", member_function(BuiltinFunction::StringReplaceAll)))
1226 }
1227}
1228
1229struct ColorExpression<'a>(&'a Expression);
1230impl LookupObject for ColorExpression<'_> {
1231 fn for_each_entry<R>(
1232 &self,
1233 ctx: &LookupCtx,
1234 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1235 ) -> Option<R> {
1236 let member_function = |f: BuiltinFunction| {
1237 let base = if (f == BuiltinFunction::ColorHsvaStruct
1238 || f == BuiltinFunction::ColorOklchStruct)
1239 && self.0.ty() == Type::Brush
1240 {
1241 Expression::Cast { from: Box::new(self.0.clone()), to: Type::Color }
1242 } else {
1243 self.0.clone()
1244 };
1245 LookupResult::Callable(LookupResultCallable::MemberFunction {
1246 base,
1247 source_node: ctx.current_token.clone(),
1248 member: Box::new(LookupResultCallable::Callable(Callable::Builtin(f))),
1249 })
1250 };
1251 let field_access = |f: &'static str| {
1252 let base = if self.0.ty() == Type::Brush {
1253 Expression::Cast { from: Box::new(self.0.clone()), to: Type::Color }
1254 } else {
1255 self.0.clone()
1256 };
1257 LookupResult::from(Expression::StructFieldAccess {
1258 base: Box::new(Expression::FunctionCall {
1259 function: BuiltinFunction::ColorRgbaStruct.into(),
1260 source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()),
1261 arguments: vec![base],
1262 }),
1263 name: SmolStr::new_static(f),
1264 })
1265 };
1266
1267 let mut f = |s, res| f(&SmolStr::new_static(s), res);
1268 None.or_else(|| f("red", field_access("red")))
1269 .or_else(|| f("green", field_access("green")))
1270 .or_else(|| f("blue", field_access("blue")))
1271 .or_else(|| f("alpha", field_access("alpha")))
1272 .or_else(|| f("to-hsv", member_function(BuiltinFunction::ColorHsvaStruct)))
1273 .or_else(|| f("to-oklch", member_function(BuiltinFunction::ColorOklchStruct)))
1274 .or_else(|| f("brighter", member_function(BuiltinFunction::ColorBrighter)))
1275 .or_else(|| f("darker", member_function(BuiltinFunction::ColorDarker)))
1276 .or_else(|| f("transparentize", member_function(BuiltinFunction::ColorTransparentize)))
1277 .or_else(|| f("with-alpha", member_function(BuiltinFunction::ColorWithAlpha)))
1278 .or_else(|| f("mix", member_function(BuiltinFunction::ColorMix)))
1279 }
1280}
1281
1282struct ImageExpression<'a>(&'a Expression);
1283impl LookupObject for ImageExpression<'_> {
1284 fn for_each_entry<R>(
1285 &self,
1286 ctx: &LookupCtx,
1287 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1288 ) -> Option<R> {
1289 let field_access = |f: &str| {
1290 LookupResult::from(Expression::StructFieldAccess {
1291 base: Box::new(Expression::FunctionCall {
1292 function: BuiltinFunction::ImageSize.into(),
1293 source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()),
1294 arguments: vec![self.0.clone()],
1295 }),
1296 name: f.into(),
1297 })
1298 };
1299 let mut f = |s, res| f(&SmolStr::new_static(s), res);
1300 None.or_else(|| f("width", field_access("width")))
1301 .or_else(|| f("height", field_access("height")))
1302 }
1303}
1304
1305struct ArrayExpression<'a>(&'a Expression);
1306impl LookupObject for ArrayExpression<'_> {
1307 fn for_each_entry<R>(
1308 &self,
1309 ctx: &LookupCtx,
1310 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1311 ) -> Option<R> {
1312 let member_function = |f: BuiltinFunction| {
1313 LookupResult::Callable(LookupResultCallable::MemberFunction {
1314 base: self.0.clone(),
1315 source_node: ctx.current_token.clone(),
1316 member: LookupResultCallable::Callable(Callable::Builtin(f)).into(),
1317 })
1318 };
1319 let function_call = |f: BuiltinFunction| {
1320 LookupResult::from(Expression::FunctionCall {
1321 function: Callable::Builtin(f),
1322 source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()),
1323 arguments: vec![self.0.clone()],
1324 })
1325 };
1326 let mut member_macro = member_macro_generator(self.0.clone(), ctx.current_token.clone());
1327
1328 let mut f = |s, res| f(&SmolStr::new_static(s), res);
1329 None.or_else(|| f("length", function_call(BuiltinFunction::ArrayLength)))
1330 .or_else(|| f("push", member_macro(BuiltinMacroFunction::ArrayPush)))
1331 .or_else(|| f("remove", member_macro(BuiltinMacroFunction::ArrayRemove)))
1332 .or_else(|| f("insert", member_macro(BuiltinMacroFunction::ArrayInsert)))
1333 .or_else(|| {
1334 if !ctx.experimental_lookup_enabled() {
1336 return None;
1337 }
1338 f("index-of", member_macro(BuiltinMacroFunction::ArrayIndexOf))
1339 .or_else(|| f("any", member_function(BuiltinFunction::ArrayAny)))
1340 .or_else(|| f("all", member_function(BuiltinFunction::ArrayAll)))
1341 .or_else(|| f("find-index", member_function(BuiltinFunction::ArrayFindIndex)))
1342 })
1343 }
1344}
1345
1346struct NumberExpression<'a>(&'a Expression);
1348impl LookupObject for NumberExpression<'_> {
1349 fn for_each_entry<R>(
1350 &self,
1351 ctx: &LookupCtx,
1352 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1353 ) -> Option<R> {
1354 let member_function = builtin_member_function_generator(self.0, ctx);
1355 let mut member_macro = member_macro_generator(self.0.clone(), ctx.current_token.clone());
1356
1357 let mut f2 = |s, res| f(&SmolStr::new_static(s), res);
1358 None.or_else(|| f2("round", member_function(BuiltinFunction::Round)))
1359 .or_else(|| f2("ceil", member_function(BuiltinFunction::Ceil)))
1360 .or_else(|| f2("floor", member_function(BuiltinFunction::Floor)))
1361 .or_else(|| f2("sqrt", member_function(BuiltinFunction::Sqrt)))
1362 .or_else(|| f2("asin", member_function(BuiltinFunction::ASin)))
1363 .or_else(|| f2("acos", member_function(BuiltinFunction::ACos)))
1364 .or_else(|| f2("atan", member_function(BuiltinFunction::ATan)))
1365 .or_else(|| f2("log", member_function(BuiltinFunction::Log)))
1366 .or_else(|| f2("ln", member_function(BuiltinFunction::Ln)))
1367 .or_else(|| f2("pow", member_function(BuiltinFunction::Pow)))
1368 .or_else(|| f2("exp", member_function(BuiltinFunction::Exp)))
1369 .or_else(|| f2("sign", member_macro(BuiltinMacroFunction::Sign)))
1370 .or_else(|| f2("to-fixed", member_function(BuiltinFunction::ToFixed)))
1371 .or_else(|| f2("to-precision", member_function(BuiltinFunction::ToPrecision)))
1372 .or_else(|| {
1373 f2("to-string-unlocalized", member_function(BuiltinFunction::ToStringUnlocalized))
1374 })
1375 .or_else(|| NumberWithUnitExpression(self.0).for_each_entry(ctx, f))
1376 }
1377}
1378
1379fn builtin_member_function_generator<'a>(
1380 base: &'a Expression,
1381 ctx: &'a LookupCtx,
1382) -> impl Fn(BuiltinFunction) -> LookupResult {
1383 move |func: BuiltinFunction| {
1384 LookupResult::Callable(LookupResultCallable::MemberFunction {
1385 base: base.clone(),
1386 source_node: ctx.current_token.clone(),
1387 member: Box::new(LookupResultCallable::Callable(Callable::Builtin(func))),
1388 })
1389 }
1390}
1391
1392fn member_macro_generator(
1393 base: Expression,
1394 source_node: Option<NodeOrToken>,
1395) -> impl FnMut(BuiltinMacroFunction) -> LookupResult {
1396 move |func: BuiltinMacroFunction| {
1397 LookupResult::Callable(LookupResultCallable::MemberFunction {
1398 base: base.clone(),
1399 source_node: source_node.clone(),
1400 member: Box::new(LookupResultCallable::Macro(func)),
1401 })
1402 }
1403}
1404
1405struct NumberWithUnitExpression<'a>(&'a Expression);
1407impl LookupObject for NumberWithUnitExpression<'_> {
1408 fn for_each_entry<R>(
1409 &self,
1410 ctx: &LookupCtx,
1411 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1412 ) -> Option<R> {
1413 let mut member_macro = member_macro_generator(self.0.clone(), ctx.current_token.clone());
1414 let member_function = builtin_member_function_generator(self.0, ctx);
1415 let mut f = |s, res| f(&SmolStr::new_static(s), res);
1416 None.or_else(|| f("mod", member_macro(BuiltinMacroFunction::Mod)))
1417 .or_else(|| f("clamp", member_macro(BuiltinMacroFunction::Clamp)))
1418 .or_else(|| f("abs", member_macro(BuiltinMacroFunction::Abs)))
1419 .or_else(|| f("max", member_macro(BuiltinMacroFunction::Max)))
1420 .or_else(|| f("min", member_macro(BuiltinMacroFunction::Min)))
1421 .or_else(|| {
1422 if self.0.ty() != Type::Angle {
1423 return None;
1424 }
1425 None.or_else(|| f("sin", member_function(BuiltinFunction::Sin)))
1426 .or_else(|| f("cos", member_function(BuiltinFunction::Cos)))
1427 .or_else(|| f("tan", member_function(BuiltinFunction::Tan)))
1428 })
1429 }
1430}
1431
1432struct KeysExpression<'a>(&'a Expression);
1433
1434impl LookupObject for KeysExpression<'_> {
1435 fn for_each_entry<R>(
1436 &self,
1437 ctx: &LookupCtx,
1438 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1439 ) -> Option<R> {
1440 let member_function = builtin_member_function_generator(self.0, ctx);
1441 None.or_else(|| {
1442 f(&SmolStr::new_static("to-string"), member_function(BuiltinFunction::KeysToString))
1443 })
1444 }
1445}