jlrs/data/types/
typecheck.rs

1//! Trait for checking properties of Julia data.
2//!
3//! Several properties of Julia data can be checked by using [`Value::is`] and [`DataType::is`],
4//! these methods must be used in combination with a type that implements the [`Typecheck`] trait.
5//! Most types that implement this trait also implement [`Managed`] or [`Unbox`], for these types
6//! the typecheck indicates whether or not it's valid to cast the value to or unbox it as that
7//! type.
8//!
9//! [`Value::is`]: crate::data::managed::value::Value::is
10//! [`Managed`]: crate::data::managed::Managed
11//! [`Unbox`]: crate::convert::unbox::Unbox
12use std::{ffi::c_void, marker::PhantomData};
13
14// TODO: Unify with other ConstructType and abstract types?
15use jl_sys::jl_string_type;
16
17use super::abstract_type::AbstractType;
18use crate::{
19    convert::into_julia::IntoJulia,
20    data::managed::{Managed, datatype::DataType, type_name::TypeName, union_all::UnionAll},
21    memory::target::unrooted::Unrooted,
22    prelude::LocalScope,
23};
24
25/// This trait is used in combination with [`Value::is`] and [`DataType::is`] to check if that
26/// property holds true.
27///
28/// Safety: If this trait is implemented for some type which also implements `Unbox`, the trait
29/// method `typecheck` must only return `true` if it's guaranteed that `Unbox::unbox` can safely
30/// be called for values whose type is that method's argument.
31///
32/// [`Value::is`]: crate::data::managed::value::Value::is
33/// [`Unbox`]: crate::convert::unbox::Unbox
34/// [`Managed`]: crate::data::managed::Managed
35#[diagnostic::on_unimplemented(
36    message = "the trait bound `{Self}: Typecheck` is not satisfied",
37    label = "the trait `Typecheck` is not implemented for `{Self}`",
38    note = "Custom types that implement `Typecheck` should be generated with JlrsCore.reflect",
39    note = "Do not implement `ForeignType` or `OpaqueType` unless this type is exported to Julia with `julia_module!`"
40)]
41pub unsafe trait Typecheck {
42    /// Returns whether the property implied by `Self` holds true.
43    fn typecheck(t: DataType) -> bool;
44}
45
46/// Type that implements [`Typecheck`] for every [`AbstractType`] `A`.
47///
48/// The typecheck returns `true` if the type is a subtype of `A`.
49pub struct AbstractTypecheck<A: AbstractType>(PhantomData<A>);
50
51unsafe impl<A: AbstractType> Typecheck for AbstractTypecheck<A> {
52    fn typecheck(t: DataType) -> bool {
53        t.unrooted_target().local_scope::<_, 1>(|mut frame| {
54            let ty = A::construct_type(&mut frame);
55            t.as_value().subtype(ty)
56        })
57    }
58}
59
60#[doc(hidden)]
61#[macro_export]
62macro_rules! impl_julia_typecheck {
63    ($type:ty, $jl_type:expr_2021, $($lt:lifetime),+) => {
64        unsafe impl<$($lt),+> crate::data::types::typecheck::Typecheck for $type {
65            #[inline]
66            fn typecheck(t: $crate::data::managed::datatype::DataType) -> bool {
67                unsafe {
68                    <$crate::data::managed::datatype::DataType as $crate::data::managed::private::ManagedPriv>::unwrap(t, crate::private::Private) == $jl_type
69                }
70            }
71        }
72    };
73    ($type:ty, $jl_type:expr_2021) => {
74        unsafe impl crate::data::types::typecheck::Typecheck for $type {
75            #[inline]
76            fn typecheck(t: $crate::data::managed::datatype::DataType) -> bool {
77                unsafe {
78                    <$crate::data::managed::datatype::DataType as $crate::data::managed::private::ManagedPriv>::unwrap(t, crate::private::Private) == $jl_type
79                }
80            }
81        }
82    };
83    ($type:ty) => {
84        unsafe impl crate::data::types::typecheck::Typecheck for $type {
85            #[inline]
86            fn typecheck(t: crate::data::managed::datatype::DataType) -> bool {
87                unsafe {
88                    let global = $crate::memory::target::unrooted::Unrooted::new();
89                    <$crate::data::managed::datatype::DataType as $crate::data::managed::private::ManagedPriv>::unwrap(t, crate::private::Private) == <$type as $crate::convert::into_julia::IntoJulia>::julia_type(global).ptr().as_ptr()
90                }
91            }
92        }
93    };
94}
95
96impl_julia_typecheck!(i8);
97impl_julia_typecheck!(i16);
98impl_julia_typecheck!(i32);
99impl_julia_typecheck!(i64);
100impl_julia_typecheck!(isize);
101impl_julia_typecheck!(u8);
102impl_julia_typecheck!(u16);
103impl_julia_typecheck!(u32);
104impl_julia_typecheck!(u64);
105impl_julia_typecheck!(usize);
106impl_julia_typecheck!(f32);
107impl_julia_typecheck!(f64);
108impl_julia_typecheck!(bool);
109impl_julia_typecheck!(char);
110impl_julia_typecheck!(*mut c_void);
111
112unsafe impl<T: IntoJulia> Typecheck for *mut T {
113    #[inline]
114    fn typecheck(t: DataType) -> bool {
115        unsafe {
116            let global = Unrooted::new();
117            let ptr_tname = TypeName::of_pointer(&global);
118
119            if t.type_name() != ptr_tname {
120                return false;
121            }
122
123            let params = t.parameters();
124            let param = params.data().get(global, 0);
125            let inner_ty = T::julia_type(global);
126            if param.unwrap_unchecked().as_value() != inner_ty.as_value() {
127                return false;
128            }
129
130            true
131        }
132    }
133}
134
135/// A typecheck that can be used in combination with `DataType::is`. This method returns true if
136/// the `DataType` (or the `DataType` of the `Value`) is a kind, i.e. its the type of a
137/// `DataType`, a `UnionAll`, a `Union` or a `Union{}`.
138pub struct Type;
139unsafe impl Typecheck for Type {
140    #[inline]
141    fn typecheck(t: DataType) -> bool {
142        t.as_value().is_kind()
143    }
144}
145
146/// A typecheck that can be used in combination with `DataType::is`. This method returns true if
147/// the `DataType` (or the `DataType` of the `Value`) is a bits type.
148pub struct Bits;
149unsafe impl Typecheck for Bits {
150    #[inline]
151    fn typecheck(t: DataType) -> bool {
152        t.is_bits()
153    }
154}
155
156/// A typecheck that can be used in combination with `DataType::is`. This method returns true if
157/// the `DataType` is abstract. If it's invoked through `Value::is` it will always return false.
158pub struct Abstract;
159unsafe impl Typecheck for Abstract {
160    #[inline]
161    fn typecheck(t: DataType) -> bool {
162        t.is_abstract()
163    }
164}
165
166/// A typecheck that can be used in combination with `DataType::is`. This method returns true if
167/// the value is a `Ref`.
168pub struct AbstractRef;
169unsafe impl Typecheck for AbstractRef {
170    fn typecheck(t: DataType) -> bool {
171        unsafe {
172            t.type_name()
173                == UnionAll::ref_type(&Unrooted::new())
174                    .body()
175                    .cast_unchecked::<DataType>()
176                    .type_name()
177        }
178    }
179}
180
181/// A typecheck that can be used in combination with `DataType::is`. This method returns true if
182/// the value is a `VecElement`.
183pub struct VecElement;
184unsafe impl Typecheck for VecElement {
185    #[inline]
186    fn typecheck(t: DataType) -> bool {
187        unsafe { t.type_name() == TypeName::of_vecelement(&Unrooted::new()) }
188    }
189}
190
191/// A typecheck that can be used in combination with `DataType::is`. This method returns true if
192/// the value is a `Type{T}`.
193pub struct TypeType;
194unsafe impl Typecheck for TypeType {
195    fn typecheck(t: DataType) -> bool {
196        unsafe {
197            t.type_name()
198                == UnionAll::type_type(&Unrooted::new())
199                    .body()
200                    .cast_unchecked::<DataType>()
201                    .type_name()
202        }
203    }
204}
205
206/// A typecheck that can be used in combination with `DataType::is`. This method returns true if
207/// the fields of a value of this type can be modified.
208pub struct Mutable;
209unsafe impl Typecheck for Mutable {
210    #[inline]
211    fn typecheck(t: DataType) -> bool {
212        t.mutable()
213    }
214}
215
216/// A typecheck that can be used in combination with `DataType::is`. This method returns true if
217/// the fields of a value of this type cannot be modified.
218pub struct Immutable;
219unsafe impl Typecheck for Immutable {
220    #[inline]
221    fn typecheck(t: DataType) -> bool {
222        !t.mutable()
223    }
224}
225
226/// A typecheck that can be used in combination with `DataType::is`. This method returns true if
227/// a value of this type is a primitive type.
228pub struct PrimitiveType;
229unsafe impl Typecheck for PrimitiveType {
230    fn typecheck(t: DataType) -> bool {
231        unsafe {
232            t.is::<Immutable>()
233                && t.has_layout()
234                && t.n_fields().unwrap_unchecked() == 0
235                && t.size().unwrap_unchecked() > 0
236        }
237    }
238}
239
240/// A typecheck that can be used in combination with `DataType::is`. This method returns true if
241/// a value of this type is a struct type.
242pub struct StructType;
243unsafe impl Typecheck for StructType {
244    #[inline]
245    fn typecheck(t: DataType) -> bool {
246        !t.is_abstract() && !t.is::<PrimitiveType>()
247    }
248}
249
250/// A typecheck that can be used in combination with `DataType::is`. This method returns true if
251/// a value of this type is a struct type.
252pub struct Singleton;
253unsafe impl Typecheck for Singleton {
254    #[inline]
255    fn typecheck(t: DataType) -> bool {
256        t.instance().is_some()
257    }
258}
259
260impl_julia_typecheck!(String, jl_string_type);
261
262/// A typecheck that can be used in combination with `DataType::is`. This method returns true if
263/// a value of this type is a pointer to data not owned by Julia.
264pub struct Pointer;
265unsafe impl Typecheck for Pointer {
266    #[inline]
267    fn typecheck(t: DataType) -> bool {
268        unsafe { t.type_name() == TypeName::of_pointer(&Unrooted::new()) }
269    }
270}
271
272/// A typecheck that can be used in combination with `DataType::is`. This method returns true if
273/// a value of this type is an LLVM pointer.
274pub struct LLVMPointer;
275unsafe impl Typecheck for LLVMPointer {
276    #[inline]
277    fn typecheck(t: DataType) -> bool {
278        unsafe { t.type_name() == TypeName::of_llvmpointer(&Unrooted::new()) }
279    }
280}
281
282/// A typecheck that can be used in combination with `DataType::is`. This method returns true if
283/// instances of the type can be created.
284pub struct Concrete;
285unsafe impl Typecheck for Concrete {
286    #[inline]
287    fn typecheck(t: DataType) -> bool {
288        t.is_concrete_type()
289    }
290}