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