|
1 | 1 | use either::Either;
|
2 |
| -use ide_db::defs::Definition; |
| 2 | +use ide_db::{defs::Definition, search::FileReference}; |
3 | 3 | use itertools::Itertools;
|
4 | 4 | use syntax::{
|
5 | 5 | ast::{self, AstNode, HasGenericParams, HasVisibility},
|
6 |
| - match_ast, SyntaxKind, SyntaxNode, |
| 6 | + match_ast, SyntaxKind, |
7 | 7 | };
|
8 | 8 |
|
9 | 9 | use crate::{assist_context::SourceChangeBuilder, AssistContext, AssistId, AssistKind, Assists};
|
@@ -141,57 +141,70 @@ fn edit_struct_references(
|
141 | 141 | };
|
142 | 142 | let usages = strukt_def.usages(&ctx.sema).include_self_refs().all();
|
143 | 143 |
|
144 |
| - let edit_node = |edit: &mut SourceChangeBuilder, node: SyntaxNode| -> Option<()> { |
145 |
| - match_ast! { |
146 |
| - match node { |
147 |
| - ast::RecordPat(record_struct_pat) => { |
148 |
| - let Some(fr) = ctx.sema.original_range_opt(record_struct_pat.syntax()) else { |
149 |
| - // We've found the node to replace, so we should return `Some` even if the |
150 |
| - // replacement failed to stop the ancestor node traversal. |
151 |
| - return Some(()); |
152 |
| - }; |
153 |
| - edit.replace( |
154 |
| - fr.range, |
155 |
| - ast::make::tuple_struct_pat( |
156 |
| - record_struct_pat.path()?, |
157 |
| - record_struct_pat |
158 |
| - .record_pat_field_list()? |
159 |
| - .fields() |
160 |
| - .filter_map(|pat| pat.pat()) |
161 |
| - ) |
162 |
| - .to_string() |
163 |
| - ); |
164 |
| - }, |
165 |
| - ast::RecordExpr(record_expr) => { |
166 |
| - let Some(fr) = ctx.sema.original_range_opt(record_expr.syntax()) else { |
167 |
| - // See the comment above. |
168 |
| - return Some(()); |
169 |
| - }; |
170 |
| - let path = record_expr.path()?; |
171 |
| - let args = record_expr |
172 |
| - .record_expr_field_list()? |
173 |
| - .fields() |
174 |
| - .filter_map(|f| f.expr()) |
175 |
| - .join(", "); |
176 |
| - |
177 |
| - edit.replace(fr.range, format!("{path}({args})")); |
178 |
| - }, |
179 |
| - _ => return None, |
180 |
| - } |
181 |
| - } |
182 |
| - Some(()) |
183 |
| - }; |
184 |
| - |
185 | 144 | for (file_id, refs) in usages {
|
186 | 145 | edit.edit_file(file_id);
|
187 | 146 | for r in refs {
|
188 |
| - for node in r.name.syntax().ancestors() { |
189 |
| - if edit_node(edit, node).is_some() { |
190 |
| - break; |
191 |
| - } |
192 |
| - } |
| 147 | + process_struct_name_reference(ctx, r, edit); |
| 148 | + } |
| 149 | + } |
| 150 | +} |
| 151 | + |
| 152 | +fn process_struct_name_reference( |
| 153 | + ctx: &AssistContext<'_>, |
| 154 | + r: FileReference, |
| 155 | + edit: &mut SourceChangeBuilder, |
| 156 | +) -> Option<()> { |
| 157 | + // First check if it's the last semgnet of a path that directly belongs to a record |
| 158 | + // expression/pattern. |
| 159 | + let name_ref = r.name.as_name_ref()?; |
| 160 | + let path_segment = name_ref.syntax().parent().and_then(ast::PathSegment::cast)?; |
| 161 | + // A `PathSegment` always belongs to a `Path`, so there's at least one `Path` at this point. |
| 162 | + let full_path = |
| 163 | + path_segment.syntax().parent()?.ancestors().map_while(ast::Path::cast).last().unwrap(); |
| 164 | + |
| 165 | + if full_path.segment().unwrap().name_ref()? != *name_ref { |
| 166 | + // `name_ref` isn't the last segment of the path, so `full_path` doesn't point to the |
| 167 | + // struct we want to edit. |
| 168 | + return None; |
| 169 | + } |
| 170 | + |
| 171 | + let parent = full_path.syntax().parent()?; |
| 172 | + match_ast! { |
| 173 | + match parent { |
| 174 | + ast::RecordPat(record_struct_pat) => { |
| 175 | + // When we failed to get the original range for the whole struct expression node, |
| 176 | + // we can't provide any reasonable edit. Leave it untouched. |
| 177 | + let file_range = ctx.sema.original_range_opt(record_struct_pat.syntax())?; |
| 178 | + edit.replace( |
| 179 | + file_range.range, |
| 180 | + ast::make::tuple_struct_pat( |
| 181 | + record_struct_pat.path()?, |
| 182 | + record_struct_pat |
| 183 | + .record_pat_field_list()? |
| 184 | + .fields() |
| 185 | + .filter_map(|pat| pat.pat()) |
| 186 | + ) |
| 187 | + .to_string() |
| 188 | + ); |
| 189 | + }, |
| 190 | + ast::RecordExpr(record_expr) => { |
| 191 | + // When we failed to get the original range for the whole struct pattern node, |
| 192 | + // we can't provide any reasonable edit. Leave it untouched. |
| 193 | + let file_range = ctx.sema.original_range_opt(record_expr.syntax())?; |
| 194 | + let path = record_expr.path()?; |
| 195 | + let args = record_expr |
| 196 | + .record_expr_field_list()? |
| 197 | + .fields() |
| 198 | + .filter_map(|f| f.expr()) |
| 199 | + .join(", "); |
| 200 | + |
| 201 | + edit.replace(file_range.range, format!("{path}({args})")); |
| 202 | + }, |
| 203 | + _ => {} |
193 | 204 | }
|
194 | 205 | }
|
| 206 | + |
| 207 | + Some(()) |
195 | 208 | }
|
196 | 209 |
|
197 | 210 | fn edit_field_references(
|
@@ -898,6 +911,69 @@ fn test() {
|
898 | 911 | let Struct(inner) = s;
|
899 | 912 | }
|
900 | 913 | }
|
| 914 | +"#, |
| 915 | + ); |
| 916 | + } |
| 917 | + |
| 918 | + #[test] |
| 919 | + fn struct_name_ref_may_not_be_part_of_struct_expr_or_struct_pat() { |
| 920 | + check_assist( |
| 921 | + convert_named_struct_to_tuple_struct, |
| 922 | + r#" |
| 923 | +struct $0Struct { |
| 924 | + inner: i32, |
| 925 | +} |
| 926 | +struct Outer<T> { |
| 927 | + value: T, |
| 928 | +} |
| 929 | +fn foo<T>() -> T { loop {} } |
| 930 | +
|
| 931 | +fn test() { |
| 932 | + Outer { |
| 933 | + value: foo::<Struct>(); |
| 934 | + } |
| 935 | +} |
| 936 | +
|
| 937 | +trait HasAssoc { |
| 938 | + type Assoc; |
| 939 | + fn test(); |
| 940 | +} |
| 941 | +impl HasAssoc for Struct { |
| 942 | + type Assoc = Outer<i32>; |
| 943 | + fn test() { |
| 944 | + let a = Self::Assoc { |
| 945 | + value: 42, |
| 946 | + }; |
| 947 | + let Self::Assoc { value } = a; |
| 948 | + } |
| 949 | +} |
| 950 | +"#, |
| 951 | + r#" |
| 952 | +struct Struct(i32); |
| 953 | +struct Outer<T> { |
| 954 | + value: T, |
| 955 | +} |
| 956 | +fn foo<T>() -> T { loop {} } |
| 957 | +
|
| 958 | +fn test() { |
| 959 | + Outer { |
| 960 | + value: foo::<Struct>(); |
| 961 | + } |
| 962 | +} |
| 963 | +
|
| 964 | +trait HasAssoc { |
| 965 | + type Assoc; |
| 966 | + fn test(); |
| 967 | +} |
| 968 | +impl HasAssoc for Struct { |
| 969 | + type Assoc = Outer<i32>; |
| 970 | + fn test() { |
| 971 | + let a = Self::Assoc { |
| 972 | + value: 42, |
| 973 | + }; |
| 974 | + let Self::Assoc { value } = a; |
| 975 | + } |
| 976 | +} |
901 | 977 | "#,
|
902 | 978 | );
|
903 | 979 | }
|
|
0 commit comments