• 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

67.31
/facet-core/src/impls_core/pointer.rs
1
use crate::Variance;
2
use core::fmt;
3
use core::hash::Hash;
4

5
use crate::{
6
    CmpVTable, Def, Facet, FormatVTable, HashVTable, MarkerTraits, PointerType, Shape, Type,
7
    TypeParam, ValuePointerType, ValueVTable,
8
};
9

10
// *const pointers
11
unsafe impl<'a, T: Facet<'a> + ?Sized> Facet<'a> for *const T {
12
    const SHAPE: &'static Shape = &const {
13
        Shape {
14
            id: Shape::id_of::<Self>(),
15
            layout: Shape::layout_of::<Self>(),
16
            vtable: ValueVTable {
17
                type_name: |f, opts| {
12✔
18
                    if let Some(opts) = opts.for_children() {
12✔
19
                        write!(f, "*const ")?;
12✔
20
                        (T::SHAPE.vtable.type_name())(f, opts)
12✔
21
                    } else {
UNCOV
22
                        write!(f, "*const …")
×
23
                    }
24
                },
12✔
25
                drop_in_place: ValueVTable::drop_in_place_for::<Self>(),
26
                default_in_place: None,
27
                clone_into: Some(|src, dst| unsafe { dst.put(*src.get::<Self>()) }),
4✔
28
                parse: None,
29
                invariants: None,
30
                try_from: None,
31
                try_into_inner: None,
32
                try_borrow_inner: None,
33
                format: FormatVTable {
34
                    display: None,
35
                    debug: Some(|p, f| fmt::Debug::fmt(unsafe { p.get::<Self>() }, f)),
48✔
36
                },
37
                cmp: CmpVTable {
38
                    partial_eq: Some(|a, b| {
6✔
39
                        #[allow(ambiguous_wide_pointer_comparisons)]
40
                        unsafe {
41
                            *a.get::<Self>() == *b.get::<Self>()
6✔
42
                        }
43
                    }),
6✔
44
                    partial_ord: Some(|a, b| {
6✔
45
                        #[allow(ambiguous_wide_pointer_comparisons)]
46
                        unsafe {
47
                            a.get::<Self>().partial_cmp(b.get::<Self>())
6✔
48
                        }
49
                    }),
6✔
50
                    ord: Some(|a, b| {
6✔
51
                        #[allow(ambiguous_wide_pointer_comparisons)]
52
                        unsafe {
53
                            a.get::<Self>().cmp(b.get::<Self>())
6✔
54
                        }
55
                    }),
6✔
56
                },
57
                hash: HashVTable {
58
                    hash: Some(|value, hasher| unsafe {
59
                        value.get::<Self>().hash(&mut { hasher })
×
UNCOV
60
                    }),
×
61
                },
62
                markers: MarkerTraits::EMPTY.with_eq().with_copy(),
63
            },
64
            ty: {
65
                let is_wide = ::core::mem::size_of::<Self>() != ::core::mem::size_of::<*const ()>();
66
                let vpt = ValuePointerType {
67
                    mutable: false,
68
                    wide: is_wide,
69
                    target: T::SHAPE,
70
                };
71

72
                Type::Pointer(PointerType::Raw(vpt))
73
            },
74
            def: Def::Scalar,
75
            type_identifier: "*const _",
76
            type_params: &[TypeParam {
77
                name: "T",
78
                shape: T::SHAPE,
79
            }],
80
            doc: &[],
81
            attributes: &[],
82
            type_tag: None,
83
            inner: Some(T::SHAPE),
84
            proxy: None,
85
            // *const T is covariant in T per the Rust Reference:
86
            // https://doc.rust-lang.org/reference/subtyping.html#r-subtyping.variance.builtin-types
87
            variance: Variance::COVARIANT,
88
        }
89
    };
90
}
91

92
// *mut pointers
93
unsafe impl<'a, T: Facet<'a> + ?Sized> Facet<'a> for *mut T {
94
    const SHAPE: &'static Shape = &const {
