scale_typegen_description/
description.rs1use 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
10pub 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
77fn 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(); };
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}