ruka_types/
ownership.rs

1use std::fmt;
2
3use crate::Ty;
4
5/// Access intent associated with one normalized type at a usage boundary.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
7pub enum AccessMode {
8    /// Read-only view access.
9    View,
10    /// Mutable borrow access.
11    MutBorrow,
12    /// Owned value access.
13    Owned,
14}
15
16/// Pure type identity without ownership or mutability semantics.
17#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub enum BaseTy {
19    /// The unit type.
20    Unit,
21    /// Unsigned 8-bit integer type.
22    U8,
23    /// Unsigned 16-bit integer type.
24    U16,
25    /// Unsigned 32-bit integer type.
26    U32,
27    /// Unsigned 64-bit integer type.
28    U64,
29    /// Signed 8-bit integer type.
30    I8,
31    /// Signed 16-bit integer type.
32    I16,
33    /// Signed 32-bit integer type.
34    I32,
35    /// Signed 64-bit integer type.
36    I64,
37    /// 32-bit floating-point type.
38    F32,
39    /// 64-bit floating-point type.
40    F64,
41    /// The string value type.
42    String,
43    /// The boolean type.
44    Bool,
45    /// Nullable owned pointer to another type.
46    Pointer(Box<BaseTy>),
47    /// Optional value.
48    Option(Box<BaseTy>),
49    /// Fixed-size owned array of items.
50    StaticArray {
51        /// Item type stored in the array.
52        item: Box<BaseTy>,
53        /// Number of items in the array.
54        len: usize,
55    },
56    /// Runtime-sized owned array of items.
57    DynamicArray(Box<BaseTy>),
58    /// Runtime-sized non-owning slice view.
59    Slice(Box<BaseTy>),
60    /// Fixed-extent non-owning slice view.
61    StaticSlice {
62        /// Item type visible through the view.
63        item: Box<BaseTy>,
64        /// Number of items visible through the view.
65        len: usize,
66    },
67    /// Tuple of positional elements.
68    Tuple(Vec<BaseTy>),
69    /// Struct type with optional concrete type arguments.
70    Struct {
71        /// Struct constructor name.
72        name: String,
73        /// Concrete type arguments applied to the struct.
74        args: Vec<BaseTy>,
75    },
76    /// Enum type with optional concrete type arguments.
77    Enum {
78        /// Enum constructor name.
79        name: String,
80        /// Concrete type arguments applied to the enum.
81        args: Vec<BaseTy>,
82    },
83}
84
85/// Normalized ownership representation used by checker and lowering boundaries.
86#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
87pub struct NormalizedTy {
88    /// Base type identity.
89    pub base: BaseTy,
90    /// Access intent for the base type.
91    pub access: AccessMode,
92}
93
94/// Error returned when converting semantic type shapes into normalized forms.
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub enum NormalizeError {
97    /// Type contained ownership-encoded references in nested positions.
98    NestedReference,
99}
100
101impl fmt::Display for NormalizeError {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        match self {
104            Self::NestedReference => write!(f, "nested RefRo/RefMut is unsupported"),
105        }
106    }
107}
108
109impl std::error::Error for NormalizeError {}
110
111impl BaseTy {
112    /// Convert one base type back into semantic type form.
113    pub fn into_ty(self) -> Ty {
114        match self {
115            Self::Unit => Ty::Unit,
116            Self::U8 => Ty::U8,
117            Self::U16 => Ty::U16,
118            Self::U32 => Ty::U32,
119            Self::U64 => Ty::U64,
120            Self::I8 => Ty::I8,
121            Self::I16 => Ty::I16,
122            Self::I32 => Ty::I32,
123            Self::I64 => Ty::I64,
124            Self::F32 => Ty::F32,
125            Self::F64 => Ty::F64,
126            Self::String => Ty::String,
127            Self::Bool => Ty::Bool,
128            Self::Pointer(item) => Ty::Pointer(Box::new(item.into_ty())),
129            Self::Option(item) => Ty::Option(Box::new(item.into_ty())),
130            Self::StaticArray { item, len } => Ty::Array {
131                item: Box::new(item.into_ty()),
132                len,
133            },
134            Self::DynamicArray(item) => Ty::Slice(Box::new(item.into_ty())),
135            Self::Slice(item) => Ty::Slice(Box::new(item.into_ty())),
136            Self::StaticSlice { item, len } => Ty::Array {
137                item: Box::new(item.into_ty()),
138                len,
139            },
140            Self::Tuple(items) => {
141                Ty::Tuple(items.into_iter().map(BaseTy::into_ty).collect::<Vec<_>>())
142            }
143            Self::Struct { name, args } => Ty::Struct {
144                name,
145                args: args.into_iter().map(BaseTy::into_ty).collect::<Vec<_>>(),
146            },
147            Self::Enum { name, args } => Ty::Enum {
148                name,
149                args: args.into_iter().map(BaseTy::into_ty).collect::<Vec<_>>(),
150            },
151        }
152    }
153}
154
155impl NormalizedTy {
156    /// Convert one normalized type back into semantic type form.
157    pub fn into_ty(self) -> Ty {
158        let base_ty = self.base.into_ty();
159        match self.access {
160            AccessMode::View => Ty::RefRo(Box::new(base_ty)),
161            AccessMode::MutBorrow => Ty::RefMut(Box::new(base_ty)),
162            AccessMode::Owned => base_ty,
163        }
164    }
165}
166
167/// Normalize one type by extracting top-level reference ownership.
168pub fn normalize_ty(ty: &Ty) -> Result<NormalizedTy, NormalizeError> {
169    match ty {
170        Ty::RefRo(inner) => normalize_ty_with_access(inner, AccessMode::View),
171        Ty::RefMut(inner) => normalize_ty_with_access(inner, AccessMode::MutBorrow),
172        _ => normalize_ty_with_access(ty, AccessMode::Owned),
173    }
174}
175
176/// Normalize one type using one explicit boundary access mode.
177pub fn normalize_ty_with_access(
178    ty: &Ty,
179    access: AccessMode,
180) -> Result<NormalizedTy, NormalizeError> {
181    let base = normalize_base_ty(ty)?;
182    Ok(NormalizedTy {
183        base: boundary_base_for_access(base, access),
184        access,
185    })
186}
187
188/// Convert one type into base type identity without access semantics.
189fn normalize_base_ty(ty: &Ty) -> Result<BaseTy, NormalizeError> {
190    match ty {
191        Ty::Unit => Ok(BaseTy::Unit),
192        Ty::U8 => Ok(BaseTy::U8),
193        Ty::U16 => Ok(BaseTy::U16),
194        Ty::U32 => Ok(BaseTy::U32),
195        Ty::U64 => Ok(BaseTy::U64),
196        Ty::I8 => Ok(BaseTy::I8),
197        Ty::I16 => Ok(BaseTy::I16),
198        Ty::I32 => Ok(BaseTy::I32),
199        Ty::I64 => Ok(BaseTy::I64),
200        Ty::F32 => Ok(BaseTy::F32),
201        Ty::F64 => Ok(BaseTy::F64),
202        Ty::String => Ok(BaseTy::String),
203        Ty::Bool => Ok(BaseTy::Bool),
204        Ty::Pointer(item) => Ok(BaseTy::Pointer(Box::new(normalize_base_ty(item)?))),
205        Ty::Option(item) => Ok(BaseTy::Option(Box::new(normalize_base_ty(item)?))),
206        Ty::Array { item, len } => Ok(BaseTy::StaticArray {
207            item: Box::new(normalize_base_ty(item)?),
208            len: *len,
209        }),
210        Ty::Slice(item) => Ok(BaseTy::DynamicArray(Box::new(normalize_base_ty(item)?))),
211        Ty::Tuple(items) => Ok(BaseTy::Tuple(
212            items
213                .iter()
214                .map(normalize_base_ty)
215                .collect::<Result<Vec<_>, _>>()?,
216        )),
217        Ty::Struct { name, args } => Ok(BaseTy::Struct {
218            name: name.clone(),
219            args: args
220                .iter()
221                .map(normalize_base_ty)
222                .collect::<Result<Vec<_>, _>>()?,
223        }),
224        Ty::Enum { name, args } => Ok(BaseTy::Enum {
225            name: name.clone(),
226            args: args
227                .iter()
228                .map(normalize_base_ty)
229                .collect::<Result<Vec<_>, _>>()?,
230        }),
231        Ty::RefRo(_) | Ty::RefMut(_) => Err(NormalizeError::NestedReference),
232    }
233}
234
235/// Apply boundary access semantics to one top-level base type.
236fn boundary_base_for_access(base: BaseTy, access: AccessMode) -> BaseTy {
237    match (base, access) {
238        (BaseTy::StaticArray { item, len }, AccessMode::View | AccessMode::MutBorrow) => {
239            BaseTy::StaticSlice { item, len }
240        }
241        (BaseTy::DynamicArray(item), AccessMode::View | AccessMode::MutBorrow) => {
242            BaseTy::Slice(item)
243        }
244        (base, _) => base,
245    }
246}
247
248impl fmt::Display for BaseTy {
249    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250        match self {
251            BaseTy::Unit => write!(f, "Unit"),
252            BaseTy::U8 => write!(f, "u8"),
253            BaseTy::U16 => write!(f, "u16"),
254            BaseTy::U32 => write!(f, "u32"),
255            BaseTy::U64 => write!(f, "u64"),
256            BaseTy::I8 => write!(f, "i8"),
257            BaseTy::I16 => write!(f, "i16"),
258            BaseTy::I32 => write!(f, "i32"),
259            BaseTy::I64 => write!(f, "i64"),
260            BaseTy::F32 => write!(f, "f32"),
261            BaseTy::F64 => write!(f, "f64"),
262            BaseTy::String => write!(f, "String"),
263            BaseTy::Bool => write!(f, "Bool"),
264            BaseTy::Pointer(item) => write!(f, "*{item}"),
265            BaseTy::Option(item) => write!(f, "Option[{item}]"),
266            BaseTy::StaticArray { item, len } => write!(f, "StaticArray[{item}; {len}]"),
267            BaseTy::DynamicArray(item) => write!(f, "DynamicArray[{item}]"),
268            BaseTy::Slice(item) => write!(f, "Slice[{item}]"),
269            BaseTy::StaticSlice { item, len } => write!(f, "StaticSlice[{item}; {len}]"),
270            BaseTy::Tuple(items) => {
271                write!(f, "(")?;
272                for (index, item) in items.iter().enumerate() {
273                    if index > 0 {
274                        write!(f, ", ")?;
275                    }
276                    write!(f, "{item}")?;
277                }
278                if items.len() == 1 {
279                    write!(f, ",")?;
280                }
281                write!(f, ")")
282            }
283            BaseTy::Struct { name, args } | BaseTy::Enum { name, args } => {
284                if args.is_empty() {
285                    write!(f, "{name}")
286                } else {
287                    let rendered = args
288                        .iter()
289                        .map(ToString::to_string)
290                        .collect::<Vec<_>>()
291                        .join(", ");
292                    write!(f, "{name}[{rendered}]")
293                }
294            }
295        }
296    }
297}
298
299impl fmt::Display for NormalizedTy {
300    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301        match self.access {
302            AccessMode::View => write!(f, "view {base}", base = self.base),
303            AccessMode::MutBorrow => write!(f, "mut-borrow {base}", base = self.base),
304            AccessMode::Owned => write!(f, "owned {base}", base = self.base),
305        }
306    }
307}
308
309#[cfg(test)]
310mod tests {
311    use super::*;
312
313    #[test]
314    fn normalize_owned_scalar() {
315        let normalized = normalize_ty(&Ty::I64).expect("owned scalar should normalize");
316        assert_eq!(normalized.access, AccessMode::Owned);
317        assert_eq!(normalized.base, BaseTy::I64);
318    }
319
320    #[test]
321    fn normalize_view_reference() {
322        let normalized = normalize_ty(&Ty::RefRo(Box::new(Ty::Slice(Box::new(Ty::I64)))))
323            .expect("view ref should normalize");
324        assert_eq!(normalized.access, AccessMode::View);
325        assert_eq!(normalized.base, BaseTy::Slice(Box::new(BaseTy::I64)));
326    }
327
328    #[test]
329    fn normalize_mut_borrow_reference() {
330        let normalized = normalize_ty(&Ty::RefMut(Box::new(Ty::Array {
331            item: Box::new(Ty::Bool),
332            len: 4,
333        })))
334        .expect("mutable ref should normalize");
335        assert_eq!(normalized.access, AccessMode::MutBorrow);
336        assert_eq!(
337            normalized.base,
338            BaseTy::StaticSlice {
339                item: Box::new(BaseTy::Bool),
340                len: 4,
341            }
342        );
343    }
344
345    #[test]
346    fn reject_nested_reference() {
347        let err = normalize_ty(&Ty::RefRo(Box::new(Ty::RefMut(Box::new(Ty::I64)))))
348            .expect_err("nested refs should be rejected");
349        assert_eq!(err, NormalizeError::NestedReference);
350    }
351
352    #[test]
353    fn round_trip_ty_conversion() {
354        let source = Ty::RefMut(Box::new(Ty::Tuple(vec![Ty::I64, Ty::Bool])));
355        let normalized = normalize_ty(&source).expect("source type should normalize");
356        let restored = normalized.into_ty();
357        assert_eq!(restored, source);
358    }
359}