Skip to main content

turbo_tasks/vc/
operation.rs

1use std::{fmt::Debug, hash::Hash, marker::PhantomData};
2
3use anyhow::Result;
4use auto_hash_map::AutoSet;
5use bincode::{Decode, Encode};
6use serde::{Deserialize, Serialize};
7pub use turbo_tasks_macros::OperationValue;
8
9use crate::{
10    CollectiblesSource, IntoTraitRef, RawVc, ReadVcFuture, ResolvedVc, TaskInput, TraitRef,
11    UpcastStrict, Vc, VcValueTrait, VcValueType, marker_trait::impl_auto_marker_trait,
12    trace::TraceRawVcs,
13};
14
15/// A "subtype" (can be converted via [`.connect()`]) of [`Vc`] that
16/// represents a specific call (with arguments) to [a task][macro@crate::function].
17///
18/// Unlike [`Vc`], `OperationVc`:
19///
20/// - Does not potentially refer to task-local information, meaning that it implements
21///   [`NonLocalValue`], and can be used in any [`#[turbo_tasks::value]`][macro@crate::value].
22///
23/// - Has only one potential internal representation, meaning that it has a saner equality
24///   definition.
25///
26/// - Can be [reconnected][OperationVc::connect] to the strongly-consistent compilation graph after
27///   being placed inside of a [`State`].
28///
29/// - Makes sense with [collectibles][`CollectiblesSource`], as it represents a function call, and
30///   only function calls can have issues or side-effects.
31///
32///
33/// ## Equality & Hashing
34///
35/// Equality between two `OperationVc`s means that both have an identical in-memory representation
36/// and point to the same task function call. The implementation of [`Hash`] has similar behavior.
37///
38/// If [connected] and then `.await`ed at the same time, both would likely resolve to the same
39/// [`ReadRef`], though it is possible that they may not if the task or cell is invalidated between
40/// `.await`s.
41///
42/// Because equality is a synchronous operation that cannot read the cell contents, even if the
43/// `OperationVc`s are not equal, it is possible that if `.await`ed, both `OperationVc`s could point
44/// to the same or equal values.
45///
46/// [`.connect()`]: OperationVc::connect
47/// [reconnected]: OperationVc::connect
48/// [connected]: OperationVc::connect
49/// [`NonLocalValue`]: crate::NonLocalValue
50/// [`State`]: crate::State
51/// [`ReadRef`]: crate::ReadRef
52#[must_use]
53#[derive(Serialize, Deserialize, Encode, Decode)]
54#[serde(transparent, bound = "")]
55#[bincode(bounds = "T: ?Sized")]
56#[repr(transparent)]
57pub struct OperationVc<T>
58where
59    T: ?Sized,
60{
61    pub(crate) node: Vc<T>,
62}
63
64impl<T: ?Sized> OperationVc<T> {
65    /// Called by the `#[turbo_tasks::function]` macro.
66    ///
67    /// The macro ensures that the `Vc` is not a local task and it points to a single operation.
68    #[doc(hidden)]
69    #[deprecated = "This is an internal function. Use #[turbo_tasks::function(operation)] instead."]
70    pub fn cell_private(node: Vc<T>) -> Self {
71        debug_assert!(
72            matches!(node.node, RawVc::TaskOutput(..)),
73            "OperationVc::cell_private must be called on the immediate return value of a task \
74             function"
75        );
76        Self { node }
77    }
78
79    /// Marks this operation's underlying function call as a child of the current task, and returns
80    /// a [`Vc`] that can be [resolved][Vc::to_resolved] or read with `.await?`.
81    ///
82    /// By marking this function call as a child of the current task, turbo-tasks will re-run tasks
83    /// as-needed to achieve strong consistency at the root of the function call tree. This explicit
84    /// operation is needed as `OperationVc` types can be stored outside of the call graph as part
85    /// of [`State`][crate::State]s.
86    pub fn connect(self) -> Vc<T> {
87        self.node.node.connect();
88        self.node
89    }
90
91    /// Returns the `RawVc` corresponding to this `Vc`.
92    pub fn into_raw(vc: Self) -> RawVc {
93        vc.node.node
94    }
95
96    /// Upcasts the given `OperationVc<T>` to a `OperationVc<Box<dyn K>>`.
97    ///
98    /// This is also available as an `Into`/`From` conversion.
99    #[inline(always)]
100    pub fn upcast<K>(vc: Self) -> OperationVc<K>
101    where
102        T: UpcastStrict<K>,
103        K: VcValueTrait + ?Sized,
104    {
105        OperationVc {
106            node: Vc::upcast(vc.node),
107        }
108    }
109
110    /// [Connects the `OperationVc`][Self::connect] and [resolves][Vc::to_resolved] the reference
111    /// until it points to a cell directly in a [strongly
112    /// consistent][crate::ReadConsistency::Strong] way.
113    ///
114    /// Resolving will wait for task execution to be finished, so that the returned [`ResolvedVc`]
115    /// points to a cell that stores a value.
116    ///
117    /// Resolving is necessary to compare identities of [`Vc`]s.
118    ///
119    /// This is async and will rethrow any fatal error that happened during task execution.
120    pub async fn resolve_strongly_consistent(self) -> Result<ResolvedVc<T>> {
121        Ok(ResolvedVc {
122            node: Vc {
123                node: self.connect().node.resolve_strongly_consistent().await?,
124                _t: PhantomData,
125            },
126        })
127    }
128
129    /// [Connects the `OperationVc`][Self::connect] and returns a [strongly
130    /// consistent][crate::ReadConsistency::Strong] read of the value.
131    ///
132    /// This ensures that all internal tasks are finished before the read is returned.
133    pub fn read_strongly_consistent(self) -> ReadVcFuture<T>
134    where
135        T: VcValueType,
136    {
137        self.connect()
138            .node
139            .into_read(T::has_serialization())
140            .strongly_consistent()
141            .into()
142    }
143
144    /// [Connects the `OperationVc`][Self::connect] and returns a [strongly
145    /// consistent][crate::ReadConsistency::Strong] read of the value.
146    ///
147    /// This ensures that all internal tasks are finished before the read is returned.
148    pub async fn read_trait_strongly_consistent(self) -> Result<TraitRef<T>>
149    where
150        T: VcValueTrait,
151    {
152        self.connect().into_trait_ref().strongly_consistent().await
153    }
154}
155
156impl<T> Copy for OperationVc<T> where T: ?Sized {}
157
158impl<T> Clone for OperationVc<T>
159where
160    T: ?Sized,
161{
162    fn clone(&self) -> Self {
163        *self
164    }
165}
166
167impl<T> Hash for OperationVc<T>
168where
169    T: ?Sized,
170{
171    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
172        self.node.hash(state);
173    }
174}
175
176impl<T> PartialEq<OperationVc<T>> for OperationVc<T>
177where
178    T: ?Sized,
179{
180    fn eq(&self, other: &Self) -> bool {
181        self.node == other.node
182    }
183}
184
185impl<T> Eq for OperationVc<T> where T: ?Sized {}
186
187impl<T> Debug for OperationVc<T>
188where
189    T: ?Sized,
190{
191    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192        f.debug_struct("OperationVc")
193            .field("node", &self.node.node)
194            .finish()
195    }
196}
197
198// NOTE: This uses the default implementation of `is_resolved` which returns `true` because we don't
199// want `OperationVc` arguments to get resolved when passed to a `#[turbo_tasks::function]`.
200impl<T> TaskInput for OperationVc<T>
201where
202    T: ?Sized + Send + Sync,
203{
204    fn is_transient(&self) -> bool {
205        self.node.is_transient()
206    }
207}
208
209impl<T> TryFrom<RawVc> for OperationVc<T>
210where
211    T: ?Sized,
212{
213    type Error = anyhow::Error;
214
215    fn try_from(raw: RawVc) -> Result<Self> {
216        if !matches!(raw, RawVc::TaskOutput(..)) {
217            anyhow::bail!("Given RawVc {raw:?} is not a TaskOutput");
218        }
219        Ok(Self {
220            node: Vc::from(raw),
221        })
222    }
223}
224
225impl<T> TraceRawVcs for OperationVc<T>
226where
227    T: ?Sized,
228{
229    fn trace_raw_vcs(&self, trace_context: &mut crate::trace::TraceRawVcsContext) {
230        self.node.trace_raw_vcs(trace_context);
231    }
232}
233
234impl<T> CollectiblesSource for OperationVc<T>
235where
236    T: ?Sized,
237{
238    fn drop_collectibles<Vt: VcValueTrait>(self) {
239        self.node.node.drop_collectibles::<Vt>();
240    }
241
242    fn take_collectibles<Vt: VcValueTrait>(self) -> AutoSet<ResolvedVc<Vt>> {
243        self.node.node.take_collectibles()
244    }
245
246    fn peek_collectibles<Vt: VcValueTrait>(self) -> AutoSet<ResolvedVc<Vt>> {
247        self.node.node.peek_collectibles()
248    }
249}
250
251/// Indicates that a type does not contain any instances of [`Vc`] or [`ResolvedVc`]. It may contain
252/// [`OperationVc`].
253///
254/// # Safety
255///
256/// This trait is marked as unsafe. You should not derive it yourself, but instead you should rely
257/// on [`#[derive(OperationValue)]`][macro@OperationValue] to do it for you.
258pub unsafe trait OperationValue {}
259
260unsafe impl<T: ?Sized + Send> OperationValue for OperationVc<T> {}
261
262impl_auto_marker_trait!(OperationValue);