aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/assists/src/handlers/generate_from_impl_for_enum.rs61
-rw-r--r--crates/assists/src/handlers/generate_impl.rs70
-rw-r--r--crates/assists/src/handlers/replace_derive_with_manual_impl.rs26
-rw-r--r--crates/assists/src/utils.rs52
-rw-r--r--crates/ide/src/references.rs35
-rw-r--r--crates/ide/src/references/rename.rs179
-rw-r--r--crates/ide_db/src/defs.rs14
-rw-r--r--crates/project_model/src/cargo_workspace.rs11
-rw-r--r--crates/project_model/src/lib.rs4
-rw-r--r--crates/project_model/src/sysroot.rs40
-rw-r--r--crates/project_model/src/workspace.rs13
-rw-r--r--crates/rust-analyzer/src/config.rs15
-rw-r--r--crates/syntax/src/ast/node_ext.rs5
13 files changed, 348 insertions, 177 deletions
diff --git a/crates/assists/src/handlers/generate_from_impl_for_enum.rs b/crates/assists/src/handlers/generate_from_impl_for_enum.rs
index f6febd3aa..d9388a737 100644
--- a/crates/assists/src/handlers/generate_from_impl_for_enum.rs
+++ b/crates/assists/src/handlers/generate_from_impl_for_enum.rs
@@ -1,15 +1,9 @@
1use ast::GenericParamsOwner;
2use ide_db::helpers::FamousDefs; 1use ide_db::helpers::FamousDefs;
3use ide_db::RootDatabase; 2use ide_db::RootDatabase;
4use itertools::Itertools; 3use syntax::ast::{self, AstNode, NameOwner};
5use stdx::format_to;
6use syntax::{
7 ast::{self, AstNode, NameOwner},
8 SmolStr,
9};
10use test_utils::mark; 4use test_utils::mark;
11 5
12use crate::{AssistContext, AssistId, AssistKind, Assists}; 6use crate::{utils::generate_trait_impl_text, AssistContext, AssistId, AssistKind, Assists};
13 7
14// Assist: generate_from_impl_for_enum 8// Assist: generate_from_impl_for_enum
15// 9//
@@ -31,8 +25,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
31pub(crate) fn generate_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 25pub(crate) fn generate_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
32 let variant = ctx.find_node_at_offset::<ast::Variant>()?; 26 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
33 let variant_name = variant.name()?; 27 let variant_name = variant.name()?;
34 let enum_name = variant.parent_enum().name()?; 28 let enum_ = ast::Adt::Enum(variant.parent_enum());
35 let enum_type_params = variant.parent_enum().generic_param_list();
36 let (field_name, field_type) = match variant.kind() { 29 let (field_name, field_type) = match variant.kind() {
37 ast::StructKind::Tuple(field_list) => { 30 ast::StructKind::Tuple(field_list) => {
38 if field_list.fields().count() != 1 { 31 if field_list.fields().count() != 1 {
@@ -62,49 +55,27 @@ pub(crate) fn generate_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext
62 target, 55 target,
63 |edit| { 56 |edit| {
64 let start_offset = variant.parent_enum().syntax().text_range().end(); 57 let start_offset = variant.parent_enum().syntax().text_range().end();
65 let mut buf = String::from("\n\nimpl"); 58 let from_trait = format!("From<{}>", field_type.syntax());
66 if let Some(type_params) = &enum_type_params { 59 let impl_code = if let Some(name) = field_name {
67 format_to!(buf, "{}", type_params.syntax()); 60 format!(
68 } 61 r#" fn from({0}: {1}) -> Self {{
69 format_to!(buf, " From<{}> for {}", field_type.syntax(), enum_name);
70 if let Some(type_params) = enum_type_params {
71 let lifetime_params = type_params
72 .lifetime_params()
73 .filter_map(|it| it.lifetime())
74 .map(|it| SmolStr::from(it.text()));
75 let type_params = type_params
76 .type_params()
77 .filter_map(|it| it.name())
78 .map(|it| SmolStr::from(it.text()));
79
80 let generic_params = lifetime_params.chain(type_params).format(", ");
81 format_to!(buf, "<{}>", generic_params)
82 }
83 if let Some(name) = field_name {
84 format_to!(
85 buf,
86 r#" {{
87 fn from({0}: {1}) -> Self {{
88 Self::{2} {{ {0} }} 62 Self::{2} {{ {0} }}
89 }} 63 }}"#,
90}}"#,
91 name.text(), 64 name.text(),
92 field_type.syntax(), 65 field_type.syntax(),
93 variant_name, 66 variant_name,
94 ); 67 )
95 } else { 68 } else {
96 format_to!( 69 format!(
97 buf, 70 r#" fn from(v: {}) -> Self {{
98 r#" {{
99 fn from(v: {}) -> Self {{
100 Self::{}(v) 71 Self::{}(v)
101 }} 72 }}"#,
102}}"#,
103 field_type.syntax(), 73 field_type.syntax(),
104 variant_name, 74 variant_name,
105 ); 75 )
106 } 76 };
107 edit.insert(start_offset, buf); 77 let from_impl = generate_trait_impl_text(&enum_, &from_trait, &impl_code);
78 edit.insert(start_offset, from_impl);
108 }, 79 },
109 ) 80 )
110} 81}
diff --git a/crates/assists/src/handlers/generate_impl.rs b/crates/assists/src/handlers/generate_impl.rs
index 61d1bd25c..16a600e6f 100644
--- a/crates/assists/src/handlers/generate_impl.rs
+++ b/crates/assists/src/handlers/generate_impl.rs
@@ -1,11 +1,6 @@
1use itertools::Itertools; 1use syntax::ast::{self, AstNode, NameOwner};
2use stdx::format_to;
3use syntax::{
4 ast::{self, AstNode, AttrsOwner, GenericParamsOwner, NameOwner},
5 SmolStr,
6};
7 2
8use crate::{AssistContext, AssistId, AssistKind, Assists}; 3use crate::{utils::generate_impl_text, AssistContext, AssistId, AssistKind, Assists};
9 4
10// Assist: generate_impl 5// Assist: generate_impl
11// 6//
@@ -36,44 +31,15 @@ pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()
36 format!("Generate impl for `{}`", name), 31 format!("Generate impl for `{}`", name),
37 target, 32 target,
38 |edit| { 33 |edit| {
39 let type_params = nominal.generic_param_list();
40 let start_offset = nominal.syntax().text_range().end(); 34 let start_offset = nominal.syntax().text_range().end();
41 let mut buf = String::new();
42 buf.push_str("\n\n");
43 nominal
44 .attrs()
45 .filter(|attr| {
46 attr.as_simple_call().map(|(name, _arg)| name == "cfg").unwrap_or(false)
47 })
48 .for_each(|attr| buf.push_str(format!("{}\n", attr.to_string()).as_str()));
49
50 buf.push_str("impl");
51 if let Some(type_params) = &type_params {
52 format_to!(buf, "{}", type_params.syntax());
53 }
54 buf.push_str(" ");
55 buf.push_str(name.text());
56 if let Some(type_params) = type_params {
57 let lifetime_params = type_params
58 .lifetime_params()
59 .filter_map(|it| it.lifetime())
60 .map(|it| SmolStr::from(it.text()));
61 let type_params = type_params
62 .type_params()
63 .filter_map(|it| it.name())
64 .map(|it| SmolStr::from(it.text()));
65
66 let generic_params = lifetime_params.chain(type_params).format(", ");
67 format_to!(buf, "<{}>", generic_params)
68 }
69 match ctx.config.snippet_cap { 35 match ctx.config.snippet_cap {
70 Some(cap) => { 36 Some(cap) => {
71 buf.push_str(" {\n $0\n}"); 37 let snippet = generate_impl_text(&nominal, " $0");
72 edit.insert_snippet(cap, start_offset, buf); 38 edit.insert_snippet(cap, start_offset, snippet);
73 } 39 }
74 None => { 40 None => {
75 buf.push_str(" {\n}"); 41 let snippet = generate_impl_text(&nominal, "");
76 edit.insert(start_offset, buf); 42 edit.insert(start_offset, snippet);
77 } 43 }
78 } 44 }
79 }, 45 },
@@ -132,6 +98,30 @@ mod tests {
132 $0 98 $0
133 }"#, 99 }"#,
134 ); 100 );
101
102 check_assist(
103 generate_impl,
104 r#"
105 struct Defaulted<T = i32> {}$0"#,
106 r#"
107 struct Defaulted<T = i32> {}
108
109 impl<T> Defaulted<T> {
110 $0
111 }"#,
112 );
113
114 check_assist(
115 generate_impl,
116 r#"
117 struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String> {}$0"#,
118 r#"
119 struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String> {}
120
121 impl<'a, 'b: 'a, T: Debug + Clone + 'a + 'b> Defaulted<'a, 'b, T> {
122 $0
123 }"#,
124 );
135 } 125 }
136 126
137 #[test] 127 #[test]
diff --git a/crates/assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/assists/src/handlers/replace_derive_with_manual_impl.rs
index 6aa9d2f2c..c69bc5cac 100644
--- a/crates/assists/src/handlers/replace_derive_with_manual_impl.rs
+++ b/crates/assists/src/handlers/replace_derive_with_manual_impl.rs
@@ -2,8 +2,7 @@ use ide_db::helpers::mod_path_to_ast;
2use ide_db::imports_locator; 2use ide_db::imports_locator;
3use itertools::Itertools; 3use itertools::Itertools;
4use syntax::{ 4use syntax::{
5 ast::{self, make, AstNode}, 5 ast::{self, make, AstNode, NameOwner},
6 Direction,
7 SyntaxKind::{IDENT, WHITESPACE}, 6 SyntaxKind::{IDENT, WHITESPACE},
8 TextSize, 7 TextSize,
9}; 8};
@@ -11,7 +10,8 @@ use syntax::{
11use crate::{ 10use crate::{
12 assist_context::{AssistBuilder, AssistContext, Assists}, 11 assist_context::{AssistBuilder, AssistContext, Assists},
13 utils::{ 12 utils::{
14 add_trait_assoc_items_to_impl, filter_assoc_items, render_snippet, Cursor, DefaultMethods, 13 add_trait_assoc_items_to_impl, filter_assoc_items, generate_trait_impl_text,
14 render_snippet, Cursor, DefaultMethods,
15 }, 15 },
16 AssistId, AssistKind, 16 AssistId, AssistKind,
17}; 17};
@@ -57,8 +57,9 @@ pub(crate) fn replace_derive_with_manual_impl(
57 let trait_token = ctx.token_at_offset().find(|t| t.kind() == IDENT && t.text() != "derive")?; 57 let trait_token = ctx.token_at_offset().find(|t| t.kind() == IDENT && t.text() != "derive")?;
58 let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text()))); 58 let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text())));
59 59
60 let annotated_name = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?; 60 let adt = attr.syntax().parent().and_then(ast::Adt::cast)?;
61 let insert_pos = annotated_name.syntax().parent()?.text_range().end(); 61 let annotated_name = adt.name()?;
62 let insert_pos = adt.syntax().text_range().end();
62 63
63 let current_module = ctx.sema.scope(annotated_name.syntax()).module()?; 64 let current_module = ctx.sema.scope(annotated_name.syntax()).module()?;
64 let current_crate = current_module.krate(); 65 let current_crate = current_module.krate();
@@ -82,10 +83,10 @@ pub(crate) fn replace_derive_with_manual_impl(
82 83
83 let mut no_traits_found = true; 84 let mut no_traits_found = true;
84 for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) { 85 for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) {
85 add_assist(acc, ctx, &attr, &trait_path, Some(trait_), &annotated_name, insert_pos)?; 86 add_assist(acc, ctx, &attr, &trait_path, Some(trait_), &adt, &annotated_name, insert_pos)?;
86 } 87 }
87 if no_traits_found { 88 if no_traits_found {
88 add_assist(acc, ctx, &attr, &trait_path, None, &annotated_name, insert_pos)?; 89 add_assist(acc, ctx, &attr, &trait_path, None, &adt, &annotated_name, insert_pos)?;
89 } 90 }
90 Some(()) 91 Some(())
91} 92}
@@ -96,6 +97,7 @@ fn add_assist(
96 attr: &ast::Attr, 97 attr: &ast::Attr,
97 trait_path: &ast::Path, 98 trait_path: &ast::Path,
98 trait_: Option<hir::Trait>, 99 trait_: Option<hir::Trait>,
100 adt: &ast::Adt,
99 annotated_name: &ast::Name, 101 annotated_name: &ast::Name,
100 insert_pos: TextSize, 102 insert_pos: TextSize,
101) -> Option<()> { 103) -> Option<()> {
@@ -112,15 +114,15 @@ fn add_assist(
112 let impl_def_with_items = 114 let impl_def_with_items =
113 impl_def_from_trait(&ctx.sema, annotated_name, trait_, trait_path); 115 impl_def_from_trait(&ctx.sema, annotated_name, trait_, trait_path);
114 update_attribute(builder, &input, &trait_name, &attr); 116 update_attribute(builder, &input, &trait_name, &attr);
117 let trait_path = format!("{}", trait_path);
115 match (ctx.config.snippet_cap, impl_def_with_items) { 118 match (ctx.config.snippet_cap, impl_def_with_items) {
116 (None, _) => builder.insert( 119 (None, _) => {
117 insert_pos, 120 builder.insert(insert_pos, generate_trait_impl_text(adt, &trait_path, ""))
118 format!("\n\nimpl {} for {} {{\n\n}}", trait_path, annotated_name), 121 }
119 ),
120 (Some(cap), None) => builder.insert_snippet( 122 (Some(cap), None) => builder.insert_snippet(
121 cap, 123 cap,
122 insert_pos, 124 insert_pos,
123 format!("\n\nimpl {} for {} {{\n $0\n}}", trait_path, annotated_name), 125 generate_trait_impl_text(adt, &trait_path, " $0"),
124 ), 126 ),
125 (Some(cap), Some((impl_def, first_assoc_item))) => { 127 (Some(cap), Some((impl_def, first_assoc_item))) => {
126 let mut cursor = Cursor::Before(first_assoc_item.syntax()); 128 let mut cursor = Cursor::Before(first_assoc_item.syntax());
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs
index 5dd32aef1..8418e6e12 100644
--- a/crates/assists/src/utils.rs
+++ b/crates/assists/src/utils.rs
@@ -2,6 +2,7 @@
2 2
3use std::ops; 3use std::ops;
4 4
5use ast::TypeBoundsOwner;
5use hir::{Adt, HasSource}; 6use hir::{Adt, HasSource};
6use ide_db::{helpers::SnippetCap, RootDatabase}; 7use ide_db::{helpers::SnippetCap, RootDatabase};
7use itertools::Itertools; 8use itertools::Itertools;
@@ -367,21 +368,56 @@ pub(crate) fn find_impl_block_end(impl_def: ast::Impl, buf: &mut String) -> Opti
367// Generates the surrounding `impl Type { <code> }` including type and lifetime 368// Generates the surrounding `impl Type { <code> }` including type and lifetime
368// parameters 369// parameters
369pub(crate) fn generate_impl_text(adt: &ast::Adt, code: &str) -> String { 370pub(crate) fn generate_impl_text(adt: &ast::Adt, code: &str) -> String {
370 let type_params = adt.generic_param_list(); 371 generate_impl_text_inner(adt, None, code)
372}
373
374// Generates the surrounding `impl <trait> for Type { <code> }` including type
375// and lifetime parameters
376pub(crate) fn generate_trait_impl_text(adt: &ast::Adt, trait_text: &str, code: &str) -> String {
377 generate_impl_text_inner(adt, Some(trait_text), code)
378}
379
380fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str) -> String {
381 let generic_params = adt.generic_param_list();
371 let mut buf = String::with_capacity(code.len()); 382 let mut buf = String::with_capacity(code.len());
372 buf.push_str("\n\nimpl"); 383 buf.push_str("\n\n");
373 if let Some(type_params) = &type_params { 384 adt.attrs()
374 format_to!(buf, "{}", type_params.syntax()); 385 .filter(|attr| attr.as_simple_call().map(|(name, _arg)| name == "cfg").unwrap_or(false))
386 .for_each(|attr| buf.push_str(format!("{}\n", attr.to_string()).as_str()));
387 buf.push_str("impl");
388 if let Some(generic_params) = &generic_params {
389 let lifetimes = generic_params.lifetime_params().map(|lt| format!("{}", lt.syntax()));
390 let type_params = generic_params.type_params().map(|type_param| {
391 let mut buf = String::new();
392 if let Some(it) = type_param.name() {
393 format_to!(buf, "{}", it.syntax());
394 }
395 if let Some(it) = type_param.colon_token() {
396 format_to!(buf, "{} ", it);
397 }
398 if let Some(it) = type_param.type_bound_list() {
399 format_to!(buf, "{}", it.syntax());
400 }
401 buf
402 });
403 let generics = lifetimes.chain(type_params).format(", ");
404 format_to!(buf, "<{}>", generics);
375 } 405 }
376 buf.push(' '); 406 buf.push(' ');
407 if let Some(trait_text) = trait_text {
408 buf.push_str(trait_text);
409 buf.push_str(" for ");
410 }
377 buf.push_str(adt.name().unwrap().text()); 411 buf.push_str(adt.name().unwrap().text());
378 if let Some(type_params) = type_params { 412 if let Some(generic_params) = generic_params {
379 let lifetime_params = type_params 413 let lifetime_params = generic_params
380 .lifetime_params() 414 .lifetime_params()
381 .filter_map(|it| it.lifetime()) 415 .filter_map(|it| it.lifetime())
382 .map(|it| SmolStr::from(it.text())); 416 .map(|it| SmolStr::from(it.text()));
383 let type_params = 417 let type_params = generic_params
384 type_params.type_params().filter_map(|it| it.name()).map(|it| SmolStr::from(it.text())); 418 .type_params()
419 .filter_map(|it| it.name())
420 .map(|it| SmolStr::from(it.text()));
385 format_to!(buf, "<{}>", lifetime_params.chain(type_params).format(", ")) 421 format_to!(buf, "<{}>", lifetime_params.chain(type_params).format(", "))
386 } 422 }
387 423
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index c7cefb3b6..17086f7d4 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -1127,4 +1127,39 @@ impl Foo {
1127 "#]], 1127 "#]],
1128 ); 1128 );
1129 } 1129 }
1130
1131 #[test]
1132 fn test_attr_differs_from_fn_with_same_name() {
1133 check(
1134 r#"
1135#[test]
1136fn test$0() {
1137 test();
1138}
1139"#,
1140 expect![[r#"
1141 test Function FileId(0) 0..33 11..15
1142
1143 FileId(0) 24..28
1144 "#]],
1145 );
1146 }
1147
1148 #[test]
1149 fn test_attr_matches_proc_macro_fn() {
1150 check(
1151 r#"
1152#[proc_macro_attribute]
1153fn my_proc_macro() {}
1154
1155#[my_proc_macro$0]
1156fn test() {}
1157"#,
1158 expect![[r#"
1159 my_proc_macro Function FileId(0) 0..45 27..40
1160
1161 FileId(0) 49..62
1162 "#]],
1163 );
1164 }
1130} 1165}
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index b04214291..08f16b54d 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -75,8 +75,7 @@ pub(crate) fn rename_with_semantics(
75 let source_file = sema.parse(position.file_id); 75 let source_file = sema.parse(position.file_id);
76 let syntax = source_file.syntax(); 76 let syntax = source_file.syntax();
77 77
78 let def = find_definition(sema, syntax, position) 78 let def = find_definition(sema, syntax, position)?;
79 .ok_or_else(|| format_err!("No references found at position"))?;
80 match def { 79 match def {
81 Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name), 80 Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name),
82 def => rename_reference(sema, def, new_name), 81 def => rename_reference(sema, def, new_name),
@@ -149,18 +148,30 @@ fn find_definition(
149 sema: &Semantics<RootDatabase>, 148 sema: &Semantics<RootDatabase>,
150 syntax: &SyntaxNode, 149 syntax: &SyntaxNode,
151 position: FilePosition, 150 position: FilePosition,
152) -> Option<Definition> { 151) -> RenameResult<Definition> {
153 let def = match find_name_like(sema, syntax, position)? { 152 match find_name_like(sema, syntax, position)
154 NameLike::Name(name) => NameClass::classify(sema, &name)?.referenced_or_defined(sema.db), 153 .ok_or_else(|| format_err!("No references found at position"))?
155 NameLike::NameRef(name_ref) => NameRefClass::classify(sema, &name_ref)?.referenced(sema.db), 154 {
155 // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet
156 NameLike::Name(name)
157 if name.syntax().parent().map_or(false, |it| ast::Rename::can_cast(it.kind())) =>
158 {
159 bail!("Renaming aliases is currently unsupported")
160 }
161 NameLike::Name(name) => {
162 NameClass::classify(sema, &name).map(|class| class.referenced_or_defined(sema.db))
163 }
164 NameLike::NameRef(name_ref) => {
165 NameRefClass::classify(sema, &name_ref).map(|class| class.referenced(sema.db))
166 }
156 NameLike::Lifetime(lifetime) => NameRefClass::classify_lifetime(sema, &lifetime) 167 NameLike::Lifetime(lifetime) => NameRefClass::classify_lifetime(sema, &lifetime)
157 .map(|class| NameRefClass::referenced(class, sema.db)) 168 .map(|class| NameRefClass::referenced(class, sema.db))
158 .or_else(|| { 169 .or_else(|| {
159 NameClass::classify_lifetime(sema, &lifetime) 170 NameClass::classify_lifetime(sema, &lifetime)
160 .map(|it| it.referenced_or_defined(sema.db)) 171 .map(|it| it.referenced_or_defined(sema.db))
161 })?, 172 }),
162 }; 173 }
163 Some(def) 174 .ok_or_else(|| format_err!("No references found at position"))
164} 175}
165 176
166fn source_edit_from_references( 177fn source_edit_from_references(
@@ -173,21 +184,40 @@ fn source_edit_from_references(
173 let mut edit = TextEdit::builder(); 184 let mut edit = TextEdit::builder();
174 for reference in references { 185 for reference in references {
175 let (range, replacement) = match &reference.name { 186 let (range, replacement) = match &reference.name {
176 NameLike::Name(_) => (None, format!("{}", new_name)), 187 // if the ranges differ then the node is inside a macro call, we can't really attempt
177 NameLike::NameRef(name_ref) => source_edit_from_name_ref(name_ref, new_name, def), 188 // to make special rewrites like shorthand syntax and such, so just rename the node in
178 NameLike::Lifetime(_) => (None, format!("{}", new_name)), 189 // the macro input
179 }; 190 NameLike::NameRef(name_ref) if name_ref.syntax().text_range() == reference.range => {
180 // FIXME: Some(range) will be incorrect when we are inside macros 191 source_edit_from_name_ref(name_ref, new_name, def)
181 edit.replace(range.unwrap_or(reference.range), replacement); 192 }
193 NameLike::Name(name) if name.syntax().text_range() == reference.range => {
194 source_edit_from_name(name, new_name)
195 }
196 _ => None,
197 }
198 .unwrap_or_else(|| (reference.range, new_name.to_string()));
199 edit.replace(range, replacement);
182 } 200 }
183 (file_id, edit.finish()) 201 (file_id, edit.finish())
184} 202}
185 203
204fn source_edit_from_name(name: &ast::Name, new_name: &str) -> Option<(TextRange, String)> {
205 if let Some(_) = ast::RecordPatField::for_field_name(name) {
206 if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) {
207 return Some((
208 TextRange::empty(ident_pat.syntax().text_range().start()),
209 format!("{}: ", new_name),
210 ));
211 }
212 }
213 None
214}
215
186fn source_edit_from_name_ref( 216fn source_edit_from_name_ref(
187 name_ref: &ast::NameRef, 217 name_ref: &ast::NameRef,
188 new_name: &str, 218 new_name: &str,
189 def: Definition, 219 def: Definition,
190) -> (Option<TextRange>, String) { 220) -> Option<(TextRange, String)> {
191 if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) { 221 if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) {
192 let rcf_name_ref = record_field.name_ref(); 222 let rcf_name_ref = record_field.name_ref();
193 let rcf_expr = record_field.expr(); 223 let rcf_expr = record_field.expr();
@@ -197,45 +227,40 @@ fn source_edit_from_name_ref(
197 if field_name == *name_ref { 227 if field_name == *name_ref {
198 if init.text() == new_name { 228 if init.text() == new_name {
199 mark::hit!(test_rename_field_put_init_shorthand); 229 mark::hit!(test_rename_field_put_init_shorthand);
200 // same names, we can use a shorthand here instead 230 // same names, we can use a shorthand here instead.
201 // we do not want to erase attributes hence this range start 231 // we do not want to erase attributes hence this range start
202 let s = field_name.syntax().text_range().start(); 232 let s = field_name.syntax().text_range().start();
203 let e = record_field.syntax().text_range().end(); 233 let e = record_field.syntax().text_range().end();
204 return (Some(TextRange::new(s, e)), format!("{}", new_name)); 234 return Some((TextRange::new(s, e), new_name.to_owned()));
205 } 235 }
206 } else if init == *name_ref { 236 } else if init == *name_ref {
207 if field_name.text() == new_name { 237 if field_name.text() == new_name {
208 mark::hit!(test_rename_local_put_init_shorthand); 238 mark::hit!(test_rename_local_put_init_shorthand);
209 // same names, we can use a shorthand here instead 239 // same names, we can use a shorthand here instead.
210 // we do not want to erase attributes hence this range start 240 // we do not want to erase attributes hence this range start
211 let s = field_name.syntax().text_range().start(); 241 let s = field_name.syntax().text_range().start();
212 let e = record_field.syntax().text_range().end(); 242 let e = record_field.syntax().text_range().end();
213 return (Some(TextRange::new(s, e)), format!("{}", new_name)); 243 return Some((TextRange::new(s, e), new_name.to_owned()));
214 } 244 }
215 } 245 }
246 None
216 } 247 }
217 // init shorthand 248 // init shorthand
218 (None, Some(_)) => { 249 // FIXME: instead of splitting the shorthand, recursively trigger a rename of the
219 // FIXME: instead of splitting the shorthand, recursively trigger a rename of the 250 // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547
220 // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547 251 (None, Some(_)) if matches!(def, Definition::Field(_)) => {
221 match def { 252 mark::hit!(test_rename_field_in_field_shorthand);
222 Definition::Field(_) => { 253 let s = name_ref.syntax().text_range().start();
223 mark::hit!(test_rename_field_in_field_shorthand); 254 Some((TextRange::empty(s), format!("{}: ", new_name)))
224 let s = name_ref.syntax().text_range().start();
225 return (Some(TextRange::empty(s)), format!("{}: ", new_name));
226 }
227 Definition::Local(_) => {
228 mark::hit!(test_rename_local_in_field_shorthand);
229 let s = name_ref.syntax().text_range().end();
230 return (Some(TextRange::empty(s)), format!(": {}", new_name));
231 }
232 _ => {}
233 }
234 } 255 }
235 _ => {} 256 (None, Some(_)) if matches!(def, Definition::Local(_)) => {
257 mark::hit!(test_rename_local_in_field_shorthand);
258 let s = name_ref.syntax().text_range().end();
259 Some((TextRange::empty(s), format!(": {}", new_name)))
260 }
261 _ => None,
236 } 262 }
237 } 263 } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) {
238 if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) {
239 let rcf_name_ref = record_field.name_ref(); 264 let rcf_name_ref = record_field.name_ref();
240 let rcf_pat = record_field.pat(); 265 let rcf_pat = record_field.pat();
241 match (rcf_name_ref, rcf_pat) { 266 match (rcf_name_ref, rcf_pat) {
@@ -244,17 +269,20 @@ fn source_edit_from_name_ref(
244 // field name is being renamed 269 // field name is being renamed
245 if pat.name().map_or(false, |it| it.text() == new_name) { 270 if pat.name().map_or(false, |it| it.text() == new_name) {
246 mark::hit!(test_rename_field_put_init_shorthand_pat); 271 mark::hit!(test_rename_field_put_init_shorthand_pat);
247 // same names, we can use a shorthand here instead 272 // same names, we can use a shorthand here instead/
248 // we do not want to erase attributes hence this range start 273 // we do not want to erase attributes hence this range start
249 let s = field_name.syntax().text_range().start(); 274 let s = field_name.syntax().text_range().start();
250 let e = record_field.syntax().text_range().end(); 275 let e = record_field.syntax().text_range().end();
251 return (Some(TextRange::new(s, e)), format!("{}", new_name)); 276 Some((TextRange::new(s, e), pat.to_string()))
277 } else {
278 None
252 } 279 }
253 } 280 }
254 _ => {} 281 _ => None,
255 } 282 }
283 } else {
284 None
256 } 285 }
257 (None, format!("{}", new_name))
258} 286}
259 287
260fn rename_mod( 288fn rename_mod(
@@ -1477,7 +1505,7 @@ fn foo(i: i32) -> Foo {
1477 } 1505 }
1478 1506
1479 #[test] 1507 #[test]
1480 fn test_struct_field_destructure_into_shorthand() { 1508 fn test_struct_field_pat_into_shorthand() {
1481 mark::check!(test_rename_field_put_init_shorthand_pat); 1509 mark::check!(test_rename_field_put_init_shorthand_pat);
1482 check( 1510 check(
1483 "baz", 1511 "baz",
@@ -1485,16 +1513,16 @@ fn foo(i: i32) -> Foo {
1485struct Foo { i$0: i32 } 1513struct Foo { i$0: i32 }
1486 1514
1487fn foo(foo: Foo) { 1515fn foo(foo: Foo) {
1488 let Foo { i: baz } = foo; 1516 let Foo { i: ref baz @ qux } = foo;
1489 let _ = baz; 1517 let _ = qux;
1490} 1518}
1491"#, 1519"#,
1492 r#" 1520 r#"
1493struct Foo { baz: i32 } 1521struct Foo { baz: i32 }
1494 1522
1495fn foo(foo: Foo) { 1523fn foo(foo: Foo) {
1496 let Foo { baz } = foo; 1524 let Foo { ref baz @ qux } = foo;
1497 let _ = baz; 1525 let _ = qux;
1498} 1526}
1499"#, 1527"#,
1500 ); 1528 );
@@ -1568,6 +1596,27 @@ fn foo(Foo { i: bar }: foo) -> i32 {
1568 } 1596 }
1569 1597
1570 #[test] 1598 #[test]
1599 fn test_struct_field_complex_ident_pat() {
1600 check(
1601 "baz",
1602 r#"
1603struct Foo { i$0: i32 }
1604
1605fn foo(foo: Foo) {
1606 let Foo { ref i } = foo;
1607}
1608"#,
1609 r#"
1610struct Foo { baz: i32 }
1611
1612fn foo(foo: Foo) {
1613 let Foo { baz: ref i } = foo;
1614}
1615"#,
1616 );
1617 }
1618
1619 #[test]
1571 fn test_rename_lifetimes() { 1620 fn test_rename_lifetimes() {
1572 mark::check!(rename_lifetime); 1621 mark::check!(rename_lifetime);
1573 check( 1622 check(
@@ -1674,4 +1723,38 @@ impl Foo {
1674"#, 1723"#,
1675 ) 1724 )
1676 } 1725 }
1726
1727 #[test]
1728 fn test_rename_field_in_pat_in_macro_doesnt_shorthand() {
1729 // ideally we would be able to make this emit a short hand, but I doubt this is easily possible
1730 check(
1731 "baz",
1732 r#"
1733macro_rules! foo {
1734 ($pattern:pat) => {
1735 let $pattern = loop {};
1736 };
1737}
1738struct Foo {
1739 bar$0: u32,
1740}
1741fn foo() {
1742 foo!(Foo { bar: baz });
1743}
1744"#,
1745 r#"
1746macro_rules! foo {
1747 ($pattern:pat) => {
1748 let $pattern = loop {};
1749 };
1750}
1751struct Foo {
1752 baz: u32,
1753}
1754fn foo() {
1755 foo!(Foo { baz: baz });
1756}
1757"#,
1758 )
1759 }
1677} 1760}
diff --git a/crates/ide_db/src/defs.rs b/crates/ide_db/src/defs.rs
index a8091dbee..ff612b7d0 100644
--- a/crates/ide_db/src/defs.rs
+++ b/crates/ide_db/src/defs.rs
@@ -6,8 +6,8 @@
6// FIXME: this badly needs rename/rewrite (matklad, 2020-02-06). 6// FIXME: this badly needs rename/rewrite (matklad, 2020-02-06).
7 7
8use hir::{ 8use hir::{
9 db::HirDatabase, Crate, Field, GenericParam, HasVisibility, Impl, Label, Local, MacroDef, 9 db::HirDatabase, Crate, Field, GenericParam, HasAttrs, HasVisibility, Impl, Label, Local,
10 Module, ModuleDef, Name, PathResolution, Semantics, Visibility, 10 MacroDef, Module, ModuleDef, Name, PathResolution, Semantics, Visibility,
11}; 11};
12use syntax::{ 12use syntax::{
13 ast::{self, AstNode, PathSegmentKind}, 13 ast::{self, AstNode, PathSegmentKind},
@@ -366,7 +366,15 @@ impl NameRefClass {
366 366
367 if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) { 367 if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) {
368 if let Some(resolved) = sema.resolve_path(&path) { 368 if let Some(resolved) = sema.resolve_path(&path) {
369 return Some(NameRefClass::Definition(resolved.into())); 369 if path.syntax().parent().and_then(ast::Attr::cast).is_some() {
370 if let PathResolution::Def(ModuleDef::Function(func)) = resolved {
371 if func.attrs(sema.db).by_key("proc_macro_attribute").exists() {
372 return Some(NameRefClass::Definition(resolved.into()));
373 }
374 }
375 } else {
376 return Some(NameRefClass::Definition(resolved.into()));
377 }
370 } 378 }
371 } 379 }
372 380
diff --git a/crates/project_model/src/cargo_workspace.rs b/crates/project_model/src/cargo_workspace.rs
index f47898b9b..1d8d34a0b 100644
--- a/crates/project_model/src/cargo_workspace.rs
+++ b/crates/project_model/src/cargo_workspace.rs
@@ -44,6 +44,15 @@ impl ops::Index<Target> for CargoWorkspace {
44 } 44 }
45} 45}
46 46
47/// Describes how to set the rustc source directory.
48#[derive(Clone, Debug, PartialEq, Eq)]
49pub enum RustcSource {
50 /// Explicit path for the rustc source directory.
51 Path(AbsPathBuf),
52 /// Try to automatically detect where the rustc source directory is.
53 Discover,
54}
55
47#[derive(Default, Clone, Debug, PartialEq, Eq)] 56#[derive(Default, Clone, Debug, PartialEq, Eq)]
48pub struct CargoConfig { 57pub struct CargoConfig {
49 /// Do not activate the `default` feature. 58 /// Do not activate the `default` feature.
@@ -64,7 +73,7 @@ pub struct CargoConfig {
64 pub no_sysroot: bool, 73 pub no_sysroot: bool,
65 74
66 /// rustc private crate source 75 /// rustc private crate source
67 pub rustc_source: Option<AbsPathBuf>, 76 pub rustc_source: Option<RustcSource>,
68} 77}
69 78
70pub type Package = Idx<PackageData>; 79pub type Package = Idx<PackageData>;
diff --git a/crates/project_model/src/lib.rs b/crates/project_model/src/lib.rs
index d712095a6..a5b35ed95 100644
--- a/crates/project_model/src/lib.rs
+++ b/crates/project_model/src/lib.rs
@@ -21,8 +21,8 @@ use rustc_hash::FxHashSet;
21pub use crate::{ 21pub use crate::{
22 build_data::{BuildDataCollector, BuildDataResult}, 22 build_data::{BuildDataCollector, BuildDataResult},
23 cargo_workspace::{ 23 cargo_workspace::{
24 CargoConfig, CargoWorkspace, Package, PackageData, PackageDependency, Target, TargetData, 24 CargoConfig, CargoWorkspace, Package, PackageData, PackageDependency, RustcSource, Target,
25 TargetKind, 25 TargetData, TargetKind,
26 }, 26 },
27 project_json::{ProjectJson, ProjectJsonData}, 27 project_json::{ProjectJson, ProjectJsonData},
28 sysroot::Sysroot, 28 sysroot::Sysroot,
diff --git a/crates/project_model/src/sysroot.rs b/crates/project_model/src/sysroot.rs
index ff44dae4a..3b0ff506d 100644
--- a/crates/project_model/src/sysroot.rs
+++ b/crates/project_model/src/sysroot.rs
@@ -51,11 +51,18 @@ impl Sysroot {
51 pub fn discover(cargo_toml: &AbsPath) -> Result<Sysroot> { 51 pub fn discover(cargo_toml: &AbsPath) -> Result<Sysroot> {
52 log::debug!("Discovering sysroot for {}", cargo_toml.display()); 52 log::debug!("Discovering sysroot for {}", cargo_toml.display());
53 let current_dir = cargo_toml.parent().unwrap(); 53 let current_dir = cargo_toml.parent().unwrap();
54 let sysroot_src_dir = discover_sysroot_src_dir(current_dir)?; 54 let sysroot_dir = discover_sysroot_dir(current_dir)?;
55 let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir, current_dir)?;
55 let res = Sysroot::load(&sysroot_src_dir)?; 56 let res = Sysroot::load(&sysroot_src_dir)?;
56 Ok(res) 57 Ok(res)
57 } 58 }
58 59
60 pub fn discover_rustc(cargo_toml: &AbsPath) -> Option<AbsPathBuf> {
61 log::debug!("Discovering rustc source for {}", cargo_toml.display());
62 let current_dir = cargo_toml.parent().unwrap();
63 discover_sysroot_dir(current_dir).ok().and_then(|sysroot_dir| get_rustc_src(&sysroot_dir))
64 }
65
59 pub fn load(sysroot_src_dir: &AbsPath) -> Result<Sysroot> { 66 pub fn load(sysroot_src_dir: &AbsPath) -> Result<Sysroot> {
60 let mut sysroot = Sysroot { crates: Arena::default() }; 67 let mut sysroot = Sysroot { crates: Arena::default() };
61 68
@@ -110,7 +117,18 @@ impl Sysroot {
110 } 117 }
111} 118}
112 119
113fn discover_sysroot_src_dir(current_dir: &AbsPath) -> Result<AbsPathBuf> { 120fn discover_sysroot_dir(current_dir: &AbsPath) -> Result<AbsPathBuf> {
121 let mut rustc = Command::new(toolchain::rustc());
122 rustc.current_dir(current_dir).args(&["--print", "sysroot"]);
123 log::debug!("Discovering sysroot by {:?}", rustc);
124 let stdout = utf8_stdout(rustc)?;
125 Ok(AbsPathBuf::assert(PathBuf::from(stdout)))
126}
127
128fn discover_sysroot_src_dir(
129 sysroot_path: &AbsPathBuf,
130 current_dir: &AbsPath,
131) -> Result<AbsPathBuf> {
114 if let Ok(path) = env::var("RUST_SRC_PATH") { 132 if let Ok(path) = env::var("RUST_SRC_PATH") {
115 let path = AbsPathBuf::try_from(path.as_str()) 133 let path = AbsPathBuf::try_from(path.as_str())
116 .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?; 134 .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?;
@@ -122,14 +140,6 @@ fn discover_sysroot_src_dir(current_dir: &AbsPath) -> Result<AbsPathBuf> {
122 log::debug!("RUST_SRC_PATH is set, but is invalid (no core: {:?}), ignoring", core); 140 log::debug!("RUST_SRC_PATH is set, but is invalid (no core: {:?}), ignoring", core);
123 } 141 }
124 142
125 let sysroot_path = {
126 let mut rustc = Command::new(toolchain::rustc());
127 rustc.current_dir(current_dir).args(&["--print", "sysroot"]);
128 log::debug!("Discovering sysroot by {:?}", rustc);
129 let stdout = utf8_stdout(rustc)?;
130 AbsPathBuf::assert(PathBuf::from(stdout))
131 };
132
133 get_rust_src(&sysroot_path) 143 get_rust_src(&sysroot_path)
134 .or_else(|| { 144 .or_else(|| {
135 let mut rustup = Command::new(toolchain::rustup()); 145 let mut rustup = Command::new(toolchain::rustup());
@@ -149,6 +159,16 @@ try installing the Rust source the same way you installed rustc",
149 }) 159 })
150} 160}
151 161
162fn get_rustc_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> {
163 let rustc_src = sysroot_path.join("lib/rustlib/rustc-src/rust/compiler/rustc/Cargo.toml");
164 log::debug!("Checking for rustc source code: {}", rustc_src.display());
165 if rustc_src.exists() {
166 Some(rustc_src)
167 } else {
168 None
169 }
170}
171
152fn get_rust_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> { 172fn get_rust_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> {
153 // Try the new path first since the old one still exists. 173 // Try the new path first since the old one still exists.
154 let rust_src = sysroot_path.join("lib/rustlib/src/rust"); 174 let rust_src = sysroot_path.join("lib/rustlib/src/rust");
diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs
index c30861976..0220efdb4 100644
--- a/crates/project_model/src/workspace.rs
+++ b/crates/project_model/src/workspace.rs
@@ -114,6 +114,7 @@ impl ProjectWorkspace {
114 cargo_version 114 cargo_version
115 ) 115 )
116 })?; 116 })?;
117
117 let sysroot = if config.no_sysroot { 118 let sysroot = if config.no_sysroot {
118 Sysroot::default() 119 Sysroot::default()
119 } else { 120 } else {
@@ -125,7 +126,17 @@ impl ProjectWorkspace {
125 })? 126 })?
126 }; 127 };
127 128
128 let rustc = if let Some(rustc_dir) = &config.rustc_source { 129 let rustc_dir = if let Some(rustc_source) = &config.rustc_source {
130 use cargo_workspace::RustcSource;
131 match rustc_source {
132 RustcSource::Path(path) => Some(path.clone()),
133 RustcSource::Discover => Sysroot::discover_rustc(&cargo_toml),
134 }
135 } else {
136 None
137 };
138
139 let rustc = if let Some(rustc_dir) = rustc_dir {
129 Some( 140 Some(
130 CargoWorkspace::from_cargo_metadata(&rustc_dir, config, progress) 141 CargoWorkspace::from_cargo_metadata(&rustc_dir, config, progress)
131 .with_context(|| { 142 .with_context(|| {
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index cc0b22bff..f9098968a 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -18,7 +18,7 @@ use ide_db::helpers::{
18}; 18};
19use itertools::Itertools; 19use itertools::Itertools;
20use lsp_types::{ClientCapabilities, MarkupKind}; 20use lsp_types::{ClientCapabilities, MarkupKind};
21use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest}; 21use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource};
22use rustc_hash::FxHashSet; 22use rustc_hash::FxHashSet;
23use serde::{de::DeserializeOwned, Deserialize}; 23use serde::{de::DeserializeOwned, Deserialize};
24use vfs::AbsPathBuf; 24use vfs::AbsPathBuf;
@@ -177,8 +177,9 @@ config_data! {
177 /// tests or binaries.\nFor example, it may be `--release`. 177 /// tests or binaries.\nFor example, it may be `--release`.
178 runnables_cargoExtraArgs: Vec<String> = "[]", 178 runnables_cargoExtraArgs: Vec<String> = "[]",
179 179
180 /// Path to the rust compiler sources, for usage in rustc_private projects. 180 /// Path to the rust compiler sources, for usage in rustc_private projects, or "discover"
181 rustcSource : Option<PathBuf> = "null", 181 /// to try to automatically find it.
182 rustcSource : Option<String> = "null",
182 183
183 /// Additional arguments to `rustfmt`. 184 /// Additional arguments to `rustfmt`.
184 rustfmt_extraArgs: Vec<String> = "[]", 185 rustfmt_extraArgs: Vec<String> = "[]",
@@ -473,7 +474,13 @@ impl Config {
473 self.data.cargo_loadOutDirsFromCheck 474 self.data.cargo_loadOutDirsFromCheck
474 } 475 }
475 pub fn cargo(&self) -> CargoConfig { 476 pub fn cargo(&self) -> CargoConfig {
476 let rustc_source = self.data.rustcSource.as_ref().map(|it| self.root_path.join(&it)); 477 let rustc_source = self.data.rustcSource.as_ref().map(|rustc_src| {
478 if rustc_src == "discover" {
479 RustcSource::Discover
480 } else {
481 RustcSource::Path(self.root_path.join(rustc_src))
482 }
483 });
477 484
478 CargoConfig { 485 CargoConfig {
479 no_default_features: self.data.cargo_noDefaultFeatures, 486 no_default_features: self.data.cargo_noDefaultFeatures,
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index b105cb0e0..307e150e9 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -3,12 +3,11 @@
3 3
4use std::fmt; 4use std::fmt;
5 5
6use ast::AttrsOwner;
7use itertools::Itertools; 6use itertools::Itertools;
8use parser::SyntaxKind; 7use parser::SyntaxKind;
9 8
10use crate::{ 9use crate::{
11 ast::{self, support, AstNode, AstToken, NameOwner, SyntaxNode}, 10 ast::{self, support, AstNode, AstToken, AttrsOwner, NameOwner, SyntaxNode},
12 SmolStr, SyntaxElement, SyntaxToken, T, 11 SmolStr, SyntaxElement, SyntaxToken, T,
13}; 12};
14 13
@@ -324,7 +323,7 @@ impl ast::RecordPatField {
324 323
325 pub fn for_field_name(field_name: &ast::Name) -> Option<ast::RecordPatField> { 324 pub fn for_field_name(field_name: &ast::Name) -> Option<ast::RecordPatField> {
326 let candidate = 325 let candidate =
327 field_name.syntax().ancestors().nth(3).and_then(ast::RecordPatField::cast)?; 326 field_name.syntax().ancestors().nth(2).and_then(ast::RecordPatField::cast)?;
328 match candidate.field_name()? { 327 match candidate.field_name()? {
329 NameOrNameRef::Name(name) if name == *field_name => Some(candidate), 328 NameOrNameRef::Name(name) if name == *field_name => Some(candidate),
330 _ => None, 329 _ => None,