pilota_build/codegen/
mod.rs

1use std::{
2    io::Write,
3    ops::Deref,
4    path::{Path, PathBuf},
5};
6
7use ahash::{AHashMap, AHashSet};
8use dashmap::{DashMap, mapref::one::RefMut};
9use faststr::FastStr;
10use itertools::Itertools;
11use normpath::PathExt;
12use pkg_tree::PkgNode;
13use quote::quote;
14use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
15use traits::CodegenBackend;
16
17use self::workspace::Workspace;
18use crate::{
19    Context, Symbol,
20    db::RirDatabase,
21    dedup::def_id_equal,
22    fmt::fmt_file,
23    middle::{
24        self,
25        context::{Mode, tls::CUR_ITEM},
26        rir,
27    },
28    rir::{Item, NodeKind},
29    symbol::{DefId, EnumRepr, FileId, ModPath},
30    tags::protobuf::Deprecated,
31};
32
33pub(crate) mod pkg_tree;
34pub mod toml;
35pub(crate) mod traits;
36
37mod workspace;
38
39pub mod pb;
40pub mod thrift;
41
42#[derive(Clone)]
43pub struct Codegen<B> {
44    backend: B,
45}
46
47impl<B> Deref for Codegen<B>
48where
49    B: CodegenBackend,
50{
51    type Target = Context;
52
53    fn deref(&self) -> &Self::Target {
54        self.backend.cx()
55    }
56}
57
58impl<B> Codegen<B> {
59    pub fn new(backend: B) -> Self {
60        Codegen { backend }
61    }
62}
63
64#[derive(Clone, Copy)]
65pub enum CodegenKind {
66    Direct,
67    RePub,
68}
69
70#[derive(Clone, Copy)]
71pub struct CodegenItem {
72    def_id: DefId,
73    kind: CodegenKind,
74}
75
76impl From<DefId> for CodegenItem {
77    fn from(value: DefId) -> Self {
78        CodegenItem {
79            def_id: value,
80            kind: CodegenKind::Direct,
81        }
82    }
83}
84
85impl<B> Codegen<B>
86where
87    B: CodegenBackend + Send,
88{
89    fn is_deprecated(&self, def_id: DefId) -> bool {
90        self.node_tags(def_id)
91            .and_then(|tags| tags.get::<Deprecated>().map(|d| d.0))
92            .unwrap_or_default()
93    }
94
95    pub fn write_struct(&self, def_id: DefId, stream: &mut String, s: &rir::Message) {
96        let name = self.rust_name(def_id);
97
98        let mut fields = s
99            .fields
100            .iter()
101            .map(|f| {
102                let name = self.rust_name(f.did);
103                self.with_adjust(f.did, |adjust| {
104                    let ty = self.codegen_item_ty(f.ty.kind.clone());
105                    let mut ty = format!("{ty}");
106
107                    if let Some(adjust) = adjust {
108                        if adjust.boxed() {
109                            ty = format!("::std::boxed::Box<{ty}>")
110                        }
111                    }
112
113                    if f.is_optional() {
114                        ty = format!("::std::option::Option<{ty}>")
115                    }
116
117                    let attrs = adjust.iter().flat_map(|a| a.attrs()).join("");
118
119                    let deprecated_attr = if self.is_deprecated(f.did) {
120                        "#[deprecated]\n"
121                    } else {
122                        ""
123                    };
124
125                    if self.config.with_comments {
126                        let leading_comment = f.leading_comments.to_string();
127                        let trailing_comment = f.trailing_comments.to_string();
128
129                        format! {
130                            r#"
131                        {leading_comment}
132                        {attrs}
133                        {deprecated_attr}pub {name}: {ty},{trailing_comment}"#
134                        }
135                    } else {
136                        format! {
137                            r#"
138                        {attrs}
139                        {deprecated_attr}pub {name}: {ty},"#
140                        }
141                    }
142                })
143            })
144            .join("\n");
145
146        if self.cache.keep_unknown_fields.contains(&def_id) {
147            fields.push_str("\npub _unknown_fields: ::pilota::BytesVec,\n");
148        }
149
150        if !s.is_wrapper && self.config.with_field_mask {
151            fields.push_str(
152                "\npub _field_mask: ::std::option::Option<::pilota_thrift_fieldmask::FieldMask>,\n",
153            );
154        }
155
156        let deprecated_attr = if self.is_deprecated(def_id) {
157            "#[deprecated]\n"
158        } else {
159            ""
160        };
161
162        let trailing_comment = if self.config.with_comments {
163            s.trailing_comments.as_str()
164        } else {
165            ""
166        };
167
168        stream.push_str(&format! {
169            r#"#[derive(Clone, PartialEq)]
170                {deprecated_attr}pub struct {name} {{
171                    {fields}
172                }}{trailing_comment}"#
173        });
174
175        self.backend.codegen_struct_impl(def_id, stream, s);
176    }
177
178    pub fn write_item(
179        &self,
180        stream: &mut String,
181        item: CodegenItem,
182        dup: &mut AHashMap<FastStr, Vec<DefId>>,
183    ) {
184        CUR_ITEM.set(&item.def_id, || match item.kind {
185            CodegenKind::Direct => {
186                if !self.duplicate(dup, item.def_id) {
187                    let def_id = item.def_id;
188                    let item = self.item(def_id).unwrap();
189                    tracing::trace!("write item {}", item.symbol_name());
190
191                    // write leading comments
192                    let comments = if self.config.with_comments {
193                        match &*item {
194                            middle::rir::Item::Message(s) => s.leading_comments.as_str(),
195                            middle::rir::Item::Enum(e) => e.leading_comments.as_str(),
196                            middle::rir::Item::Service(s) => s.leading_comments.as_str(),
197                            middle::rir::Item::NewType(t) => t.leading_comments.as_str(),
198                            middle::rir::Item::Const(c) => c.leading_comments.as_str(),
199                            _ => "",
200                        }
201                    } else {
202                        ""
203                    };
204
205                    if !comments.is_empty() {
206                        stream.push_str(&format!("\n{comments}\n"));
207                    }
208
209                    self.with_adjust(def_id, |adjust| {
210                        let attrs = adjust.iter().flat_map(|a| a.attrs()).join("\n");
211
212                        let impls = adjust
213                            .iter()
214                            .flat_map(|a| &a.nested_items)
215                            .sorted()
216                            .join("\n");
217                        stream.push_str(&impls);
218                        stream.push_str(&attrs);
219                    });
220
221                    match &*item {
222                        middle::rir::Item::Message(s) => {
223                            self.write_struct(def_id, stream, s);
224                        }
225                        middle::rir::Item::Enum(e) => self.write_enum(def_id, stream, e),
226                        middle::rir::Item::Service(s) => self.write_service(def_id, stream, s),
227                        middle::rir::Item::NewType(t) => self.write_new_type(def_id, stream, t),
228                        middle::rir::Item::Const(c) => self.write_const(def_id, stream, c),
229                        middle::rir::Item::Mod(m) => {
230                            let name = self.rust_name(def_id);
231                            let mut inner = Default::default();
232                            self.backend.codegen_pilota_trait(&mut inner);
233                            m.items.iter().for_each(|def_id| {
234                                self.write_item(&mut inner, (*def_id).into(), dup)
235                            });
236
237                            if self.config.with_descriptor && m.extensions.has_extendees() {
238                                let cur_pkg = self.item_path(def_id);
239                                self.backend.codegen_mod_exts(
240                                    &mut inner,
241                                    &name,
242                                    &cur_pkg,
243                                    &m.extensions,
244                                );
245                            }
246
247                            let name = self.rust_name(def_id);
248                            stream.push_str(&format! {
249                                r#"pub mod {name} {{
250                                    {inner}
251                                }}"#
252                            });
253                        }
254                    }
255                };
256            }
257            CodegenKind::RePub => {
258                let path = self
259                    .item_path(item.def_id)
260                    .iter()
261                    .map(|item| item.to_string())
262                    .join("::");
263                stream.push_str(format!("pub use ::{path};").as_str());
264            }
265        })
266    }
267
268    fn duplicate(&self, dup: &mut AHashMap<FastStr, Vec<DefId>>, def_id: DefId) -> bool {
269        let name = self.rust_name(def_id);
270        if !self.cache.dedups.contains(&name.0) {
271            return false;
272        }
273        let dup = dup.entry(name.0).or_default();
274        for id in dup.iter() {
275            if def_id_equal(self.nodes(), *id, def_id) {
276                return true;
277            }
278        }
279        dup.push(def_id);
280        false
281    }
282
283    pub fn write_enum_as_new_type(
284        &self,
285        def_id: DefId,
286        stream: &mut String,
287        e: &middle::rir::Enum,
288    ) {
289        let name = self.rust_name(def_id);
290
291        let repr = match e.repr {
292            Some(EnumRepr::I32) => quote!(i32),
293            _ => panic!(),
294        };
295
296        let variants = e
297            .variants
298            .iter()
299            .map(|v| {
300                let name = self.rust_name(v.did);
301
302                let discr = v.discr.unwrap();
303                let discr = match e.repr {
304                    Some(EnumRepr::I32) => discr as i32,
305                    None => panic!(),
306                };
307
308                let deprecated_attr = if self.is_deprecated(v.did) {
309                    "#[deprecated]\n"
310                } else {
311                    ""
312                };
313
314                (
315                    format!("{deprecated_attr}pub const {name}: Self = Self({discr});"),
316                    format!("Self({discr}) => ::std::string::String::from(\"{name}\"),"),
317                )
318            })
319            .collect::<Vec<_>>();
320        let variants_const = variants.iter().map(|(v, _)| v).join("");
321        let variants_as_str_fields = variants.iter().map(|(_, v)| v).join("");
322        let try_from_arms = e
323            .variants
324            .iter()
325            .map(|v| {
326                let name = self.rust_name(v.did);
327                let discr = v.discr.unwrap();
328                let discr = match e.repr {
329                    Some(EnumRepr::I32) => discr as i32,
330                    None => panic!(),
331                };
332                format!("{discr} => Some(Self::{name}),")
333            })
334            .join("\n");
335
336        let deprecated_attr = if self.is_deprecated(def_id) {
337            "#[deprecated]\n"
338        } else {
339            ""
340        };
341
342        let impl_enum_message = if self.config.with_descriptor {
343            self.backend.codegen_impl_enum_message(&name)
344        } else {
345            Default::default()
346        };
347
348        stream.push_str(&format! {
349            r#"#[derive(Clone, PartialEq, Copy)]
350            #[repr(transparent)]
351            {deprecated_attr}pub struct {name}({repr});
352
353            impl {name} {{
354                {variants_const}
355
356                pub fn inner(&self) -> {repr} {{
357                    self.0
358                }}
359
360                pub fn to_string(&self) -> ::std::string::String {{
361                    match self {{
362                        {variants_as_str_fields}
363                        Self(val) => val.to_string(),
364                    }}
365                }}
366
367                pub fn try_from_{repr}(value: {repr}) -> ::std::option::Option<Self> {{
368                    match value {{
369                        {try_from_arms}
370                        _ => None,
371                    }}
372                }}
373            }}
374
375            {impl_enum_message}
376
377            impl ::std::convert::From<{repr}> for {name} {{
378                fn from(value: {repr}) -> Self {{
379                    Self(value)
380                }}
381            }}
382
383            impl ::std::convert::From<{name}> for {repr} {{
384                fn from(value: {name}) -> {repr} {{
385                    value.0
386                }}
387            }}
388
389            "#
390        });
391
392        self.backend.codegen_enum_impl(def_id, stream, e);
393    }
394
395    pub fn write_enum(&self, def_id: DefId, stream: &mut String, e: &middle::rir::Enum) {
396        if e.repr.is_some() {
397            return self.write_enum_as_new_type(def_id, stream, e);
398        }
399        let name = self.rust_name(def_id);
400
401        let mut keep = true;
402        let mut variants = e
403            .variants
404            .iter()
405            .map(|v| {
406                let name = self.rust_name(v.did);
407
408                self.with_adjust(v.did, |adjust| {
409                    let attrs = adjust.iter().flat_map(|a| a.attrs()).join("\n");
410
411                    let fields = v
412                        .fields
413                        .iter()
414                        .map(|ty| self.codegen_item_ty(ty.kind.clone()).to_string())
415                        .join(",");
416
417                    let fields_stream = if fields.is_empty() {
418                        keep = false;
419                        Default::default()
420                    } else {
421                        format!("({fields})")
422                    };
423
424                    let leading_comment = if self.config.with_comments {
425                        v.leading_comments.as_str()
426                    } else {
427                        ""
428                    };
429
430                    format!(
431                        r#"{leading_comment}
432                        {attrs}
433                        {name} {fields_stream},"#
434                    )
435                })
436            })
437            .join("\n");
438
439        if self.cache.keep_unknown_fields.contains(&def_id) && keep {
440            variants.push_str("_UnknownFields(::pilota::BytesVec),");
441        }
442        let trailing_comment = if self.config.with_comments {
443            e.trailing_comments.as_str()
444        } else {
445            ""
446        };
447        stream.push_str(&format! {
448            r#"
449            #[derive(Clone, PartialEq)]
450            pub enum {name} {{
451                {variants}
452            }}{trailing_comment}
453            "#
454        });
455
456        self.backend.codegen_enum_impl(def_id, stream, e);
457    }
458
459    pub fn write_service(&self, def_id: DefId, stream: &mut String, s: &middle::rir::Service) {
460        let name = self.rust_name(def_id);
461        let methods = self.service_methods(def_id);
462
463        let methods = methods
464            .iter()
465            .map(|m| self.backend.codegen_service_method(def_id, m))
466            .join("\n");
467
468        let deprecated_attr = if self.is_deprecated(def_id) {
469            "#[deprecated]\n"
470        } else {
471            ""
472        };
473
474        stream.push_str(&format! {
475            r#"
476            {deprecated_attr}pub trait {name} {{
477                {methods}
478            }}
479            "#
480        });
481        self.backend.codegen_service_impl(def_id, stream, s);
482    }
483
484    /// get service information for volo-cli init, return path of service and
485    /// methods
486    pub fn get_init_service(&self, def_id: DefId) -> (String, String) {
487        CUR_ITEM.set(&def_id, || {
488            let service_name = self.rust_name(def_id).to_string();
489            let mod_prefix = self.mod_path(def_id);
490            let service_path = if mod_prefix.is_empty() {
491                service_name
492            } else {
493                let mod_path = mod_prefix.iter().map(|item| item.to_string()).join("::");
494                format!("{mod_path}::{service_name}")
495            };
496            tracing::debug!("service_path: {}", service_path);
497            let methods = self.service_methods(def_id);
498
499            let methods = methods
500                .iter()
501                .map(|m| {
502                    self.backend
503                        .codegen_service_method_with_global_path(def_id, m)
504                })
505                .join("\n");
506
507            (service_path, methods)
508        })
509    }
510
511    // pick first service as init service from idlservice
512    pub fn pick_init_service(&self, path: PathBuf) -> anyhow::Result<(String, String)> {
513        // convert path to absolute path to match with file_id_map
514        let path = path
515            .normalize()
516            .map_err(|e| {
517                anyhow::Error::msg(format!(
518                    "Normalize path {} failed: {}, please check service path",
519                    path.display(),
520                    e
521                ))
522            })?
523            .into_path_buf();
524        tracing::debug!("path {:?}", path);
525        let file_id: FileId = self.file_id(path).unwrap();
526        let item = self
527            .cache
528            .codegen_items
529            .iter()
530            .copied()
531            .filter(|def_id| {
532                // select service kind
533                let item = self.item(*def_id).unwrap();
534                matches!(&*item, middle::rir::Item::Service(_))
535            })
536            .find(
537                // check for same file
538                |def_id| self.node(*def_id).unwrap().file_id == file_id,
539            );
540        match item {
541            Some(def_id) => Ok(self.get_init_service(def_id)),
542            None => Err(anyhow::anyhow!("No service found.")),
543        }
544    }
545
546    pub fn write_new_type(&self, def_id: DefId, stream: &mut String, t: &middle::rir::NewType) {
547        let name = self.rust_name(def_id);
548        let ty = self.codegen_item_ty(t.ty.kind.clone());
549        stream.push_str(&format! {
550            r#"
551            #[derive(Clone, PartialEq)]
552            pub struct {name}(pub {ty});
553
554            impl ::std::ops::Deref for {name} {{
555                type Target = {ty};
556
557                fn deref(&self) -> &Self::Target {{
558                    &self.0
559                }}
560            }}
561
562            impl From<{ty}> for {name} {{
563                fn from(v: {ty}) -> Self {{
564                    Self(v)
565                }}
566            }}
567
568            "#
569        });
570        self.backend.codegen_newtype_impl(def_id, stream, t);
571    }
572
573    pub fn write_const(&self, did: DefId, stream: &mut String, c: &middle::rir::Const) {
574        let mut ty = self.codegen_ty(did);
575
576        let name = self.rust_name(did);
577
578        if name.to_string().starts_with("__PILOTA_PB_EXT_") {
579            return;
580        }
581
582        stream.push_str(&self.def_lit(&name, &c.lit, &mut ty).unwrap())
583    }
584
585    pub fn write_workspace(self, base_dir: PathBuf) -> anyhow::Result<()> {
586        let ws = Workspace::new(base_dir, self);
587        ws.write_crates()
588    }
589
590    pub fn write_items(
591        &self,
592        stream: &mut String,
593        mod_items: AHashMap<ModPath, Vec<CodegenItem>>,
594        base_dir: &Path,
595    ) {
596        // collect mod files and file has direct
597        let mut mod_files = AHashMap::<ModPath, AHashSet<FileId>>::default();
598        let mut file_has_direct = AHashMap::default();
599
600        for (mod_path, items) in mod_items.iter() {
601            for item in items.iter() {
602                let file_id = self.node(item.def_id).unwrap().file_id;
603                let set = mod_files.entry(mod_path.clone()).or_default();
604                if !set.contains(&file_id) {
605                    set.insert(file_id);
606                    file_has_direct.insert(file_id, false);
607                }
608                if matches!(item.kind, CodegenKind::Direct) {
609                    *file_has_direct.get_mut(&file_id).unwrap() = true;
610                }
611            }
612        }
613
614        // 1. global level
615        // 1.1 register mod file descriptor
616        if self.config.with_descriptor {
617            let mods_files_with_direct_items = mod_items
618                .keys()
619                .flat_map(|mod_path| {
620                    mod_files
621                        .get(mod_path)
622                        .unwrap()
623                        .iter()
624                        .filter_map(|file_id| {
625                            if *file_has_direct.get(file_id).unwrap()
626                                && let Some(file_path) = self.file_paths().get(file_id)
627                            {
628                                Some((mod_path.clone(), file_path.clone()))
629                            } else {
630                                None
631                            }
632                        })
633                        .collect::<Vec<_>>()
634                })
635                .collect::<Vec<_>>();
636            if !mods_files_with_direct_items.is_empty() {
637                self.backend
638                    .codegen_register_mod_file_descriptor(stream, &mods_files_with_direct_items);
639            }
640        }
641
642        // 2. mod stream level
643        let mut pkgs: DashMap<ModPath, String> = Default::default();
644        let this = self.clone();
645        mod_items
646            .par_iter()
647            .for_each_with(this, |this, (mod_path, items)| {
648                let mut stream = pkgs.entry(mod_path.clone()).or_default();
649                // 2.1 file
650                for file_id in mod_files.get(mod_path).unwrap().iter() {
651                    let file = this.file(*file_id).unwrap();
652                    // 2.1.1 comments
653                    if !file.comments.is_empty() && this.config.with_comments {
654                        stream.push_str(&format!("\n{}\n", file.comments));
655                    }
656
657                    if this.config.with_descriptor && *file_has_direct.get(file_id).unwrap() {
658                        // 2.1.2 file descriptor
659                        this.backend.codegen_file_descriptor_at_mod(
660                            &mut stream,
661                            &file,
662                            mod_path,
663                            *file_has_direct.get(file_id).unwrap(),
664                        );
665
666                        // 2.1.3 file extensions
667                        let file_pkg = file
668                            .package
669                            .iter()
670                            .map(|s| s.0.clone())
671                            .collect::<Vec<_>>()
672                            .join("::");
673                        if file_pkg == "google::protobuf" {
674                            continue;
675                        }
676                        let mod_pkg = mod_path.iter().cloned().collect::<Vec<_>>().join("::");
677                        if file_pkg == mod_pkg {
678                            let filename_lower = this
679                                .file_name(*file_id)
680                                .unwrap()
681                                .replace(".", "_")
682                                .to_lowercase();
683                            if file.extensions.has_extendees() {
684                                this.backend.codegen_file_exts(
685                                    &mut stream,
686                                    &filename_lower,
687                                    &file.package,
688                                    &file.extensions,
689                                );
690                            }
691                        }
692                    }
693                }
694
695                // 2.2 mod
696                let mut dup = AHashMap::default();
697
698                let span = tracing::span!(tracing::Level::TRACE, "write_mod", path = ?mod_path);
699                let _enter = span.enter();
700
701                if let Some(mod_idx) = this.cache.mod_idxes.get(mod_path) {
702                    let mod_item = this.item(*mod_idx).unwrap();
703
704                    if let middle::rir::Item::Mod(m) = &*mod_item {
705                        if this.config.with_descriptor && m.extensions.has_extendees() {
706                            let name = this.rust_name(*mod_idx);
707                            let cur_pkg = this.item_path(*mod_idx).clone();
708                            this.backend.codegen_mod_exts(
709                                &mut stream,
710                                &name,
711                                &cur_pkg,
712                                &m.extensions,
713                            );
714                        }
715                    }
716                }
717
718                // 2.3 items
719                if this.config.split {
720                    Self::write_split_mod(this, base_dir, mod_path, items, &mut stream, &mut dup);
721                } else {
722                    for def_id in items.iter() {
723                        this.write_item(&mut stream, *def_id, &mut dup)
724                    }
725                }
726            });
727
728        let keys = pkgs.iter().map(|kv| kv.key().clone()).collect_vec();
729        let pkg_node = PkgNode::from_pkgs(&keys.iter().map(|s| &**s).collect_vec());
730        tracing::debug!(?pkg_node);
731
732        self.write_stream(&mut pkgs, stream, &pkg_node);
733    }
734
735    fn write_stream(
736        &self,
737        pkgs: &mut DashMap<ModPath, String>,
738        stream: &mut String,
739        nodes: &[PkgNode],
740    ) {
741        for node in nodes.iter().sorted_by_key(|x| &x.path) {
742            let mut inner_stream = String::default();
743            if let Some((_, node_stream)) = pkgs.remove(&node.path) {
744                inner_stream.push_str(&node_stream);
745            }
746
747            self.write_stream(pkgs, &mut inner_stream, &node.children);
748            let name = node.ident();
749            if name.clone().unwrap_or_default() == "" {
750                stream.push_str(&inner_stream);
751                return;
752            }
753
754            let name = Symbol::from(name.unwrap());
755            let mut pilota_buf_trait = Default::default();
756            self.backend.codegen_pilota_trait(&mut pilota_buf_trait);
757            stream.push_str(&format! {
758                r#"
759                pub mod {name} {{
760                    {pilota_buf_trait}
761                    {inner_stream}
762                }}
763                "#
764            });
765        }
766    }
767
768    fn write_split_mod(
769        this: &mut Codegen<B>,
770        base_dir: &Path,
771        mod_path: &ModPath,
772        def_ids: &[CodegenItem],
773        stream: &mut RefMut<ModPath, String>,
774        dup: &mut AHashMap<FastStr, Vec<DefId>>,
775    ) {
776        let base_mod_name = mod_path.iter().map(|s| s.to_string()).join("/");
777        let mod_file_name = format!("{base_mod_name}/mod.rs");
778        let mut mod_stream = String::new();
779
780        let mut existing_file_names: AHashSet<String> = AHashSet::new();
781
782        for def_id in def_ids.iter() {
783            let mut item_stream = String::new();
784            let node = this.db.node(def_id.def_id).unwrap();
785            let name_prefix = match node.kind {
786                NodeKind::Item(ref item) => match item.as_ref() {
787                    Item::Message(_) => "message",
788                    Item::Enum(_) => "enum",
789                    Item::Service(_) => "service",
790                    Item::NewType(_) => "new_type",
791                    Item::Const(_) => "const",
792                    Item::Mod(_) => "mod",
793                },
794                NodeKind::Variant(_) => "variant",
795                NodeKind::Field(_) => "field",
796                NodeKind::Method(_) => "method",
797                NodeKind::Arg(_) => "arg",
798            };
799
800            let mod_dir = base_dir.join(base_mod_name.clone());
801
802            let simple_name = format!("{}_{}", name_prefix, node.name());
803            let unique_name = Self::generate_unique_name(&existing_file_names, &simple_name);
804            existing_file_names.insert(unique_name.to_ascii_lowercase().clone());
805            let file_name = format!("{unique_name}.rs");
806            this.write_item(&mut item_stream, *def_id, dup);
807
808            let full_path = mod_dir.join(file_name.clone());
809            std::fs::create_dir_all(mod_dir).unwrap();
810
811            let item_stream = item_stream.lines().map(|s| s.trim_end()).join("\n");
812            let mut file =
813                std::io::BufWriter::new(std::fs::File::create(full_path.clone()).unwrap());
814            file.write_all(item_stream.as_bytes()).unwrap();
815            file.flush().unwrap();
816            fmt_file(full_path);
817
818            mod_stream.push_str(format!("include!(\"{file_name}\");\n").as_str());
819        }
820
821        let mod_path = base_dir.join(&mod_file_name);
822        let mod_stream = mod_stream.lines().map(|s| s.trim_end()).join("\n");
823        let mut mod_file = std::io::BufWriter::new(std::fs::File::create(&mod_path).unwrap());
824        mod_file.write_all(mod_stream.as_bytes()).unwrap();
825        mod_file.flush().unwrap();
826        fmt_file(&mod_path);
827
828        stream.push_str(format!("include!(\"{mod_file_name}\");\n").as_str());
829    }
830
831    /**
832        On Windows and macOS, files names are case-insensitive
833        To avoid problems when generating files for services with similar names, e.g.
834        testService and TestService, such names are de-duplicated by adding a number to their nam5e
835    */
836    fn generate_unique_name(existing_names: &AHashSet<String>, simple_name: &str) -> String {
837        let mut counter = 1;
838        let mut name = simple_name.to_string();
839        while existing_names.contains(name.to_ascii_lowercase().as_str()) {
840            counter += 1;
841            name = format!("{simple_name}_{counter}")
842        }
843        name
844    }
845
846    fn collect_direct_codegen_items(
847        &self,
848        mod_items: &AHashMap<ModPath, Vec<DefId>>,
849    ) -> AHashMap<ModPath, Vec<CodegenItem>> {
850        mod_items
851            .iter()
852            .map(|(mod_path, items)| {
853                (
854                    mod_path.clone(),
855                    items.iter().map(|def_id| (*def_id).into()).collect_vec(),
856                )
857            })
858            .collect::<AHashMap<_, _>>()
859    }
860
861    pub fn write_file(self, ns_name: Symbol, file_name: impl AsRef<Path>) {
862        let base_dir = file_name.as_ref().parent().unwrap();
863        let mut stream = String::default();
864        self.backend.codegen_pilota_trait(&mut stream);
865
866        let mod_items = self.collect_direct_codegen_items(&self.cache.mod_items);
867
868        self.write_items(&mut stream, mod_items, base_dir);
869
870        stream = format! {r#"pub mod {ns_name} {{
871                #![allow(warnings, clippy::all)]
872                {stream}
873            }}"#};
874        let stream = stream.lines().map(|s| s.trim_end()).join("\n");
875        let mut file = std::io::BufWriter::new(std::fs::File::create(&file_name).unwrap());
876        file.write_all(stream.as_bytes()).unwrap();
877        file.flush().unwrap();
878        fmt_file(file_name)
879    }
880
881    pub fn r#gen(self) -> anyhow::Result<()> {
882        match &*self.source.mode.clone() {
883            Mode::Workspace(info) => self.write_workspace(info.dir.clone()),
884            Mode::SingleFile { file_path: p } => {
885                self.write_file(
886                    FastStr::new(
887                        p.file_name()
888                            .and_then(|s| s.to_str())
889                            .and_then(|s| s.split('.').next())
890                            .unwrap(),
891                    )
892                    .into(),
893                    p,
894                );
895                Ok(())
896            }
897        }
898    }
899}