Skip to main content

duchess_reflect/
class_info.rs

1use std::{collections::BTreeMap, sync::Arc};
2
3use inflector::Inflector;
4use proc_macro2::{Delimiter, Ident, Span, TokenStream, TokenTree};
5use quote::quote_spanned;
6
7use crate::{
8    parse::{Parse, TextAccum},
9    upcasts::Upcasts,
10};
11
12/// Stores all the data about the classes/packages to be translated
13/// as well as whatever we have learned from reflection.
14#[derive(Debug)]
15pub struct RootMap {
16    pub subpackages: BTreeMap<Id, SpannedPackageInfo>,
17    pub classes: BTreeMap<DotId, Arc<ClassInfo>>,
18    pub upcasts: Upcasts,
19}
20
21impl RootMap {
22    /// Finds the class with the given name (if present).
23    pub fn find_class(&self, cn: &DotId) -> Option<&Arc<ClassInfo>> {
24        self.classes.get(cn)
25    }
26
27    /// Finds the package with the given name (if present).
28    pub fn find_package(&self, ids: &[Id]) -> Option<&SpannedPackageInfo> {
29        let (p0, ps) = ids.split_first().unwrap();
30        self.subpackages.get(p0)?.find_subpackage(ps)
31    }
32
33    pub fn to_packages(&self) -> impl Iterator<Item = &SpannedPackageInfo> {
34        self.subpackages.values()
35    }
36
37    /// Find the names of all classes contained within.
38    pub fn class_names(&self) -> Vec<DotId> {
39        self.classes.keys().cloned().collect()
40    }
41}
42
43#[derive(Debug)]
44pub struct SpannedPackageInfo {
45    pub name: Id,
46    pub span: Span,
47    pub subpackages: BTreeMap<Id, SpannedPackageInfo>,
48    pub classes: Vec<DotId>,
49}
50
51impl SpannedPackageInfo {
52    /// Find a (sub)package given its relative name
53    pub fn find_subpackage(&self, ids: &[Id]) -> Option<&SpannedPackageInfo> {
54        let Some((p0, ps)) = ids.split_first() else {
55            return Some(self);
56        };
57
58        self.subpackages.get(p0)?.find_subpackage(ps)
59    }
60
61    /// Finds a class in this package with the given name (if any)
62    pub fn find_class(&self, cn: &Id) -> Option<&DotId> {
63        self.classes.iter().find(|c| c.is_class(cn))
64    }
65}
66
67#[derive(Debug)]
68pub enum ClassDecl {
69    /// User wrote `class Foo { * }`
70    Reflected(ReflectedClassInfo),
71
72    /// User wrote `class Foo { ... }` with full details.
73    Specified(ClassInfo),
74}
75
76impl Parse for ClassDecl {
77    fn parse(p: &mut crate::parse::Parser) -> syn::Result<Option<Self>> {
78        // Look for a keyword that could start a class definition.
79        let Some(t0) = p.peek_token() else {
80            return Ok(None);
81        };
82        match t0 {
83            TokenTree::Ident(i) => {
84                static START_KEYWORDS: &[&str] = &[
85                    "class",
86                    "public",
87                    "final",
88                    "abstract",
89                    "interface",
90                    "enum",
91                    "record",
92                ];
93                let s = i.to_string();
94                if !START_KEYWORDS.contains(&s.as_str()) {
95                    return Ok(None);
96                }
97            }
98            _ => return Ok(None),
99        }
100
101        // Accumulate tokens until we see a braced block `{}` that is the class body.
102        let t0 = p.eat_token().unwrap();
103        let mut accum = TextAccum::new(p, t0);
104        while let Some(t1) = accum.accum() {
105            match t1 {
106                TokenTree::Group(d) if d.delimiter() == Delimiter::Brace => {
107                    break;
108                }
109                _ => {}
110            }
111        }
112
113        // Parse the text with LALRPOP.
114        let (text, span) = accum.into_accumulated_result();
115        let r = javap::parse_class_decl(span, &text)?;
116        Ok(Some(r))
117    }
118
119    fn description() -> String {
120        format!("class definition (copy/paste the output from `javap -public`)")
121    }
122}
123
124#[derive(Clone, Debug)]
125pub struct ReflectedClassInfo {
126    pub span: Span,
127    #[allow(dead_code)] // FIXME: replace with `#[expect]` once that stabilizes
128    pub flags: Flags,
129    pub name: DotId,
130    pub kind: ClassKind,
131}
132
133#[derive(Clone, Debug)]
134pub struct ClassInfo {
135    pub span: Span,
136    #[allow(dead_code)] // FIXME: replace with `#[expect]` once that stabilizes
137    pub flags: Flags,
138    pub name: DotId,
139    pub kind: ClassKind,
140    pub generics: Vec<Generic>,
141    pub extends: Vec<ClassRef>,
142    pub implements: Vec<ClassRef>,
143    pub constructors: Vec<Constructor>,
144    pub fields: Vec<Field>,
145    pub methods: Vec<Method>,
146}
147
148impl ClassInfo {
149    pub fn parse(text: &str, span: Span) -> syn::Result<ClassInfo> {
150        javap::parse_class_info(span, &text)
151    }
152
153    pub fn this_ref(&self) -> ClassRef {
154        ClassRef {
155            name: self.name.clone(),
156            generics: self
157                .generics
158                .iter()
159                .map(|g| RefType::TypeParameter(g.id.clone()))
160                .collect(),
161        }
162    }
163
164    /// Indicates whether a member with the given privacy level should be reflected in Rust.
165    /// We always mirror things declared as public.
166    /// In classes, the default privacy indicates "package level" visibility and we do not mirror.
167    /// In interfaces, the default privacy indicates "public" visibility and we DO mirror.
168    pub fn should_mirror_in_rust(&self, privacy: Privacy) -> bool {
169        match (privacy, self.kind) {
170            (Privacy::Public, _) | (Privacy::Default, ClassKind::Interface) => true,
171
172            (Privacy::Protected, _)
173            | (Privacy::Private, _)
174            | (Privacy::Default, ClassKind::Class) => false,
175        }
176    }
177
178    pub fn generics_scope(&self) -> GenericsScope<'_> {
179        GenericsScope::Generics(&self.generics, &GenericsScope::Empty)
180    }
181}
182
183#[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Debug)]
184pub struct Generic {
185    pub id: Id,
186    pub extends: Vec<ClassRef>,
187}
188
189impl Generic {
190    pub fn to_ident(&self, span: Span) -> Ident {
191        self.id.to_ident(span)
192    }
193}
194
195impl std::fmt::Display for Generic {
196    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197        write!(f, "{}", self.id)?;
198        if let Some((e0, e1)) = self.extends.split_first() {
199            write!(f, " extends {e0}")?;
200            for ei in e1 {
201                write!(f, " & {ei}")?;
202            }
203        }
204        Ok(())
205    }
206}
207
208#[derive(Eq, Ord, PartialEq, PartialOrd, Copy, Clone, Debug)]
209pub enum ClassKind {
210    Class,
211    Interface,
212}
213
214#[derive(Eq, Ord, PartialEq, PartialOrd, Copy, Clone, Debug)]
215pub struct Flags {
216    pub privacy: Privacy,
217    pub is_final: bool,
218    pub is_synchronized: bool,
219    pub is_native: bool,
220    pub is_abstract: bool,
221    pub is_static: bool,
222    pub is_default: bool,
223    pub is_transient: bool,
224    pub is_volatile: bool,
225}
226
227impl Flags {
228    pub fn new(p: Privacy) -> Self {
229        Flags {
230            privacy: p,
231            is_final: false,
232            is_synchronized: false,
233            is_native: false,
234            is_abstract: false,
235            is_static: false,
236            is_default: false,
237            is_transient: false,
238            is_volatile: false,
239        }
240    }
241}
242
243#[derive(Eq, Ord, PartialEq, PartialOrd, Copy, Clone, Debug)]
244pub enum Privacy {
245    Public,
246    Protected,
247    Private,
248
249    /// NB: The default privacy depends on context.
250    /// In a class, it is package.
251    /// In an interface, it is public.
252    Default,
253}
254
255impl std::fmt::Display for Privacy {
256    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
257        match self {
258            Privacy::Public => write!(f, "`public`"),
259            Privacy::Protected => write!(f, "`protected`"),
260            Privacy::Private => write!(f, "`private`"),
261            Privacy::Default => write!(f, "default privacy"),
262        }
263    }
264}
265
266#[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Debug)]
267pub enum MemberFunction {
268    Constructor(Constructor),
269    Method(Method),
270}
271
272#[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Debug)]
273pub struct Constructor {
274    pub flags: Flags,
275    pub generics: Vec<Generic>,
276    pub argument_tys: Vec<Type>,
277    pub throws: Vec<ClassRef>,
278}
279
280impl Constructor {
281    pub fn to_method_sig(&self, class: &ClassInfo) -> MethodSig {
282        MethodSig {
283            name: class.name.class_name().clone(),
284            generics: self.generics.clone(),
285            argument_tys: self.argument_tys.clone(),
286        }
287    }
288
289    /// Returns the JVM descriptor script for the constructor.
290    ///
291    /// # Parameters
292    ///
293    /// * `ctx` is the generics scope of the class.
294    pub fn descriptor(&self, ctx: &GenericsScope<'_>) -> String {
295        let ctx = &ctx.nest(&self.generics);
296        format!(
297            "({})V",
298            self.argument_tys
299                .iter()
300                .map(|a| a.descriptor(ctx))
301                .collect::<String>()
302        )
303    }
304}
305
306#[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Debug)]
307pub struct Field {
308    pub flags: Flags,
309    pub name: Id,
310    pub ty: Type,
311}
312
313#[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Debug)]
314pub struct Method {
315    pub flags: Flags,
316    pub name: Id,
317    pub generics: Vec<Generic>,
318    pub argument_tys: Vec<Type>,
319    pub return_ty: Option<Type>,
320    pub throws: Vec<ClassRef>,
321}
322
323impl Method {
324    pub fn to_method_sig(&self) -> MethodSig {
325        MethodSig {
326            name: self.name.clone(),
327            generics: self.generics.clone(),
328            argument_tys: self.argument_tys.clone(),
329        }
330    }
331
332    /// Returns the JVM descriptor for the method.
333    ///
334    /// # Parameters
335    ///
336    /// * `ctx` is the generics scope of the class.
337    pub fn descriptor(&self, ctx: &GenericsScope<'_>) -> String {
338        let ctx = &ctx.nest(&self.generics);
339        format!(
340            "({}){}",
341            self.argument_tys
342                .iter()
343                .map(|a| a.descriptor(ctx))
344                .collect::<String>(),
345            self.return_ty
346                .as_ref()
347                .map(|r| r.descriptor(ctx))
348                .unwrap_or_else(|| format!("V")),
349        )
350    }
351}
352
353/// Signature of a single method in a class;
354/// identifies the method precisely enough
355/// to select from one of many overloaded methods.
356#[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Debug)]
357pub struct MethodSig {
358    pub name: Id,
359    pub generics: Vec<Generic>,
360    pub argument_tys: Vec<Type>,
361}
362
363impl std::fmt::Display for MethodSig {
364    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
365        if let Some((generic_id0, generic_ids)) = self.generics.split_first() {
366            write!(f, "<{generic_id0}")?;
367            for id in generic_ids {
368                write!(f, ", {id}")?;
369            }
370            write!(f, "> ")?;
371        }
372        write!(f, "{}(", self.name)?;
373        if let Some((ty0, tys)) = self.argument_tys.split_first() {
374            write!(f, "{ty0}")?;
375            for ty in tys {
376                write!(f, ", {ty}")?;
377            }
378        }
379        write!(f, ")")?;
380        Ok(())
381    }
382}
383
384#[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Debug)]
385pub struct ClassRef {
386    pub name: DotId,
387    pub generics: Vec<RefType>,
388}
389
390impl std::fmt::Display for ClassRef {
391    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
392        write!(f, "{}", self.name)?;
393        if let Some((ty0, tys)) = self.generics.split_first() {
394            write!(f, "<{ty0}")?;
395            for ty in tys {
396                write!(f, ", {ty}")?;
397            }
398            write!(f, ">")?;
399        }
400        Ok(())
401    }
402}
403
404#[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Debug)]
405pub enum Type {
406    Ref(RefType),
407    Scalar(ScalarType),
408    Repeat(Arc<Type>),
409}
410
411impl From<ClassRef> for Type {
412    fn from(value: ClassRef) -> Self {
413        Type::Ref(RefType::Class(value))
414    }
415}
416
417impl std::fmt::Display for Type {
418    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
419        match self {
420            Type::Ref(t) => write!(f, "{t}"),
421            Type::Scalar(t) => write!(f, "{t}"),
422            Type::Repeat(t) => write!(f, "{t}..."),
423        }
424    }
425}
426
427impl Type {
428    pub fn is_scalar(&self) -> bool {
429        match self {
430            Type::Scalar(_) => true,
431            Type::Ref(_) | Type::Repeat(_) => false,
432        }
433    }
434
435    /// Convert a potentially repeating type to a non-repeating one.
436    /// Types like `T...` become an array `T[]`.
437    pub fn to_non_repeating(&self) -> NonRepeatingType {
438        match self {
439            Type::Ref(t) => NonRepeatingType::Ref(t.clone()),
440            Type::Scalar(t) => NonRepeatingType::Scalar(t.clone()),
441            Type::Repeat(t) => NonRepeatingType::Ref(RefType::Array(t.clone())),
442        }
443    }
444
445    /// Returns the JVM descriptor for this type, suitable for embedding a method descriptor.
446    ///
447    /// # Parameters
448    ///
449    /// * `ctx` is the generics scope where the type appears.
450    pub fn descriptor(&self, ctx: &GenericsScope<'_>) -> String {
451        self.to_non_repeating().descriptor(ctx)
452    }
453}
454
455/// Track generics currently in scope
456///
457/// In order to resolve descriptors for methods containing `<X extends Y>`, we need to know how `T` was declared.
458pub enum GenericsScope<'a> {
459    Empty,
460    Generics(&'a [Generic], &'a GenericsScope<'a>),
461}
462
463impl<'a> GenericsScope<'a> {
464    /// Find a generic within this scope by name
465    fn find(&self, ty: &Id) -> Option<&Generic> {
466        match self {
467            GenericsScope::Empty => None,
468            GenericsScope::Generics(g, inner) => g.iter().find(|g| &g.id == ty).or(inner.find(ty)),
469        }
470    }
471
472    /// Add an additional layer to this scope (e.g. combining generics from a class with generics from a method)
473    ///
474    /// The newly provided generics are higher priority than the inner generics (but
475    /// I don't think we can have namespace collisions here in Java anyway)
476    fn nest(&'a self, generics: &'a [Generic]) -> GenericsScope<'a> {
477        GenericsScope::Generics(generics, self)
478    }
479}
480
481/// A variant of type
482#[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Debug)]
483pub enum NonRepeatingType {
484    Ref(RefType),
485    Scalar(ScalarType),
486}
487
488impl NonRepeatingType {
489    /// Returns the JVM descriptor for this type, suitable for embedding a method descriptor.
490    ///
491    /// # Parameters
492    ///
493    /// * `ctx` is the generics scope where the type appears.
494    pub fn descriptor(&self, ctx: &GenericsScope<'_>) -> String {
495        match self {
496            NonRepeatingType::Ref(r) => match r {
497                RefType::Class(c) => format!("L{};", c.name.to_jni_name()),
498                RefType::Array(r) => format!("[{}", r.descriptor(ctx)),
499
500                RefType::TypeParameter(id) => {
501                    let generic = ctx.find(id).expect("generic did not exist.");
502                    match generic.extends.get(0) {
503                        Some(c) => format!("L{};", c.name.to_jni_name()),
504                        _ => format!("Ljava/lang/Object;"),
505                    }
506                }
507                RefType::Extends(_) | RefType::Super(_) | RefType::Wildcard => {
508                    format!("Ljava/lang/Object;")
509                }
510            },
511            NonRepeatingType::Scalar(s) => match s {
512                ScalarType::Int => format!("I"),
513                ScalarType::Long => format!("J"),
514                ScalarType::Short => format!("S"),
515                ScalarType::Byte => format!("B"),
516                ScalarType::F64 => format!("D"),
517                ScalarType::F32 => format!("F"),
518                ScalarType::Boolean => format!("Z"),
519                ScalarType::Char => format!("C"),
520            },
521        }
522    }
523}
524
525#[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Debug)]
526pub enum RefType {
527    Class(ClassRef),
528    Array(Arc<Type>),
529    TypeParameter(Id),
530    Extends(Arc<RefType>),
531    Super(Arc<RefType>),
532    Wildcard,
533}
534
535impl std::fmt::Display for RefType {
536    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
537        match self {
538            RefType::Class(c) => write!(f, "{c}"),
539            RefType::Array(e) => write!(f, "{e}[]"),
540            RefType::TypeParameter(id) => write!(f, "{id}"),
541            RefType::Extends(t) => write!(f, "? extends {t}"),
542            RefType::Super(t) => write!(f, "? super {t}"),
543            RefType::Wildcard => write!(f, "?"),
544        }
545    }
546}
547
548#[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Debug)]
549pub enum ScalarType {
550    Int,
551    Long,
552    Short,
553    Byte,
554    F64,
555    F32,
556    Boolean,
557    Char,
558}
559
560impl std::fmt::Display for ScalarType {
561    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
562        match self {
563            ScalarType::Int => write!(f, "int"),
564            ScalarType::Long => write!(f, "long"),
565            ScalarType::Short => write!(f, "long"),
566            ScalarType::Byte => write!(f, "byte"),
567            ScalarType::F64 => write!(f, "double"),
568            ScalarType::F32 => write!(f, "float"),
569            ScalarType::Boolean => write!(f, "boolean"),
570            ScalarType::Char => write!(f, "char"),
571        }
572    }
573}
574
575impl ScalarType {
576    pub fn to_tokens(&self, span: Span) -> TokenStream {
577        match self {
578            ScalarType::Char => quote_spanned!(span => u16),
579            ScalarType::Int => quote_spanned!(span => i32),
580            ScalarType::Long => quote_spanned!(span => i64),
581            ScalarType::Short => quote_spanned!(span => i16),
582            ScalarType::Byte => quote_spanned!(span => i8),
583            ScalarType::F64 => quote_spanned!(span => f64),
584            ScalarType::F32 => quote_spanned!(span => f32),
585            ScalarType::Boolean => quote_spanned!(span => bool),
586        }
587    }
588}
589
590/// A single identifier
591#[derive(Eq, Hash, Ord, PartialEq, PartialOrd, Clone, Debug)]
592pub struct Id {
593    pub data: String,
594}
595
596impl std::ops::Deref for Id {
597    type Target = String;
598
599    fn deref(&self) -> &String {
600        &self.data
601    }
602}
603
604impl From<String> for Id {
605    fn from(value: String) -> Self {
606        Id { data: value }
607    }
608}
609
610impl From<&str> for Id {
611    fn from(value: &str) -> Self {
612        Id {
613            data: value.to_owned(),
614        }
615    }
616}
617
618impl Id {
619    pub fn dot(self, s: &str) -> DotId {
620        DotId::from(self).dot(s)
621    }
622
623    pub fn to_ident(&self, span: Span) -> Ident {
624        let data = self.data.replace("$", "__");
625        // A legal Java identifier may be an illegal Rust identifier, in which case we'd 
626        // need to use raw syntax. 
627        let mut ident = syn::parse_str(data.as_str()).unwrap_or_else(|_| Ident::new_raw(&data, span));
628        ident.set_span(span);
629        ident
630    }
631
632    pub fn to_snake_case(&self) -> Self {
633        Self {
634            data: self.data.to_snake_case(),
635        }
636    }
637}
638
639impl std::fmt::Display for Id {
640    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
641        write!(f, "{}", self.data)
642    }
643}
644
645/// A dotted identifier
646#[derive(Eq, Hash, Ord, PartialEq, PartialOrd, Clone, Debug)]
647pub struct DotId {
648    /// Dotted components. Invariant: len >= 1.
649    ids: Vec<Id>,
650}
651
652impl From<Id> for DotId {
653    fn from(value: Id) -> Self {
654        DotId { ids: vec![value] }
655    }
656}
657
658impl From<&Id> for DotId {
659    fn from(value: &Id) -> Self {
660        DotId {
661            ids: vec![value.clone()],
662        }
663    }
664}
665
666impl FromIterator<Id> for DotId {
667    fn from_iter<T: IntoIterator<Item = Id>>(iter: T) -> Self {
668        let ids: Vec<Id> = iter.into_iter().collect();
669        assert!(ids.len() >= 1);
670        DotId { ids }
671    }
672}
673
674impl DotId {
675    pub fn new(package: &[Id], class: &Id) -> Self {
676        DotId {
677            ids: package
678                .iter()
679                .chain(std::iter::once(class))
680                .cloned()
681                .collect(),
682        }
683    }
684
685    pub fn object() -> Self {
686        Self::parse("java.lang.Object")
687    }
688
689    pub fn exception() -> Self {
690        Self::parse("java.lang.Exception")
691    }
692
693    pub fn runtime_exception() -> Self {
694        Self::parse("java.lang.RuntimeException")
695    }
696
697    pub fn throwable() -> Self {
698        Self::parse("java.lang.Throwable")
699    }
700
701    pub fn parse(s: impl AsRef<str>) -> DotId {
702        let s: &str = s.as_ref();
703        let ids: Vec<Id> = s.split(".").map(Id::from).collect();
704        assert!(ids.len() > 1, "bad input to DotId::parse: {s:?}");
705        DotId { ids }
706    }
707
708    pub fn dot(mut self, s: &str) -> DotId {
709        self.ids.push(Id::from(s));
710        self
711    }
712
713    pub fn is_class(&self, s: &Id) -> bool {
714        self.split().1 == s
715    }
716
717    /// returns the class name in JNI format with _'s escaped with _1
718    /// https://docs.oracle.com/en/java/javase/17/docs/specs/jni/design.html
719    pub fn to_jni_class_name(&self) -> Id {
720        self.split().1.data.replace("_", "_1").into()
721    }
722
723    pub fn class_name(&self) -> &Id {
724        self.split().1
725    }
726
727    /// returns the package in JNI format with _'s escaped with _1
728    /// https://docs.oracle.com/en/java/javase/17/docs/specs/jni/design.html
729    pub fn to_jni_package(&self) -> String {
730        self.split()
731            .0
732            .iter()
733            .map(|id| id.data.replace("_", "_1"))
734            .collect::<Vec<_>>()
735            .join("_")
736    }
737
738    /// Split and return the (package name, class name) pair.
739    pub fn split(&self) -> (&[Id], &Id) {
740        let (name, package) = self.ids.split_last().unwrap();
741        (package, name)
742    }
743
744    /// Returns a name like `java/lang/Object`
745    pub fn to_jni_name(&self) -> String {
746        self.ids
747            .iter()
748            .map(|id| &id[..])
749            .collect::<Vec<_>>()
750            .join("/")
751    }
752
753    /// Returns a token stream like `java::lang::Object`
754    pub fn to_module_name(&self, span: Span) -> TokenStream {
755        let (package_names, struct_name) = self.split();
756        let struct_ident = struct_name.to_ident(span);
757        let package_idents: Vec<Ident> = package_names.iter().map(|n| n.to_ident(span)).collect();
758        quote_spanned!(span => #(#package_idents ::)* #struct_ident)
759    }
760}
761
762impl std::ops::Deref for DotId {
763    type Target = [Id];
764
765    fn deref(&self) -> &Self::Target {
766        &self.ids
767    }
768}
769
770impl std::fmt::Display for DotId {
771    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
772        let (package, class) = self.split();
773        for id in package {
774            write!(f, "{id}.")?;
775        }
776        write!(f, "{class}")?;
777        Ok(())
778    }
779}
780
781mod javap;