• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

facet-rs / facet / 20024290994

08 Dec 2025 10:08AM UTC coverage: 58.613% (+0.01%) from 58.6%
20024290994

push

github

fasterthanlime
feat: compute variance automatically from field types

This implements automatic variance computation for derived types:

- Change `Shape.variance` from `fn() -> Variance` to `fn(&'static Shape) -> Variance`
  to allow variance functions to walk type structure without capturing generics

- Add `Shape::computed_variance()` that walks struct fields, enum variants,
  and inner shapes (for Box, Vec, Option, etc.) to compute variance

- Use thread-local cycle detection to prevent stack overflow with recursive types.
  When a cycle is detected, returns `Covariant` (identity for `combine`)

- Update derive macros to use `.variance(Shape::computed_variance)`
  instead of generating per-type variance computation code

- Update `Box<T>` to use computed variance (depends on T's variance)
  instead of hardcoded `Invariant`

- Add variance tests for recursive types

Closes #1171

44 of 60 new or added lines in 3 files covered. (73.33%)

487 existing lines in 23 files now uncovered.

24730 of 42192 relevant lines covered (58.61%)

544.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.25
/facet-core/src/impls_alloc/boxed.rs
1
use core::{alloc::Layout, ptr::NonNull};
2

3
use alloc::boxed::Box;
4

5
use crate::{
6
    Def, Facet, KnownPointer, PointerDef, PointerFlags, PointerVTable, PtrConst, PtrMut, PtrUninit,
7
    Shape, TryBorrowInnerError, TryFromError, TryIntoInnerError, Type, UserType, ValueVTable,
8
    shape_util::vtable_for_ptr,
9
};
10

11
// Define the functions for transparent conversion between Box<T> and T
12
unsafe fn try_from<'src, 'dst>(
66✔
13
    src_ptr: PtrConst<'src>,
66✔
14
    src_shape: &'static Shape,
66✔
15
    dst: PtrUninit<'dst>,
66✔
16
) -> Result<PtrMut<'dst>, TryFromError> {
66✔
17
    let layout = src_shape.layout.sized_layout().unwrap();
66✔
18

19
    unsafe {
20
        let alloc = alloc::alloc::alloc(layout);
66✔
21
        if alloc.is_null() {
66✔
UNCOV
22
            alloc::alloc::handle_alloc_error(layout);
×
23
        }
66✔
24

25
        let src_ptr = src_ptr.as_ptr::<u8>();
66✔
26
        core::ptr::copy_nonoverlapping(src_ptr, alloc, layout.size());
66✔
27

28
        // layout of
29
        // Box<T> == *mut T == *mut u8
30
        Ok(dst.put(alloc))
66✔
31
    }
32
}
66✔
33

34
unsafe fn try_into_inner<'a, 'src, 'dst, T: ?Sized + Facet<'a>>(
×
35
    src_ptr: PtrMut<'src>,
×
36
    dst: PtrUninit<'dst>,
×
UNCOV
37
) -> Result<PtrMut<'dst>, TryIntoInnerError> {
×
38
    if const { size_of::<*const T>() == size_of::<*const ()>() } {
39
        let boxed = unsafe { src_ptr.read::<Box<T>>() };
×
40
        let layout = Layout::for_value(&*boxed);
×
UNCOV
41
        let ptr = Box::into_raw(boxed) as *mut u8;
×
42
        unsafe {
43
            core::ptr::copy_nonoverlapping(ptr, dst.as_mut_byte_ptr(), layout.size());
×
44
            alloc::alloc::dealloc(ptr, layout);
×
UNCOV
45
            Ok(dst.assume_init())
×
46
        }
47
    } else {
UNCOV
48
        panic!();
×
49
    }
UNCOV
50
}
×
51
unsafe impl<'a, T: ?Sized + Facet<'a>> Facet<'a> for Box<T> {
52
    const SHAPE: &'static crate::Shape = &const {
53
        unsafe fn try_borrow_inner<'a, 'src, T: ?Sized + Facet<'a>>(
72✔
54
            src_ptr: PtrConst<'src>,
72✔
55
        ) -> Result<PtrConst<'src>, TryBorrowInnerError> {
72✔
56
            let boxed = unsafe { src_ptr.get::<Box<T>>() };
72✔
57
            Ok(PtrConst::new(NonNull::from(&**boxed)))
72✔
58
        }
72✔
59

