• 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

18.09
/facet-core/src/impls_ordered_float.rs
1
use crate::{
2
    Def, Facet, PtrConst, PtrMut, PtrUninit, Repr, Shape, StructType, TryBorrowInnerError,
3
    TryFromError, TryIntoInnerError, Type, UserType, Variance, field_in_type, value_vtable,
4
};
5
use ordered_float::{NotNan, OrderedFloat};
6

7
macro_rules! impl_facet_for_ordered_float_and_notnan {
8
    ($float:ty) => {
9
        unsafe impl<'a> Facet<'a> for OrderedFloat<$float> {
10
            const SHAPE: &'static Shape = &const {
11
                Shape {
12
                    id: Shape::id_of::<Self>(),
13
                    layout: Shape::layout_of::<Self>(),
14
                    vtable: {
15
                        // Define conversion functions for transparency
16
                        unsafe fn try_from<'dst>(
3✔
17
                            src_ptr: PtrConst<'_>,
3✔
18
                            src_shape: &'static Shape,
3✔
19
                            dst: PtrUninit<'dst>,
3✔
20
                        ) -> Result<PtrMut<'dst>, TryFromError> {
3✔
21
                            if src_shape == <$float as Facet>::SHAPE {
3✔
22
                                // Get the inner value and wrap as OrderedFloat
23
                                let value = unsafe { src_ptr.get::<$float>() };
2✔
24
                                let ord = OrderedFloat(*value);
2✔
25
                                Ok(unsafe { dst.put(ord) })
2✔
26
                            } else {
27
                                let inner_try_from = <$float as Facet>::SHAPE
1✔
28
                                    .vtable
1✔
29
                                    .try_from
1✔
30
                                    .ok_or(TryFromError::UnsupportedSourceShape {
1✔
31
                                        src_shape,
1✔
32
                                        expected: &[<$float as Facet>::SHAPE],
1✔
33
                                    })?;
1✔
34
                                // fallback to inner's try_from
35
                                // This relies on the fact that `dst` is the same size as `OrderedFloat<$float>`
36
                                // which should be true because `OrderedFloat` is `repr(transparent)`
37
                                let inner_result =
×
38
                                    unsafe { (inner_try_from)(src_ptr, src_shape, dst) };
×
39
                                match inner_result {
×
40
                                    Ok(result) => {
×
41
                                        // After conversion to inner type, wrap as OrderedFloat
42
                                        let value = unsafe { result.read::<$float>() };
×
43
                                        let ord = OrderedFloat(value);
×
44
                                        Ok(unsafe { dst.put(ord) })
×
45
                                    }
46
                                    Err(e) => Err(e),
×
47
                                }
48
                            }
49
                        }
3✔
50

51
                        // Conversion back to inner float type
52
                        unsafe fn try_into_inner<'dst>(
×
53
                            src_ptr: PtrMut<'_>,
×
54
                            dst: PtrUninit<'dst>,
×
55
                        ) -> Result<PtrMut<'dst>, TryIntoInnerError> {
×
56
                            let v = unsafe { src_ptr.read::<OrderedFloat<$float>>() };
×
57
                            Ok(unsafe { dst.put(v.0) })
×
58
                        }
×
59

60
                        // Borrow inner float type
61
                        unsafe fn try_borrow_inner(
×
62
                            src_ptr: PtrConst<'_>,
×
63
                        ) -> Result<PtrConst<'_>, TryBorrowInnerError> {
×
64
                            let v = unsafe { src_ptr.get::<OrderedFloat<$float>>() };
×
65
                            Ok(PtrConst::new((&v.0).into()))
×
66
                        }
×
67

68
                        let mut vtable = value_vtable!((), |f, _opts| write!(
×
69
                            f,
×
70
                            "{}",
71
                            Self::SHAPE.type_identifier
×
72
                        ));
73
                        {
74
                            vtable.parse = {
75
                                // `OrderedFloat` is `repr(transparent)`
76
                                <$float as Facet>::SHAPE.vtable.parse
77
                            };
78
                            vtable.try_from = Some(try_from);
79
                            vtable.try_into_inner = Some(try_into_inner);
80
                            vtable.try_borrow_inner = Some(try_borrow_inner);
81
                        }
82
                        vtable
83
                    },
84
                    ty: Type::User(UserType::Struct(StructType {
85
                        repr: Repr::transparent(),
86
                        fields: &const { [field_in_type!(Self, 0, $float)] },
87
                        kind: crate::StructKind::Tuple,
88
                    })),
89
                    def: Def::Scalar,
90
                    type_identifier: "OrderedFloat",
91
                    type_params: &[],
92
                    doc: &[],
93
                    attributes: &[],
94
                    type_tag: None,
95
                    inner: Some(<$float as Facet>::SHAPE),
96
                    proxy: None,
97
                    // OrderedFloat has no lifetime parameters, so it's covariant
98
                    variance: Variance::COVARIANT,
99
                }
100
            };
101
        }
102

