mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-27 12:29:21 +00:00
Complete desugared and resugared async fn in trait impls
This commit is contained in:
parent
18feb726be
commit
eb64cc8c35
5 changed files with 267 additions and 87 deletions
|
@ -2207,51 +2207,33 @@ impl Function {
|
||||||
db.function_data(self.id).is_async()
|
db.function_data(self.id).is_async()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this function is a `fn` that returns `impl Future`.
|
pub fn returns_impl_future(self, db: &dyn HirDatabase) -> bool {
|
||||||
pub fn is_desugar_async(self, db: &dyn HirDatabase) -> bool {
|
if self.is_async(db) {
|
||||||
if self.is_async(db) || self.is_const(db) {
|
return true;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(impl_traits) = self.ret_type(db).as_impl_traits(db) else { return false };
|
let Some(impl_traits) = self.ret_type(db).as_impl_traits(db) else { return false };
|
||||||
|
|
||||||
let Some(future_trait_id) =
|
let Some(future_trait_id) =
|
||||||
db.lang_item(self.ty(db).env.krate, LangItem::Future).and_then(|t| t.as_trait())
|
db.lang_item(self.ty(db).env.krate, LangItem::Future).and_then(|t| t.as_trait())
|
||||||
else {
|
else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
let Some(sized_trait_id) =
|
||||||
let Some(size_trait_id) =
|
|
||||||
db.lang_item(self.ty(db).env.krate, LangItem::Sized).and_then(|t| t.as_trait())
|
db.lang_item(self.ty(db).env.krate, LangItem::Sized).and_then(|t| t.as_trait())
|
||||||
else {
|
else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(sync_trait_id) =
|
|
||||||
db.lang_item(self.ty(db).env.krate, LangItem::Sync).and_then(|t| t.as_trait())
|
|
||||||
else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: There's no `LangItem::Send`. How do we get the id of `Send` trait?
|
|
||||||
// let Some(send_trait_id) = db.lang_item(self.ty(db).env.krate, LangItem::Send).and_then(|t| t.as_trait()) else {
|
|
||||||
// eprint!("no future_trait_id\n");
|
|
||||||
// return false
|
|
||||||
// };
|
|
||||||
|
|
||||||
let allowed_to_leaked_types = vec![size_trait_id, sync_trait_id];
|
|
||||||
|
|
||||||
let mut has_impl_future = false;
|
let mut has_impl_future = false;
|
||||||
let mut has_types_not_allow_to_leaked = false;
|
impl_traits
|
||||||
for impl_trait in impl_traits {
|
.filter(|t| {
|
||||||
if impl_trait.id == future_trait_id {
|
let fut = t.id == future_trait_id;
|
||||||
has_impl_future = true;
|
has_impl_future |= fut;
|
||||||
} else if !allowed_to_leaked_types.contains(&impl_trait.id) {
|
!fut && t.id != sized_trait_id
|
||||||
has_types_not_allow_to_leaked = true;
|
})
|
||||||
}
|
// all traits but the future trait must be auto traits
|
||||||
}
|
.all(|t| t.is_auto(db))
|
||||||
|
&& has_impl_future
|
||||||
has_impl_future && !has_types_not_allow_to_leaked
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Does this function have `#[test]` attribute?
|
/// Does this function have `#[test]` attribute?
|
||||||
|
|
|
@ -31,14 +31,14 @@
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use hir::HasAttrs;
|
use hir::{HasAttrs, Name};
|
||||||
use ide_db::{
|
use ide_db::{
|
||||||
documentation::HasDocs, path_transform::PathTransform,
|
documentation::HasDocs, path_transform::PathTransform,
|
||||||
syntax_helpers::insert_whitespace_into_node, traits::get_missing_assoc_items, SymbolKind,
|
syntax_helpers::insert_whitespace_into_node, traits::get_missing_assoc_items, SymbolKind,
|
||||||
};
|
};
|
||||||
use syntax::{
|
use syntax::{
|
||||||
ast::{self, edit_in_place::AttrsOwnerEdit, HasTypeBounds},
|
ast::{self, edit_in_place::AttrsOwnerEdit, make, HasGenericArgs, HasTypeBounds},
|
||||||
format_smolstr, AstNode, SmolStr, SyntaxElement, SyntaxKind, TextRange, ToSmolStr, T,
|
format_smolstr, ted, AstNode, SmolStr, SyntaxElement, SyntaxKind, TextRange, ToSmolStr, T,
|
||||||
};
|
};
|
||||||
use text_edit::TextEdit;
|
use text_edit::TextEdit;
|
||||||
|
|
||||||
|
@ -178,12 +178,36 @@ fn add_function_impl(
|
||||||
func: hir::Function,
|
func: hir::Function,
|
||||||
impl_def: hir::Impl,
|
impl_def: hir::Impl,
|
||||||
) {
|
) {
|
||||||
let fn_name = func.name(ctx.db);
|
let fn_name = &func.name(ctx.db);
|
||||||
|
let sugar: &[_] = if func.is_async(ctx.db) {
|
||||||
|
&[AsyncSugaring::Async, AsyncSugaring::Desugar]
|
||||||
|
} else if func.returns_impl_future(ctx.db) {
|
||||||
|
&[AsyncSugaring::Plain, AsyncSugaring::Resugar]
|
||||||
|
} else {
|
||||||
|
&[AsyncSugaring::Plain]
|
||||||
|
};
|
||||||
|
for &sugaring in sugar {
|
||||||
|
add_function_impl_(acc, ctx, replacement_range, func, impl_def, fn_name, sugaring);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let is_async = func.is_async(ctx.db);
|
fn add_function_impl_(
|
||||||
|
acc: &mut Completions,
|
||||||
|
ctx: &CompletionContext<'_>,
|
||||||
|
replacement_range: TextRange,
|
||||||
|
func: hir::Function,
|
||||||
|
impl_def: hir::Impl,
|
||||||
|
fn_name: &Name,
|
||||||
|
async_sugaring: AsyncSugaring,
|
||||||
|
) {
|
||||||
|
let async_ = if let AsyncSugaring::Async | AsyncSugaring::Resugar = async_sugaring {
|
||||||
|
"async "
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
let label = format_smolstr!(
|
let label = format_smolstr!(
|
||||||
"{}fn {}({})",
|
"{}fn {}({})",
|
||||||
if is_async { "async " } else { "" },
|
async_,
|
||||||
fn_name.display(ctx.db, ctx.edition),
|
fn_name.display(ctx.db, ctx.edition),
|
||||||
if func.assoc_fn_params(ctx.db).is_empty() { "" } else { ".." }
|
if func.assoc_fn_params(ctx.db).is_empty() { "" } else { ".." }
|
||||||
);
|
);
|
||||||
|
@ -195,22 +219,14 @@ fn add_function_impl(
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut item = CompletionItem::new(completion_kind, replacement_range, label, ctx.edition);
|
let mut item = CompletionItem::new(completion_kind, replacement_range, label, ctx.edition);
|
||||||
item.lookup_by(format!(
|
item.lookup_by(format!("{}fn {}", async_, fn_name.display(ctx.db, ctx.edition)))
|
||||||
"{}fn {}",
|
.set_documentation(func.docs(ctx.db))
|
||||||
if is_async { "async " } else { "" },
|
.set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
|
||||||
fn_name.display(ctx.db, ctx.edition)
|
|
||||||
))
|
|
||||||
.set_documentation(func.docs(ctx.db))
|
|
||||||
.set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
|
|
||||||
|
|
||||||
if let Some(source) = ctx.sema.source(func) {
|
if let Some(source) = ctx.sema.source(func) {
|
||||||
let assoc_item = ast::AssocItem::Fn(source.value);
|
if let Some(transformed_fn) =
|
||||||
if let Some(transformed_item) = get_transformed_assoc_item(ctx, assoc_item, impl_def) {
|
get_transformed_fn(ctx, source.value, impl_def, async_sugaring)
|
||||||
let transformed_fn = match transformed_item {
|
{
|
||||||
ast::AssocItem::Fn(func) => func,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
// TODO: need `function_decl` that unwraps future in the return type
|
|
||||||
let function_decl = function_declaration(&transformed_fn, source.file_id.is_macro());
|
let function_decl = function_declaration(&transformed_fn, source.file_id.is_macro());
|
||||||
match ctx.config.snippet_cap {
|
match ctx.config.snippet_cap {
|
||||||
Some(cap) => {
|
Some(cap) => {
|
||||||
|
@ -225,42 +241,14 @@ fn add_function_impl(
|
||||||
item.add_to(acc, ctx.db);
|
item.add_to(acc, ctx.db);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
eprint!("is_desugar_async: {}", func.is_desugar_async(ctx.db));
|
#[derive(Copy, Clone)]
|
||||||
if func.is_desugar_async(ctx.db) {
|
enum AsyncSugaring {
|
||||||
let label = format_smolstr!(
|
Desugar,
|
||||||
"async fn {}({})",
|
Resugar,
|
||||||
fn_name.display(ctx.db),
|
Async,
|
||||||
if func.assoc_fn_params(ctx.db).is_empty() { "" } else { ".." }
|
Plain,
|
||||||
);
|
|
||||||
let mut item = CompletionItem::new(completion_kind, replacement_range, label);
|
|
||||||
item.lookup_by(format!("async fn {}", fn_name.display(ctx.db)))
|
|
||||||
.set_documentation(func.docs(ctx.db))
|
|
||||||
.set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
|
|
||||||
if let Some(source) = ctx.sema.source(func) {
|
|
||||||
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, source.file_id.is_macro());
|
|
||||||
match ctx.config.snippet_cap {
|
|
||||||
Some(cap) => {
|
|
||||||
let snippet = format!("{function_decl} {{\n $0\n}}");
|
|
||||||
item.snippet_edit(cap, TextEdit::replace(replacement_range, snippet));
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let header = format!("{function_decl} {{");
|
|
||||||
item.text_edit(TextEdit::replace(replacement_range, header));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
item.add_to(acc, ctx.db);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transform a relevant associated item to inline generics from the impl, remove attrs and docs, etc.
|
/// Transform a relevant associated item to inline generics from the impl, remove attrs and docs, etc.
|
||||||
|
@ -287,6 +275,82 @@ fn get_transformed_assoc_item(
|
||||||
Some(assoc_item)
|
Some(assoc_item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Transform a relevant associated item to inline generics from the impl, remove attrs and docs, etc.
|
||||||
|
fn get_transformed_fn(
|
||||||
|
ctx: &CompletionContext<'_>,
|
||||||
|
fn_: ast::Fn,
|
||||||
|
impl_def: hir::Impl,
|
||||||
|
async_: AsyncSugaring,
|
||||||
|
) -> Option<ast::Fn> {
|
||||||
|
let trait_ = impl_def.trait_(ctx.db)?;
|
||||||
|
let source_scope = &ctx.sema.scope(fn_.syntax())?;
|
||||||
|
let target_scope = &ctx.sema.scope(ctx.sema.source(impl_def)?.syntax().value)?;
|
||||||
|
let transform = PathTransform::trait_impl(
|
||||||
|
target_scope,
|
||||||
|
source_scope,
|
||||||
|
trait_,
|
||||||
|
ctx.sema.source(impl_def)?.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
let fn_ = fn_.clone_for_update();
|
||||||
|
// FIXME: Paths in nested macros are not handled well. See
|
||||||
|
// `macro_generated_assoc_item2` test.
|
||||||
|
transform.apply(fn_.syntax());
|
||||||
|
fn_.remove_attrs_and_docs();
|
||||||
|
match async_ {
|
||||||
|
AsyncSugaring::Desugar => {
|
||||||
|
match fn_.ret_type() {
|
||||||
|
Some(ret_ty) => {
|
||||||
|
let ty = ret_ty.ty()?;
|
||||||
|
ted::replace(
|
||||||
|
ty.syntax(),
|
||||||
|
make::ty(&format!("impl Future<Output = {ty}>"))
|
||||||
|
.syntax()
|
||||||
|
.clone_for_update(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => ted::append_child(
|
||||||
|
fn_.param_list()?.syntax(),
|
||||||
|
make::ret_type(make::ty("impl Future<Output = ()>"))
|
||||||
|
.syntax()
|
||||||
|
.clone_for_update(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
fn_.async_token().unwrap().detach();
|
||||||
|
}
|
||||||
|
AsyncSugaring::Resugar => {
|
||||||
|
let ty = fn_.ret_type()?.ty()?;
|
||||||
|
match &ty {
|
||||||
|
// best effort guessing here
|
||||||
|
ast::Type::ImplTraitType(t) => {
|
||||||
|
let output = t.type_bound_list()?.bounds().find_map(|b| match b.ty()? {
|
||||||
|
ast::Type::PathType(p) => {
|
||||||
|
let p = p.path()?.segment()?;
|
||||||
|
if p.name_ref()?.text() != "Future" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
match p.generic_arg_list()?.generic_args().next()? {
|
||||||
|
ast::GenericArg::AssocTypeArg(a)
|
||||||
|
if a.name_ref()?.text() == "Output" =>
|
||||||
|
{
|
||||||
|
a.ty()
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})?;
|
||||||
|
ted::replace(ty.syntax(), output.syntax());
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
ted::prepend_child(fn_.syntax(), make::token(T![async]));
|
||||||
|
}
|
||||||
|
AsyncSugaring::Async | AsyncSugaring::Plain => (),
|
||||||
|
}
|
||||||
|
Some(fn_)
|
||||||
|
}
|
||||||
|
|
||||||
fn add_type_alias_impl(
|
fn add_type_alias_impl(
|
||||||
acc: &mut Completions,
|
acc: &mut Completions,
|
||||||
ctx: &CompletionContext<'_>,
|
ctx: &CompletionContext<'_>,
|
||||||
|
@ -1437,6 +1501,134 @@ trait Tr {
|
||||||
impl Tr for () {
|
impl Tr for () {
|
||||||
type Item = $0;
|
type Item = $0;
|
||||||
}
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn impl_fut() {
|
||||||
|
check_edit(
|
||||||
|
"fn foo",
|
||||||
|
r#"
|
||||||
|
//- minicore: future, send, sized
|
||||||
|
use core::future::Future;
|
||||||
|
|
||||||
|
trait DesugaredAsyncTrait {
|
||||||
|
fn foo(&self) -> impl Future<Output = usize> + Send;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DesugaredAsyncTrait for () {
|
||||||
|
$0
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
use core::future::Future;
|
||||||
|
|
||||||
|
trait DesugaredAsyncTrait {
|
||||||
|
fn foo(&self) -> impl Future<Output = usize> + Send;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DesugaredAsyncTrait for () {
|
||||||
|
fn foo(&self) -> impl Future<Output = usize> + Send {
|
||||||
|
$0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn impl_fut_resugared() {
|
||||||
|
check_edit(
|
||||||
|
"async fn foo",
|
||||||
|
r#"
|
||||||
|
//- minicore: future, send, sized
|
||||||
|
use core::future::Future;
|
||||||
|
|
||||||
|
trait DesugaredAsyncTrait {
|
||||||
|
fn foo(&self) -> impl Future<Output = usize> + Send;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DesugaredAsyncTrait for () {
|
||||||
|
$0
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
use core::future::Future;
|
||||||
|
|
||||||
|
trait DesugaredAsyncTrait {
|
||||||
|
fn foo(&self) -> impl Future<Output = usize> + Send;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DesugaredAsyncTrait for () {
|
||||||
|
async fn foo(&self) -> usize {
|
||||||
|
$0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn async_desugared() {
|
||||||
|
check_edit(
|
||||||
|
"fn foo",
|
||||||
|
r#"
|
||||||
|
//- minicore: future, send, sized
|
||||||
|
use core::future::Future;
|
||||||
|
|
||||||
|
trait DesugaredAsyncTrait {
|
||||||
|
async fn foo(&self) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DesugaredAsyncTrait for () {
|
||||||
|
$0
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
use core::future::Future;
|
||||||
|
|
||||||
|
trait DesugaredAsyncTrait {
|
||||||
|
async fn foo(&self) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DesugaredAsyncTrait for () {
|
||||||
|
fn foo(&self) -> impl Future<Output = usize> {
|
||||||
|
$0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn async_() {
|
||||||
|
check_edit(
|
||||||
|
"async fn foo",
|
||||||
|
r#"
|
||||||
|
//- minicore: future, send, sized
|
||||||
|
use core::future::Future;
|
||||||
|
|
||||||
|
trait DesugaredAsyncTrait {
|
||||||
|
async fn foo(&self) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DesugaredAsyncTrait for () {
|
||||||
|
$0
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
use core::future::Future;
|
||||||
|
|
||||||
|
trait DesugaredAsyncTrait {
|
||||||
|
async fn foo(&self) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DesugaredAsyncTrait for () {
|
||||||
|
async fn foo(&self) -> usize {
|
||||||
|
$0
|
||||||
|
}
|
||||||
|
}
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -313,6 +313,7 @@ impl Test for () {
|
||||||
ct const CONST1: () =
|
ct const CONST1: () =
|
||||||
fn async fn function2()
|
fn async fn function2()
|
||||||
fn fn function1()
|
fn fn function1()
|
||||||
|
fn fn function2()
|
||||||
ma makro!(…) macro_rules! makro
|
ma makro!(…) macro_rules! makro
|
||||||
md module
|
md module
|
||||||
ta type Type1 =
|
ta type Type1 =
|
||||||
|
|
|
@ -1162,7 +1162,7 @@ pub mod tokens {
|
||||||
|
|
||||||
pub(super) static SOURCE_FILE: LazyLock<Parse<SourceFile>> = LazyLock::new(|| {
|
pub(super) static SOURCE_FILE: LazyLock<Parse<SourceFile>> = LazyLock::new(|| {
|
||||||
SourceFile::parse(
|
SourceFile::parse(
|
||||||
"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,
|
"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,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -147,6 +147,11 @@ pub fn append_child_raw(node: &(impl Into<SyntaxNode> + Clone), child: impl Elem
|
||||||
insert_raw(position, child);
|
insert_raw(position, child);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn prepend_child(node: &(impl Into<SyntaxNode> + Clone), child: impl Element) {
|
||||||
|
let position = Position::first_child_of(node);
|
||||||
|
insert(position, child);
|
||||||
|
}
|
||||||
|
|
||||||
fn ws_before(position: &Position, new: &SyntaxElement) -> Option<SyntaxToken> {
|
fn ws_before(position: &Position, new: &SyntaxElement) -> Option<SyntaxToken> {
|
||||||
let prev = match &position.repr {
|
let prev = match &position.repr {
|
||||||
PositionRepr::FirstChild(_) => return None,
|
PositionRepr::FirstChild(_) => return None,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue