Skip to main content

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