Skip to content

Commit 8d8d542

Browse files
bors[bot]kdelorey
andauthored
Merge #3108
3108: Magic Completion for `impl Trait for` Associated Items r=matklad a=kdelorey # Summary This PR adds a set of magic completions to auto complete associated trait items (functions/consts/types). ![Associated Trait Impl](https://user-images.githubusercontent.com/2295721/74493144-d8f1af00-4e96-11ea-93a4-82725bf89646.gif) ## Notes Since the assist and completion share the same logic when figuring out the associated items that are missing, a shared utility was created in the `ra_assists::utils` module. Resolves #1046 As this is my first PR to the rust-analyzer project, I'm new to the codebase, feedback welcomed! Co-authored-by: Kevin DeLorey <[email protected]>
2 parents 953dbe3 + 057d0be commit 8d8d542

File tree

6 files changed

+536
-36
lines changed

6 files changed

+536
-36
lines changed

crates/ra_assists/src/handlers/add_missing_impl_members.rs

Lines changed: 14 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
use hir::{db::HirDatabase, HasSource, InFile};
1+
use hir::{HasSource, InFile};
22
use ra_syntax::{
33
ast::{self, edit, make, AstNode, NameOwner},
44
SmolStr,
55
};
66

77
use crate::{
88
ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
9+
utils::{get_missing_impl_items, resolve_target_trait},
910
Assist, AssistCtx, AssistId,
1011
};
1112

