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