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