turbo_tasks/vc/
operation.rs

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