summaryrefslogtreecommitdiffstats
path: root/vendor/yoke/src/yoke.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/yoke/src/yoke.rs')
-rw-r--r--vendor/yoke/src/yoke.rs228
1 files changed, 200 insertions, 28 deletions
diff --git a/vendor/yoke/src/yoke.rs b/vendor/yoke/src/yoke.rs
index c3d8c37d9..7468b4d99 100644
--- a/vendor/yoke/src/yoke.rs
+++ b/vendor/yoke/src/yoke.rs
@@ -6,7 +6,6 @@ use crate::either::EitherCart;
#[cfg(feature = "alloc")]
use crate::erased::{ErasedArcCart, ErasedBoxCart, ErasedRcCart};
use crate::trait_hack::YokeTraitHack;
-use crate::IsCovariant;
use crate::Yokeable;
use core::marker::PhantomData;
use core::ops::Deref;
@@ -48,8 +47,7 @@ use alloc::sync::Arc;
/// into another `Yoke` containing a different type that may contain elements of the original yoked
/// value. See the [`Yoke::map_project()`] docs for more details.
///
-/// In general, `C` is a concrete type, but it is also possible for it to be a trait object;
-/// for more information, see [`IsCovariant`].
+/// In general, `C` is a concrete type, but it is also possible for it to be a trait object.
///
/// # Example
///
@@ -83,7 +81,10 @@ pub struct Yoke<Y: for<'a> Yokeable<'a>, C> {
cart: C,
}
-impl<Y: for<'a> Yokeable<'a>, C: StableDeref> Yoke<Y, C> {
+impl<Y: for<'a> Yokeable<'a>, C: StableDeref> Yoke<Y, C>
+where
+ <C as Deref>::Target: 'static,
+{
/// Construct a [`Yoke`] by yokeing an object to a cart in a closure.
///
/// See also [`Yoke::try_attach_to_cart()`] to return a `Result` from the closure.
@@ -116,7 +117,14 @@ impl<Y: for<'a> Yokeable<'a>, C: StableDeref> Yoke<Y, C> {
/// ```
pub fn attach_to_cart<F>(cart: C, f: F) -> Self
where
+ // safety note: This works by enforcing that the *only* place the return value of F
+ // can borrow from is the cart, since `F` must be valid for all lifetimes `'de`
+ //
+ // The <C as Deref>::Target: 'static on the impl is crucial for safety as well
+ //
+ // See safety docs at the bottom of this file for more information
F: for<'de> FnOnce(&'de <C as Deref>::Target) -> <Y as Yokeable<'de>>::Output,
+ <C as Deref>::Target: 'static,
{
let deserialized = f(cart.deref());
Self {
@@ -220,8 +228,10 @@ impl<Y: for<'a> Yokeable<'a>, C> Yoke<Y, C> {
/// ```
/// use yoke::Yoke;
///
- /// let local_data = "foo".to_string();
- /// let yoke = Yoke::<&'static str, Box<String>>::attach_to_zero_copy_cart(Box::new(local_data));
+ /// let local_data = "foo".to_owned();
+ /// let yoke = Yoke::<&'static str, Box<String>>::attach_to_zero_copy_cart(
+ /// Box::new(local_data),
+ /// );
/// assert_eq!(*yoke.get(), "foo");
///
/// // Get back the cart
@@ -235,9 +245,11 @@ impl<Y: for<'a> Yokeable<'a>, C> Yoke<Y, C> {
/// use std::borrow::Cow;
/// use yoke::Yoke;
///
- /// let local_data = "foo".to_string();
+ /// let local_data = "foo".to_owned();
/// let mut yoke =
- /// Yoke::<Cow<'static, str>, Box<String>>::attach_to_zero_copy_cart(Box::new(local_data));
+ /// Yoke::<Cow<'static, str>, Box<String>>::attach_to_zero_copy_cart(
+ /// Box::new(local_data),
+ /// );
/// assert_eq!(yoke.get(), "foo");
///
/// // Override data in the cart
@@ -265,6 +277,9 @@ impl<Y: for<'a> Yokeable<'a>, C> Yoke<Y, C> {
/// - `f()` must not panic
/// - References from the yokeable `Y` should still be valid for the lifetime of the
/// returned cart type `C`.
+ /// - Lifetimes inside C must not be lengthened, even if they are themselves contravariant.
+ /// I.e., if C contains an `fn(&'a u8)`, it cannot be replaced with `fn(&'static u8),
+ /// even though that is typically safe.
///
/// Typically, this means implementing `f` as something which _wraps_ the inner cart type `C`.
/// `Yoke` only really cares about destructors for its carts so it's fine to erase other
@@ -506,12 +521,9 @@ where
}
}
-// This is safe because Y is 'static and C has a covariant lifetime
-unsafe impl<'b, Y: for<'a> Yokeable<'a>, C: IsCovariant<'b>> IsCovariant<'b> for Yoke<Y, C> {}
-
#[test]
fn test_clone() {
- let local_data = "foo".to_string();
+ let local_data = "foo".to_owned();
let y1 = Yoke::<alloc::borrow::Cow<'static, str>, Rc<String>>::attach_to_zero_copy_cart(
Rc::new(local_data),
);
@@ -575,7 +587,9 @@ impl<Y: for<'a> Yokeable<'a>, C> Yoke<Y, C> {
/// string_2: &'a str,
/// }
///
- /// fn map_project_string_1(bar: Yoke<Bar<'static>, Rc<[u8]>>) -> Yoke<&'static str, Rc<[u8]>> {
+ /// fn map_project_string_1(
+ /// bar: Yoke<Bar<'static>, Rc<[u8]>>,
+ /// ) -> Yoke<&'static str, Rc<[u8]>> {
/// bar.map_project(|bar, _| bar.string_1)
/// }
///
@@ -651,7 +665,9 @@ impl<Y: for<'a> Yokeable<'a>, C> Yoke<Y, C> {
/// # use yoke::Yoke;
/// # use std::str::{self, Utf8Error};
/// #
- /// fn slice(y: Yoke<&'static [u8], Rc<[u8]>>) -> Result<Yoke<&'static str, Rc<[u8]>>, Utf8Error> {
+ /// fn slice(
+ /// y: Yoke<&'static [u8], Rc<[u8]>>,
+ /// ) -> Result<Yoke<&'static str, Rc<[u8]>>, Utf8Error> {
/// y.try_map_project(move |bytes, _| str::from_utf8(bytes))
/// }
/// ```
@@ -671,7 +687,9 @@ impl<Y: for<'a> Yokeable<'a>, C> Yoke<Y, C> {
/// string_2: &'a str,
/// }
///
- /// fn map_project_string_1(bar: Yoke<Bar<'static>, Rc<[u8]>>) -> Result<Yoke<&'static str, Rc<[u8]>>, Utf8Error> {
+ /// fn map_project_string_1(
+ /// bar: Yoke<Bar<'static>, Rc<[u8]>>,
+ /// ) -> Result<Yoke<&'static str, Rc<[u8]>>, Utf8Error> {
/// bar.try_map_project(|bar, _| str::from_utf8(bar.bytes_1))
/// }
///
@@ -861,17 +879,19 @@ impl<Y: for<'a> Yokeable<'a>, C: 'static + Sized> Yoke<Y, Rc<C>> {
/// let buffer1: Rc<String> = Rc::new(" foo bar baz ".into());
/// let buffer2: Box<String> = Box::new(" baz quux ".into());
///
- /// let yoke1 = Yoke::<&'static str, _>::attach_to_cart(buffer1, |rc| rc.trim());
+ /// let yoke1 =
+ /// Yoke::<&'static str, _>::attach_to_cart(buffer1, |rc| rc.trim());
/// let yoke2 = Yoke::<&'static str, _>::attach_to_cart(buffer2, |b| b.trim());
///
/// let erased1: Yoke<_, ErasedRcCart> = yoke1.erase_rc_cart();
/// // Wrap the Box in an Rc to make it compatible
- /// let erased2: Yoke<_, ErasedRcCart> = yoke2.wrap_cart_in_rc().erase_rc_cart();
+ /// let erased2: Yoke<_, ErasedRcCart> =
+ /// yoke2.wrap_cart_in_rc().erase_rc_cart();
///
/// // Now erased1 and erased2 have the same type!
/// ```
///
- /// Available with the `"alloc"` feature enabled.
+ /// Available with the `"alloc"` Cargo feature enabled.
pub fn erase_rc_cart(self) -> Yoke<Y, ErasedRcCart> {
unsafe {
// safe because the cart is preserved, just
@@ -905,17 +925,19 @@ impl<Y: for<'a> Yokeable<'a>, C: 'static + Sized + Send + Sync> Yoke<Y, Arc<C>>
/// let buffer1: Arc<String> = Arc::new(" foo bar baz ".into());
/// let buffer2: Box<String> = Box::new(" baz quux ".into());
///
- /// let yoke1 = Yoke::<&'static str, _>::attach_to_cart(buffer1, |arc| arc.trim());
+ /// let yoke1 =
+ /// Yoke::<&'static str, _>::attach_to_cart(buffer1, |arc| arc.trim());
/// let yoke2 = Yoke::<&'static str, _>::attach_to_cart(buffer2, |b| b.trim());
///
/// let erased1: Yoke<_, ErasedArcCart> = yoke1.erase_arc_cart();
/// // Wrap the Box in an Rc to make it compatible
- /// let erased2: Yoke<_, ErasedArcCart> = yoke2.wrap_cart_in_arc().erase_arc_cart();
+ /// let erased2: Yoke<_, ErasedArcCart> =
+ /// yoke2.wrap_cart_in_arc().erase_arc_cart();
///
/// // Now erased1 and erased2 have the same type!
/// ```
///
- /// Available with the `"alloc"` feature enabled.
+ /// Available with the `"alloc"` Cargo feature enabled.
pub fn erase_arc_cart(self) -> Yoke<Y, ErasedArcCart> {
unsafe {
// safe because the cart is preserved, just
@@ -949,17 +971,19 @@ impl<Y: for<'a> Yokeable<'a>, C: 'static + Sized> Yoke<Y, Box<C>> {
/// let buffer1: Rc<String> = Rc::new(" foo bar baz ".into());
/// let buffer2: Box<String> = Box::new(" baz quux ".into());
///
- /// let yoke1 = Yoke::<&'static str, _>::attach_to_cart(buffer1, |rc| rc.trim());
+ /// let yoke1 =
+ /// Yoke::<&'static str, _>::attach_to_cart(buffer1, |rc| rc.trim());
/// let yoke2 = Yoke::<&'static str, _>::attach_to_cart(buffer2, |b| b.trim());
///
/// // Wrap the Rc in an Box to make it compatible
- /// let erased1: Yoke<_, ErasedBoxCart> = yoke1.wrap_cart_in_box().erase_box_cart();
+ /// let erased1: Yoke<_, ErasedBoxCart> =
+ /// yoke1.wrap_cart_in_box().erase_box_cart();
/// let erased2: Yoke<_, ErasedBoxCart> = yoke2.erase_box_cart();
///
/// // Now erased1 and erased2 have the same type!
/// ```
///
- /// Available with the `"alloc"` feature enabled.
+ /// Available with the `"alloc"` Cargo feature enabled.
pub fn erase_box_cart(self) -> Yoke<Y, ErasedBoxCart> {
unsafe {
// safe because the cart is preserved, just
@@ -974,7 +998,7 @@ impl<Y: for<'a> Yokeable<'a>, C> Yoke<Y, C> {
/// Helper function allowing one to wrap the cart type `C` in a `Box<T>`.
/// Can be paired with [`Yoke::erase_box_cart()`]
///
- /// Available with the `"alloc"` feature enabled.
+ /// Available with the `"alloc"` Cargo feature enabled.
#[inline]
pub fn wrap_cart_in_box(self) -> Yoke<Y, Box<C>> {
unsafe {
@@ -986,7 +1010,7 @@ impl<Y: for<'a> Yokeable<'a>, C> Yoke<Y, C> {
/// Can be paired with [`Yoke::erase_rc_cart()`], or generally used
/// to make the [`Yoke`] cloneable.
///
- /// Available with the `"alloc"` feature enabled.
+ /// Available with the `"alloc"` Cargo feature enabled.
#[inline]
pub fn wrap_cart_in_rc(self) -> Yoke<Y, Rc<C>> {
unsafe {
@@ -998,7 +1022,7 @@ impl<Y: for<'a> Yokeable<'a>, C> Yoke<Y, C> {
/// Can be paired with [`Yoke::erase_arc_cart()`], or generally used
/// to make the [`Yoke`] cloneable.
///
- /// Available with the `"alloc"` feature enabled.
+ /// Available with the `"alloc"` Cargo feature enabled.
#[inline]
pub fn wrap_cart_in_arc(self) -> Yoke<Y, Arc<C>> {
unsafe {
@@ -1037,7 +1061,7 @@ impl<Y: for<'a> Yokeable<'a>, C> Yoke<Y, C> {
}
}
-/// Safety docs for project()
+/// # Safety docs for project()
///
/// (Docs are on a private const to allow the use of compile_fail doctests)
///
@@ -1140,3 +1164,151 @@ impl<Y: for<'a> Yokeable<'a>, C> Yoke<Y, C> {
/// the output yokeable can _only_ have borrowed data flow in to it from the input. All paths of unsoundness require the
/// unification of an existential and universal lifetime, which isn't possible.
const _: () = ();
+
+/// # Safety docs for attach_to_cart()'s signature
+///
+/// The `attach_to_cart()` family of methods get by by using the following bound:
+///
+/// ```rust,ignore
+/// F: for<'de> FnOnce(&'de <C as Deref>::Target) -> <Y as Yokeable<'de>>::Output,
+/// C::Target: 'static
+/// ```
+///
+/// to enforce that the yoking closure produces a yokeable that is *only* allowed to borrow from the cart.
+/// A way to be sure of this is as follows: imagine if `F` *did* borrow data of lifetime `'a` and stuff it in
+/// its output. Then that lifetime `'a` would have to live at least as long as `'de` *for all `'de`*.
+/// The only lifetime that satisfies that is `'static` (since at least one of the potential `'de`s is `'static`),
+/// and we're fine with that.
+///
+/// ## Implied bounds and variance
+///
+/// The `C::Target: 'static` bound is tricky, however. Let's imagine a situation where we *didn't* have that bound.
+///
+/// One thing to remember is that we are okay with the cart itself borrowing from places,
+/// e.g. `&[u8]` is a valid cart, as is `Box<&[u8]>`. `C` is not `'static`.
+///
+/// (I'm going to use `CT` in prose to refer to `C::Target` here, since almost everything here has to do
+/// with C::Target and not C itself.)
+///
+/// Unfortunately, there's a sneaky additional bound inside `F`. The signature of `F` is *actually*
+///
+/// ```rust,ignore
+/// F: for<'de> where<C::Target: 'de> FnOnce(&'de C::Target) -> <Y as Yokeable<'de>>::Output
+/// ```
+///
+/// using made-up "where clause inside HRTB" syntax to represent a type that can be represented inside the compiler
+/// and type system but not in Rust code. The `CT: 'de` bond comes from the `&'de C::Target`: any time you
+/// write `&'a T`, an implied bound of `T: 'a` materializes and is stored alongside it, since references cannot refer
+/// to data that itself refers to data of shorter lifetimes. If a reference is valid, its referent must be valid for
+/// the duration of the reference's lifetime, so every reference *inside* its referent must also be valid, giving us `T: 'a`.
+/// This kind of constraint is often called a "well formedness" constraint: `&'a T` is not "well formed" without that
+/// bound, and rustc is being helpful by giving it to us for free.
+///
+/// Unfortunately, this messes with our universal quantification. The `for<'de>` is no longer "For all lifetimes `'de`",
+/// it is "for all lifetimes `'de` *where `CT: 'de`*". And if `CT` borrows from somewhere (with lifetime `'ct`), then we get a
+/// `'ct: 'de` bound, and `'de` candidates that live longer than `'ct` won't actually be considered.
+/// The neat little logic at the beginning stops working.
+///
+/// `attach_to_cart()` will instead enforce that the produced yokeable *either* borrows from the cart (fine), or from
+/// data that has a lifetime that is at least `'ct`. Which means that `attach_to_cart()` will allow us to borrow locals
+/// provided they live at least as long as `'ct`.
+///
+/// Is this a problem?
+///
+/// This is totally fine if CT's lifetime is covariant: if C is something like `Box<&'ct [u8]>`, even if our
+/// yoked object borrows from locals outliving `'ct`, our Yoke can't outlive that
+/// lifetime `'ct` anyway (since it's a part of the cart type), so we're fine.
+///
+/// However it's completely broken for contravariant carts (e.g. `Box<fn(&'ct u8)>`). In that case
+/// we still get `'ct: 'de`, and we still end up being able to
+/// borrow from locals that outlive `'ct`. However, our Yoke _can_ outlive
+/// that lifetime, because Yoke shares its variance over `'ct`
+/// with the cart type, and the cart type is contravariant over `'ct`.
+/// So the Yoke can be upcast to having a longer lifetime than `'ct`, and *that* Yoke
+/// can outlive `'ct`.
+///
+/// We fix this by forcing `C::Target: 'static` in `attach_to_cart()`, which would make it work
+/// for fewer types, but would also allow Yoke to continue to be covariant over cart lifetimes if necessary.
+///
+/// An alternate fix would be to not allowing yoke to ever be upcast over lifetimes contained in the cart
+/// by forcing them to be invariant. This is a bit more restrictive and affects *all* `Yoke` users, not just
+/// those using `attach_to_cart()`.
+///
+/// See https://github.com/unicode-org/icu4x/issues/2926
+/// See also https://github.com/rust-lang/rust/issues/106431 for potentially fixing this upstream by
+/// changing how the bound works.
+///
+/// # Tests
+///
+/// Here's a broken `attach_to_cart()` that attempts to borrow from a local:
+///
+/// ```rust,compile_fail
+/// use yoke::{Yoke, Yokeable};
+///
+/// let cart = vec![1, 2, 3, 4].into_boxed_slice();
+/// let local = vec![4, 5, 6, 7];
+/// let yoke: Yoke<&[u8], Box<[u8]>> = Yoke::attach_to_cart(cart, |_| &*local);
+/// ```
+///
+/// Fails as expected.
+///
+/// And here's a working one with a local borrowed cart that does not do any sneaky borrows whilst attaching.
+///
+/// ```rust
+/// use yoke::{Yoke, Yokeable};
+///
+/// let cart = vec![1, 2, 3, 4].into_boxed_slice();
+/// let local = vec![4, 5, 6, 7];
+/// let yoke: Yoke<&[u8], &[u8]> = Yoke::attach_to_cart(&cart, |c| &*c);
+/// ```
+///
+/// Here's an `attach_to_cart()` that attempts to borrow from a longer-lived local due to
+/// the cart being covariant. It fails, but would not if the alternate fix of forcing Yoke to be invariant
+/// were implemented. It is technically a safe operation:
+///
+/// ```rust,compile_fail
+/// use yoke::{Yoke, Yokeable};
+/// // longer lived
+/// let local = vec![4, 5, 6, 7];
+///
+/// let backing = vec![1, 2, 3, 4];
+/// let cart = Box::new(&*backing);
+///
+/// let yoke: Yoke<&[u8], Box<&[u8]>> = Yoke::attach_to_cart(cart, |_| &*local);
+/// println!("{:?}", yoke.get());
+/// ```
+///
+/// Finally, here's an `attach_to_cart()` that attempts to borrow from a longer lived local
+/// in the case of a contravariant lifetime. It does not compile, but in and of itself is not dangerous:
+///
+/// ```rust,compile_fail
+/// use yoke::Yoke;
+///
+/// type Contra<'a> = fn(&'a ());
+///
+/// let local = String::from("Hello World!");
+/// let yoke: Yoke<&'static str, Box<Contra<'_>>> = Yoke::attach_to_cart(Box::new((|_| {}) as _), |_| &local[..]);
+/// println!("{:?}", yoke.get());
+/// ```
+///
+/// It is dangerous if allowed to transform (testcase from #2926)
+///
+/// ```rust,compile_fail
+/// use yoke::Yoke;
+///
+/// type Contra<'a> = fn(&'a ());
+///
+///
+/// let local = String::from("Hello World!");
+/// let yoke: Yoke<&'static str, Box<Contra<'_>>> = Yoke::attach_to_cart(Box::new((|_| {}) as _), |_| &local[..]);
+/// println!("{:?}", yoke.get());
+/// let yoke_longer: Yoke<&'static str, Box<Contra<'static>>> = yoke;
+/// let leaked: &'static Yoke<&'static str, Box<Contra<'static>>> = Box::leak(Box::new(yoke_longer));
+/// let reference: &'static str = leaked.get();
+///
+/// println!("pre-drop: {reference}");
+/// drop(local);
+/// println!("post-drop: {reference}");
+///
+/// ```
+const _: () = ();