Skip to main content

ryo_symbol/
error.rs

1//! Error types for ryo-symbol
2
3use std::path::PathBuf;
4use thiserror::Error;
5use uuid::Uuid;
6
7use crate::id::SymbolId;
8use crate::kind::SymbolKind;
9use crate::path::SymbolPath;
10
11/// SymbolPath parse error
12#[derive(Debug, Clone, Error)]
13pub enum ParseError {
14    /// The input string was empty (or contained only separators).
15    #[error("empty path")]
16    Empty,
17
18    /// A path segment was not a valid Rust identifier. Carries the offending
19    /// segment.
20    #[error("invalid identifier: {0}")]
21    InvalidIdentifier(String),
22
23    /// The overall path layout was malformed (e.g. leading/trailing `::`,
24    /// empty segment between `::`). Carries the offending input.
25    #[error("invalid path format: {0}")]
26    InvalidFormat(String),
27
28    #[error(
29        "semantic keyword '{0}' not allowed in SymbolPath\n\n\
30        SymbolPath requires canonical (absolute) paths. Context-dependent keywords like 'crate', 'self', 'super' \
31        cannot be used because they depend on the current module context.\n\n\
32        Why canonical paths?\n\
33        - Rust's AST uses relative keywords (crate::, self::, super::)\n\
34        - Ryo's SymbolPath uses absolute paths for:\n\
35          • Fast lookup in SymbolRegistry (HashMap key)\n\
36          • Unambiguous mutation targeting across files\n\
37          • Efficient AST updates without context re-resolution\n\n\
38        Solutions:\n\
39        1. In tests: Use actual crate name\n\
40           ✗ SymbolPath::parse(\"crate::Config\")\n\
41           ✓ SymbolPath::parse(\"test_crate::Config\")\n\n\
42        2. In implementation: Resolve context first\n\
43           • Use SymbolResolver to resolve 'crate' to actual crate name\n\
44           • Get canonical path from AnalysisContext/SymbolRegistry\n\
45           • Pass canonical path from parent context\n\n\
46        3. Design consideration:\n\
47           If you cannot determine the canonical path at this point,\n\
48           refactor the API to receive it from a higher context that has the information."
49    )]
50    /// A context-dependent keyword (`crate`, `self`, `super`) appeared in a
51    /// path that requires canonical (absolute) form. Carries the offending
52    /// keyword; the variant's `#[error]` message walks the caller through
53    /// how to resolve to a canonical name.
54    SemanticKeyword(String),
55
56    /// The leading crate segment did not match any registered crate. Carries
57    /// the full path, the unrecognized crate segment, and a rendered list of
58    /// known crate names for diagnostics.
59    #[error("unknown crate '{crate_name}' in path '{path}'. Known crates: [{known}]")]
60    UnknownCrate {
61        /// Full SymbolPath input that triggered the error.
62        path: String,
63        /// The leading segment that did not match any known crate.
64        crate_name: String,
65        /// Comma-separated rendering of currently registered crate names.
66        known: String,
67    },
68}
69
70/// Symbol registration error
71#[derive(Debug, Clone, Error)]
72pub enum RegistrationError {
73    /// The supplied symbol path failed to parse; wraps the underlying
74    /// [`ParseError`].
75    #[error("invalid path: {0}")]
76    InvalidPath(#[from] ParseError),
77
78    /// A symbol with the same path was already registered with a different
79    /// [`SymbolKind`]; re-registration with a conflicting kind is rejected.
80    #[error("conflicting kind: {path} already registered as {existing:?}, got {new:?}")]
81    ConflictingKind {
82        /// The path that was being re-registered.
83        path: Box<SymbolPath>,
84        /// Kind already recorded in the registry.
85        existing: SymbolKind,
86        /// Kind requested by the new registration.
87        new: SymbolKind,
88    },
89
90    /// The declared parent symbol does not exist or is not a valid parent
91    /// for the symbol being registered.
92    #[error("invalid parent symbol")]
93    InvalidParent,
94
95    /// Two distinct UUIDs were observed for the same [`SymbolId`], which
96    /// must not happen under normal operation.
97    #[error("UUID conflict for {id:?}: existing={existing}, provided={provided}")]
98    UuidConflict {
99        /// The symbol whose UUID disagreed.
100        id: SymbolId,
101        /// UUID already stored in the registry.
102        existing: Uuid,
103        /// UUID provided by the new registration attempt.
104        provided: Uuid,
105    },
106}
107
108/// Re-export unregistration error
109#[derive(Debug, Clone, Error)]
110pub enum UnregisterReexportError {
111    /// The alias path was not found in the re-export registry.
112    #[error("alias path not found in re-export registry")]
113    NotFound,
114}
115
116/// Invalid SymbolId error
117///
118/// This is a **fatal error**. When encountered:
119/// - Immediately return Err and abort the current Tick
120/// - Propagate to upper LLM for re-planning
121/// - Never attempt to continue with corrupted state
122#[derive(Debug, Clone, Error)]
123#[error("invalid symbol id: {0:?}")]
124pub struct InvalidSymbolId(pub SymbolId);
125
126/// Symbol rename error
127#[derive(Debug, Clone, Error)]
128pub enum RenameError {
129    /// The supplied [`SymbolId`] does not exist in the registry.
130    #[error("invalid symbol id: {0:?}")]
131    InvalidId(SymbolId),
132
133    /// The target path is already occupied by another symbol, so the rename
134    /// would collide.
135    #[error("path already exists: {0}")]
136    PathExists(Box<SymbolPath>),
137}
138
139/// Path resolution error (for local paths like `crate::`, `self::`, `super::`)
140#[derive(Debug, Clone, Error)]
141pub enum ResolutionError {
142    /// The leading `crate` segment could not be resolved to an actual crate
143    /// name in the current context. Carries the path that triggered the
144    /// failure.
145    #[error("unresolved crate: {0}")]
146    UnresolvedCrate(String),
147
148    /// A `self` / inner module reference could not be resolved against the
149    /// active module hierarchy. Carries the unresolved path.
150    #[error("unresolved module: {0}")]
151    UnresolvedModule(String),
152
153    /// `super` was used at the crate root, where it has no parent to refer
154    /// to.
155    #[error("super at crate root")]
156    SuperAtRoot,
157
158    /// Resolution succeeded structurally but the named symbol does not
159    /// exist in the registry. Carries the offending path.
160    #[error("symbol not found: {0}")]
161    SymbolNotFound(String),
162
163    /// The symbol exists but no source-span information is available, so
164    /// the requested location cannot be produced.
165    #[error("no span info for symbol: {0}")]
166    NoSpanInfo(String),
167}
168
169/// WorkspacePathResolver error
170#[derive(Debug, Error)]
171pub enum ResolveError {
172    /// The requested path lives outside the active workspace root, so it
173    /// cannot be resolved to a crate-relative location.
174    #[error("path '{}' is outside workspace '{}'", path.display(), workspace.display())]
175    OutsideWorkspace {
176        /// Requested filesystem path.
177        path: PathBuf,
178        /// Workspace root the resolver is currently scoped to.
179        workspace: PathBuf,
180    },
181
182    /// The requested file does not exist on disk.
183    #[error("file not found: '{}'", .0.display())]
184    FileNotFound(PathBuf),
185
186    /// No crate in the workspace owns the requested path (e.g. the file is
187    /// inside the workspace root but not under any crate's `src/`).
188    #[error("crate not found for path: '{}'", .0.display())]
189    CrateNotFound(PathBuf),
190
191    /// Underlying I/O failure while walking the workspace.
192    #[error("io error: {0}")]
193    Io(#[from] std::io::Error),
194
195    /// Ambiguous `crate::` path in multi-crate workspace
196    ///
197    /// In a workspace with multiple crates, `crate::xxx` is ambiguous because
198    /// it's unclear which crate's `xxx` is being referred to.
199    #[error(
200        "ambiguous path '{path}' in multi-crate workspace\n\n\
201        In a workspace with multiple crates, 'crate::' prefix is ambiguous.\n\n\
202        Solutions:\n\
203        1. Specify the crate with -p flag: ryo run -p {example_crate_path} -f intent.json\n\
204        2. Use file path instead: \"parent\": \"{example_file_path}\"\n\
205        3. Use explicit crate name: \"parent\": \"{example_crate_name}::xxx\""
206    )]
207    AmbiguousCratePath {
208        /// The ambiguous path (e.g., "crate::domain")
209        path: String,
210        /// Example crate path for -p flag (e.g., "crates/core")
211        example_crate_path: String,
212        /// Example file path (e.g., "crates/core/src/domain.rs")
213        example_file_path: String,
214        /// Example crate name (e.g., "core")
215        example_crate_name: String,
216    },
217}