From 1f441a1f0c6065ca5d9765049d1af5db022f0d9e Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Mon, 8 May 2023 13:03:40 +0200 Subject: [PATCH] Add `Future::map` --- compiler/rustc_ty_utils/src/instance.rs | 24 ++++---- library/core/src/future/future.rs | 34 +++++++++++ library/core/src/future/map.rs | 57 +++++++++++++++++++ library/core/src/future/mod.rs | 6 ++ library/core/tests/future.rs | 12 +++- library/core/tests/lib.rs | 1 + ...5079-missing-move-in-nested-closure.stderr | 2 +- .../static-return-lifetime-infered.stderr | 4 +- tests/ui/lint/issue-106991.stderr | 2 +- tests/ui/methods/method-missing-call.stderr | 2 +- .../method-not-found-generic-arg-elision.rs | 2 +- ...ethod-not-found-generic-arg-elision.stderr | 2 +- .../mismatched_types/closure-arg-count.stderr | 8 +-- .../closure-arg-type-mismatch.stderr | 6 +- tests/ui/nll/issue-54556-stephaneyfx.stderr | 2 +- tests/ui/recursion/issue-83150.stderr | 4 +- .../return_type_containing_closure.stderr | 2 +- 17 files changed, 136 insertions(+), 34 deletions(-) create mode 100644 library/core/src/future/map.rs diff --git a/compiler/rustc_ty_utils/src/instance.rs b/compiler/rustc_ty_utils/src/instance.rs index b10aaad5f2af4..b0d9a8323e1ad 100644 --- a/compiler/rustc_ty_utils/src/instance.rs +++ b/compiler/rustc_ty_utils/src/instance.rs @@ -195,21 +195,17 @@ fn resolve_associated_item<'tcx>( }) } traits::ImplSource::Future(future_data) => { - if cfg!(debug_assertions) && tcx.item_name(trait_item_id) != sym::poll { - // For compiler developers who'd like to add new items to `Future`, - // you either need to generate a shim body, or perhaps return - // `InstanceDef::Item` pointing to a trait default method body if - // it is given a default implementation by the trait. - span_bug!( - tcx.def_span(future_data.generator_def_id), - "no definition for `{trait_ref}::{}` for built-in async generator type", - tcx.item_name(trait_item_id) - ) + if Some(trait_item_id) == tcx.lang_items().future_poll_fn() { + // `Future::poll` is generated by the compiler. + Some(Instance { + def: ty::InstanceDef::Item(future_data.generator_def_id), + substs: future_data.substs, + }) + } else { + // All other methods are default methods of the `Future` trait. + // (this assumes that `ImplSource::Future` is only used for methods on `Future`) + Some(Instance::new(trait_item_id, rcvr_substs)) } - Some(Instance { - def: ty::InstanceDef::Item(future_data.generator_def_id), - substs: future_data.substs, - }) } traits::ImplSource::Closure(closure_data) => { if cfg!(debug_assertions) diff --git a/library/core/src/future/future.rs b/library/core/src/future/future.rs index 8c7111cb3ff0b..c32cafc362dd4 100644 --- a/library/core/src/future/future.rs +++ b/library/core/src/future/future.rs @@ -1,5 +1,7 @@ #![stable(feature = "futures_api", since = "1.36.0")] +#[cfg(not(bootstrap))] +use crate::future::Map; use crate::marker::Unpin; use crate::ops; use crate::pin::Pin; @@ -103,6 +105,38 @@ pub trait Future { #[lang = "poll"] #[stable(feature = "futures_api", since = "1.36.0")] fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll; + + /// Map this future's output to a different type, returning a new future of + /// the resulting type. + /// + /// This function is similar to [`Option::map`] or [`Iterator::map`] where + /// it will change the type of the underlying future. This is useful to + /// chain along a computation once a future has been resolved. + /// + /// Note that this function consumes the receiving future and returns a + /// wrapped version of it, similar to the existing `map` methods in the + /// standard library. + /// + /// # Examples + /// + /// ``` + /// #![feature(future_map)] + /// use core::future::Future; + /// # async fn f() { + /// let future = async { 1 }; + /// let new_future = future.map(|x| x + 3); + /// assert_eq!(new_future.await, 4); + /// # } + /// ``` + #[cfg(not(bootstrap))] + #[unstable(feature = "future_map", issue = "none")] + fn map(self, f: F) -> Map + where + F: FnOnce(Self::Output) -> U, + Self: Sized, + { + Map::new(self, f) + } } #[stable(feature = "futures_api", since = "1.36.0")] diff --git a/library/core/src/future/map.rs b/library/core/src/future/map.rs new file mode 100644 index 0000000000000..ed77973c7bf89 --- /dev/null +++ b/library/core/src/future/map.rs @@ -0,0 +1,57 @@ +#![allow(unused)] + +use crate::future::Future; +use crate::pin::Pin; +use crate::task::{Context, Poll}; +use crate::{fmt, mem, ptr}; + +/// A [`Future`] that maps the output of a wrapped [`Future`]. +/// +/// Returned by [`Future::map`]. +#[unstable(feature = "future_map", issue = "none")] +pub struct Map { + inner: Option<(Fut, F)>, +} + +impl Map { + pub(crate) fn new(future: Fut, f: F) -> Self { + Self { inner: Some((future, f)) } + } +} + +#[unstable(feature = "future_map", issue = "none")] +impl fmt::Debug for Map { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Map").field("future", &self.inner.as_ref().map(|(fut, _)| fut)).finish() + } +} + +#[unstable(feature = "future_map", issue = "none")] +impl U, U> Future for Map { + type Output = U; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // SAFETY: we make sure to not move the inner future + unsafe { + let this = Pin::into_inner_unchecked(self); + match &mut this.inner { + Some((future, _)) => { + let pin = Pin::new_unchecked(&mut *future); + match pin.poll(cx) { + Poll::Ready(value) => { + // The future must be dropped in-place since it is pinned. + ptr::drop_in_place(future); + + let (future, map) = this.inner.take().unwrap_unchecked(); + mem::forget(future); + + Poll::Ready(map(value)) + } + Poll::Pending => Poll::Pending, + } + } + None => panic!("Map must not be polled after it returned `Poll::Ready`"), + } + } + } +} diff --git a/library/core/src/future/mod.rs b/library/core/src/future/mod.rs index 7a8d0cacdece5..6e963ddff777f 100644 --- a/library/core/src/future/mod.rs +++ b/library/core/src/future/mod.rs @@ -15,6 +15,8 @@ use crate::task::Context; mod future; mod into_future; mod join; +#[cfg(not(bootstrap))] +mod map; mod pending; mod poll_fn; mod ready; @@ -36,6 +38,10 @@ pub use ready::{ready, Ready}; #[stable(feature = "future_poll_fn", since = "1.64.0")] pub use poll_fn::{poll_fn, PollFn}; +#[cfg(not(bootstrap))] +#[unstable(feature = "future_map", issue = "none")] +pub use map::Map; + /// This type is needed because: /// /// a) Generators cannot implement `for<'a, 'b> Generator<&'a mut Context<'b>>`, so we need to pass diff --git a/library/core/tests/future.rs b/library/core/tests/future.rs index 74b6f74e4013c..fe962aaffb783 100644 --- a/library/core/tests/future.rs +++ b/library/core/tests/future.rs @@ -99,7 +99,7 @@ mod test_join_function_like_value_arg_semantics { } } -fn block_on(fut: impl Future) { +fn block_on(fut: F) -> F::Output { struct Waker; impl Wake for Waker { fn wake(self: Arc) { @@ -113,7 +113,7 @@ fn block_on(fut: impl Future) { loop { match fut.as_mut().poll(&mut cx) { - Poll::Ready(_) => break, + Poll::Ready(value) => break value, Poll::Pending => thread::park(), } } @@ -126,3 +126,11 @@ fn _pending_impl_all_auto_traits() { all_auto_traits::>(); } + +#[cfg(not(bootstrap))] +#[test] +fn test_map() { + let future = async { 1 }; + let future = future.map(|x| x + 3); + assert_eq!(block_on(future), 4); +} diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs index 84859a54c2604..ca43becf9e337 100644 --- a/library/core/tests/lib.rs +++ b/library/core/tests/lib.rs @@ -36,6 +36,7 @@ #![feature(fmt_internals)] #![feature(float_minimum_maximum)] #![feature(future_join)] +#![cfg_attr(not(bootstrap), feature(future_map))] #![feature(generic_assert_internals)] #![feature(array_try_from_fn)] #![feature(hasher_prefixfree_extras)] diff --git a/tests/ui/borrowck/issue-95079-missing-move-in-nested-closure.stderr b/tests/ui/borrowck/issue-95079-missing-move-in-nested-closure.stderr index 776c338deacf4..716569099acd6 100644 --- a/tests/ui/borrowck/issue-95079-missing-move-in-nested-closure.stderr +++ b/tests/ui/borrowck/issue-95079-missing-move-in-nested-closure.stderr @@ -24,7 +24,7 @@ error: lifetime may not live long enough LL | move |()| s.chars().map(|c| format!("{}{}", c, s)) | --------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2` | | | - | | return type of closure `Map, [closure@$DIR/issue-95079-missing-move-in-nested-closure.rs:11:29: 11:32]>` contains a lifetime `'2` + | | return type of closure `std::iter::Map, [closure@$DIR/issue-95079-missing-move-in-nested-closure.rs:11:29: 11:32]>` contains a lifetime `'2` | lifetime `'1` represents this closure's body | = note: closure implements `Fn`, so references to captured variables can't escape the closure diff --git a/tests/ui/impl-trait/static-return-lifetime-infered.stderr b/tests/ui/impl-trait/static-return-lifetime-infered.stderr index 488cb821c1051..d44e85d8c6d3c 100644 --- a/tests/ui/impl-trait/static-return-lifetime-infered.stderr +++ b/tests/ui/impl-trait/static-return-lifetime-infered.stderr @@ -4,7 +4,7 @@ error[E0700]: hidden type for `impl Iterator` captures lifetime that LL | fn iter_values_anon(&self) -> impl Iterator { | ----- ----------------------- opaque type defined here | | - | hidden type `Map, [closure@$DIR/static-return-lifetime-infered.rs:7:27: 7:30]>` captures the anonymous lifetime defined here + | hidden type `std::iter::Map, [closure@$DIR/static-return-lifetime-infered.rs:7:27: 7:30]>` captures the anonymous lifetime defined here LL | self.x.iter().map(|a| a.0) | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | @@ -19,7 +19,7 @@ error[E0700]: hidden type for `impl Iterator` captures lifetime that LL | fn iter_values<'a>(&'a self) -> impl Iterator { | -- ----------------------- opaque type defined here | | - | hidden type `Map, [closure@$DIR/static-return-lifetime-infered.rs:11:27: 11:30]>` captures the lifetime `'a` as defined here + | hidden type `std::iter::Map, [closure@$DIR/static-return-lifetime-infered.rs:11:27: 11:30]>` captures the lifetime `'a` as defined here LL | self.x.iter().map(|a| a.0) | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | diff --git a/tests/ui/lint/issue-106991.stderr b/tests/ui/lint/issue-106991.stderr index 7b43f0b2ca8f3..b4172c5642205 100644 --- a/tests/ui/lint/issue-106991.stderr +++ b/tests/ui/lint/issue-106991.stderr @@ -4,7 +4,7 @@ error[E0271]: expected `foo` to be a fn item that returns `i32`, but it returns LL | fn bar() -> impl Iterator { | ^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `i32` | - = note: required for `Map>, for<'a> fn(&'a mut Vec) {foo}>` to implement `Iterator` + = note: required for `std::iter::Map>, for<'a> fn(&'a mut Vec) {foo}>` to implement `Iterator` error: aborting due to previous error diff --git a/tests/ui/methods/method-missing-call.stderr b/tests/ui/methods/method-missing-call.stderr index 040a65d168091..710f9af1f7879 100644 --- a/tests/ui/methods/method-missing-call.stderr +++ b/tests/ui/methods/method-missing-call.stderr @@ -9,7 +9,7 @@ help: use parentheses to call the method LL | .get_x(); | ++ -error[E0615]: attempted to take value of method `filter_map` on type `Filter, [closure@$DIR/method-missing-call.rs:27:20: 27:23]>, [closure@$DIR/method-missing-call.rs:28:23: 28:28]>` +error[E0615]: attempted to take value of method `filter_map` on type `Filter, [closure@$DIR/method-missing-call.rs:27:20: 27:23]>, [closure@$DIR/method-missing-call.rs:28:23: 28:28]>` --> $DIR/method-missing-call.rs:29:16 | LL | .filter_map; diff --git a/tests/ui/methods/method-not-found-generic-arg-elision.rs b/tests/ui/methods/method-not-found-generic-arg-elision.rs index 538eeadae0836..12217bb040e7b 100644 --- a/tests/ui/methods/method-not-found-generic-arg-elision.rs +++ b/tests/ui/methods/method-not-found-generic-arg-elision.rs @@ -85,7 +85,7 @@ fn main() { //~^ ERROR no method named `other` found for struct `Point let v = vec![1, 2, 3]; v.iter().map(Box::new(|x| x * x) as Box i32>).extend(std::iter::once(100)); - //~^ ERROR no method named `extend` found for struct `Map + //~^ ERROR no method named `extend` found for struct `std::iter::Map let wrapper = Wrapper(true); wrapper.method(); //~^ ERROR no method named `method` found for struct `Wrapper diff --git a/tests/ui/methods/method-not-found-generic-arg-elision.stderr b/tests/ui/methods/method-not-found-generic-arg-elision.stderr index b97688d3868b8..09c163f21b9a9 100644 --- a/tests/ui/methods/method-not-found-generic-arg-elision.stderr +++ b/tests/ui/methods/method-not-found-generic-arg-elision.stderr @@ -19,7 +19,7 @@ LL | struct Point { LL | let d = point_i32.other(); | ^^^^^ method not found in `Point` -error[E0599]: no method named `extend` found for struct `Map` in the current scope +error[E0599]: no method named `extend` found for struct `std::iter::Map` in the current scope --> $DIR/method-not-found-generic-arg-elision.rs:87:67 | LL | v.iter().map(Box::new(|x| x * x) as Box i32>).extend(std::iter::once(100)); diff --git a/tests/ui/mismatched_types/closure-arg-count.stderr b/tests/ui/mismatched_types/closure-arg-count.stderr index 2ecab9f024a12..218aca0ef11c5 100644 --- a/tests/ui/mismatched_types/closure-arg-count.stderr +++ b/tests/ui/mismatched_types/closure-arg-count.stderr @@ -126,7 +126,7 @@ LL | let _it = vec![1, 2, 3].into_iter().enumerate().map(foo); LL | fn foo() {} | -------- takes 0 arguments | -note: required by a bound in `map` +note: required by a bound in `std::iter::Iterator::map` --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL error[E0593]: closure is expected to take a single 2-tuple as argument, but it takes 3 distinct arguments @@ -139,7 +139,7 @@ LL | let _it = vec![1, 2, 3].into_iter().enumerate().map(bar); | | | required by a bound introduced by this call | -note: required by a bound in `map` +note: required by a bound in `std::iter::Iterator::map` --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL error[E0593]: function is expected to take a single 2-tuple as argument, but it takes 2 distinct arguments @@ -153,7 +153,7 @@ LL | let _it = vec![1, 2, 3].into_iter().enumerate().map(qux); LL | fn qux(x: usize, y: usize) {} | -------------------------- takes 2 distinct arguments | -note: required by a bound in `map` +note: required by a bound in `std::iter::Iterator::map` --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL error[E0593]: function is expected to take 1 argument, but it takes 2 arguments @@ -164,7 +164,7 @@ LL | let _it = vec![1, 2, 3].into_iter().map(usize::checked_add); | | | required by a bound introduced by this call | -note: required by a bound in `map` +note: required by a bound in `std::iter::Iterator::map` --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL error[E0593]: function is expected to take 0 arguments, but it takes 1 argument diff --git a/tests/ui/mismatched_types/closure-arg-type-mismatch.stderr b/tests/ui/mismatched_types/closure-arg-type-mismatch.stderr index 811ff0533f012..1d3424e42d208 100644 --- a/tests/ui/mismatched_types/closure-arg-type-mismatch.stderr +++ b/tests/ui/mismatched_types/closure-arg-type-mismatch.stderr @@ -8,7 +8,7 @@ LL | a.iter().map(|_: (u32, u32)| 45); | = note: expected closure signature `fn(&(u32, u32)) -> _` found closure signature `fn((u32, u32)) -> _` -note: required by a bound in `map` +note: required by a bound in `std::iter::Iterator::map` --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL help: consider borrowing the argument | @@ -25,7 +25,7 @@ LL | a.iter().map(|_: &(u16, u16)| 45); | = note: expected closure signature `fn(&(u32, u32)) -> _` found closure signature `for<'a> fn(&'a (u16, u16)) -> _` -note: required by a bound in `map` +note: required by a bound in `std::iter::Iterator::map` --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL error[E0631]: type mismatch in closure arguments @@ -38,7 +38,7 @@ LL | a.iter().map(|_: (u16, u16)| 45); | = note: expected closure signature `fn(&(u32, u32)) -> _` found closure signature `fn((u16, u16)) -> _` -note: required by a bound in `map` +note: required by a bound in `std::iter::Iterator::map` --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL error: aborting due to 3 previous errors diff --git a/tests/ui/nll/issue-54556-stephaneyfx.stderr b/tests/ui/nll/issue-54556-stephaneyfx.stderr index f9e82cb003fc2..a9d116bc65ba0 100644 --- a/tests/ui/nll/issue-54556-stephaneyfx.stderr +++ b/tests/ui/nll/issue-54556-stephaneyfx.stderr @@ -12,7 +12,7 @@ LL | } | - | | | `stmt` dropped here while still borrowed - | ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Map, [closure@$DIR/issue-54556-stephaneyfx.rs:28:14: 28:19]>` + | ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `std::iter::Map, [closure@$DIR/issue-54556-stephaneyfx.rs:28:14: 28:19]>` | = note: the temporary is part of an expression at the end of a block; consider forcing this temporary to be dropped sooner, before the block's local variables are dropped diff --git a/tests/ui/recursion/issue-83150.stderr b/tests/ui/recursion/issue-83150.stderr index 64683ae3a6ebd..befb2ed52b535 100644 --- a/tests/ui/recursion/issue-83150.stderr +++ b/tests/ui/recursion/issue-83150.stderr @@ -9,10 +9,10 @@ LL | func(&mut iter.map(|x| x + 1)) = help: a `loop` may express intention better if this is on purpose = note: `#[warn(unconditional_recursion)]` on by default -error[E0275]: overflow evaluating the requirement `Map<&mut std::ops::Range, [closure@$DIR/issue-83150.rs:12:24: 12:27]>: Iterator` +error[E0275]: overflow evaluating the requirement `std::iter::Map<&mut std::ops::Range, [closure@$DIR/issue-83150.rs:12:24: 12:27]>: Iterator` | = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`issue_83150`) - = note: required for `&mut Map<&mut std::ops::Range, [closure@$DIR/issue-83150.rs:12:24: 12:27]>` to implement `Iterator` + = note: required for `&mut std::iter::Map<&mut std::ops::Range, [closure@$DIR/issue-83150.rs:12:24: 12:27]>` to implement `Iterator` = note: 65 redundant requirements hidden = note: required for `&mut Map<&mut Map<&mut Map<&mut Map<&mut Map<&mut Map<&mut Map<..., ...>, ...>, ...>, ...>, ...>, ...>, ...>` to implement `Iterator` = note: the full type name has been written to '$TEST_BUILD_DIR/recursion/issue-83150/issue-83150.long-type-hash.txt' diff --git a/tests/ui/typeck/return_type_containing_closure.stderr b/tests/ui/typeck/return_type_containing_closure.stderr index f9a240963997b..7129ad6118d78 100644 --- a/tests/ui/typeck/return_type_containing_closure.stderr +++ b/tests/ui/typeck/return_type_containing_closure.stderr @@ -5,7 +5,7 @@ LL | vec!['a'].iter().map(|c| c) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `Map, ...>` | = note: expected unit type `()` - found struct `Map, [closure@$DIR/return_type_containing_closure.rs:3:26: 3:29]>` + found struct `std::iter::Map, [closure@$DIR/return_type_containing_closure.rs:3:26: 3:29]>` help: consider using a semicolon here | LL | vec!['a'].iter().map(|c| c);