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