Skip to content

Commit fc8a035

Browse files
committed
Auto merge of #17737 - hyf0:hyf_32089420384, r=Veykril
feat(ide-completion): extra sugar auto-completion `async fn ...` in `impl trait` for `async fn in trait` that's defined in desugar form Solves #17719. --- Preview <img width="670" alt="image" src="https://github.com/user-attachments/assets/64ccef84-4062-4702-8760-89220585f422"> <img width="540" alt="image" src="https://github.com/user-attachments/assets/d22637f9-d531-43b2-a9f1-cd40a002903a"> <img width="631" alt="image" src="https://github.com/user-attachments/assets/21cd2142-bb8e-4493-9ac7-e6a9e7076904">
2 parents 1875784 + e2e3dbf commit fc8a035

File tree

5 files changed

+285
-21
lines changed

5 files changed

+285
-21
lines changed

crates/hir/src/lib.rs

+29
Original file line numberDiff line numberDiff line change
@@ -2207,6 +2207,35 @@ impl Function {
22072207
db.function_data(self.id).is_async()
22082208
}
22092209

2210+
pub fn returns_impl_future(self, db: &dyn HirDatabase) -> bool {
2211+
if self.is_async(db) {
2212+
return true;
2213+
}
2214+
2215+
let Some(impl_traits) = self.ret_type(db).as_impl_traits(db) else { return false };
2216+
let Some(future_trait_id) =
2217+
db.lang_item(self.ty(db).env.krate, LangItem::Future).and_then(|t| t.as_trait())
2218+
else {
2219+
return false;
2220+
};
2221+
let Some(sized_trait_id) =
2222+
db.lang_item(self.ty(db).env.krate, LangItem::Sized).and_then(|t| t.as_trait())
2223+
else {
2224+
return false;
2225+
};
2226+
2227+
let mut has_impl_future = false;
2228+
impl_traits
2229+
.filter(|t| {
2230+
let fut = t.id == future_trait_id;
2231+
has_impl_future |= fut;
2232+
!fut && t.id != sized_trait_id
2233+
})
2234+
// all traits but the future trait must be auto traits
2235+
.all(|t| t.is_auto(db))
2236+
&& has_impl_future
2237+
}
2238+
22102239
/// Does this function have `#[test]` attribute?
22112240
pub fn is_test(self, db: &dyn HirDatabase) -> bool {
22122241
db.function_data(self.id).attrs.is_test()

crates/ide-completion/src/completions/item_list/trait_impl.rs

+248-20
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,14 @@
3131
//! }
3232
//! ```
3333
34-
use hir::HasAttrs;
34+
use hir::{HasAttrs, Name};
3535
use ide_db::{
3636
documentation::HasDocs, path_transform::PathTransform,
3737
syntax_helpers::insert_whitespace_into_node, traits::get_missing_assoc_items, SymbolKind,
3838
};
3939
use syntax::{
40-
ast::{self, edit_in_place::AttrsOwnerEdit, HasTypeBounds},
41-
format_smolstr, AstNode, SmolStr, SyntaxElement, SyntaxKind, TextRange, ToSmolStr, T,
40+
ast::{self, edit_in_place::AttrsOwnerEdit, make, HasGenericArgs, HasTypeBounds},
41+
format_smolstr, ted, AstNode, SmolStr, SyntaxElement, SyntaxKind, TextRange, ToSmolStr, T,
4242
};
4343
use text_edit::TextEdit;
4444

@@ -178,12 +178,36 @@ fn add_function_impl(
178178
func: hir::Function,
179179
impl_def: hir::Impl,
180180
) {
181-
let fn_name = func.name(ctx.db);
181+
let fn_name = &func.name(ctx.db);
182+
let sugar: &[_] = if func.is_async(ctx.db) {
183+
&[AsyncSugaring::Async, AsyncSugaring::Desugar]
184+
} else if func.returns_impl_future(ctx.db) {
185+
&[AsyncSugaring::Plain, AsyncSugaring::Resugar]
186+
} else {
187+
&[AsyncSugaring::Plain]
188+
};
189+
for &sugaring in sugar {
190+
add_function_impl_(acc, ctx, replacement_range, func, impl_def, fn_name, sugaring);
191+
}
192+
}
182193

183-
let is_async = func.is_async(ctx.db);
194+
fn add_function_impl_(
195+
acc: &mut Completions,
196+
ctx: &CompletionContext<'_>,
197+
replacement_range: TextRange,
198+
func: hir::Function,
199+
impl_def: hir::Impl,
200+
fn_name: &Name,
201+
async_sugaring: AsyncSugaring,
202+
) {
203+
let async_ = if let AsyncSugaring::Async | AsyncSugaring::Resugar = async_sugaring {
204+
"async "
205+
} else {
206+
""
207+
};
184208
let label = format_smolstr!(
185209
"{}fn {}({})",
186-
if is_async { "async " } else { "" },
210+
async_,
187211
fn_name.display(ctx.db, ctx.edition),
188212
if func.assoc_fn_params(ctx.db).is_empty() { "" } else { ".." }
189213
);
@@ -195,22 +219,14 @@ fn add_function_impl(
195219
});
196220

197221
let mut item = CompletionItem::new(completion_kind, replacement_range, label, ctx.edition);
198-
item.lookup_by(format!(
199-
"{}fn {}",
200-
if is_async { "async " } else { "" },
201-
fn_name.display(ctx.db, ctx.edition)
202-
))
203-
.set_documentation(func.docs(ctx.db))
204-
.set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
222+
item.lookup_by(format!("{}fn {}", async_, fn_name.display(ctx.db, ctx.edition)))
223+
.set_documentation(func.docs(ctx.db))
224+
.set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
205225

206226
if let Some(source) = ctx.sema.source(func) {
207-
let assoc_item = ast::AssocItem::Fn(source.value);
208-
if let Some(transformed_item) = get_transformed_assoc_item(ctx, assoc_item, impl_def) {
209-
let transformed_fn = match transformed_item {
210-
ast::AssocItem::Fn(func) => func,
211-
_ => unreachable!(),
212-
};
213-
227+
if let Some(transformed_fn) =
228+
get_transformed_fn(ctx, source.value, impl_def, async_sugaring)
229+
{
214230
let function_decl = function_declaration(&transformed_fn, source.file_id.is_macro());
215231
match ctx.config.snippet_cap {
216232
Some(cap) => {
@@ -227,6 +243,14 @@ fn add_function_impl(
227243
}
228244
}
229245

246+
#[derive(Copy, Clone)]
247+
enum AsyncSugaring {
248+
Desugar,
249+
Resugar,
250+
Async,
251+
Plain,
252+
}
253+
230254
/// Transform a relevant associated item to inline generics from the impl, remove attrs and docs, etc.
231255
fn get_transformed_assoc_item(
232256
ctx: &CompletionContext<'_>,
@@ -251,6 +275,82 @@ fn get_transformed_assoc_item(
251275
Some(assoc_item)
252276
}
253277

278+
/// Transform a relevant associated item to inline generics from the impl, remove attrs and docs, etc.
279+
fn get_transformed_fn(
280+
ctx: &CompletionContext<'_>,
281+
fn_: ast::Fn,
282+
impl_def: hir::Impl,
283+
async_: AsyncSugaring,
284+
) -> Option<ast::Fn> {
285+
let trait_ = impl_def.trait_(ctx.db)?;
286+
let source_scope = &ctx.sema.scope(fn_.syntax())?;
287+
let target_scope = &ctx.sema.scope(ctx.sema.source(impl_def)?.syntax().value)?;
288+
let transform = PathTransform::trait_impl(
289+
target_scope,
290+
source_scope,
291+
trait_,
292+
ctx.sema.source(impl_def)?.value,
293+
);
294+
295+
let fn_ = fn_.clone_for_update();
296+
// FIXME: Paths in nested macros are not handled well. See
297+
// `macro_generated_assoc_item2` test.
298+
transform.apply(fn_.syntax());
299+
fn_.remove_attrs_and_docs();
300+
match async_ {
301+
AsyncSugaring::Desugar => {
302+
match fn_.ret_type() {
303+
Some(ret_ty) => {
304+
let ty = ret_ty.ty()?;
305+
ted::replace(
306+
ty.syntax(),
307+
make::ty(&format!("impl Future<Output = {ty}>"))
308+
.syntax()
309+
.clone_for_update(),
310+
);
311+
}
312+
None => ted::append_child(
313+
fn_.param_list()?.syntax(),
314+
make::ret_type(make::ty("impl Future<Output = ()>"))
315+
.syntax()
316+
.clone_for_update(),
317+
),
318+
}
319+
fn_.async_token().unwrap().detach();
320+
}
321+
AsyncSugaring::Resugar => {
322+
let ty = fn_.ret_type()?.ty()?;
323+
match &ty {
324+
// best effort guessing here
325+
ast::Type::ImplTraitType(t) => {
326+
let output = t.type_bound_list()?.bounds().find_map(|b| match b.ty()? {
327+
ast::Type::PathType(p) => {
328+
let p = p.path()?.segment()?;
329+
if p.name_ref()?.text() != "Future" {
330+
return None;
331+
}
332+
match p.generic_arg_list()?.generic_args().next()? {
333+
ast::GenericArg::AssocTypeArg(a)
334+
if a.name_ref()?.text() == "Output" =>
335+
{
336+
a.ty()
337+
}
338+
_ => None,
339+
}
340+
}
341+
_ => None,
342+
})?;
343+
ted::replace(ty.syntax(), output.syntax());
344+
}
345+
_ => (),
346+
}
347+
ted::prepend_child(fn_.syntax(), make::token(T![async]));
348+
}
349+
AsyncSugaring::Async | AsyncSugaring::Plain => (),
350+
}
351+
Some(fn_)
352+
}
353+
254354
fn add_type_alias_impl(
255355
acc: &mut Completions,
256356
ctx: &CompletionContext<'_>,
@@ -1401,6 +1501,134 @@ trait Tr {
14011501
impl Tr for () {
14021502
type Item = $0;
14031503
}
1504+
"#,
1505+
);
1506+
}
1507+
1508+
#[test]
1509+
fn impl_fut() {
1510+
check_edit(
1511+
"fn foo",
1512+
r#"
1513+
//- minicore: future, send, sized
1514+
use core::future::Future;
1515+
1516+
trait DesugaredAsyncTrait {
1517+
fn foo(&self) -> impl Future<Output = usize> + Send;
1518+
}
1519+
1520+
impl DesugaredAsyncTrait for () {
1521+
$0
1522+
}
1523+
"#,
1524+
r#"
1525+
use core::future::Future;
1526+
1527+
trait DesugaredAsyncTrait {
1528+
fn foo(&self) -> impl Future<Output = usize> + Send;
1529+
}
1530+
1531+
impl DesugaredAsyncTrait for () {
1532+
fn foo(&self) -> impl Future<Output = usize> + Send {
1533+
$0
1534+
}
1535+
}
1536+
"#,
1537+
);
1538+
}
1539+
1540+
#[test]
1541+
fn impl_fut_resugared() {
1542+
check_edit(
1543+
"async fn foo",
1544+
r#"
1545+
//- minicore: future, send, sized
1546+
use core::future::Future;
1547+
1548+
trait DesugaredAsyncTrait {
1549+
fn foo(&self) -> impl Future<Output = usize> + Send;
1550+
}
1551+
1552+
impl DesugaredAsyncTrait for () {
1553+
$0
1554+
}
1555+
"#,
1556+
r#"
1557+
use core::future::Future;
1558+
1559+
trait DesugaredAsyncTrait {
1560+
fn foo(&self) -> impl Future<Output = usize> + Send;
1561+
}
1562+
1563+
impl DesugaredAsyncTrait for () {
1564+
async fn foo(&self) -> usize {
1565+
$0
1566+
}
1567+
}
1568+
"#,
1569+
);
1570+
}
1571+
1572+
#[test]
1573+
fn async_desugared() {
1574+
check_edit(
1575+
"fn foo",
1576+
r#"
1577+
//- minicore: future, send, sized
1578+
use core::future::Future;
1579+
1580+
trait DesugaredAsyncTrait {
1581+
async fn foo(&self) -> usize;
1582+
}
1583+
1584+
impl DesugaredAsyncTrait for () {
1585+
$0
1586+
}
1587+
"#,
1588+
r#"
1589+
use core::future::Future;
1590+
1591+
trait DesugaredAsyncTrait {
1592+
async fn foo(&self) -> usize;
1593+
}
1594+
1595+
impl DesugaredAsyncTrait for () {
1596+
fn foo(&self) -> impl Future<Output = usize> {
1597+
$0
1598+
}
1599+
}
1600+
"#,
1601+
);
1602+
}
1603+
1604+
#[test]
1605+
fn async_() {
1606+
check_edit(
1607+
"async fn foo",
1608+
r#"
1609+
//- minicore: future, send, sized
1610+
use core::future::Future;
1611+
1612+
trait DesugaredAsyncTrait {
1613+
async fn foo(&self) -> usize;
1614+
}
1615+
1616+
impl DesugaredAsyncTrait for () {
1617+
$0
1618+
}
1619+
"#,
1620+
r#"
1621+
use core::future::Future;
1622+
1623+
trait DesugaredAsyncTrait {
1624+
async fn foo(&self) -> usize;
1625+
}
1626+
1627+
impl DesugaredAsyncTrait for () {
1628+
async fn foo(&self) -> usize {
1629+
$0
1630+
}
1631+
}
14041632
"#,
14051633
);
14061634
}

crates/ide-completion/src/tests/item_list.rs

+1
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ impl Test for () {
313313
ct const CONST1: () =
314314
fn async fn function2()
315315
fn fn function1()
316+
fn fn function2()
316317
ma makro!(…) macro_rules! makro
317318
md module
318319
ta type Type1 =

crates/syntax/src/ast/make.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1162,7 +1162,7 @@ pub mod tokens {
11621162

11631163
pub(super) static SOURCE_FILE: LazyLock<Parse<SourceFile>> = LazyLock::new(|| {
11641164
SourceFile::parse(
1165-
"const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p, { let _ @ [] })\n;\n\nimpl A for B where: {}", Edition::CURRENT,
1165+
"const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p, async { let _ @ [] })\n;\n\nimpl A for B where: {}", Edition::CURRENT,
11661166
)
11671167
});
11681168

crates/syntax/src/ted.rs

+6
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,12 @@ pub fn append_child_raw(node: &(impl Into<SyntaxNode> + Clone), child: impl Elem
147147
insert_raw(position, child);
148148
}
149149

150+
151+
pub fn prepend_child(node: &(impl Into<SyntaxNode> + Clone), child: impl Element) {
152+
let position = Position::first_child_of(node);
153+
insert(position, child);
154+
}
155+
150156
fn ws_before(position: &Position, new: &SyntaxElement) -> Option<SyntaxToken> {
151157
let prev = match &position.repr {
152158
PositionRepr::FirstChild(_) => return None,

0 commit comments

Comments
 (0)