ryo_mutations/analyzer/mod.rs
1//! rust-analyzer Integration: LSP-based code intelligence for mutations
2//!
3//! This module provides integration with [rust-analyzer](https://rust-analyzer.github.io/)
4//! via the Language Server Protocol (LSP). It enables semantic-aware code transformations
5//! that require type information beyond what pure AST analysis can provide.
6//!
7//! # Architecture
8//!
9//! ```text
10//! ┌─────────────────────────────────────────────────────────────────┐
11//! │ rust-analyzer Integration │
12//! ├─────────────────────────────────────────────────────────────────┤
13//! │ │
14//! │ ┌──────────────┐ LSP Protocol ┌──────────────────────┐ │
15//! │ │ ryo-mutations│◄──────────────────►│ rust-analyzer │ │
16//! │ │ │ │ │ │
17//! │ │ - Mutations │ textDocument/ │ - Type inference │ │
18//! │ │ - PureFile │ inlayHint │ - Semantic tokens │ │
19//! │ │ │ textDocument/ │ - Code actions │ │
20//! │ │ │ codeAction │ - Diagnostics │ │
21//! │ └──────────────┘ └──────────────────────┘ │
22//! │ │ │ │
23//! │ ▼ ▼ │
24//! │ ┌──────────────────────────────────────────────────────────┐ │
25//! │ │ Enriched Mutations │ │
26//! │ │ │ │
27//! │ │ - CloneOnCopy with accurate Copy detection │ │
28//! │ │ - (v0.1.0) AddExplicitType / FillMatchArms / │ │
29//! │ │ AddMissingFields are NOT YET shipped — type-inference │ │
30//! │ │ infrastructure required │ │
31//! │ └──────────────────────────────────────────────────────────┘ │
32//! └─────────────────────────────────────────────────────────────────┘
33//! ```
34//!
35//! # LSP Endpoints Used
36//!
37//! ## InlayHints (`textDocument/inlayHint`)
38//!
39//! Provides type information that the compiler infers but isn't written in code:
40//!
41//! | Hint Kind | Example | Use Case |
42//! |-----------|---------|----------|
43//! | Type | `let x: i32 = ...` | Add explicit type annotations |
44//! | Parameter | `foo(/* name: */ value)` | Parameter name completion |
45//! | Chaining | `.map(...): Vec<_>` | Method chain type tracking |
46//!
47//! ## CodeAction (`textDocument/codeAction`)
48//!
49//! rust-analyzer "assists" that can be converted to Mutations:
50//!
51//! | Assist | Mutation | Description |
52//! |--------|----------|-------------|
53//! | `fill_match_arms` | _(deferred — needs type inference)_ | Add missing match arms |
54//! | `add_missing_fields` | _(deferred — needs type inference)_ | Add missing struct fields |
55//! | `inline_type_alias` | `InlineTypeAliasMutation` | Expand type alias |
56//! | `extract_type_alias` | `ExtractTypeAliasMutation` | Create type alias |
57//! | `add_explicit_type` | _(deferred — needs type inference)_ | Add type annotation |
58//! | `replace_if_let_with_match` | `IfLetToMatchMutation` | Convert if let to match |
59//! | `replace_match_with_if_let` | `MatchToIfLetMutation` | Convert match to if let |
60//!
61//! `fill_match_arms` / `add_missing_fields` / `add_explicit_type` derivations
62//! were removed in v0.1.0 because their AST-level apply paths required
63//! type-inference infrastructure that has not yet landed; they will return
64//! when that infrastructure is in place.
65//!
66//! ## Hover (`textDocument/hover`)
67//!
68//! Type information and documentation for symbols.
69//!
70//! ## SemanticTokens (`textDocument/semanticTokens`)
71//!
72//! Token classification for semantic highlighting:
73//! - Variable modifiers: `mutable`, `consuming`, `reference`
74//! - Type modifiers: `copy`, `associated`
75//!
76//! # Usage
77//!
78//! ```rust,ignore
79//! use ryo_mutations::analyzer::{AnalyzerClient, InlayHintQuery};
80//!
81//! // Connect to rust-analyzer
82//! let client = AnalyzerClient::new("/path/to/project")?;
83//!
84//! // Get type hints for a file
85//! let hints = client.inlay_hints("src/lib.rs")?;
86//!
87//! // Use hints to enrich mutations
88//! for hint in hints.type_hints() {
89//! if hint.is_copy_type() {
90//! // Can safely apply CloneOnCopy mutation
91//! }
92//! }
93//!
94//! // Get available code actions at a position
95//! let actions = client.code_actions("src/lib.rs", line, column)?;
96//!
97//! // Convert to mutations
98//! for action in actions {
99//! if let Some(mutation) = action.to_mutation() {
100//! println!("Available: {}", mutation.describe());
101//! }
102//! }
103//! ```
104//!
105//! # Relationship with Clippy Integration
106//!
107//! | Feature | Clippy | rust-analyzer |
108//! |---------|--------|---------------|
109//! | Type info | No | Yes (InlayHints) |
110//! | Lint fixes | Yes (suggestions) | Yes (CodeActions) |
111//! | Refactoring | Limited | Extensive (assists) |
112//! | Batch apply | Yes | Per-file |
113//!
114//! For best results, combine both:
115//! - Use Clippy for lint-driven transformations
116//! - Use rust-analyzer for semantic-aware refactoring
117//!
118//! # Performance Considerations
119//!
120//! - rust-analyzer startup can take several seconds for large projects
121//! - InlayHints are computed lazily, request only needed ranges
122//! - Consider caching results for repeated operations
123//!
124//! # Future Extensions
125//!
126//! - [ ] Workspace-wide rename with type checking
127//! - [ ] Automatic import resolution
128//! - [ ] Trait implementation scaffolding
129//! - [ ] Lifetime inference and annotation
130
131mod code_action;
132mod inlay_hints;
133
134pub use code_action::{AnalyzerCodeAction, CodeActionKind, CodeActionToMutation};
135pub use inlay_hints::{InlayHint, InlayHintKind, InlayHintQuery, TypeHint};
136
137use std::path::Path;
138
139/// Known rust-analyzer assist IDs that map to mutations
140pub mod assists {
141 pub const FILL_MATCH_ARMS: &str = "fill_match_arms";
142 pub const ADD_MISSING_FIELDS: &str = "add_missing_impl_members";
143 pub const ADD_EXPLICIT_TYPE: &str = "add_explicit_type";
144 pub const INLINE_TYPE_ALIAS: &str = "inline_type_alias";
145 pub const EXTRACT_TYPE_ALIAS: &str = "extract_type_alias";
146 pub const REPLACE_IF_LET_WITH_MATCH: &str = "replace_if_let_with_match";
147 pub const REPLACE_MATCH_WITH_IF_LET: &str = "replace_match_with_if_let";
148 pub const CONVERT_TO_GUARDED_RETURN: &str = "convert_to_guarded_return";
149 pub const INLINE_FUNCTION: &str = "inline_into_callers";
150 pub const EXTRACT_FUNCTION: &str = "extract_function";
151}
152
153/// Common Copy types for heuristic detection
154pub mod copy_types {
155 /// Primitive types that are always Copy
156 pub const PRIMITIVES: &[&str] = &[
157 "bool", "char", "i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32", "u64",
158 "u128", "usize", "f32", "f64",
159 ];
160
161 /// Common std types that are Copy
162 pub const STD_COPY: &[&str] = &[
163 "NonZeroI8",
164 "NonZeroI16",
165 "NonZeroI32",
166 "NonZeroI64",
167 "NonZeroI128",
168 "NonZeroIsize",
169 "NonZeroU8",
170 "NonZeroU16",
171 "NonZeroU32",
172 "NonZeroU64",
173 "NonZeroU128",
174 "NonZeroUsize",
175 "Duration",
176 "Instant",
177 "IpAddr",
178 "Ipv4Addr",
179 "Ipv6Addr",
180 "SocketAddr",
181 "SocketAddrV4",
182 "SocketAddrV6",
183 "Ordering",
184 "TypeId",
185 ];
186
187 /// Check if a type name is a known Copy type
188 pub fn is_known_copy(type_name: &str) -> bool {
189 // Strip references and generics for basic check
190 let base = type_name
191 .trim_start_matches('&')
192 .trim_start_matches("mut ")
193 .split('<')
194 .next()
195 .unwrap_or(type_name)
196 .trim();
197
198 PRIMITIVES.contains(&base) || STD_COPY.contains(&base)
199 }
200}
201
202/// Client for communicating with rust-analyzer
203///
204/// This is a lightweight wrapper that can use an existing LSP connection
205/// or spawn a new rust-analyzer instance.
206pub struct AnalyzerClient {
207 workspace_root: std::path::PathBuf,
208 // In the future, this will hold the LSP connection
209 // For now, we provide utilities that work with LSP responses
210}
211
212impl AnalyzerClient {
213 /// Create a new analyzer client for a workspace
214 pub fn new(workspace_root: impl AsRef<Path>) -> Self {
215 Self {
216 workspace_root: workspace_root.as_ref().to_path_buf(),
217 }
218 }
219
220 /// Get the workspace root
221 pub fn workspace_root(&self) -> &Path {
222 &self.workspace_root
223 }
224
225 /// Check if a type is Copy based on known patterns
226 ///
227 /// This is a heuristic check. For accurate results, use InlayHints
228 /// with actual type information from rust-analyzer.
229 pub fn is_likely_copy(&self, type_name: &str) -> bool {
230 copy_types::is_known_copy(type_name)
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 #[test]
239 fn test_is_known_copy_primitives() {
240 assert!(copy_types::is_known_copy("i32"));
241 assert!(copy_types::is_known_copy("bool"));
242 assert!(copy_types::is_known_copy("f64"));
243 assert!(copy_types::is_known_copy("usize"));
244 }
245
246 #[test]
247 fn test_is_known_copy_with_reference() {
248 assert!(copy_types::is_known_copy("&i32"));
249 assert!(copy_types::is_known_copy("&mut bool"));
250 }
251
252 #[test]
253 fn test_is_known_copy_std_types() {
254 assert!(copy_types::is_known_copy("Duration"));
255 assert!(copy_types::is_known_copy("Ordering"));
256 }
257
258 #[test]
259 fn test_is_not_copy() {
260 assert!(!copy_types::is_known_copy("String"));
261 assert!(!copy_types::is_known_copy("Vec<i32>"));
262 assert!(!copy_types::is_known_copy("Box<dyn Trait>"));
263 }
264}