From b4d9fa4a7090f856b020ed595b90d934821eb43f Mon Sep 17 00:00:00 2001 From: albab-hasan Date: Wed, 20 May 2026 18:43:47 +0600 Subject: [PATCH] feat: emit async modifier for functions returning impl Future the `async` semantic token modifier was only emitted for `async fn`. functions in embedded async crates like embassy commonly return `impl Future` or a concrete type that implements `Future` without the `async` keyword, causing inconsistent highlighting. `returns_impl_future` in `hir` is extended to fall through to an `impls_trait` check when the return type is not `impl Trait` syntax, covering concrete structs and type aliases that implement `Future`. the `Sized` lang item is now treated as optional in the impl Trait path rather than a hard failure, so its absence in minimal crate environments no longer silently returns false. closes rust-lang/rust-analyzer#22407 --- crates/hir/src/lib.rs | 29 +++--- .../ide/src/syntax_highlighting/highlight.rs | 4 +- .../test_data/highlight_async.html | 96 +++++++++++++++++++ crates/ide/src/syntax_highlighting/tests.rs | 64 +++++++++++++ 4 files changed, 177 insertions(+), 16 deletions(-) create mode 100644 crates/ide/src/syntax_highlighting/test_data/highlight_async.html diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 9f94243062b3..e704511d655c 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -2790,25 +2790,26 @@ impl Function { } let ret_type = self.ret_type(db); - let Some(impl_traits) = ret_type.as_impl_traits(db) else { return false }; let lang_items = hir_def::lang_item::lang_items(db, self.krate(db).id); let Some(future_trait_id) = lang_items.Future else { return false; }; - let Some(sized_trait_id) = lang_items.Sized else { - return false; - }; - let mut has_impl_future = false; - impl_traits - .filter(|t| { - let fut = t.id == future_trait_id; - has_impl_future |= fut; - !fut && t.id != sized_trait_id - }) - // all traits but the future trait must be auto traits - .all(|t| t.is_auto(db)) - && has_impl_future + if let Some(impl_traits) = ret_type.as_impl_traits(db) { + let sized_trait_id = lang_items.Sized; + let mut has_impl_future = false; + return impl_traits + .filter(|t| { + let fut = t.id == future_trait_id; + has_impl_future |= fut; + !fut && sized_trait_id.map_or(true, |sid| t.id != sid) + }) + // all traits but the future trait must be auto traits + .all(|t| t.is_auto(db)) + && has_impl_future; + } + + ret_type.impls_trait(db, Trait::from(future_trait_id), &[]) } /// Does this function have `#[test]` attribute? diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs index 6823736d1273..9101aaffb8ed 100644 --- a/crates/ide/src/syntax_highlighting/highlight.rs +++ b/crates/ide/src/syntax_highlighting/highlight.rs @@ -520,7 +520,7 @@ pub(super) fn highlight_def( if !is_ref && func.is_unsafe_to_call(db, None, edition) { h |= HlMod::Unsafe; } - if func.is_async(db) { + if func.returns_impl_future(db) { h |= HlMod::Async; } if func.is_const(db) { @@ -706,7 +706,7 @@ fn highlight_method_call( if is_unsafe { h |= HlMod::Unsafe; } - if func.is_async(sema.db) { + if func.returns_impl_future(sema.db) { h |= HlMod::Async; } if func.is_const(sema.db) { diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_async.html b/crates/ide/src/syntax_highlighting/test_data/highlight_async.html new file mode 100644 index 000000000000..d4866679c603 --- /dev/null +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_async.html @@ -0,0 +1,96 @@ + + +
use core::future::Future;
+use core::pin::Pin;
+use core::task::{Context, Poll};
+
+async fn async_fn() {}
+
+fn returns_impl_future() -> impl Future<Output = ()> {
+    async {}
+}
+
+struct MyFuture;
+
+impl Future for MyFuture {
+    type Output = ();
+    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
+        Poll::Ready(())
+    }
+}
+
+struct Foo;
+
+impl Foo {
+    async fn async_method(&self) {}
+    fn impl_future_method(&self) -> impl Future<Output = ()> {
+        async {}
+    }
+    fn concrete_future_method(&self) -> MyFuture {
+        MyFuture
+    }
+}
+
+fn returns_concrete_future() -> MyFuture {
+    MyFuture
+}
+
+type AliasFuture = MyFuture;
+
+fn returns_alias_future() -> AliasFuture {
+    MyFuture
+}
+
+fn not_async() {}
+
+fn use_them() {
+    returns_impl_future();
+    returns_concrete_future();
+    returns_alias_future();
+    not_async();
+    let foo = Foo;
+    foo.async_method();
+    foo.impl_future_method();
+    foo.concrete_future_method();
+}
\ No newline at end of file diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs index ab69578ed9d0..74a95642acb7 100644 --- a/crates/ide/src/syntax_highlighting/tests.rs +++ b/crates/ide/src/syntax_highlighting/tests.rs @@ -1584,3 +1584,67 @@ static STATIC: () = (); false, ); } + +#[test] +fn test_async_highlighting() { + check_highlighting( + r#" +//- minicore: future, pin, sized +use core::future::Future; +use core::pin::Pin; +use core::task::{Context, Poll}; + +async fn async_fn() {} + +fn returns_impl_future() -> impl Future { + async {} +} + +struct MyFuture; + +impl Future for MyFuture { + type Output = (); + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + Poll::Ready(()) + } +} + +struct Foo; + +impl Foo { + async fn async_method(&self) {} + fn impl_future_method(&self) -> impl Future { + async {} + } + fn concrete_future_method(&self) -> MyFuture { + MyFuture + } +} + +fn returns_concrete_future() -> MyFuture { + MyFuture +} + +type AliasFuture = MyFuture; + +fn returns_alias_future() -> AliasFuture { + MyFuture +} + +fn not_async() {} + +fn use_them() { + returns_impl_future(); + returns_concrete_future(); + returns_alias_future(); + not_async(); + let foo = Foo; + foo.async_method(); + foo.impl_future_method(); + foo.concrete_future_method(); +} +"#, + expect_file!["./test_data/highlight_async.html"], + false, + ); +}