Skip to main content

sim_lib_class/
descriptor.rs

1//! Checked, loader-neutral declarations for runtime classes.
2
3use std::{collections::BTreeSet, error::Error, fmt};
4
5use sim_kernel::{ClassId, ClassRef, ReadConstructorRef, Ref, ShapeRef, Symbol, Value};
6
7/// Stable identity carried by a class declaration or parent reference.
8#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct ClassIdentity {
10    id: ClassId,
11    symbol: Symbol,
12}
13
14impl ClassIdentity {
15    /// Checks a numeric identity and its canonical (not merely display) name.
16    pub fn checked(id: ClassId, symbol: Symbol) -> Result<Self, ClassDescriptorError> {
17        validate_symbol(&symbol).map_err(|reason| ClassDescriptorError::InvalidIdentity {
18            name: symbol.clone(),
19            reason,
20        })?;
21        Ok(Self { id, symbol })
22    }
23
24    pub fn id(&self) -> ClassId {
25        self.id
26    }
27    pub fn symbol(&self) -> &Symbol {
28        &self.symbol
29    }
30}
31
32/// A declared parent, explicitly separated by resolution state.
33#[derive(Clone, Debug)]
34pub enum DeclaredParent {
35    /// The loader has supplied a concrete class object.
36    Resolved {
37        identity: ClassIdentity,
38        class: ClassRef,
39    },
40    /// Resolution remains loader-owned; the reference is preserved verbatim.
41    Unresolved {
42        identity: ClassIdentity,
43        reference: Ref,
44    },
45}
46
47impl DeclaredParent {
48    pub fn resolved(identity: ClassIdentity, class: ClassRef) -> Self {
49        Self::Resolved { identity, class }
50    }
51    pub fn unresolved(identity: ClassIdentity, reference: Ref) -> Self {
52        Self::Unresolved {
53            identity,
54            reference,
55        }
56    }
57    pub fn identity(&self) -> &ClassIdentity {
58        match self {
59            Self::Resolved { identity, .. } | Self::Unresolved { identity, .. } => identity,
60        }
61    }
62    pub fn unresolved_reference(&self) -> Option<&Ref> {
63        match self {
64            Self::Unresolved { reference, .. } => Some(reference),
65            Self::Resolved { .. } => None,
66        }
67    }
68    pub fn resolved_class(&self) -> Option<&ClassRef> {
69        match self {
70            Self::Resolved { class, .. } => Some(class),
71            Self::Unresolved { .. } => None,
72        }
73    }
74}
75
76/// One named member and the Shape promised for its value.
77#[derive(Clone, Debug)]
78pub struct MemberShape {
79    pub name: Symbol,
80    pub shape: ShapeRef,
81}
82
83/// Open metadata: unknown keys are retained rather than interpreted here.
84#[derive(Clone, Debug)]
85pub struct OpenMetadataEntry {
86    pub name: Symbol,
87    pub value: Value,
88}
89
90/// Checked read-construct metadata.
91#[derive(Clone, Debug)]
92pub struct ReadConstruction {
93    pub constructor: ReadConstructorRef,
94    pub args_shape: ShapeRef,
95}
96
97/// Unchecked input consumed exactly once by [`ClassDescriptor::new`].
98#[derive(Clone, Debug)]
99pub struct ClassDescriptorInput {
100    pub identity: ClassIdentity,
101    pub parents: Vec<DeclaredParent>,
102    pub constructor_shape: ShapeRef,
103    pub instance_shape: ShapeRef,
104    pub members: Vec<MemberShape>,
105    pub read_construction: Option<ReadConstruction>,
106    pub metadata: Vec<OpenMetadataEntry>,
107}
108
109/// Immutable class metadata after construction-time validation.
110#[derive(Clone, Debug)]
111pub struct ClassDescriptor {
112    input: ClassDescriptorInput,
113}
114
115impl ClassDescriptor {
116    pub fn new(input: ClassDescriptorInput) -> Result<Self, ClassDescriptorError> {
117        validate_shape(Symbol::new("constructor"), &input.constructor_shape)?;
118        validate_shape(Symbol::new("instance"), &input.instance_shape)?;
119
120        let mut parents = BTreeSet::new();
121        for parent in &input.parents {
122            let identity = parent.identity();
123            if identity.id == input.identity.id {
124                return Err(ClassDescriptorError::SelfParent {
125                    name: identity.symbol.clone(),
126                });
127            }
128            if !parents.insert(identity.id) {
129                return Err(ClassDescriptorError::DuplicateParent {
130                    name: identity.symbol.clone(),
131                });
132            }
133            if let DeclaredParent::Resolved { class, .. } = parent {
134                let Some(actual) = class.object().as_class() else {
135                    return Err(ClassDescriptorError::InvalidParent {
136                        name: identity.symbol.clone(),
137                        reason: "resolved value is not a class",
138                    });
139                };
140                if actual.id() != identity.id || actual.symbol() != identity.symbol {
141                    return Err(ClassDescriptorError::InvalidParent {
142                        name: identity.symbol.clone(),
143                        reason: "resolved class identity does not match its declaration",
144                    });
145                }
146            }
147        }
148
149        let mut members = BTreeSet::new();
150        for member in &input.members {
151            validate_symbol(&member.name).map_err(|reason| {
152                ClassDescriptorError::InvalidMember {
153                    name: member.name.clone(),
154                    reason,
155                }
156            })?;
157            if !members.insert(member.name.clone()) {
158                return Err(ClassDescriptorError::DuplicateMember {
159                    name: member.name.clone(),
160                });
161            }
162            validate_shape(member.name.clone(), &member.shape)?;
163        }
164
165        if let Some(read) = &input.read_construction {
166            if read.constructor.object().as_read_constructor().is_none() {
167                return Err(ClassDescriptorError::InvalidReadConstructor);
168            }
169            validate_shape(Symbol::new("read-constructor"), &read.args_shape)?;
170        }
171
172        let mut metadata = BTreeSet::new();
173        for entry in &input.metadata {
174            validate_symbol(&entry.name).map_err(|reason| {
175                ClassDescriptorError::InvalidMetadata {
176                    name: entry.name.clone(),
177                    reason,
178                }
179            })?;
180            if !metadata.insert(entry.name.clone()) {
181                return Err(ClassDescriptorError::DuplicateMetadata {
182                    name: entry.name.clone(),
183                });
184            }
185        }
186        Ok(Self { input })
187    }
188
189    pub fn identity(&self) -> &ClassIdentity {
190        &self.input.identity
191    }
192    pub fn parents(&self) -> &[DeclaredParent] {
193        &self.input.parents
194    }
195    pub fn constructor_shape(&self) -> &ShapeRef {
196        &self.input.constructor_shape
197    }
198    pub fn instance_shape(&self) -> &ShapeRef {
199        &self.input.instance_shape
200    }
201    pub fn members(&self) -> &[MemberShape] {
202        &self.input.members
203    }
204    pub fn read_construction(&self) -> Option<&ReadConstruction> {
205        self.input.read_construction.as_ref()
206    }
207    pub fn metadata(&self) -> &[OpenMetadataEntry] {
208        &self.input.metadata
209    }
210}
211
212fn validate_shape(name: Symbol, shape: &ShapeRef) -> Result<(), ClassDescriptorError> {
213    if shape.object().as_shape().is_none() {
214        return Err(ClassDescriptorError::MalformedShape { name });
215    }
216    Ok(())
217}
218
219fn validate_symbol(symbol: &Symbol) -> Result<(), &'static str> {
220    if symbol.name.is_empty() {
221        return Err("name is empty");
222    }
223    Symbol::checked(symbol.name.clone()).map_err(|_| "name is malformed")?;
224    if let Some(namespace) = &symbol.namespace {
225        if namespace.is_empty() {
226            return Err("namespace is empty");
227        }
228        Symbol::checked(namespace.clone()).map_err(|_| "namespace is malformed")?;
229    }
230    Ok(())
231}
232
233/// Exact construction failure, retaining the offending declaration name.
234#[derive(Clone, Debug, PartialEq, Eq)]
235pub enum ClassDescriptorError {
236    InvalidIdentity { name: Symbol, reason: &'static str },
237    InvalidParent { name: Symbol, reason: &'static str },
238    DuplicateParent { name: Symbol },
239    SelfParent { name: Symbol },
240    InvalidMember { name: Symbol, reason: &'static str },
241    DuplicateMember { name: Symbol },
242    MalformedShape { name: Symbol },
243    InvalidReadConstructor,
244    InvalidMetadata { name: Symbol, reason: &'static str },
245    DuplicateMetadata { name: Symbol },
246}
247
248impl fmt::Display for ClassDescriptorError {
249    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250        write!(f, "{self:?}")
251    }
252}
253impl Error for ClassDescriptorError {}
254
255#[cfg(test)]
256mod tests {
257    use std::sync::Arc;
258
259    use sim_kernel::{Cx, Expr, MatchScore, Shape, ShapeDoc, ShapeMatch};
260
261    use super::*;
262
263    struct AnyShape;
264    impl Shape for AnyShape {
265        fn check_value(&self, _cx: &mut Cx, _value: Value) -> sim_kernel::Result<ShapeMatch> {
266            Ok(ShapeMatch::accept(MatchScore::exact(1)))
267        }
268        fn check_expr(&self, _cx: &mut Cx, _expr: &Expr) -> sim_kernel::Result<ShapeMatch> {
269            Ok(ShapeMatch::accept(MatchScore::exact(1)))
270        }
271        fn describe(&self, _cx: &mut Cx) -> sim_kernel::Result<ShapeDoc> {
272            Ok(ShapeDoc::new("any"))
273        }
274    }
275
276    fn shape(cx: &Cx) -> ShapeRef {
277        cx.factory().opaque(Arc::new(AnyShape)).unwrap()
278    }
279
280    fn identity(id: u32, name: &str) -> ClassIdentity {
281        ClassIdentity::checked(ClassId(id), Symbol::qualified("test", name)).unwrap()
282    }
283
284    fn input(cx: &Cx) -> ClassDescriptorInput {
285        ClassDescriptorInput {
286            identity: identity(40, "Child"),
287            parents: Vec::new(),
288            constructor_shape: shape(cx),
289            instance_shape: shape(cx),
290            members: Vec::new(),
291            read_construction: None,
292            metadata: Vec::new(),
293        }
294    }
295
296    #[test]
297    fn unresolved_parent_is_preserved_as_unresolved_typed_input() {
298        let cx = sim_kernel::testing::bare_cx();
299        let unresolved = Ref::Symbol(Symbol::qualified("loader", "Parent"));
300        let mut raw = input(&cx);
301        raw.parents.push(DeclaredParent::unresolved(
302            identity(41, "Parent"),
303            unresolved.clone(),
304        ));
305
306        let descriptor = ClassDescriptor::new(raw).unwrap();
307        assert_eq!(descriptor.parents().len(), 1);
308        assert_eq!(
309            descriptor.parents()[0].unresolved_reference(),
310            Some(&unresolved)
311        );
312        assert!(descriptor.parents()[0].resolved_class().is_none());
313    }
314
315    #[test]
316    fn duplicate_member_reports_the_offending_name() {
317        let cx = sim_kernel::testing::bare_cx();
318        let mut raw = input(&cx);
319        let name = Symbol::new("answer");
320        raw.members.push(MemberShape {
321            name: name.clone(),
322            shape: shape(&cx),
323        });
324        raw.members.push(MemberShape {
325            name: name.clone(),
326            shape: shape(&cx),
327        });
328        assert_eq!(
329            ClassDescriptor::new(raw).unwrap_err(),
330            ClassDescriptorError::DuplicateMember { name }
331        );
332    }
333
334    #[test]
335    fn malformed_member_shape_reports_the_offending_name() {
336        let cx = sim_kernel::testing::bare_cx();
337        let mut raw = input(&cx);
338        let name = Symbol::new("broken");
339        raw.members.push(MemberShape {
340            name: name.clone(),
341            shape: cx.factory().nil().unwrap(),
342        });
343        assert_eq!(
344            ClassDescriptor::new(raw).unwrap_err(),
345            ClassDescriptorError::MalformedShape { name }
346        );
347    }
348
349    #[test]
350    fn self_parent_reports_the_declared_parent_name() {
351        let cx = sim_kernel::testing::bare_cx();
352        let mut raw = input(&cx);
353        let own_identity = raw.identity.clone();
354        let name = own_identity.symbol().clone();
355        raw.parents.push(DeclaredParent::unresolved(
356            own_identity,
357            Ref::Symbol(name.clone()),
358        ));
359        assert_eq!(
360            ClassDescriptor::new(raw).unwrap_err(),
361            ClassDescriptorError::SelfParent { name }
362        );
363    }
364}