60
        Shape {
61
            id: Shape::id_of::<Self>(),
62
            layout: Shape::layout_of::<Self>(),
63
            vtable: ValueVTable {
64
                type_name: |f, opts| {
37✔
65
                    write!(f, "{}", Self::SHAPE.type_identifier)?;
37✔
66
                    if let Some(opts) = opts.for_children() {
37✔
67
                        write!(f, "<")?;
37✔
68
                        (T::SHAPE.vtable.type_name())(f, opts)?;
37✔
69
                        write!(f, ">")?;
37✔
70
                    } else {
UNCOV
71
                        write!(f, "<…>")?;
×
72
                    }
73
                    Ok(())
37✔
74
                },
37✔
75
                try_from: if size_of::<*const T>() == size_of::<*const ()>() {
76
                    Some(try_from)
77
                } else {
78
                    None
79
                },
80
                try_into_inner: if size_of::<*const T>() == size_of::<*const ()>() {
81
                    Some(try_into_inner::<T>)
82
                } else {
83
                    None
84
                },
85
                try_borrow_inner: Some(try_borrow_inner::<T>),
86
                ..vtable_for_ptr::<T, Self>()
87
            },
88
            ty: Type::User(UserType::Opaque),
89
            def: Def::Pointer(PointerDef {
90
                vtable: &const {
91
                    PointerVTable {
92
                        borrow_fn: Some(|this| unsafe {
93
                            let concrete = this.get::<Box<T>>();
55✔
94
                            let t: &T = concrete.as_ref();
55✔
95
                            PtrConst::new(NonNull::from(t))
55✔
96
                        }),
55✔
97
                        new_into_fn: if size_of::<*const T>() == size_of::<*const ()>() {
98
                            Some(|this, ptr| unsafe {
99
                                try_from(ptr.as_const(), T::SHAPE, this).unwrap()
54✔
100
                            })
54✔
101
                        } else {
102
                            None
103
                        },
104
                        ..PointerVTable::new()
105
                    }
106
                },
107
                pointee: Some(T::SHAPE),
108
                weak: None,
109
                strong: None,
110
                flags: PointerFlags::EMPTY,
111
                known: Some(KnownPointer::Box),
112
            }),
113
            type_identifier: "Box",
114
            type_params: &[crate::TypeParam {
115
                name: "T",
116
                shape: T::SHAPE,
117
            }],
118
            doc: &[],
119
            attributes: &[],
120
            type_tag: None,
121
            inner: Some(T::SHAPE),
122
            proxy: None,
123
            variance: Shape::computed_variance,
124
        }
125
    };
126
}
127

128
#[cfg(test)]
129
mod tests {
130
    use core::mem::ManuallyDrop;
131

132
    use alloc::boxed::Box;
133
    use alloc::string::String;
134

135
    use super::*;
136

137
    #[test]
138
    fn test_box_type_params() {
1✔
139
        let [type_param_1] = <Box<i32>>::SHAPE.type_params else {
1✔
UNCOV
140
            panic!("Box<T> should only have 1 type param")
×
141
        };
142
        assert_eq!(type_param_1.shape(), i32::SHAPE);
1✔
143
    }
1✔
144

145
    #[test]
146
    fn test_box_vtable_1_new_borrow_drop() {
1✔
147
        facet_testhelpers::setup();
1✔
148

149
        let box_shape = <Box<String>>::SHAPE;
1✔
150
        let box_def = box_shape
1✔
151
            .def
1✔
152
            .into_pointer()
1✔
153
            .expect("Box<T> should have a smart pointer definition");
1✔
154

155
        // Allocate memory for the Box
156
        let box_uninit_ptr = box_shape.allocate().unwrap();
1✔
157

158
        // Get the function pointer for creating a new Box from a value
159
        let new_into_fn = box_def
1✔
160
            .vtable
1✔
161
            .new_into_fn
1✔
162
            .expect("Box<T> should have new_into_fn");
1✔
163

164
        // Create the value and initialize the Box
165
        let mut value = ManuallyDrop::new(String::from("example"));
1✔
166
        let box_ptr =
1✔
167
            unsafe { new_into_fn(box_uninit_ptr, PtrMut::new(NonNull::from(&mut value))) };
1✔
168
        // The value now belongs to the Box, prevent its drop
169

170
        // Get the function pointer for borrowing the inner value
171
        let borrow_fn = box_def
1✔
172
            .vtable
1✔
173
            .borrow_fn
1✔
174
            .expect("Box<T> should have borrow_fn");
1✔
175

176
        // Borrow the inner value and check it
177
        let borrowed_ptr = unsafe { borrow_fn(box_ptr.as_const()) };
1✔
178
        // SAFETY: borrowed_ptr points to a valid String within the Box
179
        assert_eq!(unsafe { borrowed_ptr.get::<String>() }, "example");
1✔
180

181
        // Get the function pointer for dropping the Box
182
        let drop_fn = box_shape
1✔
183
            .vtable
1✔
184
            .drop_in_place
1✔
185
            .expect("Box<T> should have drop_in_place");
1✔
186

187
        // Drop the Box in place
188
        // SAFETY: box_ptr points to a valid Box<String>
189
        unsafe { drop_fn(box_ptr) };
1✔
190

191
        // Deallocate the memory
192
        // SAFETY: box_ptr was allocated by box_shape and is now dropped (but memory is still valid)
193
        unsafe { box_shape.deallocate_mut(box_ptr).unwrap() };
1✔
194
    }
1✔
195
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc