Skip to main content

facet_core/impls/core/
option.rs

1//! Facet implementation for `Option<T>`
2
3use core::cmp::Ordering;
4
5use crate::{
6    Def, EnumRepr, EnumType, Facet, FieldBuilder, HashProxy, OptionDef, OptionVTable, OxPtrConst,
7    OxPtrMut, OxPtrUninit, OxRef, PtrConst, Repr, Shape, ShapeBuilder, Type, TypeOpsIndirect,
8    TypeParam, UserType, VTableIndirect, Variance, VarianceDep, VarianceDesc, VariantBuilder,
9};
10
11/// Extract the OptionDef from a shape, returns None if not an Option
12#[inline]
13const fn get_option_def(shape: &'static Shape) -> Option<&'static OptionDef> {
14    match shape.def {
15        Def::Option(ref def) => Some(def),
16        _ => None,
17    }
18}
19
20fn option_type_name(
21    shape: &'static Shape,
22    f: &mut core::fmt::Formatter<'_>,
23    opts: crate::TypeNameOpts,
24) -> core::fmt::Result {
25    write!(f, "Option")?;
26    if let Some(opts) = opts.for_children() {
27        write!(f, "<")?;
28        if let Some(tp) = shape.type_params.first() {
29            tp.shape.write_type_name(f, opts)?;
30        }
31        write!(f, ">")?;
32    } else {
33        write!(f, "<…>")?;
34    }
35    Ok(())
36}
37
38#[inline]
39unsafe fn option_get_value_ptr(def: &OptionDef, ptr: PtrConst) -> Option<PtrConst> {
40    let raw = unsafe { (def.vtable.get_value)(ptr) };
41    if raw.is_null() {
42        None
43    } else {
44        Some(PtrConst::new_sized(raw))
45    }
46}
47
48/// Display for `Option<T>` - delegates to inner T's display if available
49unsafe fn option_display(
50    ox: OxPtrConst,
51    f: &mut core::fmt::Formatter<'_>,
52) -> Option<core::fmt::Result> {
53    let shape = ox.shape();
54    let def = get_option_def(shape)?;
55    let ptr = ox.ptr();
56
57    if unsafe { (def.vtable.is_some)(ptr) } {
58        // Get the inner value using the vtable
59        let inner_ptr = unsafe { option_get_value_ptr(def, ptr)? };
60        // Delegate to inner type's display
61        unsafe { def.t.call_display(inner_ptr, f) }
62    } else {
63        Some(f.write_str("None"))
64    }
65}
66
67/// Debug for `Option<T>` - delegates to inner T's debug if available
68unsafe fn option_debug(
69    ox: OxPtrConst,
70    f: &mut core::fmt::Formatter<'_>,
71) -> Option<core::fmt::Result> {
72    let shape = ox.shape();
73    let def = get_option_def(shape)?;
74    let ptr = ox.ptr();
75
76    if unsafe { (def.vtable.is_some)(ptr) } {
77        // Get the inner value using the vtable
78        // SAFETY: is_some returned true, so get_value returns a valid pointer.
79        // The caller guarantees the OxPtrConst points to a valid Option.
80        let inner_ptr = unsafe { option_get_value_ptr(def, ptr)? };
81        let inner_ox = unsafe { OxRef::new(inner_ptr, def.t) };
82        Some(f.debug_tuple("Some").field(&inner_ox).finish())
83    } else {
84        Some(f.write_str("None"))
85    }
86}
87
88/// Hash for `Option<T>` - delegates to inner T's hash if available
89unsafe fn option_hash(ox: OxPtrConst, hasher: &mut HashProxy<'_>) -> Option<()> {
90    let shape = ox.shape();
91    let def = get_option_def(shape)?;
92    let ptr = ox.ptr();
93
94    use core::hash::Hash;
95    if unsafe { (def.vtable.is_some)(ptr) } {
96        1u8.hash(hasher);
97        let inner_ptr = unsafe { option_get_value_ptr(def, ptr)? };
98        unsafe { def.t.call_hash(inner_ptr, hasher)? };
99    } else {
100        0u8.hash(hasher);
101    }
102    Some(())
103}
104
105/// PartialEq for `Option<T>`
106unsafe fn option_partial_eq(a: OxPtrConst, b: OxPtrConst) -> Option<bool> {
107    let shape = a.shape();
108    let def = get_option_def(shape)?;
109
110    let a_ptr = a.ptr();
111    let b_ptr = b.ptr();
112    let a_is_some = unsafe { (def.vtable.is_some)(a_ptr) };
113    let b_is_some = unsafe { (def.vtable.is_some)(b_ptr) };
114
115    Some(match (a_is_some, b_is_some) {
116        (false, false) => true,
117        (true, true) => {
118            let a_inner = unsafe { option_get_value_ptr(def, a_ptr)? };
119            let b_inner = unsafe { option_get_value_ptr(def, b_ptr)? };
120            unsafe { def.t.call_partial_eq(a_inner, b_inner)? }
121        }
122        _ => false,
123    })
124}
125
126/// PartialOrd for `Option<T>`
127unsafe fn option_partial_cmp(a: OxPtrConst, b: OxPtrConst) -> Option<Option<Ordering>> {
128    let shape = a.shape();
129    let def = get_option_def(shape)?;
130
131    let a_ptr = a.ptr();
132    let b_ptr = b.ptr();
133    let a_is_some = unsafe { (def.vtable.is_some)(a_ptr) };
134    let b_is_some = unsafe { (def.vtable.is_some)(b_ptr) };
135
136    Some(match (a_is_some, b_is_some) {
137        (false, false) => Some(Ordering::Equal),
138        (false, true) => Some(Ordering::Less),
139        (true, false) => Some(Ordering::Greater),
140        (true, true) => {
141            let a_inner = unsafe { option_get_value_ptr(def, a_ptr)? };
142            let b_inner = unsafe { option_get_value_ptr(def, b_ptr)? };
143            unsafe { def.t.call_partial_cmp(a_inner, b_inner)? }
144        }
145    })
146}
147
148/// Ord for `Option<T>`
149unsafe fn option_cmp(a: OxPtrConst, b: OxPtrConst) -> Option<Ordering> {
150    let shape = a.shape();
151    let def = get_option_def(shape)?;
152
153    let a_ptr = a.ptr();
154    let b_ptr = b.ptr();
155    let a_is_some = unsafe { (def.vtable.is_some)(a_ptr) };
156    let b_is_some = unsafe { (def.vtable.is_some)(b_ptr) };
157
158    Some(match (a_is_some, b_is_some) {
159        (false, false) => Ordering::Equal,
160        (false, true) => Ordering::Less,
161        (true, false) => Ordering::Greater,
162        (true, true) => {
163            let a_inner = unsafe { option_get_value_ptr(def, a_ptr)? };
164            let b_inner = unsafe { option_get_value_ptr(def, b_ptr)? };
165            unsafe { def.t.call_cmp(a_inner, b_inner)? }
166        }
167    })
168}
169
170/// Drop for `Option<T>`
171unsafe fn option_drop(ox: OxPtrMut) {
172    let shape = ox.shape();
173    let Some(def) = get_option_def(shape) else {
174        return;
175    };
176    let ptr = ox.ptr();
177
178    if unsafe { (def.vtable.is_some)(ptr.as_const()) } {
179        // Get a mutable pointer to the inner value directly.
180        // We can't use get_value() because it creates a shared reference via as_ref(),
181        // which would violate Stacked Borrows when we try to drop through a mutable pointer.
182        // Instead, call the typed drop function which takes a mutable pointer.
183        unsafe { option_drop_inner(ptr, def) };
184    }
185}
186
187/// Helper to drop the inner value of a Some option with proper mutable access
188unsafe fn option_drop_inner(ptr: crate::PtrMut, def: &OptionDef) {
189    // Use the replace_with vtable function to replace Some with None.
190    // This properly handles the drop of the inner value.
191    unsafe { (def.vtable.replace_with)(ptr, core::ptr::null_mut()) };
192}
193
194/// Default for `Option<T>` - always None (no `T::Default` requirement)
195unsafe fn option_default<T>(ox: OxPtrUninit) -> bool {
196    unsafe { ox.put(Option::<T>::None) };
197    true
198}
199
200/// Check if `Option<T>` is Some
201unsafe fn option_is_some<T>(option: PtrConst) -> bool {
202    unsafe { option.get::<Option<T>>().is_some() }
203}
204
205/// Check if `Option<T>` is Some (C ABI wrapper for vtable callbacks)
206unsafe extern "C" fn option_is_some_vtable<T>(option: PtrConst) -> bool {
207    unsafe { option_is_some::<T>(option) }
208}
209
210/// Get the value from `Option<T>` if present
211unsafe extern "C" fn option_get_value<T>(option: PtrConst) -> *const u8 {
212    unsafe {
213        option
214            .get::<Option<T>>()
215            .as_ref()
216            .map_or(core::ptr::null(), |t| t as *const T as *const u8)
217    }
218}
219
220/// Initialize `Option<T>` with Some(value)
221unsafe extern "C" fn option_init_some<T>(
222    option: crate::PtrUninit,
223    value: crate::PtrMut,
224) -> crate::PtrMut {
225    unsafe { option.put(Option::Some(value.read::<T>())) }
226}
227
228/// Initialize `Option<T>` with None
229unsafe extern "C" fn option_init_none<T>(option: crate::PtrUninit) -> crate::PtrMut {
230    unsafe { option.put(<Option<T>>::None) }
231}
232
233/// Replace `Option<T>` with a new value
234unsafe extern "C" fn option_replace_with<T>(option: crate::PtrMut, value: *mut u8) {
235    unsafe {
236        let option = option.as_mut::<Option<T>>();
237        if value.is_null() {
238            option.take();
239        } else {
240            option.replace(crate::PtrMut::new_sized(value).read::<T>());
241        }
242    }
243}
244
245unsafe impl<'a, T: Facet<'a>> Facet<'a> for Option<T> {
246    const SHAPE: &'static Shape = &const {
247        const fn build_option_vtable<T>() -> OptionVTable {
248            OptionVTable::builder()
249                .is_some(option_is_some_vtable::<T>)
250                .get_value(option_get_value::<T>)
251                .init_some(option_init_some::<T>)
252                .init_none(option_init_none::<T>)
253                .replace_with(option_replace_with::<T>)
254                .build()
255        }
256
257        const fn build_vtable() -> VTableIndirect {
258            VTableIndirect {
259                display: Some(option_display),
260                debug: Some(option_debug),
261                hash: Some(option_hash),
262                invariants: None,
263                parse: None,
264                parse_bytes: None,
265                try_from: None,
266                try_into_inner: None,
267                try_borrow_inner: None,
268                partial_eq: Some(option_partial_eq),
269                partial_cmp: Some(option_partial_cmp),
270                cmp: Some(option_cmp),
271            }
272        }
273
274        const fn build_type_ops<T>() -> TypeOpsIndirect {
275            TypeOpsIndirect {
276                drop_in_place: option_drop,
277                default_in_place: Some(option_default::<T>),
278                clone_into: None,
279                is_truthy: Some(option_is_some::<T>),
280            }
281        }
282
283        ShapeBuilder::for_sized::<Option<T>>("Option")
284            .module_path("core::option")
285            .type_name(option_type_name)
286            .ty(Type::User(
287                // Null-Pointer-Optimization check
288                if core::mem::size_of::<T>() == core::mem::size_of::<Option<T>>()
289                    && core::mem::size_of::<T>() <= core::mem::size_of::<usize>()
290                {
291                    UserType::Enum(EnumType {
292                        repr: Repr::default(),
293                        enum_repr: EnumRepr::RustNPO,
294                        variants: &const {
295                            [
296                                VariantBuilder::unit("None").discriminant(0).build(),
297                                VariantBuilder::tuple(
298                                    "Some",
299                                    &const { [FieldBuilder::new("0", crate::shape_of::<T>, 0).build()] },
300                                )
301                                .discriminant(0)
302                                .build(),
303                            ]
304                        },
305                        is_cow: false,
306                    })
307                } else {
308                    UserType::Enum(EnumType {
309                        repr: Repr::default(),
310                        enum_repr: EnumRepr::Rust,
311                        variants: &const {
312                            [
313                                VariantBuilder::unit("None").discriminant(0).build(),
314                                VariantBuilder::tuple(
315                                    "Some",
316                                    &const { [FieldBuilder::new("0", crate::shape_of::<T>, 0).build()] },
317                                )
318                                .discriminant(1)
319                                .build(),
320                            ]
321                        },
322                        is_cow: false,
323                    })
324                },
325            ))
326            .def(Def::Option(OptionDef::new(
327                &const { build_option_vtable::<T>() },
328                T::SHAPE,
329            )))
330            .type_params(&[TypeParam {
331                name: "T",
332                shape: T::SHAPE,
333            }])
334            .inner(T::SHAPE)
335            .vtable_indirect(&const { build_vtable() })
336            .type_ops_indirect(&const { build_type_ops::<T>() })
337            // Option<T> propagates T's variance
338            .variance(VarianceDesc {
339                base: Variance::Bivariant,
340                deps: &const { [VarianceDep::covariant(T::SHAPE)] },
341            })
342            .build()
343    };
344}