|
| 1 | +//! `AstTransformer`s are functions that replace nodes in an AST and can be easily combined. |
| 2 | +use std::collections::HashMap; |
| 3 | + |
| 4 | +use hir::{db::HirDatabase, InFile, PathResolution}; |
| 5 | +use ra_syntax::ast::{self, make, AstNode}; |
| 6 | + |
| 7 | +pub trait AstTransform<'a> { |
| 8 | + fn get_substitution( |
| 9 | + &self, |
| 10 | + node: InFile<&ra_syntax::SyntaxNode>, |
| 11 | + ) -> Option<ra_syntax::SyntaxNode>; |
| 12 | + |
| 13 | + fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a>; |
| 14 | + fn or<T: AstTransform<'a> + 'a>(self, other: T) -> Box<dyn AstTransform<'a> + 'a> |
| 15 | + where |
| 16 | + Self: Sized + 'a, |
| 17 | + { |
| 18 | + self.chain_before(Box::new(other)) |
| 19 | + } |
| 20 | +} |
| 21 | + |
| 22 | +struct NullTransformer; |
| 23 | + |
| 24 | +impl<'a> AstTransform<'a> for NullTransformer { |
| 25 | + fn get_substitution( |
| 26 | + &self, |
| 27 | + _node: InFile<&ra_syntax::SyntaxNode>, |
| 28 | + ) -> Option<ra_syntax::SyntaxNode> { |
| 29 | + None |
| 30 | + } |
| 31 | + fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> { |
| 32 | + other |
| 33 | + } |
| 34 | +} |
| 35 | + |
| 36 | +pub struct SubstituteTypeParams<'a, DB: HirDatabase> { |
| 37 | + db: &'a DB, |
| 38 | + substs: HashMap<hir::TypeParam, ast::TypeRef>, |
| 39 | + previous: Box<dyn AstTransform<'a> + 'a>, |
| 40 | +} |
| 41 | + |
| 42 | +impl<'a, DB: HirDatabase> SubstituteTypeParams<'a, DB> { |
| 43 | + pub fn for_trait_impl( |
| 44 | + db: &'a DB, |
| 45 | + trait_: hir::Trait, |
| 46 | + impl_block: ast::ImplBlock, |
| 47 | + ) -> SubstituteTypeParams<'a, DB> { |
| 48 | + let substs = get_syntactic_substs(impl_block).unwrap_or_default(); |
| 49 | + let generic_def: hir::GenericDef = trait_.into(); |
| 50 | + let substs_by_param: HashMap<_, _> = generic_def |
| 51 | + .params(db) |
| 52 | + .into_iter() |
| 53 | + // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky |
| 54 | + .skip(1) |
| 55 | + .zip(substs.into_iter()) |
| 56 | + .collect(); |
| 57 | + return SubstituteTypeParams { |
| 58 | + db, |
| 59 | + substs: substs_by_param, |
| 60 | + previous: Box::new(NullTransformer), |
| 61 | + }; |
| 62 | + |
| 63 | + fn get_syntactic_substs(impl_block: ast::ImplBlock) -> Option<Vec<ast::TypeRef>> { |
| 64 | + let target_trait = impl_block.target_trait()?; |
| 65 | + let path_type = match target_trait { |
| 66 | + ast::TypeRef::PathType(path) => path, |
| 67 | + _ => return None, |
| 68 | + }; |
| 69 | + let type_arg_list = path_type.path()?.segment()?.type_arg_list()?; |
| 70 | + let mut result = Vec::new(); |
| 71 | + for type_arg in type_arg_list.type_args() { |
| 72 | + let type_arg: ast::TypeArg = type_arg; |
| 73 | + result.push(type_arg.type_ref()?); |
| 74 | + } |
| 75 | + Some(result) |
| 76 | + } |
| 77 | + } |
| 78 | + fn get_substitution_inner( |
| 79 | + &self, |
| 80 | + node: InFile<&ra_syntax::SyntaxNode>, |
| 81 | + ) -> Option<ra_syntax::SyntaxNode> { |
| 82 | + let type_ref = ast::TypeRef::cast(node.value.clone())?; |
| 83 | + let path = match &type_ref { |
| 84 | + ast::TypeRef::PathType(path_type) => path_type.path()?, |
| 85 | + _ => return None, |
| 86 | + }; |
| 87 | + let analyzer = hir::SourceAnalyzer::new(self.db, node, None); |
| 88 | + let resolution = analyzer.resolve_path(self.db, &path)?; |
| 89 | + match resolution { |
| 90 | + hir::PathResolution::TypeParam(tp) => Some(self.substs.get(&tp)?.syntax().clone()), |
| 91 | + _ => None, |
| 92 | + } |
| 93 | + } |
| 94 | +} |
| 95 | + |
| 96 | +impl<'a, DB: HirDatabase> AstTransform<'a> for SubstituteTypeParams<'a, DB> { |
| 97 | + fn get_substitution( |
| 98 | + &self, |
| 99 | + node: InFile<&ra_syntax::SyntaxNode>, |
| 100 | + ) -> Option<ra_syntax::SyntaxNode> { |
| 101 | + self.get_substitution_inner(node).or_else(|| self.previous.get_substitution(node)) |
| 102 | + } |
| 103 | + fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> { |
| 104 | + Box::new(SubstituteTypeParams { previous: other, ..self }) |
| 105 | + } |
| 106 | +} |
| 107 | + |
| 108 | +pub struct QualifyPaths<'a, DB: HirDatabase> { |
| 109 | + db: &'a DB, |
| 110 | + from: Option<hir::Module>, |
| 111 | + previous: Box<dyn AstTransform<'a> + 'a>, |
| 112 | +} |
| 113 | + |
| 114 | +impl<'a, DB: HirDatabase> QualifyPaths<'a, DB> { |
| 115 | + pub fn new(db: &'a DB, from: Option<hir::Module>) -> Self { |
| 116 | + Self { db, from, previous: Box::new(NullTransformer) } |
| 117 | + } |
| 118 | + |
| 119 | + fn get_substitution_inner( |
| 120 | + &self, |
| 121 | + node: InFile<&ra_syntax::SyntaxNode>, |
| 122 | + ) -> Option<ra_syntax::SyntaxNode> { |
| 123 | + // FIXME handle value ns? |
| 124 | + let from = self.from?; |
| 125 | + let p = ast::Path::cast(node.value.clone())?; |
| 126 | + if p.segment().and_then(|s| s.param_list()).is_some() { |
| 127 | + // don't try to qualify `Fn(Foo) -> Bar` paths, they are in prelude anyway |
| 128 | + return None; |
| 129 | + } |
| 130 | + let analyzer = hir::SourceAnalyzer::new(self.db, node, None); |
| 131 | + let resolution = analyzer.resolve_path(self.db, &p)?; |
| 132 | + match resolution { |
| 133 | + PathResolution::Def(def) => { |
| 134 | + let found_path = from.find_path(self.db, def)?; |
| 135 | + let args = p |
| 136 | + .segment() |
| 137 | + .and_then(|s| s.type_arg_list()) |
| 138 | + .map(|arg_list| apply(self, node.with_value(arg_list))); |
| 139 | + Some(make::path_with_type_arg_list(found_path.to_ast(), args).syntax().clone()) |
| 140 | + } |
| 141 | + PathResolution::Local(_) |
| 142 | + | PathResolution::TypeParam(_) |
| 143 | + | PathResolution::SelfType(_) => None, |
| 144 | + PathResolution::Macro(_) => None, |
| 145 | + PathResolution::AssocItem(_) => None, |
| 146 | + } |
| 147 | + } |
| 148 | +} |
| 149 | + |
| 150 | +pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: InFile<N>) -> N { |
| 151 | + let syntax = node.value.syntax(); |
| 152 | + let result = ra_syntax::algo::replace_descendants(syntax, &|element| match element { |
| 153 | + ra_syntax::SyntaxElement::Node(n) => { |
| 154 | + let replacement = transformer.get_substitution(node.with_value(&n))?; |
| 155 | + Some(replacement.into()) |
| 156 | + } |
| 157 | + _ => None, |
| 158 | + }); |
| 159 | + N::cast(result).unwrap() |
| 160 | +} |
| 161 | + |
| 162 | +impl<'a, DB: HirDatabase> AstTransform<'a> for QualifyPaths<'a, DB> { |
| 163 | + fn get_substitution( |
| 164 | + &self, |
| 165 | + node: InFile<&ra_syntax::SyntaxNode>, |
| 166 | + ) -> Option<ra_syntax::SyntaxNode> { |
| 167 | + self.get_substitution_inner(node).or_else(|| self.previous.get_substitution(node)) |
| 168 | + } |
| 169 | + fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> { |
| 170 | + Box::new(QualifyPaths { previous: other, ..self }) |
| 171 | + } |
| 172 | +} |
| 173 | + |
| 174 | +// FIXME: It would probably be nicer if we could get this via HIR (i.e. get the |
| 175 | +// trait ref, and then go from the types in the substs back to the syntax) |
| 176 | +// FIXME: This should be a general utility (not even just for assists) |
| 177 | + |
| 178 | +// FIXME: This should be a general utility (not even just for assists) |
0 commit comments