i_slint_compiler/
lookup.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! Helper to do lookup in expressions
5
6use std::rc::Rc;
7
8use crate::diagnostics::{BuildDiagnostics, Spanned};
9use crate::expression_tree::{
10    BuiltinFunction, BuiltinMacroFunction, Callable, EasingCurve, Expression, Unit,
11};
12use crate::langtype::{ElementType, Enumeration, EnumerationValue, Type};
13use crate::namedreference::NamedReference;
14use crate::object_tree::{ElementRc, PropertyVisibility};
15use crate::parser::NodeOrToken;
16use crate::typeregister::TypeRegister;
17use smol_str::{SmolStr, ToSmolStr};
18use std::cell::RefCell;
19
20mod named_colors;
21
22pub use named_colors::named_colors;
23
24/// Contains information which allow to lookup identifier in expressions
25pub struct LookupCtx<'a> {
26    /// the name of the property for which this expression refers.
27    pub property_name: Option<&'a str>,
28
29    /// the type of the property for which this expression refers.
30    /// (some property come in the scope)
31    pub property_type: Type,
32
33    /// Here is the stack in which id applies. (the last element in the scope is looked up first)
34    pub component_scope: &'a [ElementRc],
35
36    /// Somewhere to report diagnostics
37    pub diag: &'a mut BuildDiagnostics,
38
39    /// The name of the arguments of the callback or function
40    pub arguments: Vec<SmolStr>,
41
42    /// The type register in which to look for Globals
43    pub type_register: &'a TypeRegister,
44
45    /// The type loader instance, which may be used to resolve relative path references
46    /// for example for img!
47    pub type_loader: Option<&'a crate::typeloader::TypeLoader>,
48
49    /// The token currently processed
50    pub current_token: Option<NodeOrToken>,
51
52    /// A stack of local variable scopes
53    pub local_variables: Vec<Vec<(SmolStr, Type)>>,
54}
55
56impl<'a> LookupCtx<'a> {
57    /// Return a context that is just suitable to build simple const expression
58    pub fn empty_context(type_register: &'a TypeRegister, diag: &'a mut BuildDiagnostics) -> Self {
59        Self {
60            property_name: Default::default(),
61            property_type: Default::default(),
62            component_scope: Default::default(),
63            diag,
64            arguments: Default::default(),
65            type_register,
66            type_loader: None,
67            current_token: None,
68            local_variables: Default::default(),
69        }
70    }
71
72    pub fn return_type(&self) -> &Type {
73        match &self.property_type {
74            Type::Callback(f) | Type::Function(f) => &f.return_type,
75            _ => &self.property_type,
76        }
77    }
78
79    pub fn is_legacy_component(&self) -> bool {
80        self.component_scope.first().is_some_and(|e| e.borrow().is_legacy_syntax)
81    }
82
83    /// True if the element is in the same component as the scope
84    pub fn is_local_element(&self, elem: &ElementRc) -> bool {
85        Option::zip(
86            elem.borrow().enclosing_component.upgrade(),
87            self.component_scope.first().and_then(|x| x.borrow().enclosing_component.upgrade()),
88        )
89        .map_or(true, |(x, y)| Rc::ptr_eq(&x, &y))
90    }
91}
92
93#[derive(Debug)]
94pub enum LookupResult {
95    Expression {
96        expression: Expression,
97        /// When set, this is deprecated, and the string is the new name
98        deprecated: Option<String>,
99    },
100    Enumeration(Rc<Enumeration>),
101    Namespace(BuiltinNamespace),
102    Callable(LookupResultCallable),
103}
104
105#[derive(Debug)]
106pub enum LookupResultCallable {
107    Callable(Callable),
108    Macro(BuiltinMacroFunction),
109    /// for example for `item.focus`, where `item` is the base
110    MemberFunction {
111        /// This becomes the first argument of the function call
112        base: Expression,
113        base_node: Option<NodeOrToken>,
114        member: Box<LookupResultCallable>,
115    },
116}
117
118#[derive(Debug, derive_more::Display)]
119pub enum BuiltinNamespace {
120    Colors,
121    Easing,
122    Math,
123    Key,
124    SlintInternal,
125}
126
127impl From<Expression> for LookupResult {
128    fn from(expression: Expression) -> Self {
129        Self::Expression { expression, deprecated: None }
130    }
131}
132impl From<Callable> for LookupResult {
133    fn from(callable: Callable) -> Self {
134        Self::Callable(LookupResultCallable::Callable(callable))
135    }
136}
137impl From<BuiltinMacroFunction> for LookupResult {
138    fn from(macro_function: BuiltinMacroFunction) -> Self {
139        Self::Callable(LookupResultCallable::Macro(macro_function))
140    }
141}
142impl From<BuiltinFunction> for LookupResult {
143    fn from(function: BuiltinFunction) -> Self {
144        Self::Callable(LookupResultCallable::Callable(Callable::Builtin(function)))
145    }
146}
147
148impl LookupResult {
149    pub fn deprecated(&self) -> Option<&str> {
150        match self {
151            Self::Expression { deprecated: Some(x), .. } => Some(x.as_str()),
152            _ => None,
153        }
154    }
155}
156
157/// Represent an object which has properties which can be accessible
158pub trait LookupObject {
159    /// Will call the function for each entry (useful for completion)
160    /// If the function return Some, it will immediately be returned and not called further
161    fn for_each_entry<R>(
162        &self,
163        ctx: &LookupCtx,
164        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
165    ) -> Option<R>;
166
167    /// Perform a lookup of a given identifier.
168    /// One does not have to re-implement unless we can make it faster
169    fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
170        self.for_each_entry(ctx, &mut |prop, expr| (prop == name).then_some(expr))
171    }
172}
173
174impl<T1: LookupObject, T2: LookupObject> LookupObject for (T1, T2) {
175    fn for_each_entry<R>(
176        &self,
177        ctx: &LookupCtx,
178        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
179    ) -> Option<R> {
180        self.0.for_each_entry(ctx, f).or_else(|| self.1.for_each_entry(ctx, f))
181    }
182
183    fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
184        self.0.lookup(ctx, name).or_else(|| self.1.lookup(ctx, name))
185    }
186}
187
188impl LookupObject for LookupResult {
189    fn for_each_entry<R>(
190        &self,
191        ctx: &LookupCtx,
192        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
193    ) -> Option<R> {
194        match self {
195            LookupResult::Expression { expression, .. } => expression.for_each_entry(ctx, f),
196            LookupResult::Enumeration(e) => e.for_each_entry(ctx, f),
197            LookupResult::Namespace(BuiltinNamespace::Colors) => {
198                (ColorSpecific, ColorFunctions).for_each_entry(ctx, f)
199            }
200            LookupResult::Namespace(BuiltinNamespace::Easing) => {
201                EasingSpecific.for_each_entry(ctx, f)
202            }
203            LookupResult::Namespace(BuiltinNamespace::Math) => MathFunctions.for_each_entry(ctx, f),
204            LookupResult::Namespace(BuiltinNamespace::Key) => KeysLookup.for_each_entry(ctx, f),
205            LookupResult::Namespace(BuiltinNamespace::SlintInternal) => {
206                SlintInternal.for_each_entry(ctx, f)
207            }
208            LookupResult::Callable(..) => None,
209        }
210    }
211
212    fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
213        match self {
214            LookupResult::Expression { expression, .. } => expression.lookup(ctx, name),
215            LookupResult::Enumeration(e) => e.lookup(ctx, name),
216            LookupResult::Namespace(BuiltinNamespace::Colors) => {
217                (ColorSpecific, ColorFunctions).lookup(ctx, name)
218            }
219            LookupResult::Namespace(BuiltinNamespace::Easing) => EasingSpecific.lookup(ctx, name),
220            LookupResult::Namespace(BuiltinNamespace::Math) => MathFunctions.lookup(ctx, name),
221            LookupResult::Namespace(BuiltinNamespace::Key) => KeysLookup.lookup(ctx, name),
222            LookupResult::Namespace(BuiltinNamespace::SlintInternal) => {
223                SlintInternal.lookup(ctx, name)
224            }
225            LookupResult::Callable(..) => None,
226        }
227    }
228}
229
230struct LocalVariableLookup;
231impl LookupObject for LocalVariableLookup {
232    fn for_each_entry<R>(
233        &self,
234        ctx: &LookupCtx,
235        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
236    ) -> Option<R> {
237        for scope in ctx.local_variables.iter() {
238            for (name, ty) in scope {
239                if let Some(r) = f(
240                    // we need to strip the "local_" prefix because a lookup call will not include it
241                    &name.strip_prefix("local_").unwrap_or(name).into(),
242                    Expression::ReadLocalVariable { name: name.clone(), ty: ty.clone() }.into(),
243                ) {
244                    return Some(r);
245                }
246            }
247        }
248        None
249    }
250}
251
252struct ArgumentsLookup;
253impl LookupObject for ArgumentsLookup {
254    fn for_each_entry<R>(
255        &self,
256        ctx: &LookupCtx,
257        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
258    ) -> Option<R> {
259        let args = match &ctx.property_type {
260            Type::Callback(f) | Type::Function(f) => &f.args,
261            _ => return None,
262        };
263        for (index, (name, ty)) in ctx.arguments.iter().zip(args.iter()).enumerate() {
264            if let Some(r) =
265                f(name, Expression::FunctionParameterReference { index, ty: ty.clone() }.into())
266            {
267                return Some(r);
268            }
269        }
270        None
271    }
272}
273
274struct SpecialIdLookup;
275impl LookupObject for SpecialIdLookup {
276    fn for_each_entry<R>(
277        &self,
278        ctx: &LookupCtx,
279        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
280    ) -> Option<R> {
281        let last = ctx.component_scope.last();
282        let mut f = |n, e: Expression| f(&SmolStr::new_static(n), e.into());
283        None.or_else(|| f("self", Expression::ElementReference(Rc::downgrade(last?))))
284            .or_else(|| {
285                let len = ctx.component_scope.len();
286                if len >= 2 {
287                    f(
288                        "parent",
289                        Expression::ElementReference(Rc::downgrade(&ctx.component_scope[len - 2]))
290                            .into(),
291                    )
292                } else {
293                    None
294                }
295            })
296            .or_else(|| f("true", Expression::BoolLiteral(true)))
297            .or_else(|| f("false", Expression::BoolLiteral(false)))
298        // "root" is just a normal id
299    }
300}
301
302struct IdLookup;
303impl LookupObject for IdLookup {
304    fn for_each_entry<R>(
305        &self,
306        ctx: &LookupCtx,
307        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
308    ) -> Option<R> {
309        fn visit<R>(
310            root: &ElementRc,
311            f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
312        ) -> Option<R> {
313            if !root.borrow().id.is_empty() {
314                if let Some(r) =
315                    f(&root.borrow().id, Expression::ElementReference(Rc::downgrade(root)).into())
316                {
317                    return Some(r);
318                }
319            }
320            for x in &root.borrow().children {
321                if x.borrow().repeated.is_some() {
322                    continue;
323                }
324                if let Some(r) = visit(x, f) {
325                    return Some(r);
326                }
327            }
328            None
329        }
330        for e in ctx.component_scope.iter().rev() {
331            if e.borrow().repeated.is_some() {
332                if let Some(r) = visit(e, f) {
333                    return Some(r);
334                }
335            }
336        }
337        if let Some(root) = ctx.component_scope.first() {
338            if let Some(r) = visit(root, f) {
339                return Some(r);
340            }
341        }
342        None
343    }
344    // TODO: hash based lookup
345}
346
347/// In-scope properties, or model
348pub struct InScopeLookup;
349impl InScopeLookup {
350    fn visit_scope<R>(
351        ctx: &LookupCtx,
352        mut visit_entry: impl FnMut(&SmolStr, LookupResult) -> Option<R>,
353        mut visit_legacy_scope: impl FnMut(&ElementRc) -> Option<R>,
354        mut visit_scope: impl FnMut(&ElementRc) -> Option<R>,
355    ) -> Option<R> {
356        let is_legacy = ctx.is_legacy_component();
357        for (idx, elem) in ctx.component_scope.iter().rev().enumerate() {
358            if let Some(repeated) = &elem.borrow().repeated {
359                if !repeated.index_id.is_empty() {
360                    if let Some(r) = visit_entry(
361                        &repeated.index_id,
362                        Expression::RepeaterIndexReference { element: Rc::downgrade(elem) }.into(),
363                    ) {
364                        return Some(r);
365                    }
366                }
367                if !repeated.model_data_id.is_empty() {
368                    if let Some(r) = visit_entry(
369                        &repeated.model_data_id,
370                        Expression::RepeaterModelReference { element: Rc::downgrade(elem) }.into(),
371                    ) {
372                        return Some(r);
373                    }
374                }
375            }
376
377            if is_legacy {
378                if elem.borrow().repeated.is_some()
379                    || idx == 0
380                    || idx == ctx.component_scope.len() - 1
381                {
382                    if let Some(r) = visit_legacy_scope(elem) {
383                        return Some(r);
384                    }
385                }
386            } else if let Some(r) = visit_scope(elem) {
387                return Some(r);
388            }
389        }
390        None
391    }
392}
393impl LookupObject for InScopeLookup {
394    fn for_each_entry<R>(
395        &self,
396        ctx: &LookupCtx,
397        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
398    ) -> Option<R> {
399        let f = RefCell::new(f);
400        Self::visit_scope(
401            ctx,
402            |str, r| f.borrow_mut()(str, r),
403            |elem| elem.for_each_entry(ctx, *f.borrow_mut()),
404            |elem| {
405                for (name, prop) in &elem.borrow().property_declarations {
406                    let e = expression_from_reference(
407                        NamedReference::new(elem, name.clone()),
408                        &prop.property_type,
409                        None,
410                    );
411                    if let Some(r) = f.borrow_mut()(name, e) {
412                        return Some(r);
413                    }
414                }
415                None
416            },
417        )
418    }
419
420    fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
421        if name.is_empty() {
422            return None;
423        }
424        Self::visit_scope(
425            ctx,
426            |str, r| (str == name).then_some(r),
427            |elem| elem.lookup(ctx, name),
428            |elem| {
429                elem.borrow().property_declarations.get(name).map(|prop| {
430                    expression_from_reference(
431                        NamedReference::new(elem, name.clone()),
432                        &prop.property_type,
433                        None,
434                    )
435                })
436            },
437        )
438    }
439}
440
441impl LookupObject for ElementRc {
442    fn for_each_entry<R>(
443        &self,
444        ctx: &LookupCtx,
445        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
446    ) -> Option<R> {
447        for (name, prop) in &self.borrow().property_declarations {
448            let r = expression_from_reference(
449                NamedReference::new(self, name.clone()),
450                &prop.property_type,
451                check_extra_deprecated(self, ctx, name),
452            );
453            if let Some(r) = f(name, r) {
454                return Some(r);
455            }
456        }
457        let list = self.borrow().base_type.property_list();
458        for (name, ty) in list {
459            let e = expression_from_reference(NamedReference::new(self, name.clone()), &ty, None);
460            if let Some(r) = f(&name, e) {
461                return Some(r);
462            }
463        }
464        if !(matches!(self.borrow().base_type, ElementType::Global)) {
465            for (name, ty, _) in crate::typeregister::reserved_properties() {
466                let name = SmolStr::new_static(name);
467                let e =
468                    expression_from_reference(NamedReference::new(self, name.clone()), &ty, None);
469                if let Some(r) = f(&name, e) {
470                    return Some(r);
471                }
472            }
473        }
474        None
475    }
476
477    fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
478        let lookup_result = self.borrow().lookup_property(name);
479        if lookup_result.property_type != Type::Invalid
480            && (lookup_result.is_local_to_component
481                || lookup_result.property_visibility != PropertyVisibility::Private)
482        {
483            let deprecated = (lookup_result.resolved_name != name.as_str())
484                .then(|| lookup_result.resolved_name.to_string())
485                .or_else(|| check_extra_deprecated(self, ctx, name));
486            Some(expression_from_reference(
487                NamedReference::new(self, lookup_result.resolved_name.to_smolstr()),
488                &lookup_result.property_type,
489                deprecated,
490            ))
491        } else {
492            None
493        }
494    }
495}
496
497pub fn check_extra_deprecated(
498    elem: &ElementRc,
499    ctx: &LookupCtx<'_>,
500    name: &SmolStr,
501) -> Option<String> {
502    if crate::typeregister::DEPRECATED_ROTATION_ORIGIN_PROPERTIES.iter().any(|(p, _)| p == name) {
503        return Some(format!("transform-origin.{}", &name[name.len() - 1..]));
504    }
505    let borrow = elem.borrow();
506    (!ctx.type_register.expose_internal_types
507        && matches!(
508            borrow.enclosing_component.upgrade().unwrap().id.as_str(),
509            "StyleMetrics" | "NativeStyleMetrics"
510        )
511        && borrow
512            .debug
513            .first()
514            .and_then(|x| x.node.source_file())
515            .map_or(true, |x| x.path().starts_with("builtin:"))
516        && !name.starts_with("layout-"))
517    .then(|| format!("Palette.{name}"))
518}
519
520fn expression_from_reference(
521    n: NamedReference,
522    ty: &Type,
523    deprecated: Option<String>,
524) -> LookupResult {
525    match ty {
526        Type::Callback { .. } => Callable::Callback(n).into(),
527        Type::InferredCallback => Callable::Callback(n).into(),
528        Type::Function { .. } => Callable::Function(n).into(),
529        _ => LookupResult::Expression { expression: Expression::PropertyReference(n), deprecated },
530    }
531}
532
533/// Lookup for Globals and Enum.
534struct LookupType;
535impl LookupObject for LookupType {
536    fn for_each_entry<R>(
537        &self,
538        ctx: &LookupCtx,
539        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
540    ) -> Option<R> {
541        for (name, ty) in ctx.type_register.all_types() {
542            if let Some(r) = Self::from_type(ty).and_then(|e| f(&name, e)) {
543                return Some(r);
544            }
545        }
546        for (name, ty) in ctx.type_register.all_elements() {
547            if let Some(r) = Self::from_element(ty, ctx, &name).and_then(|e| f(&name, e)) {
548                return Some(r);
549            }
550        }
551        None
552    }
553
554    fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
555        Self::from_type(ctx.type_register.lookup(name))
556            .or_else(|| Self::from_element(ctx.type_register.lookup_element(name).ok()?, ctx, name))
557    }
558}
559impl LookupType {
560    fn from_type(ty: Type) -> Option<LookupResult> {
561        match ty {
562            Type::Enumeration(e) => Some(LookupResult::Enumeration(e)),
563            _ => None,
564        }
565    }
566
567    fn from_element(el: ElementType, ctx: &LookupCtx, name: &str) -> Option<LookupResult> {
568        match el {
569            ElementType::Component(c) if c.is_global() => {
570                // Check if it is internal, but allow re-export (different name) eg: NativeStyleMetrics re-exported as StyleMetrics
571                if c.root_element
572                    .borrow()
573                    .builtin_type()
574                    .is_some_and(|x| x.is_internal && x.name == name)
575                    && !ctx.type_register.expose_internal_types
576                {
577                    None
578                } else {
579                    Some(Expression::ElementReference(Rc::downgrade(&c.root_element)).into())
580                }
581            }
582            _ => None,
583        }
584    }
585}
586
587/// Lookup for things specific to the return type (eg: colors or enums)
588pub struct ReturnTypeSpecificLookup;
589impl LookupObject for ReturnTypeSpecificLookup {
590    fn for_each_entry<R>(
591        &self,
592        ctx: &LookupCtx,
593        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
594    ) -> Option<R> {
595        match ctx.return_type() {
596            Type::Color => ColorSpecific.for_each_entry(ctx, f),
597            Type::Brush => ColorSpecific.for_each_entry(ctx, f),
598            Type::Easing => EasingSpecific.for_each_entry(ctx, f),
599            Type::Enumeration(enumeration) => enumeration.clone().for_each_entry(ctx, f),
600            _ => None,
601        }
602    }
603
604    fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
605        match ctx.return_type() {
606            Type::Color => ColorSpecific.lookup(ctx, name),
607            Type::Brush => ColorSpecific.lookup(ctx, name),
608            Type::Easing => EasingSpecific.lookup(ctx, name),
609            Type::Enumeration(enumeration) => enumeration.clone().lookup(ctx, name),
610            _ => None,
611        }
612    }
613}
614
615struct ColorSpecific;
616impl LookupObject for ColorSpecific {
617    fn for_each_entry<R>(
618        &self,
619        _ctx: &LookupCtx,
620        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
621    ) -> Option<R> {
622        for (name, c) in named_colors::named_colors().iter() {
623            if let Some(r) = f(&SmolStr::new_static(name), Self::as_result(*c)) {
624                return Some(r);
625            }
626        }
627        None
628    }
629    fn lookup(&self, _ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
630        named_colors::named_colors().get(name.as_str()).map(|c| Self::as_result(*c))
631    }
632}
633impl ColorSpecific {
634    fn as_result(value: u32) -> LookupResult {
635        Expression::Cast {
636            from: Box::new(Expression::NumberLiteral(value as f64, Unit::None)),
637            to: Type::Color,
638        }
639        .into()
640    }
641}
642
643struct KeysLookup;
644
645macro_rules! special_keys_lookup {
646    ($($char:literal # $name:ident # $($qt:ident)|* # $($winit:ident $(($_pos:ident))?)|* # $($_xkb:ident)|*;)*) => {
647        impl LookupObject for KeysLookup {
648            fn for_each_entry<R>(
649                &self,
650                _ctx: &LookupCtx,
651                f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
652            ) -> Option<R> {
653                None
654                $(.or_else(|| {
655                    let mut tmp = [0; 4];
656                    f(&SmolStr::new_static(stringify!($name)), Expression::StringLiteral(SmolStr::new_inline($char.encode_utf8(&mut tmp))).into())
657                }))*
658            }
659        }
660    };
661}
662
663i_slint_common::for_each_special_keys!(special_keys_lookup);
664
665struct EasingSpecific;
666impl LookupObject for EasingSpecific {
667    fn for_each_entry<R>(
668        &self,
669        _ctx: &LookupCtx,
670        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
671    ) -> Option<R> {
672        use EasingCurve::CubicBezier;
673        let mut curve = |n, e| f(&SmolStr::new_static(n), Expression::EasingCurve(e).into());
674        let r = None
675            .or_else(|| curve("linear", EasingCurve::Linear))
676            .or_else(|| curve("ease-in-quad", CubicBezier(0.11, 0.0, 0.5, 0.0)))
677            .or_else(|| curve("ease-out-quad", CubicBezier(0.5, 1.0, 0.89, 1.0)))
678            .or_else(|| curve("ease-in-out-quad", CubicBezier(0.45, 0.0, 0.55, 1.0)))
679            .or_else(|| curve("ease", CubicBezier(0.25, 0.1, 0.25, 1.0)))
680            .or_else(|| curve("ease-in", CubicBezier(0.42, 0.0, 1.0, 1.0)))
681            .or_else(|| curve("ease-in-out", CubicBezier(0.42, 0.0, 0.58, 1.0)))
682            .or_else(|| curve("ease-out", CubicBezier(0.0, 0.0, 0.58, 1.0)))
683            .or_else(|| curve("ease-in-quart", CubicBezier(0.5, 0.0, 0.75, 0.0)))
684            .or_else(|| curve("ease-out-quart", CubicBezier(0.25, 1.0, 0.5, 1.0)))
685            .or_else(|| curve("ease-in-out-quart", CubicBezier(0.76, 0.0, 0.24, 1.0)))
686            .or_else(|| curve("ease-in-quint", CubicBezier(0.64, 0.0, 0.78, 0.0)))
687            .or_else(|| curve("ease-out-quint", CubicBezier(0.22, 1.0, 0.36, 1.0)))
688            .or_else(|| curve("ease-in-out-quint", CubicBezier(0.83, 0.0, 0.17, 1.0)))
689            .or_else(|| curve("ease-in-expo", CubicBezier(0.7, 0.0, 0.84, 0.0)))
690            .or_else(|| curve("ease-out-expo", CubicBezier(0.16, 1.0, 0.3, 1.0)))
691            .or_else(|| curve("ease-in-out-expo", CubicBezier(0.87, 0.0, 0.13, 1.0)))
692            .or_else(|| curve("ease-in-back", CubicBezier(0.36, 0.0, 0.66, -0.56)))
693            .or_else(|| curve("ease-out-back", CubicBezier(0.34, 1.56, 0.64, 1.0)))
694            .or_else(|| curve("ease-in-out-back", CubicBezier(0.68, -0.6, 0.32, 1.6)))
695            .or_else(|| curve("ease-in-sine", CubicBezier(0.12, 0.0, 0.39, 0.0)))
696            .or_else(|| curve("ease-out-sine", CubicBezier(0.61, 1.0, 0.88, 1.0)))
697            .or_else(|| curve("ease-in-out-sine", CubicBezier(0.37, 0.0, 0.63, 1.0)))
698            .or_else(|| curve("ease-in-circ", CubicBezier(0.55, 0.0, 1.0, 0.45)))
699            .or_else(|| curve("ease-out-circ", CubicBezier(0.0, 0.55, 0.45, 1.0)))
700            .or_else(|| curve("ease-in-out-circ", CubicBezier(0.85, 0.0, 0.15, 1.0)))
701            .or_else(|| curve("ease-in-elastic", EasingCurve::EaseInElastic))
702            .or_else(|| curve("ease-out-elastic", EasingCurve::EaseOutElastic))
703            .or_else(|| curve("ease-in-out-elastic", EasingCurve::EaseInOutElastic))
704            .or_else(|| curve("ease-in-bounce", EasingCurve::EaseInBounce))
705            .or_else(|| curve("ease-out-bounce", EasingCurve::EaseOutBounce))
706            .or_else(|| curve("ease-in-out-bounce", EasingCurve::EaseInOutBounce));
707        r.or_else(|| {
708            f(&SmolStr::new_static("cubic-bezier"), BuiltinMacroFunction::CubicBezier.into())
709        })
710    }
711}
712
713impl LookupObject for Rc<Enumeration> {
714    fn for_each_entry<R>(
715        &self,
716        _ctx: &LookupCtx,
717        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
718    ) -> Option<R> {
719        for (value, name) in self.values.iter().enumerate() {
720            if let Some(r) = f(
721                name,
722                Expression::EnumerationValue(EnumerationValue { value, enumeration: self.clone() })
723                    .into(),
724            ) {
725                return Some(r);
726            }
727        }
728        None
729    }
730}
731
732struct MathFunctions;
733impl LookupObject for MathFunctions {
734    fn for_each_entry<R>(
735        &self,
736        _ctx: &LookupCtx,
737        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
738    ) -> Option<R> {
739        let mut f = |n, e| f(&SmolStr::new_static(n), e);
740        let b = |b| LookupResult::from(Callable::Builtin(b));
741        None.or_else(|| f("mod", BuiltinMacroFunction::Mod.into()))
742            .or_else(|| f("round", b(BuiltinFunction::Round)))
743            .or_else(|| f("ceil", b(BuiltinFunction::Ceil)))
744            .or_else(|| f("floor", b(BuiltinFunction::Floor)))
745            .or_else(|| f("clamp", BuiltinMacroFunction::Clamp.into()))
746            .or_else(|| f("abs", BuiltinMacroFunction::Abs.into()))
747            .or_else(|| f("sqrt", b(BuiltinFunction::Sqrt)))
748            .or_else(|| f("max", BuiltinMacroFunction::Max.into()))
749            .or_else(|| f("min", BuiltinMacroFunction::Min.into()))
750            .or_else(|| f("sin", b(BuiltinFunction::Sin)))
751            .or_else(|| f("cos", b(BuiltinFunction::Cos)))
752            .or_else(|| f("tan", b(BuiltinFunction::Tan)))
753            .or_else(|| f("asin", b(BuiltinFunction::ASin)))
754            .or_else(|| f("acos", b(BuiltinFunction::ACos)))
755            .or_else(|| f("atan", b(BuiltinFunction::ATan)))
756            .or_else(|| f("atan2", b(BuiltinFunction::ATan2)))
757            .or_else(|| f("log", b(BuiltinFunction::Log)))
758            .or_else(|| f("ln", b(BuiltinFunction::Ln)))
759            .or_else(|| f("pow", b(BuiltinFunction::Pow)))
760            .or_else(|| f("exp", b(BuiltinFunction::Exp)))
761            .or_else(|| f("sign", BuiltinMacroFunction::Sign.into()))
762    }
763}
764
765struct SlintInternal;
766impl LookupObject for SlintInternal {
767    fn for_each_entry<R>(
768        &self,
769        ctx: &LookupCtx,
770        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
771    ) -> Option<R> {
772        let sl = || ctx.current_token.as_ref().map(|t| t.to_source_location());
773        let mut f = |n, e: LookupResult| f(&SmolStr::new_static(n), e);
774        let b = |b| LookupResult::from(Callable::Builtin(b));
775        None.or_else(|| {
776            let style = ctx.type_loader.and_then(|tl| tl.compiler_config.style.as_ref());
777            f(
778                "color-scheme",
779                if style.is_some_and(|s| s.ends_with("-light")) {
780                    let e = crate::typeregister::BUILTIN.with(|e| e.enums.ColorScheme.clone());
781                    Expression::EnumerationValue(e.try_value_from_string("light").unwrap())
782                } else if style.is_some_and(|s| s.ends_with("-dark")) {
783                    let e = crate::typeregister::BUILTIN.with(|e| e.enums.ColorScheme.clone());
784                    Expression::EnumerationValue(e.try_value_from_string("dark").unwrap())
785                } else {
786                    Expression::FunctionCall {
787                        function: BuiltinFunction::ColorScheme.into(),
788                        arguments: vec![],
789                        source_location: sl(),
790                    }
791                }
792                .into(),
793            )
794        })
795        .or_else(|| {
796            f(
797                "use-24-hour-format",
798                Expression::FunctionCall {
799                    function: BuiltinFunction::Use24HourFormat.into(),
800                    arguments: vec![],
801                    source_location: sl(),
802                }
803                .into(),
804            )
805        })
806        .or_else(|| f("month-day-count", b(BuiltinFunction::MonthDayCount)))
807        .or_else(|| f("month-offset", b(BuiltinFunction::MonthOffset)))
808        .or_else(|| f("format-date", b(BuiltinFunction::FormatDate)))
809        .or_else(|| f("date-now", b(BuiltinFunction::DateNow)))
810        .or_else(|| f("valid-date", b(BuiltinFunction::ValidDate)))
811        .or_else(|| f("parse-date", b(BuiltinFunction::ParseDate)))
812    }
813}
814
815struct ColorFunctions;
816impl LookupObject for ColorFunctions {
817    fn for_each_entry<R>(
818        &self,
819        _ctx: &LookupCtx,
820        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
821    ) -> Option<R> {
822        let mut f = |n, m| f(&SmolStr::new_static(n), LookupResult::from(m));
823        None.or_else(|| f("rgb", BuiltinMacroFunction::Rgb))
824            .or_else(|| f("rgba", BuiltinMacroFunction::Rgb))
825            .or_else(|| f("hsv", BuiltinMacroFunction::Hsv))
826    }
827}
828
829struct BuiltinFunctionLookup;
830impl LookupObject for BuiltinFunctionLookup {
831    fn for_each_entry<R>(
832        &self,
833        ctx: &LookupCtx,
834        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
835    ) -> Option<R> {
836        (MathFunctions, ColorFunctions)
837            .for_each_entry(ctx, f)
838            .or_else(|| f(&SmolStr::new_static("debug"), BuiltinMacroFunction::Debug.into()))
839            .or_else(|| {
840                f(&SmolStr::new_static("animation-tick"), BuiltinFunction::AnimationTick.into())
841            })
842    }
843}
844
845struct BuiltinNamespaceLookup;
846impl LookupObject for BuiltinNamespaceLookup {
847    fn for_each_entry<R>(
848        &self,
849        ctx: &LookupCtx,
850        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
851    ) -> Option<R> {
852        let mut f = |s, res| f(&SmolStr::new_static(s), res);
853        None.or_else(|| f("Colors", LookupResult::Namespace(BuiltinNamespace::Colors)))
854            .or_else(|| f("Easing", LookupResult::Namespace(BuiltinNamespace::Easing)))
855            .or_else(|| f("Math", LookupResult::Namespace(BuiltinNamespace::Math)))
856            .or_else(|| f("Key", LookupResult::Namespace(BuiltinNamespace::Key)))
857            .or_else(|| {
858                if ctx.type_register.expose_internal_types {
859                    f("SlintInternal", LookupResult::Namespace(BuiltinNamespace::SlintInternal))
860                } else {
861                    None
862                }
863            })
864    }
865}
866
867pub fn global_lookup() -> impl LookupObject {
868    (
869        LocalVariableLookup,
870        (
871            ArgumentsLookup,
872            (
873                SpecialIdLookup,
874                (
875                    IdLookup,
876                    (
877                        InScopeLookup,
878                        (
879                            LookupType,
880                            (
881                                BuiltinNamespaceLookup,
882                                (ReturnTypeSpecificLookup, BuiltinFunctionLookup),
883                            ),
884                        ),
885                    ),
886                ),
887            ),
888        ),
889    )
890}
891
892impl LookupObject for Expression {
893    fn for_each_entry<R>(
894        &self,
895        ctx: &LookupCtx,
896        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
897    ) -> Option<R> {
898        match self {
899            Expression::ElementReference(e) => e.upgrade().unwrap().for_each_entry(ctx, f),
900            _ => match self.ty() {
901                Type::Struct(s) => {
902                    for name in s.fields.keys() {
903                        if let Some(r) = f(
904                            name,
905                            Expression::StructFieldAccess {
906                                base: Box::new(self.clone()),
907                                name: name.clone(),
908                            }
909                            .into(),
910                        ) {
911                            return Some(r);
912                        }
913                    }
914                    None
915                }
916                Type::String => StringExpression(self).for_each_entry(ctx, f),
917                Type::Brush | Type::Color => ColorExpression(self).for_each_entry(ctx, f),
918                Type::Image => ImageExpression(self).for_each_entry(ctx, f),
919                Type::Array(_) => ArrayExpression(self).for_each_entry(ctx, f),
920                Type::Float32 | Type::Int32 | Type::Percent => {
921                    NumberExpression(self).for_each_entry(ctx, f)
922                }
923                ty if ty.as_unit_product().is_some() => {
924                    NumberWithUnitExpression(self).for_each_entry(ctx, f)
925                }
926                _ => None,
927            },
928        }
929    }
930
931    fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
932        match self {
933            Expression::ElementReference(e) => e.upgrade().unwrap().lookup(ctx, name),
934            _ => match self.ty() {
935                Type::Struct(s) => s.fields.contains_key(name).then(|| {
936                    LookupResult::from(Expression::StructFieldAccess {
937                        base: Box::new(self.clone()),
938                        name: name.clone(),
939                    })
940                }),
941                Type::String => StringExpression(self).lookup(ctx, name),
942                Type::Brush | Type::Color => ColorExpression(self).lookup(ctx, name),
943                Type::Image => ImageExpression(self).lookup(ctx, name),
944                Type::Array(_) => ArrayExpression(self).lookup(ctx, name),
945                Type::Float32 | Type::Int32 | Type::Percent => {
946                    NumberExpression(self).lookup(ctx, name)
947                }
948                ty if ty.as_unit_product().is_some() => {
949                    NumberWithUnitExpression(self).lookup(ctx, name)
950                }
951                _ => None,
952            },
953        }
954    }
955}
956
957struct StringExpression<'a>(&'a Expression);
958impl LookupObject for StringExpression<'_> {
959    fn for_each_entry<R>(
960        &self,
961        ctx: &LookupCtx,
962        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
963    ) -> Option<R> {
964        let member_function = |f: BuiltinFunction| {
965            LookupResult::Callable(LookupResultCallable::MemberFunction {
966                base: self.0.clone(),
967                base_node: ctx.current_token.clone(), // Note that this is not the base_node, but the function's node
968                member: LookupResultCallable::Callable(Callable::Builtin(f)).into(),
969            })
970        };
971        let function_call = |f: BuiltinFunction| {
972            LookupResult::from(Expression::FunctionCall {
973                function: Callable::Builtin(f),
974                source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()),
975                arguments: vec![self.0.clone()],
976            })
977        };
978
979        let mut f = |s, res| f(&SmolStr::new_static(s), res);
980        None.or_else(|| f("is-float", member_function(BuiltinFunction::StringIsFloat)))
981            .or_else(|| f("to-float", member_function(BuiltinFunction::StringToFloat)))
982            .or_else(|| f("is-empty", function_call(BuiltinFunction::StringIsEmpty)))
983            .or_else(|| f("character-count", function_call(BuiltinFunction::StringCharacterCount)))
984            .or_else(|| f("to-lowercase", member_function(BuiltinFunction::StringToLowercase)))
985            .or_else(|| f("to-uppercase", member_function(BuiltinFunction::StringToUppercase)))
986    }
987}
988struct ColorExpression<'a>(&'a Expression);
989impl LookupObject for ColorExpression<'_> {
990    fn for_each_entry<R>(
991        &self,
992        ctx: &LookupCtx,
993        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
994    ) -> Option<R> {
995        let member_function = |f: BuiltinFunction| {
996            let base = if f == BuiltinFunction::ColorHsvaStruct && self.0.ty() == Type::Brush {
997                Expression::Cast { from: Box::new(self.0.clone()), to: Type::Color }
998            } else {
999                self.0.clone()
1000            };
1001            LookupResult::Callable(LookupResultCallable::MemberFunction {
1002                base,
1003                base_node: ctx.current_token.clone(), // Note that this is not the base_node, but the function's node
1004                member: Box::new(LookupResultCallable::Callable(Callable::Builtin(f))),
1005            })
1006        };
1007        let field_access = |f: &'static str| {
1008            let base = if self.0.ty() == Type::Brush {
1009                Expression::Cast { from: Box::new(self.0.clone()), to: Type::Color }
1010            } else {
1011                self.0.clone()
1012            };
1013            LookupResult::from(Expression::StructFieldAccess {
1014                base: Box::new(Expression::FunctionCall {
1015                    function: BuiltinFunction::ColorRgbaStruct.into(),
1016                    source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()),
1017                    arguments: vec![base],
1018                }),
1019                name: SmolStr::new_static(f),
1020            })
1021        };
1022
1023        let mut f = |s, res| f(&SmolStr::new_static(s), res);
1024        None.or_else(|| f("red", field_access("red")))
1025            .or_else(|| f("green", field_access("green")))
1026            .or_else(|| f("blue", field_access("blue")))
1027            .or_else(|| f("alpha", field_access("alpha")))
1028            .or_else(|| f("to-hsv", member_function(BuiltinFunction::ColorHsvaStruct)))
1029            .or_else(|| f("brighter", member_function(BuiltinFunction::ColorBrighter)))
1030            .or_else(|| f("darker", member_function(BuiltinFunction::ColorDarker)))
1031            .or_else(|| f("transparentize", member_function(BuiltinFunction::ColorTransparentize)))
1032            .or_else(|| f("with-alpha", member_function(BuiltinFunction::ColorWithAlpha)))
1033            .or_else(|| f("mix", member_function(BuiltinFunction::ColorMix)))
1034    }
1035}
1036
1037struct ImageExpression<'a>(&'a Expression);
1038impl LookupObject for ImageExpression<'_> {
1039    fn for_each_entry<R>(
1040        &self,
1041        ctx: &LookupCtx,
1042        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1043    ) -> Option<R> {
1044        let field_access = |f: &str| {
1045            LookupResult::from(Expression::StructFieldAccess {
1046                base: Box::new(Expression::FunctionCall {
1047                    function: BuiltinFunction::ImageSize.into(),
1048                    source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()),
1049                    arguments: vec![self.0.clone()],
1050                }),
1051                name: f.into(),
1052            })
1053        };
1054        let mut f = |s, res| f(&SmolStr::new_static(s), res);
1055        None.or_else(|| f("width", field_access("width")))
1056            .or_else(|| f("height", field_access("height")))
1057    }
1058}
1059
1060struct ArrayExpression<'a>(&'a Expression);
1061impl LookupObject for ArrayExpression<'_> {
1062    fn for_each_entry<R>(
1063        &self,
1064        ctx: &LookupCtx,
1065        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1066    ) -> Option<R> {
1067        let member_function = |f: BuiltinFunction| {
1068            LookupResult::from(Expression::FunctionCall {
1069                function: Callable::Builtin(f),
1070                source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()),
1071                arguments: vec![self.0.clone()],
1072            })
1073        };
1074        None.or_else(|| {
1075            f(&SmolStr::new_static("length"), member_function(BuiltinFunction::ArrayLength))
1076        })
1077    }
1078}
1079
1080/// An expression of type int or float
1081struct NumberExpression<'a>(&'a Expression);
1082impl LookupObject for NumberExpression<'_> {
1083    fn for_each_entry<R>(
1084        &self,
1085        ctx: &LookupCtx,
1086        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1087    ) -> Option<R> {
1088        let member = |f: LookupResultCallable| {
1089            LookupResult::Callable(LookupResultCallable::MemberFunction {
1090                base: self.0.clone(),
1091                base_node: ctx.current_token.clone(), // Note that this is not the base_node, but the function's node
1092                member: f.into(),
1093            })
1094        };
1095        let member_function = |f| member(LookupResultCallable::Callable(Callable::Builtin(f)));
1096        let member_macro = |f| member(LookupResultCallable::Macro(f));
1097
1098        let mut f2 = |s, res| f(&SmolStr::new_static(s), res);
1099        None.or_else(|| f2("round", member_function(BuiltinFunction::Round)))
1100            .or_else(|| f2("ceil", member_function(BuiltinFunction::Ceil)))
1101            .or_else(|| f2("floor", member_function(BuiltinFunction::Floor)))
1102            .or_else(|| f2("sqrt", member_function(BuiltinFunction::Sqrt)))
1103            .or_else(|| f2("asin", member_function(BuiltinFunction::ASin)))
1104            .or_else(|| f2("acos", member_function(BuiltinFunction::ACos)))
1105            .or_else(|| f2("atan", member_function(BuiltinFunction::ATan)))
1106            .or_else(|| f2("log", member_function(BuiltinFunction::Log)))
1107            .or_else(|| f2("ln", member_function(BuiltinFunction::Ln)))
1108            .or_else(|| f2("pow", member_function(BuiltinFunction::Pow)))
1109            .or_else(|| f2("exp", member_function(BuiltinFunction::Exp)))
1110            .or_else(|| f2("sign", member_macro(BuiltinMacroFunction::Sign)))
1111            .or_else(|| f2("to-fixed", member_function(BuiltinFunction::ToFixed)))
1112            .or_else(|| f2("to-precision", member_function(BuiltinFunction::ToPrecision)))
1113            .or_else(|| NumberWithUnitExpression(self.0).for_each_entry(ctx, f))
1114    }
1115}
1116
1117/// An expression of any numerical value with an unit
1118struct NumberWithUnitExpression<'a>(&'a Expression);
1119impl LookupObject for NumberWithUnitExpression<'_> {
1120    fn for_each_entry<R>(
1121        &self,
1122        ctx: &LookupCtx,
1123        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1124    ) -> Option<R> {
1125        let member_macro = |f: BuiltinMacroFunction| {
1126            LookupResult::Callable(LookupResultCallable::MemberFunction {
1127                base: self.0.clone(),
1128                base_node: ctx.current_token.clone(), // Note that this is not the base_node, but the function's node
1129                member: Box::new(LookupResultCallable::Macro(f)),
1130            })
1131        };
1132
1133        let mut f = |s, res| f(&SmolStr::new_static(s), res);
1134        None.or_else(|| f("mod", member_macro(BuiltinMacroFunction::Mod)))
1135            .or_else(|| f("clamp", member_macro(BuiltinMacroFunction::Clamp)))
1136            .or_else(|| f("abs", member_macro(BuiltinMacroFunction::Abs)))
1137            .or_else(|| f("max", member_macro(BuiltinMacroFunction::Max)))
1138            .or_else(|| f("min", member_macro(BuiltinMacroFunction::Min)))
1139            .or_else(|| {
1140                if self.0.ty() != Type::Angle {
1141                    return None;
1142                }
1143                let member_function = |f: BuiltinFunction| {
1144                    LookupResult::Callable(LookupResultCallable::MemberFunction {
1145                        base: self.0.clone(),
1146                        base_node: ctx.current_token.clone(), // Note that this is not the base_node, but the function's node
1147                        member: Box::new(LookupResultCallable::Callable(Callable::Builtin(f))),
1148                    })
1149                };
1150                None.or_else(|| f("sin", member_function(BuiltinFunction::Sin)))
1151                    .or_else(|| f("cos", member_function(BuiltinFunction::Cos)))
1152                    .or_else(|| f("tan", member_function(BuiltinFunction::Tan)))
1153            })
1154    }
1155}