]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs
Rollup merge of #101308 - nerdypepper:feature/is-ascii-octdigit, r=joshtriplett
[rust.git] / src / tools / rust-analyzer / crates / ide-db / src / path_transform.rs
1 //! See [`PathTransform`].
2
3 use crate::helpers::mod_path_to_ast;
4 use either::Either;
5 use hir::{AsAssocItem, HirDisplay, SemanticsScope};
6 use rustc_hash::FxHashMap;
7 use syntax::{
8     ast::{self, AstNode},
9     ted, SyntaxNode,
10 };
11
12 /// `PathTransform` substitutes path in SyntaxNodes in bulk.
13 ///
14 /// This is mostly useful for IDE code generation. If you paste some existing
15 /// code into a new context (for example, to add method overrides to an `impl`
16 /// block), you generally want to appropriately qualify the names, and sometimes
17 /// you might want to substitute generic parameters as well:
18 ///
19 /// ```
20 /// mod x {
21 ///   pub struct A<V>;
22 ///   pub trait T<U> { fn foo(&self, _: U) -> A<U>; }
23 /// }
24 ///
25 /// mod y {
26 ///   use x::T;
27 ///
28 ///   impl T<()> for () {
29 ///      // If we invoke **Add Missing Members** here, we want to copy-paste `foo`.
30 ///      // But we want a slightly-modified version of it:
31 ///      fn foo(&self, _: ()) -> x::A<()> {}
32 ///   }
33 /// }
34 /// ```
35 pub struct PathTransform<'a> {
36     generic_def: hir::GenericDef,
37     substs: Vec<ast::Type>,
38     target_scope: &'a SemanticsScope<'a>,
39     source_scope: &'a SemanticsScope<'a>,
40 }
41
42 impl<'a> PathTransform<'a> {
43     pub fn trait_impl(
44         target_scope: &'a SemanticsScope<'a>,
45         source_scope: &'a SemanticsScope<'a>,
46         trait_: hir::Trait,
47         impl_: ast::Impl,
48     ) -> PathTransform<'a> {
49         PathTransform {
50             source_scope,
51             target_scope,
52             generic_def: trait_.into(),
53             substs: get_syntactic_substs(impl_).unwrap_or_default(),
54         }
55     }
56
57     pub fn function_call(
58         target_scope: &'a SemanticsScope<'a>,
59         source_scope: &'a SemanticsScope<'a>,
60         function: hir::Function,
61         generic_arg_list: ast::GenericArgList,
62     ) -> PathTransform<'a> {
63         PathTransform {
64             source_scope,
65             target_scope,
66             generic_def: function.into(),
67             substs: get_type_args_from_arg_list(generic_arg_list).unwrap_or_default(),
68         }
69     }
70
71     pub fn apply(&self, syntax: &SyntaxNode) {
72         self.build_ctx().apply(syntax)
73     }
74
75     fn build_ctx(&self) -> Ctx<'a> {
76         let db = self.source_scope.db;
77         let target_module = self.target_scope.module();
78         let source_module = self.source_scope.module();
79         let skip = match self.generic_def {
80             // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky
81             hir::GenericDef::Trait(_) => 1,
82             _ => 0,
83         };
84         let substs_by_param: FxHashMap<_, _> = self
85             .generic_def
86             .type_params(db)
87             .into_iter()
88             .skip(skip)
89             // The actual list of trait type parameters may be longer than the one
90             // used in the `impl` block due to trailing default type parameters.
91             // For that case we extend the `substs` with an empty iterator so we
92             // can still hit those trailing values and check if they actually have
93             // a default type. If they do, go for that type from `hir` to `ast` so
94             // the resulting change can be applied correctly.
95             .zip(self.substs.iter().map(Some).chain(std::iter::repeat(None)))
96             .filter_map(|(k, v)| match k.split(db) {
97                 Either::Left(_) => None,
98                 Either::Right(t) => match v {
99                     Some(v) => Some((k, v.clone())),
100                     None => {
101                         let default = t.default(db)?;
102                         Some((
103                             k,
104                             ast::make::ty(
105                                 &default.display_source_code(db, source_module.into()).ok()?,
106                             ),
107                         ))
108                     }
109                 },
110             })
111             .collect();
112         Ctx { substs: substs_by_param, target_module, source_scope: self.source_scope }
113     }
114 }
115
116 struct Ctx<'a> {
117     substs: FxHashMap<hir::TypeOrConstParam, ast::Type>,
118     target_module: hir::Module,
119     source_scope: &'a SemanticsScope<'a>,
120 }
121
122 impl<'a> Ctx<'a> {
123     fn apply(&self, item: &SyntaxNode) {
124         // `transform_path` may update a node's parent and that would break the
125         // tree traversal. Thus all paths in the tree are collected into a vec
126         // so that such operation is safe.
127         let paths = item
128             .preorder()
129             .filter_map(|event| match event {
130                 syntax::WalkEvent::Enter(_) => None,
131                 syntax::WalkEvent::Leave(node) => Some(node),
132             })
133             .filter_map(ast::Path::cast)
134             .collect::<Vec<_>>();
135
136         for path in paths {
137             self.transform_path(path);
138         }
139     }
140     fn transform_path(&self, path: ast::Path) -> Option<()> {
141         if path.qualifier().is_some() {
142             return None;
143         }
144         if path.segment().map_or(false, |s| {
145             s.param_list().is_some() || (s.self_token().is_some() && path.parent_path().is_none())
146         }) {
147             // don't try to qualify `Fn(Foo) -> Bar` paths, they are in prelude anyway
148             // don't try to qualify sole `self` either, they are usually locals, but are returned as modules due to namespace clashing
149             return None;
150         }
151
152         let resolution = self.source_scope.speculative_resolve(&path)?;
153
154         match resolution {
155             hir::PathResolution::TypeParam(tp) => {
156                 if let Some(subst) = self.substs.get(&tp.merge()) {
157                     let parent = path.syntax().parent()?;
158                     if let Some(parent) = ast::Path::cast(parent.clone()) {
159                         // Path inside path means that there is an associated
160                         // type/constant on the type parameter. It is necessary
161                         // to fully qualify the type with `as Trait`. Even
162                         // though it might be unnecessary if `subst` is generic
163                         // type, always fully qualifying the path is safer
164                         // because of potential clash of associated types from
165                         // multiple traits
166
167                         let trait_ref = find_trait_for_assoc_item(
168                             self.source_scope,
169                             tp,
170                             parent.segment()?.name_ref()?,
171                         )
172                         .and_then(|trait_ref| {
173                             let found_path = self.target_module.find_use_path(
174                                 self.source_scope.db.upcast(),
175                                 hir::ModuleDef::Trait(trait_ref),
176                                 false,
177                             )?;
178                             match ast::make::ty_path(mod_path_to_ast(&found_path)) {
179                                 ast::Type::PathType(path_ty) => Some(path_ty),
180                                 _ => None,
181                             }
182                         });
183
184                         let segment = ast::make::path_segment_ty(subst.clone(), trait_ref);
185                         let qualified =
186                             ast::make::path_from_segments(std::iter::once(segment), false);
187                         ted::replace(path.syntax(), qualified.clone_for_update().syntax());
188                     } else if let Some(path_ty) = ast::PathType::cast(parent) {
189                         ted::replace(
190                             path_ty.syntax(),
191                             subst.clone_subtree().clone_for_update().syntax(),
192                         );
193                     } else {
194                         ted::replace(
195                             path.syntax(),
196                             subst.clone_subtree().clone_for_update().syntax(),
197                         );
198                     }
199                 }
200             }
201             hir::PathResolution::Def(def) if def.as_assoc_item(self.source_scope.db).is_none() => {
202                 if let hir::ModuleDef::Trait(_) = def {
203                     if matches!(path.segment()?.kind()?, ast::PathSegmentKind::Type { .. }) {
204                         // `speculative_resolve` resolves segments like `<T as
205                         // Trait>` into `Trait`, but just the trait name should
206                         // not be used as the replacement of the original
207                         // segment.
208                         return None;
209                     }
210                 }
211
212                 let found_path =
213                     self.target_module.find_use_path(self.source_scope.db.upcast(), def, false)?;
214                 let res = mod_path_to_ast(&found_path).clone_for_update();
215                 if let Some(args) = path.segment().and_then(|it| it.generic_arg_list()) {
216                     if let Some(segment) = res.segment() {
217                         let old = segment.get_or_create_generic_arg_list();
218                         ted::replace(old.syntax(), args.clone_subtree().syntax().clone_for_update())
219                     }
220                 }
221                 ted::replace(path.syntax(), res.syntax())
222             }
223             hir::PathResolution::Local(_)
224             | hir::PathResolution::ConstParam(_)
225             | hir::PathResolution::SelfType(_)
226             | hir::PathResolution::Def(_)
227             | hir::PathResolution::BuiltinAttr(_)
228             | hir::PathResolution::ToolModule(_)
229             | hir::PathResolution::DeriveHelper(_) => (),
230         }
231         Some(())
232     }
233 }
234
235 // FIXME: It would probably be nicer if we could get this via HIR (i.e. get the
236 // trait ref, and then go from the types in the substs back to the syntax).
237 fn get_syntactic_substs(impl_def: ast::Impl) -> Option<Vec<ast::Type>> {
238     let target_trait = impl_def.trait_()?;
239     let path_type = match target_trait {
240         ast::Type::PathType(path) => path,
241         _ => return None,
242     };
243     let generic_arg_list = path_type.path()?.segment()?.generic_arg_list()?;
244
245     get_type_args_from_arg_list(generic_arg_list)
246 }
247
248 fn get_type_args_from_arg_list(generic_arg_list: ast::GenericArgList) -> Option<Vec<ast::Type>> {
249     let mut result = Vec::new();
250     for generic_arg in generic_arg_list.generic_args() {
251         if let ast::GenericArg::TypeArg(type_arg) = generic_arg {
252             result.push(type_arg.ty()?)
253         }
254     }
255
256     Some(result)
257 }
258
259 fn find_trait_for_assoc_item(
260     scope: &SemanticsScope<'_>,
261     type_param: hir::TypeParam,
262     assoc_item: ast::NameRef,
263 ) -> Option<hir::Trait> {
264     let db = scope.db;
265     let trait_bounds = type_param.trait_bounds(db);
266
267     let assoc_item_name = assoc_item.text();
268
269     for trait_ in trait_bounds {
270         let names = trait_.items(db).into_iter().filter_map(|item| match item {
271             hir::AssocItem::TypeAlias(ta) => Some(ta.name(db)),
272             hir::AssocItem::Const(cst) => cst.name(db),
273             _ => None,
274         });
275
276         for name in names {
277             if assoc_item_name.as_str() == name.as_text()?.as_str() {
278                 // It is fine to return the first match because in case of
279                 // multiple possibilities, the exact trait must be disambiguated
280                 // in the definition of trait being implemented, so this search
281                 // should not be needed.
282                 return Some(trait_);
283             }
284         }
285     }
286
287     None
288 }