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