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}