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);