Skip to main content

microcad_lang/resolve/
lookup.rs

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