pilota_build/middle/
context.rs

1use std::{collections::HashMap, ops::Deref, path::PathBuf, sync::Arc};
2
3use ahash::{AHashMap, HashSet};
4use anyhow::Context as _;
5use dashmap::DashMap;
6use faststr::FastStr;
7use itertools::Itertools;
8use normpath::PathExt;
9use rustc_hash::{FxHashMap, FxHashSet};
10
11use self::tls::with_cur_item;
12use super::{
13    adjust::Adjust,
14    resolver::{DefaultPathResolver, PathResolver, WorkspacePathResolver},
15    rir::NodeKind,
16};
17use crate::{
18    Plugin,
19    db::{RirDatabase, RootDatabase},
20    rir::{self, Field, Item, ItemPath, Literal},
21    symbol::{DefId, FileId, IdentName, ModPath, SPECIAL_NAMINGS, Symbol},
22    tags::{TagId, Tags},
23    ty::{AdtDef, AdtKind, CodegenTy, Visitor},
24};
25
26#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone)]
27pub struct CrateId {
28    pub(crate) main_file: FileId,
29}
30
31#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone)]
32pub enum DefLocation {
33    Fixed(CrateId, ItemPath),
34    Dynamic,
35}
36
37pub enum CollectMode {
38    All,
39    OnlyUsed {
40        touches: Vec<(std::path::PathBuf, Vec<String>)>,
41    },
42}
43
44#[derive(Debug)]
45pub struct WorkspaceInfo {
46    pub dir: PathBuf,
47    pub(crate) location_map: FxHashMap<DefId, DefLocation>,
48}
49
50#[derive(Debug)]
51pub enum Mode {
52    Workspace(WorkspaceInfo),
53    SingleFile { file_path: std::path::PathBuf },
54}
55
56pub struct Context {
57    pub db: RootDatabase,
58    pub source: Source,
59    pub config: Config,
60    pub cache: Cache,
61}
62
63#[derive(Clone)]
64pub struct Source {
65    pub source_type: SourceType,
66    pub services: Arc<[crate::IdlService]>,
67    pub mode: Arc<Mode>,
68    pub path_resolver: Arc<dyn PathResolver>,
69}
70
71#[derive(Clone)]
72pub struct Config {
73    pub change_case: bool,
74    pub split: bool,
75    pub with_descriptor: bool,
76    pub with_field_mask: bool,
77    pub touch_all: bool,
78    pub common_crate_name: FastStr,
79    pub with_comments: bool,
80}
81
82#[derive(Clone)]
83pub struct Cache {
84    pub adjusts: Arc<DashMap<DefId, Adjust>>,
85    pub mod_idxes: AHashMap<ModPath, DefId>, // mod kind index
86    pub codegen_items: Vec<DefId>,
87    pub mod_items: AHashMap<ModPath, Vec<DefId>>,
88    pub def_mod: HashMap<DefId, ModPath>,
89    pub mod_files: HashMap<ModPath, Vec<FileId>>,
90    pub keep_unknown_fields: Arc<FxHashSet<DefId>>,
91    pub location_map: Arc<FxHashMap<DefId, DefLocation>>,
92    pub entry_map: Arc<HashMap<DefLocation, Vec<(DefId, DefLocation)>>>,
93    pub plugin_gen: Arc<DashMap<DefLocation, String>>,
94    pub dedups: Vec<FastStr>,
95    pub names: FxHashMap<DefId, usize>,
96}
97
98impl Clone for Context {
99    fn clone(&self) -> Self {
100        Self {
101            db: self.db.clone(),
102            source: self.source.clone(),
103            config: self.config.clone(),
104            cache: self.cache.clone(),
105        }
106    }
107}
108
109pub(crate) struct ContextBuilder {
110    db: RootDatabase,
111    pub(crate) codegen_items: Vec<DefId>,
112    input_items: Vec<DefId>,
113    mode: Mode,
114    keep_unknown_fields: FxHashSet<DefId>,
115    pub location_map: FxHashMap<DefId, DefLocation>,
116    entry_map: HashMap<DefLocation, Vec<(DefId, DefLocation)>>,
117}
118
119impl ContextBuilder {
120    pub fn new(db: RootDatabase, mode: Mode, input_items: Vec<DefId>) -> Self {
121        ContextBuilder {
122            db,
123            mode,
124            input_items,
125            codegen_items: Default::default(),
126            keep_unknown_fields: Default::default(),
127            location_map: Default::default(),
128            entry_map: Default::default(),
129        }
130    }
131    pub(crate) fn collect(&mut self, mode: CollectMode) {
132        match mode {
133            CollectMode::All => {
134                let nodes = self.db.nodes();
135                self.codegen_items
136                    .extend(nodes.iter().filter_map(|(k, v)| match &v.kind {
137                        NodeKind::Item(i) => {
138                            if !matches!(&**i, Item::Mod(_)) {
139                                Some(k)
140                            } else {
141                                None
142                            }
143                        }
144                        _ => None,
145                    }));
146            }
147            CollectMode::OnlyUsed { touches } => {
148                let extra_def_ids = touches
149                    .into_iter()
150                    .flat_map(|s| {
151                        let path = s.0.normalize().unwrap().into_path_buf();
152                        let file_id = *self.db.file_ids_map().get(&path).unwrap();
153                        s.1.into_iter()
154                            .filter_map(|item_name| {
155                                let def_id = self
156                                    .db
157                                    .files()
158                                    .get(&file_id)
159                                    .unwrap()
160                                    .items
161                                    .iter()
162                                    .find(|def_id| {
163                                        *self.db.item(**def_id).unwrap().symbol_name() == item_name
164                                    })
165                                    .cloned();
166                                if let Some(def_id) = def_id {
167                                    Some(def_id)
168                                } else {
169                                    println!(
170                                        "cargo:warning=item `{item_name}` of `{}` not exists",
171                                        path.display(),
172                                    );
173                                    None
174                                }
175                            })
176                            .collect::<Vec<_>>()
177                    })
178                    .collect::<Vec<_>>();
179
180                self.input_items.extend(extra_def_ids);
181
182                let def_ids = self.collect_items(&self.input_items);
183                self.codegen_items.extend(def_ids.iter());
184            }
185        }
186        if matches!(self.mode, Mode::Workspace(_)) {
187            let location_map = self.workspace_collect_def_ids(&self.codegen_items);
188            self.location_map = location_map.clone();
189            self.entry_map = location_map
190                .clone()
191                .into_iter()
192                .into_group_map_by(|item| item.1.clone());
193            if let Mode::Workspace(info) = &mut self.mode {
194                info.location_map = location_map
195            }
196        }
197    }
198
199    pub(crate) fn collect_items(&self, input: &[DefId]) -> FxHashSet<DefId> {
200        struct PathCollector<'a> {
201            set: &'a mut FxHashSet<DefId>,
202            cx: &'a ContextBuilder,
203            file_ids: &'a mut FxHashSet<FileId>,
204        }
205
206        impl super::ty::Visitor for PathCollector<'_> {
207            fn visit_path(&mut self, path: &crate::rir::Path) {
208                collect(self.cx, path.did, self.set, self.file_ids)
209            }
210        }
211
212        fn collect(
213            cx: &ContextBuilder,
214            def_id: DefId,
215            set: &mut FxHashSet<DefId>,
216            file_ids: &mut FxHashSet<FileId>,
217        ) {
218            if set.contains(&def_id) {
219                return;
220            }
221
222            let node = cx.db.node(def_id).unwrap();
223
224            if !file_ids.contains(&node.file_id) {
225                file_ids.insert(node.file_id);
226
227                let file = cx.db.file(node.file_id).unwrap();
228                if file.extensions.has_used_options() {
229                    file.extensions
230                        .unwrap_as_pb()
231                        .used_options
232                        .0
233                        .iter()
234                        .for_each(|option| {
235                            let extendee = cx.db.pb_ext(option).unwrap();
236                            PathCollector { cx, set, file_ids }
237                                .visit(&extendee.extendee_ty.item_ty);
238                        });
239                }
240            }
241
242            match node.kind {
243                NodeKind::Item(_) => {}
244                _ => return collect(cx, node.parent.unwrap(), set, file_ids),
245            }
246
247            if !matches!(&*cx.db.item(def_id).unwrap(), rir::Item::Mod(_)) {
248                set.insert(def_id);
249            }
250
251            let node = cx.db.node(def_id).unwrap();
252            tracing::trace!("collecting {:?}", node.expect_item().symbol_name());
253
254            node.related_nodes
255                .iter()
256                .for_each(|def_id| collect(cx, *def_id, set, file_ids));
257
258            let item = node.expect_item();
259
260            match item {
261                rir::Item::Message(m) => {
262                    // collect fields
263                    m.fields.iter().for_each(|f| {
264                        PathCollector { cx, set, file_ids }.visit(&f.ty);
265                        if let Some(Literal::Path(p)) = &f.default {
266                            PathCollector { cx, set, file_ids }.visit_path(p);
267                        }
268                        // collect extensions
269                        if f.item_exts.has_used_options() {
270                            f.item_exts
271                                .unwrap_as_pb()
272                                .used_options
273                                .0
274                                .iter()
275                                .for_each(|index| {
276                                    let extendee = cx.db.pb_ext(index).unwrap_or_else(|| {
277                                        panic!("extension:{:?} not found", index)
278                                    });
279                                    PathCollector { cx, set, file_ids }
280                                        .visit(&extendee.extendee_ty.item_ty);
281                                });
282                        }
283                    });
284                    // collect extensions
285                    if m.item_exts.has_used_options() {
286                        m.item_exts
287                            .unwrap_as_pb()
288                            .used_options
289                            .0
290                            .iter()
291                            .for_each(|index| {
292                                let extendee = cx.db.pb_ext(index).unwrap();
293                                PathCollector { cx, set, file_ids }
294                                    .visit(&extendee.extendee_ty.item_ty);
295                            });
296                    }
297                }
298                rir::Item::Enum(e) => {
299                    e.variants.iter().for_each(|v| {
300                        for ty in &v.fields {
301                            PathCollector { cx, set, file_ids }.visit(ty);
302                        }
303                        if v.item_exts.has_used_options() {
304                            v.item_exts
305                                .unwrap_as_pb()
306                                .used_options
307                                .0
308                                .iter()
309                                .for_each(|index| {
310                                    let extendee = cx.db.pb_ext(index).unwrap();
311                                    PathCollector { cx, set, file_ids }
312                                        .visit(&extendee.extendee_ty.item_ty);
313                                });
314                        }
315                    });
316                    if e.item_exts.has_used_options() {
317                        e.item_exts
318                            .unwrap_as_pb()
319                            .used_options
320                            .0
321                            .iter()
322                            .for_each(|index| {
323                                let extendee = cx.db.pb_ext(index).unwrap();
324                                PathCollector { cx, set, file_ids }
325                                    .visit(&extendee.extendee_ty.item_ty);
326                            });
327                    }
328                }
329                rir::Item::Service(s) => {
330                    s.extend
331                        .iter()
332                        .for_each(|p| collect(cx, p.did, set, file_ids));
333                    s.methods.iter().for_each(|m| {
334                        // collect args
335                        m.args
336                            .iter()
337                            .for_each(|f| PathCollector { cx, set, file_ids }.visit(&f.ty));
338                        // collect ret
339                        PathCollector { cx, set, file_ids }.visit(&m.ret);
340                        // collect exceptions
341                        if let Some(exceptions) = &m.exceptions {
342                            PathCollector { cx, set, file_ids }.visit_path(exceptions);
343                        }
344                        // collect extensions
345                        if m.item_exts.has_used_options() {
346                            m.item_exts
347                                .unwrap_as_pb()
348                                .used_options
349                                .0
350                                .iter()
351                                .for_each(|index| {
352                                    let extendee = cx.db.pb_ext(index).unwrap();
353                                    PathCollector { cx, set, file_ids }
354                                        .visit(&extendee.extendee_ty.item_ty);
355                                });
356                        }
357                    });
358
359                    // collect extensions
360                    if s.item_exts.has_used_options() {
361                        s.item_exts
362                            .unwrap_as_pb()
363                            .used_options
364                            .0
365                            .iter()
366                            .for_each(|index| {
367                                let extendee = cx.db.pb_ext(index).unwrap();
368                                PathCollector { cx, set, file_ids }
369                                    .visit(&extendee.extendee_ty.item_ty);
370                            });
371                    }
372                }
373                rir::Item::NewType(n) => PathCollector { cx, set, file_ids }.visit(&n.ty),
374                rir::Item::Const(c) => {
375                    PathCollector { cx, set, file_ids }.visit(&c.ty);
376                }
377                rir::Item::Mod(m) => {
378                    m.items.iter().for_each(|i| collect(cx, *i, set, file_ids));
379                }
380            }
381        }
382        let mut set = FxHashSet::default();
383
384        let mut file_ids = FxHashSet::default();
385
386        input.iter().for_each(|def_id| {
387            collect(self, *def_id, &mut set, &mut file_ids);
388        });
389
390        self.db.nodes().iter().for_each(|(def_id, node)| {
391            if let NodeKind::Item(item) = &node.kind {
392                if let rir::Item::Const(_) = &**item {
393                    collect(self, *def_id, &mut set, &mut file_ids);
394                }
395            }
396        });
397
398        set
399    }
400
401    pub(crate) fn workspace_collect_def_ids(
402        &self,
403        input: &[DefId],
404    ) -> FxHashMap<DefId, DefLocation> {
405        self.db.collect_def_ids(input, None)
406    }
407
408    pub(crate) fn keep(&mut self, keep_unknown_fields: Vec<PathBuf>) {
409        let mut file_ids = FxHashSet::default();
410        keep_unknown_fields.into_iter().for_each(|p| {
411            let path = p.normalize().unwrap().into_path_buf();
412            let file_id = {
413                let file_ids_map = self.db.file_ids_map();
414                *file_ids_map.get(&path).unwrap()
415            };
416            keep_files(self, &file_id, &mut file_ids);
417
418            fn keep_files(
419                cx: &mut ContextBuilder,
420                file_id: &FileId,
421                file_ids: &mut FxHashSet<FileId>,
422            ) {
423                if !file_ids.insert(*file_id) {
424                    return;
425                }
426                let (uses, items_to_keep) = {
427                    let files = cx.db.files();
428                    let file = files.get(file_id).unwrap();
429                    let uses = file.uses.clone();
430                    let items_to_keep = file
431                        .items
432                        .iter()
433                        .filter(|&&def_id| match cx.db.node(def_id) {
434                            Some(rir::Node {
435                                kind: rir::NodeKind::Item(_),
436                                tags,
437                                ..
438                            }) => !matches!(
439                                cx.db.tags_map().get(&tags).and_then(|tags| {
440                                    tags.get::<crate::tags::KeepUnknownFields>()
441                                }),
442                                Some(crate::tags::KeepUnknownFields(false))
443                            ),
444                            _ => true,
445                        })
446                        .cloned()
447                        .collect::<Vec<_>>();
448                    (uses, items_to_keep)
449                };
450
451                for f in &uses {
452                    keep_files(cx, f, file_ids);
453                }
454
455                cx.keep_unknown_fields.extend(items_to_keep);
456            }
457        });
458    }
459
460    #[allow(clippy::too_many_arguments)]
461    pub(crate) fn build(
462        self,
463        services: Arc<[crate::IdlService]>,
464        source_type: SourceType,
465        change_case: bool,
466        dedups: Vec<FastStr>,
467        special_namings: Vec<FastStr>,
468        common_crate_name: FastStr,
469        split: bool,
470        with_descriptor: bool,
471        with_field_mask: bool,
472        touch_all: bool,
473        with_comments: bool,
474    ) -> Context {
475        let mode = Arc::new(self.mode);
476        SPECIAL_NAMINGS.get_or_init(|| special_namings);
477        let mut cx = Context {
478            db: self.db.clone(),
479            source: Source {
480                source_type,
481                services,
482                mode: mode.clone(),
483                path_resolver: match &*mode {
484                    Mode::Workspace(_) => Arc::new(WorkspacePathResolver),
485                    Mode::SingleFile { .. } => Arc::new(DefaultPathResolver),
486                },
487            },
488            config: Config {
489                change_case,
490                split,
491                with_descriptor,
492                with_field_mask,
493                touch_all,
494                common_crate_name,
495                with_comments,
496            },
497            cache: Cache {
498                adjusts: Default::default(),
499                codegen_items: self.codegen_items,
500                keep_unknown_fields: Arc::new(self.keep_unknown_fields),
501                location_map: Arc::new(self.location_map),
502                entry_map: Arc::new(self.entry_map),
503                plugin_gen: Default::default(),
504                dedups,
505                names: Default::default(),
506                mod_idxes: Default::default(),
507                mod_items: Default::default(),
508                mod_files: Default::default(),
509                def_mod: Default::default(),
510            },
511        };
512        let mut map: FxHashMap<(Vec<DefId>, String), Vec<DefId>> = FxHashMap::default();
513        let mut mod_idxes = AHashMap::default();
514        cx.nodes()
515            .iter()
516            .for_each(|(def_id, node)| match &node.kind {
517                NodeKind::Item(item) => {
518                    if let crate::rir::Item::Mod(_) = &**item {
519                        mod_idxes.insert(
520                            ModPath::from(Arc::from_iter(
521                                cx.mod_path(*def_id).iter().map(|s| s.0.clone()),
522                            )),
523                            *def_id,
524                        );
525                        return;
526                    }
527                    if let Mode::Workspace(_) = &*cx.source.mode {
528                        if !cx.cache.location_map.contains_key(def_id) {
529                            return;
530                        }
531                    }
532                    let rust_name = cx.item_path(*def_id).join("::");
533                    map.entry((vec![], rust_name)).or_default().push(*def_id);
534                }
535                _ => {
536                    let mut item_def_ids = vec![];
537                    let mut item_def_id = *def_id;
538                    while !matches!(cx.node(item_def_id).unwrap().kind, NodeKind::Item(_)) {
539                        item_def_id = cx.node(item_def_id).unwrap().parent.unwrap();
540                        item_def_ids.push(item_def_id);
541                    }
542                    let rust_name = cx.rust_name(*def_id).to_string();
543                    map.entry((item_def_ids, rust_name))
544                        .or_default()
545                        .push(*def_id);
546                }
547            });
548        cx.cache.names.extend(
549            map.into_iter()
550                .filter(|(_, v)| v.len() > 1)
551                .map(|(_, v)| v)
552                .flat_map(|v| v.into_iter().enumerate().map(|(i, def_id)| (def_id, i)))
553                .collect::<HashMap<DefId, usize>>(),
554        );
555        cx.cache.mod_idxes.extend(mod_idxes);
556
557        if matches!(&*cx.source.mode, Mode::SingleFile { .. }) {
558            let mut mod_files = HashMap::<ModPath, HashSet<FileId>>::default();
559            let mod_items = cx
560                .cache
561                .codegen_items
562                .clone()
563                .into_iter()
564                .into_group_map_by(|def_id| {
565                    let file_id = cx.node(*def_id).unwrap().file_id;
566
567                    let mod_path = cx.mod_index(*def_id);
568                    let set = mod_files.entry(mod_path.clone()).or_default();
569                    if !set.contains(&file_id) {
570                        set.insert(file_id);
571                    }
572                    cx.cache.def_mod.insert(*def_id, mod_path.clone());
573                    mod_path
574                });
575            cx.cache.mod_items.extend(AHashMap::from_iter(mod_items));
576            cx.cache.mod_files.extend(
577                mod_files
578                    .into_iter()
579                    .map(|(k, v)| (k, v.into_iter().collect())),
580            );
581        }
582        cx
583    }
584}
585
586impl Deref for Context {
587    type Target = RootDatabase;
588
589    fn deref(&self) -> &Self::Target {
590        &self.db
591    }
592}
593
594#[derive(Clone, Copy)]
595pub enum SourceType {
596    Thrift,
597    Protobuf,
598}
599
600impl Context {
601    pub fn config_data(&self) -> &Config {
602        &self.config
603    }
604
605    pub fn cache_data(&self) -> &Cache {
606        &self.cache
607    }
608
609    pub fn source_data(&self) -> &Source {
610        &self.source
611    }
612
613    pub fn with_adjust<T, F>(&self, def_id: DefId, f: F) -> T
614    where
615        F: FnOnce(Option<&Adjust>) -> T,
616    {
617        match self.cache.adjusts.get(&def_id) {
618            Some(adj) => f(Some(&*adj)),
619            None => f(None),
620        }
621    }
622
623    pub fn with_adjust_mut<T, F>(&self, def_id: DefId, f: F) -> T
624    where
625        F: FnOnce(&mut Adjust) -> T,
626    {
627        let adjust = &mut *self.cache.adjusts.entry(def_id).or_default();
628        f(adjust)
629    }
630
631    pub fn tags(&self, tags_id: TagId) -> Option<Arc<Tags>> {
632        self.db.tags_map().get(&tags_id).cloned()
633    }
634
635    pub fn node_tags(&self, def_id: DefId) -> Option<Arc<Tags>> {
636        let tags_id = self.node(def_id).unwrap().tags;
637        self.tags(tags_id)
638    }
639
640    pub fn contains_tag<T: 'static>(&self, tags_id: TagId) -> bool {
641        self.tags(tags_id)
642            .and_then(|tags| tags.contains::<T>().then_some(true))
643            .is_some()
644    }
645
646    pub fn node_contains_tag<T: 'static>(&self, def_id: DefId) -> bool {
647        self.node_tags(def_id)
648            .and_then(|tags| tags.contains::<T>().then_some(true))
649            .is_some()
650    }
651
652    pub fn symbol_name(&self, def_id: DefId) -> Symbol {
653        let item = self.item(def_id).unwrap();
654        item.symbol_name()
655    }
656
657    fn get_codegen_ty_for_path(&self, def_id: DefId) -> CodegenTy {
658        let node = self.node(def_id).unwrap();
659        match &node.kind {
660            NodeKind::Item(item) => match &**item {
661                Item::Const(c) => {
662                    let ty = self.codegen_const_ty(c.ty.kind.clone());
663                    if ty.should_lazy_static() {
664                        CodegenTy::LazyStaticRef(Arc::new(ty))
665                    } else {
666                        ty
667                    }
668                }
669                Item::Enum(_) => CodegenTy::Adt(AdtDef {
670                    did: def_id,
671                    kind: AdtKind::Enum,
672                }),
673                Item::NewType(t) => CodegenTy::Adt(AdtDef {
674                    did: def_id,
675                    kind: AdtKind::NewType(Arc::new(self.codegen_item_ty(t.ty.kind.clone()))),
676                }),
677                Item::Message(_) => CodegenTy::Adt(AdtDef {
678                    did: def_id,
679                    kind: AdtKind::Struct,
680                }),
681                _ => panic!("Unexpected item type for path: {:?}", item),
682            },
683            NodeKind::Variant(_v) => {
684                // For enum variants, get the parent enum's type
685                let parent_def_id = node.parent.unwrap();
686                CodegenTy::Adt(AdtDef {
687                    did: parent_def_id,
688                    kind: AdtKind::Enum,
689                })
690            }
691            NodeKind::Field(_) | NodeKind::Method(_) | NodeKind::Arg(_) => {
692                panic!("Unexpected node kind for path: {:?}", node.kind)
693            }
694        }
695    }
696
697    fn convert_element_expr(&self, expr: &str, from: &CodegenTy, to: &CodegenTy) -> String {
698        if from == to {
699            if self.is_copy_ty(from) {
700                expr.to_string()
701            } else {
702                format!("({expr}).clone()", expr = expr)
703            }
704        } else if let Some((converted, _)) =
705            self.convert_codegen_ty_expr(expr.to_string().into(), from, to, false)
706        {
707            converted.to_string()
708        } else {
709            expr.to_string()
710        }
711    }
712
713    fn convert_owned_element_expr(&self, expr: &str, from: &CodegenTy, to: &CodegenTy) -> String {
714        if from == to {
715            return expr.to_string();
716        }
717
718        match (from, to) {
719            (
720                CodegenTy::Adt(AdtDef {
721                    kind: AdtKind::NewType(inner),
722                    ..
723                }),
724                _,
725            ) => {
726                let inner_expr = format!("({expr}).0", expr = expr);
727                self.convert_owned_element_expr(&inner_expr, inner, to)
728            }
729            (
730                _,
731                CodegenTy::Adt(AdtDef {
732                    kind: AdtKind::NewType(inner),
733                    did,
734                }),
735            ) => {
736                let inner_expr = self.convert_owned_element_expr(expr, from, inner);
737                let ctor = self.cur_related_item_path(*did);
738                format!("{ctor}({inner_expr})")
739            }
740            _ => self
741                .convert_codegen_ty_expr(expr.to_string().into(), from, to, true)
742                .map(|(converted, _)| converted.to_string())
743                .unwrap_or_else(|| expr.to_string()),
744        }
745    }
746
747    fn is_copy_ty(&self, ty: &CodegenTy) -> bool {
748        matches!(
749            ty,
750            CodegenTy::Bool
751                | CodegenTy::I8
752                | CodegenTy::I16
753                | CodegenTy::I32
754                | CodegenTy::I64
755                | CodegenTy::UInt32
756                | CodegenTy::UInt64
757                | CodegenTy::U8
758                | CodegenTy::F32
759                | CodegenTy::F64
760                | CodegenTy::OrderedF64
761        )
762    }
763
764    pub fn default_val(&self, f: &Field) -> Option<(FastStr, bool /* const? */)> {
765        f.default.as_ref().map(|d| {
766            let ty = self.codegen_item_ty(f.ty.kind.clone());
767            match self
768                .lit_as_rvalue(d, &ty)
769                .with_context(|| format!("calc the default value for field {}", f.name))
770            {
771                Ok(v) => v,
772                Err(err) => {
773                    panic!("{err}")
774                }
775            }
776        })
777    }
778
779    fn map_literal_expr(
780        &self,
781        entries: &[(Literal, Literal)],
782        key_ty: &Arc<CodegenTy>,
783        value_ty: &Arc<CodegenTy>,
784        btree: bool,
785    ) -> anyhow::Result<FastStr> {
786        let key_ty = &**key_ty;
787        let value_ty = &**value_ty;
788        let len = entries.len();
789        let kvs = entries
790            .iter()
791            .map(|(k, v)| {
792                let (k_expr, _) = self.lit_into_ty(k, key_ty)?;
793                let (v_expr, _) = self.lit_into_ty(v, value_ty)?;
794                anyhow::Ok(format!("map.insert({k_expr}, {v_expr});"))
795            })
796            .try_collect::<_, Vec<_>, _>()?
797            .join("");
798        let new = if btree {
799            "::std::collections::BTreeMap::new()".to_string()
800        } else {
801            format!("::pilota::AHashMap::with_capacity({len})")
802        };
803        Ok(format! {r#"{{
804                let mut map = {new};
805                {kvs}
806                map
807            }}"#}
808        .into())
809    }
810
811    fn lit_as_rvalue(
812        &self,
813        lit: &Literal,
814        ty: &CodegenTy,
815    ) -> anyhow::Result<(FastStr, bool /* const? */)> {
816        anyhow::Ok(match (lit, ty) {
817            (Literal::Map(m), CodegenTy::LazyStaticRef(map)) => match &**map {
818                CodegenTy::Map(k_ty, v_ty) => (self.map_literal_expr(m, k_ty, v_ty, false)?, false),
819                CodegenTy::BTreeMap(k_ty, v_ty) => {
820                    (self.map_literal_expr(m, k_ty, v_ty, true)?, false)
821                }
822                _ => panic!("invalid map type {map:?}"),
823            },
824            (Literal::Map(m), CodegenTy::Map(k_ty, v_ty)) => {
825                (self.map_literal_expr(m, k_ty, v_ty, false)?, false)
826            }
827            (Literal::Map(m), CodegenTy::BTreeMap(k_ty, v_ty)) => {
828                (self.map_literal_expr(m, k_ty, v_ty, true)?, false)
829            }
830            (Literal::List(l), CodegenTy::LazyStaticRef(map)) => match &**map {
831                CodegenTy::Map(_, _) => {
832                    assert!(l.is_empty());
833                    ("::pilota::AHashMap::new()".into(), false)
834                }
835                CodegenTy::BTreeMap(_, _) => {
836                    assert!(l.is_empty());
837                    ("::std::collections::BTreeMap::new()".into(), false)
838                }
839                CodegenTy::Set(inner) => {
840                    if l.is_empty() {
841                        ("::pilota::AHashSet::new()".into(), false)
842                    } else {
843                        let stream = self.list_stream(l, inner)?;
844                        (
845                            format!("::pilota::AHashSet::from([{stream}])").into(),
846                            false,
847                        )
848                    }
849                }
850                CodegenTy::BTreeSet(inner) => {
851                    if l.is_empty() {
852                        ("::std::collections::BTreeSet::new()".into(), false)
853                    } else {
854                        let stream = self.list_stream(l, inner)?;
855                        (
856                            format!("::std::collections::BTreeSet::from([{stream}])").into(),
857                            false,
858                        )
859                    }
860                }
861                _ => panic!("invalid map type {map:?}"),
862            },
863            (Literal::List(l), CodegenTy::Map(_, _)) => {
864                assert!(l.is_empty());
865                ("::pilota::AHashMap::new()".into(), false)
866            }
867            (Literal::List(l), CodegenTy::BTreeMap(_, _)) => {
868                assert!(l.is_empty());
869                ("::std::collections::BTreeMap::new()".into(), false)
870            }
871            _ => self.lit_into_ty(lit, ty)?,
872        })
873    }
874
875    fn ident_into_ty(
876        &self,
877        did: DefId,
878        ident_ty: &CodegenTy,
879        target: &CodegenTy,
880    ) -> (FastStr, bool /* const? */) {
881        let stream = self.cur_related_item_path(did);
882        if ident_ty == target {
883            return (stream.clone(), true);
884        }
885
886        let ident_norm = self.normalize_codegen_ty(ident_ty);
887        let target_norm = self.normalize_codegen_ty(target);
888
889        if ident_norm == target_norm {
890            if let Some((converted, is_const)) =
891                self.convert_codegen_ty_expr(stream.clone(), ident_ty, target, true)
892            {
893                return (converted, is_const);
894            }
895        }
896
897        match (ident_ty, target) {
898            (CodegenTy::Str, CodegenTy::FastStr) => (
899                format!("::pilota::FastStr::from_static_str({stream})").into(),
900                true,
901            ),
902            (
903                CodegenTy::Adt(AdtDef {
904                    did: _,
905                    kind: AdtKind::Enum,
906                }),
907                CodegenTy::I64,
908            )
909            | (
910                CodegenTy::Adt(AdtDef {
911                    did: _,
912                    kind: AdtKind::Enum,
913                }),
914                CodegenTy::I32,
915            )
916            | (
917                CodegenTy::Adt(AdtDef {
918                    did: _,
919                    kind: AdtKind::Enum,
920                }),
921                CodegenTy::I16,
922            )
923            | (
924                CodegenTy::Adt(AdtDef {
925                    did: _,
926                    kind: AdtKind::Enum,
927                }),
928                CodegenTy::I8,
929            ) => {
930                let stream = self.cur_related_item_path(did);
931                let target = match target {
932                    CodegenTy::I64 => "i64",
933                    CodegenTy::I32 => "i32",
934                    CodegenTy::I16 => "i16",
935                    CodegenTy::I8 => "i8",
936                    _ => unreachable!(),
937                };
938                (format!("({stream}.inner() as {target})").into(), true)
939            }
940            _ => panic!("invalid convert {ident_ty:?} to {target:?}"),
941        }
942    }
943
944    // normalize the codegen ty to the raw type
945    fn normalize_codegen_ty(&self, ty: &CodegenTy) -> CodegenTy {
946        match ty {
947            CodegenTy::Adt(AdtDef {
948                kind: AdtKind::NewType(inner),
949                ..
950            }) => self.normalize_codegen_ty(inner),
951            CodegenTy::LazyStaticRef(inner) | CodegenTy::StaticRef(inner) => {
952                self.normalize_codegen_ty(inner)
953            }
954            CodegenTy::Str => CodegenTy::FastStr,
955            CodegenTy::FastStr => CodegenTy::FastStr,
956            CodegenTy::Vec(inner) => CodegenTy::Vec(Arc::new(self.normalize_codegen_ty(inner))),
957            CodegenTy::Array(inner, size) => {
958                CodegenTy::Array(Arc::new(self.normalize_codegen_ty(inner)), *size)
959            }
960            CodegenTy::Set(inner) => CodegenTy::Set(Arc::new(self.normalize_codegen_ty(inner))),
961            CodegenTy::BTreeSet(inner) => {
962                CodegenTy::BTreeSet(Arc::new(self.normalize_codegen_ty(inner)))
963            }
964            CodegenTy::Map(k, v) => CodegenTy::Map(
965                Arc::new(self.normalize_codegen_ty(k)),
966                Arc::new(self.normalize_codegen_ty(v)),
967            ),
968            CodegenTy::BTreeMap(k, v) => CodegenTy::BTreeMap(
969                Arc::new(self.normalize_codegen_ty(k)),
970                Arc::new(self.normalize_codegen_ty(v)),
971            ),
972            CodegenTy::Arc(inner) => CodegenTy::Arc(Arc::new(self.normalize_codegen_ty(inner))),
973            _ => ty.clone(),
974        }
975    }
976
977    fn convert_codegen_ty_expr(
978        &self,
979        expr: FastStr,
980        from: &CodegenTy,
981        to: &CodegenTy,
982        owned: bool,
983    ) -> Option<(FastStr, bool)> {
984        if from == to {
985            if owned || self.is_copy_ty(from) {
986                return Some((expr, true));
987            }
988            let expr_str = expr.to_string();
989            return Some((format!("({expr_str}).clone()").into(), false));
990        }
991
992        match (from, to) {
993            (
994                CodegenTy::Adt(AdtDef {
995                    kind: AdtKind::NewType(inner),
996                    ..
997                }),
998                _,
999            ) => {
1000                if owned {
1001                    let expr_str = expr.to_string();
1002                    let inner_expr: FastStr = format!("({expr_str}).0").into();
1003                    let (converted, is_const) =
1004                        self.convert_codegen_ty_expr(inner_expr, inner, to, true)?;
1005                    Some((converted, is_const))
1006                } else {
1007                    let expr_str = expr.to_string();
1008                    let cloned: FastStr = format!("{expr_str}.clone()").into();
1009                    let (converted, _) = self.convert_codegen_ty_expr(cloned, inner, to, true)?;
1010                    Some((converted, false))
1011                }
1012            }
1013            (
1014                _,
1015                CodegenTy::Adt(AdtDef {
1016                    kind: AdtKind::NewType(inner),
1017                    did,
1018                }),
1019            ) => {
1020                let (inner_expr, _) = self.convert_codegen_ty_expr(expr, from, inner, owned)?;
1021                let ident = self.cur_related_item_path(*did);
1022                Some((format!("{ident}({inner_expr})").into(), false))
1023            }
1024            (CodegenTy::Str, CodegenTy::FastStr) => {
1025                let expr_str = expr.to_string();
1026                Some((
1027                    format!("::pilota::FastStr::from_static_str({expr_str})").into(),
1028                    true,
1029                ))
1030            }
1031            (CodegenTy::LazyStaticRef(inner), _) => {
1032                let expr_str = expr.to_string();
1033                let needs_clone = !expr_str.trim_end().ends_with(".clone()");
1034                let owned_expr: FastStr = if needs_clone {
1035                    format!("{expr_str}.clone()").into()
1036                } else {
1037                    expr_str.into()
1038                };
1039                let (converted, _) = self.convert_codegen_ty_expr(owned_expr, inner, to, true)?;
1040                Some((converted, false))
1041            }
1042            (CodegenTy::StaticRef(inner), _) => {
1043                let expr_str = expr.to_string();
1044                let needs_clone = !expr_str.trim_end().ends_with(".clone()");
1045                let owned_expr: FastStr = if needs_clone {
1046                    format!("{expr_str}.clone()").into()
1047                } else {
1048                    expr_str.into()
1049                };
1050                let (converted, _) = self.convert_codegen_ty_expr(owned_expr, inner, to, true)?;
1051                Some((converted, false))
1052            }
1053            (CodegenTy::Vec(from_inner), CodegenTy::Vec(to_inner)) => {
1054                if from_inner == to_inner {
1055                    return Some((expr, owned));
1056                }
1057                let expr_str = expr.to_string();
1058                if owned {
1059                    let body = self.convert_owned_element_expr("el.clone()", from_inner, to_inner);
1060                    let converted = format!(
1061                        r#"{{
1062                        {expr_str}
1063                            .iter()
1064                            .map(|el| {body})
1065                            .collect::<::std::vec::Vec<_>>()
1066                    }}"#
1067                    )
1068                    .into();
1069                    Some((converted, false))
1070                } else {
1071                    let body = self.convert_element_expr("el", from_inner, to_inner);
1072                    let converted = format!(
1073                        r#"{{
1074                        {expr_str}
1075                            .iter()
1076                            .map(|el| {body})
1077                            .collect::<::std::vec::Vec<_>>()
1078                    }}"#
1079                    )
1080                    .into();
1081                    Some((converted, false))
1082                }
1083            }
1084            (CodegenTy::Set(from_inner), CodegenTy::Set(to_inner)) => {
1085                if from_inner == to_inner {
1086                    return Some((expr, owned));
1087                }
1088                let expr_str = expr.to_string();
1089                if owned {
1090                    let body = self.convert_owned_element_expr("el.clone()", from_inner, to_inner);
1091                    let converted = format!(
1092                        r#"{{
1093                        {expr_str}
1094                            .iter()
1095                            .map(|el| {body})
1096                            .collect::<::pilota::AHashSet<_>>()
1097                    }}"#
1098                    )
1099                    .into();
1100                    Some((converted, false))
1101                } else {
1102                    let body = self.convert_element_expr("el", from_inner, to_inner);
1103                    let converted = format!(
1104                        r#"{{
1105                        {expr_str}
1106                            .iter()
1107                            .map(|el| {body})
1108                            .collect::<::pilota::AHashSet<_>>()
1109                    }}"#
1110                    )
1111                    .into();
1112                    Some((converted, false))
1113                }
1114            }
1115            (CodegenTy::BTreeSet(from_inner), CodegenTy::BTreeSet(to_inner)) => {
1116                if from_inner == to_inner {
1117                    return Some((expr, owned));
1118                }
1119                let expr_str = expr.to_string();
1120                if owned {
1121                    let body = self.convert_owned_element_expr("el.clone()", from_inner, to_inner);
1122                    let converted = format!(
1123                        r#"{{
1124                        {expr_str}.iter()
1125                            .map(|el| {body})
1126                            .collect::<::std::collections::BTreeSet<_>>()
1127                    }}"#
1128                    )
1129                    .into();
1130                    Some((converted, false))
1131                } else {
1132                    let body = self.convert_element_expr("el", from_inner, to_inner);
1133                    let converted = format!(
1134                        r#"{{
1135                        {expr_str}.iter()
1136                            .map(|el| {body})
1137                            .collect::<::std::collections::BTreeSet<_>>()
1138                    }}"#
1139                    )
1140                    .into();
1141                    Some((converted, false))
1142                }
1143            }
1144            (CodegenTy::Map(from_k, from_v), CodegenTy::Map(to_k, to_v)) => {
1145                if from_k == to_k && from_v == to_v {
1146                    return Some((expr, owned));
1147                }
1148                let expr_str = expr.to_string();
1149                if owned {
1150                    let k_body = self.convert_owned_element_expr("k.clone()", from_k, to_k);
1151                    let v_body = self.convert_owned_element_expr("v.clone()", from_v, to_v);
1152                    let converted = format!(
1153                        r#"{{
1154                        {expr_str}
1155                            .iter()
1156                            .map(|(k, v)| ({k_body}, {v_body}))
1157                            .collect::<::pilota::AHashMap<_, _>>()
1158                    }}"#
1159                    )
1160                    .into();
1161                    Some((converted, false))
1162                } else {
1163                    let k_body = self.convert_element_expr("k", from_k, to_k);
1164                    let v_body = self.convert_element_expr("v", from_v, to_v);
1165                    let converted = format!(
1166                        r#"{{
1167                        {expr_str}
1168                            .iter()
1169                            .map(|(k, v)| ({k_body}, {v_body}))
1170                            .collect::<::pilota::AHashMap<_, _>>()
1171                    }}"#
1172                    )
1173                    .into();
1174                    Some((converted, false))
1175                }
1176            }
1177            (CodegenTy::BTreeMap(from_k, from_v), CodegenTy::BTreeMap(to_k, to_v)) => {
1178                if from_k == to_k && from_v == to_v {
1179                    return Some((expr, owned));
1180                }
1181                let expr_str = expr.to_string();
1182                if owned {
1183                    let k_body = self.convert_owned_element_expr("k.clone()", from_k, to_k);
1184                    let v_body = self.convert_owned_element_expr("v.clone()", from_v, to_v);
1185                    let converted = format!(
1186                        r#"{{
1187                        {expr_str}
1188                            .iter()
1189                            .map(|(k, v)| ({k_body}, {v_body}))
1190                            .collect::<::std::collections::BTreeMap<_, _>>()
1191                    }}"#
1192                    )
1193                    .into();
1194                    Some((converted, false))
1195                } else {
1196                    let k_body = self.convert_element_expr("k", from_k, to_k);
1197                    let v_body = self.convert_element_expr("v", from_v, to_v);
1198                    let converted = format!(
1199                        r#"{{
1200                        {expr_str}
1201                            .iter()
1202                            .map(|(k, v)| ({k_body}, {v_body}))
1203                            .collect::<::std::collections::BTreeMap<_, _>>()
1204                    }}"#
1205                    )
1206                    .into();
1207                    Some((converted, false))
1208                }
1209            }
1210            _ => None,
1211        }
1212    }
1213
1214    fn lit_into_ty(
1215        &self,
1216        lit: &Literal,
1217        ty: &CodegenTy,
1218    ) -> anyhow::Result<(FastStr, bool /* const? */)> {
1219        Ok(match (lit, ty) {
1220            (Literal::Path(p), ty) => {
1221                let ident_ty = self.get_codegen_ty_for_path(p.did);
1222
1223                if matches!(
1224                    ty,
1225                    CodegenTy::Map(_, _)
1226                        | CodegenTy::BTreeMap(_, _)
1227                        | CodegenTy::Set(_)
1228                        | CodegenTy::BTreeSet(_)
1229                ) {
1230                    let normalized_ident_ty = self.normalize_codegen_ty(&ident_ty);
1231                    if matches!(
1232                        normalized_ident_ty,
1233                        CodegenTy::Map(_, _)
1234                            | CodegenTy::BTreeMap(_, _)
1235                            | CodegenTy::Set(_)
1236                            | CodegenTy::BTreeSet(_)
1237                    ) {
1238                        let stream = self.cur_related_item_path(p.did);
1239                        if let Some((converted, _)) =
1240                            self.convert_codegen_ty_expr(stream.clone(), &ident_ty, ty, false)
1241                        {
1242                            return Ok((converted, false));
1243                        }
1244                    }
1245                }
1246
1247                self.ident_into_ty(p.did, &ident_ty, ty)
1248            }
1249            (Literal::String(s), CodegenTy::Str) => (format!("\"{s}\"").into(), true),
1250            (Literal::String(s), CodegenTy::String) => {
1251                (format! {"\"{s}\".to_string()"}.into(), false)
1252            }
1253            (Literal::String(s), CodegenTy::FastStr) => (
1254                format! { "::pilota::FastStr::from_static_str(\"{s}\")" }.into(),
1255                true,
1256            ),
1257            (Literal::Int(i), CodegenTy::I8) => (format! { "{i}i8" }.into(), true),
1258            (Literal::Int(i), CodegenTy::I16) => (format! { "{i}i16" }.into(), true),
1259            (Literal::Int(i), CodegenTy::I32) => (format! { "{i}i32" }.into(), true),
1260            (Literal::Int(i), CodegenTy::I64) => (format! { "{i}i64" }.into(), true),
1261            (Literal::Int(i), CodegenTy::F32) => {
1262                let f = (*i) as f32;
1263                (format!("{f}f32").into(), true)
1264            }
1265            (Literal::Int(i), CodegenTy::F64) => {
1266                let f = (*i) as f64;
1267                (format!("{f}f64").into(), true)
1268            }
1269            (
1270                Literal::Int(i),
1271                CodegenTy::Adt(AdtDef {
1272                    did,
1273                    kind: AdtKind::Enum,
1274                }),
1275            ) => {
1276                let item = self.item(*did).unwrap();
1277                let e = match &*item {
1278                    Item::Enum(e) => e,
1279                    _ => panic!("invalid enum"),
1280                };
1281
1282                (
1283                    e.variants.iter().find(|v| v.discr == Some(*i)).map_or_else(
1284                        || panic!("invalid enum value"),
1285                        |v| self.cur_related_item_path(v.did),
1286                    ),
1287                    true,
1288                )
1289            }
1290            (Literal::Float(f), CodegenTy::F32) => {
1291                let f = f.parse::<f32>().unwrap();
1292                (format! { "{f}f32" }.into(), true)
1293            }
1294            (Literal::Float(f), CodegenTy::F64) => {
1295                let f = f.parse::<f64>().unwrap();
1296                (format! { "{f}f64" }.into(), true)
1297            }
1298            (Literal::Float(f), CodegenTy::OrderedF64) => {
1299                let f = f.parse::<f64>().unwrap();
1300                (format! { "::pilota::OrderedFloat({f}f64)" }.into(), true)
1301            }
1302            (
1303                l,
1304                CodegenTy::Adt(AdtDef {
1305                    kind: AdtKind::NewType(inner_ty),
1306                    did,
1307                }),
1308            ) => {
1309                let ident = self.cur_related_item_path(*did);
1310                let (stream, is_const) = self.lit_into_ty(l, inner_ty)?;
1311                (format! { "{ident}({stream})" }.into(), is_const)
1312            }
1313            (Literal::Map(_), CodegenTy::StaticRef(map)) => match &**map {
1314                CodegenTy::Map(_, _) | CodegenTy::BTreeMap(_, _) => {
1315                    let lazy_map =
1316                        self.def_lit("INNER_MAP", lit, &mut CodegenTy::LazyStaticRef(map.clone()))?;
1317                    let stream = format! {
1318                        r#"{{
1319                            {lazy_map}
1320                            &*INNER_MAP
1321                        }}"#
1322                    }
1323                    .into();
1324                    (stream, false)
1325                }
1326                _ => panic!("invalid map type {map:?}"),
1327            },
1328            (Literal::List(els), CodegenTy::Array(inner, _)) => {
1329                let stream = els
1330                    .iter()
1331                    .map(|el| self.lit_into_ty(el, inner))
1332                    .try_collect::<_, Vec<_>, _>()?;
1333                let is_const = stream.iter().all(|(_, is_const)| *is_const);
1334                let stream = stream.into_iter().map(|(s, _)| s).join(",");
1335
1336                (format! {"[{stream}]" }.into(), is_const)
1337            }
1338            (Literal::List(els), CodegenTy::Vec(inner)) => {
1339                let stream = self.list_stream(els, inner)?;
1340                (format! { "::std::vec![{stream}]" }.into(), false)
1341            }
1342            (Literal::List(els), CodegenTy::Set(inner)) => {
1343                let stream = self.list_stream(els, inner)?;
1344                (
1345                    format! { "::pilota::AHashSet::from([{stream}])" }.into(),
1346                    false,
1347                )
1348            }
1349            (Literal::List(els), CodegenTy::BTreeSet(inner)) => {
1350                let stream = self.list_stream(els, inner)?;
1351                (
1352                    format! { "::std::collections::BTreeSet::from([{stream}])" }.into(),
1353                    false,
1354                )
1355            }
1356            (Literal::Bool(b), CodegenTy::Bool) => (format! { "{b}" }.into(), true),
1357            (Literal::Int(i), CodegenTy::Bool) => {
1358                let b = *i != 0;
1359                (format! { "{b}" }.into(), true)
1360            }
1361            (Literal::String(s), CodegenTy::Bytes) => {
1362                let s = &**s;
1363                (
1364                    format! { "::pilota::Bytes::from_static(\"{s}\".as_bytes())" }.into(),
1365                    true,
1366                )
1367            }
1368            (
1369                Literal::Map(m),
1370                CodegenTy::Adt(AdtDef {
1371                    did,
1372                    kind: AdtKind::Struct,
1373                }),
1374            ) => {
1375                let def = self.item(*did).unwrap();
1376                let def = match &*def {
1377                    Item::Message(m) => m,
1378                    _ => panic!(),
1379                };
1380
1381                let fields: Vec<_> = def
1382                    .fields
1383                    .iter()
1384                    .map(|f| {
1385                        let v = m.iter().find_map(|(k, v)| {
1386                            let k = match k {
1387                                Literal::String(s) => s,
1388                                _ => panic!(),
1389                            };
1390                            if **k == **f.name { Some(v) } else { None }
1391                        });
1392
1393                        let name = self.rust_name(f.did);
1394
1395                        if let Some(v) = v {
1396                            let (mut v, is_const) =
1397                                self.lit_into_ty(v, &self.codegen_item_ty(f.ty.kind.clone()))?;
1398
1399                            if f.is_optional() {
1400                                v = format!("Some({v})").into()
1401                            }
1402                            anyhow::Ok((format!("{name}: {v}"), is_const))
1403                        } else if f.is_optional() {
1404                            anyhow::Ok((format!("{name}: None"), true))
1405                        } else {
1406                            anyhow::Ok((format!("{name}: Default::default()"), false))
1407                        }
1408                    })
1409                    .try_collect()?;
1410                let is_const = fields.iter().all(|(_, is_const)| *is_const);
1411                let fields = fields.into_iter().map(|f| f.0).join(",");
1412
1413                let name = self.cur_related_item_path(*did);
1414
1415                (
1416                    format! {
1417                        r#"{name} {{
1418                            {fields}
1419                        }}"#
1420                    }
1421                    .into(),
1422                    is_const,
1423                )
1424            }
1425            (Literal::Map(m), CodegenTy::Map(k_ty, v_ty)) => {
1426                (self.map_literal_expr(m, k_ty, v_ty, false)?, false)
1427            }
1428            (Literal::Map(m), CodegenTy::BTreeMap(k_ty, v_ty)) => {
1429                (self.map_literal_expr(m, k_ty, v_ty, true)?, false)
1430            }
1431            (Literal::List(l), CodegenTy::Map(_, _)) => {
1432                assert!(l.is_empty());
1433                ("::pilota::AHashMap::new()".into(), false)
1434            }
1435            (Literal::List(l), CodegenTy::BTreeMap(_, _)) => {
1436                assert!(l.is_empty());
1437                ("::std::collections::BTreeMap::new()".into(), false)
1438            }
1439            _ => {
1440                let (def_path, idl_file) = with_cur_item(|def_id| {
1441                    let def_path = self.item_path(def_id).iter().join("::");
1442                    let file_path = self
1443                        .db
1444                        .node(def_id)
1445                        .and_then(|node| {
1446                            self.db
1447                                .file_paths()
1448                                .get(&node.file_id)
1449                                .map(|path| path.display().to_string())
1450                        })
1451                        .unwrap_or_else(|| "<unknown>".to_string());
1452                    (def_path, file_path)
1453                });
1454
1455                let error_message = format!(
1456                    "unexpected literal {lit:?} with ty {ty}, def_path: {def_path}, idl_file: {idl_file}"
1457                );
1458
1459                panic!("{error_message}");
1460            }
1461        })
1462    }
1463
1464    #[inline]
1465    fn list_stream(&self, els: &[Literal], inner: &Arc<CodegenTy>) -> anyhow::Result<String> {
1466        Ok(els
1467            .iter()
1468            .map(|el| self.lit_into_ty(el, inner))
1469            .try_collect::<_, Vec<_>, _>()?
1470            .into_iter()
1471            .map(|(s, _)| s)
1472            .join(","))
1473    }
1474
1475    pub(crate) fn def_lit(
1476        &self,
1477        name: &str,
1478        lit: &Literal,
1479        ty: &mut CodegenTy,
1480    ) -> anyhow::Result<String> {
1481        let should_lazy_static = ty.should_lazy_static();
1482        if let (Literal::List(lit), CodegenTy::Array(_, size)) = (lit, &mut *ty) {
1483            *size = lit.len()
1484        }
1485        Ok(if should_lazy_static {
1486            let lit = self.lit_as_rvalue(lit, ty)?.0;
1487            format! {r#"
1488                pub static {name}: ::std::sync::LazyLock<{ty}> = ::std::sync::LazyLock::new(|| {{
1489                    {lit}
1490                }});
1491            "#}
1492        } else {
1493            let (lit, is_const) = self.lit_into_ty(lit, ty)?;
1494            if is_const {
1495                format!(r#"pub const {name}: {ty} = {lit};"#)
1496            } else {
1497                format! {r#"
1498                pub static {name}: ::std::sync::LazyLock<{ty}> = ::std::sync::LazyLock::new(|| {{
1499                    {lit}
1500                }});
1501            "#}
1502            }
1503        })
1504    }
1505
1506    pub fn rust_name(&self, def_id: DefId) -> Symbol {
1507        let node = self.node(def_id).unwrap();
1508
1509        if let Some(name) = self
1510            .tags(node.tags)
1511            .and_then(|tags| tags.get::<crate::tags::PilotaName>().cloned())
1512        {
1513            return name.0.into();
1514        }
1515
1516        if !self.config.change_case || self.cache.names.contains_key(&def_id) {
1517            return node.name();
1518        }
1519
1520        match self.node(def_id).unwrap().kind {
1521            NodeKind::Item(item) => match &*item {
1522                crate::rir::Item::Message(m) => (&**m.name).struct_ident(),
1523                crate::rir::Item::Enum(e) => (&**e.name).enum_ident(),
1524                crate::rir::Item::Service(s) => (&**s.name).trait_ident(),
1525                crate::rir::Item::NewType(t) => (&**t.name).newtype_ident(),
1526                crate::rir::Item::Const(c) => (&**c.name).const_ident(),
1527                crate::rir::Item::Mod(m) => (&**m.name).mod_ident(),
1528            },
1529            NodeKind::Variant(v) => {
1530                let parent = self.node(def_id).unwrap().parent.unwrap();
1531                let item = self.expect_item(parent);
1532                match &*item {
1533                    rir::Item::Enum(e) => {
1534                        if e.repr.is_some() {
1535                            (&**v.name).const_ident()
1536                        } else {
1537                            (&**v.name).variant_ident()
1538                        }
1539                    }
1540                    _ => unreachable!(),
1541                }
1542            }
1543            NodeKind::Field(f) => (&**f.name).field_ident(),
1544            NodeKind::Method(m) => (&**m.name).fn_ident(),
1545            NodeKind::Arg(a) => (&**a.name).field_ident(),
1546        }
1547        .into()
1548    }
1549
1550    pub fn mod_path(&self, def_id: DefId) -> Arc<[Symbol]> {
1551        self.source.path_resolver.mod_prefix(self, def_id)
1552    }
1553
1554    pub fn mod_index(&self, def_id: DefId) -> ModPath {
1555        let mod_path = self
1556            .mod_path(def_id)
1557            .iter()
1558            .map(|s| s.0.clone())
1559            .collect_vec();
1560
1561        match &*self.source.mode {
1562            Mode::Workspace(_) => ModPath::from(&mod_path[1..]),
1563            Mode::SingleFile { .. } => ModPath::from(mod_path),
1564        }
1565    }
1566
1567    pub fn item_path(&self, def_id: DefId) -> Arc<[Symbol]> {
1568        self.source.path_resolver.path_for_def_id(self, def_id)
1569    }
1570
1571    fn related_path(&self, p1: &[Symbol], p2: &[Symbol]) -> FastStr {
1572        self.source.path_resolver.related_path(p1, p2)
1573    }
1574
1575    pub fn cur_related_item_path(&self, did: DefId) -> FastStr {
1576        let a = with_cur_item(|def_id| def_id);
1577        self.related_item_path(a, did)
1578    }
1579
1580    pub fn related_item_path(&self, a: DefId, b: DefId) -> FastStr {
1581        let cur_item_path = self.item_path(a);
1582        let mut mod_segs = vec![];
1583
1584        cur_item_path[..cur_item_path.len() - 1]
1585            .iter()
1586            .for_each(|p| {
1587                mod_segs.push(p.clone());
1588            });
1589
1590        let other_item_path = self.item_path(b);
1591        self.related_path(&mod_segs, &other_item_path)
1592    }
1593
1594    #[allow(clippy::single_match)]
1595    pub fn exec_plugin<P: Plugin>(&self, mut p: P) {
1596        p.on_codegen_uint(self, &self.cache.codegen_items);
1597
1598        p.on_emit(self)
1599    }
1600
1601    pub(crate) fn workspace_info(&self) -> &WorkspaceInfo {
1602        let Mode::Workspace(info) = &*self.source.mode else {
1603            panic!(
1604                "can not access workspace info in mode `{:?}`",
1605                self.source.mode
1606            )
1607        };
1608        info
1609    }
1610
1611    // pub fn def_id_info(&self, def_id: DefId) -> FastStr {
1612    //     let file_path = self
1613    //         .file(self.node(def_id).unwrap().file_id)
1614    //         .unwrap()
1615    //         .package
1616    //         .clone();
1617    //     file_path
1618    //         .iter()
1619    //         .chain(&[self.node(def_id).unwrap().name()])
1620    //         .join("::")
1621    //         .into()
1622    // }
1623
1624    pub fn config(&self, crate_id: &CrateId) -> &serde_yaml::Value {
1625        &self.find_service(crate_id.main_file).config
1626    }
1627
1628    pub(crate) fn crate_name(&self, location: &DefLocation) -> FastStr {
1629        match location {
1630            DefLocation::Fixed(crate_id, _) => {
1631                let main_file = crate_id.main_file;
1632                let service = self.find_service(main_file);
1633                self.config(crate_id)
1634                    .get("crate_name")
1635                    .and_then(|s| s.as_str().map(FastStr::new))
1636                    .unwrap_or_else(|| {
1637                        service
1638                            .path
1639                            .file_stem()
1640                            .unwrap()
1641                            .to_str()
1642                            .unwrap()
1643                            .replace('.', "_")
1644                            .into()
1645                    })
1646            }
1647            DefLocation::Dynamic => self.config.common_crate_name.clone(),
1648        }
1649    }
1650
1651    fn find_service(&self, file_id: FileId) -> &crate::IdlService {
1652        self.source
1653            .services
1654            .iter()
1655            .find(|s| {
1656                let path = s
1657                    .path
1658                    .normalize()
1659                    .unwrap_or_else(|err| {
1660                        panic!("normalize path {} failed: {:?}", s.path.display(), err)
1661                    })
1662                    .into_path_buf();
1663                self.file_id(path.clone()).unwrap_or_else(|| {
1664                    panic!(
1665                        "file_id not found for path {} in file_ids_map {:?}",
1666                        path.display(),
1667                        self.file_ids_map()
1668                    )
1669                }) == file_id
1670            })
1671            .unwrap()
1672    }
1673}
1674
1675pub mod tls {
1676
1677    use scoped_tls::scoped_thread_local;
1678
1679    use super::Context;
1680    use crate::DefId;
1681
1682    scoped_thread_local!(pub static CONTEXT: Context);
1683    scoped_thread_local!(pub static CUR_ITEM: DefId);
1684
1685    pub fn with_cx<T, F>(f: F) -> T
1686    where
1687        F: FnOnce(&Context) -> T,
1688    {
1689        CONTEXT.with(|cx| f(cx))
1690    }
1691
1692    pub fn with_cur_item<T, F>(f: F) -> T
1693    where
1694        F: FnOnce(DefId) -> T,
1695    {
1696        CUR_ITEM.with(|def_id| f(*def_id))
1697    }
1698}
1699
1700#[cfg(test)]
1701mod tests {
1702    use std::{collections::HashMap, path::PathBuf, sync::Arc};
1703
1704    use anyhow::Result;
1705    use faststr::FastStr;
1706    use pilota::Bytes;
1707    use rustc_hash::{FxHashMap, FxHashSet};
1708
1709    use super::*;
1710    use crate::{
1711        middle::{
1712            ext::{FileExts, ItemExts},
1713            rir::{self, FieldKind, Message},
1714            ty::{CodegenTy, Ty, TyKind},
1715        },
1716        symbol::{Ident, Symbol},
1717    };
1718
1719    fn make_test_context() -> Context {
1720        let mode = Arc::new(Mode::SingleFile {
1721            file_path: PathBuf::from("dummy.rs"),
1722        });
1723        let services: Arc<[crate::IdlService]> =
1724            Arc::from(Vec::<crate::IdlService>::new().into_boxed_slice());
1725        Context {
1726            db: RootDatabase::default(),
1727            source: Source {
1728                source_type: SourceType::Thrift,
1729                services,
1730                mode: mode.clone(),
1731                path_resolver: Arc::new(DefaultPathResolver),
1732            },
1733            config: Config {
1734                change_case: false,
1735                split: false,
1736                with_descriptor: false,
1737                with_field_mask: false,
1738                touch_all: false,
1739                common_crate_name: "common".into(),
1740                with_comments: false,
1741            },
1742            cache: Cache {
1743                adjusts: Arc::new(DashMap::default()),
1744                mod_idxes: AHashMap::new(),
1745                codegen_items: Vec::new(),
1746                mod_items: AHashMap::new(),
1747                def_mod: HashMap::new(),
1748                mod_files: HashMap::new(),
1749                keep_unknown_fields: Arc::new(FxHashSet::default()),
1750                location_map: Arc::new(FxHashMap::default()),
1751                entry_map: Arc::new(HashMap::default()),
1752                plugin_gen: Arc::new(DashMap::default()),
1753                dedups: Vec::new(),
1754                names: FxHashMap::default(),
1755            },
1756        }
1757    }
1758
1759    #[test]
1760    fn collect_items_traverses_field_dependencies() {
1761        let file_id = FileId::from_u32(0);
1762        let root_id = DefId::from_u32(0);
1763        let dep_id = DefId::from_u32(1);
1764        let field_id = DefId::from_u32(2);
1765        let tag_id = TagId::from_u32(0);
1766
1767        let dep_message = Arc::new(rir::Item::Message(Message {
1768            name: Ident::from("Dep"),
1769            fields: Vec::new(),
1770            is_wrapper: false,
1771            item_exts: ItemExts::Thrift,
1772            leading_comments: FastStr::new(""),
1773            trailing_comments: FastStr::new(""),
1774        }));
1775
1776        let dep_node = rir::Node {
1777            file_id,
1778            kind: rir::NodeKind::Item(dep_message.clone()),
1779            parent: None,
1780            tags: tag_id,
1781            related_nodes: Vec::new(),
1782        };
1783
1784        let field_ty = Ty {
1785            kind: TyKind::Path(rir::Path {
1786                kind: rir::DefKind::Type,
1787                did: dep_id,
1788            }),
1789            tags_id: tag_id,
1790        };
1791
1792        let field = Arc::new(rir::Field {
1793            did: field_id,
1794            name: Ident::from("dep"),
1795            id: 1,
1796            ty: field_ty,
1797            kind: FieldKind::Required,
1798            tags_id: tag_id,
1799            default: None,
1800            item_exts: ItemExts::Thrift,
1801            leading_comments: FastStr::new(""),
1802            trailing_comments: FastStr::new(""),
1803        });
1804
1805        let root_message = Arc::new(rir::Item::Message(Message {
1806            name: Ident::from("Root"),
1807            fields: vec![field],
1808            is_wrapper: false,
1809            item_exts: ItemExts::Thrift,
1810            leading_comments: FastStr::new(""),
1811            trailing_comments: FastStr::new(""),
1812        }));
1813
1814        let root_node = rir::Node {
1815            file_id,
1816            kind: rir::NodeKind::Item(root_message.clone()),
1817            parent: None,
1818            tags: tag_id,
1819            related_nodes: Vec::new(),
1820        };
1821
1822        let mut nodes = FxHashMap::default();
1823        nodes.insert(root_id, root_node);
1824        nodes.insert(dep_id, dep_node);
1825
1826        let package = ItemPath::from(Arc::<[Symbol]>::from(
1827            Vec::<Symbol>::new().into_boxed_slice(),
1828        ));
1829        let file = rir::File {
1830            package,
1831            items: vec![root_id, dep_id],
1832            file_id,
1833            uses: Vec::new(),
1834            descriptor: Bytes::new(),
1835            extensions: FileExts::Thrift,
1836            comments: FastStr::new(""),
1837        };
1838
1839        let file_arc = Arc::new(file);
1840
1841        let mut file_ids_map = FxHashMap::default();
1842        let normalized = Arc::new(PathBuf::from("/tmp/test.thrift"));
1843        file_ids_map.insert(normalized.clone(), file_id);
1844
1845        let mut file_paths = FxHashMap::default();
1846        file_paths.insert(file_id, normalized);
1847
1848        let mut file_names = FxHashMap::default();
1849        file_names.insert(file_id, FastStr::from_static_str("test.thrift"));
1850
1851        let db = RootDatabase::default()
1852            .with_nodes(nodes)
1853            .with_files(vec![(file_id, file_arc)].into_iter())
1854            .with_file_ids_map(file_ids_map)
1855            .with_file_paths(file_paths)
1856            .with_file_names(file_names)
1857            .with_input_files(vec![file_id]);
1858
1859        let builder = ContextBuilder::new(
1860            db,
1861            Mode::SingleFile {
1862                file_path: PathBuf::from("/tmp/output.rs"),
1863            },
1864            vec![root_id],
1865        );
1866
1867        let collected = builder.collect_items(&[root_id]);
1868
1869        assert!(collected.contains(&root_id));
1870        assert!(collected.contains(&dep_id));
1871        assert_eq!(collected.len(), 2);
1872    }
1873
1874    #[test]
1875    fn lit_into_ty_handles_basic_literals() -> Result<()> {
1876        let cx = make_test_context();
1877
1878        let (expr, is_const) =
1879            cx.lit_into_ty(&Literal::String(Arc::from("hello")), &CodegenTy::FastStr)?;
1880        assert_eq!(&*expr, "::pilota::FastStr::from_static_str(\"hello\")");
1881        assert!(is_const);
1882
1883        let list_lit = Literal::List(vec![Literal::Int(1), Literal::Int(2)]);
1884        let vec_ty = CodegenTy::Vec(Arc::new(CodegenTy::I32));
1885        let (expr, is_const) = cx.lit_into_ty(&list_lit, &vec_ty)?;
1886        assert_eq!(&*expr, "::std::vec![1i32,2i32]");
1887        assert!(!is_const);
1888
1889        Ok(())
1890    }
1891
1892    #[test]
1893    fn convert_codegen_vec_owned_iter_clone() {
1894        let cx = make_test_context();
1895        let from_ty = CodegenTy::Vec(Arc::new(CodegenTy::Str));
1896        let to_ty = CodegenTy::Vec(Arc::new(CodegenTy::FastStr));
1897        let (expr, _) = cx
1898            .convert_codegen_ty_expr(FastStr::from_static_str("items"), &from_ty, &to_ty, true)
1899            .expect("vec conversion should succeed");
1900        let rendered = expr.to_string();
1901        assert!(rendered.contains(".iter()"));
1902        assert!(!rendered.contains("into_iter"));
1903        assert!(rendered.contains("::pilota::FastStr::from_static_str"));
1904    }
1905
1906    #[test]
1907    fn convert_codegen_set_owned_iter_clone() {
1908        let cx = make_test_context();
1909        let from_ty = CodegenTy::Set(Arc::new(CodegenTy::Str));
1910        let to_ty = CodegenTy::Set(Arc::new(CodegenTy::FastStr));
1911        let (expr, _) = cx
1912            .convert_codegen_ty_expr(
1913                FastStr::from_static_str("set_items"),
1914                &from_ty,
1915                &to_ty,
1916                true,
1917            )
1918            .expect("set conversion should succeed");
1919        let rendered = expr.to_string();
1920        assert!(rendered.contains(".iter()"));
1921        assert!(!rendered.contains("into_iter"));
1922        assert!(rendered.contains("::pilota::FastStr::from_static_str"));
1923    }
1924
1925    #[test]
1926    fn convert_codegen_map_owned_iter_clone() {
1927        let cx = make_test_context();
1928        let from_ty = CodegenTy::Map(Arc::new(CodegenTy::Str), Arc::new(CodegenTy::Str));
1929        let to_ty = CodegenTy::Map(Arc::new(CodegenTy::FastStr), Arc::new(CodegenTy::FastStr));
1930        let (expr, _) = cx
1931            .convert_codegen_ty_expr(
1932                FastStr::from_static_str("map_items"),
1933                &from_ty,
1934                &to_ty,
1935                true,
1936            )
1937            .expect("map conversion should succeed");
1938        let rendered = expr.to_string();
1939        assert!(rendered.contains(".iter()"));
1940        assert!(!rendered.contains("into_iter"));
1941        assert!(rendered.contains("::pilota::FastStr::from_static_str(k.clone())"));
1942        assert!(rendered.contains("::pilota::FastStr::from_static_str(v.clone())"));
1943    }
1944}