Skip to main content

meerkat_machine_schema/
identity.rs

1//! Slug-validated identifier newtypes for the machine schema layer.
2//!
3//! Every identity in the machine kernel vocabulary — machine names, phase names,
4//! variant names, field names, transitions, routes, protocols, actors, enum type
5//! names and variants, and composition names — is represented here as a distinct
6//! newtype wrapping a validated ASCII slug. This closes the first dogma gap in
7//! wave (b): kernel identities stop being bare `String` and become typed, so that
8//! the compiler rejects field/phase/variant cross-contamination at the boundary
9//! instead of the runtime.
10//!
11//! Validation rules (identical for every identity type):
12//! - non-empty
13//! - first character: ASCII alphabetic or `_`
14//! - subsequent characters: ASCII alphanumeric, `_`, or `-`
15//!
16//! Anything else — spaces, dots, slashes, control characters, non-ASCII — is
17//! rejected at construction time with a structured [`IdentityError`].
18
19use serde::{Deserialize, Deserializer, Serialize, Serializer};
20use std::fmt;
21use thiserror::Error;
22
23/// Why an identity string failed validation.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum IdentityErrorKind {
26    /// The input string was empty.
27    Empty,
28    /// The first character was not ASCII alpha or underscore.
29    InvalidStartChar(char),
30    /// A later character was not ASCII alphanumeric, underscore, or hyphen.
31    InvalidChar { ch: char, position: usize },
32}
33
34/// Structured error returned by every identity `parse` constructor.
35#[derive(Debug, Clone, PartialEq, Eq, Error)]
36pub struct IdentityError {
37    pub kind: IdentityErrorKind,
38    pub raw: String,
39}
40
41impl fmt::Display for IdentityError {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        match &self.kind {
44            IdentityErrorKind::Empty => {
45                write!(f, "identity must not be empty")
46            }
47            IdentityErrorKind::InvalidStartChar(ch) => {
48                write!(
49                    f,
50                    "identity {:?} must start with ASCII letter or underscore, found {:?}",
51                    self.raw, ch
52                )
53            }
54            IdentityErrorKind::InvalidChar { ch, position } => {
55                write!(
56                    f,
57                    "identity {:?} contains invalid character {:?} at position {}",
58                    self.raw, ch, position
59                )
60            }
61        }
62    }
63}
64
65fn validate_slug(raw: &str) -> Result<(), IdentityErrorKind> {
66    let mut chars = raw.chars().enumerate();
67    let (_, first) = chars.next().ok_or(IdentityErrorKind::Empty)?;
68    if !(first.is_ascii_alphabetic() || first == '_') {
69        return Err(IdentityErrorKind::InvalidStartChar(first));
70    }
71    for (pos, ch) in chars {
72        if !(ch.is_ascii_alphanumeric() || ch == '_' || ch == '-') {
73            return Err(IdentityErrorKind::InvalidChar { ch, position: pos });
74        }
75    }
76    Ok(())
77}
78
79macro_rules! define_identity {
80    ($(#[$attr:meta])* $name:ident) => {
81        $(#[$attr])*
82        #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
83        pub struct $name(String);
84
85        impl $name {
86            /// Parse a slug, returning a structured error on violation.
87            pub fn parse(value: impl Into<String>) -> Result<Self, IdentityError> {
88                let raw = value.into();
89                match validate_slug(&raw) {
90                    Ok(()) => Ok(Self(raw)),
91                    Err(kind) => Err(IdentityError { kind, raw }),
92                }
93            }
94
95            /// Borrow the underlying validated slug.
96            pub fn as_str(&self) -> &str {
97                &self.0
98            }
99        }
100
101        impl AsRef<str> for $name {
102            fn as_ref(&self) -> &str {
103                &self.0
104            }
105        }
106
107        impl fmt::Display for $name {
108            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109                f.write_str(&self.0)
110            }
111        }
112
113        impl Serialize for $name {
114            fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
115                serializer.serialize_str(&self.0)
116            }
117        }
118
119        impl<'de> Deserialize<'de> for $name {
120            fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
121                let raw = String::deserialize(deserializer)?;
122                Self::parse(raw).map_err(serde::de::Error::custom)
123            }
124        }
125    };
126}
127
128define_identity!(
129    /// Name of a declared machine (e.g. `"MobMachine"`).
130    MachineId
131);
132define_identity!(
133    /// Instance id of a machine within a composition (e.g. `"mob"`).
134    MachineInstanceId
135);
136define_identity!(
137    /// Phase name within a machine.
138    PhaseId
139);
140define_identity!(
141    /// Input-variant name.
142    InputVariantId
143);
144
145impl InputVariantId {
146    /// Construct from a crate-owned catalog literal.
147    ///
148    /// This is intentionally crate-private: schema catalog metadata can use
149    /// typed identities without fallible runtime parsing, while external
150    /// callers still go through [`Self::parse`].
151    pub(crate) fn from_trusted_catalog_literal(value: &'static str) -> Self {
152        Self(value.to_owned())
153    }
154}
155define_identity!(
156    /// Signal-variant name.
157    SignalVariantId
158);
159
160impl SignalVariantId {
161    /// Construct from a crate-owned catalog literal.
162    pub(crate) fn from_trusted_catalog_literal(value: &'static str) -> Self {
163        Self(value.to_owned())
164    }
165}
166define_identity!(
167    /// Effect-variant name.
168    EffectVariantId
169);
170
171impl EffectVariantId {
172    /// Construct from a crate-owned catalog literal.
173    pub(crate) fn from_trusted_catalog_literal(value: &'static str) -> Self {
174        Self(value.to_owned())
175    }
176}
177define_identity!(
178    /// Field name within a kernel state, input, signal, or effect.
179    FieldId
180);
181define_identity!(
182    /// Transition name.
183    TransitionId
184);
185
186impl TransitionId {
187    /// Construct from a crate-owned catalog literal.
188    pub(crate) fn from_trusted_catalog_literal(value: &'static str) -> Self {
189        Self(value.to_owned())
190    }
191
192    /// Construct from a crate-owned catalog string assembled from trusted
193    /// literals.
194    pub(crate) fn from_trusted_catalog_string(value: String) -> Self {
195        Self(value)
196    }
197}
198define_identity!(
199    /// Route name within a composition.
200    RouteId
201);
202define_identity!(
203    /// Protocol name (e.g. for effect handoff).
204    ProtocolId
205);
206define_identity!(
207    /// Actor name within a composition.
208    ActorId
209);
210define_identity!(
211    /// Named type alias declared in the DSL.
212    NamedTypeId
213);
214define_identity!(
215    /// Enum type declared in the DSL.
216    EnumTypeId
217);
218define_identity!(
219    /// Variant name inside an enum type.
220    EnumVariantId
221);
222define_identity!(
223    /// Composition name.
224    CompositionId
225);
226define_identity!(
227    /// Driver name within a composition.
228    CompositionDriverId
229);
230define_identity!(
231    /// Transaction plan name within a composition.
232    TransactionPlanId
233);
234define_identity!(
235    /// Transaction trigger name within a composition.
236    TransactionTriggerId
237);
238define_identity!(
239    /// Witness name within a composition.
240    CompositionWitnessId
241);
242define_identity!(
243    /// Entry input name within a composition.
244    EntryInputId
245);
246
247/// Store primitive referenced by a composition transaction plan.
248///
249/// Unlike kernel slugs, store primitives name existing Rust-side atomic
250/// operations and may use qualified path syntax such as
251/// `ScheduleStore::claim_due_occurrences`. The type still owns validation at
252/// the schema boundary instead of letting transaction plans carry raw strings.
253#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
254pub struct StorePrimitiveId(String);
255
256impl StorePrimitiveId {
257    pub fn parse(value: impl Into<String>) -> Result<Self, IdentityError> {
258        let raw = value.into();
259        if raw.is_empty() {
260            return Err(IdentityError {
261                kind: IdentityErrorKind::Empty,
262                raw,
263            });
264        }
265        for (position, ch) in raw.chars().enumerate() {
266            if ch.is_control() || ch.is_whitespace() {
267                return Err(IdentityError {
268                    kind: IdentityErrorKind::InvalidChar { ch, position },
269                    raw,
270                });
271            }
272        }
273        Ok(Self(raw))
274    }
275
276    pub fn as_str(&self) -> &str {
277        &self.0
278    }
279}
280
281impl AsRef<str> for StorePrimitiveId {
282    fn as_ref(&self) -> &str {
283        &self.0
284    }
285}
286
287impl fmt::Display for StorePrimitiveId {
288    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289        f.write_str(&self.0)
290    }
291}
292
293impl Serialize for StorePrimitiveId {
294    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
295        serializer.serialize_str(&self.0)
296    }
297}
298
299impl<'de> Deserialize<'de> for StorePrimitiveId {
300    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
301        let raw = String::deserialize(deserializer)?;
302        Self::parse(raw).map_err(serde::de::Error::custom)
303    }
304}
305
306/// Payload field shapes for structural variants in a [`RustTypeAtom::TypePathEnum`].
307#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
308#[serde(rename_all = "snake_case")]
309pub enum TypePathEnumPayloadAtom {
310    /// Finite set of plain strings.
311    StringSet,
312    /// Finite set of values drawn from a named value domain.
313    NamedSet(NamedTypeId),
314    /// Single plain string value.
315    String,
316    /// Optional plain string value.
317    OptionalString,
318    /// Single value drawn from a named value domain.
319    Named(NamedTypeId),
320}
321
322/// One field in a structural enum-variant sample carried by the typed owner.
323#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
324pub struct TypePathEnumPayloadField {
325    pub name: FieldId,
326    pub atom: TypePathEnumPayloadAtom,
327}
328
329impl TypePathEnumPayloadField {
330    /// Construct a payload field whose value is a finite set of strings.
331    pub fn string_set(name: &str) -> Self {
332        Self {
333            #[allow(clippy::expect_used)]
334            name: FieldId::parse(name).expect("valid structural enum field slug"),
335            atom: TypePathEnumPayloadAtom::StringSet,
336        }
337    }
338
339    /// Construct a payload field whose value is a finite set of values from a
340    /// named value domain.
341    pub fn named_set(name: &str, type_name: &str) -> Self {
342        #[allow(clippy::expect_used)]
343        let type_name = NamedTypeId::parse(type_name).expect("valid nested named-type slug");
344        Self {
345            #[allow(clippy::expect_used)]
346            name: FieldId::parse(name).expect("valid structural enum field slug"),
347            atom: TypePathEnumPayloadAtom::NamedSet(type_name),
348        }
349    }
350
351    /// Construct a payload field whose value is a single string.
352    pub fn string(name: &str) -> Self {
353        Self {
354            #[allow(clippy::expect_used)]
355            name: FieldId::parse(name).expect("valid structural enum field slug"),
356            atom: TypePathEnumPayloadAtom::String,
357        }
358    }
359
360    /// Construct a payload field whose value is an optional string.
361    pub fn optional_string(name: &str) -> Self {
362        Self {
363            #[allow(clippy::expect_used)]
364            name: FieldId::parse(name).expect("valid structural enum field slug"),
365            atom: TypePathEnumPayloadAtom::OptionalString,
366        }
367    }
368
369    /// Construct a payload field whose value is a single value from a named
370    /// value domain.
371    pub fn named(name: &str, type_name: &str) -> Self {
372        #[allow(clippy::expect_used)]
373        let type_name = NamedTypeId::parse(type_name).expect("valid nested named-type slug");
374        Self {
375            #[allow(clippy::expect_used)]
376            name: FieldId::parse(name).expect("valid structural enum field slug"),
377            atom: TypePathEnumPayloadAtom::Named(type_name),
378        }
379    }
380}
381
382/// Structural enum variants whose sample values are represented as tagged maps.
383#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
384pub struct TypePathEnumStructuralVariant {
385    pub variant: EnumVariantId,
386    pub fields: Vec<TypePathEnumPayloadField>,
387}
388
389impl TypePathEnumStructuralVariant {
390    /// Construct a one-field structural variant with a string-set payload.
391    pub fn string_set(variant: &str, field: &str) -> Self {
392        Self {
393            #[allow(clippy::expect_used)]
394            variant: EnumVariantId::parse(variant).expect("valid enum variant slug"),
395            fields: vec![TypePathEnumPayloadField::string_set(field)],
396        }
397    }
398
399    /// Construct a one-field structural variant whose payload is a finite set
400    /// of values from a named value domain.
401    pub fn named_set(variant: &str, field: &str, type_name: &str) -> Self {
402        Self {
403            #[allow(clippy::expect_used)]
404            variant: EnumVariantId::parse(variant).expect("valid enum variant slug"),
405            fields: vec![TypePathEnumPayloadField::named_set(field, type_name)],
406        }
407    }
408
409    /// Construct a structural variant from explicit payload fields.
410    pub fn with_fields(variant: &str, fields: Vec<TypePathEnumPayloadField>) -> Self {
411        Self {
412            #[allow(clippy::expect_used)]
413            variant: EnumVariantId::parse(variant).expect("valid enum variant slug"),
414            fields,
415        }
416    }
417}
418
419/// Field value shapes for structural type-path records.
420#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
421#[serde(rename_all = "snake_case")]
422pub enum TypePathStructFieldAtom {
423    String,
424    Named(NamedTypeId),
425    OptionalNamed(NamedTypeId),
426}
427
428/// One field in a structural record carried by the typed owner.
429#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
430pub struct TypePathStructField {
431    pub name: FieldId,
432    pub atom: TypePathStructFieldAtom,
433}
434
435impl TypePathStructField {
436    /// Construct a structural field whose value is a string.
437    pub fn string(name: &str) -> Self {
438        Self {
439            #[allow(clippy::expect_used)]
440            name: FieldId::parse(name).expect("valid structural record field slug"),
441            atom: TypePathStructFieldAtom::String,
442        }
443    }
444
445    /// Construct a structural field whose value is another named type.
446    pub fn named(name: &str, type_name: &str) -> Self {
447        #[allow(clippy::expect_used)]
448        let type_name = NamedTypeId::parse(type_name).expect("valid nested named-type slug");
449        Self {
450            #[allow(clippy::expect_used)]
451            name: FieldId::parse(name).expect("valid structural record field slug"),
452            atom: TypePathStructFieldAtom::Named(type_name),
453        }
454    }
455
456    /// Construct a structural field whose value is an optional named type.
457    pub fn optional_named(name: &str, type_name: &str) -> Self {
458        #[allow(clippy::expect_used)]
459        let type_name = NamedTypeId::parse(type_name).expect("valid nested named-type slug");
460        Self {
461            #[allow(clippy::expect_used)]
462            name: FieldId::parse(name).expect("valid structural record field slug"),
463            atom: TypePathStructFieldAtom::OptionalNamed(type_name),
464        }
465    }
466}
467
468/// Atomic Rust-level representation used by [`NamedTypeBinding`] to anchor a
469/// DSL-declared named type to the concrete Rust type codegen must emit.
470///
471/// Grown as needed by wave-b codegen. Intentionally small and explicit — avoids
472/// the old `render_named_type_alias_target` allow-list which silently defaulted
473/// unknown aliases to `String`.
474#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
475#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
476pub enum RustTypeAtom {
477    U64,
478    U32,
479    U16,
480    U8,
481    Bool,
482    String,
483    /// String-backed closed semantic domain.
484    ///
485    /// This keeps the schema-level representation compatible with DSL enum
486    /// literals while giving codegen and the runtime oracle an authoritative
487    /// finite value set for named string types.
488    StringEnum {
489        variants: Vec<EnumVariantId>,
490    },
491    /// Fully-qualified Rust type path, e.g. `"crate::domain::MySpecialType"`.
492    TypePath(String),
493    /// Fully-qualified Rust struct type path whose model domain is represented
494    /// as finite sets of present field names.
495    TypePathFieldPresenceSet {
496        path: String,
497        fields: Vec<FieldId>,
498    },
499    /// Fully-qualified Rust struct type path whose model and runtime domains are
500    /// represented as structural records with typed fields.
501    TypePathStruct {
502        path: String,
503        fields: Vec<TypePathStructField>,
504    },
505    /// Fully-qualified Rust enum type path with explicit unit variants that
506    /// can appear as DSL named-variant literals.
507    TypePathEnum {
508        path: String,
509        unit_variants: Vec<EnumVariantId>,
510        #[serde(default)]
511        structural_variants: Vec<TypePathEnumStructuralVariant>,
512    },
513}
514
515impl RustTypeAtom {
516    /// Returns whether two named-type bindings project to the same composition
517    /// model domain shape.
518    ///
519    /// Machine-local `TypePath` owners can differ by Rust module path while
520    /// still sharing a composition-level TLA domain through the named slug.
521    /// `TypePathEnum` owners are likewise path-agnostic here, but their unit
522    /// and structural variant payload shapes must agree because those variants
523    /// define the generated finite domain.
524    pub fn has_same_composition_domain_shape(&self, other: &Self) -> bool {
525        if self == other {
526            return true;
527        }
528
529        match (self, other) {
530            (Self::TypePath(_), Self::TypePath(_)) => true,
531            (
532                Self::TypePathFieldPresenceSet {
533                    fields: left_fields,
534                    ..
535                },
536                Self::TypePathFieldPresenceSet {
537                    fields: right_fields,
538                    ..
539                },
540            ) => left_fields == right_fields,
541            (
542                Self::TypePathStruct {
543                    fields: left_fields,
544                    ..
545                },
546                Self::TypePathStruct {
547                    fields: right_fields,
548                    ..
549                },
550            ) => left_fields == right_fields,
551            (
552                Self::TypePathEnum {
553                    unit_variants: left_units,
554                    structural_variants: left_structural,
555                    ..
556                },
557                Self::TypePathEnum {
558                    unit_variants: right_units,
559                    structural_variants: right_structural,
560                    ..
561                },
562            ) => left_units == right_units && left_structural == right_structural,
563            _ => false,
564        }
565    }
566}
567
568/// Authoritative binding from a DSL-declared named type to its Rust atom.
569///
570/// Consumed by codegen (B-2) to replace the hard-coded allow-list. The mapping
571/// is carried on the DSL declaration itself so the schema layer is the single
572/// source of truth.
573#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
574pub struct NamedTypeBinding {
575    pub name: NamedTypeId,
576    pub rust: RustTypeAtom,
577}
578
579impl NamedTypeBinding {
580    /// Construct a binding whose Rust representation is `u64`.
581    ///
582    /// Panics if `name` is not a valid [`NamedTypeId`] slug. Intended for
583    /// catalog construction sites; callers that want fallible
584    /// construction should build [`NamedTypeId`] directly and assemble
585    /// the struct by hand.
586    pub fn u64(name: &str) -> Self {
587        Self {
588            #[allow(clippy::expect_used)]
589            name: NamedTypeId::parse(name).expect("valid named-type slug"),
590            rust: RustTypeAtom::U64,
591        }
592    }
593
594    /// Construct a binding whose Rust representation is `String`.
595    pub fn string(name: &str) -> Self {
596        Self {
597            #[allow(clippy::expect_used)]
598            name: NamedTypeId::parse(name).expect("valid named-type slug"),
599            rust: RustTypeAtom::String,
600        }
601    }
602
603    /// Construct a binding whose Rust representation is a closed string
604    /// domain rendered as a Rust enum.
605    ///
606    /// Panics if `name` or any variant is not a valid slug, or if the variant
607    /// set is empty. Intended for catalog construction sites.
608    pub fn string_enum(name: &str, variants: &[&str]) -> Self {
609        assert!(
610            !variants.is_empty(),
611            "string enum named-type bindings require at least one variant"
612        );
613        Self {
614            #[allow(clippy::expect_used)]
615            name: NamedTypeId::parse(name).expect("valid named-type slug"),
616            rust: RustTypeAtom::StringEnum {
617                variants: variants
618                    .iter()
619                    .map(|variant| {
620                        #[allow(clippy::expect_used)]
621                        EnumVariantId::parse(*variant).expect("valid enum variant slug")
622                    })
623                    .collect(),
624            },
625        }
626    }
627
628    /// Construct a binding whose Rust representation is a fully-qualified
629    /// type path.
630    pub fn type_path(name: &str, rust_path: impl Into<String>) -> Self {
631        Self {
632            #[allow(clippy::expect_used)]
633            name: NamedTypeId::parse(name).expect("valid named-type slug"),
634            rust: RustTypeAtom::TypePath(rust_path.into()),
635        }
636    }
637
638    /// Construct a binding whose Rust representation is a fully-qualified type
639    /// path and whose generated model domain is finite field-presence sets.
640    pub fn type_path_field_presence_set(
641        name: &str,
642        rust_path: impl Into<String>,
643        fields: &[&str],
644    ) -> Self {
645        assert!(
646            !fields.is_empty(),
647            "field-presence named-type bindings require at least one field"
648        );
649        Self {
650            #[allow(clippy::expect_used)]
651            name: NamedTypeId::parse(name).expect("valid named-type slug"),
652            rust: RustTypeAtom::TypePathFieldPresenceSet {
653                path: rust_path.into(),
654                fields: fields
655                    .iter()
656                    .map(|field| {
657                        #[allow(clippy::expect_used)]
658                        FieldId::parse(*field).expect("valid field-presence slug")
659                    })
660                    .collect(),
661            },
662        }
663    }
664
665    /// Construct a binding whose Rust representation is a fully-qualified type
666    /// path and whose generated model/runtime domain is a typed structural
667    /// record.
668    pub fn type_path_struct(
669        name: &str,
670        rust_path: impl Into<String>,
671        fields: Vec<TypePathStructField>,
672    ) -> Self {
673        assert!(
674            !fields.is_empty(),
675            "struct named-type bindings require at least one field"
676        );
677        Self {
678            #[allow(clippy::expect_used)]
679            name: NamedTypeId::parse(name).expect("valid named-type slug"),
680            rust: RustTypeAtom::TypePathStruct {
681                path: rust_path.into(),
682                fields,
683            },
684        }
685    }
686
687    /// Construct a binding whose Rust representation is a fully-qualified
688    /// structural enum type path with a closed variant domain.
689    pub fn type_path_enum(
690        name: &str,
691        rust_path: impl Into<String>,
692        unit_variants: &[&str],
693    ) -> Self {
694        assert!(
695            !unit_variants.is_empty(),
696            "type-path enum named-type bindings require at least one unit variant"
697        );
698        Self {
699            #[allow(clippy::expect_used)]
700            name: NamedTypeId::parse(name).expect("valid named-type slug"),
701            rust: RustTypeAtom::TypePathEnum {
702                path: rust_path.into(),
703                unit_variants: unit_variants
704                    .iter()
705                    .map(|variant| {
706                        #[allow(clippy::expect_used)]
707                        EnumVariantId::parse(*variant).expect("valid enum variant slug")
708                    })
709                    .collect(),
710                structural_variants: Vec::new(),
711            },
712        }
713    }
714
715    /// Construct a binding whose Rust representation is a fully-qualified
716    /// structural enum type path with unit and payload-carrying variants.
717    pub fn type_path_enum_with_structural_variants(
718        name: &str,
719        rust_path: impl Into<String>,
720        unit_variants: &[&str],
721        structural_variants: Vec<TypePathEnumStructuralVariant>,
722    ) -> Self {
723        let mut binding = Self::type_path_enum(name, rust_path, unit_variants);
724        if let RustTypeAtom::TypePathEnum {
725            structural_variants: variants,
726            ..
727        } = &mut binding.rust
728        {
729            *variants = structural_variants;
730        }
731        binding
732    }
733}