Feat: inline generics in const and func trait completions

This commit is contained in:
rdambrosio 2021-06-17 19:54:28 -04:00
parent 0d863ccea9
commit 8e08b86304
5 changed files with 260 additions and 37 deletions

View File

@ -15,7 +15,6 @@ macro_rules! eprintln {
#[cfg(test)]
mod tests;
pub mod utils;
pub mod path_transform;
use hir::Semantics;
use ide_db::{base_db::FileRange, RootDatabase};

View File

@ -8,6 +8,7 @@
use hir::{Adt, HasSource, Semantics};
use ide_db::{
helpers::{FamousDefs, SnippetCap},
path_transform::PathTransform,
RootDatabase,
};
use itertools::Itertools;
@ -22,10 +23,7 @@
SyntaxNode, TextSize, T,
};
use crate::{
assist_context::{AssistBuilder, AssistContext},
path_transform::PathTransform,
};
use crate::assist_context::{AssistBuilder, AssistContext};
pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr {
extract_trivial_expression(&block)

View File

@ -32,7 +32,7 @@
//! ```
use hir::{self, HasAttrs, HasSource};
use ide_db::{traits::get_missing_assoc_items, SymbolKind};
use ide_db::{path_transform::PathTransform, traits::get_missing_assoc_items, SymbolKind};
use syntax::{
ast::{self, edit},
display::function_declaration,
@ -56,7 +56,9 @@ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext
hir::AssocItem::Function(fn_item)
if kind == ImplCompletionKind::All || kind == ImplCompletionKind::Fn =>
{
add_function_impl(&trigger, acc, ctx, fn_item)
if let Some(impl_def) = ctx.sema.to_def(&impl_def) {
add_function_impl(&trigger, acc, ctx, fn_item, impl_def)
}
}
hir::AssocItem::TypeAlias(type_item)
if kind == ImplCompletionKind::All || kind == ImplCompletionKind::TypeAlias =>
@ -66,7 +68,9 @@ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext
hir::AssocItem::Const(const_item)
if kind == ImplCompletionKind::All || kind == ImplCompletionKind::Const =>
{
add_const_impl(&trigger, acc, ctx, const_item)
if let Some(impl_def) = ctx.sema.to_def(&impl_def) {
add_const_impl(&trigger, acc, ctx, const_item, impl_def)
}
}
_ => {}
});
@ -129,6 +133,7 @@ fn add_function_impl(
acc: &mut Completions,
ctx: &CompletionContext,
func: hir::Function,
impl_def: hir::Impl,
) {
let fn_name = func.name(ctx.db).to_string();
@ -147,23 +152,55 @@ fn add_function_impl(
CompletionItemKind::SymbolKind(SymbolKind::Function)
};
let range = replacement_range(ctx, fn_def_node);
if let Some(src) = func.source(ctx.db) {
let function_decl = function_declaration(&src.value);
match ctx.config.snippet_cap {
Some(cap) => {
let snippet = format!("{} {{\n $0\n}}", function_decl);
item.snippet_edit(cap, TextEdit::replace(range, snippet));
}
None => {
let header = format!("{} {{", function_decl);
item.text_edit(TextEdit::replace(range, header));
}
};
item.kind(completion_kind);
item.add_to(acc);
if let Some(source) = func.source(ctx.db) {
let assoc_item = ast::AssocItem::Fn(source.value);
if let Some(transformed_item) = get_transformed_assoc_item(ctx, assoc_item, impl_def) {
let transformed_fn = match transformed_item {
ast::AssocItem::Fn(func) => func,
_ => unreachable!(),
};
let function_decl = function_declaration(&transformed_fn);
match ctx.config.snippet_cap {
Some(cap) => {
let snippet = format!("{} {{\n $0\n}}", function_decl);
item.snippet_edit(cap, TextEdit::replace(range, snippet));
}
None => {
let header = format!("{} {{", function_decl);
item.text_edit(TextEdit::replace(range, header));
}
};
item.kind(completion_kind);
item.add_to(acc);
}
}
}
/// Transform a relevant associated item to inline generics from the impl, remove attrs and docs, etc.
fn get_transformed_assoc_item(
ctx: &CompletionContext,
assoc_item: ast::AssocItem,
impl_def: hir::Impl,
) -> Option<ast::AssocItem> {
let assoc_item = assoc_item.clone_for_update();
let trait_ = impl_def.trait_(ctx.db)?;
let source_scope = &ctx.sema.scope_for_def(trait_);
let target_scope = &ctx.sema.scope(impl_def.source(ctx.db)?.syntax().value);
let transform = PathTransform {
subst: (trait_, impl_def.source(ctx.db)?.value),
source_scope,
target_scope,
};
transform.apply(assoc_item.clone());
Some(match assoc_item {
ast::AssocItem::Fn(func) => ast::AssocItem::Fn(edit::remove_attrs_and_docs(&func)),
_ => assoc_item,
})
}
fn add_type_alias_impl(
type_def_node: &SyntaxNode,
acc: &mut Completions,
@ -188,21 +225,30 @@ fn add_const_impl(
acc: &mut Completions,
ctx: &CompletionContext,
const_: hir::Const,
impl_def: hir::Impl,
) {
let const_name = const_.name(ctx.db).map(|n| n.to_string());
if let Some(const_name) = const_name {
if let Some(source) = const_.source(ctx.db) {
let snippet = make_const_compl_syntax(&source.value);
let assoc_item = ast::AssocItem::Const(source.value);
if let Some(transformed_item) = get_transformed_assoc_item(ctx, assoc_item, impl_def) {
let transformed_const = match transformed_item {
ast::AssocItem::Const(const_) => const_,
_ => unreachable!(),
};
let range = replacement_range(ctx, const_def_node);
let mut item =
CompletionItem::new(CompletionKind::Magic, ctx.source_range(), snippet.clone());
item.text_edit(TextEdit::replace(range, snippet))
.lookup_by(const_name)
.kind(SymbolKind::Const)
.set_documentation(const_.docs(ctx.db));
item.add_to(acc);
let snippet = make_const_compl_syntax(&transformed_const);
let range = replacement_range(ctx, const_def_node);
let mut item =
CompletionItem::new(CompletionKind::Magic, ctx.source_range(), snippet.clone());
item.text_edit(TextEdit::replace(range, snippet))
.lookup_by(const_name)
.kind(SymbolKind::Const)
.set_documentation(const_.docs(ctx.db));
item.add_to(acc);
}
}
}
}
@ -779,4 +825,183 @@ impl Foo for T {{
test("Type", "type T$0", "type Type = ");
test("CONST", "const C$0", "const CONST: i32 = ");
}
#[test]
fn generics_are_inlined_in_return_type() {
check_edit(
"function",
r#"
trait Foo<T> {
fn function() -> T;
}
struct Bar;
impl Foo<u32> for Bar {
fn f$0
}
"#,
r#"
trait Foo<T> {
fn function() -> T;
}
struct Bar;
impl Foo<u32> for Bar {
fn function() -> u32 {
$0
}
}
"#,
)
}
#[test]
fn generics_are_inlined_in_parameter() {
check_edit(
"function",
r#"
trait Foo<T> {
fn function(bar: T);
}
struct Bar;
impl Foo<u32> for Bar {
fn f$0
}
"#,
r#"
trait Foo<T> {
fn function(bar: T);
}
struct Bar;
impl Foo<u32> for Bar {
fn function(bar: u32) {
$0
}
}
"#,
)
}
#[test]
fn generics_are_inlined_when_part_of_other_types() {
check_edit(
"function",
r#"
trait Foo<T> {
fn function(bar: Vec<T>);
}
struct Bar;
impl Foo<u32> for Bar {
fn f$0
}
"#,
r#"
trait Foo<T> {
fn function(bar: Vec<T>);
}
struct Bar;
impl Foo<u32> for Bar {
fn function(bar: Vec<u32>) {
$0
}
}
"#,
)
}
#[test]
fn generics_are_inlined_complex() {
check_edit(
"function",
r#"
trait Foo<T, U, V> {
fn function(bar: Vec<T>, baz: U) -> Arc<Vec<V>>;
}
struct Bar;
impl Foo<u32, Vec<usize>, u8> for Bar {
fn f$0
}
"#,
r#"
trait Foo<T, U, V> {
fn function(bar: Vec<T>, baz: U) -> Arc<Vec<V>>;
}
struct Bar;
impl Foo<u32, Vec<usize>, u8> for Bar {
fn function(bar: Vec<u32>, baz: Vec<usize>) -> Arc<Vec<u8>> {
$0
}
}
"#,
)
}
#[test]
fn generics_are_inlined_in_associated_const() {
check_edit(
"BAR",
r#"
trait Foo<T> {
const BAR: T;
}
struct Bar;
impl Foo<u32> for Bar {
const B$0
}
"#,
r#"
trait Foo<T> {
const BAR: T;
}
struct Bar;
impl Foo<u32> for Bar {
const BAR: u32 =
}
"#,
)
}
#[test]
fn generics_are_inlined_in_where_clause() {
check_edit(
"function",
r#"
trait SomeTrait<T> {}
trait Foo<T> {
fn function()
where Self: SomeTrait<T>;
}
struct Bar;
impl Foo<u32> for Bar {
fn f$0
}
"#,
r#"
trait SomeTrait<T> {}
trait Foo<T> {
fn function()
where Self: SomeTrait<T>;
}
struct Bar;
impl Foo<u32> for Bar {
fn function()
where Self: SomeTrait<u32> {
$0
}
}
"#,
)
}
}

View File

@ -14,6 +14,7 @@
pub mod traits;
pub mod call_info;
pub mod helpers;
pub mod path_transform;
pub mod search;
pub mod rename;

View File

@ -1,7 +1,7 @@
//! See [`PathTransform`].
use crate::helpers::mod_path_to_ast;
use hir::{HirDisplay, SemanticsScope};
use ide_db::helpers::mod_path_to_ast;
use rustc_hash::FxHashMap;
use syntax::{
ast::{self, AstNode},
@ -31,14 +31,14 @@
/// }
/// }
/// ```
pub(crate) struct PathTransform<'a> {
pub(crate) subst: (hir::Trait, ast::Impl),
pub(crate) target_scope: &'a SemanticsScope<'a>,
pub(crate) source_scope: &'a SemanticsScope<'a>,
pub struct PathTransform<'a> {
pub subst: (hir::Trait, ast::Impl),
pub target_scope: &'a SemanticsScope<'a>,
pub source_scope: &'a SemanticsScope<'a>,
}
impl<'a> PathTransform<'a> {
pub(crate) fn apply(&self, item: ast::AssocItem) {
pub fn apply(&self, item: ast::AssocItem) {
if let Some(ctx) = self.build_ctx() {
ctx.apply(item)
}