1 //! See [`PathTransform`].
3 use crate::helpers::mod_path_to_ast;
5 use hir::{AsAssocItem, HirDisplay, SemanticsScope};
6 use rustc_hash::FxHashMap;
12 /// `PathTransform` substitutes path in SyntaxNodes in bulk.
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:
22 /// pub trait T<U> { fn foo(&self, _: U) -> A<U>; }
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<()> {}
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>,
42 impl<'a> PathTransform<'a> {
44 target_scope: &'a SemanticsScope<'a>,
45 source_scope: &'a SemanticsScope<'a>,
48 ) -> PathTransform<'a> {
52 generic_def: trait_.into(),
53 substs: get_syntactic_substs(impl_).unwrap_or_default(),
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> {
66 generic_def: function.into(),
67 substs: get_type_args_from_arg_list(generic_arg_list).unwrap_or_default(),
71 pub fn apply(&self, syntax: &SyntaxNode) {
72 self.build_ctx().apply(syntax)
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,
84 let substs_by_param: FxHashMap<_, _> = self
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())),
101 let default = t.default(db)?;
105 &default.display_source_code(db, source_module.into()).ok()?,
112 Ctx { substs: substs_by_param, target_module, source_scope: self.source_scope }
117 substs: FxHashMap<hir::TypeOrConstParam, ast::Type>,
118 target_module: hir::Module,
119 source_scope: &'a SemanticsScope<'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.
129 .filter_map(|event| match event {
130 syntax::WalkEvent::Enter(_) => None,
131 syntax::WalkEvent::Leave(node) => Some(node),
133 .filter_map(ast::Path::cast)
134 .collect::<Vec<_>>();
137 self.transform_path(path);
140 fn transform_path(&self, path: ast::Path) -> Option<()> {
141 if path.qualifier().is_some() {
144 if path.segment().map_or(false, |s| {
145 s.param_list().is_some() || (s.self_token().is_some() && path.parent_path().is_none())
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
152 let resolution = self.source_scope.speculative_resolve(&path)?;
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
167 let trait_ref = find_trait_for_assoc_item(
170 parent.segment()?.name_ref()?,
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),
177 match ast::make::ty_path(mod_path_to_ast(&found_path)) {
178 ast::Type::PathType(path_ty) => Some(path_ty),
183 let segment = ast::make::path_segment_ty(subst.clone(), trait_ref);
185 ast::make::path_from_segments(std::iter::once(segment), false);
186 ted::replace(path.syntax(), qualified.clone_for_update().syntax());
187 } else if let Some(path_ty) = ast::PathType::cast(parent) {
190 subst.clone_subtree().clone_for_update().syntax(),
195 subst.clone_subtree().clone_for_update().syntax(),
200 hir::PathResolution::Def(def) if def.as_assoc_item(self.source_scope.db).is_none() => {
201 if let hir::ModuleDef::Trait(_) = def {
202 if matches!(path.segment()?.kind()?, ast::PathSegmentKind::Type { .. }) {
203 // `speculative_resolve` resolves segments like `<T as
204 // Trait>` into `Trait`, but just the trait name should
205 // not be used as the replacement of the original
212 self.target_module.find_use_path(self.source_scope.db.upcast(), def)?;
213 let res = mod_path_to_ast(&found_path).clone_for_update();
214 if let Some(args) = path.segment().and_then(|it| it.generic_arg_list()) {
215 if let Some(segment) = res.segment() {
216 let old = segment.get_or_create_generic_arg_list();
217 ted::replace(old.syntax(), args.clone_subtree().syntax().clone_for_update())
220 ted::replace(path.syntax(), res.syntax())
222 hir::PathResolution::Local(_)
223 | hir::PathResolution::ConstParam(_)
224 | hir::PathResolution::SelfType(_)
225 | hir::PathResolution::Def(_)
226 | hir::PathResolution::BuiltinAttr(_)
227 | hir::PathResolution::ToolModule(_)
228 | hir::PathResolution::DeriveHelper(_) => (),
234 // FIXME: It would probably be nicer if we could get this via HIR (i.e. get the
235 // trait ref, and then go from the types in the substs back to the syntax).
236 fn get_syntactic_substs(impl_def: ast::Impl) -> Option<Vec<ast::Type>> {
237 let target_trait = impl_def.trait_()?;
238 let path_type = match target_trait {
239 ast::Type::PathType(path) => path,
242 let generic_arg_list = path_type.path()?.segment()?.generic_arg_list()?;
244 get_type_args_from_arg_list(generic_arg_list)
247 fn get_type_args_from_arg_list(generic_arg_list: ast::GenericArgList) -> Option<Vec<ast::Type>> {
248 let mut result = Vec::new();
249 for generic_arg in generic_arg_list.generic_args() {
250 if let ast::GenericArg::TypeArg(type_arg) = generic_arg {
251 result.push(type_arg.ty()?)
258 fn find_trait_for_assoc_item(
259 scope: &SemanticsScope<'_>,
260 type_param: hir::TypeParam,
261 assoc_item: ast::NameRef,
262 ) -> Option<hir::Trait> {
264 let trait_bounds = type_param.trait_bounds(db);
266 let assoc_item_name = assoc_item.text();
268 for trait_ in trait_bounds {
269 let names = trait_.items(db).into_iter().filter_map(|item| match item {
270 hir::AssocItem::TypeAlias(ta) => Some(ta.name(db)),
271 hir::AssocItem::Const(cst) => cst.name(db),
276 if assoc_item_name.as_str() == name.as_text()?.as_str() {
277 // It is fine to return the first match because in case of
278 // multiple possibilities, the exact trait must be disambiguated
279 // in the definition of trait being implemented, so this search
280 // should not be needed.