Skip to main content

scale_typegen_description/
description.rs

1use scale_info::{
2    form::PortableForm, Field, PortableRegistry, Type, TypeDef, TypeDefPrimitive, TypeDefTuple,
3    TypeDefVariant, Variant,
4};
5
6use crate::transformer::Transformer;
7
8use super::formatting::format_type_description;
9
10/// Describes the type that is registered under the given `type_id`. This type description
11/// is supposed to be very close to actual rust types, with some minar differences:
12/// - The `struct` keyword is omitted. So the description of `struct Human { age: u8 }` is just `Human { age: u8 }`.
13/// - Types are presented in a nested fashion, similar to how structures can be defined in e.g. the C programming language.
14///
15/// If the `format` flag is enabled, the end result is formatted across multiple lines. Otherwise the description will be one single line string.
16pub fn type_description(
17    type_id: u32,
18    type_registry: &PortableRegistry,
19    format: bool,
20) -> anyhow::Result<String> {
21    fn return_type_name(
22        _type_id: u32,
23        ty: &Type<PortableForm>,
24        transformer: &Transformer<String>,
25    ) -> Option<anyhow::Result<String>> {
26        if ty.path.ident().is_some() {
27            return Some(Ok(type_name_with_type_params(ty, transformer.types())));
28        }
29        None
30    }
31
32    fn return_type_name_on_cache_hit(
33        _type_id: u32,
34        ty: &Type<PortableForm>,
35        cached: &String,
36        transformer: &Transformer<String>,
37    ) -> Option<anyhow::Result<String>> {
38        if ty.path.ident().is_some() {
39            return Some(Ok(type_name_with_type_params(ty, transformer.types())));
40        }
41        Some(Ok(cached.to_owned()))
42    }
43    let transformer = Transformer::new(
44        ty_description,
45        return_type_name,
46        return_type_name_on_cache_hit,
47        (),
48        type_registry,
49    );
50    let mut description = transformer.resolve(type_id)?;
51    if format {
52        description = format_type_description(&description);
53    }
54    Ok(description)
55}
56
57fn ty_description(
58    _type_id: u32,
59    ty: &Type<PortableForm>,
60    transformer: &Transformer<String>,
61) -> anyhow::Result<String> {
62    let name_and_params = if ty.path.ident().is_some() {
63        type_name_with_type_params(ty, transformer.types())
64    } else {
65        String::new()
66    };
67
68    let prefix = match &ty.type_def {
69        TypeDef::Variant(_) => "enum ",
70        TypeDef::Composite(_) => "struct ",
71        _ => "",
72    };
73    let type_def_description = type_def_type_description(&ty.type_def, transformer)?;
74    Ok(format!("{prefix}{name_and_params}{type_def_description}"))
75}
76
77/// Can be None for types that have an empty path
78fn type_name_with_type_params(ty: &Type<PortableForm>, types: &PortableRegistry) -> String {
79    match &ty.type_def {
80        TypeDef::Sequence(s) => {
81            let inner = type_name_with_type_params(types.resolve(s.type_param.id).unwrap(), types);
82            return format!("Vec<{inner}>",);
83        }
84        TypeDef::Array(a) => {
85            let inner = type_name_with_type_params(types.resolve(a.type_param.id).unwrap(), types);
86            let len = a.len;
87            return format!("[{inner};{len}]",);
88        }
89        TypeDef::Tuple(t) => {
90            let mut output = "(".to_string();
91            let mut iter = t.fields.iter().peekable();
92            while let Some(ty) = iter.next() {
93                let type_name = type_name_with_type_params(types.resolve(ty.id).unwrap(), types);
94                output.push_str(&type_name);
95                if iter.peek().is_some() || t.fields.len() == 1 {
96                    output.push(',')
97                }
98            }
99            output.push(')');
100            return output;
101        }
102        TypeDef::Primitive(p) => return primitive_type_description(p).into(),
103        TypeDef::Compact(c) => {
104            let inner = type_name_with_type_params(types.resolve(c.type_param.id).unwrap(), types);
105            return format!("Compact<{inner}>",);
106        }
107        TypeDef::BitSequence(_) => return "BitSequence".into(),
108        TypeDef::Composite(_) => {}
109        TypeDef::Variant(_) => {}
110    }
111
112    let Some(ident) = ty.path.ident() else {
113        return "_".to_string(); // this should happen rarely
114    };
115
116    let params = ty
117        .type_params
118        .iter()
119        .map(|e| {
120            let Some(ty) = e.ty.as_ref() else {
121                return "_".to_string();
122            };
123
124            let ty = types.resolve(ty.id).unwrap();
125            type_name_with_type_params(ty, types)
126        })
127        .collect::<Vec<_>>()
128        .join(",");
129
130    if params.is_empty() {
131        ident.to_string()
132    } else {
133        format!("{ident}<{}>", params)
134    }
135}
136
137fn type_def_type_description(
138    type_def: &TypeDef<PortableForm>,
139    transformer: &Transformer<String>,
140) -> anyhow::Result<String> {
141    match type_def {
142        TypeDef::Composite(composite) => fields_type_description(&composite.fields, transformer),
143        TypeDef::Variant(variant) => variant_type_def_type_description(variant, transformer),
144        TypeDef::Sequence(sequence) => Ok(format!(
145            "Vec<{}>",
146            transformer.resolve(sequence.type_param.id)?
147        )),
148        TypeDef::Array(array) => Ok(format!(
149            "[{}; {}]",
150            transformer.resolve(array.type_param.id)?,
151            array.len
152        )),
153        TypeDef::Tuple(tuple) => tuple_type_description(tuple, transformer),
154        TypeDef::Primitive(primitive) => Ok(primitive_type_description(primitive).into()),
155        TypeDef::Compact(compact) => Ok(format!(
156            "Compact<{}>",
157            transformer.resolve(compact.type_param.id)?
158        )),
159        TypeDef::BitSequence(bit_sequence) => {
160            let bit_order_type = transformer.resolve(bit_sequence.bit_order_type.id)?;
161            let bit_store_type = transformer.resolve(bit_sequence.bit_store_type.id)?;
162            Ok(format!("BitSequence({bit_order_type}, {bit_store_type})"))
163        }
164    }
165}
166
167fn tuple_type_description(
168    tuple: &TypeDefTuple<PortableForm>,
169    transformer: &Transformer<String>,
170) -> anyhow::Result<String> {
171    let mut output = "(".to_string();
172    let mut iter = tuple.fields.iter().peekable();
173    let field_count = tuple.fields.len();
174    while let Some(ty) = iter.next() {
175        let type_description = transformer.resolve(ty.id)?;
176        output.push_str(&type_description);
177        if iter.peek().is_some() || field_count == 1 {
178            output.push(',')
179        }
180    }
181    output.push(')');
182    Ok(output)
183}
184
185fn primitive_type_description(primitive: &TypeDefPrimitive) -> &'static str {
186    match &primitive {
187        TypeDefPrimitive::Bool => "bool",
188        TypeDefPrimitive::Char => "char",
189        TypeDefPrimitive::Str => "String",
190        TypeDefPrimitive::U8 => "u8",
191        TypeDefPrimitive::U16 => "u16",
192        TypeDefPrimitive::U32 => "u32",
193        TypeDefPrimitive::U64 => "u64",
194        TypeDefPrimitive::U128 => "u128",
195        TypeDefPrimitive::U256 => "u256",
196        TypeDefPrimitive::I8 => "i8",
197        TypeDefPrimitive::I16 => "i16",
198        TypeDefPrimitive::I32 => "i32",
199        TypeDefPrimitive::I64 => "i64",
200        TypeDefPrimitive::I128 => "i128",
201        TypeDefPrimitive::I256 => "i256",
202    }
203}
204
205fn variant_type_def_type_description(
206    variant_type_def: &TypeDefVariant<PortableForm>,
207    transformer: &Transformer<String>,
208) -> anyhow::Result<String> {
209    let mut variants_string = String::new();
210    variants_string.push('{');
211    let mut iter = variant_type_def.variants.iter().peekable();
212    while let Some(variant) = iter.next() {
213        let variant_string = variant_type_description(variant, transformer)?;
214        variants_string.push_str(&variant_string);
215
216        if iter.peek().is_some() {
217            variants_string.push(',');
218        }
219    }
220    variants_string.push('}');
221    Ok(variants_string)
222}
223
224fn variant_type_description(
225    variant: &Variant<PortableForm>,
226    transformer: &Transformer<String>,
227) -> anyhow::Result<String> {
228    let fields_string = fields_type_description(&variant.fields, transformer)?;
229    let output = if fields_string == "()" {
230        variant.name.to_string()
231    } else {
232        format!("{}{}", &variant.name, fields_string)
233    };
234    Ok(output)
235}
236
237fn fields_type_description(
238    fields: &[Field<PortableForm>],
239    transformer: &Transformer<String>,
240) -> anyhow::Result<String> {
241    if fields.is_empty() {
242        return Ok("()".to_string());
243    }
244
245    let all_fields_named = fields.iter().all(|f| f.name.is_some());
246    let all_fields_unnamed = fields.iter().all(|f| f.name.is_none());
247    let brackets = match (all_fields_named, all_fields_unnamed) {
248        (true, false) => ('{', '}'),
249        (false, true) => ('(', ')'),
250        _ => {
251            return Err(anyhow::anyhow!(
252                "combination of named and unnamed fields in compound type"
253            ));
254        }
255    };
256
257    let mut fields_string = String::new();
258    fields_string.push(brackets.0);
259    let mut iter = fields.iter().peekable();
260    while let Some(field) = iter.next() {
261        let field_description = field_type_description(field, transformer)?;
262        fields_string.push_str(&field_description);
263
264        if iter.peek().is_some() {
265            fields_string.push(',')
266        }
267    }
268    fields_string.push(brackets.1);
269    Ok(fields_string)
270}
271
272fn field_type_description(
273    field: &Field<PortableForm>,
274    transformer: &Transformer<String>,
275) -> anyhow::Result<String> {
276    let mut type_description = transformer.resolve(field.ty.id)?;
277    let is_boxed = field
278        .type_name
279        .as_ref()
280        .map(|e| e.contains("Box<"))
281        .unwrap_or_default();
282    if is_boxed {
283        type_description = format!("Box<{}>", type_description);
284    }
285
286    let type_description_maybe_named = if let Some(name) = &field.name {
287        format!("{}: {}", name, type_description)
288    } else {
289        type_description
290    };
291    Ok(type_description_maybe_named)
292}