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