diff options
author | Igor Aleksanov <popzxc@yandex.ru> | 2020-08-14 05:34:07 +0100 |
---|---|---|
committer | Igor Aleksanov <popzxc@yandex.ru> | 2020-08-14 05:34:07 +0100 |
commit | c26c911ec1e6c2ad1dcb7d155a6a1d528839ad1a (patch) | |
tree | 7cff36c38234be0afb65273146d8247083a5cfeb /crates/assists/src/utils.rs | |
parent | 3c018bf84de5c693b5ee1c6bec0fed3b201c2060 (diff) | |
parent | f1f73649a686dc6e6449afc35e0fa6fed00e225d (diff) |
Merge branch 'master' into add-disable-diagnostics
Diffstat (limited to 'crates/assists/src/utils.rs')
-rw-r--r-- | crates/assists/src/utils.rs | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs new file mode 100644 index 000000000..84ccacafe --- /dev/null +++ b/crates/assists/src/utils.rs | |||
@@ -0,0 +1,313 @@ | |||
1 | //! Assorted functions shared by several assists. | ||
2 | pub(crate) mod insert_use; | ||
3 | |||
4 | use std::{iter, ops}; | ||
5 | |||
6 | use hir::{Adt, Crate, Enum, ScopeDef, Semantics, Trait, Type}; | ||
7 | use ide_db::RootDatabase; | ||
8 | use itertools::Itertools; | ||
9 | use rustc_hash::FxHashSet; | ||
10 | use syntax::{ | ||
11 | ast::{self, make, NameOwner}, | ||
12 | AstNode, | ||
13 | SyntaxKind::*, | ||
14 | SyntaxNode, TextSize, T, | ||
15 | }; | ||
16 | |||
17 | use crate::assist_config::SnippetCap; | ||
18 | |||
19 | pub(crate) use insert_use::{find_insert_use_container, insert_use_statement}; | ||
20 | |||
21 | pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr { | ||
22 | extract_trivial_expression(&block) | ||
23 | .filter(|expr| !expr.syntax().text().contains_char('\n')) | ||
24 | .unwrap_or_else(|| block.into()) | ||
25 | } | ||
26 | |||
27 | pub fn extract_trivial_expression(block: &ast::BlockExpr) -> Option<ast::Expr> { | ||
28 | let has_anything_else = |thing: &SyntaxNode| -> bool { | ||
29 | let mut non_trivial_children = | ||
30 | block.syntax().children_with_tokens().filter(|it| match it.kind() { | ||
31 | WHITESPACE | T!['{'] | T!['}'] => false, | ||
32 | _ => it.as_node() != Some(thing), | ||
33 | }); | ||
34 | non_trivial_children.next().is_some() | ||
35 | }; | ||
36 | |||
37 | if let Some(expr) = block.expr() { | ||
38 | if has_anything_else(expr.syntax()) { | ||
39 | return None; | ||
40 | } | ||
41 | return Some(expr); | ||
42 | } | ||
43 | // Unwrap `{ continue; }` | ||
44 | let (stmt,) = block.statements().next_tuple()?; | ||
45 | if let ast::Stmt::ExprStmt(expr_stmt) = stmt { | ||
46 | if has_anything_else(expr_stmt.syntax()) { | ||
47 | return None; | ||
48 | } | ||
49 | let expr = expr_stmt.expr()?; | ||
50 | match expr.syntax().kind() { | ||
51 | CONTINUE_EXPR | BREAK_EXPR | RETURN_EXPR => return Some(expr), | ||
52 | _ => (), | ||
53 | } | ||
54 | } | ||
55 | None | ||
56 | } | ||
57 | |||
58 | #[derive(Clone, Copy, Debug)] | ||
59 | pub(crate) enum Cursor<'a> { | ||
60 | Replace(&'a SyntaxNode), | ||
61 | Before(&'a SyntaxNode), | ||
62 | } | ||
63 | |||
64 | impl<'a> Cursor<'a> { | ||
65 | fn node(self) -> &'a SyntaxNode { | ||
66 | match self { | ||
67 | Cursor::Replace(node) | Cursor::Before(node) => node, | ||
68 | } | ||
69 | } | ||
70 | } | ||
71 | |||
72 | pub(crate) fn render_snippet(_cap: SnippetCap, node: &SyntaxNode, cursor: Cursor) -> String { | ||
73 | assert!(cursor.node().ancestors().any(|it| it == *node)); | ||
74 | let range = cursor.node().text_range() - node.text_range().start(); | ||
75 | let range: ops::Range<usize> = range.into(); | ||
76 | |||
77 | let mut placeholder = cursor.node().to_string(); | ||
78 | escape(&mut placeholder); | ||
79 | let tab_stop = match cursor { | ||
80 | Cursor::Replace(placeholder) => format!("${{0:{}}}", placeholder), | ||
81 | Cursor::Before(placeholder) => format!("$0{}", placeholder), | ||
82 | }; | ||
83 | |||
84 | let mut buf = node.to_string(); | ||
85 | buf.replace_range(range, &tab_stop); | ||
86 | return buf; | ||
87 | |||
88 | fn escape(buf: &mut String) { | ||
89 | stdx::replace(buf, '{', r"\{"); | ||
90 | stdx::replace(buf, '}', r"\}"); | ||
91 | stdx::replace(buf, '$', r"\$"); | ||
92 | } | ||
93 | } | ||
94 | |||
95 | pub fn get_missing_assoc_items( | ||
96 | sema: &Semantics<RootDatabase>, | ||
97 | impl_def: &ast::Impl, | ||
98 | ) -> Vec<hir::AssocItem> { | ||
99 | // Names must be unique between constants and functions. However, type aliases | ||
100 | // may share the same name as a function or constant. | ||
101 | let mut impl_fns_consts = FxHashSet::default(); | ||
102 | let mut impl_type = FxHashSet::default(); | ||
103 | |||
104 | if let Some(item_list) = impl_def.assoc_item_list() { | ||
105 | for item in item_list.assoc_items() { | ||
106 | match item { | ||
107 | ast::AssocItem::Fn(f) => { | ||
108 | if let Some(n) = f.name() { | ||
109 | impl_fns_consts.insert(n.syntax().to_string()); | ||
110 | } | ||
111 | } | ||
112 | |||
113 | ast::AssocItem::TypeAlias(t) => { | ||
114 | if let Some(n) = t.name() { | ||
115 | impl_type.insert(n.syntax().to_string()); | ||
116 | } | ||
117 | } | ||
118 | |||
119 | ast::AssocItem::Const(c) => { | ||
120 | if let Some(n) = c.name() { | ||
121 | impl_fns_consts.insert(n.syntax().to_string()); | ||
122 | } | ||
123 | } | ||
124 | ast::AssocItem::MacroCall(_) => (), | ||
125 | } | ||
126 | } | ||
127 | } | ||
128 | |||
129 | resolve_target_trait(sema, impl_def).map_or(vec![], |target_trait| { | ||
130 | target_trait | ||
131 | .items(sema.db) | ||
132 | .iter() | ||
133 | .filter(|i| match i { | ||
134 | hir::AssocItem::Function(f) => { | ||
135 | !impl_fns_consts.contains(&f.name(sema.db).to_string()) | ||
136 | } | ||
137 | hir::AssocItem::TypeAlias(t) => !impl_type.contains(&t.name(sema.db).to_string()), | ||
138 | hir::AssocItem::Const(c) => c | ||
139 | .name(sema.db) | ||
140 | .map(|n| !impl_fns_consts.contains(&n.to_string())) | ||
141 | .unwrap_or_default(), | ||
142 | }) | ||
143 | .cloned() | ||
144 | .collect() | ||
145 | }) | ||
146 | } | ||
147 | |||
148 | pub(crate) fn resolve_target_trait( | ||
149 | sema: &Semantics<RootDatabase>, | ||
150 | impl_def: &ast::Impl, | ||
151 | ) -> Option<hir::Trait> { | ||
152 | let ast_path = | ||
153 | impl_def.trait_().map(|it| it.syntax().clone()).and_then(ast::PathType::cast)?.path()?; | ||
154 | |||
155 | match sema.resolve_path(&ast_path) { | ||
156 | Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => Some(def), | ||
157 | _ => None, | ||
158 | } | ||
159 | } | ||
160 | |||
161 | pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize { | ||
162 | node.children_with_tokens() | ||
163 | .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR)) | ||
164 | .map(|it| it.text_range().start()) | ||
165 | .unwrap_or_else(|| node.text_range().start()) | ||
166 | } | ||
167 | |||
168 | pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr { | ||
169 | if let Some(expr) = invert_special_case(&expr) { | ||
170 | return expr; | ||
171 | } | ||
172 | make::expr_prefix(T![!], expr) | ||
173 | } | ||
174 | |||
175 | fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> { | ||
176 | match expr { | ||
177 | ast::Expr::BinExpr(bin) => match bin.op_kind()? { | ||
178 | ast::BinOp::NegatedEqualityTest => bin.replace_op(T![==]).map(|it| it.into()), | ||
179 | ast::BinOp::EqualityTest => bin.replace_op(T![!=]).map(|it| it.into()), | ||
180 | _ => None, | ||
181 | }, | ||
182 | ast::Expr::PrefixExpr(pe) if pe.op_kind()? == ast::PrefixOp::Not => pe.expr(), | ||
183 | // FIXME: | ||
184 | // ast::Expr::Literal(true | false ) | ||
185 | _ => None, | ||
186 | } | ||
187 | } | ||
188 | |||
189 | #[derive(Clone, Copy)] | ||
190 | pub enum TryEnum { | ||
191 | Result, | ||
192 | Option, | ||
193 | } | ||
194 | |||
195 | impl TryEnum { | ||
196 | const ALL: [TryEnum; 2] = [TryEnum::Option, TryEnum::Result]; | ||
197 | |||
198 | pub fn from_ty(sema: &Semantics<RootDatabase>, ty: &Type) -> Option<TryEnum> { | ||
199 | let enum_ = match ty.as_adt() { | ||
200 | Some(Adt::Enum(it)) => it, | ||
201 | _ => return None, | ||
202 | }; | ||
203 | TryEnum::ALL.iter().find_map(|&var| { | ||
204 | if &enum_.name(sema.db).to_string() == var.type_name() { | ||
205 | return Some(var); | ||
206 | } | ||
207 | None | ||
208 | }) | ||
209 | } | ||
210 | |||
211 | pub(crate) fn happy_case(self) -> &'static str { | ||
212 | match self { | ||
213 | TryEnum::Result => "Ok", | ||
214 | TryEnum::Option => "Some", | ||
215 | } | ||
216 | } | ||
217 | |||
218 | pub(crate) fn sad_pattern(self) -> ast::Pat { | ||
219 | match self { | ||
220 | TryEnum::Result => make::tuple_struct_pat( | ||
221 | make::path_unqualified(make::path_segment(make::name_ref("Err"))), | ||
222 | iter::once(make::wildcard_pat().into()), | ||
223 | ) | ||
224 | .into(), | ||
225 | TryEnum::Option => make::ident_pat(make::name("None")).into(), | ||
226 | } | ||
227 | } | ||
228 | |||
229 | fn type_name(self) -> &'static str { | ||
230 | match self { | ||
231 | TryEnum::Result => "Result", | ||
232 | TryEnum::Option => "Option", | ||
233 | } | ||
234 | } | ||
235 | } | ||
236 | |||
237 | /// Helps with finding well-know things inside the standard library. This is | ||
238 | /// somewhat similar to the known paths infra inside hir, but it different; We | ||
239 | /// want to make sure that IDE specific paths don't become interesting inside | ||
240 | /// the compiler itself as well. | ||
241 | pub(crate) struct FamousDefs<'a, 'b>(pub(crate) &'a Semantics<'b, RootDatabase>, pub(crate) Crate); | ||
242 | |||
243 | #[allow(non_snake_case)] | ||
244 | impl FamousDefs<'_, '_> { | ||
245 | #[cfg(test)] | ||
246 | pub(crate) const FIXTURE: &'static str = r#"//- /libcore.rs crate:core | ||
247 | pub mod convert { | ||
248 | pub trait From<T> { | ||
249 | fn from(T) -> Self; | ||
250 | } | ||
251 | } | ||
252 | |||
253 | pub mod option { | ||
254 | pub enum Option<T> { None, Some(T)} | ||
255 | } | ||
256 | |||
257 | pub mod prelude { | ||
258 | pub use crate::{convert::From, option::Option::{self, *}}; | ||
259 | } | ||
260 | #[prelude_import] | ||
261 | pub use prelude::*; | ||
262 | "#; | ||
263 | |||
264 | pub(crate) fn core_convert_From(&self) -> Option<Trait> { | ||
265 | self.find_trait("core:convert:From") | ||
266 | } | ||
267 | |||
268 | pub(crate) fn core_option_Option(&self) -> Option<Enum> { | ||
269 | self.find_enum("core:option:Option") | ||
270 | } | ||
271 | |||
272 | fn find_trait(&self, path: &str) -> Option<Trait> { | ||
273 | match self.find_def(path)? { | ||
274 | hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it), | ||
275 | _ => None, | ||
276 | } | ||
277 | } | ||
278 | |||
279 | fn find_enum(&self, path: &str) -> Option<Enum> { | ||
280 | match self.find_def(path)? { | ||
281 | hir::ScopeDef::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(it))) => Some(it), | ||
282 | _ => None, | ||
283 | } | ||
284 | } | ||
285 | |||
286 | fn find_def(&self, path: &str) -> Option<ScopeDef> { | ||
287 | let db = self.0.db; | ||
288 | let mut path = path.split(':'); | ||
289 | let trait_ = path.next_back()?; | ||
290 | let std_crate = path.next()?; | ||
291 | let std_crate = self | ||
292 | .1 | ||
293 | .dependencies(db) | ||
294 | .into_iter() | ||
295 | .find(|dep| &dep.name.to_string() == std_crate)? | ||
296 | .krate; | ||
297 | |||
298 | let mut module = std_crate.root_module(db); | ||
299 | for segment in path { | ||
300 | module = module.children(db).find_map(|child| { | ||
301 | let name = child.name(db)?; | ||
302 | if &name.to_string() == segment { | ||
303 | Some(child) | ||
304 | } else { | ||
305 | None | ||
306 | } | ||
307 | })?; | ||
308 | } | ||
309 | let def = | ||
310 | module.scope(db, None).into_iter().find(|(name, _def)| &name.to_string() == trait_)?.1; | ||
311 | Some(def) | ||
312 | } | ||
313 | } | ||