Unnamed repository; edit this file 'description' to name the repository.
| -rw-r--r-- | crates/hir/src/semantics.rs | 3 | ||||
| -rw-r--r-- | crates/ide/src/expand_macro.rs | 91 | ||||
| -rw-r--r-- | crates/ide_assists/src/handlers/add_missing_impl_members.rs | 53 | ||||
| -rw-r--r-- | crates/ide_assists/src/handlers/convert_iter_for_each_to_for.rs | 2 | ||||
| -rw-r--r-- | crates/ide_assists/src/handlers/generate_is_empty_from_len.rs | 2 | ||||
| -rw-r--r-- | crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs | 14 | ||||
| -rw-r--r-- | crates/ide_assists/src/utils.rs | 15 | ||||
| -rw-r--r-- | crates/ide_completion/src/completions/dot.rs | 2 | ||||
| -rw-r--r-- | crates/ide_completion/src/completions/qualified_path.rs | 4 | ||||
| -rw-r--r-- | crates/ide_db/src/helpers.rs | 1 | ||||
| -rw-r--r-- | crates/ide_db/src/helpers/insert_whitespace_into_node.rs | 119 | ||||
| -rw-r--r-- | crates/ide_ssr/src/resolving.rs | 2 |
12 files changed, 205 insertions, 103 deletions
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index ed1b2f64fd..75f6b02577 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -1164,8 +1164,7 @@ impl<'a> SemanticsScope<'a> { } /// Note: `FxHashSet<TraitId>` should be treated as an opaque type, passed into `Type - // FIXME: rename to visible_traits to not repeat scope? - pub fn traits_in_scope(&self) -> FxHashSet<TraitId> { + pub fn visible_traits(&self) -> FxHashSet<TraitId> { let resolver = &self.resolver; resolver.traits_in_scope(self.db.upcast()) } diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs index ee49732240..949744c01b 100644 --- a/crates/ide/src/expand_macro.rs +++ b/crates/ide/src/expand_macro.rs @@ -1,9 +1,10 @@ -use std::iter; - use hir::Semantics; -use ide_db::{helpers::pick_best_token, RootDatabase}; +use ide_db::{ + helpers::{insert_whitespace_into_node::insert_ws_into, pick_best_token}, + RootDatabase, +}; use itertools::Itertools; -use syntax::{ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxNode, WalkEvent, T}; +use syntax::{ast, ted, AstNode, SyntaxKind, SyntaxNode}; use crate::FilePosition; @@ -49,7 +50,7 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option< let expansions = sema.expand_derive_macro(&attr)?; Some(ExpandedMacro { name: tt, - expansion: expansions.into_iter().map(insert_whitespaces).join(""), + expansion: expansions.into_iter().map(insert_ws_into).join(""), }) } else { None @@ -82,7 +83,7 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option< // FIXME: // macro expansion may lose all white space information // But we hope someday we can use ra_fmt for that - let expansion = insert_whitespaces(expanded?); + let expansion = insert_ws_into(expanded?).to_string(); Some(ExpandedMacro { name: name.unwrap_or_else(|| "???".to_owned()), expansion }) } @@ -122,84 +123,6 @@ fn expand<T: AstNode>( Some(expanded) } -// FIXME: It would also be cool to share logic here and in the mbe tests, -// which are pretty unreadable at the moment. -fn insert_whitespaces(syn: SyntaxNode) -> String { - use SyntaxKind::*; - let mut res = String::new(); - - let mut indent = 0; - let mut last: Option<SyntaxKind> = None; - - for event in syn.preorder_with_tokens() { - let token = match event { - WalkEvent::Enter(NodeOrToken::Token(token)) => token, - WalkEvent::Leave(NodeOrToken::Node(node)) - if matches!(node.kind(), ATTR | MATCH_ARM | STRUCT | ENUM | UNION | FN | IMPL) => - { - res.push('\n'); - res.extend(iter::repeat(" ").take(2 * indent)); - continue; - } - _ => continue, - }; - let is_next = |f: fn(SyntaxKind) -> bool, default| -> bool { - token.next_token().map(|it| f(it.kind())).unwrap_or(default) - }; - let is_last = - |f: fn(SyntaxKind) -> bool, default| -> bool { last.map(f).unwrap_or(default) }; - - match token.kind() { - k if is_text(k) && is_next(|it| !it.is_punct(), true) => { - res.push_str(token.text()); - res.push(' '); - } - L_CURLY if is_next(|it| it != R_CURLY, true) => { - indent += 1; - if is_last(is_text, false) { - res.push(' '); - } - res.push_str("{\n"); - res.extend(iter::repeat(" ").take(2 * indent)); - } - R_CURLY if is_last(|it| it != L_CURLY, true) => { - indent = indent.saturating_sub(1); - res.push('\n'); - res.extend(iter::repeat(" ").take(2 * indent)); - res.push_str("}"); - } - R_CURLY => { - res.push_str("}\n"); - res.extend(iter::repeat(" ").take(2 * indent)); - } - LIFETIME_IDENT if is_next(|it| it == IDENT || it == MUT_KW, true) => { - res.push_str(token.text()); - res.push(' '); - } - AS_KW => { - res.push_str(token.text()); - res.push(' '); - } - T![;] => { - res.push_str(";\n"); - res.extend(iter::repeat(" ").take(2 * indent)); - } - T![->] => res.push_str(" -> "), - T![=] => res.push_str(" = "), - T![=>] => res.push_str(" => "), - _ => res.push_str(token.text()), - } - - last = Some(token.kind()); - } - - return res; - - fn is_text(k: SyntaxKind) -> bool { - k.is_keyword() || k.is_literal() || k == IDENT - } -} - #[cfg(test)] mod tests { use expect_test::{expect, Expect}; diff --git a/crates/ide_assists/src/handlers/add_missing_impl_members.rs b/crates/ide_assists/src/handlers/add_missing_impl_members.rs index a145598c79..a10eca10d1 100644 --- a/crates/ide_assists/src/handlers/add_missing_impl_members.rs +++ b/crates/ide_assists/src/handlers/add_missing_impl_members.rs @@ -1,5 +1,5 @@ use hir::HasSource; -use ide_db::traits::resolve_target_trait; +use ide_db::{helpers::insert_whitespace_into_node::insert_ws_into, traits::resolve_target_trait}; use syntax::ast::{self, make, AstNode}; use crate::{ @@ -105,7 +105,7 @@ fn add_missing_impl_members_inner( let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?; let missing_items = filter_assoc_items( - ctx.db(), + &ctx.sema, &ide_db::traits::get_missing_assoc_items(&ctx.sema, &impl_def), mode, ); @@ -117,6 +117,17 @@ fn add_missing_impl_members_inner( let target = impl_def.syntax().text_range(); acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| { let target_scope = ctx.sema.scope(impl_def.syntax()); + let missing_items = missing_items + .into_iter() + .map(|it| { + if ctx.sema.hir_file_for(it.syntax()).is_macro() { + if let Some(it) = ast::AssocItem::cast(insert_ws_into(it.syntax().clone())) { + return it; + } + } + it.clone_for_update() + }) + .collect(); let (new_impl_def, first_new_item) = add_trait_assoc_items_to_impl( &ctx.sema, missing_items, @@ -893,4 +904,42 @@ impl Default for Foo { "#, ) } + + #[test] + fn test_from_macro() { + check_assist( + add_missing_default_members, + r#" +macro_rules! foo { + () => { + trait FooB { + fn foo<'lt>(&'lt self) {} + } + } +} +foo!(); +struct Foo(usize); + +impl FooB for Foo { + $0 +} +"#, + r#" +macro_rules! foo { + () => { + trait FooB { + fn foo<'lt>(&'lt self) {} + } + } +} +foo!(); +struct Foo(usize); + +impl FooB for Foo { + $0fn foo< 'lt>(& 'lt self){} + +} +"#, + ) + } } diff --git a/crates/ide_assists/src/handlers/convert_iter_for_each_to_for.rs b/crates/ide_assists/src/handlers/convert_iter_for_each_to_for.rs index eca6d047a1..7fbbdb4f5e 100644 --- a/crates/ide_assists/src/handlers/convert_iter_for_each_to_for.rs +++ b/crates/ide_assists/src/handlers/convert_iter_for_each_to_for.rs @@ -148,7 +148,7 @@ fn is_ref_and_impls_iter_method( let ty = sema.type_of_expr(&expr_behind_ref)?.adjusted(); let scope = sema.scope(iterable.syntax()); let krate = scope.module()?.krate(); - let traits_in_scope = scope.traits_in_scope(); + let traits_in_scope = scope.visible_traits(); let iter_trait = FamousDefs(sema, Some(krate)).core_iter_Iterator()?; let has_wanted_method = ty diff --git a/crates/ide_assists/src/handlers/generate_is_empty_from_len.rs b/crates/ide_assists/src/handlers/generate_is_empty_from_len.rs index d831289775..15025cf0d0 100644 --- a/crates/ide_assists/src/handlers/generate_is_empty_from_len.rs +++ b/crates/ide_assists/src/handlers/generate_is_empty_from_len.rs @@ -92,7 +92,7 @@ fn get_impl_method( let scope = ctx.sema.scope(impl_.syntax()); let krate = impl_def.module(db).krate(); let ty = impl_def.self_ty(db); - let traits_in_scope = scope.traits_in_scope(); + let traits_in_scope = scope.visible_traits(); ty.iterate_method_candidates(db, krate, &traits_in_scope, Some(fn_name), |_, func| Some(func)) } diff --git a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs index 46bf4d7b46..b3723710a8 100644 --- a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs @@ -1,4 +1,5 @@ use hir::ModuleDef; +use ide_db::helpers::insert_whitespace_into_node::insert_ws_into; use ide_db::helpers::{ get_path_at_cursor_in_tt, import_assets::NameToImport, mod_path_to_ast, parse_tt_as_comma_sep_paths, @@ -170,7 +171,7 @@ fn impl_def_from_trait( ) -> Option<(ast::Impl, ast::AssocItem)> { let trait_ = trait_?; let target_scope = sema.scope(annotated_name.syntax()); - let trait_items = filter_assoc_items(sema.db, &trait_.items(sema.db), DefaultMethods::No); + let trait_items = filter_assoc_items(sema, &trait_.items(sema.db), DefaultMethods::No); if trait_items.is_empty() { return None; } @@ -193,6 +194,17 @@ fn impl_def_from_trait( node }; + let trait_items = trait_items + .into_iter() + .map(|it| { + if sema.hir_file_for(it.syntax()).is_macro() { + if let Some(it) = ast::AssocItem::cast(insert_ws_into(it.syntax().clone())) { + return it; + } + } + it.clone_for_update() + }) + .collect(); let (impl_def, first_assoc_item) = add_trait_assoc_items_to_impl(sema, trait_items, trait_, impl_def, target_scope); diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs index 03433fc42a..90ec710c8e 100644 --- a/crates/ide_assists/src/utils.rs +++ b/crates/ide_assists/src/utils.rs @@ -5,7 +5,7 @@ use std::ops; use itertools::Itertools; pub(crate) use gen_trait_fn_body::gen_trait_fn_body; -use hir::{db::HirDatabase, HasSource, HirDisplay}; +use hir::{db::HirDatabase, HirDisplay, Semantics}; use ide_db::{ helpers::FamousDefs, helpers::SnippetCap, path_transform::PathTransform, RootDatabase, }; @@ -92,7 +92,7 @@ pub enum DefaultMethods { } pub fn filter_assoc_items( - db: &RootDatabase, + sema: &Semantics<RootDatabase>, items: &[hir::AssocItem], default_methods: DefaultMethods, ) -> Vec<ast::AssocItem> { @@ -109,11 +109,11 @@ pub fn filter_assoc_items( items .iter() // Note: This throws away items with no source. - .filter_map(|i| { + .filter_map(|&i| { let item = match i { - hir::AssocItem::Function(i) => ast::AssocItem::Fn(i.source(db)?.value), - hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(i.source(db)?.value), - hir::AssocItem::Const(i) => ast::AssocItem::Const(i.source(db)?.value), + hir::AssocItem::Function(i) => ast::AssocItem::Fn(sema.source(i)?.value), + hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(sema.source(i)?.value), + hir::AssocItem::Const(i) => ast::AssocItem::Const(sema.source(i)?.value), }; Some(item) }) @@ -129,7 +129,7 @@ pub fn filter_assoc_items( } pub fn add_trait_assoc_items_to_impl( - sema: &hir::Semantics<ide_db::RootDatabase>, + sema: &Semantics<RootDatabase>, items: Vec<ast::AssocItem>, trait_: hir::Trait, impl_: ast::Impl, @@ -140,7 +140,6 @@ pub fn add_trait_assoc_items_to_impl( let transform = PathTransform::trait_impl(&target_scope, &source_scope, trait_, impl_.clone()); let items = items.into_iter().map(|assoc_item| { - let assoc_item = assoc_item.clone_for_update(); transform.apply(assoc_item.syntax()); assoc_item.remove_attrs_and_docs(); assoc_item diff --git a/crates/ide_completion/src/completions/dot.rs b/crates/ide_completion/src/completions/dot.rs index e01e9c9fa7..e7371270fb 100644 --- a/crates/ide_completion/src/completions/dot.rs +++ b/crates/ide_completion/src/completions/dot.rs @@ -79,7 +79,7 @@ fn complete_methods( ) { if let Some(krate) = ctx.krate { let mut seen_methods = FxHashSet::default(); - let traits_in_scope = ctx.scope.traits_in_scope(); + let traits_in_scope = ctx.scope.visible_traits(); receiver.iterate_method_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, func| { if func.self_param(ctx.db).is_some() && seen_methods.insert(func.name(ctx.db)) { f(func); diff --git a/crates/ide_completion/src/completions/qualified_path.rs b/crates/ide_completion/src/completions/qualified_path.rs index b5c3d83c16..a0d6d5cdc6 100644 --- a/crates/ide_completion/src/completions/qualified_path.rs +++ b/crates/ide_completion/src/completions/qualified_path.rs @@ -187,7 +187,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon let krate = ctx.krate; if let Some(krate) = krate { - let traits_in_scope = ctx.scope.traits_in_scope(); + let traits_in_scope = ctx.scope.visible_traits(); ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| { add_assoc_item(acc, ctx, item); None::<()> @@ -220,7 +220,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon add_enum_variants(acc, ctx, e); } - let traits_in_scope = ctx.scope.traits_in_scope(); + let traits_in_scope = ctx.scope.visible_traits(); let mut seen = FxHashSet::default(); ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| { // We might iterate candidates of a trait multiple times here, so deduplicate diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs index 7e78b7136c..18a5f64891 100644 --- a/crates/ide_db/src/helpers.rs +++ b/crates/ide_db/src/helpers.rs @@ -4,6 +4,7 @@ pub mod generated_lints; pub mod import_assets; pub mod insert_use; pub mod merge_imports; +pub mod insert_whitespace_into_node; pub mod node_ext; pub mod rust_doc; diff --git a/crates/ide_db/src/helpers/insert_whitespace_into_node.rs b/crates/ide_db/src/helpers/insert_whitespace_into_node.rs new file mode 100644 index 0000000000..251a4caa13 --- /dev/null +++ b/crates/ide_db/src/helpers/insert_whitespace_into_node.rs @@ -0,0 +1,119 @@ +//! Utilities for formatting macro expanded nodes until we get a proper formatter. +use syntax::{ + ast::make, + ted::{self, Position}, + NodeOrToken, + SyntaxKind::{self, *}, + SyntaxNode, SyntaxToken, WalkEvent, T, +}; + +// FIXME: It would also be cool to share logic here and in the mbe tests, +// which are pretty unreadable at the moment. +/// Renders a [`SyntaxNode`] with whitespace inserted between tokens that require them. +pub fn insert_ws_into(syn: SyntaxNode) -> SyntaxNode { + let mut indent = 0; + let mut last: Option<SyntaxKind> = None; + let mut mods = Vec::new(); + let syn = syn.clone_subtree().clone_for_update(); + + let before = Position::before; + let after = Position::after; + + let do_indent = |pos: fn(_) -> Position, token: &SyntaxToken, indent| { + (pos(token.clone()), make::tokens::whitespace(&" ".repeat(2 * indent))) + }; + let do_ws = |pos: fn(_) -> Position, token: &SyntaxToken| { + (pos(token.clone()), make::tokens::single_space()) + }; + let do_nl = |pos: fn(_) -> Position, token: &SyntaxToken| { + (pos(token.clone()), make::tokens::single_newline()) + }; + + for event in syn.preorder_with_tokens() { + let token = match event { + WalkEvent::Enter(NodeOrToken::Token(token)) => token, + WalkEvent::Leave(NodeOrToken::Node(node)) + if matches!(node.kind(), ATTR | MATCH_ARM | STRUCT | ENUM | UNION | FN | IMPL) => + { + if indent > 0 { + mods.push(( + Position::after(node.clone()), + make::tokens::whitespace(&" ".repeat(2 * indent)), + )); + } + if node.parent().is_some() { + mods.push((Position::after(node), make::tokens::single_newline())); + } + continue; + } + _ => continue, + }; + let tok = &token; + + let is_next = |f: fn(SyntaxKind) -> bool, default| -> bool { + tok.next_token().map(|it| f(it.kind())).unwrap_or(default) + }; + let is_last = + |f: fn(SyntaxKind) -> bool, default| -> bool { last.map(f).unwrap_or(default) }; + + match tok.kind() { + k if is_text(k) && is_next(|it| !it.is_punct(), true) => { + mods.push(do_ws(after, tok)); + } + L_CURLY if is_next(|it| it != R_CURLY, true) => { + indent += 1; + if is_last(is_text, false) { + mods.push(do_ws(before, tok)); + } + + if indent > 0 { + mods.push(do_indent(after, tok, indent)); + } + mods.push(do_nl(after, &tok)); + } + R_CURLY if is_last(|it| it != L_CURLY, true) => { + indent = indent.saturating_sub(1); + + if indent > 0 { + mods.push(do_indent(before, tok, indent)); + } + mods.push(do_nl(before, tok)); + } + R_CURLY => { + if indent > 0 { + mods.push(do_indent(after, tok, indent)); + } + mods.push(do_nl(after, tok)); + } + LIFETIME_IDENT if is_next(|it| is_text(it), true) => { + mods.push(do_ws(after, tok)); + } + AS_KW => { + mods.push(do_ws(after, tok)); + } + T![;] => { + if indent > 0 { + mods.push(do_indent(after, tok, indent)); + } + mods.push(do_nl(after, tok)); + } + T![->] | T![=] | T![=>] => { + mods.push(do_ws(before, tok)); + mods.push(do_ws(after, tok)); + } + _ => (), + } + + last = Some(tok.kind()); + } + + for (pos, insert) in mods { + ted::insert(pos, insert); + } + + syn +} + +fn is_text(k: SyntaxKind) -> bool { + k.is_keyword() || k.is_literal() || k == IDENT +} diff --git a/crates/ide_ssr/src/resolving.rs b/crates/ide_ssr/src/resolving.rs index 84e5f82604..7902295d29 100644 --- a/crates/ide_ssr/src/resolving.rs +++ b/crates/ide_ssr/src/resolving.rs @@ -222,7 +222,7 @@ impl<'db> ResolutionScope<'db> { adt.ty(self.scope.db).iterate_path_candidates( self.scope.db, self.scope.module()?.krate(), - &self.scope.traits_in_scope(), + &self.scope.visible_traits(), None, |_ty, assoc_item| { let item_name = assoc_item.name(self.scope.db)?; |