starlark/values/layout/
complex.rs

1/*
2 * Copyright 2019 The Starlark in Rust Authors.
3 * Copyright (c) Facebook, Inc. and its affiliates.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18use std::convert::Infallible;
19use std::fmt;
20use std::marker::PhantomData;
21
22use allocative::Allocative;
23use dupe::Clone_;
24use dupe::Copy_;
25use dupe::Dupe_;
26use either::Either;
27use starlark_syntax::value_error;
28
29use crate::typing::Ty;
30use crate::values::type_repr::StarlarkTypeRepr;
31use crate::values::AllocValue;
32use crate::values::ComplexValue;
33use crate::values::Freeze;
34use crate::values::FreezeError;
35use crate::values::FreezeResult;
36use crate::values::Freezer;
37use crate::values::FrozenValueTyped;
38use crate::values::StarlarkValue;
39use crate::values::Trace;
40use crate::values::Tracer;
41use crate::values::UnpackValue;
42use crate::values::Value;
43use crate::values::ValueLike;
44use crate::values::ValueTyped;
45
46/// Value which is either a complex mutable value or a frozen value.
47#[derive(Copy_, Clone_, Dupe_, Allocative)]
48#[allocative(skip)] // Heap owns the value.
49pub struct ValueTypedComplex<'v, T>(Value<'v>, PhantomData<T>)
50where
51    T: ComplexValue<'v>,
52    T::Frozen: StarlarkValue<'static>;
53
54impl<'v, T> ValueTypedComplex<'v, T>
55where
56    T: ComplexValue<'v>,
57    T::Frozen: StarlarkValue<'static>,
58{
59    /// Downcast
60    pub fn new(value: Value<'v>) -> Option<Self> {
61        if value.downcast_ref::<T>().is_some()
62            || unsafe { value.cast_lifetime() }
63                .downcast_ref::<T::Frozen>()
64                .is_some()
65        {
66            Some(ValueTypedComplex(value, PhantomData))
67        } else {
68            None
69        }
70    }
71
72    /// Downcast.
73    pub fn new_err(value: Value<'v>) -> anyhow::Result<Self> {
74        match Self::new(value) {
75            Some(v) => Ok(v),
76            None => Err(value_error!(
77                "Expected value of type `{}`, got: `{}`",
78                T::TYPE,
79                value.to_string_for_type_error()
80            )
81            .into_anyhow()),
82        }
83    }
84
85    /// Get the value back.
86    #[inline]
87    pub fn to_value(self) -> Value<'v> {
88        self.0
89    }
90
91    /// Unpack the mutable or frozen value.
92    #[inline]
93    pub fn unpack(self) -> Either<&'v T, &'v T::Frozen> {
94        if let Some(v) = self.0.downcast_ref::<T>() {
95            Either::Left(v)
96        } else if let Some(v) =
97            unsafe { self.0.to_value().cast_lifetime() }.downcast_ref::<T::Frozen>()
98        {
99            Either::Right(v)
100        } else {
101            unreachable!("validated at construction")
102        }
103    }
104}
105
106impl<'v, T> StarlarkTypeRepr for ValueTypedComplex<'v, T>
107where
108    T: ComplexValue<'v>,
109    T::Frozen: StarlarkValue<'static>,
110{
111    type Canonical = <T as StarlarkTypeRepr>::Canonical;
112
113    fn starlark_type_repr() -> Ty {
114        T::starlark_type_repr()
115    }
116}
117
118impl<'v, T> AllocValue<'v> for ValueTypedComplex<'v, T>
119where
120    T: ComplexValue<'v>,
121    T::Frozen: StarlarkValue<'static>,
122{
123    #[inline]
124    fn alloc_value(self, _heap: &'v crate::values::Heap) -> Value<'v> {
125        self.0
126    }
127}
128
129impl<'v, T> UnpackValue<'v> for ValueTypedComplex<'v, T>
130where
131    T: ComplexValue<'v>,
132    T::Frozen: StarlarkValue<'static>,
133{
134    type Error = Infallible;
135
136    fn unpack_value_impl(value: Value<'v>) -> Result<Option<Self>, Self::Error> {
137        Ok(Self::new(value))
138    }
139}
140
141impl<'v, T> From<ValueTyped<'v, T>> for ValueTypedComplex<'v, T>
142where
143    T: ComplexValue<'v>,
144    T::Frozen: StarlarkValue<'static>,
145{
146    fn from(t: ValueTyped<'v, T>) -> Self {
147        Self(t.to_value(), PhantomData)
148    }
149}
150
151impl<'v, T> fmt::Debug for ValueTypedComplex<'v, T>
152where
153    T: ComplexValue<'v>,
154    T::Frozen: StarlarkValue<'static>,
155{
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        f.debug_tuple("ValueTypedComplex").field(&self.0).finish()
158    }
159}
160
161unsafe impl<'v, T> Trace<'v> for ValueTypedComplex<'v, T>
162where
163    T: ComplexValue<'v>,
164    T::Frozen: StarlarkValue<'static>,
165{
166    fn trace(&mut self, tracer: &Tracer<'v>) {
167        tracer.trace(&mut self.0);
168        // If type of value changed, dereference will produce the wrong object type.
169        debug_assert!(Self::new(self.0).is_some());
170    }
171}
172
173impl<'v, T> Freeze for ValueTypedComplex<'v, T>
174where
175    T: ComplexValue<'v>,
176    T::Frozen: StarlarkValue<'static>,
177{
178    type Frozen = FrozenValueTyped<'static, T::Frozen>;
179
180    fn freeze(self, freezer: &Freezer) -> FreezeResult<Self::Frozen> {
181        FrozenValueTyped::new_err(self.0.freeze(freezer)?)
182            .map_err(|e| FreezeError::new(format!("{e}")))
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use anyhow::Context;
189    use either::Either;
190    use starlark_derive::starlark_module;
191
192    use crate as starlark;
193    use crate::assert::Assert;
194    use crate::const_frozen_string;
195    use crate::environment::GlobalsBuilder;
196    use crate::tests::util::TestComplexValue;
197    use crate::values::layout::complex::ValueTypedComplex;
198    use crate::values::Value;
199
200    #[starlark_module]
201    fn test_module(globals: &mut GlobalsBuilder) {
202        fn test_unpack<'v>(
203            v: ValueTypedComplex<'v, TestComplexValue<Value<'v>>>,
204        ) -> anyhow::Result<&'v str> {
205            Ok(match v.unpack() {
206                Either::Left(v) => v.0.unpack_str().context("not a string")?,
207                Either::Right(v) => v.0.to_value().unpack_str().context("not a string")?,
208            })
209        }
210    }
211
212    #[test]
213    fn test_unpack() {
214        let mut a = Assert::new();
215        a.globals_add(test_module);
216        a.setup_eval(|eval| {
217            let s = eval.heap().alloc("test1");
218            let x = eval.heap().alloc(TestComplexValue(s));
219            let y = eval.frozen_heap().alloc(TestComplexValue(
220                const_frozen_string!("test2").to_frozen_value(),
221            ));
222            eval.module().set("x", x);
223            eval.module().set("y", y.to_value());
224        });
225        a.eq("'test1'", "test_unpack(x)");
226        a.eq("'test2'", "test_unpack(y)");
227    }
228}