Skip to main content

facet_reflect/
spanned.rs

1//! Types for tracking source span information during deserialization.
2
3use core::{mem, ops::Deref};
4
5use facet_core::{
6    Def, Facet, FieldBuilder, Shape, StructKind, TypeOpsDirect, type_ops_direct, vtable_direct,
7};
8
9/// Source span with offset and length.
10///
11/// This type tracks a byte offset and length within a source document,
12/// useful for error reporting that can point back to the original source.
13#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
14pub struct Span {
15    /// Byte offset from start of source.
16    pub offset: usize,
17    /// Length in bytes.
18    pub len: usize,
19}
20
21impl Span {
22    /// Create a new span with the given offset and length.
23    pub const fn new(offset: usize, len: usize) -> Self {
24        Self { offset, len }
25    }
26
27    /// Check if this span is unknown (zero offset and length).
28    pub const fn is_unknown(&self) -> bool {
29        self.offset == 0 && self.len == 0
30    }
31
32    /// Get the end offset (offset + len).
33    pub const fn end(&self) -> usize {
34        self.offset + self.len
35    }
36}
37
38// SAFETY: Span is a simple struct with two usize fields, properly laid out
39unsafe impl Facet<'_> for Span {
40    const SHAPE: &'static Shape = &const {
41        static FIELDS: [facet_core::Field; 2] = [
42            FieldBuilder::new(
43                "offset",
44                facet_core::shape_of::<usize>,
45                mem::offset_of!(Span, offset),
46            )
47            .build(),
48            FieldBuilder::new(
49                "len",
50                facet_core::shape_of::<usize>,
51                mem::offset_of!(Span, len),
52            )
53            .build(),
54        ];
55
56        const VTABLE: facet_core::VTableDirect = vtable_direct!(Span => Debug, PartialEq);
57        const TYPE_OPS: TypeOpsDirect = type_ops_direct!(Span => Default, Clone);
58
59        Shape::builder_for_sized::<Span>("Span")
60            .vtable_direct(&VTABLE)
61            .type_ops_direct(&TYPE_OPS)
62            .ty(facet_core::Type::struct_builder(StructKind::Struct, &FIELDS).build())
63            .def(Def::Undefined)
64            .build()
65    };
66}
67
68/// A value with source span information.
69///
70/// This struct wraps a value along with the source location (offset and length)
71/// where it was parsed from. This is useful for error reporting that can point
72/// back to the original source.
73#[derive(Debug)]
74pub struct Spanned<T> {
75    /// The wrapped value.
76    pub value: T,
77    /// The source span (offset and length).
78    pub span: Span,
79}
80
81impl<T> Spanned<T> {
82    /// Create a new spanned value.
83    pub const fn new(value: T, span: Span) -> Self {
84        Self { value, span }
85    }
86
87    /// Get the source span.
88    pub const fn span(&self) -> Span {
89        self.span
90    }
91
92    /// Get a reference to the inner value.
93    pub const fn value(&self) -> &T {
94        &self.value
95    }
96
97    /// Unwrap into the inner value, discarding span information.
98    pub fn into_inner(self) -> T {
99        self.value
100    }
101}
102
103impl<T> Deref for Spanned<T> {
104    type Target = T;
105    fn deref(&self) -> &Self::Target {
106        &self.value
107    }
108}
109
110impl<T: Default> Default for Spanned<T> {
111    fn default() -> Self {
112        Self {
113            value: T::default(),
114            span: Span::default(),
115        }
116    }
117}
118
119impl<T: Clone> Clone for Spanned<T> {
120    fn clone(&self) -> Self {
121        Self {
122            value: self.value.clone(),
123            span: self.span,
124        }
125    }
126}
127
128impl<T: PartialEq> PartialEq for Spanned<T> {
129    fn eq(&self, other: &Self) -> bool {
130        // Only compare the value, not the span
131        self.value == other.value
132    }
133}
134
135impl<T: Eq> Eq for Spanned<T> {}
136
137// SAFETY: Spanned<T> is a simple struct with a value and span field, properly laid out
138unsafe impl<'a, T: Facet<'a>> Facet<'a> for Spanned<T> {
139    const SHAPE: &'static Shape = &const {
140        use facet_core::{TypeOpsIndirect, TypeParam, VTableIndirect};
141
142        unsafe fn drop_in_place<T>(ox: facet_core::OxPtrMut) {
143            // SAFETY: The caller guarantees ox points to a valid Spanned<T>
144            unsafe { core::ptr::drop_in_place(ox.ptr().as_byte_ptr() as *mut Spanned<T>) };
145        }
146
147        Shape::builder_for_sized::<Spanned<T>>("Spanned")
148            .module_path("facet_reflect::spanned")
149            .vtable_indirect(&VTableIndirect::EMPTY)
150            .type_ops_indirect(
151                &const {
152                    TypeOpsIndirect {
153                        drop_in_place: drop_in_place::<T>,
154                        default_in_place: None,
155                        clone_into: None,
156                        is_truthy: None,
157                    }
158                },
159            )
160            .type_params(
161                &const {
162                    [TypeParam {
163                        name: "T",
164                        shape: T::SHAPE,
165                    }]
166                },
167            )
168            .ty(facet_core::Type::struct_builder(
169                StructKind::Struct,
170                &const {
171                    [
172                        FieldBuilder::new(
173                            "value",
174                            facet_core::shape_of::<T>,
175                            mem::offset_of!(Spanned<T>, value),
176                        )
177                        .build(),
178                        FieldBuilder::new(
179                            "span",
180                            facet_core::shape_of::<Span>,
181                            mem::offset_of!(Spanned<T>, span),
182                        )
183                        // Mark span as metadata - excluded from structural hashing/equality
184                        // Deserializers that support span metadata will populate this field
185                        .metadata("span")
186                        .build(),
187                    ]
188                },
189            )
190            .build())
191            .def(Def::Undefined)
192            .type_name(|_shape, f, opts| {
193                write!(f, "Spanned")?;
194                if let Some(opts) = opts.for_children() {
195                    write!(f, "<")?;
196                    if let Some(type_name_fn) = T::SHAPE.type_name {
197                        type_name_fn(T::SHAPE, f, opts)?;
198                    } else {
199                        write!(f, "{}", T::SHAPE.type_identifier)?;
200                    }
201                    write!(f, ">")?;
202                } else {
203                    write!(f, "<…>")?;
204                }
205                Ok(())
206            })
207            .build()
208    };
209}
210
211/// Check if a shape represents a type with span metadata (like `Spanned<T>`).
212///
213/// Returns `true` if the shape is a struct with:
214/// - At least one non-metadata field (the actual value)
215/// - A field with `#[facet(metadata = span)]` for storing source location
216///
217/// This allows any struct to be "spanned" by adding the metadata attribute,
218/// not just the built-in `Spanned<T>` wrapper.
219pub fn is_spanned_shape(shape: &Shape) -> bool {
220    use facet_core::{Type, UserType};
221
222    if let Type::User(UserType::Struct(struct_def)) = &shape.ty {
223        let has_span_metadata = struct_def
224            .fields
225            .iter()
226            .any(|f| f.metadata_kind() == Some("span"));
227        let has_value_field = struct_def.fields.iter().any(|f| !f.is_metadata());
228        return has_span_metadata && has_value_field;
229    }
230    false
231}
232
233/// Find the span metadata field in a struct shape.
234///
235/// Returns the field with `#[facet(metadata = span)]` if present.
236pub fn find_span_metadata_field(shape: &Shape) -> Option<&'static facet_core::Field> {
237    use facet_core::{Type, UserType};
238
239    if let Type::User(UserType::Struct(struct_def)) = &shape.ty {
240        return struct_def
241            .fields
242            .iter()
243            .find(|f| f.metadata_kind() == Some("span"));
244    }
245    None
246}
247
248/// Extract the inner value shape from a Spanned-like struct.
249///
250/// For a struct with span metadata, this returns the shape of the first
251/// non-metadata field (typically the `value` field in `Spanned<T>`).
252///
253/// This is useful when you need to look through a Spanned wrapper to
254/// determine the actual type being wrapped, such as when matching
255/// untagged enum variants against scalar values.
256///
257/// Returns `None` if the shape is not spanned or has no value fields.
258pub fn get_spanned_inner_shape(shape: &Shape) -> Option<&'static Shape> {
259    use facet_core::{Type, UserType};
260
261    if !is_spanned_shape(shape) {
262        return None;
263    }
264
265    if let Type::User(UserType::Struct(struct_def)) = &shape.ty {
266        // Find the first non-metadata field (the actual value)
267        struct_def
268            .fields
269            .iter()
270            .find(|f| !f.is_metadata())
271            .map(|f| f.shape.get())
272    } else {
273        None
274    }
275}