Skip to main content

microcad_lang/resolve/
resolve_error.rs

1// Copyright © 2025-2026 The µcad authors <info@microcad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4#![allow(unused_assignments)]
5//! Resolve error
6
7use miette::{Diagnostic, SourceSpan};
8use thiserror::Error;
9
10use crate::resolve::grant::Scope;
11use crate::{parse::*, syntax::*};
12use microcad_lang_base::{DiagError, SrcRef, SrcReferrer};
13
14fn capitalize_first(s: &str) -> String {
15    let mut c = s.chars();
16    match c.next() {
17        None => String::new(),
18        Some(first) => first.to_uppercase().collect::<String>() + c.as_str(),
19    }
20}
21
22/// Resolve error.
23#[derive(Debug, Error, Diagnostic)]
24pub enum ResolveError {
25    /// Parse Error.
26    #[error("Parse Error: {0}")]
27    #[diagnostic(transparent)]
28    ParseError(#[from] ParseErrorsWithSource),
29
30    /// Can't find a project file by hash.
31    #[error("Could not find a file with hash {0}")]
32    UnknownHash(u64),
33
34    /// Hash is zero
35    #[error("Hash is zero")]
36    NulHash,
37
38    /// Name of external symbol is unknown.
39    #[error("External symbol `{0}` not found")]
40    ExternalSymbolNotFound(QualifiedName),
41
42    /// Path of external file is unknown.
43    #[error("External path `{0}` not found")]
44    ExternalPathNotFound(std::path::PathBuf),
45
46    /// Can't find a project file by it's path.
47    #[error("Could not find a file with path {0}")]
48    FileNotFound(std::path::PathBuf),
49
50    /// Symbol not found.
51    #[error("Symbol {0} not found while resolving.")]
52    SymbolNotFound(QualifiedName),
53
54    /// Symbol not found (retry to load from external).
55    #[error("Symbol {0} must be loaded from {1}")]
56    SymbolMustBeLoaded(QualifiedName, std::path::PathBuf),
57
58    /// Symbol is not a value
59    #[error("Symbol {0} is not a value")]
60    NotAValue(QualifiedName),
61
62    /// Sternal module file not found
63    #[error("Ambiguous external module files found {0:?}")]
64    AmbiguousExternals(Vec<std::path::PathBuf>),
65
66    /// Ambiguous symbol was found
67    #[error("Symbol {0} already defined")]
68    SymbolAlreadyDefined(QualifiedName),
69
70    /// Ambiguous symbol was found
71    #[error("Ambiguous symbol found: {0}")]
72    AmbiguousSymbol(QualifiedName, QualifiedNames),
73
74    /// Ambiguous symbol was found
75    #[error("Ambiguous identifier '{ambiguous}'")]
76    #[allow(missing_docs)]
77    AmbiguousId {
78        #[label(primary, "First usage of '{first}'")]
79        first: Identifier,
80        #[label("Ambiguous usage of '{ambiguous}'")]
81        ambiguous: Identifier,
82    },
83
84    /// ScanDir Error
85    #[error("{0}")]
86    ScanDirError(#[from] scan_dir::Error),
87
88    /// Invalid path.
89    #[error("Invalid path: {0:?}")]
90    InvalidPath(std::path::PathBuf),
91
92    /// Diagnostic error
93    #[error("Diagnostic error: {0}")]
94    DiagError(#[from] DiagError),
95
96    /// Statement is not supported in this context.
97    #[error(transparent)]
98    #[diagnostic(transparent)]
99    StatementNotSupported(#[from] StatementNotSupportedError),
100
101    /// Resolve check failed
102    #[error("Resolve failed: {0}")]
103    ResolveCheckFailed(SrcRef),
104
105    /// Symbol is private
106    #[error("Symbol {0} is private")]
107    SymbolIsPrivate(QualifiedName),
108
109    /// ScanDir Error
110    #[error("{0}")]
111    IoError(#[from] std::io::Error),
112
113    /// Invalid path.
114    #[error(
115        "Source of module '{0}' could not be found in {1:?} (expecting a file '{0}.µcad' or '{0}/mod.µcad')"
116    )]
117    SourceFileNotFound(#[label("module not found")] Identifier, std::path::PathBuf),
118
119    /// Wrong lookup target
120    #[error("Wrong lookup target")]
121    WrongTarget,
122
123    /// Statement not allowed within workbenches
124    #[error("Statement not allowed within workbenches")]
125    IllegalWorkbenchStatement,
126
127    /// Code Between initializers
128    #[error("Code between initializers is not allowed")]
129    #[allow(missing_docs)]
130    CodeBetweenInitializers {
131        #[label("Between these initializers")]
132        initializers: SrcRef,
133        #[label(primary, "This statement is not allowed")]
134        statement: SrcRef,
135        #[label("Inside this {scope}")]
136        workbench: SrcRef,
137        scope: &'static str,
138    },
139
140    /// Statement not allowed prior initializers
141    #[error("Statement not allowed prior initializers")]
142    #[allow(missing_docs)]
143    StatementNotAllowedPriorInitializers {
144        #[label("Before this initializer")]
145        initializer: SrcRef,
146        #[label(primary, "This statement is not allowed")]
147        statement: SrcRef,
148        #[label("Inside this {scope}")]
149        workbench: SrcRef,
150        scope: &'static str,
151    },
152}
153
154/// Statement is not supported in this context.
155#[derive(Debug, Error, Diagnostic)]
156#[error("{} is not allowed {} {}", capitalize_first(inner), self.placement(), self.outer())]
157#[diagnostic(help("{inner} is only allowed within {}", self.allowed_parents()))]
158pub struct StatementNotSupportedError {
159    inner: &'static str,
160    #[label(primary, "This {inner} is not allowed{}", self.maybe_here())]
161    inner_span: SrcRef,
162    outer: &'static str,
163    #[label("Within this {outer}")]
164    outer_span: Option<SourceSpan>,
165    allowed_parents: Vec<&'static str>,
166}
167
168impl StatementNotSupportedError {
169    /// Create an error from inner node name, src_ref and parent
170    pub(super) fn new(node: &Scope, parent: &Scope) -> Self {
171        StatementNotSupportedError {
172            inner: node.to_str(),
173            inner_span: node.src_ref(),
174            outer: parent.to_str(),
175            outer_span: parent.src_ref().as_miette_span(),
176            allowed_parents: node
177                .ty()
178                .allowed_parents()
179                .iter()
180                .map(|scope| scope.to_str())
181                .collect(),
182        }
183    }
184
185    fn allowed_parents(&self) -> impl std::fmt::Display {
186        struct AllowedParents(Vec<&'static str>);
187
188        impl std::fmt::Display for AllowedParents {
189            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190                let mut items = self.0.iter();
191                if let Some(first) = items.next() {
192                    write!(f, "{first}")?;
193                }
194                let last = items.next_back();
195                for item in items {
196                    write!(f, ", {item}")?;
197                }
198                if let Some(last) = last {
199                    write!(f, " or {last}")?;
200                }
201                Ok(())
202            }
203        }
204
205        AllowedParents(self.allowed_parents.clone())
206    }
207
208    fn parent_is_root(&self) -> bool {
209        self.outer == "source file"
210    }
211
212    fn placement(&self) -> &'static str {
213        if self.parent_is_root() {
214            "at"
215        } else {
216            "within"
217        }
218    }
219
220    fn outer(&self) -> &'static str {
221        if self.parent_is_root() {
222            "source root"
223        } else {
224            self.outer
225        }
226    }
227
228    fn maybe_here(&self) -> &'static str {
229        if self.parent_is_root() { " here" } else { "" }
230    }
231}
232
233impl SrcReferrer for ResolveError {
234    fn src_ref(&self) -> SrcRef {
235        match self {
236            ResolveError::SourceFileNotFound(identifier, _) => identifier.src_ref(),
237            ResolveError::ParseError(parse_error) => parse_error.src_ref(),
238            ResolveError::ResolveCheckFailed(src_ref) => src_ref.clone(),
239            _ => SrcRef(None),
240        }
241    }
242}
243
244/// Result type of any resolve.
245pub type ResolveResult<T> = std::result::Result<T, ResolveError>;