microcad_lang/resolve/
lookup.rs

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