Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-db/src/path_transform.rs')
| -rw-r--r-- | crates/ide-db/src/path_transform.rs | 169 |
1 files changed, 127 insertions, 42 deletions
diff --git a/crates/ide-db/src/path_transform.rs b/crates/ide-db/src/path_transform.rs index 0ee627a44c..73e6a920ee 100644 --- a/crates/ide-db/src/path_transform.rs +++ b/crates/ide-db/src/path_transform.rs @@ -9,6 +9,19 @@ use syntax::{ ted, SyntaxNode, }; +#[derive(Default)] +struct AstSubsts { + types_and_consts: Vec<TypeOrConst>, + lifetimes: Vec<ast::LifetimeArg>, +} + +enum TypeOrConst { + Either(ast::TypeArg), // indistinguishable type or const param + Const(ast::ConstArg), +} + +type LifetimeName = String; + /// `PathTransform` substitutes path in SyntaxNodes in bulk. /// /// This is mostly useful for IDE code generation. If you paste some existing @@ -34,7 +47,7 @@ use syntax::{ /// ``` pub struct PathTransform<'a> { generic_def: Option<hir::GenericDef>, - substs: Vec<ast::Type>, + substs: AstSubsts, target_scope: &'a SemanticsScope<'a>, source_scope: &'a SemanticsScope<'a>, } @@ -72,7 +85,12 @@ impl<'a> PathTransform<'a> { target_scope: &'a SemanticsScope<'a>, source_scope: &'a SemanticsScope<'a>, ) -> PathTransform<'a> { - PathTransform { source_scope, target_scope, generic_def: None, substs: Vec::new() } + PathTransform { + source_scope, + target_scope, + generic_def: None, + substs: AstSubsts::default(), + } } pub fn apply(&self, syntax: &SyntaxNode) { @@ -91,12 +109,14 @@ impl<'a> PathTransform<'a> { let target_module = self.target_scope.module(); let source_module = self.source_scope.module(); let skip = match self.generic_def { - // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky + // this is a trait impl, so we need to skip the first type parameter (i.e. Self) -- this is a bit hacky Some(hir::GenericDef::Trait(_)) => 1, _ => 0, }; - let substs_by_param: FxHashMap<_, _> = self - .generic_def + let mut type_substs: FxHashMap<hir::TypeParam, ast::Type> = Default::default(); + let mut const_substs: FxHashMap<hir::ConstParam, SyntaxNode> = Default::default(); + let mut default_types: Vec<hir::TypeParam> = Default::default(); + self.generic_def .into_iter() .flat_map(|it| it.type_params(db)) .skip(skip) @@ -106,53 +126,105 @@ impl<'a> PathTransform<'a> { // can still hit those trailing values and check if they actually have // a default type. If they do, go for that type from `hir` to `ast` so // the resulting change can be applied correctly. - .zip(self.substs.iter().map(Some).chain(std::iter::repeat(None))) - .filter_map(|(k, v)| match k.split(db) { - Either::Left(_) => None, - Either::Right(t) => match v { - Some(v) => Some((k, v.clone())), - None => { - let default = t.default(db)?; - Some(( - k, - ast::make::ty( - &default - .display_source_code(db, source_module.into(), false) - .ok()?, - ), - )) + .zip(self.substs.types_and_consts.iter().map(Some).chain(std::iter::repeat(None))) + .for_each(|(k, v)| match (k.split(db), v) { + (Either::Right(k), Some(TypeOrConst::Either(v))) => { + if let Some(ty) = v.ty() { + type_substs.insert(k, ty.clone()); + } + } + (Either::Right(k), None) => { + if let Some(default) = k.default(db) { + if let Some(default) = + &default.display_source_code(db, source_module.into(), false).ok() + { + type_substs.insert(k, ast::make::ty(default).clone_for_update()); + default_types.push(k); + } + } + } + (Either::Left(k), Some(TypeOrConst::Either(v))) => { + if let Some(ty) = v.ty() { + const_substs.insert(k, ty.syntax().clone()); } - }, - }) + } + (Either::Left(k), Some(TypeOrConst::Const(v))) => { + if let Some(expr) = v.expr() { + // FIXME: expressions in curly brackets can cause ambiguity after insertion + // (e.g. `N * 2` -> `{1 + 1} * 2`; it's unclear whether `{1 + 1}` + // is a standalone statement or a part of another expresson) + // and sometimes require slight modifications; see + // https://doc.rust-lang.org/reference/statements.html#expression-statements + const_substs.insert(k, expr.syntax().clone()); + } + } + (Either::Left(_), None) => (), // FIXME: get default const value + _ => (), // ignore mismatching params + }); + let lifetime_substs: FxHashMap<_, _> = self + .generic_def + .into_iter() + .flat_map(|it| it.lifetime_params(db)) + .zip(self.substs.lifetimes.clone()) + .filter_map(|(k, v)| Some((k.name(db).display(db.upcast()).to_string(), v.lifetime()?))) .collect(); - Ctx { substs: substs_by_param, target_module, source_scope: self.source_scope } + let ctx = Ctx { + type_substs, + const_substs, + lifetime_substs, + target_module, + source_scope: self.source_scope, + }; + ctx.transform_default_type_substs(default_types); + ctx } } struct Ctx<'a> { - substs: FxHashMap<hir::TypeOrConstParam, ast::Type>, + type_substs: FxHashMap<hir::TypeParam, ast::Type>, + const_substs: FxHashMap<hir::ConstParam, SyntaxNode>, + lifetime_substs: FxHashMap<LifetimeName, ast::Lifetime>, target_module: hir::Module, source_scope: &'a SemanticsScope<'a>, } +fn postorder(item: &SyntaxNode) -> impl Iterator<Item = SyntaxNode> { + item.preorder().filter_map(|event| match event { + syntax::WalkEvent::Enter(_) => None, + syntax::WalkEvent::Leave(node) => Some(node), + }) +} + impl<'a> Ctx<'a> { fn apply(&self, item: &SyntaxNode) { // `transform_path` may update a node's parent and that would break the // tree traversal. Thus all paths in the tree are collected into a vec // so that such operation is safe. - let paths = item - .preorder() - .filter_map(|event| match event { - syntax::WalkEvent::Enter(_) => None, - syntax::WalkEvent::Leave(node) => Some(node), - }) - .filter_map(ast::Path::cast) - .collect::<Vec<_>>(); - + let paths = postorder(item).filter_map(ast::Path::cast).collect::<Vec<_>>(); for path in paths { self.transform_path(path); } + + postorder(item).filter_map(ast::Lifetime::cast).for_each(|lifetime| { + if let Some(subst) = self.lifetime_substs.get(&lifetime.syntax().text().to_string()) { + ted::replace(lifetime.syntax(), subst.clone_subtree().clone_for_update().syntax()); + } + }); } + + fn transform_default_type_substs(&self, default_types: Vec<hir::TypeParam>) { + for k in default_types { + let v = self.type_substs.get(&k).unwrap(); + // `transform_path` may update a node's parent and that would break the + // tree traversal. Thus all paths in the tree are collected into a vec + // so that such operation is safe. + let paths = postorder(&v.syntax()).filter_map(ast::Path::cast).collect::<Vec<_>>(); + for path in paths { + self.transform_path(path); + } + } + } + fn transform_path(&self, path: ast::Path) -> Option<()> { if path.qualifier().is_some() { return None; @@ -169,7 +241,7 @@ impl<'a> Ctx<'a> { match resolution { hir::PathResolution::TypeParam(tp) => { - if let Some(subst) = self.substs.get(&tp.merge()) { + if let Some(subst) = self.type_substs.get(&tp) { let parent = path.syntax().parent()?; if let Some(parent) = ast::Path::cast(parent.clone()) { // Path inside path means that there is an associated @@ -236,8 +308,12 @@ impl<'a> Ctx<'a> { } ted::replace(path.syntax(), res.syntax()) } + hir::PathResolution::ConstParam(cp) => { + if let Some(subst) = self.const_substs.get(&cp) { + ted::replace(path.syntax(), subst.clone_subtree().clone_for_update()); + } + } hir::PathResolution::Local(_) - | hir::PathResolution::ConstParam(_) | hir::PathResolution::SelfType(_) | hir::PathResolution::Def(_) | hir::PathResolution::BuiltinAttr(_) @@ -250,7 +326,7 @@ impl<'a> Ctx<'a> { // FIXME: It would probably be nicer if we could get this via HIR (i.e. get the // trait ref, and then go from the types in the substs back to the syntax). -fn get_syntactic_substs(impl_def: ast::Impl) -> Option<Vec<ast::Type>> { +fn get_syntactic_substs(impl_def: ast::Impl) -> Option<AstSubsts> { let target_trait = impl_def.trait_()?; let path_type = match target_trait { ast::Type::PathType(path) => path, @@ -261,13 +337,22 @@ fn get_syntactic_substs(impl_def: ast::Impl) -> Option<Vec<ast::Type>> { get_type_args_from_arg_list(generic_arg_list) } -fn get_type_args_from_arg_list(generic_arg_list: ast::GenericArgList) -> Option<Vec<ast::Type>> { - let mut result = Vec::new(); - for generic_arg in generic_arg_list.generic_args() { - if let ast::GenericArg::TypeArg(type_arg) = generic_arg { - result.push(type_arg.ty()?) +fn get_type_args_from_arg_list(generic_arg_list: ast::GenericArgList) -> Option<AstSubsts> { + let mut result = AstSubsts::default(); + generic_arg_list.generic_args().for_each(|generic_arg| match generic_arg { + // Const params are marked as consts on definition only, + // being passed to the trait they are indistguishable from type params; + // anyway, we don't really need to distinguish them here. + ast::GenericArg::TypeArg(type_arg) => { + result.types_and_consts.push(TypeOrConst::Either(type_arg)) } - } + // Some const values are recognized correctly. + ast::GenericArg::ConstArg(const_arg) => { + result.types_and_consts.push(TypeOrConst::Const(const_arg)); + } + ast::GenericArg::LifetimeArg(l_arg) => result.lifetimes.push(l_arg), + _ => (), + }); Some(result) } |