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}