aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/hir/src/semantics.rs92
-rw-r--r--crates/hir/src/semantics/source_to_def.rs15
-rw-r--r--crates/hir_def/src/child_by_source.rs4
-rw-r--r--crates/hir_def/src/item_scope.rs17
-rw-r--r--crates/hir_def/src/keys.rs3
-rw-r--r--crates/hir_def/src/nameres/collector.rs5
-rw-r--r--crates/ide/src/expand_macro.rs34
-rw-r--r--crates/ide/src/syntax_highlighting.rs30
-rw-r--r--crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs352
9 files changed, 507 insertions, 45 deletions
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index c7f2c02e4..2d08a7704 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -117,6 +117,16 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
117 pub fn expand(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> { 117 pub fn expand(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> {
118 self.imp.expand(macro_call) 118 self.imp.expand(macro_call)
119 } 119 }
120
121 /// If `item` has an attribute macro attached to it, expands it.
122 pub fn expand_attr_macro(&self, item: &ast::Item) -> Option<SyntaxNode> {
123 self.imp.expand_attr_macro(item)
124 }
125
126 pub fn is_attr_macro_call(&self, item: &ast::Item) -> bool {
127 self.imp.is_attr_macro_call(item)
128 }
129
120 pub fn speculative_expand( 130 pub fn speculative_expand(
121 &self, 131 &self,
122 actual_macro_call: &ast::MacroCall, 132 actual_macro_call: &ast::MacroCall,
@@ -332,6 +342,22 @@ impl<'db> SemanticsImpl<'db> {
332 Some(node) 342 Some(node)
333 } 343 }
334 344
345 fn expand_attr_macro(&self, item: &ast::Item) -> Option<SyntaxNode> {
346 let sa = self.analyze(item.syntax());
347 let src = InFile::new(sa.file_id, item.clone());
348 let macro_call_id = self.with_ctx(|ctx| ctx.item_to_macro_call(src))?;
349 let file_id = macro_call_id.as_file();
350 let node = self.db.parse_or_expand(file_id)?;
351 self.cache(node.clone(), file_id);
352 Some(node)
353 }
354
355 fn is_attr_macro_call(&self, item: &ast::Item) -> bool {
356 let sa = self.analyze(item.syntax());
357 let src = InFile::new(sa.file_id, item.clone());
358 self.with_ctx(|ctx| ctx.item_to_macro_call(src).is_some())
359 }
360
335 fn speculative_expand( 361 fn speculative_expand(
336 &self, 362 &self,
337 actual_macro_call: &ast::MacroCall, 363 actual_macro_call: &ast::MacroCall,
@@ -362,25 +388,57 @@ impl<'db> SemanticsImpl<'db> {
362 388
363 let token = successors(Some(InFile::new(sa.file_id, token)), |token| { 389 let token = successors(Some(InFile::new(sa.file_id, token)), |token| {
364 self.db.unwind_if_cancelled(); 390 self.db.unwind_if_cancelled();
365 let macro_call = token.value.ancestors().find_map(ast::MacroCall::cast)?; 391
366 let tt = macro_call.token_tree()?; 392 for node in token.value.ancestors() {
367 if !tt.syntax().text_range().contains_range(token.value.text_range()) { 393 match_ast! {
368 return None; 394 match node {
369 } 395 ast::MacroCall(macro_call) => {
370 let file_id = sa.expand(self.db, token.with_value(&macro_call))?; 396 let tt = macro_call.token_tree()?;
371 let token = self 397 if !tt.syntax().text_range().contains_range(token.value.text_range()) {
372 .expansion_info_cache 398 return None;
373 .borrow_mut() 399 }
374 .entry(file_id) 400 let file_id = sa.expand(self.db, token.with_value(&macro_call))?;
375 .or_insert_with(|| file_id.expansion_info(self.db.upcast())) 401 let token = self
376 .as_ref()? 402 .expansion_info_cache
377 .map_token_down(token.as_ref())?; 403 .borrow_mut()
378 404 .entry(file_id)
379 if let Some(parent) = token.value.parent() { 405 .or_insert_with(|| file_id.expansion_info(self.db.upcast()))
380 self.cache(find_root(&parent), token.file_id); 406 .as_ref()?
407 .map_token_down(token.as_ref())?;
408
409 if let Some(parent) = token.value.parent() {
410 self.cache(find_root(&parent), token.file_id);
411 }
412
413 return Some(token);
414 },
415 ast::Item(item) => {
416 match self.with_ctx(|ctx| ctx.item_to_macro_call(token.with_value(item))) {
417 Some(call_id) => {
418 let file_id = call_id.as_file();
419 let token = self
420 .expansion_info_cache
421 .borrow_mut()
422 .entry(file_id)
423 .or_insert_with(|| file_id.expansion_info(self.db.upcast()))
424 .as_ref()?
425 .map_token_down(token.as_ref())?;
426
427 if let Some(parent) = token.value.parent() {
428 self.cache(find_root(&parent), token.file_id);
429 }
430
431 return Some(token);
432 }
433 None => {}
434 }
435 },
436 _ => {}
437 }
438 }
381 } 439 }
382 440
383 Some(token) 441 None
384 }) 442 })
385 .last() 443 .last()
386 .unwrap(); 444 .unwrap();
diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs
index 9a5a2255f..22e196196 100644
--- a/crates/hir/src/semantics/source_to_def.rs
+++ b/crates/hir/src/semantics/source_to_def.rs
@@ -10,7 +10,7 @@ use hir_def::{
10 ImplId, LifetimeParamId, ModuleId, StaticId, StructId, TraitId, TypeAliasId, TypeParamId, 10 ImplId, LifetimeParamId, ModuleId, StaticId, StructId, TraitId, TypeAliasId, TypeParamId,
11 UnionId, VariantId, 11 UnionId, VariantId,
12}; 12};
13use hir_expand::{name::AsName, AstId, MacroDefKind}; 13use hir_expand::{name::AsName, AstId, MacroCallId, MacroDefKind};
14use rustc_hash::FxHashMap; 14use rustc_hash::FxHashMap;
15use smallvec::SmallVec; 15use smallvec::SmallVec;
16use stdx::impl_from; 16use stdx::impl_from;
@@ -145,16 +145,25 @@ impl SourceToDefCtx<'_, '_> {
145 Some((container, label_id)) 145 Some((container, label_id))
146 } 146 }
147 147
148 pub(super) fn item_to_macro_call(&mut self, src: InFile<ast::Item>) -> Option<MacroCallId> {
149 let map = self.dyn_map(src.as_ref())?;
150 map[keys::ATTR_MACRO].get(&src).copied()
151 }
152
148 fn to_def<Ast: AstNode + 'static, ID: Copy + 'static>( 153 fn to_def<Ast: AstNode + 'static, ID: Copy + 'static>(
149 &mut self, 154 &mut self,
150 src: InFile<Ast>, 155 src: InFile<Ast>,
151 key: Key<Ast, ID>, 156 key: Key<Ast, ID>,
152 ) -> Option<ID> { 157 ) -> Option<ID> {
153 let container = self.find_container(src.as_ref().map(|it| it.syntax()))?; 158 self.dyn_map(src.as_ref())?[key].get(&src).copied()
159 }
160
161 fn dyn_map<Ast: AstNode + 'static>(&mut self, src: InFile<&Ast>) -> Option<&DynMap> {
162 let container = self.find_container(src.map(|it| it.syntax()))?;
154 let db = self.db; 163 let db = self.db;
155 let dyn_map = 164 let dyn_map =
156 &*self.cache.entry(container).or_insert_with(|| container.child_by_source(db)); 165 &*self.cache.entry(container).or_insert_with(|| container.child_by_source(db));
157 dyn_map[key].get(&src).copied() 166 Some(dyn_map)
158 } 167 }
159 168
160 pub(super) fn type_param_to_def(&mut self, src: InFile<ast::TypeParam>) -> Option<TypeParamId> { 169 pub(super) fn type_param_to_def(&mut self, src: InFile<ast::TypeParam>) -> Option<TypeParamId> {
diff --git a/crates/hir_def/src/child_by_source.rs b/crates/hir_def/src/child_by_source.rs
index f2e809ca9..f22383e22 100644
--- a/crates/hir_def/src/child_by_source.rs
+++ b/crates/hir_def/src/child_by_source.rs
@@ -85,6 +85,10 @@ impl ChildBySource for ItemScope {
85 res[keys::CONST].insert(src, konst); 85 res[keys::CONST].insert(src, konst);
86 }); 86 });
87 self.impls().for_each(|imp| add_impl(db, res, imp)); 87 self.impls().for_each(|imp| add_impl(db, res, imp));
88 self.attr_macro_invocs().for_each(|(ast_id, call_id)| {
89 let item = ast_id.with_value(ast_id.to_node(db.upcast()));
90 res[keys::ATTR_MACRO].insert(item, call_id);
91 });
88 92
89 fn add_module_def(db: &dyn DefDatabase, map: &mut DynMap, item: ModuleDefId) { 93 fn add_module_def(db: &dyn DefDatabase, map: &mut DynMap, item: ModuleDefId) {
90 match item { 94 match item {
diff --git a/crates/hir_def/src/item_scope.rs b/crates/hir_def/src/item_scope.rs
index 9014468ea..0f74f050d 100644
--- a/crates/hir_def/src/item_scope.rs
+++ b/crates/hir_def/src/item_scope.rs
@@ -4,11 +4,11 @@
4use std::collections::hash_map::Entry; 4use std::collections::hash_map::Entry;
5 5
6use base_db::CrateId; 6use base_db::CrateId;
7use hir_expand::name::Name; 7use hir_expand::{name::Name, AstId, MacroCallId, MacroDefKind};
8use hir_expand::MacroDefKind;
9use once_cell::sync::Lazy; 8use once_cell::sync::Lazy;
10use rustc_hash::{FxHashMap, FxHashSet}; 9use rustc_hash::{FxHashMap, FxHashSet};
11use stdx::format_to; 10use stdx::format_to;
11use syntax::ast;
12 12
13use crate::{ 13use crate::{
14 db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, ConstId, ImplId, 14 db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, ConstId, ImplId,
@@ -53,6 +53,7 @@ pub struct ItemScope {
53 // FIXME: Macro shadowing in one module is not properly handled. Non-item place macros will 53 // FIXME: Macro shadowing in one module is not properly handled. Non-item place macros will
54 // be all resolved to the last one defined if shadowing happens. 54 // be all resolved to the last one defined if shadowing happens.
55 legacy_macros: FxHashMap<Name, MacroDefId>, 55 legacy_macros: FxHashMap<Name, MacroDefId>,
56 attr_macros: FxHashMap<AstId<ast::Item>, MacroCallId>,
56} 57}
57 58
58pub(crate) static BUILTIN_SCOPE: Lazy<FxHashMap<Name, PerNs>> = Lazy::new(|| { 59pub(crate) static BUILTIN_SCOPE: Lazy<FxHashMap<Name, PerNs>> = Lazy::new(|| {
@@ -169,6 +170,16 @@ impl ItemScope {
169 self.legacy_macros.insert(name, mac); 170 self.legacy_macros.insert(name, mac);
170 } 171 }
171 172
173 pub(crate) fn add_attr_macro_invoc(&mut self, item: AstId<ast::Item>, call: MacroCallId) {
174 self.attr_macros.insert(item, call);
175 }
176
177 pub(crate) fn attr_macro_invocs(
178 &self,
179 ) -> impl Iterator<Item = (AstId<ast::Item>, MacroCallId)> + '_ {
180 self.attr_macros.iter().map(|(k, v)| (*k, *v))
181 }
182
172 pub(crate) fn unnamed_trait_vis(&self, tr: TraitId) -> Option<Visibility> { 183 pub(crate) fn unnamed_trait_vis(&self, tr: TraitId) -> Option<Visibility> {
173 self.unnamed_trait_imports.get(&tr).copied() 184 self.unnamed_trait_imports.get(&tr).copied()
174 } 185 }
@@ -307,6 +318,7 @@ impl ItemScope {
307 unnamed_consts, 318 unnamed_consts,
308 unnamed_trait_imports, 319 unnamed_trait_imports,
309 legacy_macros, 320 legacy_macros,
321 attr_macros,
310 } = self; 322 } = self;
311 types.shrink_to_fit(); 323 types.shrink_to_fit();
312 values.shrink_to_fit(); 324 values.shrink_to_fit();
@@ -317,6 +329,7 @@ impl ItemScope {
317 unnamed_consts.shrink_to_fit(); 329 unnamed_consts.shrink_to_fit();
318 unnamed_trait_imports.shrink_to_fit(); 330 unnamed_trait_imports.shrink_to_fit();
319 legacy_macros.shrink_to_fit(); 331 legacy_macros.shrink_to_fit();
332 attr_macros.shrink_to_fit();
320 } 333 }
321} 334}
322 335
diff --git a/crates/hir_def/src/keys.rs b/crates/hir_def/src/keys.rs
index 89b3ed868..688cd9fcf 100644
--- a/crates/hir_def/src/keys.rs
+++ b/crates/hir_def/src/keys.rs
@@ -2,7 +2,7 @@
2 2
3use std::marker::PhantomData; 3use std::marker::PhantomData;
4 4
5use hir_expand::{InFile, MacroDefId}; 5use hir_expand::{InFile, MacroCallId, MacroDefId};
6use rustc_hash::FxHashMap; 6use rustc_hash::FxHashMap;
7use syntax::{ast, AstNode, AstPtr}; 7use syntax::{ast, AstNode, AstPtr};
8 8
@@ -32,6 +32,7 @@ pub const LIFETIME_PARAM: Key<ast::LifetimeParam, LifetimeParamId> = Key::new();
32pub const CONST_PARAM: Key<ast::ConstParam, ConstParamId> = Key::new(); 32pub const CONST_PARAM: Key<ast::ConstParam, ConstParamId> = Key::new();
33 33
34pub const MACRO: Key<ast::MacroCall, MacroDefId> = Key::new(); 34pub const MACRO: Key<ast::MacroCall, MacroDefId> = Key::new();
35pub const ATTR_MACRO: Key<ast::Item, MacroCallId> = Key::new();
35 36
36/// XXX: AST Nodes and SyntaxNodes have identity equality semantics: nodes are 37/// XXX: AST Nodes and SyntaxNodes have identity equality semantics: nodes are
37/// equal if they point to exactly the same object. 38/// equal if they point to exactly the same object.
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index d0b1db5d1..d019ba3a9 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -1112,6 +1112,11 @@ impl DefCollector<'_> {
1112 return false; 1112 return false;
1113 } 1113 }
1114 } 1114 }
1115
1116 self.def_map.modules[directive.module_id]
1117 .scope
1118 .add_attr_macro_invoc(ast_id.ast_id, call_id);
1119
1115 resolved.push((directive.module_id, call_id, directive.depth)); 1120 resolved.push((directive.module_id, call_id, directive.depth));
1116 res = ReachedFixedPoint::No; 1121 res = ReachedFixedPoint::No;
1117 return false; 1122 return false;
diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs
index e0d01fa96..3f38e2145 100644
--- a/crates/ide/src/expand_macro.rs
+++ b/crates/ide/src/expand_macro.rs
@@ -3,8 +3,7 @@ use std::iter;
3use hir::Semantics; 3use hir::Semantics;
4use ide_db::RootDatabase; 4use ide_db::RootDatabase;
5use syntax::{ 5use syntax::{
6 algo::find_node_at_offset, ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxKind::*, 6 ast, match_ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxKind::*, SyntaxNode, WalkEvent, T,
7 SyntaxNode, WalkEvent, T,
8}; 7};
9 8
10use crate::FilePosition; 9use crate::FilePosition;
@@ -28,16 +27,37 @@ pub struct ExpandedMacro {
28pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { 27pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> {
29 let sema = Semantics::new(db); 28 let sema = Semantics::new(db);
30 let file = sema.parse(position.file_id); 29 let file = sema.parse(position.file_id);
31 let mac = find_node_at_offset::<ast::MacroCall>(file.syntax(), position.offset)?;
32 let name = mac.path()?.segment()?.name_ref()?;
33 30
34 let expanded = expand_macro_recur(&sema, &mac)?; 31 let tok = file.syntax().token_at_offset(position.offset).left_biased()?;
32 let mut expanded = None;
33 let mut name = None;
34 for node in tok.ancestors() {
35 match_ast! {
36 match node {
37 ast::MacroCall(mac) => {
38 name = Some(mac.path()?.segment()?.name_ref()?.to_string());
39 expanded = expand_macro_recur(&sema, &mac);
40 break;
41 },
42 ast::Item(item) => {
43 // FIXME: add the macro name
44 // FIXME: make this recursive too
45 name = Some("?".to_string());
46 expanded = sema.expand_attr_macro(&item);
47 if expanded.is_some() {
48 break;
49 }
50 },
51 _ => {}
52 }
53 }
54 }
35 55
36 // FIXME: 56 // FIXME:
37 // macro expansion may lose all white space information 57 // macro expansion may lose all white space information
38 // But we hope someday we can use ra_fmt for that 58 // But we hope someday we can use ra_fmt for that
39 let expansion = insert_whitespaces(expanded); 59 let expansion = insert_whitespaces(expanded?);
40 Some(ExpandedMacro { name: name.to_string(), expansion }) 60 Some(ExpandedMacro { name: name?, expansion })
41} 61}
42 62
43fn expand_macro_recur( 63fn expand_macro_recur(
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index 79c2f4a1e..b03f1c71f 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -192,6 +192,7 @@ fn traverse(
192 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); 192 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
193 193
194 let mut current_macro_call: Option<ast::MacroCall> = None; 194 let mut current_macro_call: Option<ast::MacroCall> = None;
195 let mut current_attr_macro_call = None;
195 let mut current_macro: Option<ast::Macro> = None; 196 let mut current_macro: Option<ast::Macro> = None;
196 let mut macro_highlighter = MacroHighlighter::default(); 197 let mut macro_highlighter = MacroHighlighter::default();
197 let mut inside_attribute = false; 198 let mut inside_attribute = false;
@@ -227,6 +228,19 @@ fn traverse(
227 } 228 }
228 _ => (), 229 _ => (),
229 } 230 }
231 match event.clone().map(|it| it.into_node().and_then(ast::Item::cast)) {
232 WalkEvent::Enter(Some(item)) => {
233 if sema.is_attr_macro_call(&item) {
234 current_attr_macro_call = Some(item);
235 }
236 }
237 WalkEvent::Leave(Some(item)) => {
238 if current_attr_macro_call == Some(item) {
239 current_attr_macro_call = None;
240 }
241 }
242 _ => (),
243 }
230 244
231 match event.clone().map(|it| it.into_node().and_then(ast::Macro::cast)) { 245 match event.clone().map(|it| it.into_node().and_then(ast::Macro::cast)) {
232 WalkEvent::Enter(Some(mac)) => { 246 WalkEvent::Enter(Some(mac)) => {
@@ -286,6 +300,22 @@ fn traverse(
286 } 300 }
287 None => token.into(), 301 None => token.into(),
288 } 302 }
303 } else if current_attr_macro_call.is_some() {
304 let token = match element.clone().into_token() {
305 Some(it) => it,
306 _ => continue,
307 };
308 let token = sema.descend_into_macros(token.clone());
309 match token.parent() {
310 Some(parent) => {
311 // We only care Name and Name_ref
312 match (token.kind(), parent.kind()) {
313 (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(),
314 _ => token.into(),
315 }
316 }
317 None => token.into(),
318 }
289 } else { 319 } else {
290 element.clone() 320 element.clone()
291 }; 321 };
diff --git a/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs
index 70949ca35..fc5a17f05 100644
--- a/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs
+++ b/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs
@@ -1,3 +1,4 @@
1use either::Either;
1use ide_db::defs::{Definition, NameRefClass}; 2use ide_db::defs::{Definition, NameRefClass};
2use syntax::{ 3use syntax::{
3 ast::{self, AstNode, GenericParamsOwner, VisibilityOwner}, 4 ast::{self, AstNode, GenericParamsOwner, VisibilityOwner},
@@ -8,7 +9,7 @@ use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind,
8 9
9// Assist: convert_tuple_struct_to_named_struct 10// Assist: convert_tuple_struct_to_named_struct
10// 11//
11// Converts tuple struct to struct with named fields. 12// Converts tuple struct to struct with named fields, and analogously for tuple enum variants.
12// 13//
13// ``` 14// ```
14// struct Point$0(f32, f32); 15// struct Point$0(f32, f32);
@@ -49,14 +50,21 @@ pub(crate) fn convert_tuple_struct_to_named_struct(
49 acc: &mut Assists, 50 acc: &mut Assists,
50 ctx: &AssistContext, 51 ctx: &AssistContext,
51) -> Option<()> { 52) -> Option<()> {
52 let strukt = ctx.find_node_at_offset::<ast::Struct>()?; 53 let strukt = ctx
53 let tuple_fields = match strukt.field_list()? { 54 .find_node_at_offset::<ast::Struct>()
55 .map(Either::Left)
56 .or_else(|| ctx.find_node_at_offset::<ast::Variant>().map(Either::Right))?;
57 let field_list = strukt.as_ref().either(|s| s.field_list(), |v| v.field_list())?;
58 let tuple_fields = match field_list {
54 ast::FieldList::TupleFieldList(it) => it, 59 ast::FieldList::TupleFieldList(it) => it,
55 ast::FieldList::RecordFieldList(_) => return None, 60 ast::FieldList::RecordFieldList(_) => return None,
56 }; 61 };
57 let strukt_def = ctx.sema.to_def(&strukt)?; 62 let strukt_def = match &strukt {
63 Either::Left(s) => Either::Left(ctx.sema.to_def(s)?),
64 Either::Right(v) => Either::Right(ctx.sema.to_def(v)?),
65 };
66 let target = strukt.as_ref().either(|s| s.syntax(), |v| v.syntax()).text_range();
58 67
59 let target = strukt.syntax().text_range();
60 acc.add( 68 acc.add(
61 AssistId("convert_tuple_struct_to_named_struct", AssistKind::RefactorRewrite), 69 AssistId("convert_tuple_struct_to_named_struct", AssistKind::RefactorRewrite),
62 "Convert to named struct", 70 "Convert to named struct",
@@ -73,7 +81,7 @@ pub(crate) fn convert_tuple_struct_to_named_struct(
73fn edit_struct_def( 81fn edit_struct_def(
74 ctx: &AssistContext, 82 ctx: &AssistContext,
75 edit: &mut AssistBuilder, 83 edit: &mut AssistBuilder,
76 strukt: &ast::Struct, 84 strukt: &Either<ast::Struct, ast::Variant>,
77 tuple_fields: ast::TupleFieldList, 85 tuple_fields: ast::TupleFieldList,
78 names: Vec<ast::Name>, 86 names: Vec<ast::Name>,
79) { 87) {
@@ -86,27 +94,40 @@ fn edit_struct_def(
86 94
87 edit.edit_file(ctx.frange.file_id); 95 edit.edit_file(ctx.frange.file_id);
88 96
89 if let Some(w) = strukt.where_clause() { 97 if let Either::Left(strukt) = strukt {
90 edit.delete(w.syntax().text_range()); 98 if let Some(w) = strukt.where_clause() {
91 edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_newline().text()); 99 edit.delete(w.syntax().text_range());
92 edit.insert(tuple_fields_text_range.start(), w.syntax().text()); 100 edit.insert(
93 edit.insert(tuple_fields_text_range.start(), ","); 101 tuple_fields_text_range.start(),
94 edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_newline().text()); 102 ast::make::tokens::single_newline().text(),
103 );
104 edit.insert(tuple_fields_text_range.start(), w.syntax().text());
105 edit.insert(tuple_fields_text_range.start(), ",");
106 edit.insert(
107 tuple_fields_text_range.start(),
108 ast::make::tokens::single_newline().text(),
109 );
110 } else {
111 edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text());
112 }
113 strukt.semicolon_token().map(|t| edit.delete(t.text_range()));
95 } else { 114 } else {
96 edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text()); 115 edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text());
97 } 116 }
98 117
99 edit.replace(tuple_fields_text_range, record_fields.to_string()); 118 edit.replace(tuple_fields_text_range, record_fields.to_string());
100 strukt.semicolon_token().map(|t| edit.delete(t.text_range()));
101} 119}
102 120
103fn edit_struct_references( 121fn edit_struct_references(
104 ctx: &AssistContext, 122 ctx: &AssistContext,
105 edit: &mut AssistBuilder, 123 edit: &mut AssistBuilder,
106 strukt: hir::Struct, 124 strukt: Either<hir::Struct, hir::Variant>,
107 names: &[ast::Name], 125 names: &[ast::Name],
108) { 126) {
109 let strukt_def = Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(strukt))); 127 let strukt_def = match strukt {
128 Either::Left(s) => Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(s))),
129 Either::Right(v) => Definition::ModuleDef(hir::ModuleDef::Variant(v)),
130 };
110 let usages = strukt_def.usages(&ctx.sema).include_self_refs().all(); 131 let usages = strukt_def.usages(&ctx.sema).include_self_refs().all();
111 132
112 let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> { 133 let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> {
@@ -513,4 +534,305 @@ where
513"#, 534"#,
514 ); 535 );
515 } 536 }
537 #[test]
538 fn not_applicable_other_than_tuple_variant() {
539 check_assist_not_applicable(
540 convert_tuple_struct_to_named_struct,
541 r#"enum Enum { Variant$0 { value: usize } };"#,
542 );
543 check_assist_not_applicable(
544 convert_tuple_struct_to_named_struct,
545 r#"enum Enum { Variant$0 }"#,
546 );
547 }
548
549 #[test]
550 fn convert_simple_variant() {
551 check_assist(
552 convert_tuple_struct_to_named_struct,
553 r#"
554enum A {
555 $0Variant(usize),
556}
557
558impl A {
559 fn new(value: usize) -> A {
560 A::Variant(value)
561 }
562
563 fn new_with_default() -> A {
564 A::new(Default::default())
565 }
566
567 fn value(self) -> usize {
568 match self {
569 A::Variant(value) => value,
570 }
571 }
572}"#,
573 r#"
574enum A {
575 Variant { field1: usize },
576}
577
578impl A {
579 fn new(value: usize) -> A {
580 A::Variant { field1: value }
581 }
582
583 fn new_with_default() -> A {
584 A::new(Default::default())
585 }
586
587 fn value(self) -> usize {
588 match self {
589 A::Variant { field1: value } => value,
590 }
591 }
592}"#,
593 );
594 }
595
596 #[test]
597 fn convert_variant_referenced_via_self_kw() {
598 check_assist(
599 convert_tuple_struct_to_named_struct,
600 r#"
601enum A {
602 $0Variant(usize),
603}
604
605impl A {
606 fn new(value: usize) -> A {
607 Self::Variant(value)
608 }
609
610 fn new_with_default() -> A {
611 Self::new(Default::default())
612 }
613
614 fn value(self) -> usize {
615 match self {
616 Self::Variant(value) => value,
617 }
618 }
619}"#,
620 r#"
621enum A {
622 Variant { field1: usize },
623}
624
625impl A {
626 fn new(value: usize) -> A {
627 Self::Variant { field1: value }
628 }
629
630 fn new_with_default() -> A {
631 Self::new(Default::default())
632 }
633
634 fn value(self) -> usize {
635 match self {
636 Self::Variant { field1: value } => value,
637 }
638 }
639}"#,
640 );
641 }
642
643 #[test]
644 fn convert_destructured_variant() {
645 check_assist(
646 convert_tuple_struct_to_named_struct,
647 r#"
648enum A {
649 $0Variant(usize),
650}
651
652impl A {
653 fn into_inner(self) -> usize {
654 let A::Variant(first) = self;
655 first
656 }
657
658 fn into_inner_via_self(self) -> usize {
659 let Self::Variant(first) = self;
660 first
661 }
662}"#,
663 r#"
664enum A {
665 Variant { field1: usize },
666}
667
668impl A {
669 fn into_inner(self) -> usize {
670 let A::Variant { field1: first } = self;
671 first
672 }
673
674 fn into_inner_via_self(self) -> usize {
675 let Self::Variant { field1: first } = self;
676 first
677 }
678}"#,
679 );
680 }
681
682 #[test]
683 fn convert_variant_with_wrapped_references() {
684 check_assist(
685 convert_tuple_struct_to_named_struct,
686 r#"
687enum Inner {
688 $0Variant(usize),
689}
690enum Outer {
691 Variant(Inner),
692}
693
694impl Outer {
695 fn new() -> Self {
696 Self::Variant(Inner::Variant(42))
697 }
698
699 fn into_inner_destructed(self) -> u32 {
700 let Outer::Variant(Inner::Variant(x)) = self;
701 x
702 }
703}"#,
704 r#"
705enum Inner {
706 Variant { field1: usize },
707}
708enum Outer {
709 Variant(Inner),
710}
711
712impl Outer {
713 fn new() -> Self {
714 Self::Variant(Inner::Variant { field1: 42 })
715 }
716
717 fn into_inner_destructed(self) -> u32 {
718 let Outer::Variant(Inner::Variant { field1: x }) = self;
719 x
720 }
721}"#,
722 );
723
724 check_assist(
725 convert_tuple_struct_to_named_struct,
726 r#"
727enum Inner {
728 Variant(usize),
729}
730enum Outer {
731 $0Variant(Inner),
732}
733
734impl Outer {
735 fn new() -> Self {
736 Self::Variant(Inner::Variant(42))
737 }
738
739 fn into_inner_destructed(self) -> u32 {
740 let Outer::Variant(Inner::Variant(x)) = self;
741 x
742 }
743}"#,
744 r#"
745enum Inner {
746 Variant(usize),
747}
748enum Outer {
749 Variant { field1: Inner },
750}
751
752impl Outer {
753 fn new() -> Self {
754 Self::Variant { field1: Inner::Variant(42) }
755 }
756
757 fn into_inner_destructed(self) -> u32 {
758 let Outer::Variant { field1: Inner::Variant(x) } = self;
759 x
760 }
761}"#,
762 );
763 }
764
765 #[test]
766 fn convert_variant_with_multi_file_references() {
767 check_assist(
768 convert_tuple_struct_to_named_struct,
769 r#"
770//- /main.rs
771struct Inner;
772enum A {
773 $0Variant(Inner),
774}
775
776mod foo;
777
778//- /foo.rs
779use crate::{A, Inner};
780fn f() {
781 let a = A::Variant(Inner);
782}
783"#,
784 r#"
785//- /main.rs
786struct Inner;
787enum A {
788 Variant { field1: Inner },
789}
790
791mod foo;
792
793//- /foo.rs
794use crate::{A, Inner};
795fn f() {
796 let a = A::Variant { field1: Inner };
797}
798"#,
799 );
800 }
801
802 #[test]
803 fn convert_directly_used_variant() {
804 check_assist(
805 convert_tuple_struct_to_named_struct,
806 r#"
807//- /main.rs
808struct Inner;
809enum A {
810 $0Variant(Inner),
811}
812
813mod foo;
814
815//- /foo.rs
816use crate::{A::Variant, Inner};
817fn f() {
818 let a = Variant(Inner);
819}
820"#,
821 r#"
822//- /main.rs
823struct Inner;
824enum A {
825 Variant { field1: Inner },
826}
827
828mod foo;
829
830//- /foo.rs
831use crate::{A::Variant, Inner};
832fn f() {
833 let a = Variant { field1: Inner };
834}
835"#,
836 );
837 }
516} 838}