@@ -103,11 +104,9 @@ fn add_missing_impl_members_inner(
103104
let impl_node = ctx.find_node_at_offset::<ast::ImplBlock>()?;
104105
let impl_item_list = impl_node.item_list()?;
105106

106-
let (trait_, trait_def) = {
107-
let analyzer = ctx.source_analyzer(impl_node.syntax(), None);
107+
let analyzer = ctx.source_analyzer(impl_node.syntax(), None);
108108

109-
resolve_target_trait_def(ctx.db, &analyzer, &impl_node)?
110-
};
109+
let trait_ = resolve_target_trait(ctx.db, &analyzer, &impl_node)?;
111110

112111
let def_name = |item: &ast::ImplItem| -> Option<SmolStr> {
113112
match item {
@@ -118,20 +117,23 @@ fn add_missing_impl_members_inner(
118117
.map(|it| it.text().clone())
119118
};
120119

121-
let trait_items = trait_def.item_list()?.impl_items();
122-
let impl_items = impl_item_list.impl_items().collect::<Vec<_>>();
123-
124-
let missing_items: Vec<_> = trait_items
125-
.filter(|t| def_name(t).is_some())
120+
let missing_items = get_missing_impl_items(ctx.db, &analyzer, &impl_node)
121+
.iter()
122+
.map(|i| match i {
123+
hir::AssocItem::Function(i) => ast::ImplItem::FnDef(i.source(ctx.db).value),
124+
hir::AssocItem::TypeAlias(i) => ast::ImplItem::TypeAliasDef(i.source(ctx.db).value),
125+
hir::AssocItem::Const(i) => ast::ImplItem::ConstDef(i.source(ctx.db).value),
126+
})
127+
.filter(|t| def_name(&t).is_some())
126128
.filter(|t| match t {
127129
ast::ImplItem::FnDef(def) => match mode {
128130
AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(),
129131
AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(),
130132
},
131133
_ => mode == AddMissingImplMembersMode::NoDefaultMethods,
132134
})
133-
.filter(|t| impl_items.iter().all(|i| def_name(i) != def_name(t)))
134-
.collect();
135+
.collect::<Vec<_>>();
136+
135137
if missing_items.is_empty() {
136138
return None;
137139
}
@@ -177,27 +179,6 @@ fn add_body(fn_def: ast::FnDef) -> ast::FnDef {
177179
}
178180
}
179181

180-
/// Given an `ast::ImplBlock`, resolves the target trait (the one being
181-
/// implemented) to a `ast::TraitDef`.
182-
fn resolve_target_trait_def(
183-
db: &impl HirDatabase,
184-
analyzer: &hir::SourceAnalyzer,
185-
impl_block: &ast::ImplBlock,
186-
) -> Option<(hir::Trait, ast::TraitDef)> {
187-
let ast_path = impl_block
188-
.target_trait()
189-
.map(|it| it.syntax().clone())
190-
.and_then(ast::PathType::cast)?
191-
.path()?;
192-
193-
match analyzer.resolve_path(db, &ast_path) {
194-
Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => {
195-
Some((def, def.source(db).value))
196-
}
197-
_ => None,
198-
}
199-
}
200-
201182
#[cfg(test)]
202183
mod tests {
203184
use super::*;

crates/ra_assists/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ mod assist_ctx;
99
mod marks;
1010
#[cfg(test)]
1111
mod doc_tests;
12-
mod utils;
12+
pub mod utils;
1313
pub mod ast_transform;
1414

1515
use ra_db::FileRange;

crates/ra_assists/src/utils.rs

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,81 @@
11
//! Assorted functions shared by several assists.
22
33
use ra_syntax::{
4-
ast::{self, make},
5-
T,
4+
ast::{self, make, NameOwner},
5+
AstNode, T,
66
};
77

8+
use hir::db::HirDatabase;
9+
use rustc_hash::FxHashSet;
10+
11+
pub fn get_missing_impl_items(
12+
db: &impl HirDatabase,
13+
analyzer: &hir::SourceAnalyzer,
14+
impl_block: &ast::ImplBlock,
15+
) -> Vec<hir::AssocItem> {
16+
// Names must be unique between constants and functions. However, type aliases
17+
// may share the same name as a function or constant.
18+
let mut impl_fns_consts = FxHashSet::default();
19+
let mut impl_type = FxHashSet::default();
20+
21+
if let Some(item_list) = impl_block.item_list() {
22+
for item in item_list.impl_items() {
23+
match item {
24+
ast::ImplItem::FnDef(f) => {
25+
if let Some(n) = f.name() {
26+
impl_fns_consts.insert(n.syntax().to_string());
27+
}
28+
}
29+
30+
ast::ImplItem::TypeAliasDef(t) => {
31+
if let Some(n) = t.name() {
32+
impl_type.insert(n.syntax().to_string());
33+
}
34+
}
35+
36+
ast::ImplItem::ConstDef(c) => {
37+
if let Some(n) = c.name() {
38+
impl_fns_consts.insert(n.syntax().to_string());
39+
}
40+
}
41+
}
42+
}
43+
}
44+
45+
resolve_target_trait(db, analyzer, impl_block).map_or(vec![], |target_trait| {
46+
target_trait
47+
.items(db)
48+
.iter()
49+
.filter(|i| match i {
50+
hir::AssocItem::Function(f) => !impl_fns_consts.contains(&f.name(db).to_string()),
51+
hir::AssocItem::TypeAlias(t) => !impl_type.contains(&t.name(db).to_string()),
52+
hir::AssocItem::Const(c) => c
53+
.name(db)
54+
.map(|n| !impl_fns_consts.contains(&n.to_string()))
55+
.unwrap_or_default(),
56+
})
57+
.cloned()
58+
.collect()
59+
})
60+
}
61+
62+
pub(crate) fn resolve_target_trait(
63+
db: &impl HirDatabase,
64+
analyzer: &hir::SourceAnalyzer,
65+
impl_block: &ast::ImplBlock,
66+
) -> Option<hir::Trait> {
67+
let ast_path = impl_block
68+
.target_trait()
69+
.map(|it| it.syntax().clone())
70+
.and_then(ast::PathType::cast)?
71+
.path()?;
72+
73+
match analyzer.resolve_path(db, &ast_path) {
74+
Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => Some(def),
75+
_ => None,
76+
}
77+
}
78+
879
pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr {
980
if let Some(expr) = invert_special_case(&expr) {
1081
return expr;

crates/ra_ide/src/completion.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ mod complete_path;
1515
mod complete_scope;
1616
mod complete_postfix;
1717
mod complete_macro_in_item_position;
18+
mod complete_trait_impl;
1819

1920
use ra_db::SourceDatabase;
2021
use ra_ide_db::RootDatabase;
@@ -74,5 +75,7 @@ pub(crate) fn completions(db: &RootDatabase, position: FilePosition) -> Option<C
7475
complete_pattern::complete_pattern(&mut acc, &ctx);
7576
complete_postfix::complete_postfix(&mut acc, &ctx);
7677
complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
78+
complete_trait_impl::complete_trait_impl(&mut acc, &ctx);
79+
7780
Some(acc)
7881
}

0 commit comments

Comments
 (0)