103
        unsafe impl<'a> Facet<'a> for NotNan<$float> {
104
            const SHAPE: &'static Shape = &const {
105
                Shape {
106
                    id: Shape::id_of::<Self>(),
107
                    layout: Shape::layout_of::<Self>(),
108
                    vtable: {
109
                        // Conversion from inner float type to NotNan<$float>
110
                        unsafe fn try_from<'dst>(
×
111
                            src_ptr: PtrConst<'_>,
×
112
                            src_shape: &'static Shape,
×
113
                            dst: PtrUninit<'dst>,
×
114
                        ) -> Result<PtrMut<'dst>, TryFromError> {
×
UNCOV
115
                            if src_shape == <$float as Facet>::SHAPE {
×
116
                                // Get the inner value and check that it's not NaN
117
                                let value = unsafe { *src_ptr.get::<$float>() };
×
118
                                let nn = NotNan::new(value)
×
119
                                    .map_err(|_| TryFromError::Generic("was NaN"))?;
×
UNCOV
120
                                Ok(unsafe { dst.put(nn) })
×
121
                            } else {
122
                                let inner_try_from = <$float as Facet>::SHAPE
×
123
                                    .vtable
×
124
                                    .try_from
×
125
                                    .ok_or(TryFromError::UnsupportedSourceShape {
×
126
                                        src_shape,
×
127
                                        expected: &[<$float as Facet>::SHAPE],
×
UNCOV
128
                                    })?;
×
129

130
                                // fallback to inner's try_from
131
                                // This relies on the fact that `dst` is the same size as `NotNan<$float>`
132
                                // which should be true because `NotNan` is `repr(transparent)`
133
                                let inner_result =
×
134
                                    unsafe { (inner_try_from)(src_ptr, src_shape, dst) };
×
135
                                match inner_result {
×
UNCOV
136
                                    Ok(result) => {
×
137
                                        // After conversion to inner type, wrap as NotNan
138
                                        let value = unsafe { *result.get::<$float>() };
×
139
                                        let nn = NotNan::new(value)
×
140
                                            .map_err(|_| TryFromError::Generic("was NaN"))?;
×
UNCOV
141
                                        Ok(unsafe { dst.put(nn) })
×
142
                                    }
UNCOV
143
                                    Err(e) => Err(e),
×
144
                                }
145
                            }
UNCOV
146
                        }
×
147

148
                        // Conversion back to inner float type
149
                        unsafe fn try_into_inner<'dst>(
×
150
                            src_ptr: PtrMut<'_>,
×
151
                            dst: PtrUninit<'dst>,
×
152
                        ) -> Result<PtrMut<'dst>, TryIntoInnerError> {
×
153
                            let v = unsafe { src_ptr.read::<NotNan<$float>>() };
×
154
                            Ok(unsafe { dst.put(v.into_inner()) })
×
UNCOV
155
                        }
×
156

157
                        // Borrow inner float type
158
                        unsafe fn try_borrow_inner(
×
159
                            src_ptr: PtrConst<'_>,
×
160
                        ) -> Result<PtrConst<'_>, TryBorrowInnerError> {
×
161
                            let v = unsafe { src_ptr.get::<NotNan<$float>>() };
×
162
                            Ok(PtrConst::new((&v.into_inner()).into()))
×
UNCOV
163
                        }
×
164

165
                        let mut vtable = value_vtable!((), |f, _opts| write!(
×
UNCOV
166
                            f,
×
167
                            "{}",
UNCOV
168
                            Self::SHAPE.type_identifier
×
169
                        ));
170
                        // Accept parsing as inner T, but enforce NotNan invariant
171
                        {
172
                            vtable.parse = {
173
                                Some(|s, target| match s.parse::<$float>() {
×
174
                                    Ok(inner) => match NotNan::new(inner) {
×
175
                                        Ok(not_nan) => Ok(unsafe { target.put(not_nan) }),
×
176
                                        Err(_) => Err(crate::ParseError::Generic(
×
177
                                            "NaN is not allowed for NotNan",
×
UNCOV
178
                                        )),
×
179
                                    },
180
                                    Err(_) => Err(crate::ParseError::Generic(
×
181
                                        "Failed to parse inner type for NotNan",
×
182
                                    )),
×
UNCOV
183
                                })
×
184
                            };
185
                            vtable.try_from = Some(try_from);
186
                            vtable.try_into_inner = Some(try_into_inner);
187
                            vtable.try_borrow_inner = Some(try_borrow_inner);
188
                        }
189
                        vtable
190
                    },
191
                    ty: Type::User(UserType::Opaque),
192
                    def: Def::Scalar,
193
                    type_identifier: "NotNan",
194
                    type_params: &[],
195
                    doc: &[],
196
                    attributes: &[],
197
                    type_tag: None,
198
                    inner: Some(<$float as Facet>::SHAPE),
199
                    proxy: None,
200
                    // NotNan has no lifetime parameters, so it's covariant
201
                    variance: Variance::COVARIANT,
202
                }
203
            };
204
        }
205
    };
206
}
207

208
impl_facet_for_ordered_float_and_notnan!(f32);
209
impl_facet_for_ordered_float_and_notnan!(f64);
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