starlark/values/typing/type_compiled/
compiled.rs1use std::fmt;
19use std::fmt::Debug;
20use std::fmt::Display;
21use std::fmt::Formatter;
22use std::hash::Hash;
23use std::hash::Hasher;
24
25use allocative::Allocative;
26use dupe::Dupe;
27use starlark_derive::starlark_module;
28use starlark_derive::starlark_value;
29use starlark_map::StarlarkHasher;
30use starlark_syntax::slice_vec_ext::SliceExt;
31use starlark_syntax::slice_vec_ext::VecExt;
32use thiserror::Error;
33
34use crate as starlark;
35use crate::any::ProvidesStaticType;
36use crate::coerce::Coerce;
37use crate::environment::Methods;
38use crate::environment::MethodsBuilder;
39use crate::environment::MethodsStatic;
40use crate::private::Private;
41use crate::typing::Ty;
42use crate::values::dict::DictRef;
43use crate::values::layout::avalue::alloc_static;
44use crate::values::layout::avalue::AValueBasic;
45use crate::values::layout::avalue::AValueImpl;
46use crate::values::layout::heap::repr::AValueRepr;
47use crate::values::list::ListRef;
48use crate::values::none::NoneType;
49use crate::values::type_repr::StarlarkTypeRepr;
50use crate::values::types::tuple::value::Tuple;
51use crate::values::typing::type_compiled::factory::TypeCompiledFactory;
52use crate::values::typing::type_compiled::matcher::TypeMatcher;
53use crate::values::typing::type_compiled::matchers::IsAny;
54use crate::values::AllocValue;
55use crate::values::Demand;
56use crate::values::Freeze;
57use crate::values::FreezeResult;
58use crate::values::FrozenHeap;
59use crate::values::FrozenValue;
60use crate::values::Heap;
61use crate::values::NoSerialize;
62use crate::values::StarlarkValue;
63use crate::values::StringValue;
64use crate::values::Trace;
65use crate::values::Value;
66use crate::values::ValueLifetimeless;
67use crate::values::ValueLike;
68
69#[derive(Debug, Error)]
70enum TypingError {
71 #[error("Value `{0}` of type `{1}` does not match the type annotation `{2}` for {3}")]
73 TypeAnnotationMismatch(String, String, String, String),
74 #[error("Type `{0}` is not a valid type annotation")]
76 InvalidTypeAnnotation(String),
77 #[error("`{{A: B}}` cannot be used as type, perhaps you meant `dict[A, B]`")]
78 Dict,
79 #[error("`[X]` cannot be used as type, perhaps you meant `list[X]`")]
80 List,
81 #[error(r#"Found `{0}` instead of a valid type annotation. Perhaps you meant `"{1}"`?"#)]
84 PerhapsYouMeant(String, String),
85 #[error("Value of type `{1}` does not match type `{2}`: {0}")]
86 ValueDoesNotMatchType(String, &'static str, String),
87 #[error("String literals are not allowed in type expressions: `{0}`")]
88 StringLiteralNotAllowed(String),
89}
90
91pub(crate) trait TypeCompiledDyn: Debug + Allocative + Send + Sync + 'static {
92 fn as_ty_dyn(&self) -> &Ty;
93 fn is_runtime_wildcard_dyn(&self) -> bool;
94 fn to_frozen_dyn(&self, heap: &FrozenHeap) -> TypeCompiled<FrozenValue>;
95}
96
97unsafe impl<'v> ProvidesStaticType<'v> for &'v dyn TypeCompiledDyn {
99 type StaticType = &'static dyn TypeCompiledDyn;
100}
101
102impl<T> TypeCompiledDyn for TypeCompiledImplAsStarlarkValue<T>
103where
104 T: TypeMatcher,
105{
106 fn as_ty_dyn(&self) -> &Ty {
107 &self.ty
108 }
109 fn is_runtime_wildcard_dyn(&self) -> bool {
110 self.type_compiled_impl.is_wildcard()
111 }
112 fn to_frozen_dyn(&self, heap: &FrozenHeap) -> TypeCompiled<FrozenValue> {
113 TypeCompiled(heap.alloc_simple::<TypeCompiledImplAsStarlarkValue<T>>(Self::clone(self)))
114 }
115}
116
117#[derive(
118 Clone,
119 Eq,
120 PartialEq,
121 Debug,
122 Allocative,
123 ProvidesStaticType,
124 NoSerialize
125)]
126pub struct TypeCompiledImplAsStarlarkValue<T: 'static> {
127 type_compiled_impl: T,
128 ty: Ty,
129}
130
131impl<T> TypeCompiledImplAsStarlarkValue<T>
132where
133 TypeCompiledImplAsStarlarkValue<T>: StarlarkValue<'static>,
134{
135 pub(crate) const fn alloc_static(
136 imp: T,
137 ty: Ty,
138 ) -> AValueRepr<AValueImpl<'static, AValueBasic<TypeCompiledImplAsStarlarkValue<T>>>> {
139 alloc_static(TypeCompiledImplAsStarlarkValue {
140 type_compiled_impl: imp,
141 ty,
142 })
143 }
144}
145
146#[doc(hidden)]
147#[derive(Hash, Eq, PartialEq, Debug, Clone, Allocative)]
148pub struct DummyTypeMatcher;
149
150impl TypeMatcher for DummyTypeMatcher {
151 fn matches(&self, _value: Value) -> bool {
152 unreachable!()
153 }
154}
155
156#[starlark_value(type = "type")]
157impl<'v, T: 'static> StarlarkValue<'v> for TypeCompiledImplAsStarlarkValue<T>
158where
159 T: TypeMatcher,
160{
161 type Canonical = TypeCompiledImplAsStarlarkValue<DummyTypeMatcher>;
162
163 fn type_matches_value(&self, value: Value<'v>, _private: Private) -> bool {
164 self.type_compiled_impl.matches(value)
165 }
166
167 fn provide(&'v self, demand: &mut Demand<'_, 'v>) {
168 demand.provide_ref_static::<dyn TypeCompiledDyn>(self);
169 }
170
171 fn write_hash(&self, hasher: &mut StarlarkHasher) -> crate::Result<()> {
172 Hash::hash(&self.ty, hasher);
173 Ok(())
174 }
175
176 fn equals(&self, other: Value<'v>) -> crate::Result<bool> {
177 let Some(other) = other.downcast_ref::<Self>() else {
178 return Ok(false);
179 };
180 Ok(self.ty == other.ty)
181 }
182
183 fn eval_type(&self) -> Option<Ty> {
184 Some(self.ty.clone())
188 }
189
190 fn get_methods() -> Option<&'static Methods>
191 where
192 Self: Sized,
193 {
194 static RES: MethodsStatic = MethodsStatic::new();
195 RES.methods(type_compiled_methods)
196 }
197}
198
199impl<T: TypeMatcher> Display for TypeCompiledImplAsStarlarkValue<T> {
200 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
201 write!(f, "{}", self.ty)
202 }
203}
204
205#[starlark_module]
206fn type_compiled_methods(methods: &mut MethodsBuilder) {
207 fn matches<'v>(this: Value<'v>, value: Value<'v>) -> anyhow::Result<bool> {
209 Ok(this.get_ref().type_matches_value(value))
210 }
211
212 fn check_matches<'v>(this: Value<'v>, value: Value<'v>) -> anyhow::Result<NoneType> {
214 if !this.get_ref().type_matches_value(value) {
215 return Err(TypingError::ValueDoesNotMatchType(
216 value.to_repr(),
217 value.get_type(),
218 TypeCompiled(this).to_string(),
219 )
220 .into());
221 }
222 Ok(NoneType)
223 }
224}
225
226#[derive(
228 Debug,
229 Allocative,
230 Freeze,
231 Trace,
232 Clone,
233 Copy,
234 Dupe,
235 Coerce,
236 ProvidesStaticType
237)]
238#[repr(transparent)]
239pub struct TypeCompiled<V: ValueLifetimeless>(
240 V,
242);
243
244impl<'v, V: ValueLike<'v>> Display for TypeCompiled<V> {
245 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
246 match self.downcast() {
247 Ok(t) => Display::fmt(&t.as_ty_dyn(), f),
248 Err(_) => {
249 Display::fmt(&self.0, f)
251 }
252 }
253 }
254}
255
256impl<V: ValueLifetimeless> StarlarkTypeRepr for TypeCompiled<V> {
257 type Canonical = TypeCompiledImplAsStarlarkValue<DummyTypeMatcher>;
258
259 fn starlark_type_repr() -> Ty {
260 TypeCompiledImplAsStarlarkValue::<DummyTypeMatcher>::starlark_type_repr()
261 }
262}
263
264impl<'v, V: ValueLike<'v>> AllocValue<'v> for TypeCompiled<V> {
265 fn alloc_value(self, _heap: &'v Heap) -> Value<'v> {
266 self.0.to_value()
267 }
268}
269
270impl<'v, V: ValueLike<'v>> TypeCompiled<V> {
271 pub(crate) fn unchecked_new(value: V) -> Self {
272 TypeCompiled(value)
273 }
274
275 fn downcast(self) -> anyhow::Result<&'v dyn TypeCompiledDyn> {
276 self.to_value()
277 .0
278 .request_value::<&dyn TypeCompiledDyn>()
279 .ok_or_else(|| anyhow::anyhow!("Not TypeCompiledImpl (internal error)"))
280 }
281
282 pub fn matches(&self, value: Value<'v>) -> bool {
284 self.0.to_value().get_ref().type_matches_value(value)
285 }
286
287 pub fn as_ty(&self) -> &'v Ty {
289 self.downcast().unwrap().as_ty_dyn()
290 }
291
292 pub(crate) fn is_runtime_wildcard(self) -> bool {
295 self.downcast().unwrap().is_runtime_wildcard_dyn()
296 }
297
298 #[cold]
299 #[inline(never)]
300 fn check_type_error(self, value: Value<'v>, arg_name: Option<&str>) -> crate::Result<()> {
301 Err(crate::Error::new_other(
302 TypingError::TypeAnnotationMismatch(
303 value.to_str(),
304 value.get_type().to_owned(),
305 self.to_string(),
306 match arg_name {
307 None => "return type".to_owned(),
308 Some(x) => format!("argument `{}`", x),
309 },
310 ),
311 ))
312 }
313
314 pub(crate) fn check_type(self, value: Value<'v>, arg_name: Option<&str>) -> crate::Result<()> {
315 if self.matches(value) {
316 Ok(())
317 } else {
318 self.check_type_error(value, arg_name)
319 }
320 }
321
322 pub(crate) fn to_value(self) -> TypeCompiled<Value<'v>> {
323 TypeCompiled(self.0.to_value())
324 }
325
326 pub(crate) fn to_inner(self) -> V {
327 self.0
328 }
329
330 pub(crate) fn write_hash(self, hasher: &mut StarlarkHasher) -> crate::Result<()> {
331 self.to_value().0.write_hash(hasher)
332 }
333
334 pub(crate) fn _equals(self, other: Self) -> crate::Result<bool> {
336 self.to_value().0.equals(other.to_value().0)
337 }
338}
339
340impl<'v, V: ValueLike<'v>> Hash for TypeCompiled<V> {
341 fn hash<H: Hasher>(&self, state: &mut H) {
342 match self.0.to_value().get_hash() {
343 Ok(h) => h.hash(state),
344 Err(_) => {
345 }
347 }
348 }
349}
350
351impl<'v, V: ValueLike<'v>> PartialEq for TypeCompiled<V> {
352 #[allow(clippy::manual_unwrap_or)]
353 fn eq(&self, other: &Self) -> bool {
354 self.0
355 .to_value()
356 .equals(other.0.to_value())
357 .unwrap_or_default()
358 }
359}
360
361impl<'v, V: ValueLike<'v>> Eq for TypeCompiled<V> {}
362
363impl<'v, V: ValueLike<'v>> TypeCompiled<V> {
364 pub fn to_frozen(self, heap: &FrozenHeap) -> TypeCompiled<FrozenValue> {
366 if let Some(v) = self.0.to_value().unpack_frozen() {
367 TypeCompiled(v)
368 } else {
369 self.to_value().downcast().unwrap().to_frozen_dyn(heap)
370 }
371 }
372}
373
374impl<'v> TypeCompiled<Value<'v>> {
377 pub(crate) fn alloc(
378 type_compiled_impl: impl TypeMatcher,
379 ty: Ty,
380 heap: &'v Heap,
381 ) -> TypeCompiled<Value<'v>> {
382 TypeCompiled(heap.alloc_simple(TypeCompiledImplAsStarlarkValue {
383 type_compiled_impl,
384 ty,
385 }))
386 }
387
388 pub(crate) fn type_list_of(
389 t: TypeCompiled<Value<'v>>,
390 heap: &'v Heap,
391 ) -> TypeCompiled<Value<'v>> {
392 TypeCompiledFactory::alloc_ty(&Ty::list(t.as_ty().clone()), heap)
393 }
394
395 pub(crate) fn type_set_of(
396 t: TypeCompiled<Value<'v>>,
397 heap: &'v Heap,
398 ) -> TypeCompiled<Value<'v>> {
399 TypeCompiledFactory::alloc_ty(&Ty::set(t.as_ty().clone()), heap)
400 }
401
402 pub(crate) fn type_any_of_two(
403 t0: TypeCompiled<Value<'v>>,
404 t1: TypeCompiled<Value<'v>>,
405 heap: &'v Heap,
406 ) -> TypeCompiled<Value<'v>> {
407 let ty = Ty::union2(t0.as_ty().clone(), t1.as_ty().clone());
408 TypeCompiledFactory::alloc_ty(&ty, heap)
409 }
410
411 pub(crate) fn type_any_of(
412 ts: Vec<TypeCompiled<Value<'v>>>,
413 heap: &'v Heap,
414 ) -> TypeCompiled<Value<'v>> {
415 let ty = Ty::unions(ts.into_map(|t| t.as_ty().clone()));
416 TypeCompiledFactory::alloc_ty(&ty, heap)
417 }
418
419 pub(crate) fn type_dict_of(
420 kt: TypeCompiled<Value<'v>>,
421 vt: TypeCompiled<Value<'v>>,
422 heap: &'v Heap,
423 ) -> TypeCompiled<Value<'v>> {
424 let ty = Ty::dict(kt.as_ty().clone(), vt.as_ty().clone());
425 TypeCompiledFactory::alloc_ty(&ty, heap)
426 }
427
428 fn from_list(t: &ListRef<'v>, heap: &'v Heap) -> anyhow::Result<TypeCompiled<Value<'v>>> {
430 match t.content() {
431 [] | [_] => Err(TypingError::List.into()),
432 ts @ [_, _, ..] => {
433 let ts = ts.try_map(|t| TypeCompiled::new(*t, heap))?;
435 Ok(TypeCompiled::type_any_of(ts, heap))
436 }
437 }
438 }
439
440 pub(crate) fn from_ty(ty: &Ty, heap: &'v Heap) -> Self {
441 TypeCompiledFactory::alloc_ty(ty, heap)
442 }
443
444 pub fn new(ty: Value<'v>, heap: &'v Heap) -> anyhow::Result<Self> {
446 if let Some(s) = StringValue::new(ty) {
447 return Err(TypingError::StringLiteralNotAllowed(s.to_string()).into());
448 } else if ty.is_none() {
449 Ok(TypeCompiledFactory::alloc_ty(&Ty::none(), heap))
450 } else if let Some(t) = Tuple::from_value(ty) {
451 let elems = t
452 .content()
453 .try_map(|t| anyhow::Ok(TypeCompiled::new(*t, heap)?.as_ty().clone()))?;
454 Ok(TypeCompiled::from_ty(&Ty::tuple(elems), heap))
455 } else if let Some(t) = ListRef::from_value(ty) {
456 TypeCompiled::from_list(t, heap)
457 } else if ty.request_value::<&dyn TypeCompiledDyn>().is_some() {
458 Ok(TypeCompiled(ty))
461 } else if let Some(ty) = ty.get_ref().eval_type() {
462 Ok(TypeCompiled::from_ty(&ty, heap))
463 } else {
464 Err(invalid_type_annotation(ty, heap).into())
465 }
466 }
467}
468
469impl TypeCompiled<FrozenValue> {
470 pub(crate) fn new_frozen(ty: FrozenValue, frozen_heap: &FrozenHeap) -> anyhow::Result<Self> {
472 let heap = Heap::new();
474 let ty = TypeCompiled::new(ty.to_value(), &heap)?;
475 Ok(ty.to_frozen(frozen_heap))
476 }
477
478 pub fn any() -> TypeCompiled<FrozenValue> {
480 static ANYTHING: AValueRepr<
481 AValueImpl<'static, AValueBasic<TypeCompiledImplAsStarlarkValue<IsAny>>>,
482 > = TypeCompiledImplAsStarlarkValue::alloc_static(IsAny, Ty::any());
483
484 TypeCompiled::unchecked_new(FrozenValue::new_repr(&ANYTHING))
485 }
486}
487
488fn invalid_type_annotation<'v>(ty: Value<'v>, heap: &'v Heap) -> TypingError {
489 if DictRef::from_value(ty).is_some() {
490 TypingError::Dict
491 } else if ListRef::from_value(ty).is_some() {
492 TypingError::List
493 } else if let Some(name) = ty
494 .get_attr("type", heap)
495 .ok()
496 .flatten()
497 .and_then(|v| v.unpack_str())
498 {
499 TypingError::PerhapsYouMeant(ty.to_str(), name.into())
500 } else {
501 TypingError::InvalidTypeAnnotation(ty.to_str())
502 }
503}