1use i_codegen_code::types as st;
2use i_codegen_derive::CodegenInternal;
3use rayon::prelude::*;
4use serde::{self, Deserialize, Serialize};
5use st::TypeRoot;
6
7use std::io::Write;
8use std::path::PathBuf;
9use std::process::Command;
10use std::{
11 collections::{BTreeMap, HashMap, HashSet},
12 fmt::Debug,
13 io::BufReader,
14};
15
16#[derive(CodegenInternal, Serialize, Deserialize, Clone, Debug)]
17#[serde(transparent)]
18#[codegen(tags = "derive-codegen-internal")]
19struct LocationID(String);
20
21#[derive(Serialize, Debug, CodegenInternal)]
22#[codegen(tags = "derive-codegen-internal")]
23struct Input {
24 declarations: Vec<InputDeclaration>,
25 functions: Vec<FunctionDeclaration>,
26}
27
28#[derive(Serialize, Debug, CodegenInternal)]
29#[codegen(tags = "derive-codegen-internal")]
30struct InputDeclaration {
31 id: String,
32 id_location: LocationID,
33 #[serde(flatten)]
35 attrs: Attrs,
36 container_kind: ContainerFormat,
37}
38
39#[derive(Serialize, Debug, CodegenInternal)]
40#[codegen(tags = "derive-codegen-internal")]
41struct FunctionDeclaration {
42 id: String,
43 id_location: LocationID,
44 #[serde(flatten)]
46 attrs: Attrs,
47 function: FunctionFormat,
48}
49
50#[derive(Serialize, Debug, CodegenInternal)]
51#[codegen(tags = "derive-codegen-internal")]
52struct FunctionFormat {
53 is_async: bool,
55 self_opt: Option<Box<FunctionParameter>>,
56 params: Vec<FunctionParameter>,
57 return_type: Box<Format>,
58}
59
60#[derive(Deserialize, Debug, CodegenInternal)]
61#[codegen(tags = "derive-codegen-internal")]
62struct Output {
63 errors: Vec<OutputMessage>,
64 warnings: Vec<OutputMessage>,
65 files: Vec<OutputFile>,
66}
67
68#[derive(Deserialize, Debug, CodegenInternal)]
69#[codegen(tags = "derive-codegen-internal")]
70struct OutputFile {
71 path: String,
73 source: String,
75}
76
77#[derive(Serialize, Deserialize, Debug, CodegenInternal)]
78#[codegen(tags = "derive-codegen-internal")]
79struct OutputMessage {
80 message: String,
81 labels: Vec<(String, LocationID)>,
83}
84
85#[derive(Serialize, Debug, CodegenInternal)]
89#[codegen(tags = "derive-codegen-internal")]
90enum Format {
91 Incomplete {
92 debug: String,
93 },
94 TypeName {
96 ident: String,
97 generics: Vec<Format>,
98 },
99
100 Unit,
102 Bool,
103 I8,
104 I16,
105 I32,
106 I64,
107 I128,
108 ISIZE,
109 U8,
110 U16,
111 U32,
112 U64,
113 U128,
114 USIZE,
115 F32,
116 F64,
117 Char,
118 Str,
119 Bytes,
120
121 Option(Box<Format>),
123 Never,
125 Seq(Box<Format>),
127 Map {
129 key: Box<Format>,
130 value: Box<Format>,
131 },
132
133 Tuple(Vec<Format>),
135 TupleArray {
138 content: Box<Format>,
139 size: usize,
140 },
141}
142
143#[derive(Serialize, Debug, CodegenInternal)]
146#[codegen(tags = "derive-codegen-internal")]
147enum ContainerFormat {
148 UnitStruct,
150 NewTypeStruct(Box<Format>),
152 TupleStruct(Vec<Format>),
154 Struct { fields: Vec<NamedField> },
156 Enum {
159 repr: EnumRepresentation,
160 variants: Vec<NamedVariant>,
161 },
162}
163
164#[derive(Serialize, Debug, CodegenInternal)]
165#[codegen(tags = "derive-codegen-internal")]
166struct NamedVariant {
167 id: String,
168 id_location: LocationID,
169 #[serde(flatten)]
170 attrs: Attrs,
171 variant_format: VariantFormat,
172}
173
174#[derive(Serialize, Debug, CodegenInternal)]
175#[codegen(tags = "derive-codegen-internal")]
176struct NamedField {
177 id: String,
178 id_location: LocationID,
179 #[serde(flatten)]
180 attrs: Attrs,
181 format: Format,
182}
183
184#[derive(Serialize, Debug, CodegenInternal)]
185#[codegen(tags = "derive-codegen-internal")]
186struct FunctionParameter {
187 id: String,
188 id_location: LocationID,
189 #[serde(flatten)]
190 attrs: Attrs,
191 format: Format,
192}
193
194#[derive(Serialize, Debug, CodegenInternal)]
195#[codegen(tags = "derive-codegen-internal")]
196enum VariantFormat {
198 Unit,
200 NewType(Box<Format>),
202 Tuple(Vec<Format>),
204 Struct { fields: Vec<NamedField> },
206}
207
208#[derive(Serialize, Debug, CodegenInternal)]
209#[codegen(tags = "derive-codegen-internal")]
210struct Attrs {
211 rust_docs: Option<String>,
214 #[serde(skip_serializing_if = "Vec::is_empty", default)]
217 rust_generics: Vec<(String, LocationID)>,
218 #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
221 serde_attrs: BTreeMap<String, (String, LocationID)>,
222 #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
225 serde_flags: BTreeMap<String, LocationID>,
226 #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
228 codegen_attrs: BTreeMap<String, (String, LocationID)>,
229 #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
231 codegen_flags: BTreeMap<String, LocationID>,
232}
233
234#[derive(Serialize, Debug, CodegenInternal)]
235#[codegen(tags = "derive-codegen-internal")]
236enum EnumRepresentation {
237 External,
240 Untagged,
242 Tagged {
245 tag: String,
246 tag_location: LocationID,
247 content: Option<String>,
248 content_location: Option<LocationID>,
249 },
250}
251
252#[derive(Clone)]
253struct SourceLineNumberIndex {
254 newlines: Vec<usize>,
255 #[allow(unused)]
257 is_crlf: bool,
258}
259
260impl SourceLineNumberIndex {
261 fn new(file: impl std::io::Read) -> Self {
262 use std::io::Read;
263 let mut newlines = Vec::new();
264 let mut is_crlf = false;
265
266 let mut current_byte = 0;
267 for byte_result in BufReader::new(file).bytes() {
268 match byte_result.expect("read next byte") {
269 b'\n' => {
270 newlines.push(current_byte + 1);
271 }
272 b'\r' => {
273 is_crlf = true;
274 }
275 _ => {}
276 }
277 current_byte += 1;
278 }
279
280 Self { is_crlf, newlines }
281 }
282
283 fn get_ln_col(&self, byte: usize) -> (usize, usize) {
284 let index = match self.newlines.binary_search(&byte) {
285 Ok(exact_at) => exact_at,
286 Err(insert_at) => insert_at - 1,
287 };
288 if index >= self.newlines.len() || index == 0 {
289 (index, 0)
290 } else {
291 let newline_byte_offset = *self.newlines.get(index).expect("in bounds");
292 if byte < newline_byte_offset {
293 panic!("expected newline returned to be before byte (byte: {byte} < newline_at: {newline_byte_offset}) found at index: {index}, all new lines: {:?}", self.newlines)
294 }
295 (index + 1, byte - newline_byte_offset)
296 }
297 }
298}
299
300#[derive(Clone)]
301struct TypeRootConverter {
302 file_name: String,
303 line_number_override: Option<u32>,
304 lines: SourceLineNumberIndex,
305}
306
307impl TypeRootConverter {
308 fn get_ln_col(&self, byte: usize) -> (usize, usize) {
309 self.lines.get_ln_col(byte)
310 }
311
312 fn location_id<T>(
313 &self,
314 st::Spanned {
315 bytes: (start, end),
316 value,
317 }: st::Spanned<T>,
318 ) -> (T, LocationID) {
319 if let Some(line) = self.line_number_override {
320 (
321 value,
322 LocationID(format!(
323 "L({}:{} #B{}-B{})",
324 &self.file_name, line, start, end
325 )),
326 )
327 } else {
328 let (ln, col) = self.get_ln_col(start);
329 (
330 value,
331 LocationID(format!(
332 "L({}:{}:{} #B{}-B{})",
333 &self.file_name, ln, col, start, end
334 )),
335 )
336 }
337 }
338 fn unname<T>(
339 &self,
340 st::Named {
341 codegen_attrs,
342 codegen_flags,
343 rust_docs,
344 rust_ident,
345 rust_generics,
346 serde_attrs,
347 serde_flags,
348 value,
349 }: st::Named<T>,
350 ) -> (st::Spanned<String>, T, Attrs) {
351 (
352 rust_ident,
353 value,
354 Attrs {
355 rust_docs,
356 rust_generics: rust_generics
357 .into_iter()
358 .map(|gen| self.location_id(gen))
359 .collect(),
360 serde_attrs: {
361 let mut bt = BTreeMap::<String, (String, LocationID)>::new();
362 for st::Spanned {
363 bytes: _,
364 value: (key, value),
365 } in serde_attrs
366 {
367 bt.insert(key.value, self.location_id(value));
368 }
369 bt
370 },
371 serde_flags: {
372 let mut bt = BTreeMap::<String, LocationID>::new();
373 for flag_span in serde_flags {
374 let (a, b) = self.location_id(flag_span);
375 bt.insert(a, b);
376 }
377 bt
378 },
379 codegen_attrs: {
380 let mut bt = BTreeMap::<String, (String, LocationID)>::new();
381 for st::Spanned {
382 bytes: _,
383 value: (key, value),
384 } in codegen_attrs
385 {
386 bt.insert(key.value, self.location_id(value));
387 }
388 bt
389 },
390 codegen_flags: {
391 let mut bt = BTreeMap::<String, LocationID>::new();
392 for flag_span in codegen_flags {
393 let (a, b) = self.location_id(flag_span);
394 bt.insert(a, b);
395 }
396 bt
397 },
398 },
399 )
400 }
401 fn format_to_format(&self, format: st::Format) -> Format {
402 match format {
403 st::Format::Incomplete { debug } => Format::Incomplete { debug },
404 st::Format::TypeName { ident, generics } => Format::TypeName {
405 ident,
406 generics: generics
407 .into_iter()
408 .map(|format| self.format_to_format(format))
409 .collect(),
410 },
411 st::Format::Unit => Format::Unit,
412 st::Format::Bool => Format::Bool,
413 st::Format::I8 => Format::I8,
414 st::Format::I16 => Format::I16,
415 st::Format::I32 => Format::I32,
416 st::Format::I64 => Format::I64,
417 st::Format::I128 => Format::I128,
418 st::Format::ISIZE => Format::ISIZE,
419 st::Format::U8 => Format::U8,
420 st::Format::U16 => Format::U16,
421 st::Format::U32 => Format::U32,
422 st::Format::U64 => Format::U64,
423 st::Format::U128 => Format::U128,
424 st::Format::USIZE => Format::USIZE,
425 st::Format::F32 => Format::F32,
426 st::Format::F64 => Format::F64,
427 st::Format::Char => Format::Char,
428 st::Format::Str => Format::Str,
429 st::Format::Bytes => Format::Bytes,
430 st::Format::Option(option_format) => {
431 Format::Option(Box::new(self.format_to_format(*option_format)))
432 }
433 st::Format::Never => Format::Never,
434 st::Format::Seq(seq_format) => {
435 Format::Seq(Box::new(self.format_to_format(*seq_format)))
436 }
437 st::Format::Map { key, value } => Format::Map {
438 key: Box::new(self.format_to_format(*key)),
439 value: Box::new(self.format_to_format(*value)),
440 },
441 st::Format::Tuple(tuple_formats) => Format::Tuple(
442 tuple_formats
443 .into_iter()
444 .map(|format| self.format_to_format(format))
445 .collect(),
446 ),
447 st::Format::TupleArray { content, size } => Format::TupleArray {
448 content: Box::new(self.format_to_format(*content)),
449 size,
450 },
451 }
452 }
453
454 fn function_format_to_function_format(
455 &self,
456 function_format: st::FunctionFormat,
457 ) -> FunctionFormat {
458 let st::FunctionFormat {
459 params: args,
460 is_async,
461 ret,
462 self_opt,
463 } = function_format;
464 FunctionFormat {
465 params: args
466 .into_iter()
467 .map(|st_named_format| self.named_format_to_function_parameter(st_named_format))
468 .collect(),
469 is_async,
470 self_opt: self_opt
471 .map(|selff| Box::new(self.named_format_to_function_parameter(selff))),
472 return_type: Box::new(self.format_to_format(*ret)),
473 }
474 }
475
476 fn named_format_to_named_field(&self, named: st::Named<st::Format>) -> NamedField {
477 let (id_span, format, attrs) = self.unname(named);
478 let (id, id_location) = self.location_id(id_span);
479 NamedField {
480 attrs,
481 format: self.format_to_format(format),
482 id,
483 id_location,
484 }
485 }
486 fn named_format_to_function_parameter(
487 &self,
488 named: st::Named<st::Format>,
489 ) -> FunctionParameter {
490 let (id_span, format, attrs) = self.unname(named);
491 let (id, id_location) = self.location_id(id_span);
492 FunctionParameter {
493 attrs,
494 format: self.format_to_format(format),
495 id,
496 id_location,
497 }
498 }
499 fn container_format_to_container_format(
500 &self,
501 attrs: &Attrs,
503 container_format: st::ContainerFormat,
504 ) -> ContainerFormat {
505 match container_format {
506 st::ContainerFormat::UnitStruct => ContainerFormat::UnitStruct,
507 st::ContainerFormat::NewTypeStruct(format) => {
508 ContainerFormat::NewTypeStruct(Box::new(self.format_to_format(*format)))
509 }
510 st::ContainerFormat::TupleStruct(formats) => ContainerFormat::TupleStruct(
511 formats
512 .into_iter()
513 .map(|format| self.format_to_format(format))
514 .collect(),
515 ),
516 st::ContainerFormat::Struct(fields) => ContainerFormat::Struct {
517 fields: {
518 fields
519 .into_par_iter()
520 .map(|field| self.named_format_to_named_field(field))
521 .collect()
522 },
523 },
524 st::ContainerFormat::Enum(variants) => ContainerFormat::Enum {
525 repr: {
526 if attrs.serde_flags.contains_key("untagged") {
527 EnumRepresentation::Untagged
528 } else {
529 match (
530 attrs.serde_attrs.get("tag").cloned(),
531 attrs.serde_attrs.get("content").cloned(),
532 ) {
533 (Some((tag, tag_location)), Some((content, content_location))) => {
534 EnumRepresentation::Tagged {
535 tag,
536 tag_location,
537 content: Some(content),
538 content_location: Some(content_location),
539 }
540 }
541 (Some((tag, tag_location)), None) => EnumRepresentation::Tagged {
542 tag,
543 tag_location,
544 content: None,
545 content_location: None,
546 },
547 (None, None) => EnumRepresentation::External,
548 (None, Some(_)) => {
549 EnumRepresentation::External
551 }
552 }
553 }
554 },
555 variants: {
556 variants
557 .into_par_iter()
558 .map(|(_index, named_variant_format)| {
559 let (id_span, variant_format, attrs) =
560 self.unname(named_variant_format);
561 let (id, id_location) = self.location_id(id_span);
562 let variant_format = match variant_format {
563 st::VariantFormat::Unit => VariantFormat::Unit,
564 st::VariantFormat::NewType(format) => {
565 VariantFormat::NewType(Box::new(self.format_to_format(*format)))
566 }
567 st::VariantFormat::Tuple(formats) => VariantFormat::Tuple(
568 formats
569 .into_iter()
570 .map(|format| self.format_to_format(format))
571 .collect(),
572 ),
573 st::VariantFormat::Struct(fields) => VariantFormat::Struct {
574 fields: fields
575 .into_iter()
576 .map(|field| {
577 let (id_span, format, attrs) = self.unname(field);
578 let (id, id_location) = self.location_id(id_span);
579 NamedField {
580 attrs,
581 format: self.format_to_format(format),
582 id,
583 id_location,
584 }
585 })
586 .collect(),
587 },
588 };
589 NamedVariant {
590 id,
591 id_location,
592 attrs,
593 variant_format,
594 }
595 })
596 .collect()
597 },
598 },
599 }
600 }
601}
602
603enum GenCommand<'a> {
604 PipeInto(&'a mut std::process::Command),
605 Arg(&'a mut std::process::Command),
606}
607
608#[derive(Clone)]
609pub struct Generation {
610 tags: Vec<String>,
611}
612
613pub struct GenerationCmd<'a> {
614 relative_to: Option<PathBuf>,
615 selection: &'a Generation,
616 command: GenCommand<'a>,
617 output_path: Option<PathBuf>,
618}
619
620impl Generation {
621 pub fn for_tag(tag: &str) -> Self {
622 Generation {
623 tags: vec![tag.to_string()],
624 }
625 }
626
627 pub fn include_tag(&mut self, tag: impl Into<String>) -> &mut Self {
628 self.tags.push(tag.into());
629 self
630 }
631
632 pub fn pipe_into<'a>(&'a self, command: &'a mut Command) -> GenerationCmd<'a> {
633 GenerationCmd {
634 relative_to: command.get_current_dir().map(|dir| dir.to_owned()),
635 command: GenCommand::PipeInto(command),
636 output_path: None,
637 selection: self,
638 }
639 }
640
641 pub fn as_arg_of<'a>(&'a self, command: &'a mut Command) -> GenerationCmd<'a> {
642 GenerationCmd {
643 relative_to: command.get_current_dir().map(|dir| dir.to_owned()),
644 command: GenCommand::Arg(command),
645 output_path: None,
646 selection: self,
647 }
648 }
649
650 pub fn to_input_json_pretty(&self) -> String {
651 serde_json::to_string_pretty(&create_input_from_selection(self)).unwrap()
652 }
653
654 pub fn to_input_json(&self) -> String {
655 serde_json::to_string(&create_input_from_selection(self)).unwrap()
656 }
657}
658
659#[derive(Debug)]
660pub struct GenerationSummary {
661 pub relative_to: PathBuf,
663 pub output_files: Vec<(String, usize)>,
665}
666
667impl GenerationSummary {
668 pub fn print(self) -> Self {
669 eprintln!("{self:?}");
670 self
671 }
672}
673
674impl<'a> GenerationCmd<'a> {
675 pub fn with_output_path<P: Into<PathBuf>>(&mut self, path: P) -> &mut Self {
677 self.output_path = Some(path.into());
678 self
679 }
680
681 fn get_output_path(&self) -> PathBuf {
682 if let Some(ref rel) = self.relative_to {
683 rel.join(self.output_path.clone().unwrap_or_else(|| ".".into()))
684 } else {
685 self.output_path.clone().unwrap_or_else(|| ".".into())
686 }
687 }
688
689 #[track_caller]
690 pub fn print(&mut self) {
691 let output = self.generate();
692 for err in &output.warnings {
693 eprintln!("Output warning:\n{err:?}")
694 }
695 for err in &output.errors {
696 eprintln!("Output error:\n{err:?}")
697 }
698
699 let relative_to = self.get_output_path();
700 for output_file in output.files {
701 let write_path = relative_to.join(output_file.path);
702 println!("\x1b[48:2:255:165:0m{}\x1b[0m", write_path.display());
703 println!("{}", output_file.source);
704 }
705 }
706
707 #[track_caller]
708 pub fn write(&mut self) -> GenerationSummary {
709 let output = self.generate();
710 for err in output.warnings.iter() {
711 eprintln!("Output warning:\n{err:?}")
712 }
713 for err in output.errors.iter() {
714 eprintln!("Output error:\n{err:?}")
715 }
716
717 let relative_to = self.get_output_path();
718 let mut summary = GenerationSummary {
719 relative_to: relative_to.clone(),
720 output_files: Vec::new(),
721 };
722 for output_file in output.files.iter() {
723 let write_path = relative_to.join(&output_file.path);
724 if let Some(parent) = write_path.parent() {
725 std::fs::create_dir_all(parent).expect("creating missing directories");
726 }
727
728 let mut file = std::fs::File::create(write_path).expect("creating file");
729 write!(&mut file, "{}", output_file.source).expect("writing generated file");
730 summary
731 .output_files
732 .push((output_file.path.to_string(), output_file.source.len()));
733 }
734
735 summary
736 }
737
738 #[track_caller]
739 fn generate(&mut self) -> Output {
740 let inputs = create_input_from_selection(self.selection);
741 let stdout_output = match self.command {
742 GenCommand::PipeInto(ref mut cmd) => {
743 let cmd_str = format!("{cmd:?}");
744 let mut child = cmd
745 .stdout(std::process::Stdio::piped())
746 .stdin(std::process::Stdio::piped())
747 .spawn()
748 .map_err(|err| format!("Failure executing `{cmd_str}`: {err:?} "))
749 .expect("spawning process");
750
751 child
752 .stdin
753 .as_mut()
754 .unwrap()
755 .write_all(&serde_json::to_vec(&inputs).unwrap())
756 .unwrap();
757
758 child.wait_with_output().expect("failed to wait on child")
759 }
760 GenCommand::Arg(ref mut cmd) => {
761 let cmd_str = format!("{cmd:?} <input-json>");
762 let child = cmd
763 .arg(&serde_json::to_string(&inputs).unwrap())
764 .stdout(std::process::Stdio::piped())
765 .spawn()
766 .map_err(|err| format!("Failure executing `{cmd_str}`: {err:?} "))
767 .expect("spawning process");
768
769 child.wait_with_output().expect("failed to wait on child")
770 }
771 };
772
773 let stdout_str = String::from_utf8_lossy(&stdout_output.stdout);
774
775 if !stdout_output.status.success() {
776 eprintln!("Non-success status from generation");
777 if !stdout_str.trim().is_empty() {
778 eprintln!("Stdout:\n{stdout_str}");
779 }
780 std::process::exit(1);
781 }
782
783 serde_json::from_str::<Output>(&stdout_str)
784 .map_err(|err| {
785 format!(
786 "Failed to parsed output as JSON of output files: {err:?}, from:\n{stdout_str}"
787 )
788 })
789 .expect("parsing output")
790 }
791}
792
793fn create_input_from_selection(selection: &Generation) -> Input {
794 let tys = i_codegen_code::get_types_by_tags(&selection.tags);
795 let current_directory = std::env::current_dir()
796 .expect("getting current directory in order to find source files for line number mapping");
797 let type_root_converters: HashMap<String, TypeRootConverter> = tys
798 .iter()
799 .map(|root| root.file.clone())
800 .collect::<HashSet<_>>()
801 .into_par_iter()
802 .map(|file_name| {
803 let file = {
804 match std::fs::File::open(&file_name) {
805 Ok(found) => found,
806 Err(_) => {
807 match std::fs::File::open(
809 ¤t_directory.parent().unwrap().join(&file_name),
810 ) {
811 Ok(file) => file,
812 Err(_err) => {
813 return (
815 file_name.clone(),
816 TypeRootConverter {
817 file_name,
818 line_number_override: None,
819 lines: SourceLineNumberIndex {
820 newlines: vec![0usize],
821 is_crlf: false,
822 },
823 },
824 );
825 }
826 }
827 }
828 }
829 };
830
831 (
832 file_name.clone(),
833 TypeRootConverter {
834 file_name,
835 line_number_override: None,
836 lines: SourceLineNumberIndex::new(file),
837 },
838 )
839 })
840 .collect();
841
842 let mut functions = Vec::new();
843 let mut declarations = Vec::<InputDeclaration>::new();
844 for TypeRoot {
845 extras,
846 file,
847 line,
848 inner,
849 } in tys
850 {
851 let mut converter = type_root_converters.get(&file).unwrap().clone();
852 converter.line_number_override = Some(line);
853 let (root_id_span, root_item, attrs) = converter.unname(inner);
854 let (id, id_location) = converter.location_id(root_id_span);
855 match root_item {
856 st::RootItem::Container(container_format) => {
857 declarations.push(InputDeclaration {
858 id,
859 id_location,
860 container_kind: converter
861 .container_format_to_container_format(&attrs, container_format),
862 attrs,
863 });
864 }
865 st::RootItem::Function(function_format) => {
866 functions.push(FunctionDeclaration {
867 id,
868 id_location,
869 function: converter.function_format_to_function_format(function_format),
870 attrs,
871 });
872 }
873 }
874 for extra in extras {
876 let (id_span, container_format, attrs) = converter.unname(extra);
877 let (id, id_location) = converter.location_id(id_span);
878 declarations.push(InputDeclaration {
879 id,
880 id_location,
881 container_kind: converter
882 .container_format_to_container_format(&attrs, container_format),
883 attrs,
884 });
885 }
886 }
887
888 Input {
889 declarations,
890 functions,
891 }
892}