starlark/values/
unpack.rs

1/*
2 * Copyright 2018 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
18//! Parameter conversion utilities for `starlark_module` macros.
19
20use std::convert::Infallible;
21use std::fmt::Debug;
22
23use anyhow::Context;
24use either::Either;
25use starlark_syntax::StarlarkResultExt;
26
27use crate::typing::Ty;
28use crate::values::type_repr::StarlarkTypeRepr;
29use crate::values::Value;
30
31/// Error that can be returned by [`UnpackValue`].
32pub trait UnpackValueError: Debug + Send + Sync + 'static {
33    /// Convert into a crate error.
34    fn into_error(this: Self) -> crate::Error;
35}
36
37impl UnpackValueError for crate::Error {
38    #[cold]
39    fn into_error(this: Self) -> crate::Error {
40        this
41    }
42}
43
44impl UnpackValueError for anyhow::Error {
45    #[cold]
46    fn into_error(this: Self) -> crate::Error {
47        crate::Error::new_value(this)
48    }
49}
50
51impl UnpackValueError for Infallible {
52    #[cold]
53    fn into_error(this: Self) -> crate::Error {
54        match this {}
55    }
56}
57
58impl<A: UnpackValueError, B: UnpackValueError> UnpackValueError for Either<A, B> {
59    #[cold]
60    fn into_error(this: Self) -> crate::Error {
61        match this {
62            Either::Left(a) => UnpackValueError::into_error(a),
63            Either::Right(b) => UnpackValueError::into_error(b),
64        }
65    }
66}
67
68/// Never error.
69pub trait UnpackValueErrorInfallible: UnpackValueError {
70    /// Convert into a never type.
71    fn into_infallible(this: Self) -> !;
72}
73
74impl UnpackValueErrorInfallible for Infallible {
75    fn into_infallible(this: Self) -> ! {
76        match this {}
77    }
78}
79
80impl<A: UnpackValueErrorInfallible, B: UnpackValueErrorInfallible> UnpackValueErrorInfallible
81    for Either<A, B>
82{
83    fn into_infallible(this: Self) -> ! {
84        match this {
85            Either::Left(a) => UnpackValueErrorInfallible::into_infallible(a),
86            Either::Right(b) => UnpackValueErrorInfallible::into_infallible(b),
87        }
88    }
89}
90
91/// How to convert a [`Value`] to a Rust type. Required for all arguments in
92/// a [`#[starlark_module]`](macro@crate::starlark_module) definition.
93///
94/// Note for simple references it often can be implemented with `#[starlark_value(UnpackValue)]`,
95/// for example:
96///
97/// ```
98/// # use allocative::Allocative;
99/// # use starlark::any::ProvidesStaticType;
100/// # use starlark::values::{NoSerialize, StarlarkValue, starlark_value};
101///
102/// #[derive(
103///     Debug,
104///     derive_more::Display,
105///     Allocative,
106///     NoSerialize,
107///     ProvidesStaticType
108/// )]
109/// struct MySimpleValue;
110///
111/// #[starlark_value(type = "MySimpleValue", UnpackValue, StarlarkTypeRepr)]
112/// impl<'v> StarlarkValue<'v> for MySimpleValue {}
113/// ```
114///
115/// Whereas for types that aren't also [`StarlarkValue`](crate::values::StarlarkValue) you can define:
116///
117/// ```
118/// # use either::Either;
119/// # use starlark::typing::Ty;
120/// # use starlark::values::{UnpackValue, Value};
121/// # use starlark::values::type_repr::StarlarkTypeRepr;
122///
123/// struct BoolOrInt(i32);
124///
125/// impl StarlarkTypeRepr for BoolOrInt {
126///     type Canonical = <Either<bool, i32> as StarlarkTypeRepr>::Canonical;
127///
128///     fn starlark_type_repr() -> Ty {
129///         Either::<bool, i32>::starlark_type_repr()
130///     }
131/// }
132///
133/// impl<'v> UnpackValue<'v> for BoolOrInt {
134///     type Error = starlark::Error;
135///
136///     fn unpack_value_impl(value: Value<'v>) -> starlark::Result<Option<Self>> {
137///         if let Some(x) = value.unpack_bool() {
138///             Ok(Some(BoolOrInt(x as i32)))
139///         } else {
140///             let Some(x) = i32::unpack_value(value)? else {
141///                 return Ok(None);
142///             };
143///             Ok(Some(BoolOrInt(x)))
144///         }
145///     }
146/// }
147/// ```
148pub trait UnpackValue<'v>: Sized + StarlarkTypeRepr {
149    /// Error returned when type matches, but conversion fails.
150    ///
151    /// Typically [`starlark::Error`](crate::Error), [`anyhow::Error`], or [`Infallible`].
152    type Error: UnpackValueError;
153
154    /// Given a [`Value`], try and unpack it into the given type,
155    /// which may involve some element of conversion.
156    ///
157    /// Return `None` if the value is not of expected type (as described by [`StarlarkTypeRepr`],
158    /// and return `Err` if the value is of expected type, but conversion cannot be performed.
159    /// For example, when unpacking an integer to `String`, return `None`,
160    /// and when unpacking a large integer to `i32`, return `Err`.
161    ///
162    /// This function is needs to be implemented, but usually not meant to be called directly.
163    /// Consider using [`unpack_value`](UnpackValue::unpack_value),
164    /// [`unpack_value_err`](UnpackValue::unpack_value_err),
165    /// [`unpack_value_opt`](UnpackValue::unpack_value_opt) instead.
166    fn unpack_value_impl(value: Value<'v>) -> Result<Option<Self>, Self::Error>;
167
168    /// Given a [`Value`], try and unpack it into the given type,
169    /// which may involve some element of conversion.
170    ///
171    /// Return `None` if the value is not of expected type (as described by [`StarlarkTypeRepr`],
172    /// and return `Err` if the value is of expected type, but conversion cannot be performed.
173    /// For example, when unpacking an integer to `String`, return `None`,
174    /// and when unpacking a large integer to `i32`, return `Err`.
175    fn unpack_value(value: Value<'v>) -> Result<Option<Self>, crate::Error> {
176        Self::unpack_value_impl(value).map_err(Self::Error::into_error)
177    }
178
179    /// Unpack a value if unpacking is infallible.
180    fn unpack_value_opt(value: Value<'v>) -> Option<Self>
181    where
182        Self::Error: UnpackValueErrorInfallible,
183    {
184        match Self::unpack_value_impl(value) {
185            Ok(x) => x,
186            Err(e) => Self::Error::into_infallible(e),
187        }
188    }
189
190    /// Unpack a value, but return error instead of `None` if unpacking fails.
191    #[inline]
192    fn unpack_value_err(value: Value<'v>) -> anyhow::Result<Self> {
193        #[cold]
194        fn error<'v>(value: Value<'v>, ty: fn() -> Ty) -> anyhow::Error {
195            #[derive(thiserror::Error, Debug)]
196            #[error("Expected `{0}`, but got `{1}`")]
197            struct IncorrectType(Ty, String);
198
199            crate::Error::new_value(IncorrectType(ty(), value.to_string_for_type_error()))
200                .into_anyhow()
201        }
202
203        Self::unpack_value(value)
204            .into_anyhow_result()?
205            .ok_or_else(|| error(value, Self::starlark_type_repr))
206    }
207
208    /// Unpack value, but instead of `None` return error about incorrect argument type.
209    #[inline]
210    fn unpack_param(value: Value<'v>) -> anyhow::Result<Self> {
211        #[cold]
212        fn error<'v>(value: Value<'v>, ty: fn() -> Ty) -> anyhow::Error {
213            #[derive(thiserror::Error, Debug)]
214            #[error("Type of parameters mismatch, expected `{0}`, actual `{1}`")]
215            struct IncorrectParameterTypeWithExpected(Ty, String);
216
217            crate::Error::new_value(IncorrectParameterTypeWithExpected(
218                ty(),
219                value.to_string_for_type_error(),
220            ))
221            .into_anyhow()
222        }
223
224        Self::unpack_value(value)
225            .into_anyhow_result()?
226            .ok_or_else(|| error(value, Self::starlark_type_repr))
227    }
228
229    /// Unpack value, but instead of `None` return error about incorrect named argument type.
230    #[inline]
231    fn unpack_named_param(value: Value<'v>, param_name: &str) -> anyhow::Result<Self> {
232        #[cold]
233        fn error<'v>(value: Value<'v>, param_name: &str, ty: fn() -> Ty) -> anyhow::Error {
234            #[derive(thiserror::Error, Debug)]
235            #[error("Type of parameter `{0}` doesn't match, expected `{1}`, actual `{2}`")]
236            struct IncorrectParameterTypeNamedWithExpected(String, Ty, String);
237
238            crate::Error::new_value(IncorrectParameterTypeNamedWithExpected(
239                param_name.to_owned(),
240                ty(),
241                value.to_string_for_type_error(),
242            ))
243            .into_anyhow()
244        }
245
246        Self::unpack_value(value)
247            .into_anyhow_result()
248            .with_context(|| {
249                format!(
250                    "Error unpacking value for parameter `{}` of type `{}",
251                    param_name,
252                    Self::starlark_type_repr()
253                )
254            })?
255            .ok_or_else(|| error(value, param_name, Self::starlark_type_repr))
256    }
257}
258
259impl<'v> UnpackValue<'v> for Value<'v> {
260    type Error = Infallible;
261
262    fn unpack_value_impl(value: Value<'v>) -> Result<Option<Self>, Self::Error> {
263        Ok(Some(value))
264    }
265}
266
267impl<'v, TLeft: UnpackValue<'v>, TRight: UnpackValue<'v>> UnpackValue<'v>
268    for Either<TLeft, TRight>
269{
270    type Error = Either<TLeft::Error, TRight::Error>;
271
272    // Only implemented for types that implement [`UnpackValue`]. Nonsensical for other types.
273    fn unpack_value_impl(value: Value<'v>) -> Result<Option<Self>, Self::Error> {
274        if let Some(left) = TLeft::unpack_value_impl(value).map_err(Either::Left)? {
275            Ok(Some(Self::Left(left)))
276        } else {
277            Ok(TRight::unpack_value_impl(value)
278                .map_err(Either::Right)?
279                .map(Self::Right))
280        }
281    }
282}