9321: Inline generics in const and function trait completions r=Veykril a=RDambrosio016

This PR does a couple of things:
- moves path_transform from ide_assists to ide_db to be shared by both assists and completions
- when completing a const or a function for a trait, it will "inline" any generics in those associated items instead 
of leaving the generic's name. For example:
```rust
trait Foo<T> {
    const BAR: T;
    fn foo() -> T;
}
struct Bar;

impl Foo<u32> for Bar {
    // autocompletes to this
    fn foo() -> u32;

    // and not this (old)
    fn foo() -> T;

    // also works for associated consts and where clauses
    const BAR: u32 = /* */
}
```

Currently this does not work for const generics, because `PathTransform` does not seem to account for them. If this should work on const generics too, `PathTransform` will need to be changed. However, it is uncommon to implement a trait only for a single const value, so this isnt a huge concern.

Co-authored-by: rdambrosio <rdambrosio016@gmail.com>
This commit is contained in:
bors[bot] 2021-06-18 16:47:58 +00:00 committed by GitHub
commit 0657812bc2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 274 additions and 53 deletions

View File

@ -15,7 +15,6 @@ mod assist_context;
#[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 ast::TypeBoundsOwner;
use hir::{Adt, HasSource, Semantics};
use ide_db::{
helpers::{FamousDefs, SnippetCap},
path_transform::PathTransform,
RootDatabase,
};
use itertools::Itertools;
@ -22,10 +23,7 @@ use syntax::{
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,
@ -52,24 +52,26 @@ enum ImplCompletionKind {
pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) {
if let Some((kind, trigger, impl_def)) = completion_match(ctx.token.clone()) {
get_missing_assoc_items(&ctx.sema, &impl_def).into_iter().for_each(|item| match item {
hir::AssocItem::Function(fn_item)
if kind == ImplCompletionKind::All || kind == ImplCompletionKind::Fn =>
{
add_function_impl(&trigger, acc, ctx, fn_item)
}
hir::AssocItem::TypeAlias(type_item)
if kind == ImplCompletionKind::All || kind == ImplCompletionKind::TypeAlias =>
{
add_type_alias_impl(&trigger, acc, ctx, type_item)
}
hir::AssocItem::Const(const_item)
if kind == ImplCompletionKind::All || kind == ImplCompletionKind::Const =>
{
add_const_impl(&trigger, acc, ctx, const_item)
}
_ => {}
});
if let Some(hir_impl) = ctx.sema.to_def(&impl_def) {
get_missing_assoc_items(&ctx.sema, &impl_def).into_iter().for_each(|item| match item {
hir::AssocItem::Function(fn_item)
if kind == ImplCompletionKind::All || kind == ImplCompletionKind::Fn =>
{
add_function_impl(&trigger, acc, ctx, fn_item, hir_impl)
}
hir::AssocItem::TypeAlias(type_item)
if kind == ImplCompletionKind::All || kind == ImplCompletionKind::TypeAlias =>
{
add_type_alias_impl(&trigger, acc, ctx, type_item)
}
hir::AssocItem::Const(const_item)
if kind == ImplCompletionKind::All || kind == ImplCompletionKind::Const =>
{
add_const_impl(&trigger, acc, ctx, const_item, hir_impl)
}
_ => {}
});
}
}
}
@ -129,6 +131,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 +150,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 +223,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 +823,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 ty_filter;
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 @@ use syntax::{
/// }
/// }
/// ```
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)
}