strict_types/typesys/
type_sys.rs

1// Strict encoding schema library, implementing validation and parsing of strict encoded data
2// against a schema.
3//
4// SPDX-License-Identifier: Apache-2.0
5//
6// Designed in 2019-2025 by Dr Maxim Orlovsky <orlovsky@ubideco.org>
7// Written in 2024-2025 by Dr Maxim Orlovsky <orlovsky@ubideco.org>
8//
9// Copyright (C) 2022-2025 Laboratories for Ubiquitous Deterministic Computing (UBIDECO),
10//                         Institute for Distributed and Cognitive Systems (InDCS), Switzerland.
11// Copyright (C) 2022-2025 Dr Maxim Orlovsky.
12// All rights under the above copyrights are reserved.
13//
14// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
15// in compliance with the License. You may obtain a copy of the License at
16//
17//        http://www.apache.org/licenses/LICENSE-2.0
18//
19// Unless required by applicable law or agreed to in writing, software distributed under the License
20// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
21// or implied. See the License for the specific language governing permissions and limitations under
22// the License.
23
24//! Embedded lib is a set of compiled type libraries having no external
25//! dependencies
26
27use std::collections::{BTreeMap, BTreeSet};
28use std::fmt::{self, Display, Formatter};
29use std::ops::Index;
30
31use amplify::confinement::{self, Confined, MediumOrdMap};
32use amplify::num::u24;
33use encoding::{LibName, Sizing, StrictDeserialize, StrictSerialize, TypeName};
34use strict_encoding::STRICT_TYPES_LIB;
35
36use crate::ast::UnnamedFields;
37use crate::{SemId, Ty};
38
39#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, Error)]
40#[display("type with id `{0}` is not a part of the type system.")]
41pub struct UnknownType(SemId);
42
43#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)]
44#[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)]
45#[strict_type(lib = STRICT_TYPES_LIB)]
46#[display("{lib}.{name}")]
47#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
48pub struct TypeFqn {
49    pub lib: LibName,
50    pub name: TypeName,
51}
52
53impl TypeFqn {
54    pub fn with(lib: impl Into<LibName>, name: impl Into<TypeName>) -> TypeFqn {
55        TypeFqn {
56            lib: lib.into(),
57            name: name.into(),
58        }
59    }
60}
61
62impl From<&'static str> for TypeFqn {
63    fn from(value: &'static str) -> Self {
64        let Some((lib, name)) = value.split_once('.') else {
65            panic!("invalid fully qualified type name `{value}`");
66        };
67        TypeFqn {
68            lib: LibName::from(lib),
69            name: TypeName::from(name),
70        }
71    }
72}
73
74/// Type coupled with symbolic information.
75#[derive(Clone, Eq, PartialEq, Debug)]
76#[derive(StrictDumb, StrictType, StrictEncode, StrictDecode)]
77#[strict_type(lib = STRICT_TYPES_LIB)]
78#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
79pub struct SymTy {
80    /// Type origin providing information from which library and under which name the type is
81    /// originating from. The origin information may be empty for the unnamed types.
82    pub orig: Option<TypeFqn>,
83    /// Type definition, potentially referencing other types via semantic type id.
84    pub ty: Ty<SemId>,
85}
86
87impl SymTy {
88    pub fn named(lib_name: LibName, ty_name: TypeName, ty: Ty<SemId>) -> SymTy {
89        Self::with(Some(TypeFqn::with(lib_name, ty_name)), ty)
90    }
91
92    pub fn unnamed(ty: Ty<SemId>) -> SymTy { Self::with(None::<TypeFqn>, ty) }
93
94    pub fn with(orig: Option<impl Into<TypeFqn>>, ty: Ty<SemId>) -> SymTy {
95        SymTy {
96            orig: orig.map(|o| o.into()),
97            ty,
98        }
99    }
100}
101
102/// Type system represents a set of strict types assembled from multiple
103/// libraries. It is designed to provide all necessary type information to
104/// analyze a type with all types it depends onto.
105///
106/// # Type guarantees
107///
108/// - Total number of types do not exceed 2^24-1;
109/// - Strict-serialized size is less than 2^24 bytes;
110/// - A type with the same semantic id can't appear in more than 256 libraries;
111/// - Type system is complete (i.e. no type references a type which is not a part of the system).
112#[derive(Wrapper, Clone, Eq, PartialEq, Debug, Default, From)]
113#[wrapper(Deref)]
114#[derive(StrictType, StrictEncode, StrictDecode)]
115#[strict_type(lib = STRICT_TYPES_LIB)]
116#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
117pub struct TypeSystem(MediumOrdMap<SemId, Ty<SemId>>);
118
119impl StrictSerialize for TypeSystem {}
120impl StrictDeserialize for TypeSystem {}
121
122impl TypeSystem {
123    pub fn new() -> Self { Self::default() }
124
125    pub fn count_types(&self) -> u24 { self.0.len_u24() }
126
127    pub(super) fn insert_unchecked(
128        &mut self,
129        sem_id: SemId,
130        ty: Ty<SemId>,
131    ) -> Result<bool, confinement::Error> {
132        self.0.insert(sem_id, ty).map(|r| r.is_some())
133    }
134
135    pub fn get(&self, sem_id: SemId) -> Option<&Ty<SemId>> { self.0.get(&sem_id) }
136
137    pub fn extend(&mut self, other: Self) -> Result<(), confinement::Error> {
138        self.0.extend(other.0)
139    }
140
141    pub fn extract(&self, ids: impl IntoIterator<Item = SemId>) -> Result<Self, UnknownType> {
142        let mut ids = ids.into_iter().collect::<BTreeSet<_>>();
143        let mut found = BTreeSet::new();
144        let mut extract = BTreeMap::<SemId, Ty<SemId>>::new();
145
146        while let Some(id) = ids.pop_first() {
147            let ty = self.get(id).ok_or(UnknownType(id))?.clone();
148            found.insert(id);
149            ids.extend(ty.iter().filter(|(id, _)| !found.contains(*id)).map(|(id, _)| *id));
150            extract.insert(id, ty);
151        }
152
153        Ok(Self(Confined::from_checked(extract)))
154    }
155
156    pub(crate) fn rstring_sizing(
157        &self,
158        fields: &UnnamedFields<SemId>,
159    ) -> Result<Option<(SemId, Sizing)>, UnknownType> {
160        let rest = fields[1];
161        let rest = self.find(rest).ok_or(UnknownType(rest))?;
162        if let Ty::List(rest, sizing) = rest {
163            let mut sizing = *sizing;
164            sizing.min += 1;
165            sizing.max += 1;
166            return Ok(Some((*rest, sizing)));
167        }
168        Ok(None)
169    }
170
171    pub(crate) fn is_rstring(&self, fields: &UnnamedFields<SemId>) -> Result<bool, UnknownType> {
172        if fields.len() != 2 {
173            return Ok(false);
174        }
175        let first = fields[0];
176        let Some((rest, _)) = self.rstring_sizing(fields)? else {
177            return Ok(false);
178        };
179
180        Ok(self.find(first).ok_or(UnknownType(first))?.is_char_enum()
181            && self.find(rest).ok_or(UnknownType(rest))?.is_char_enum())
182    }
183}
184
185impl Index<SemId> for TypeSystem {
186    type Output = Ty<SemId>;
187
188    fn index(&self, index: SemId) -> &Self::Output {
189        self.get(index).unwrap_or_else(|| {
190            panic!("type with semantic id {index} is not a part of the type system")
191        })
192    }
193}
194
195impl Display for TypeSystem {
196    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
197        writeln!(f, "typesys -- {}", self.id())?;
198        writeln!(f)?;
199        for (id, ty) in &self.0 {
200            writeln!(f, "data {id:-}: {ty:-}")?;
201        }
202        Ok(())
203    }
204}
205
206#[cfg(feature = "armor")]
207impl armor::StrictArmor for TypeSystem {
208    type Id = crate::TypeSysId;
209    const PLATE_TITLE: &'static str = "STRICT TYPE SYSTEM";
210
211    fn armor_id(&self) -> Self::Id { self.id() }
212}