Skip to main content

microcad_lang/resolve/
lookup.rs

1// Copyright © 2025-2026 The µcad authors <info@microcad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4use crate::{
5    resolve::*,
6    symbol::{Symbol, SymbolDef},
7    syntax::*,
8};
9
10/// Target of symbol to look up.
11#[derive(Clone, Copy)]
12pub enum LookupTarget {
13    /// Lookup for any symbol
14    Any,
15
16    /// Lookup for everything but a method
17    AnyButMethod,
18
19    /// Lookup for methods only
20    Method,
21    /// Lookup for functions only
22    Function,
23    /// Lookup for modules only
24    Module,
25    /// Lookup for constants and const expressions only
26    Value,
27    /// Lookup for use-all and aliases only
28    Link,
29}
30
31impl LookupTarget {
32    pub(crate) fn matches(&self, symbol: &Symbol) -> bool {
33        symbol.with_def(|def| -> bool {
34            match &def {
35                SymbolDef::Root => unreachable!("<ROOT> cannot be matched"),
36                SymbolDef::SourceFile(..) | SymbolDef::Module(..) => {
37                    matches!(self, Self::Any | Self::AnyButMethod | Self::Module)
38                }
39                SymbolDef::Workbench(wd) => match *wd.kind {
40                    WorkbenchKind::Part | WorkbenchKind::Sketch => {
41                        matches!(self, Self::Any | Self::AnyButMethod | Self::Function)
42                    }
43                    WorkbenchKind::Operation => matches!(self, Self::Any | Self::Method),
44                },
45                SymbolDef::Function(..) => {
46                    matches!(self, Self::Any | Self::AnyButMethod | Self::Function)
47                }
48                SymbolDef::Builtin(b) => match b {
49                    crate::builtin::Builtin::Function(..) => {
50                        matches!(self, Self::Any | Self::AnyButMethod | Self::Function)
51                    }
52                    crate::builtin::Builtin::Workbench(bwk) => match bwk.kind {
53                        crate::builtin::BuiltinWorkbenchKind::Primitive2D
54                        | crate::builtin::BuiltinWorkbenchKind::Primitive3D => {
55                            matches!(self, Self::Any | Self::AnyButMethod | Self::Function)
56                        }
57                        crate::builtin::BuiltinWorkbenchKind::Transform
58                        | crate::builtin::BuiltinWorkbenchKind::Operation => {
59                            matches!(self, Self::Any | Self::Method)
60                        }
61                    },
62                    &crate::builtin::Builtin::Constant(_) => {
63                        matches!(self, Self::Any | Self::AnyButMethod | Self::Value)
64                    }
65                },
66                SymbolDef::Value(..) | SymbolDef::Assignment(..) => {
67                    matches!(self, Self::Any | Self::AnyButMethod | Self::Value)
68                }
69                SymbolDef::Alias(..) | SymbolDef::UseAll(..) => {
70                    matches!(self, Self::Any | Self::AnyButMethod | Self::Link)
71                }
72                #[cfg(test)]
73                SymbolDef::Tester(..) => todo!(),
74            }
75        })
76    }
77}
78
79impl std::fmt::Display for LookupTarget {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        match self {
82            LookupTarget::Any => write!(f, "any symbol"),
83            LookupTarget::AnyButMethod => write!(f, "any symbol but a method"),
84            LookupTarget::Method => write!(f, "method"),
85            LookupTarget::Function => write!(f, "function"),
86            LookupTarget::Module => write!(f, "module"),
87            LookupTarget::Value => write!(f, "value"),
88            LookupTarget::Link => write!(f, "link"),
89        }
90    }
91}
92
93/// Trait to lookup symbols by *qualified name*.
94pub trait Lookup<E: std::error::Error = ResolveError> {
95    /// Search a *symbol* by it's *qualified name*.
96    /// # Arguments
97    /// - `name`: *Qualified name* to search for.
98    /// - `target`: What to search for
99    fn lookup(&self, name: &QualifiedName, target: LookupTarget) -> Result<Symbol, E>;
100
101    /// Return an ambiguity error.
102    fn ambiguity_error(ambiguous: QualifiedName, others: QualifiedNames) -> E;
103
104    /// Search a *symbol* by it's *qualified name* **and** within the given *symbol*.
105    ///
106    /// # Arguments
107    /// - `name`: *Qualified name* to search for.
108    /// - `within`: Searches within this *symbol* too.
109    /// - `target`: What to search for
110    /// # Return
111    /// If both are found and one is an *alias* returns the other one.
112    fn lookup_within(
113        &self,
114        name: &QualifiedName,
115        within: &Symbol,
116        target: LookupTarget,
117    ) -> Result<Symbol, E> {
118        log::trace!(
119            "{lookup} for symbol '{name:?}' within '{within}'",
120            within = within.full_name(),
121            lookup = microcad_lang_base::mark!(LOOKUP)
122        );
123        match (self.lookup(name, target), within.search(name, true)) {
124            // found both
125            (Ok(global), Ok(relative)) => {
126                // check if one is an alias of the other
127                match (global.is_alias(), relative.is_alias()) {
128                    (true, false) => Ok(relative),
129                    (false, true) => Ok(global),
130                    (true, true) => unreachable!("found two aliases"),
131                    (false, false) => {
132                        if relative == global {
133                            Ok(global)
134                        } else {
135                            Err(Self::ambiguity_error(
136                                relative.full_name(),
137                                [global.full_name()].into_iter().collect(),
138                            ))
139                        }
140                    }
141                }
142            }
143            // found one
144            (Ok(symbol), Err(_)) | (Err(_), Ok(symbol)) => {
145                log::trace!(
146                    "{found} symbol '{name:?}' within '{within}'",
147                    within = within.full_name(),
148                    found = microcad_lang_base::mark!(FOUND)
149                );
150                Ok(symbol)
151            }
152            // found nothing
153            (Err(err), Err(_)) => {
154                log::trace!(
155                    "{not_found} symbol '{name:?}' within '{within}'",
156                    within = within.full_name(),
157                    not_found = microcad_lang_base::mark!(NOT_FOUND)
158                );
159                Err(err)
160            }
161        }
162    }
163
164    /// Search a *symbol* by it's *qualified name* **and** within a given *symbol*
165    ///
166    /// # Arguments
167    /// - `name`: *qualified name* to search for
168    /// - `within`: If some, searches within this *symbol* too.
169    /// - `target`: What to search for
170    /// # Return
171    /// If both are found and one is an *alias* returns the other one.
172    fn lookup_within_opt(
173        &self,
174        name: &QualifiedName,
175        within: &Option<Symbol>,
176        target: LookupTarget,
177    ) -> Result<Symbol, E> {
178        if let Some(within) = within {
179            self.lookup_within(name, within, target)
180        } else {
181            self.lookup(name, target)
182        }
183    }
184
185    /// Returns an error if name starts with `super::`.
186    fn deny_super(&self, name: &QualifiedName) -> ResolveResult<()> {
187        if name.count_super() > 0 {
188            log::trace!(
189                "{not_found} '{name:?}' is not canonical",
190                not_found = microcad_lang_base::mark!(NOT_FOUND),
191            );
192            return Err(ResolveError::SymbolNotFound(name.clone()));
193        }
194        Ok(())
195    }
196}