95
        Shape {
96
            id: Shape::id_of::<Self>(),
97
            layout: Shape::layout_of::<Self>(),
98
            vtable: ValueVTable {
99
                type_name: |f, opts| {
8✔
100
                    if let Some(opts) = opts.for_children() {
8✔
101
                        write!(f, "*mut ")?;
8✔
102
                        (T::SHAPE.vtable.type_name())(f, opts)
8✔
103
                    } else {
UNCOV
104
                        write!(f, "*mut …")
×
105
                    }
106
                },
8✔
107
                drop_in_place: ValueVTable::drop_in_place_for::<Self>(),
108
                default_in_place: None,
109
                clone_into: Some(|src, dst| unsafe { dst.put(*src.get::<Self>()) }),
4✔
110
                parse: None,
111
                invariants: None,
112
                try_from: None,
113
                try_into_inner: None,
114
                try_borrow_inner: None,
115
                format: FormatVTable {
116
                    display: None,
117
                    debug: Some(|p, f| fmt::Debug::fmt(unsafe { p.get::<Self>() }, f)),
32✔
118
                },
119
                cmp: CmpVTable {
120
                    partial_eq: Some(|a, b| {
4✔
121
                        #[allow(ambiguous_wide_pointer_comparisons)]
122
                        unsafe {
123
                            *a.get::<Self>() == *b.get::<Self>()
4✔
124
                        }
125
                    }),
4✔
126
                    partial_ord: Some(|a, b| {
4✔
127
                        #[allow(ambiguous_wide_pointer_comparisons)]
128
                        unsafe {
129
                            a.get::<Self>().partial_cmp(b.get::<Self>())
4✔
130
                        }
131
                    }),
4✔
132
                    ord: Some(|a, b| {
4✔
133
                        #[allow(ambiguous_wide_pointer_comparisons)]
134
                        unsafe {
135
                            a.get::<Self>().cmp(b.get::<Self>())
4✔
136
                        }
137
                    }),
4✔
138
                },
139
                hash: HashVTable {
140
                    hash: Some(|value, hasher| unsafe {
UNCOV
141
                        value.get::<Self>().hash(&mut { hasher })
×
UNCOV
142
                    }),
×
143
                },
144
                markers: MarkerTraits::EMPTY.with_eq().with_copy(),
145
            },
146
            ty: {
147
                let is_wide = ::core::mem::size_of::<Self>() != ::core::mem::size_of::<*const ()>();
148
                let vpt = ValuePointerType {
149
                    mutable: true,
150
                    wide: is_wide,
151
                    target: T::SHAPE,
152
                };
153

154
                Type::Pointer(PointerType::Raw(vpt))
155
            },
156
            def: Def::Scalar,
157
            type_identifier: "*mut _",
158
            type_params: &[TypeParam {
159
                name: "T",
160
                shape: T::SHAPE,
161
            }],
162
            doc: &[],
163
            attributes: &[],
164
            type_tag: None,
165
            inner: Some(T::SHAPE),
166
            proxy: None,
167
            variance: Variance::INVARIANT,
168
        }
169
    };
170
}
171

172
#[cfg(test)]
173
mod test {
174
    use core::panic::{RefUnwindSafe, UnwindSafe};
175
    use impls::impls;
176

177
    #[allow(unused)]
UNCOV
178
    const fn assert_impls_unwind_safe<T: UnwindSafe>() {}
×
179
    #[allow(unused)]
180
    const fn assert_impls_ref_unwind_safe<T: RefUnwindSafe>() {}
×
181

182
    #[allow(unused)]
UNCOV
183
    const fn ref_unwind_safe<T: RefUnwindSafe>() {
×
184
        assert_impls_unwind_safe::<&T>();
×
UNCOV
185
        assert_impls_ref_unwind_safe::<&T>();
×
186

187
        assert_impls_ref_unwind_safe::<&mut T>();
×
188

189
        assert_impls_unwind_safe::<*const T>();
×
190
        assert_impls_ref_unwind_safe::<*const T>();
×
191

UNCOV
192
        assert_impls_unwind_safe::<*mut T>();
×
UNCOV
193
        assert_impls_ref_unwind_safe::<*mut T>();
×
UNCOV
194
    }
×
195

196
    #[test]
197
    fn mut_ref_not_unwind_safe() {
1✔
198
        assert!(impls!(&mut (): !UnwindSafe));
1✔
199
    }
1✔
200
}
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