Skip to main content

microcad_lang/resolve/
resolve_error.rs

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